You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2015/01/08 14:17:28 UTC

[08/13] syncope git commit: [SYNCOPE-620] server logic in, tests missing

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UserLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UserLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UserLogic.java
new file mode 100644
index 0000000..870a075
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UserLogic.java
@@ -0,0 +1,534 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.server.logic;
+
+import org.apache.syncope.server.security.UnauthorizedRoleException;
+import java.lang.reflect.Method;
+import java.security.AccessControlException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.mod.AttrMod;
+import org.apache.syncope.common.lib.mod.StatusMod;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.BulkAction;
+import org.apache.syncope.common.lib.to.BulkActionResult;
+import org.apache.syncope.common.lib.to.BulkActionResult.Status;
+import org.apache.syncope.common.lib.to.MembershipTO;
+import org.apache.syncope.common.lib.to.PropagationStatus;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.SubjectType;
+import org.apache.syncope.persistence.api.RoleEntitlementUtil;
+import org.apache.syncope.persistence.api.dao.ConfDAO;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.RoleDAO;
+import org.apache.syncope.persistence.api.dao.SubjectSearchDAO;
+import org.apache.syncope.persistence.api.dao.UserDAO;
+import org.apache.syncope.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.persistence.api.entity.role.Role;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.apache.syncope.provisioning.api.AttributableTransformer;
+import org.apache.syncope.provisioning.api.UserProvisioningManager;
+import org.apache.syncope.provisioning.api.propagation.PropagationManager;
+import org.apache.syncope.provisioning.api.propagation.PropagationTaskExecutor;
+import org.apache.syncope.server.logic.data.UserDataBinder;
+import org.apache.syncope.server.security.AuthContextUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.interceptor.TransactionInterceptor;
+
+/**
+ * Note that this controller does not extend {@link AbstractTransactionalLogic}, hence does not provide any
+ * Spring's Transactional logic at class level.
+ */
+@Component
+public class UserLogic extends AbstractSubjectLogic<UserTO, UserMod> {
+
+    @Autowired
+    protected UserDAO userDAO;
+
+    @Autowired
+    protected RoleDAO roleDAO;
+
+    @Autowired
+    protected SubjectSearchDAO searchDAO;
+
+    @Autowired
+    protected ConfDAO confDAO;
+
+    @Autowired
+    protected UserDataBinder binder;
+
+    @Autowired
+    protected PropagationManager propagationManager;
+
+    @Autowired
+    protected PropagationTaskExecutor taskExecutor;
+
+    @Autowired
+    protected AttributableTransformer attrTransformer;
+
+    @Autowired
+    protected UserProvisioningManager provisioningManager;
+
+    @Transactional(readOnly = true)
+    public boolean isSelfRegAllowed() {
+        return confDAO.find("selfRegistration.allowed", "false").getValues().get(0).getBooleanValue();
+    }
+
+    @Transactional(readOnly = true)
+    public boolean isPwdResetAllowed() {
+        return confDAO.find("passwordReset.allowed", "false").getValues().get(0).getBooleanValue();
+    }
+
+    @Transactional(readOnly = true)
+    public boolean isPwdResetRequiringSecurityQuestions() {
+        return confDAO.find("passwordReset.securityQuestion", "true").getValues().get(0).getBooleanValue();
+    }
+
+    @PreAuthorize("hasRole('USER_READ')")
+    public String getUsername(final Long key) {
+        return binder.getUserTO(key).getUsername();
+    }
+
+    @PreAuthorize("hasRole('USER_READ')")
+    public Long getKey(final String username) {
+        return binder.getUserTO(username).getKey();
+    }
+
+    @PreAuthorize("hasRole('USER_LIST')")
+    @Transactional(readOnly = true, rollbackFor = { Throwable.class })
+    @Override
+    public int count() {
+        return userDAO.count(RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames()));
+    }
+
+    @PreAuthorize("hasRole('USER_LIST')")
+    @Transactional(readOnly = true, rollbackFor = { Throwable.class })
+    @Override
+    public int searchCount(final SearchCond searchCondition) {
+        return searchDAO.count(RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames()),
+                searchCondition, SubjectType.USER);
+    }
+
+    @PreAuthorize("hasRole('USER_LIST')")
+    @Transactional(readOnly = true, rollbackFor = { Throwable.class })
+    @Override
+    public List<UserTO> list(final int page, final int size, final List<OrderByClause> orderBy) {
+        Set<Long> adminRoleIds = RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames());
+
+        List<User> users = userDAO.findAll(adminRoleIds, page, size, orderBy);
+        List<UserTO> userTOs = new ArrayList<UserTO>(users.size());
+        for (User user : users) {
+            userTOs.add(binder.getUserTO(user));
+        }
+
+        return userTOs;
+    }
+
+    @PreAuthorize("isAuthenticated() "
+            + "and not(hasRole(T(org.apache.syncope.common.lib.SyncopeConstants).ANONYMOUS_ENTITLEMENT))")
+    @Transactional(readOnly = true)
+    public UserTO readSelf() {
+        return binder.getAuthenticatedUserTO();
+    }
+
+    @PreAuthorize("hasRole('USER_READ')")
+    @Transactional(readOnly = true)
+    @Override
+    public UserTO read(final Long key) {
+        return binder.getUserTO(key);
+    }
+
+    @PreAuthorize("hasRole('USER_LIST')")
+    @Transactional(readOnly = true)
+    @Override
+    public List<UserTO> search(final SearchCond searchCondition, final int page, final int size,
+            final List<OrderByClause> orderBy) {
+
+        final List<User> matchingUsers = searchDAO.search(
+                RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames()),
+                searchCondition, page, size, orderBy, SubjectType.USER);
+
+        final List<UserTO> result = new ArrayList<>(matchingUsers.size());
+        for (User user : matchingUsers) {
+            result.add(binder.getUserTO(user));
+        }
+
+        return result;
+    }
+
+    @PreAuthorize("isAnonymous() or hasRole(T(org.apache.syncope.common.lib.SyncopeConstants).ANONYMOUS_ENTITLEMENT)")
+    public UserTO createSelf(final UserTO userTO, final boolean storePassword) {
+        return doCreate(userTO, storePassword);
+    }
+
+    @PreAuthorize("hasRole('USER_CREATE')")
+    public UserTO create(final UserTO userTO, final boolean storePassword) {
+        Set<Long> requestRoleIds = new HashSet<Long>(userTO.getMemberships().size());
+        for (MembershipTO membership : userTO.getMemberships()) {
+            requestRoleIds.add(membership.getRoleId());
+        }
+        Set<Long> adminRoleIds = RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames());
+        requestRoleIds.removeAll(adminRoleIds);
+        if (!requestRoleIds.isEmpty()) {
+            throw new UnauthorizedRoleException(requestRoleIds);
+        }
+
+        return doCreate(userTO, storePassword);
+    }
+
+    protected UserTO doCreate(final UserTO userTO, final boolean storePassword) {
+        // Attributable transformation (if configured)
+        UserTO actual = attrTransformer.transform(userTO);
+        LOG.debug("Transformed: {}", actual);
+
+        Map.Entry<Long, List<PropagationStatus>> created = provisioningManager.create(actual, storePassword);
+
+        final UserTO savedTO = binder.getUserTO(created.getKey());
+        savedTO.getPropagationStatusTOs().addAll(created.getValue());
+        return savedTO;
+    }
+
+    @PreAuthorize("isAuthenticated() "
+            + "and not(hasRole(T(org.apache.syncope.common.lib.SyncopeConstants).ANONYMOUS_ENTITLEMENT))")
+    public UserTO updateSelf(final UserMod userMod) {
+        UserTO userTO = binder.getAuthenticatedUserTO();
+
+        if (userTO.getKey() != userMod.getKey()) {
+            throw new AccessControlException("Not allowed for user with key " + userMod.getKey());
+        }
+
+        return update(userMod);
+    }
+
+    @PreAuthorize("hasRole('USER_UPDATE')")
+    @Override
+    public UserTO update(final UserMod userMod) {
+        // AttributableMod transformation (if configured)
+        UserMod actual = attrTransformer.transform(userMod);
+        LOG.debug("Transformed: {}", actual);
+
+        // SYNCOPE-501: check if there are memberships to be removed with virtual attributes assigned
+        boolean removeMemberships = false;
+        for (Long membershipId : actual.getMembershipsToRemove()) {
+            if (!binder.fillMembershipVirtual(
+                    null,
+                    null,
+                    membershipId,
+                    Collections.<String>emptySet(),
+                    Collections.<AttrMod>emptySet(),
+                    true).isEmpty()) {
+
+                removeMemberships = true;
+            }
+        }
+
+        Map.Entry<Long, List<PropagationStatus>> updated = provisioningManager.update(actual, removeMemberships);
+
+        final UserTO updatedTO = binder.getUserTO(updated.getKey());
+        updatedTO.getPropagationStatusTOs().addAll(updated.getValue());
+        return updatedTO;
+    }
+
+    protected Map.Entry<Long, List<PropagationStatus>> setStatusOnWfAdapter(final User user,
+            final StatusMod statusMod) {
+        Map.Entry<Long, List<PropagationStatus>> updated;
+
+        switch (statusMod.getType()) {
+            case SUSPEND:
+                updated = provisioningManager.suspend(user, statusMod);
+                break;
+
+            case REACTIVATE:
+                updated = provisioningManager.reactivate(user, statusMod);
+                break;
+
+            case ACTIVATE:
+            default:
+                updated = provisioningManager.activate(user, statusMod);
+                break;
+
+        }
+
+        return updated;
+    }
+
+    @PreAuthorize("hasRole('USER_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    public UserTO status(final StatusMod statusMod) {
+        User user = userDAO.authFecthUser(statusMod.getId());
+
+        Map.Entry<Long, List<PropagationStatus>> updated = setStatusOnWfAdapter(user, statusMod);
+        final UserTO savedTO = binder.getUserTO(updated.getKey());
+        savedTO.getPropagationStatusTOs().addAll(updated.getValue());
+        return savedTO;
+    }
+
+    @PreAuthorize("isAnonymous() or hasRole(T(org.apache.syncope.common.lib.SyncopeConstants).ANONYMOUS_ENTITLEMENT)")
+    @Transactional
+    public void requestPasswordReset(final String username, final String securityAnswer) {
+        if (username == null) {
+            throw new NotFoundException("Null username");
+        }
+
+        User user = userDAO.find(username);
+        if (user == null) {
+            throw new NotFoundException("User " + username);
+        }
+
+        if (isPwdResetRequiringSecurityQuestions()
+                && (securityAnswer == null || !securityAnswer.equals(user.getSecurityAnswer()))) {
+
+            throw SyncopeClientException.build(ClientExceptionType.InvalidSecurityAnswer);
+        }
+
+        provisioningManager.requestPasswordReset(user.getKey());
+    }
+
+    @PreAuthorize("isAnonymous() or hasRole(T(org.apache.syncope.common.lib.SyncopeConstants).ANONYMOUS_ENTITLEMENT)")
+    @Transactional
+    public void confirmPasswordReset(final String token, final String password) {
+        User user = userDAO.findByToken(token);
+        if (user == null) {
+            throw new NotFoundException("User with token " + token);
+        }
+        provisioningManager.confirmPasswordReset(user, token, password);
+    }
+
+    @PreAuthorize("isAuthenticated() "
+            + "and not(hasRole(T(org.apache.syncope.common.lib.SyncopeConstants).ANONYMOUS_ENTITLEMENT))")
+    public UserTO deleteSelf() {
+        UserTO userTO = binder.getAuthenticatedUserTO();
+
+        return delete(userTO.getKey());
+    }
+
+    @PreAuthorize("hasRole('USER_DELETE')")
+    @Override
+    public UserTO delete(final Long key) {
+        List<Role> ownedRoles = roleDAO.findOwnedByUser(key);
+        if (!ownedRoles.isEmpty()) {
+            List<String> owned = new ArrayList<String>(ownedRoles.size());
+            for (Role role : ownedRoles) {
+                owned.add(role.getKey() + " " + role.getName());
+            }
+
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.RoleOwnership);
+            sce.getElements().addAll(owned);
+            throw sce;
+        }
+
+        List<PropagationStatus> statuses = provisioningManager.delete(key);
+
+        final UserTO deletedTO;
+        User deleted = userDAO.find(key);
+        if (deleted == null) {
+            deletedTO = new UserTO();
+            deletedTO.setKey(key);
+        } else {
+            deletedTO = binder.getUserTO(key);
+        }
+        deletedTO.getPropagationStatusTOs().addAll(statuses);
+
+        return deletedTO;
+    }
+
+    @PreAuthorize("(hasRole('USER_DELETE') and #bulkAction.operation == #bulkAction.operation.DELETE) or "
+            + "(hasRole('USER_UPDATE') and "
+            + "(#bulkAction.operation == #bulkAction.operation.REACTIVATE or "
+            + "#bulkAction.operation == #bulkAction.operation.SUSPEND))")
+    public BulkActionResult bulk(final BulkAction bulkAction) {
+        BulkActionResult res = new BulkActionResult();
+
+        switch (bulkAction.getOperation()) {
+            case DELETE:
+                for (String key : bulkAction.getTargets()) {
+                    try {
+                        res.add(delete(Long.valueOf(key)).getKey(), Status.SUCCESS);
+                    } catch (Exception e) {
+                        LOG.error("Error performing delete for user {}", key, e);
+                        res.add(key, Status.FAILURE);
+                    }
+                }
+                break;
+
+            case SUSPEND:
+                for (String key : bulkAction.getTargets()) {
+                    StatusMod statusMod = new StatusMod();
+                    statusMod.setId(Long.valueOf(key));
+                    statusMod.setType(StatusMod.ModType.SUSPEND);
+                    try {
+                        res.add(status(statusMod).getKey(), Status.SUCCESS);
+                    } catch (Exception e) {
+                        LOG.error("Error performing suspend for user {}", key, e);
+                        res.add(key, Status.FAILURE);
+                    }
+                }
+                break;
+
+            case REACTIVATE:
+                for (String key : bulkAction.getTargets()) {
+                    StatusMod statusMod = new StatusMod();
+                    statusMod.setId(Long.valueOf(key));
+                    statusMod.setType(StatusMod.ModType.REACTIVATE);
+                    try {
+                        res.add(status(statusMod).getKey(), Status.SUCCESS);
+                    } catch (Exception e) {
+                        LOG.error("Error performing reactivate for user {}", key, e);
+                        res.add(key, Status.FAILURE);
+                    }
+                }
+                break;
+
+            default:
+        }
+
+        return res;
+    }
+
+    @PreAuthorize("hasRole('USER_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public UserTO unlink(final Long key, final Collection<String> resources) {
+        final UserMod userMod = new UserMod();
+        userMod.setKey(key);
+        userMod.getResourcesToRemove().addAll(resources);
+        Long updatedId = provisioningManager.unlink(userMod);
+
+        return binder.getUserTO(updatedId);
+    }
+
+    @PreAuthorize("hasRole('USER_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public UserTO link(final Long key, final Collection<String> resources) {
+        final UserMod userMod = new UserMod();
+        userMod.setKey(key);
+        userMod.getResourcesToAdd().addAll(resources);
+        return binder.getUserTO(provisioningManager.link(userMod));
+    }
+
+    @PreAuthorize("hasRole('USER_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public UserTO unassign(final Long key, final Collection<String> resources) {
+        final UserMod userMod = new UserMod();
+        userMod.setKey(key);
+        userMod.getResourcesToRemove().addAll(resources);
+        return update(userMod);
+    }
+
+    @PreAuthorize("hasRole('USER_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public UserTO assign(
+            final Long key,
+            final Collection<String> resources,
+            final boolean changepwd,
+            final String password) {
+
+        final UserMod userMod = new UserMod();
+        userMod.setKey(key);
+        userMod.getResourcesToAdd().addAll(resources);
+
+        if (changepwd) {
+            StatusMod statusMod = new StatusMod();
+            statusMod.setOnSyncope(false);
+            statusMod.getResourceNames().addAll(resources);
+            userMod.setPwdPropRequest(statusMod);
+            userMod.setPassword(password);
+        }
+
+        return update(userMod);
+    }
+
+    @PreAuthorize("hasRole('USER_UPDATE')")
+    @Transactional(rollbackFor = { Throwable.class })
+    @Override
+    public UserTO deprovision(final Long key, final Collection<String> resources) {
+        final User user = userDAO.authFecthUser(key);
+
+        List<PropagationStatus> statuses = provisioningManager.deprovision(key, resources);
+
+        final UserTO updatedUserTO = binder.getUserTO(user);
+        updatedUserTO.getPropagationStatusTOs().addAll(statuses);
+        return updatedUserTO;
+    }
+
+    @PreAuthorize("hasRole('USER_UPDATE')")
+    @Transactional(readOnly = true)
+    @Override
+    public UserTO provision(
+            final Long key,
+            final Collection<String> resources,
+            final boolean changePwd,
+            final String password) {
+
+        final UserTO original = binder.getUserTO(key);
+
+        //trick: assign and retrieve propagation statuses ...
+        original.getPropagationStatusTOs().addAll(
+                assign(key, resources, changePwd, password).getPropagationStatusTOs());
+
+        // .... rollback.
+        TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
+        return original;
+    }
+
+    @Override
+    protected UserTO resolveReference(final Method method, final Object... args) throws UnresolvedReferenceException {
+        Object id = null;
+
+        if (!"confirmPasswordReset".equals(method.getName()) && ArrayUtils.isNotEmpty(args)) {
+            for (int i = 0; id == null && i < args.length; i++) {
+                if (args[i] instanceof Long) {
+                    id = (Long) args[i];
+                } else if (args[i] instanceof String) {
+                    id = (String) args[i];
+                } else if (args[i] instanceof UserTO) {
+                    id = ((UserTO) args[i]).getKey();
+                } else if (args[i] instanceof UserMod) {
+                    id = ((UserMod) args[i]).getKey();
+                }
+            }
+        }
+
+        if ((id != null) && !id.equals(0l)) {
+            try {
+                return id instanceof Long ? binder.getUserTO((Long) id) : binder.getUserTO((String) id);
+            } catch (Throwable ignore) {
+                LOG.debug("Unresolved reference", ignore);
+                throw new UnresolvedReferenceException(ignore);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UserWorkflowLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UserWorkflowLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UserWorkflowLogic.java
new file mode 100644
index 0000000..08b5e71
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UserWorkflowLogic.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.server.logic;
+
+import java.lang.reflect.Method;
+import java.util.AbstractMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.syncope.common.lib.mod.AbstractAttributableMod;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.to.WorkflowFormTO;
+import org.apache.syncope.persistence.api.dao.UserDAO;
+import org.apache.syncope.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.apache.syncope.provisioning.api.propagation.PropagationManager;
+import org.apache.syncope.provisioning.api.propagation.PropagationTaskExecutor;
+import org.apache.syncope.server.logic.data.UserDataBinder;
+import org.apache.syncope.server.workflow.api.UserWorkflowAdapter;
+import org.apache.syncope.provisioning.api.WorkflowResult;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class UserWorkflowLogic extends AbstractTransactionalLogic<WorkflowFormTO> {
+
+    @Autowired
+    private UserWorkflowAdapter uwfAdapter;
+
+    @Autowired
+    private PropagationManager propagationManager;
+
+    @Autowired
+    private PropagationTaskExecutor taskExecutor;
+
+    @Autowired
+    private UserDataBinder binder;
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @PreAuthorize("hasRole('WORKFLOW_FORM_CLAIM')")
+    @Transactional(rollbackFor = { Throwable.class })
+    public WorkflowFormTO claimForm(final String taskId) {
+        return uwfAdapter.claimForm(taskId);
+    }
+
+    @PreAuthorize("hasRole('USER_UPDATE')")
+    public UserTO executeWorkflowTask(final UserTO userTO, final String taskId) {
+        WorkflowResult<Long> updated = uwfAdapter.execute(userTO, taskId);
+
+        UserMod userMod = new UserMod();
+        userMod.setKey(userTO.getKey());
+
+        List<PropagationTask> tasks = propagationManager.getUserUpdateTaskIds(
+                new WorkflowResult<Map.Entry<UserMod, Boolean>>(
+                        new AbstractMap.SimpleEntry<UserMod, Boolean>(userMod, null),
+                        updated.getPropByRes(), updated.getPerformedTasks()));
+
+        taskExecutor.execute(tasks);
+
+        return binder.getUserTO(updated.getResult());
+    }
+
+    @PreAuthorize("hasRole('WORKFLOW_FORM_READ') and hasRole('USER_READ')")
+    @Transactional(rollbackFor = { Throwable.class })
+    public WorkflowFormTO getFormForUser(final Long key) {
+        User user = userDAO.authFecthUser(key);
+        return uwfAdapter.getForm(user.getWorkflowId());
+    }
+
+    @PreAuthorize("hasRole('WORKFLOW_FORM_LIST')")
+    @Transactional(rollbackFor = { Throwable.class })
+    public List<WorkflowFormTO> getForms() {
+        return uwfAdapter.getForms();
+    }
+
+    @PreAuthorize("hasRole('WORKFLOW_FORM_READ') and hasRole('USER_READ')")
+    @Transactional(rollbackFor = { Throwable.class })
+    public List<WorkflowFormTO> getForms(final Long key, final String formName) {
+        User user = userDAO.authFecthUser(key);
+        return uwfAdapter.getForms(user.getWorkflowId(), formName);
+    }
+
+    @PreAuthorize("hasRole('WORKFLOW_FORM_SUBMIT')")
+    @Transactional(rollbackFor = { Throwable.class })
+    public UserTO submitForm(final WorkflowFormTO form) {
+        WorkflowResult<? extends AbstractAttributableMod> updated = uwfAdapter.submitForm(form);
+
+        // propByRes can be made empty by the workflow definition if no propagation should occur 
+        // (for example, with rejected users)
+        if (updated.getResult() instanceof UserMod
+                && updated.getPropByRes() != null && !updated.getPropByRes().isEmpty()) {
+
+            List<PropagationTask> tasks = propagationManager.getUserUpdateTaskIds(
+                    new WorkflowResult<Map.Entry<UserMod, Boolean>>(
+                            new AbstractMap.SimpleEntry<UserMod, Boolean>((UserMod) updated.getResult(), Boolean.TRUE),
+                            updated.getPropByRes(),
+                            updated.getPerformedTasks()));
+
+            taskExecutor.execute(tasks);
+        }
+
+        return binder.getUserTO(updated.getResult().getKey());
+    }
+
+    @Override
+    protected WorkflowFormTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/WorkflowLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/WorkflowLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/WorkflowLogic.java
new file mode 100644
index 0000000..2e5ae74
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/WorkflowLogic.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.server.logic;
+
+import java.io.OutputStream;
+import java.lang.reflect.Method;
+import javax.ws.rs.core.MediaType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.apache.syncope.server.workflow.api.RoleWorkflowAdapter;
+import org.apache.syncope.server.workflow.api.UserWorkflowAdapter;
+import org.apache.syncope.server.workflow.api.WorkflowAdapter;
+import org.apache.syncope.server.workflow.api.WorkflowDefinitionFormat;
+import org.apache.syncope.server.workflow.api.WorkflowException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class WorkflowLogic extends AbstractTransactionalLogic<AbstractBaseBean> {
+
+    @Autowired
+    private UserWorkflowAdapter uwfAdapter;
+
+    @Autowired
+    private RoleWorkflowAdapter rwfAdapter;
+
+    private void exportDefinition(
+            final WorkflowAdapter adapter, final WorkflowDefinitionFormat format, final OutputStream os)
+            throws WorkflowException {
+
+        adapter.exportDefinition(format, os);
+    }
+
+    private WorkflowDefinitionFormat getFormat(final MediaType format) {
+        return format.equals(MediaType.APPLICATION_JSON_TYPE)
+                ? WorkflowDefinitionFormat.JSON
+                : WorkflowDefinitionFormat.XML;
+    }
+
+    @PreAuthorize("hasRole('WORKFLOW_DEF_READ')")
+    @Transactional(readOnly = true)
+    public void exportUserDefinition(final MediaType format, final OutputStream os)
+            throws WorkflowException {
+
+        exportDefinition(uwfAdapter, getFormat(format), os);
+    }
+
+    @PreAuthorize("hasRole('WORKFLOW_DEF_READ')")
+    @Transactional(readOnly = true)
+    public void exportRoleDefinition(final MediaType format, final OutputStream os)
+            throws WorkflowException {
+
+        exportDefinition(rwfAdapter, getFormat(format), os);
+    }
+
+    private void exportDiagram(final WorkflowAdapter adapter, final OutputStream os)
+            throws WorkflowException {
+        adapter.exportDiagram(os);
+    }
+
+    @PreAuthorize("hasRole('WORKFLOW_DEF_READ')")
+    @Transactional(readOnly = true)
+    public void exportUserDiagram(final OutputStream os)
+            throws WorkflowException {
+
+        exportDiagram(uwfAdapter, os);
+    }
+
+    @PreAuthorize("hasRole('WORKFLOW_DEF_READ')")
+    @Transactional(readOnly = true)
+    public void exportRoleDiagram(final OutputStream os)
+            throws WorkflowException {
+
+        exportDiagram(rwfAdapter, os);
+    }
+
+    private void importDefinition(
+            final WorkflowAdapter adapter, final WorkflowDefinitionFormat format, final String definition) {
+        adapter.importDefinition(format, definition);
+    }
+
+    @PreAuthorize("hasRole('WORKFLOW_DEF_UPDATE')")
+    public void importUserDefinition(final MediaType format, final String definition) {
+        importDefinition(uwfAdapter, getFormat(format), definition);
+    }
+
+    @PreAuthorize("hasRole('WORKFLOW_DEF_UPDATE')")
+    public void importRoleDefinition(final MediaType format, final String definition) {
+        importDefinition(rwfAdapter, getFormat(format), definition);
+    }
+
+    @Override
+    protected AbstractBaseBean resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/audit/AuditConnectionFactory.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/audit/AuditConnectionFactory.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/audit/AuditConnectionFactory.java
new file mode 100644
index 0000000..dae9fbf
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/audit/AuditConnectionFactory.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.server.logic.audit;
+
+import java.io.InputStream;
+import java.sql.Connection;
+import java.util.Properties;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.rmi.PortableRemoteObject;
+import javax.sql.DataSource;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathFactory;
+import org.apache.commons.dbcp2.BasicDataSource;
+import org.apache.commons.io.IOUtils;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PropertiesLoaderUtils;
+import org.springframework.jdbc.datasource.DataSourceUtils;
+import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
+import org.springframework.jdbc.datasource.init.ScriptUtils;
+import org.w3c.dom.Document;
+import org.w3c.dom.bootstrap.DOMImplementationRegistry;
+import org.w3c.dom.ls.DOMImplementationLS;
+import org.w3c.dom.ls.LSInput;
+import org.w3c.dom.ls.LSParser;
+
+/**
+ * LOG4J SQL connection factory that first attempts to obtain a {@link javax.sql.DataSource} from the JNDI name
+ * configured in Spring or, when not found, builds a new {@link javax.sql.DataSource DataSource} via Commons DBCP; if
+ * any datasource if found, the SQL init script is used to populate the database.
+ */
+public final class AuditConnectionFactory {
+
+    private static DataSource datasource;
+
+    private static final String PERSISTENCE_CONTEXT = "/persistenceContext.xml";
+
+    static {
+        // 1. Attempts to lookup for configured JNDI datasource (if present and available)
+        InputStream springConf = AuditConnectionFactory.class.getResourceAsStream(PERSISTENCE_CONTEXT);
+        String primary = null;
+        String fallback = null;
+        try {
+            DOMImplementationRegistry reg = DOMImplementationRegistry.newInstance();
+            DOMImplementationLS impl = (DOMImplementationLS) reg.getDOMImplementation("LS");
+            LSParser parser = impl.createLSParser(DOMImplementationLS.MODE_SYNCHRONOUS, null);
+            LSInput lsinput = impl.createLSInput();
+            lsinput.setByteStream(springConf);
+            Document source = parser.parse(lsinput);
+
+            XPathFactory xPathfactory = XPathFactory.newInstance();
+            XPath xpath = xPathfactory.newXPath();
+
+            XPathExpression expr = xpath.compile("//*[local-name()='bean' and @id='persistenceProperties']/"
+                    + "child::*[local-name()='property' and @name='primary']/@value");
+            primary = (String) expr.evaluate(source, XPathConstants.STRING);
+            expr = xpath.compile("//*[local-name()='bean' and @id='persistenceProperties']/"
+                    + "child::*[local-name()='property' and @name='fallback']/@value");
+            fallback = (String) expr.evaluate(source, XPathConstants.STRING);
+
+            expr = xpath.compile("//*[local-name()='property' and @name='jndiName']/@value");
+            String jndiName = (String) expr.evaluate(source, XPathConstants.STRING);
+
+            Context ctx = new InitialContext();
+            Object obj = ctx.lookup(jndiName);
+
+            datasource = (DataSource) PortableRemoteObject.narrow(obj, DataSource.class);
+        } catch (Exception e) {
+            // ignore
+        } finally {
+            IOUtils.closeQuietly(springConf);
+        }
+
+        // 2. Creates Commons DBCP datasource
+        String initSQLScript = null;
+        try {
+            Resource persistenceProperties = null;
+            if (primary != null) {
+                if (primary.startsWith("file:")) {
+                    persistenceProperties = new FileSystemResource(primary.substring(5));
+                }
+                if (primary.startsWith("classpath:")) {
+                    persistenceProperties = new ClassPathResource(primary.substring(10));
+                }
+            }
+            if ((persistenceProperties == null || !persistenceProperties.exists()) && fallback != null) {
+                if (fallback.startsWith("file:")) {
+                    persistenceProperties = new FileSystemResource(fallback.substring(5));
+                }
+                if (fallback.startsWith("classpath:")) {
+                    persistenceProperties = new ClassPathResource(fallback.substring(10));
+                }
+            }
+            Properties persistence = PropertiesLoaderUtils.loadProperties(persistenceProperties);
+
+            initSQLScript = persistence.getProperty("audit.sql");
+
+            if (datasource == null) {
+                BasicDataSource bds = new BasicDataSource();
+                bds.setDriverClassName(persistence.getProperty("jpa.driverClassName"));
+                bds.setUrl(persistence.getProperty("jpa.url"));
+                bds.setUsername(persistence.getProperty("jpa.username"));
+                bds.setPassword(persistence.getProperty("jpa.password"));
+
+                bds.setLogAbandoned(true);
+                bds.setRemoveAbandonedOnBorrow(true);
+                bds.setRemoveAbandonedOnMaintenance(true);
+
+                datasource = bds;
+            }
+        } catch (Exception e) {
+            throw new IllegalStateException("Audit datasource configuration failed", e);
+        }
+
+        // 3. Initializes the chosen datasource
+        ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
+        populator.setScripts(new Resource[] { new ClassPathResource("/audit/" + initSQLScript) });
+        // forces no statement separation
+        populator.setSeparator(ScriptUtils.EOF_STATEMENT_SEPARATOR);
+        Connection conn = DataSourceUtils.getConnection(datasource);
+        try {
+            populator.populate(conn);
+        } finally {
+            DataSourceUtils.releaseConnection(conn, datasource);
+        }
+    }
+
+    public static Connection getConnection() {
+        if (datasource != null) {
+            return DataSourceUtils.getConnection(datasource);
+        }
+
+        throw new IllegalStateException("Audit dataSource init failed: check logs");
+    }
+
+    private AuditConnectionFactory() {
+        // empty constructor for static utility class
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/audit/AuditManager.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/audit/AuditManager.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/audit/AuditManager.java
new file mode 100644
index 0000000..ed674fd
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/audit/AuditManager.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.server.logic.audit;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditElements.Result;
+import org.apache.syncope.common.lib.types.AuditLoggerName;
+import org.apache.syncope.common.lib.types.LoggerLevel;
+import org.apache.syncope.persistence.api.dao.LoggerDAO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+public class AuditManager {
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(AuditManager.class);
+
+    @Autowired
+    private LoggerDAO loggerDAO;
+
+    public void audit(
+            final AuditElements.EventCategoryType type,
+            final String category,
+            final String subcategory,
+            final String event,
+            final Result result,
+            final Object before,
+            final Object output,
+            final Object... input) {
+
+        final Throwable throwable;
+        final StringBuilder message = new StringBuilder();
+
+        message.append("BEFORE:\n");
+        message.append("\t").append(before == null ? "unknown" : before).append("\n");
+
+        message.append("INPUT:\n");
+
+        if (ArrayUtils.isNotEmpty(input)) {
+            for (Object obj : input) {
+                message.append("\t").append(obj == null ? null : obj.toString()).append("\n");
+            }
+        } else {
+            message.append("\t").append("none").append("\n");
+        }
+
+        message.append("OUTPUT:\n");
+
+        if (output instanceof Throwable) {
+            throwable = (Throwable) output;
+            message.append("\t").append(throwable.getMessage());
+        } else {
+            throwable = null;
+            message.append("\t").append(output == null ? "none" : output.toString());
+        }
+
+        AuditLoggerName auditLoggerName = null;
+        try {
+            auditLoggerName = new AuditLoggerName(type, category, subcategory, event, result);
+        } catch (IllegalArgumentException e) {
+            LOG.error("Invalid audit parameters, aborting...", e);
+        }
+
+        if (auditLoggerName != null) {
+            org.apache.syncope.persistence.api.entity.Logger syncopeLogger =
+                     loggerDAO.find(auditLoggerName.toLoggerName());
+            if (syncopeLogger != null && syncopeLogger.getLevel() == LoggerLevel.DEBUG) {
+                StringBuilder auditMessage = new StringBuilder();
+
+                final SecurityContext ctx = SecurityContextHolder.getContext();
+                if (ctx != null && ctx.getAuthentication() != null) {
+                    auditMessage.append('[').append(ctx.getAuthentication().getName()).append(']').append(' ');
+                }
+                auditMessage.append(message);
+
+                final Logger logger = LoggerFactory.getLogger(auditLoggerName.toLoggerName());
+                if (throwable == null) {
+                    logger.debug(auditMessage.toString());
+                } else {
+                    logger.debug(auditMessage.toString(), throwable);
+                }
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/AbstractAttributableDataBinder.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/AbstractAttributableDataBinder.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/AbstractAttributableDataBinder.java
new file mode 100644
index 0000000..d84c62f
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/AbstractAttributableDataBinder.java
@@ -0,0 +1,918 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.server.logic.data;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.SyncopeClientCompositeException;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.mod.AbstractAttributableMod;
+import org.apache.syncope.common.lib.mod.AbstractSubjectMod;
+import org.apache.syncope.common.lib.mod.AttrMod;
+import org.apache.syncope.common.lib.to.AbstractAttributableTO;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.IntMappingType;
+import org.apache.syncope.common.lib.types.MappingPurpose;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
+import org.apache.syncope.persistence.api.dao.DerAttrDAO;
+import org.apache.syncope.persistence.api.dao.DerSchemaDAO;
+import org.apache.syncope.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.persistence.api.dao.MembershipDAO;
+import org.apache.syncope.persistence.api.dao.PlainAttrDAO;
+import org.apache.syncope.persistence.api.dao.PlainAttrValueDAO;
+import org.apache.syncope.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.persistence.api.dao.PolicyDAO;
+import org.apache.syncope.persistence.api.dao.RoleDAO;
+import org.apache.syncope.persistence.api.dao.UserDAO;
+import org.apache.syncope.persistence.api.dao.VirAttrDAO;
+import org.apache.syncope.persistence.api.dao.VirSchemaDAO;
+import org.apache.syncope.persistence.api.entity.Attributable;
+import org.apache.syncope.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.persistence.api.entity.AttributableUtilFactory;
+import org.apache.syncope.persistence.api.entity.DerAttr;
+import org.apache.syncope.persistence.api.entity.DerSchema;
+import org.apache.syncope.persistence.api.entity.EntityFactory;
+import org.apache.syncope.persistence.api.entity.ExternalResource;
+import org.apache.syncope.persistence.api.entity.MappingItem;
+import org.apache.syncope.persistence.api.entity.PlainAttr;
+import org.apache.syncope.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.persistence.api.entity.PlainSchema;
+import org.apache.syncope.persistence.api.entity.Schema;
+import org.apache.syncope.persistence.api.entity.Subject;
+import org.apache.syncope.persistence.api.entity.VirAttr;
+import org.apache.syncope.persistence.api.entity.VirSchema;
+import org.apache.syncope.persistence.api.entity.membership.MDerAttr;
+import org.apache.syncope.persistence.api.entity.membership.MDerAttrTemplate;
+import org.apache.syncope.persistence.api.entity.membership.MPlainAttr;
+import org.apache.syncope.persistence.api.entity.membership.MPlainAttrTemplate;
+import org.apache.syncope.persistence.api.entity.membership.MVirAttr;
+import org.apache.syncope.persistence.api.entity.membership.MVirAttrTemplate;
+import org.apache.syncope.persistence.api.entity.membership.Membership;
+import org.apache.syncope.persistence.api.entity.role.RDerAttr;
+import org.apache.syncope.persistence.api.entity.role.RDerAttrTemplate;
+import org.apache.syncope.persistence.api.entity.role.RPlainAttr;
+import org.apache.syncope.persistence.api.entity.role.RPlainAttrTemplate;
+import org.apache.syncope.persistence.api.entity.role.RVirAttr;
+import org.apache.syncope.persistence.api.entity.role.RVirAttrTemplate;
+import org.apache.syncope.persistence.api.entity.role.Role;
+import org.apache.syncope.persistence.api.entity.user.UDerAttr;
+import org.apache.syncope.persistence.api.entity.user.UDerSchema;
+import org.apache.syncope.persistence.api.entity.user.UPlainAttr;
+import org.apache.syncope.persistence.api.entity.user.UPlainSchema;
+import org.apache.syncope.persistence.api.entity.user.UVirAttr;
+import org.apache.syncope.persistence.api.entity.user.UVirSchema;
+import org.apache.syncope.provisioning.api.propagation.PropagationByResource;
+import org.apache.syncope.server.utils.MappingUtil;
+import org.apache.syncope.server.utils.jexl.JexlUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public abstract class AbstractAttributableDataBinder {
+
+    /**
+     * Logger.
+     */
+    protected static final Logger LOG = LoggerFactory.getLogger(AbstractAttributableDataBinder.class);
+
+    @Autowired
+    protected RoleDAO roleDAO;
+
+    @Autowired
+    protected PlainSchemaDAO plainSchemaDAO;
+
+    @Autowired
+    protected DerSchemaDAO derSchemaDAO;
+
+    @Autowired
+    protected VirSchemaDAO virSchemaDAO;
+
+    @Autowired
+    protected PlainAttrDAO plainAttrDAO;
+
+    @Autowired
+    protected DerAttrDAO derAttrDAO;
+
+    @Autowired
+    protected VirAttrDAO virAttrDAO;
+
+    @Autowired
+    protected PlainAttrValueDAO plainAttrValueDAO;
+
+    @Autowired
+    protected UserDAO userDAO;
+
+    @Autowired
+    protected ExternalResourceDAO resourceDAO;
+
+    @Autowired
+    protected MembershipDAO membershipDAO;
+
+    @Autowired
+    protected PolicyDAO policyDAO;
+
+    @Autowired
+    protected EntityFactory entityFactory;
+
+    @Autowired
+    protected AttributableUtilFactory attrUtilFactory;
+
+    @SuppressWarnings("unchecked")
+    protected <T extends Schema> T getSchema(final String schemaName, final Class<T> reference) {
+        T result = null;
+
+        if (PlainSchema.class.isAssignableFrom(reference)) {
+            result = (T) getPlainSchema(schemaName, (Class<? extends PlainSchema>) reference);
+        } else if (DerSchema.class.isAssignableFrom(reference)) {
+            result = (T) getDerSchema(schemaName, (Class<? extends DerSchema>) reference);
+        } else if (VirSchema.class.isAssignableFrom(reference)) {
+            result = (T) getVirSchema(schemaName, (Class<? extends VirSchema>) reference);
+        }
+
+        return result;
+    }
+
+    protected <T extends PlainSchema> T getPlainSchema(final String schemaName, final Class<T> reference) {
+        T schema = null;
+        if (StringUtils.isNotBlank(schemaName)) {
+            schema = plainSchemaDAO.find(schemaName, reference);
+
+            // safely ignore invalid schemas from AttrTO
+            // see http://code.google.com/p/syncope/issues/detail?id=17
+            if (schema == null) {
+                LOG.debug("Ignoring invalid schema {}", schemaName);
+            } else if (schema.isReadonly()) {
+                schema = null;
+
+                LOG.debug("Ignoring readonly schema {}", schemaName);
+            }
+        }
+
+        return schema;
+    }
+
+    private <T extends DerSchema> T getDerSchema(final String derSchemaName, final Class<T> reference) {
+        T derivedSchema = null;
+        if (StringUtils.isNotBlank(derSchemaName)) {
+            derivedSchema = derSchemaDAO.find(derSchemaName, reference);
+            if (derivedSchema == null) {
+                LOG.debug("Ignoring invalid derived schema {}", derSchemaName);
+            }
+        }
+
+        return derivedSchema;
+    }
+
+    private <T extends VirSchema> T getVirSchema(final String virSchemaName, final Class<T> reference) {
+        T virtualSchema = null;
+        if (StringUtils.isNotBlank(virSchemaName)) {
+            virtualSchema = virSchemaDAO.find(virSchemaName, reference);
+
+            if (virtualSchema == null) {
+                LOG.debug("Ignoring invalid virtual schema {}", virSchemaName);
+            }
+        }
+
+        return virtualSchema;
+    }
+
+    protected void fillAttribute(final List<String> values, final AttributableUtil attributableUtil,
+            final PlainSchema schema, final PlainAttr attr, final SyncopeClientException invalidValues) {
+
+        // if schema is multivalue, all values are considered for addition;
+        // otherwise only the fist one - if provided - is considered
+        List<String> valuesProvided = schema.isMultivalue()
+                ? values
+                : (values.isEmpty()
+                        ? Collections.<String>emptyList()
+                        : Collections.singletonList(values.iterator().next()));
+
+        for (String value : valuesProvided) {
+            if (value == null || value.isEmpty()) {
+                LOG.debug("Null value for {}, ignoring", schema.getKey());
+            } else {
+                try {
+                    attr.addValue(value, attributableUtil);
+                } catch (InvalidPlainAttrValueException e) {
+                    LOG.warn("Invalid value for attribute " + schema.getKey() + ": " + value, e);
+
+                    invalidValues.getElements().add(schema.getKey() + ": " + value + " - " + e.getMessage());
+                }
+            }
+        }
+    }
+
+    private boolean evaluateMandatoryCondition(final AttributableUtil attrUtil, final ExternalResource resource,
+            final Attributable attributable, final String intAttrName, final IntMappingType intMappingType) {
+
+        boolean result = false;
+
+        final List<MappingItem> mappings = MappingUtil.getMatchingMappingItems(
+                attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION), intAttrName, intMappingType);
+        for (Iterator<MappingItem> itor = mappings.iterator(); itor.hasNext() && !result;) {
+            final MappingItem mapping = itor.next();
+            result |= JexlUtil.evaluateMandatoryCondition(mapping.getMandatoryCondition(), attributable);
+        }
+
+        return result;
+    }
+
+    private boolean evaluateMandatoryCondition(final AttributableUtil attrUtil,
+            final Attributable<?, ?, ?> attributable, final String intAttrName, final IntMappingType intMappingType) {
+
+        boolean result = false;
+
+        if (attributable instanceof Subject) {
+            for (Iterator<? extends ExternalResource> itor = ((Subject<?, ?, ?>) attributable).getResources().iterator();
+                    itor.hasNext() && !result;) {
+
+                final ExternalResource resource = itor.next();
+                if (resource.isEnforceMandatoryCondition()) {
+                    result |= evaluateMandatoryCondition(attrUtil, resource, attributable, intAttrName, intMappingType);
+                }
+            }
+        }
+
+        return result;
+    }
+
+    private SyncopeClientException checkMandatory(final AttributableUtil attrUtil,
+            final Attributable<?, ?, ?> attributable) {
+
+        SyncopeClientException reqValMissing = SyncopeClientException.build(ClientExceptionType.RequiredValuesMissing);
+
+        // Check if there is some mandatory schema defined for which no value has been provided
+        List<? extends PlainSchema> plainSchemas;
+        switch (attrUtil.getType()) {
+            case ROLE:
+                plainSchemas = ((Role) attributable).getAttrTemplateSchemas(RPlainAttrTemplate.class);
+                break;
+
+            case MEMBERSHIP:
+                plainSchemas = ((Membership) attributable).getRole().getAttrTemplateSchemas(MPlainAttrTemplate.class);
+                break;
+
+            case USER:
+            default:
+                plainSchemas = plainSchemaDAO.findAll(attrUtil.plainSchemaClass());
+        }
+        for (PlainSchema schema : plainSchemas) {
+            if (attributable.getPlainAttr(schema.getKey()) == null
+                    && !schema.isReadonly()
+                    && (JexlUtil.evaluateMandatoryCondition(schema.getMandatoryCondition(), attributable)
+                    || evaluateMandatoryCondition(attrUtil, attributable, schema.getKey(),
+                            attrUtil.intMappingType()))) {
+
+                LOG.error("Mandatory schema " + schema.getKey() + " not provided with values");
+
+                reqValMissing.getElements().add(schema.getKey());
+            }
+        }
+
+        List<? extends DerSchema> derSchemas;
+        switch (attrUtil.getType()) {
+            case ROLE:
+                derSchemas = ((Role) attributable).getAttrTemplateSchemas(RDerAttrTemplate.class);
+                break;
+
+            case MEMBERSHIP:
+                derSchemas = ((Membership) attributable).getRole().getAttrTemplateSchemas(MDerAttrTemplate.class);
+                break;
+
+            case USER:
+            default:
+                derSchemas = derSchemaDAO.findAll(attrUtil.derSchemaClass());
+        }
+        for (DerSchema derSchema : derSchemas) {
+            if (attributable.getDerAttr(derSchema.getKey()) == null
+                    && evaluateMandatoryCondition(attrUtil, attributable, derSchema.getKey(),
+                            attrUtil.derIntMappingType())) {
+
+                LOG.error("Mandatory derived schema " + derSchema.getKey() + " does not evaluate to any value");
+
+                reqValMissing.getElements().add(derSchema.getKey());
+            }
+        }
+
+        List<? extends VirSchema> virSchemas;
+        switch (attrUtil.getType()) {
+            case ROLE:
+                virSchemas = ((Role) attributable).getAttrTemplateSchemas(RVirAttrTemplate.class);
+                break;
+
+            case MEMBERSHIP:
+                virSchemas = ((Membership) attributable).getRole().getAttrTemplateSchemas(MVirAttrTemplate.class);
+                break;
+
+            case USER:
+            default:
+                virSchemas = virSchemaDAO.findAll(attrUtil.virSchemaClass());
+        }
+        for (VirSchema virSchema : virSchemas) {
+            if (attributable.getVirAttr(virSchema.getKey()) == null
+                    && !virSchema.isReadonly()
+                    && evaluateMandatoryCondition(attrUtil, attributable, virSchema.getKey(),
+                            attrUtil.virIntMappingType())) {
+
+                LOG.error("Mandatory virtual schema " + virSchema.getKey() + " not provided with values");
+
+                reqValMissing.getElements().add(virSchema.getKey());
+            }
+        }
+
+        return reqValMissing;
+    }
+
+    private void setPlainAttrSchema(final Attributable<?, ?, ?> attributable,
+            final PlainAttr attr, final PlainSchema schema) {
+
+        if (attr instanceof UPlainAttr) {
+            ((UPlainAttr) attr).setSchema((UPlainSchema) schema);
+        } else if (attr instanceof RPlainAttr) {
+            RPlainAttrTemplate template =
+                    ((Role) attributable).getAttrTemplate(RPlainAttrTemplate.class, schema.getKey());
+            if (template != null) {
+                ((RPlainAttr) attr).setTemplate(template);
+            }
+        } else if (attr instanceof MPlainAttr) {
+            MPlainAttrTemplate template = ((Membership) attributable).getRole().
+                    getAttrTemplate(MPlainAttrTemplate.class, schema.getKey());
+            if (template != null) {
+                ((MPlainAttr) attr).setTemplate(template);
+            }
+        }
+    }
+
+    private void setDerAttrSchema(final Attributable<?, ?, ?> attributable,
+            final DerAttr derAttr, final DerSchema derSchema) {
+
+        if (derAttr instanceof UDerAttr) {
+            ((UDerAttr) derAttr).setSchema((UDerSchema) derSchema);
+        } else if (derAttr instanceof RDerAttr) {
+            RDerAttrTemplate template = ((Role) attributable).
+                    getAttrTemplate(RDerAttrTemplate.class, derSchema.getKey());
+            if (template != null) {
+                ((RDerAttr) derAttr).setTemplate(template);
+            }
+        } else if (derAttr instanceof MDerAttr) {
+            MDerAttrTemplate template = ((Membership) attributable).getRole().
+                    getAttrTemplate(MDerAttrTemplate.class, derSchema.getKey());
+            if (template != null) {
+                ((MDerAttr) derAttr).setTemplate(template);
+            }
+        }
+    }
+
+    private void setVirAttrSchema(final Attributable<?, ?, ?> attributable,
+            final VirAttr virAttr, final VirSchema virSchema) {
+
+        if (virAttr instanceof UVirAttr) {
+            ((UVirAttr) virAttr).setSchema((UVirSchema) virSchema);
+        } else if (virAttr instanceof RVirAttr) {
+            RVirAttrTemplate template = ((Role) attributable).
+                    getAttrTemplate(RVirAttrTemplate.class, virSchema.getKey());
+            if (template != null) {
+                ((RVirAttr) virAttr).setTemplate(template);
+            }
+        } else if (virAttr instanceof MVirAttr) {
+            MVirAttrTemplate template =
+                    ((Membership) attributable).getRole().
+                    getAttrTemplate(MVirAttrTemplate.class, virSchema.getKey());
+            if (template != null) {
+                ((MVirAttr) virAttr).setTemplate(template);
+            }
+        }
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public PropagationByResource fillVirtual(final Attributable attributable,
+            final Set<String> vAttrsToBeRemoved, final Set<AttrMod> vAttrsToBeUpdated,
+            final AttributableUtil attrUtil) {
+
+        PropagationByResource propByRes = new PropagationByResource();
+
+        final Set<ExternalResource> externalResources = new HashSet<>();
+        if (attributable instanceof Subject) {
+            externalResources.addAll(((Subject<?, ?, ?>) attributable).getResources());
+        }
+
+        if (attributable instanceof Membership) {
+            externalResources.clear();
+            externalResources.addAll(((Membership) attributable).getUser().getResources());
+        }
+
+        // 1. virtual attributes to be removed
+        for (String vAttrToBeRemoved : vAttrsToBeRemoved) {
+            VirSchema virSchema = getVirSchema(vAttrToBeRemoved, attrUtil.virSchemaClass());
+            if (virSchema != null) {
+                VirAttr virAttr = attributable.getVirAttr(virSchema.getKey());
+                if (virAttr == null) {
+                    LOG.debug("No virtual attribute found for schema {}", virSchema.getKey());
+                } else {
+                    attributable.removeVirAttr(virAttr);
+                    virAttrDAO.delete(virAttr);
+                }
+
+                for (ExternalResource resource : resourceDAO.findAll()) {
+                    for (MappingItem mapItem : attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION)) {
+                        if (virSchema.getKey().equals(mapItem.getIntAttrName())
+                                && mapItem.getIntMappingType() == attrUtil.virIntMappingType()
+                                && externalResources.contains(resource)) {
+
+                            propByRes.add(ResourceOperation.UPDATE, resource.getKey());
+
+                            // Using virtual attribute as AccountId must be avoided
+                            if (mapItem.isAccountid() && virAttr != null && !virAttr.getValues().isEmpty()) {
+                                propByRes.addOldAccountId(resource.getKey(), virAttr.getValues().get(0));
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        LOG.debug("Virtual attributes to be removed:\n{}", propByRes);
+
+        // 2. virtual attributes to be updated
+        for (AttrMod vAttrToBeUpdated : vAttrsToBeUpdated) {
+            VirSchema virSchema = getVirSchema(vAttrToBeUpdated.getSchema(), attrUtil.virSchemaClass());
+            VirAttr virAttr = null;
+            if (virSchema != null) {
+                virAttr = attributable.getVirAttr(virSchema.getKey());
+                if (virAttr == null) {
+                    virAttr = attrUtil.newVirAttr();
+                    setVirAttrSchema(attributable, virAttr, virSchema);
+                    if (virAttr.getSchema() == null) {
+                        LOG.debug("Ignoring {} because no valid schema or template was found", vAttrToBeUpdated);
+                    } else {
+                        attributable.addVirAttr(virAttr);
+                    }
+                }
+            }
+
+            if (virSchema != null && virAttr != null && virAttr.getSchema() != null) {
+                for (ExternalResource resource : resourceDAO.findAll()) {
+                    for (MappingItem mapItem : attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION)) {
+                        if (virSchema.getKey().equals(mapItem.getIntAttrName())
+                                && mapItem.getIntMappingType() == attrUtil.virIntMappingType()
+                                && externalResources.contains(resource)) {
+
+                            propByRes.add(ResourceOperation.UPDATE, resource.getKey());
+                        }
+                    }
+                }
+
+                final List<String> values = new ArrayList<>(virAttr.getValues());
+                values.removeAll(vAttrToBeUpdated.getValuesToBeRemoved());
+                values.addAll(vAttrToBeUpdated.getValuesToBeAdded());
+
+                virAttr.getValues().clear();
+                virAttr.getValues().addAll(values);
+
+                // Owner cannot be specified before otherwise a virtual attribute remove will be invalidated.
+                virAttr.setOwner(attributable);
+            }
+        }
+
+        LOG.debug("Virtual attributes to be added:\n{}", propByRes);
+
+        return propByRes;
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    protected PropagationByResource fill(final Attributable attributable,
+            final AbstractAttributableMod attributableMod, final AttributableUtil attrUtil,
+            final SyncopeClientCompositeException scce) {
+
+        PropagationByResource propByRes = new PropagationByResource();
+
+        SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues);
+
+        if (attributable instanceof Subject && attributableMod instanceof AbstractSubjectMod) {
+            // 1. resources to be removed
+            for (String resourceToBeRemoved : ((AbstractSubjectMod) attributableMod).getResourcesToRemove()) {
+                ExternalResource resource = resourceDAO.find(resourceToBeRemoved);
+                if (resource != null) {
+                    propByRes.add(ResourceOperation.DELETE, resource.getKey());
+                    ((Subject<?, ?, ?>) attributable).removeResource(resource);
+                }
+            }
+
+            LOG.debug("Resources to be removed:\n{}", propByRes);
+
+            // 2. resources to be added
+            for (String resourceToBeAdded : ((AbstractSubjectMod) attributableMod).getResourcesToAdd()) {
+                ExternalResource resource = resourceDAO.find(resourceToBeAdded);
+                if (resource != null) {
+                    propByRes.add(ResourceOperation.CREATE, resource.getKey());
+                    ((Subject<?, ?, ?>) attributable).addResource(resource);
+                }
+            }
+
+            LOG.debug("Resources to be added:\n{}", propByRes);
+        }
+
+        // 3. attributes to be removed
+        for (String attributeToBeRemoved : attributableMod.getAttrsToRemove()) {
+            PlainSchema schema = getPlainSchema(attributeToBeRemoved, attrUtil.plainSchemaClass());
+            if (schema != null) {
+                PlainAttr attr = attributable.getPlainAttr(schema.getKey());
+                if (attr == null) {
+                    LOG.debug("No attribute found for schema {}", schema);
+                } else {
+                    String newValue = null;
+                    for (AttrMod mod : attributableMod.getAttrsToUpdate()) {
+                        if (schema.getKey().equals(mod.getSchema())) {
+                            newValue = mod.getValuesToBeAdded().get(0);
+                        }
+                    }
+
+                    if (!schema.isUniqueConstraint()
+                            || (!attr.getUniqueValue().getStringValue().equals(newValue))) {
+
+                        attributable.removePlainAttr(attr);
+                        plainAttrDAO.delete(attr.getKey(), attrUtil.plainAttrClass());
+                    }
+                }
+
+                if (attributable instanceof Subject) {
+                    for (ExternalResource resource : resourceDAO.findAll()) {
+                        for (MappingItem mapItem : attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION)) {
+                            if (schema.getKey().equals(mapItem.getIntAttrName())
+                                    && mapItem.getIntMappingType() == attrUtil.intMappingType()
+                                    && ((Subject<?, ?, ?>) attributable).getResources().contains(resource)) {
+
+                                propByRes.add(ResourceOperation.UPDATE, resource.getKey());
+
+                                if (mapItem.isAccountid() && attr != null
+                                        && !attr.getValuesAsStrings().isEmpty()) {
+
+                                    propByRes.addOldAccountId(resource.getKey(),
+                                            attr.getValuesAsStrings().iterator().next());
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        LOG.debug("Attributes to be removed:\n{}", propByRes);
+
+        // 4. attributes to be updated
+        for (AttrMod attributeMod : attributableMod.getAttrsToUpdate()) {
+            PlainSchema schema = getPlainSchema(attributeMod.getSchema(), attrUtil.plainSchemaClass());
+            PlainAttr attr = null;
+            if (schema != null) {
+                attr = attributable.getPlainAttr(schema.getKey());
+                if (attr == null) {
+                    attr = attrUtil.newPlainAttr();
+                    setPlainAttrSchema(attributable, attr, schema);
+                    if (attr.getSchema() == null) {
+                        LOG.debug("Ignoring {} because no valid schema or template was found", attributeMod);
+                    } else {
+                        attr.setOwner(attributable);
+                        attributable.addPlainAttr(attr);
+                    }
+                }
+            }
+
+            if (schema != null && attr != null && attr.getSchema() != null) {
+                if (attributable instanceof Subject) {
+                    for (ExternalResource resource : resourceDAO.findAll()) {
+                        for (MappingItem mapItem : attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION)) {
+                            if (schema.getKey().equals(mapItem.getIntAttrName())
+                                    && mapItem.getIntMappingType() == attrUtil.intMappingType()
+                                    && ((Subject<?, ?, ?>) attributable).getResources().contains(resource)) {
+
+                                propByRes.add(ResourceOperation.UPDATE, resource.getKey());
+                            }
+                        }
+                    }
+                }
+
+                // 1.1 remove values
+                Set<Long> valuesToBeRemoved = new HashSet<>();
+                for (String valueToBeRemoved : attributeMod.getValuesToBeRemoved()) {
+                    if (attr.getSchema().isUniqueConstraint()) {
+                        if (attr.getUniqueValue() != null
+                                && valueToBeRemoved.equals(attr.getUniqueValue().getValueAsString())) {
+
+                            valuesToBeRemoved.add(attr.getUniqueValue().getKey());
+                        }
+                    } else {
+                        for (PlainAttrValue mav : attr.getValues()) {
+                            if (valueToBeRemoved.equals(mav.getValueAsString())) {
+                                valuesToBeRemoved.add(mav.getKey());
+                            }
+                        }
+                    }
+                }
+                for (Long attributeValueId : valuesToBeRemoved) {
+                    plainAttrValueDAO.delete(attributeValueId, attrUtil.plainAttrValueClass());
+                }
+
+                // 1.2 add values
+                List<String> valuesToBeAdded = attributeMod.getValuesToBeAdded();
+                if (valuesToBeAdded != null && !valuesToBeAdded.isEmpty()
+                        && (!schema.isUniqueConstraint() || attr.getUniqueValue() == null
+                        || !valuesToBeAdded.iterator().next().equals(attr.getUniqueValue().getValueAsString()))) {
+
+                    fillAttribute(attributeMod.getValuesToBeAdded(), attrUtil, schema, attr, invalidValues);
+                }
+
+                // if no values are in, the attribute can be safely removed
+                if (attr.getValuesAsStrings().isEmpty()) {
+                    plainAttrDAO.delete(attr);
+                }
+            }
+        }
+
+        if (!invalidValues.isEmpty()) {
+            scce.addException(invalidValues);
+        }
+
+        LOG.debug("Attributes to be updated:\n{}", propByRes);
+
+        // 5. derived attributes to be removed
+        for (String derAttrToBeRemoved : attributableMod.getDerAttrsToRemove()) {
+            DerSchema derSchema = getDerSchema(derAttrToBeRemoved, attrUtil.derSchemaClass());
+            if (derSchema != null) {
+                DerAttr derAttr = attributable.getDerAttr(derSchema.getKey());
+                if (derAttr == null) {
+                    LOG.debug("No derived attribute found for schema {}", derSchema.getKey());
+                } else {
+                    derAttrDAO.delete(derAttr);
+                }
+
+                if (attributable instanceof Subject) {
+                    for (ExternalResource resource : resourceDAO.findAll()) {
+                        for (MappingItem mapItem : attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION)) {
+                            if (derSchema.getKey().equals(mapItem.getIntAttrName())
+                                    && mapItem.getIntMappingType() == attrUtil.derIntMappingType()
+                                    && ((Subject<?, ?, ?>) attributable).getResources().contains(resource)) {
+
+                                propByRes.add(ResourceOperation.UPDATE, resource.getKey());
+
+                                if (mapItem.isAccountid() && derAttr != null
+                                        && !derAttr.getValue(attributable.getPlainAttrs()).isEmpty()) {
+
+                                    propByRes.addOldAccountId(resource.getKey(),
+                                            derAttr.getValue(attributable.getPlainAttrs()));
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        LOG.debug("Derived attributes to be removed:\n{}", propByRes);
+
+        // 6. derived attributes to be added
+        for (String derAttrToBeAdded : attributableMod.getDerAttrsToAdd()) {
+            DerSchema derSchema = getDerSchema(derAttrToBeAdded, attrUtil.derSchemaClass());
+            if (derSchema != null) {
+                if (attributable instanceof Subject) {
+                    for (ExternalResource resource : resourceDAO.findAll()) {
+                        for (MappingItem mapItem : attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION)) {
+                            if (derSchema.getKey().equals(mapItem.getIntAttrName())
+                                    && mapItem.getIntMappingType() == attrUtil.derIntMappingType()
+                                    && ((Subject<?, ?, ?>) attributable).getResources().contains(resource)) {
+
+                                propByRes.add(ResourceOperation.UPDATE, resource.getKey());
+                            }
+                        }
+                    }
+                }
+
+                DerAttr derAttr = attrUtil.newDerAttr();
+                setDerAttrSchema(attributable, derAttr, derSchema);
+                if (derAttr.getSchema() == null) {
+                    LOG.debug("Ignoring {} because no valid schema or template was found", derAttrToBeAdded);
+                } else {
+                    derAttr.setOwner(attributable);
+                    attributable.addDerAttr(derAttr);
+                }
+            }
+        }
+
+        LOG.debug("Derived attributes to be added:\n{}", propByRes);
+
+        // 7. virtual attributes: for users and roles this is delegated to PropagationManager
+        if (AttributableType.USER != attrUtil.getType() && AttributableType.ROLE != attrUtil.getType()) {
+            fillVirtual(attributable, attributableMod.getVirAttrsToRemove(),
+                    attributableMod.getVirAttrsToUpdate(), attrUtil);
+        }
+
+        // Finally, check if mandatory values are missing
+        SyncopeClientException requiredValuesMissing = checkMandatory(attrUtil, attributable);
+        if (!requiredValuesMissing.isEmpty()) {
+            scce.addException(requiredValuesMissing);
+        }
+
+        // Throw composite exception if there is at least one element set in the composing exceptions
+        if (scce.hasExceptions()) {
+            throw scce;
+        }
+
+        return propByRes;
+    }
+
+    /**
+     * Add virtual attributes and specify values to be propagated.
+     *
+     * @param attributable attributable.
+     * @param vAttrs virtual attributes to be added.
+     * @param attrUtil attributable util.
+     */
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public void fillVirtual(final Attributable attributable, final Collection<AttrTO> vAttrs,
+            final AttributableUtil attrUtil) {
+
+        for (AttrTO attributeTO : vAttrs) {
+            VirAttr virAttr = attributable.getVirAttr(attributeTO.getSchema());
+            if (virAttr == null) {
+                VirSchema virSchema = getVirSchema(attributeTO.getSchema(), attrUtil.virSchemaClass());
+                if (virSchema != null) {
+                    virAttr = attrUtil.newVirAttr();
+                    setVirAttrSchema(attributable, virAttr, virSchema);
+                    if (virAttr.getSchema() == null) {
+                        LOG.debug("Ignoring {} because no valid schema or template was found", attributeTO);
+                    } else {
+                        virAttr.setOwner(attributable);
+                        attributable.addVirAttr(virAttr);
+                        virAttr.getValues().clear();
+                        virAttr.getValues().addAll(attributeTO.getValues());
+                    }
+                }
+            } else {
+                virAttr.getValues().clear();
+                virAttr.getValues().addAll(attributeTO.getValues());
+            }
+        }
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    protected void fill(final Attributable attributable, final AbstractAttributableTO attributableTO,
+            final AttributableUtil attributableUtil, final SyncopeClientCompositeException scce) {
+
+        // 1. attributes
+        SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues);
+
+        // Only consider attributeTO with values
+        for (AttrTO attributeTO : attributableTO.getPlainAttrs()) {
+            if (attributeTO.getValues() != null && !attributeTO.getValues().isEmpty()) {
+                PlainSchema schema = getPlainSchema(attributeTO.getSchema(), attributableUtil.plainSchemaClass());
+
+                if (schema != null) {
+                    PlainAttr attr = attributable.getPlainAttr(schema.getKey());
+                    if (attr == null) {
+                        attr = attributableUtil.newPlainAttr();
+                        setPlainAttrSchema(attributable, attr, schema);
+                    }
+                    if (attr.getSchema() == null) {
+                        LOG.debug("Ignoring {} because no valid schema or template was found", attributeTO);
+                    } else {
+                        fillAttribute(attributeTO.getValues(), attributableUtil, schema, attr, invalidValues);
+
+                        if (!attr.getValuesAsStrings().isEmpty()) {
+                            attributable.addPlainAttr(attr);
+                            attr.setOwner(attributable);
+                        }
+                    }
+                }
+            }
+        }
+
+        if (!invalidValues.isEmpty()) {
+            scce.addException(invalidValues);
+        }
+
+        // 2. derived attributes
+        for (AttrTO attributeTO : attributableTO.getDerAttrs()) {
+            DerSchema derSchema = getDerSchema(attributeTO.getSchema(), attributableUtil.derSchemaClass());
+
+            if (derSchema != null) {
+                DerAttr derAttr = attributableUtil.newDerAttr();
+                setDerAttrSchema(attributable, derAttr, derSchema);
+                if (derAttr.getSchema() == null) {
+                    LOG.debug("Ignoring {} because no valid schema or template was found", attributeTO);
+                } else {
+                    derAttr.setOwner(attributable);
+                    attributable.addDerAttr(derAttr);
+                }
+            }
+        }
+
+        // 3. user and role virtual attributes will be evaluated by the propagation manager only (if needed).
+        if (AttributableType.USER == attributableUtil.getType()
+                || AttributableType.ROLE == attributableUtil.getType()) {
+
+            for (AttrTO vattrTO : attributableTO.getVirAttrs()) {
+                VirSchema virSchema = getVirSchema(vattrTO.getSchema(), attributableUtil.virSchemaClass());
+
+                if (virSchema != null) {
+                    VirAttr virAttr = attributableUtil.newVirAttr();
+                    setVirAttrSchema(attributable, virAttr, virSchema);
+                    if (virAttr.getSchema() == null) {
+                        LOG.debug("Ignoring {} because no valid schema or template was found", vattrTO);
+                    } else {
+                        virAttr.setOwner(attributable);
+                        attributable.addVirAttr(virAttr);
+                    }
+                }
+            }
+        }
+
+        fillVirtual(attributable, attributableTO.getVirAttrs(), attributableUtil);
+
+        // 4. resources
+        if (attributable instanceof Subject && attributableTO instanceof AbstractSubjectTO) {
+            for (String resourceName : ((AbstractSubjectTO) attributableTO).getResources()) {
+                ExternalResource resource = resourceDAO.find(resourceName);
+
+                if (resource != null) {
+                    ((Subject<?, ?, ?>) attributable).addResource(resource);
+                }
+            }
+        }
+
+        SyncopeClientException requiredValuesMissing = checkMandatory(attributableUtil, attributable);
+        if (!requiredValuesMissing.isEmpty()) {
+            scce.addException(requiredValuesMissing);
+        }
+
+        // Throw composite exception if there is at least one element set in the composing exceptions
+        if (scce.hasExceptions()) {
+            throw scce;
+        }
+    }
+
+    protected void fillTO(final AbstractAttributableTO attributableTO,
+            final Collection<? extends PlainAttr> attrs,
+            final Collection<? extends DerAttr> derAttrs,
+            final Collection<? extends VirAttr> virAttrs,
+            final Collection<? extends ExternalResource> resources) {
+
+        AttrTO attributeTO;
+        for (PlainAttr attr : attrs) {
+            attributeTO = new AttrTO();
+            attributeTO.setSchema(attr.getSchema().getKey());
+            attributeTO.getValues().addAll(attr.getValuesAsStrings());
+            attributeTO.setReadonly(attr.getSchema().isReadonly());
+
+            attributableTO.getPlainAttrs().add(attributeTO);
+        }
+
+        for (DerAttr derAttr : derAttrs) {
+            attributeTO = new AttrTO();
+            attributeTO.setSchema(derAttr.getSchema().getKey());
+            attributeTO.getValues().add(derAttr.getValue(attrs));
+            attributeTO.setReadonly(true);
+
+            attributableTO.getDerAttrs().add(attributeTO);
+        }
+
+        for (VirAttr virAttr : virAttrs) {
+            attributeTO = new AttrTO();
+            attributeTO.setSchema(virAttr.getSchema().getKey());
+            attributeTO.getValues().addAll(virAttr.getValues());
+            attributeTO.setReadonly(virAttr.getSchema().isReadonly());
+
+            attributableTO.getVirAttrs().add(attributeTO);
+        }
+
+        if (attributableTO instanceof AbstractSubjectTO) {
+            for (ExternalResource resource : resources) {
+                ((AbstractSubjectTO) attributableTO).getResources().add(resource.getKey());
+            }
+        }
+    }
+}