syncope-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ilgro...@apache.org
Subject [10/52] [abbrv] [partial] syncope git commit: [SYNCOPE-620] Unit tests all in
Date Mon, 12 Jan 2015 16:31:49 GMT
http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/DefaultRoleProvisioningManager.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/DefaultRoleProvisioningManager.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/DefaultRoleProvisioningManager.java
new file mode 100644
index 0000000..666d535
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/DefaultRoleProvisioningManager.java
@@ -0,0 +1,226 @@
+/*
+ * 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.provisioning.java;
+
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.mod.RoleMod;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.PropagationStatus;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.types.PropagationByResource;
+import org.apache.syncope.server.persistence.api.RoleEntitlementUtil;
+import org.apache.syncope.server.persistence.api.dao.RoleDAO;
+import org.apache.syncope.server.persistence.api.entity.role.Role;
+import org.apache.syncope.server.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.server.provisioning.api.RoleProvisioningManager;
+import org.apache.syncope.server.provisioning.api.WorkflowResult;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationException;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationManager;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationReporter;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationTaskExecutor;
+import org.apache.syncope.server.misc.security.AuthContextUtil;
+import org.apache.syncope.server.misc.spring.ApplicationContextProvider;
+import org.apache.syncope.server.workflow.api.RoleWorkflowAdapter;
+import org.springframework.stereotype.Component;
+
+@Component
+public class DefaultRoleProvisioningManager implements RoleProvisioningManager {
+
+    private static final Logger LOG = LoggerFactory.getLogger(RoleProvisioningManager.class);
+
+    @Autowired
+    protected RoleWorkflowAdapter rwfAdapter;
+
+    @Autowired
+    protected PropagationManager propagationManager;
+
+    @Autowired
+    protected PropagationTaskExecutor taskExecutor;
+
+    @Autowired
+    protected RoleDAO roleDAO;
+
+    @Override
+    public Map.Entry<Long, List<PropagationStatus>> create(final RoleTO subject) {
+        return create(subject, Collections.<String>emptySet());
+    }
+
+    @Override
+    public Map.Entry<Long, List<PropagationStatus>> create(final RoleTO subject, final Set<String> excludedResources) {
+        WorkflowResult<Long> created = rwfAdapter.create(subject);
+
+        AuthContextUtil.extendAuthContext(
+                created.getResult(), RoleEntitlementUtil.getEntitlementNameFromRoleKey(created.getResult()));
+
+        List<PropagationTask> tasks = propagationManager.getRoleCreateTaskIds(created, subject.getVirAttrs());
+        PropagationReporter propagationReporter = ApplicationContextProvider.getApplicationContext().getBean(
+                PropagationReporter.class);
+        try {
+            taskExecutor.execute(tasks, propagationReporter);
+        } catch (PropagationException e) {
+            LOG.error("Error propagation primary resource", e);
+            propagationReporter.onPrimaryResourceFailure(tasks);
+        }
+
+        Map.Entry<Long, List<PropagationStatus>> result = new AbstractMap.SimpleEntry<Long, List<PropagationStatus>>(
+                created.getResult(), propagationReporter.getStatuses());
+        return result;
+    }
+
+    @Override
+    public Map.Entry<Long, List<PropagationStatus>> create(
+            final RoleTO roleTO, final Map<Long, String> roleOwnerMap, final Set<String> excludedResources) {
+
+        WorkflowResult<Long> created = rwfAdapter.create((RoleTO) roleTO);
+        AttrTO roleOwner = roleTO.getAttrMap().get(StringUtils.EMPTY);
+        if (roleOwner != null) {
+            roleOwnerMap.put(created.getResult(), roleOwner.getValues().iterator().next());
+        }
+
+        AuthContextUtil.extendAuthContext(
+                created.getResult(), RoleEntitlementUtil.getEntitlementNameFromRoleKey(created.getResult()));
+
+        List<PropagationTask> tasks = propagationManager.getRoleCreateTaskIds(
+                created, roleTO.getVirAttrs(), excludedResources);
+
+        taskExecutor.execute(tasks);
+
+        return new AbstractMap.SimpleEntry<Long, List<PropagationStatus>>(created.getResult(), null);
+    }
+
+    @Override
+    public Map.Entry<Long, List<PropagationStatus>> update(final RoleMod subjectMod) {
+        return update(subjectMod, Collections.<String>emptySet());
+    }
+
+    @Override
+    public Map.Entry<Long, List<PropagationStatus>> update(
+            final RoleMod subjectMod, final Set<String> excludedResources) {
+
+        WorkflowResult<Long> updated = rwfAdapter.update(subjectMod);
+
+        List<PropagationTask> tasks = propagationManager.getRoleUpdateTaskIds(updated,
+                subjectMod.getVirAttrsToRemove(), subjectMod.getVirAttrsToUpdate());
+        PropagationReporter propagationReporter = ApplicationContextProvider.getApplicationContext().getBean(
+                PropagationReporter.class);
+        try {
+            taskExecutor.execute(tasks, propagationReporter);
+        } catch (PropagationException e) {
+            LOG.error("Error propagation primary resource", e);
+            propagationReporter.onPrimaryResourceFailure(tasks);
+        }
+
+        Map.Entry<Long, List<PropagationStatus>> result = new AbstractMap.SimpleEntry<Long, List<PropagationStatus>>(
+                updated.getResult(), propagationReporter.getStatuses());
+        return result;
+    }
+
+    @Override
+    public List<PropagationStatus> delete(final Long subjectId) {
+        final List<Role> toBeDeprovisioned = new ArrayList<>();
+
+        final Role syncopeRole = roleDAO.find(subjectId);
+
+        if (syncopeRole != null) {
+            toBeDeprovisioned.add(syncopeRole);
+
+            final List<Role> descendants = roleDAO.findDescendants(toBeDeprovisioned.get(0));
+            if (descendants != null) {
+                toBeDeprovisioned.addAll(descendants);
+            }
+        }
+
+        final List<PropagationTask> tasks = new ArrayList<>();
+
+        for (Role role : toBeDeprovisioned) {
+            // Generate propagation tasks for deleting users from role resources, if they are on those resources only
+            // because of the reason being deleted (see SYNCOPE-357)
+            for (Map.Entry<Long, PropagationByResource> entry : roleDAO.findUsersWithIndirectResources(role.
+                    getKey()).entrySet()) {
+
+                WorkflowResult<Long> wfResult =
+                        new WorkflowResult<>(entry.getKey(), entry.getValue(), Collections.<String>emptySet());
+                tasks.addAll(propagationManager.getUserDeleteTaskIds(wfResult));
+            }
+
+            // Generate propagation tasks for deleting this role from resources
+            tasks.addAll(propagationManager.getRoleDeleteTaskIds(role.getKey()));
+        }
+
+        PropagationReporter propagationReporter = ApplicationContextProvider.getApplicationContext().
+                getBean(PropagationReporter.class);
+        try {
+            taskExecutor.execute(tasks, propagationReporter);
+        } catch (PropagationException e) {
+            LOG.error("Error propagation primary resource", e);
+            propagationReporter.onPrimaryResourceFailure(tasks);
+        }
+
+        try {
+            rwfAdapter.delete(subjectId);
+        } catch (RuntimeException e) {
+            throw e;
+        }
+
+        return propagationReporter.getStatuses();
+    }
+
+    @Override
+    public Long unlink(final RoleMod subjectMod) {
+        WorkflowResult<Long> updated = rwfAdapter.update(subjectMod);
+        return updated.getResult();
+    }
+
+    @Override
+    public List<PropagationStatus> deprovision(final Long roleKey, final Collection<String> resources) {
+        final Role role = roleDAO.authFetch(roleKey);
+
+        final Set<String> noPropResourceName = role.getResourceNames();
+        noPropResourceName.removeAll(resources);
+
+        final List<PropagationTask> tasks = propagationManager.getRoleDeleteTaskIds(roleKey, new HashSet<String>(
+                resources), noPropResourceName);
+        PropagationReporter propagationReporter = ApplicationContextProvider.getApplicationContext().getBean(
+                PropagationReporter.class);
+        try {
+            taskExecutor.execute(tasks, propagationReporter);
+        } catch (PropagationException e) {
+            LOG.error("Error propagation primary resource", e);
+            propagationReporter.onPrimaryResourceFailure(tasks);
+        }
+        return propagationReporter.getStatuses();
+    }
+
+    @Override
+    public Long link(final RoleMod subjectMod) {
+        return rwfAdapter.update(subjectMod).getResult();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/DefaultUserProvisioningManager.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/DefaultUserProvisioningManager.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/DefaultUserProvisioningManager.java
new file mode 100644
index 0000000..2c00a02
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/DefaultUserProvisioningManager.java
@@ -0,0 +1,368 @@
+/*
+ * 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.provisioning.java;
+
+import java.util.AbstractMap;
+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.syncope.common.lib.mod.MembershipMod;
+import org.apache.syncope.common.lib.mod.StatusMod;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.PropagationStatus;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.server.persistence.api.dao.UserDAO;
+import org.apache.syncope.server.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.server.persistence.api.entity.user.User;
+import org.apache.syncope.server.provisioning.api.UserProvisioningManager;
+import org.apache.syncope.server.provisioning.api.WorkflowResult;
+import org.apache.syncope.common.lib.types.PropagationByResource;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationException;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationManager;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationReporter;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationTaskExecutor;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningResult;
+import org.apache.syncope.server.misc.spring.ApplicationContextProvider;
+import org.apache.syncope.server.workflow.api.UserWorkflowAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class DefaultUserProvisioningManager implements UserProvisioningManager {
+
+    private static final Logger LOG = LoggerFactory.getLogger(UserProvisioningManager.class);
+
+    @Autowired
+    protected UserWorkflowAdapter uwfAdapter;
+
+    @Autowired
+    protected PropagationManager propagationManager;
+
+    @Autowired
+    protected PropagationTaskExecutor taskExecutor;
+
+    @Autowired
+    protected VirAttrHandler virtAttrHandler;
+
+    @Autowired
+    protected UserDAO userDAO;
+
+    @Override
+    public Map.Entry<Long, List<PropagationStatus>> create(final UserTO userTO) {
+        return create(userTO, true, false, null, Collections.<String>emptySet());
+    }
+
+    public Map.Entry<Long, List<PropagationStatus>> create(final UserTO userTO, final boolean storePassword) {
+        return create(userTO, storePassword, false, null, Collections.<String>emptySet());
+    }
+
+    @Override
+    public Map.Entry<Long, List<PropagationStatus>> create(final UserTO userTO, final boolean storePassword,
+            final boolean disablePwdPolicyCheck, final Boolean enabled, final Set<String> excludedResources) {
+
+        WorkflowResult<Map.Entry<Long, Boolean>> created;
+        try {
+            created = uwfAdapter.create(userTO, disablePwdPolicyCheck, enabled, storePassword);
+        } catch (PropagationException e) {
+            throw e;
+        }
+
+        List<PropagationTask> tasks = propagationManager.getUserCreateTaskIds(
+                created, userTO.getPassword(), userTO.getVirAttrs(), excludedResources, userTO.getMemberships());
+        PropagationReporter propagationReporter = ApplicationContextProvider.getApplicationContext().
+                getBean(PropagationReporter.class);
+        try {
+            taskExecutor.execute(tasks, propagationReporter);
+        } catch (PropagationException e) {
+            LOG.error("Error propagation primary resource", e);
+            propagationReporter.onPrimaryResourceFailure(tasks);
+        }
+
+        Map.Entry<Long, List<PropagationStatus>> result = new AbstractMap.SimpleEntry<Long, List<PropagationStatus>>(
+                created.getResult().getKey(), propagationReporter.getStatuses());
+        return result;
+    }
+
+    @Override
+    public Map.Entry<Long, List<PropagationStatus>> update(final UserMod userMod) {
+        return update(userMod, false);
+    }
+
+    @Override
+    public Map.Entry<Long, List<PropagationStatus>> update(final UserMod userMod, final boolean removeMemberships) {
+        WorkflowResult<Map.Entry<UserMod, Boolean>> updated = uwfAdapter.update(userMod);
+
+        List<PropagationTask> tasks = propagationManager.getUserUpdateTaskIds(updated);
+        if (tasks.isEmpty()) {
+            // SYNCOPE-459: take care of user virtual attributes ...
+            final PropagationByResource propByResVirAttr = virtAttrHandler.fillVirtual(
+                    updated.getResult().getKey().getKey(),
+                    userMod.getVirAttrsToRemove(),
+                    userMod.getVirAttrsToUpdate());
+            // SYNCOPE-501: update only virtual attributes (if any of them changed), password propagation is
+            // not required, take care also of membership virtual attributes
+            boolean addOrUpdateMemberships = false;
+            for (MembershipMod membershipMod : userMod.getMembershipsToAdd()) {
+                if (!virtAttrHandler.fillMembershipVirtual(
+                        updated.getResult().getKey().getKey(),
+                        membershipMod.getRole(),
+                        null,
+                        membershipMod.getVirAttrsToRemove(),
+                        membershipMod.getVirAttrsToUpdate(),
+                        false).isEmpty()) {
+                    addOrUpdateMemberships = true;
+                }
+            }
+            tasks.addAll(!propByResVirAttr.isEmpty() || addOrUpdateMemberships || removeMemberships
+                    ? propagationManager.getUserUpdateTaskIds(updated, false, null)
+                    : Collections.<PropagationTask>emptyList());
+        }
+        PropagationReporter propagationReporter = ApplicationContextProvider.getApplicationContext().
+                getBean(PropagationReporter.class);
+        if (!tasks.isEmpty()) {
+            try {
+                taskExecutor.execute(tasks, propagationReporter);
+            } catch (PropagationException e) {
+                LOG.error("Error propagation primary resource", e);
+                propagationReporter.onPrimaryResourceFailure(tasks);
+            }
+        }
+
+        Map.Entry<Long, List<PropagationStatus>> result = new AbstractMap.SimpleEntry<Long, List<PropagationStatus>>(
+                updated.getResult().getKey().getKey(), propagationReporter.getStatuses());
+        return result;
+    }
+
+    @Override
+    public List<PropagationStatus> delete(final Long userKey) {
+        return delete(userKey, Collections.<String>emptySet());
+    }
+
+    @Override
+    public List<PropagationStatus> delete(final Long subjectId, final Set<String> excludedResources) {
+        List<PropagationTask> tasks = propagationManager.getUserDeleteTaskIds(subjectId, excludedResources);
+
+        PropagationReporter propagationReporter = ApplicationContextProvider.getApplicationContext().
+                getBean(PropagationReporter.class);
+        try {
+            taskExecutor.execute(tasks, propagationReporter);
+        } catch (PropagationException e) {
+            LOG.error("Error propagation primary resource", e);
+            propagationReporter.onPrimaryResourceFailure(tasks);
+        }
+
+        try {
+            uwfAdapter.delete(subjectId);
+        } catch (PropagationException e) {
+            throw e;
+        }
+
+        return propagationReporter.getStatuses();
+    }
+
+    @Override
+    public Long unlink(final UserMod userMod) {
+        WorkflowResult<Map.Entry<UserMod, Boolean>> updated = uwfAdapter.update(userMod);
+        return updated.getResult().getKey().getKey();
+    }
+
+    @Override
+    public Long link(final UserMod subjectMod) {
+        return uwfAdapter.update(subjectMod).getResult().getKey().getKey();
+    }
+
+    @Override
+    public Map.Entry<Long, List<PropagationStatus>> activate(final User user, final StatusMod statusMod) {
+        WorkflowResult<Long> updated;
+        if (statusMod.isOnSyncope()) {
+            updated = uwfAdapter.activate(user.getKey(), statusMod.getToken());
+        } else {
+            updated = new WorkflowResult<Long>(user.getKey(), null, statusMod.getType().name().toLowerCase());
+        }
+
+        List<PropagationStatus> statuses = propagateStatus(user, statusMod);
+        return new AbstractMap.SimpleEntry<Long, List<PropagationStatus>>(updated.getResult(), statuses);
+    }
+
+    @Override
+    public Map.Entry<Long, List<PropagationStatus>> reactivate(final User user, final StatusMod statusMod) {
+        WorkflowResult<Long> updated;
+        if (statusMod.isOnSyncope()) {
+            updated = uwfAdapter.reactivate(user.getKey());
+        } else {
+            updated = new WorkflowResult<Long>(user.getKey(), null, statusMod.getType().name().toLowerCase());
+        }
+
+        List<PropagationStatus> statuses = propagateStatus(user, statusMod);
+        return new AbstractMap.SimpleEntry<Long, List<PropagationStatus>>(updated.getResult(), statuses);
+    }
+
+    @Override
+    public Map.Entry<Long, List<PropagationStatus>> suspend(final User user, final StatusMod statusMod) {
+        WorkflowResult<Long> updated;
+        if (statusMod.isOnSyncope()) {
+            updated = uwfAdapter.suspend(user.getKey());
+        } else {
+            updated = new WorkflowResult<Long>(user.getKey(), null, statusMod.getType().name().toLowerCase());
+        }
+
+        List<PropagationStatus> statuses = propagateStatus(user, statusMod);
+        return new AbstractMap.SimpleEntry<Long, List<PropagationStatus>>(updated.getResult(), statuses);
+    }
+
+    protected List<PropagationStatus> propagateStatus(final User user, final StatusMod statusMod) {
+        Set<String> resourcesToBeExcluded = new HashSet<String>(user.getResourceNames());
+        resourcesToBeExcluded.removeAll(statusMod.getResourceNames());
+
+        List<PropagationTask> tasks = propagationManager.getUserUpdateTaskIds(
+                user, statusMod.getType() != StatusMod.ModType.SUSPEND, resourcesToBeExcluded);
+        PropagationReporter propReporter =
+                ApplicationContextProvider.getApplicationContext().getBean(PropagationReporter.class);
+        try {
+            taskExecutor.execute(tasks, propReporter);
+        } catch (PropagationException e) {
+            LOG.error("Error propagation primary resource", e);
+            propReporter.onPrimaryResourceFailure(tasks);
+        }
+
+        return propReporter.getStatuses();
+
+    }
+
+    @Override
+    public void innerSuspend(final User user, final boolean propagate) {
+        final WorkflowResult<Long> updated = uwfAdapter.suspend(user);
+
+        // propagate suspension if and only if it is required by policy
+        if (propagate) {
+            UserMod userMod = new UserMod();
+            userMod.setKey(updated.getResult());
+
+            final List<PropagationTask> tasks = propagationManager.getUserUpdateTaskIds(
+                    new WorkflowResult<Map.Entry<UserMod, Boolean>>(
+                            new AbstractMap.SimpleEntry<UserMod, Boolean>(userMod, Boolean.FALSE),
+                            updated.getPropByRes(), updated.getPerformedTasks()));
+
+            taskExecutor.execute(tasks);
+        }
+    }
+
+    @Override
+    public List<PropagationStatus> deprovision(final Long userKey, final Collection<String> resources) {
+        final User user = userDAO.authFetch(userKey);
+
+        final Set<String> noPropResourceName = user.getResourceNames();
+        noPropResourceName.removeAll(resources);
+
+        final List<PropagationTask> tasks =
+                propagationManager.getUserDeleteTaskIds(userKey, new HashSet<String>(resources), noPropResourceName);
+        final PropagationReporter propagationReporter =
+                ApplicationContextProvider.getApplicationContext().getBean(PropagationReporter.class);
+        try {
+            taskExecutor.execute(tasks, propagationReporter);
+        } catch (PropagationException e) {
+            LOG.error("Error propagation primary resource", e);
+            propagationReporter.onPrimaryResourceFailure(tasks);
+        }
+
+        return propagationReporter.getStatuses();
+    }
+
+    @Override
+    public Map.Entry<Long, List<PropagationStatus>> update(final UserMod userMod, final Long id,
+            final ProvisioningResult result, final Boolean enabled, final Set<String> excludedResources) {
+
+        WorkflowResult<Map.Entry<UserMod, Boolean>> updated;
+        try {
+            updated = uwfAdapter.update(userMod);
+        } catch (Exception e) {
+            LOG.error("Update of user {} failed, trying to sync its status anyway (if configured)", id, e);
+
+            result.setStatus(ProvisioningResult.Status.FAILURE);
+            result.setMessage("Update failed, trying to sync status anyway (if configured)\n" + e.getMessage());
+
+            updated = new WorkflowResult<Map.Entry<UserMod, Boolean>>(
+                    new AbstractMap.SimpleEntry<UserMod, Boolean>(userMod, false), new PropagationByResource(),
+                    new HashSet<String>());
+        }
+
+        if (enabled != null) {
+            User user = userDAO.find(id);
+
+            WorkflowResult<Long> enableUpdate = null;
+            if (user.isSuspended() == null) {
+                enableUpdate = uwfAdapter.activate(id, null);
+            } else if (enabled && user.isSuspended()) {
+                enableUpdate = uwfAdapter.reactivate(id);
+            } else if (!enabled && !user.isSuspended()) {
+                enableUpdate = uwfAdapter.suspend(id);
+            }
+
+            if (enableUpdate != null) {
+                if (enableUpdate.getPropByRes() != null) {
+                    updated.getPropByRes().merge(enableUpdate.getPropByRes());
+                    updated.getPropByRes().purge();
+                }
+                updated.getPerformedTasks().addAll(enableUpdate.getPerformedTasks());
+            }
+        }
+
+        PropagationReporter propagationReporter = ApplicationContextProvider.getApplicationContext().
+                getBean(PropagationReporter.class);
+
+        List<PropagationTask> tasks = propagationManager.getUserUpdateTaskIds(
+                updated, updated.getResult().getKey().getPassword() != null, excludedResources);
+
+        try {
+            taskExecutor.execute(tasks, propagationReporter);
+        } catch (PropagationException e) {
+            LOG.error("Error propagation primary resource", e);
+            propagationReporter.onPrimaryResourceFailure(tasks);
+        }
+
+        return new AbstractMap.SimpleEntry<Long, List<PropagationStatus>>(updated.getResult().getKey().getKey(),
+                propagationReporter.getStatuses());
+
+    }
+
+    @Override
+    public void requestPasswordReset(final Long id) {
+        uwfAdapter.requestPasswordReset(id);
+    }
+
+    @Override
+    public void confirmPasswordReset(final User user, final String token, final String password) {
+        uwfAdapter.confirmPasswordReset(user.getKey(), token, password);
+
+        List<PropagationTask> tasks = propagationManager.getUserUpdateTaskIds(user, null, null);
+        PropagationReporter propReporter =
+                ApplicationContextProvider.getApplicationContext().getBean(PropagationReporter.class);
+        try {
+            taskExecutor.execute(tasks, propReporter);
+        } catch (PropagationException e) {
+            LOG.error("Error propagation primary resource", e);
+            propReporter.onPrimaryResourceFailure(tasks);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/VirAttrHandler.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/VirAttrHandler.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/VirAttrHandler.java
new file mode 100644
index 0000000..308d8dd
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/VirAttrHandler.java
@@ -0,0 +1,308 @@
+/*
+ * 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.provisioning.java;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.mod.AttrMod;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.common.lib.types.MappingPurpose;
+import org.apache.syncope.common.lib.types.PropagationByResource;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.server.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.server.persistence.api.dao.MembershipDAO;
+import org.apache.syncope.server.persistence.api.dao.UserDAO;
+import org.apache.syncope.server.persistence.api.dao.VirAttrDAO;
+import org.apache.syncope.server.persistence.api.dao.VirSchemaDAO;
+import org.apache.syncope.server.persistence.api.entity.Attributable;
+import org.apache.syncope.server.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.server.persistence.api.entity.AttributableUtilFactory;
+import org.apache.syncope.server.persistence.api.entity.ExternalResource;
+import org.apache.syncope.server.persistence.api.entity.MappingItem;
+import org.apache.syncope.server.persistence.api.entity.Subject;
+import org.apache.syncope.server.persistence.api.entity.VirAttr;
+import org.apache.syncope.server.persistence.api.entity.VirSchema;
+import org.apache.syncope.server.persistence.api.entity.membership.MVirAttr;
+import org.apache.syncope.server.persistence.api.entity.membership.MVirAttrTemplate;
+import org.apache.syncope.server.persistence.api.entity.membership.Membership;
+import org.apache.syncope.server.persistence.api.entity.role.RVirAttr;
+import org.apache.syncope.server.persistence.api.entity.role.RVirAttrTemplate;
+import org.apache.syncope.server.persistence.api.entity.role.Role;
+import org.apache.syncope.server.persistence.api.entity.user.UVirAttr;
+import org.apache.syncope.server.persistence.api.entity.user.UVirSchema;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+@Transactional(rollbackFor = { Throwable.class })
+public class VirAttrHandler {
+
+    private static final Logger LOG = LoggerFactory.getLogger(VirAttrHandler.class);
+
+    @Autowired
+    private ExternalResourceDAO resourceDAO;
+
+    @Autowired
+    private VirSchemaDAO virSchemaDAO;
+
+    @Autowired
+    private VirAttrDAO virAttrDAO;
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Autowired
+    private MembershipDAO membershipDAO;
+
+    @Autowired
+    private AttributableUtilFactory attrUtilFactory;
+
+    public <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;
+    }
+
+    public 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;
+    }
+
+    /**
+     * 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());
+            }
+        }
+    }
+
+    /**
+     * SYNCOPE-459: build virtual attribute changes in case no other changes were made.
+     *
+     * @param key user id
+     * @param vAttrsToBeRemoved virtual attributes to be removed.
+     * @param vAttrsToBeUpdated virtual attributes to be updated.
+     * @return operations to be performed on external resources for virtual attributes changes
+     */
+    public PropagationByResource fillVirtual(
+            final Long key, final Set<String> vAttrsToBeRemoved, final Set<AttrMod> vAttrsToBeUpdated) {
+
+        return fillVirtual(
+                userDAO.authFetch(key),
+                vAttrsToBeRemoved,
+                vAttrsToBeUpdated,
+                attrUtilFactory.getInstance(AttributableType.USER));
+    }
+
+    /**
+     * SYNCOPE-501: build membership virtual attribute changes in case no other changes were made.
+     *
+     * @param key user key
+     * @param roleKey role key
+     * @param membershipKey membership key
+     * @param vAttrsToBeRemoved virtual attributes to be removed.
+     * @param vAttrsToBeUpdated virtual attributes to be updated.
+     * @param isRemoval flag to check if fill is on removed or added membership
+     * @return operations to be performed on external resources for membership virtual attributes changes
+     */
+    public PropagationByResource fillMembershipVirtual(
+            final Long key, final Long roleKey, final Long membershipKey, final Set<String> vAttrsToBeRemoved,
+            final Set<AttrMod> vAttrsToBeUpdated, final boolean isRemoval) {
+
+        final Membership membership = membershipKey == null
+                ? userDAO.authFetch(key).getMembership(roleKey)
+                : membershipDAO.authFetch(membershipKey);
+
+        return membership == null ? new PropagationByResource() : isRemoval
+                ? fillVirtual(
+                        membership,
+                        membership.getVirAttrs() == null
+                                ? Collections.<String>emptySet()
+                                : getAttrNames(membership.getVirAttrs()),
+                        vAttrsToBeUpdated,
+                        attrUtilFactory.getInstance(AttributableType.MEMBERSHIP))
+                : fillVirtual(
+                        membership,
+                        vAttrsToBeRemoved,
+                        vAttrsToBeUpdated,
+                        attrUtilFactory.getInstance(AttributableType.MEMBERSHIP));
+    }
+
+    private Set<String> getAttrNames(final List<? extends VirAttr> virAttrs) {
+        final Set<String> virAttrNames = new HashSet<>();
+        for (VirAttr attr : virAttrs) {
+            virAttrNames.add(attr.getSchema().getKey());
+        }
+        return virAttrNames;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/cache/DisabledVirAttrCache.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/cache/DisabledVirAttrCache.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/cache/DisabledVirAttrCache.java
new file mode 100644
index 0000000..3fd4dcb
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/cache/DisabledVirAttrCache.java
@@ -0,0 +1,52 @@
+/*
+ * 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.provisioning.java.cache;
+
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.server.provisioning.api.cache.VirAttrCache;
+import org.apache.syncope.server.provisioning.api.cache.VirAttrCacheValue;
+
+/**
+ * Empty virtual attribute value cache implementation.
+ */
+public class DisabledVirAttrCache implements VirAttrCache {
+
+    @Override
+    public void expire(final AttributableType type, final Long id, final String schemaName) {
+        // nothing to do
+    }
+
+    @Override
+    public VirAttrCacheValue get(final AttributableType type, final Long id, final String schemaName) {
+        return null;
+    }
+
+    @Override
+    public boolean isValidEntry(VirAttrCacheValue value) {
+        return false;
+    }
+
+    @Override
+    public void put(
+            final AttributableType type, final Long id, final String schemaName, final VirAttrCacheValue value) {
+
+        // nothing to do
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/cache/MemoryVirAttrCache.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/cache/MemoryVirAttrCache.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/cache/MemoryVirAttrCache.java
new file mode 100644
index 0000000..448aaf7
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/cache/MemoryVirAttrCache.java
@@ -0,0 +1,151 @@
+/*
+ * 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.provisioning.java.cache;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.server.provisioning.api.cache.VirAttrCache;
+import org.apache.syncope.server.provisioning.api.cache.VirAttrCacheKey;
+import org.apache.syncope.server.provisioning.api.cache.VirAttrCacheValue;
+
+/**
+ * In-memory (HashMap) virtual attribute value cache implementation.
+ */
+public class MemoryVirAttrCache implements VirAttrCache {
+
+    /**
+     * Elapsed time in seconds.
+     */
+    protected int ttl;
+
+    /**
+     * Max cache size.
+     */
+    protected int maxCacheSize;
+
+    /**
+     * Cache entries.
+     */
+    protected final Map<VirAttrCacheKey, VirAttrCacheValue> cache = new HashMap<VirAttrCacheKey, VirAttrCacheValue>();
+
+    public MemoryVirAttrCache(final int ttl, final int maxCacheSize) {
+        this.ttl = ttl;
+        this.maxCacheSize = maxCacheSize;
+    }
+
+    /**
+     * Cache virtual attribute values.
+     *
+     * @param type user or role
+     * @param key user or role id
+     * @param schemaName virtual attribute name
+     * @param value virtual attribute values
+     */
+    @Override
+    public void put(
+            final AttributableType type,
+            final Long key,
+            final String schemaName,
+            final VirAttrCacheValue value) {
+
+        synchronized (cache) {
+            // this operations (retrieve cache space and put entry on) have to be thread safe.
+            if (this.cache.size() >= this.maxCacheSize) {
+                free();
+            }
+
+            cache.put(new VirAttrCacheKey(type, key, schemaName), value);
+        }
+    }
+
+    /**
+     * Retrieve cached value. Return null in case of virtual attribute not cached.
+     *
+     * @param type user or role
+     * @param id user or role id
+     * @param schemaName virtual attribute schema name.
+     * @return cached values or null if virtual attribute is not cached.
+     */
+    @Override
+    public VirAttrCacheValue get(final AttributableType type, final Long id, final String schemaName) {
+        return cache.get(new VirAttrCacheKey(type, id, schemaName));
+    }
+
+    /**
+     * Force entry expiring.
+     *
+     * @param type user or role
+     * @param id user or role id
+     * @param schemaName virtual attribute schema name
+     */
+    @Override
+    public void expire(final AttributableType type, final Long id, final String schemaName) {
+        final VirAttrCacheValue value = cache.get(new VirAttrCacheKey(type, id, schemaName));
+        if (isValidEntry(value)) {
+            synchronized (cache) {
+                value.forceExpiring();
+            }
+        }
+    }
+
+    /**
+     * Remove expired entries if exist. If required, one entry at least (the latest recently used) will be taken off.
+     * This method is not thread safe: the caller have to take care to synchronize the call.
+     */
+    private void free() {
+        final Set<VirAttrCacheKey> toBeRemoved = new HashSet<VirAttrCacheKey>();
+
+        Map.Entry<VirAttrCacheKey, VirAttrCacheValue> latest = null;
+
+        for (Map.Entry<VirAttrCacheKey, VirAttrCacheValue> entry : cache.entrySet()) {
+            if (isValidEntry(entry.getValue())) {
+                final Date date = entry.getValue().getLastAccessDate();
+                if (latest == null || latest.getValue().getLastAccessDate().after(date)) {
+                    latest = entry;
+                }
+            } else {
+                toBeRemoved.add(entry.getKey());
+            }
+        }
+
+        if (toBeRemoved.isEmpty() && latest != null) {
+            // remove the oldest entry
+            cache.remove(latest.getKey());
+        } else {
+            // remove expired entries
+            cache.keySet().removeAll(toBeRemoved);
+        }
+    }
+
+    /**
+     * Cache entry is valid if and only if value exist and it is not expired.
+     *
+     * @param value cache entry value.
+     * @return TRUE if the value is valid; FALSE otherwise.
+     */
+    @Override
+    public boolean isValidEntry(final VirAttrCacheValue value) {
+        final Date expiringDate = new Date(value == null ? 0 : value.getCreationDate().getTime() + ttl * 1000);
+        return expiringDate.after(new Date());
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/data/AbstractAttributableDataBinder.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/data/AbstractAttributableDataBinder.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/data/AbstractAttributableDataBinder.java
new file mode 100644
index 0000000..c38edfc
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/data/AbstractAttributableDataBinder.java
@@ -0,0 +1,754 @@
+/*
+ * 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.provisioning.java.data;
+
+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.server.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
+import org.apache.syncope.server.persistence.api.dao.DerAttrDAO;
+import org.apache.syncope.server.persistence.api.dao.DerSchemaDAO;
+import org.apache.syncope.server.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.server.persistence.api.dao.MembershipDAO;
+import org.apache.syncope.server.persistence.api.dao.PlainAttrDAO;
+import org.apache.syncope.server.persistence.api.dao.PlainAttrValueDAO;
+import org.apache.syncope.server.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.server.persistence.api.dao.PolicyDAO;
+import org.apache.syncope.server.persistence.api.dao.RoleDAO;
+import org.apache.syncope.server.persistence.api.dao.UserDAO;
+import org.apache.syncope.server.persistence.api.dao.VirAttrDAO;
+import org.apache.syncope.server.persistence.api.dao.VirSchemaDAO;
+import org.apache.syncope.server.persistence.api.entity.Attributable;
+import org.apache.syncope.server.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.server.persistence.api.entity.AttributableUtilFactory;
+import org.apache.syncope.server.persistence.api.entity.DerAttr;
+import org.apache.syncope.server.persistence.api.entity.DerSchema;
+import org.apache.syncope.server.persistence.api.entity.EntityFactory;
+import org.apache.syncope.server.persistence.api.entity.ExternalResource;
+import org.apache.syncope.server.persistence.api.entity.MappingItem;
+import org.apache.syncope.server.persistence.api.entity.PlainAttr;
+import org.apache.syncope.server.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.server.persistence.api.entity.PlainSchema;
+import org.apache.syncope.server.persistence.api.entity.Schema;
+import org.apache.syncope.server.persistence.api.entity.Subject;
+import org.apache.syncope.server.persistence.api.entity.VirAttr;
+import org.apache.syncope.server.persistence.api.entity.VirSchema;
+import org.apache.syncope.server.persistence.api.entity.membership.MDerAttr;
+import org.apache.syncope.server.persistence.api.entity.membership.MDerAttrTemplate;
+import org.apache.syncope.server.persistence.api.entity.membership.MPlainAttr;
+import org.apache.syncope.server.persistence.api.entity.membership.MPlainAttrTemplate;
+import org.apache.syncope.server.persistence.api.entity.membership.MVirAttrTemplate;
+import org.apache.syncope.server.persistence.api.entity.membership.Membership;
+import org.apache.syncope.server.persistence.api.entity.role.RDerAttr;
+import org.apache.syncope.server.persistence.api.entity.role.RDerAttrTemplate;
+import org.apache.syncope.server.persistence.api.entity.role.RPlainAttr;
+import org.apache.syncope.server.persistence.api.entity.role.RPlainAttrTemplate;
+import org.apache.syncope.server.persistence.api.entity.role.RVirAttrTemplate;
+import org.apache.syncope.server.persistence.api.entity.role.Role;
+import org.apache.syncope.server.persistence.api.entity.user.UDerAttr;
+import org.apache.syncope.server.persistence.api.entity.user.UDerSchema;
+import org.apache.syncope.server.persistence.api.entity.user.UPlainAttr;
+import org.apache.syncope.server.persistence.api.entity.user.UPlainSchema;
+import org.apache.syncope.common.lib.types.PropagationByResource;
+import org.apache.syncope.server.provisioning.java.VirAttrHandler;
+import org.apache.syncope.server.misc.MappingUtil;
+import org.apache.syncope.server.misc.jexl.JexlUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+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;
+
+    @Autowired
+    protected VirAttrHandler virtAttrHander;
+
+    @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) virtAttrHander.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;
+    }
+
+    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);
+            }
+        }
+    }
+
+    @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()) {
+            virtAttrHander.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;
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    protected void fill(final Attributable attributable, final AbstractAttributableTO attributableTO,
+            final AttributableUtil attrUtil, 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(), attrUtil.plainSchemaClass());
+
+                if (schema != null) {
+                    PlainAttr 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", attributeTO);
+                    } else {
+                        fillAttribute(attributeTO.getValues(), attrUtil, 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(), attrUtil.derSchemaClass());
+
+            if (derSchema != null) {
+                DerAttr derAttr = attrUtil.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 == attrUtil.getType()
+                || AttributableType.ROLE == attrUtil.getType()) {
+
+            for (AttrTO vattrTO : attributableTO.getVirAttrs()) {
+                VirSchema virSchema = virtAttrHander.getVirSchema(vattrTO.getSchema(), attrUtil.virSchemaClass());
+
+                if (virSchema != null) {
+                    VirAttr virAttr = attrUtil.newVirAttr();
+                    virtAttrHander.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);
+                    }
+                }
+            }
+        }
+
+        virtAttrHander.fillVirtual(attributable, attributableTO.getVirAttrs(), attrUtil);
+
+        // 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(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;
+        }
+    }
+
+    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());
+            }
+        }
+    }
+}


Mime
View raw message