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/12 17:31:45 UTC

[06/52] [abbrv] [partial] syncope git commit: [SYNCOPE-620] Unit tests all in

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/PropagationManagerImpl.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/PropagationManagerImpl.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/PropagationManagerImpl.java
new file mode 100644
index 0000000..cf4f309
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/PropagationManagerImpl.java
@@ -0,0 +1,772 @@
+/*
+ * 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.propagation;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.syncope.common.lib.mod.AttrMod;
+import org.apache.syncope.common.lib.mod.MembershipMod;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.MembershipTO;
+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.NotFoundException;
+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.entity.AttributableUtil;
+import org.apache.syncope.server.persistence.api.entity.AttributableUtilFactory;
+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.Subject;
+import org.apache.syncope.server.persistence.api.entity.VirAttr;
+import org.apache.syncope.server.persistence.api.entity.membership.Membership;
+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.persistence.api.entity.user.User;
+import org.apache.syncope.server.provisioning.api.WorkflowResult;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationManager;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationTaskExecutor;
+import org.apache.syncope.server.provisioning.java.VirAttrHandler;
+import org.apache.syncope.server.misc.security.UnauthorizedRoleException;
+import org.apache.syncope.server.misc.ConnObjectUtil;
+import org.apache.syncope.server.misc.MappingUtil;
+import org.apache.syncope.server.misc.jexl.JexlUtil;
+import org.identityconnectors.framework.common.objects.Attribute;
+import org.identityconnectors.framework.common.objects.AttributeBuilder;
+import org.identityconnectors.framework.common.objects.AttributeUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Manage the data propagation to external resources.
+ */
+@Transactional(rollbackFor = { Throwable.class })
+public class PropagationManagerImpl implements PropagationManager {
+
+    /**
+     * Logger.
+     */
+    protected static final Logger LOG = LoggerFactory.getLogger(PropagationManager.class);
+
+    /**
+     * User DAO.
+     */
+    @Autowired
+    protected UserDAO userDAO;
+
+    /**
+     * Role DAO.
+     */
+    @Autowired
+    protected RoleDAO roleDAO;
+
+    /**
+     * Resource DAO.
+     */
+    @Autowired
+    protected ExternalResourceDAO resourceDAO;
+
+    @Autowired
+    protected EntityFactory entityFactory;
+
+    /**
+     * ConnObjectUtil.
+     */
+    @Autowired
+    protected ConnObjectUtil connObjectUtil;
+
+    @Autowired
+    protected AttributableUtilFactory attrUtilFactory;
+
+    @Autowired
+    protected VirAttrHandler virAttrHandler;
+
+    /**
+     * Create the user on every associated resource.
+     *
+     * @param wfResult user to be propagated (and info associated), as per result from workflow
+     * @param password to be set
+     * @param vAttrs virtual attributes to be set
+     * @param membershipTOs user memberships
+     * @return list of propagation tasks
+     * @throws NotFoundException if user is not found
+     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given user
+     */
+    @Override
+    public List<PropagationTask> getUserCreateTaskIds(final WorkflowResult<Map.Entry<Long, Boolean>> wfResult,
+            final String password, final List<AttrTO> vAttrs, final List<MembershipTO> membershipTOs)
+            throws NotFoundException, UnauthorizedRoleException {
+
+        return getUserCreateTaskIds(wfResult, password, vAttrs, null, membershipTOs);
+    }
+
+    /**
+     * Create the user on every associated resource.
+     *
+     * @param wfResult user to be propagated (and info associated), as per result from workflow
+     * @param password to be set
+     * @param vAttrs virtual attributes to be set
+     * @param noPropResourceNames external resources not to be considered for propagation
+     * @param membershipTOs user memberships
+     * @return list of propagation tasks
+     * @throws NotFoundException if user is not found
+     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given user
+     */
+    @Override
+    public List<PropagationTask> getUserCreateTaskIds(final WorkflowResult<Map.Entry<Long, Boolean>> wfResult,
+            final String password, final Collection<AttrTO> vAttrs,
+            final Set<String> noPropResourceNames, final List<MembershipTO> membershipTOs)
+            throws NotFoundException, UnauthorizedRoleException {
+
+        return getUserCreateTaskIds(
+                wfResult.getResult().getKey(),
+                wfResult.getResult().getValue(),
+                wfResult.getPropByRes(),
+                password,
+                vAttrs,
+                membershipTOs,
+                noPropResourceNames);
+    }
+
+    @Override
+    public List<PropagationTask> getUserCreateTaskIds(
+            final Long key,
+            final Boolean enabled,
+            final PropagationByResource propByRes,
+            final String password,
+            final Collection<AttrTO> vAttrs,
+            final Collection<MembershipTO> membershipTOs,
+            final Collection<String> noPropResourceNames)
+            throws NotFoundException, UnauthorizedRoleException {
+
+        User user = userDAO.authFetch(key);
+        if (vAttrs != null && !vAttrs.isEmpty()) {
+            virAttrHandler.fillVirtual(user, vAttrs, attrUtilFactory.getInstance(AttributableType.USER));
+
+        }
+        for (Membership membership : user.getMemberships()) {
+            MembershipTO membershipTO;
+            if (membership.getVirAttrs() != null && !membership.getVirAttrs().isEmpty()) {
+                membershipTO = findMembershipTO(membership, membershipTOs);
+                if (membershipTO != null) {
+                    virAttrHandler.fillVirtual(membership,
+                            membershipTO.getVirAttrs(), attrUtilFactory.getInstance(AttributableType.MEMBERSHIP));
+                }
+            }
+        }
+        return getCreateTaskIds(user, password, enabled, propByRes, noPropResourceNames);
+    }
+
+    /**
+     * Create the role on every associated resource.
+     *
+     * @param wfResult user to be propagated (and info associated), as per result from workflow
+     * @param vAttrs virtual attributes to be set
+     * @return list of propagation tasks
+     * @throws NotFoundException if role is not found
+     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role
+     */
+    @Override
+    public List<PropagationTask> getRoleCreateTaskIds(final WorkflowResult<Long> wfResult, final List<AttrTO> vAttrs)
+            throws NotFoundException, UnauthorizedRoleException {
+
+        return getRoleCreateTaskIds(wfResult, vAttrs, null);
+    }
+
+    /**
+     * Create the role on every associated resource.
+     *
+     * @param wfResult role to be propagated (and info associated), as per result from workflow
+     * @param vAttrs virtual attributes to be set
+     * @param noPropResourceNames external resources performing not to be considered for propagation
+     * @return list of propagation tasks
+     * @throws NotFoundException if role is not found
+     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role
+     */
+    @Override
+    public List<PropagationTask> getRoleCreateTaskIds(
+            final WorkflowResult<Long> wfResult,
+            final Collection<AttrTO> vAttrs,
+            final Collection<String> noPropResourceNames)
+            throws NotFoundException, UnauthorizedRoleException {
+
+        return getRoleCreateTaskIds(wfResult.getResult(), vAttrs, wfResult.getPropByRes(), noPropResourceNames);
+    }
+
+    /**
+     * Create the role on every associated resource.
+     *
+     * @param key role key
+     * @param vAttrs virtual attributes to be set
+     * @param propByRes operation to be performed per resource
+     * @param noPropResourceNames external resources performing not to be considered for propagation
+     * @return list of propagation tasks
+     * @throws NotFoundException if role is not found
+     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role
+     */
+    @Override
+    public List<PropagationTask> getRoleCreateTaskIds(
+            final Long key,
+            final Collection<AttrTO> vAttrs,
+            final PropagationByResource propByRes,
+            final Collection<String> noPropResourceNames)
+            throws NotFoundException, UnauthorizedRoleException {
+
+        Role role = roleDAO.authFetch(key);
+        if (vAttrs != null && !vAttrs.isEmpty()) {
+            virAttrHandler.fillVirtual(role, vAttrs, attrUtilFactory.getInstance(AttributableType.ROLE));
+        }
+
+        return getCreateTaskIds(role, null, null, propByRes, noPropResourceNames);
+    }
+
+    protected List<PropagationTask> getCreateTaskIds(final Subject<?, ?, ?> subject,
+            final String password, final Boolean enable,
+            final PropagationByResource propByRes,
+            final Collection<String> noPropResourceNames) {
+
+        if (propByRes == null || propByRes.isEmpty()) {
+            return Collections.<PropagationTask>emptyList();
+        }
+
+        if (noPropResourceNames != null) {
+            propByRes.get(ResourceOperation.CREATE).removeAll(noPropResourceNames);
+        }
+
+        return createTasks(subject, password, true, null, null, null, null, enable, false, propByRes);
+    }
+
+    /**
+     * Performs update on each resource associated to the user excluding the specified into 'resourceNames' parameter.
+     *
+     * @param user to be propagated
+     * @param enable whether user must be enabled or not
+     * @param noPropResourceNames external resource names not to be considered for propagation
+     * @return list of propagation tasks
+     * @throws NotFoundException if user is not found
+     */
+    @Override
+    public List<PropagationTask> getUserUpdateTaskIds(final User user, final Boolean enable,
+            final Set<String> noPropResourceNames) throws NotFoundException {
+
+        return getUpdateTaskIds(
+                user, // SyncopeUser to be updated on external resources
+                null, // no password
+                false,
+                enable, // status to be propagated
+                Collections.<String>emptySet(), // no virtual attributes to be managed
+                Collections.<AttrMod>emptySet(), // no virtual attributes to be managed
+                null, // no propagation by resources
+                noPropResourceNames,
+                Collections.<MembershipMod>emptySet());
+    }
+
+    /**
+     * Performs update on each resource associated to the user.
+     *
+     * @param wfResult user to be propagated (and info associated), as per result from workflow
+     * @param changePwd whether password should be included for propagation attributes or not
+     * @param noPropResourceNames external resources not to be considered for propagation
+     * @return list of propagation tasks
+     * @throws NotFoundException if user is not found
+     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given user
+     */
+    @Override
+    public List<PropagationTask> getUserUpdateTaskIds(final WorkflowResult<Map.Entry<UserMod, Boolean>> wfResult,
+            final boolean changePwd, final Collection<String> noPropResourceNames)
+            throws NotFoundException, UnauthorizedRoleException {
+
+        User user = userDAO.authFetch(wfResult.getResult().getKey().getKey());
+        return getUpdateTaskIds(user,
+                wfResult.getResult().getKey().getPassword(),
+                changePwd,
+                wfResult.getResult().getValue(),
+                wfResult.getResult().getKey().getVirAttrsToRemove(),
+                wfResult.getResult().getKey().getVirAttrsToUpdate(),
+                wfResult.getPropByRes(),
+                noPropResourceNames,
+                wfResult.getResult().getKey().getMembershipsToAdd());
+    }
+
+    @Override
+    public List<PropagationTask> getUserUpdateTaskIds(final WorkflowResult<Map.Entry<UserMod, Boolean>> wfResult) {
+        UserMod userMod = wfResult.getResult().getKey();
+
+        // Propagate password update only to requested resources
+        List<PropagationTask> tasks = new ArrayList<>();
+        if (userMod.getPwdPropRequest() == null) {
+            // a. no specific password propagation request: generate propagation tasks for any resource associated
+            tasks = getUserUpdateTaskIds(wfResult, true, null);
+        } else {
+            // b. generate the propagation task list in two phases: first the ones containing password,
+            // the the rest (with no password)
+            final PropagationByResource origPropByRes = new PropagationByResource();
+            origPropByRes.merge(wfResult.getPropByRes());
+
+            Set<String> pwdResourceNames = new HashSet<>(userMod.getPwdPropRequest().getResourceNames());
+            Set<String> currentResourceNames = userDAO.authFetch(userMod.getKey()).getResourceNames();
+            pwdResourceNames.retainAll(currentResourceNames);
+            PropagationByResource pwdPropByRes = new PropagationByResource();
+            pwdPropByRes.addAll(ResourceOperation.UPDATE, pwdResourceNames);
+            if (!pwdPropByRes.isEmpty()) {
+                Set<String> toBeExcluded = new HashSet<>(currentResourceNames);
+                toBeExcluded.addAll(userMod.getResourcesToAdd());
+                toBeExcluded.removeAll(pwdResourceNames);
+                tasks.addAll(getUserUpdateTaskIds(wfResult, true, toBeExcluded));
+            }
+
+            final PropagationByResource nonPwdPropByRes = new PropagationByResource();
+            nonPwdPropByRes.merge(origPropByRes);
+            nonPwdPropByRes.removeAll(pwdResourceNames);
+            nonPwdPropByRes.purge();
+            if (!nonPwdPropByRes.isEmpty()) {
+                tasks.addAll(getUserUpdateTaskIds(wfResult, false, pwdResourceNames));
+            }
+        }
+
+        return tasks;
+    }
+
+    /**
+     * Performs update on each resource associated to the role.
+     *
+     * @param wfResult role to be propagated (and info associated), as per result from workflow
+     * @param vAttrsToBeRemoved virtual attributes to be removed
+     * @param vAttrsToBeUpdated virtual attributes to be added
+     * @return list of propagation tasks
+     * @throws NotFoundException if role is not found
+     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role
+     */
+    @Override
+    public List<PropagationTask> getRoleUpdateTaskIds(final WorkflowResult<Long> wfResult,
+            final Set<String> vAttrsToBeRemoved, final Set<AttrMod> vAttrsToBeUpdated)
+            throws NotFoundException, UnauthorizedRoleException {
+
+        return getRoleUpdateTaskIds(wfResult, vAttrsToBeRemoved, vAttrsToBeUpdated, null);
+    }
+
+    /**
+     * Performs update on each resource associated to the role.
+     *
+     * @param wfResult role to be propagated (and info associated), as per result from workflow
+     * @param vAttrsToBeRemoved virtual attributes to be removed
+     * @param vAttrsToBeUpdated virtual attributes to be added
+     * @param noPropResourceNames external resource names not to be considered for propagation
+     * @return list of propagation tasks
+     * @throws NotFoundException if role is not found
+     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role
+     */
+    public List<PropagationTask> getRoleUpdateTaskIds(final WorkflowResult<Long> wfResult,
+            final Set<String> vAttrsToBeRemoved, final Set<AttrMod> vAttrsToBeUpdated,
+            final Set<String> noPropResourceNames)
+            throws NotFoundException, UnauthorizedRoleException {
+
+        Role role = roleDAO.authFetch(wfResult.getResult());
+        return getUpdateTaskIds(role, null, false, null,
+                vAttrsToBeRemoved, vAttrsToBeUpdated, wfResult.getPropByRes(), noPropResourceNames,
+                Collections.<MembershipMod>emptySet());
+    }
+
+    @Override
+    public List<PropagationTask> getUpdateTaskIds(final Subject<?, ?, ?> subject,
+            final String password, final boolean changePwd, final Boolean enable,
+            final Set<String> vAttrsToBeRemoved, final Set<AttrMod> vAttrsToBeUpdated,
+            final PropagationByResource propByRes, final Collection<String> noPropResourceNames,
+            final Set<MembershipMod> membershipsToAdd)
+            throws NotFoundException {
+
+        PropagationByResource localPropByRes = virAttrHandler.fillVirtual(subject, vAttrsToBeRemoved == null
+                ? Collections.<String>emptySet()
+                : vAttrsToBeRemoved, vAttrsToBeUpdated == null
+                        ? Collections.<AttrMod>emptySet()
+                        : vAttrsToBeUpdated, attrUtilFactory.getInstance(subject));
+
+        // SYNCOPE-458 fill membership virtual attributes
+        if (subject instanceof User) {
+            final User user = (User) subject;
+            for (Membership membership : user.getMemberships()) {
+                if (membership.getVirAttrs() != null && !membership.getVirAttrs().isEmpty()) {
+                    final MembershipMod membershipMod = findMembershipMod(membership, membershipsToAdd);
+                    if (membershipMod != null) {
+                        virAttrHandler.fillVirtual(membership, membershipMod.getVirAttrsToRemove() == null
+                                ? Collections.<String>emptySet()
+                                : membershipMod.getVirAttrsToRemove(),
+                                membershipMod.getVirAttrsToUpdate() == null ? Collections.<AttrMod>emptySet()
+                                        : membershipMod.getVirAttrsToUpdate(), attrUtilFactory.getInstance(
+                                        AttributableType.MEMBERSHIP));
+                    }
+                }
+            }
+        }
+
+        if (propByRes == null || propByRes.isEmpty()) {
+            localPropByRes.addAll(ResourceOperation.UPDATE, subject.getResourceNames());
+        } else {
+            localPropByRes.merge(propByRes);
+        }
+
+        if (noPropResourceNames != null) {
+            localPropByRes.removeAll(noPropResourceNames);
+        }
+
+        Map<String, AttrMod> vAttrsToBeUpdatedMap = null;
+        if (vAttrsToBeUpdated != null) {
+            vAttrsToBeUpdatedMap = new HashMap<>();
+            for (AttrMod attrMod : vAttrsToBeUpdated) {
+                vAttrsToBeUpdatedMap.put(attrMod.getSchema(), attrMod);
+            }
+        }
+
+        // SYNCOPE-458 fill membership virtual attributes to be updated map
+        Map<String, AttrMod> membVAttrsToBeUpdatedMap = new HashMap<>();
+        for (MembershipMod membershipMod : membershipsToAdd) {
+            for (AttrMod attrMod : membershipMod.getVirAttrsToUpdate()) {
+                membVAttrsToBeUpdatedMap.put(attrMod.getSchema(), attrMod);
+            }
+        }
+
+        // SYNCOPE-458 fill membership virtual attributes to be removed set
+        final Set<String> membVAttrsToBeRemoved = new HashSet<>();
+        for (MembershipMod membershipMod : membershipsToAdd) {
+            membVAttrsToBeRemoved.addAll(membershipMod.getVirAttrsToRemove());
+        }
+
+        return createTasks(subject, password, changePwd,
+                vAttrsToBeRemoved, vAttrsToBeUpdatedMap, membVAttrsToBeRemoved, membVAttrsToBeUpdatedMap, enable, false,
+                localPropByRes);
+    }
+
+    /**
+     * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for
+     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
+     * the creation fails onto a mandatory resource.
+     *
+     * @param userKey to be deleted
+     * @return list of propagation tasks
+     * @throws NotFoundException if user is not found
+     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given user
+     */
+    @Override
+    public List<PropagationTask> getUserDeleteTaskIds(final Long userKey)
+            throws NotFoundException, UnauthorizedRoleException {
+
+        return getUserDeleteTaskIds(userKey, Collections.<String>emptySet());
+    }
+
+    /**
+     * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for
+     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
+     * the creation fails onto a mandatory resource.
+     *
+     * @param userKey to be deleted
+     * @param noPropResourceName name of external resource not to be considered for propagation
+     * @return list of propagation tasks
+     * @throws NotFoundException if user is not found
+     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given user
+     */
+    @Override
+    public List<PropagationTask> getUserDeleteTaskIds(final Long userKey, final String noPropResourceName)
+            throws NotFoundException, UnauthorizedRoleException {
+        return getUserDeleteTaskIds(userKey, Collections.<String>singleton(noPropResourceName));
+    }
+
+    /**
+     * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for
+     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
+     * the creation fails onto a mandatory resource.
+     *
+     * @param userKey to be deleted
+     * @param noPropResourceNames name of external resources not to be considered for propagation
+     * @return list of propagation tasks
+     * @throws NotFoundException if user is not found
+     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given user
+     */
+    @Override
+    public List<PropagationTask> getUserDeleteTaskIds(final Long userKey, final Collection<String> noPropResourceNames)
+            throws NotFoundException, UnauthorizedRoleException {
+
+        User user = userDAO.authFetch(userKey);
+        return getDeleteTaskIds(user, user.getResourceNames(), noPropResourceNames);
+    }
+
+    /**
+     * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for
+     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
+     * the creation fails onto a mandatory resource.
+     *
+     * @param userKey to be deleted
+     * @param resourceNames resource from which user is to be deleted
+     * @param noPropResourceNames name of external resources not to be considered for propagation
+     * @return list of propagation tasks
+     * @throws NotFoundException if user is not found
+     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given user
+     */
+    @Override
+    public List<PropagationTask> getUserDeleteTaskIds(
+            final Long userKey, final Set<String> resourceNames, final Collection<String> noPropResourceNames)
+            throws NotFoundException, UnauthorizedRoleException {
+
+        User user = userDAO.authFetch(userKey);
+        return getDeleteTaskIds(user, resourceNames, noPropResourceNames);
+    }
+
+    /**
+     * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for
+     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
+     * the creation fails onto a mandatory resource.
+     *
+     * @param wfResult user to be propagated (and info associated), as per result from workflow
+     * @return list of propagation tasks
+     */
+    @Override
+    public List<PropagationTask> getUserDeleteTaskIds(final WorkflowResult<Long> wfResult) {
+        User user = userDAO.authFetch(wfResult.getResult());
+        return createTasks(user, null, false, null, null, null, null, false, true, wfResult.getPropByRes());
+    }
+
+    /**
+     * Perform delete on each resource associated to the role. It is possible to ask for a mandatory provisioning for
+     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
+     * the creation fails onto a mandatory resource.
+     *
+     * @param roleKey to be deleted
+     * @return list of propagation tasks
+     * @throws NotFoundException if role is not found
+     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role
+     */
+    @Override
+    public List<PropagationTask> getRoleDeleteTaskIds(final Long roleKey)
+            throws NotFoundException, UnauthorizedRoleException {
+
+        return getRoleDeleteTaskIds(roleKey, Collections.<String>emptySet());
+    }
+
+    /**
+     * Perform delete on each resource associated to the role. It is possible to ask for a mandatory provisioning for
+     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
+     * the creation fails onto a mandatory resource.
+     *
+     * @param roleKey to be deleted
+     * @param noPropResourceName name of external resource not to be considered for propagation
+     * @return list of propagation tasks
+     * @throws NotFoundException if role is not found
+     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role
+     */
+    @Override
+    public List<PropagationTask> getRoleDeleteTaskIds(final Long roleKey, final String noPropResourceName)
+            throws NotFoundException, UnauthorizedRoleException {
+
+        return getRoleDeleteTaskIds(roleKey, Collections.<String>singleton(noPropResourceName));
+    }
+
+    /**
+     * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for
+     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
+     * the creation fails onto a mandatory resource.
+     *
+     * @param roleKey to be deleted
+     * @param noPropResourceNames name of external resources not to be considered for propagation
+     * @return list of propagation tasks
+     * @throws NotFoundException if role is not found
+     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role
+     */
+    @Override
+    public List<PropagationTask> getRoleDeleteTaskIds(final Long roleKey, final Collection<String> noPropResourceNames)
+            throws NotFoundException, UnauthorizedRoleException {
+
+        Role role = roleDAO.authFetch(roleKey);
+        return getDeleteTaskIds(role, role.getResourceNames(), noPropResourceNames);
+    }
+
+    /**
+     * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for
+     * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if
+     * the creation fails onto a mandatory resource.
+     *
+     * @param roleKey to be deleted
+     * @param resourceNames resource from which role is to be deleted
+     * @param noPropResourceNames name of external resources not to be considered for propagation
+     * @return list of propagation tasks
+     * @throws NotFoundException if role is not found
+     * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role
+     */
+    @Override
+    public List<PropagationTask> getRoleDeleteTaskIds(
+            final Long roleKey, final Set<String> resourceNames, final Collection<String> noPropResourceNames)
+            throws NotFoundException, UnauthorizedRoleException {
+
+        Role role = roleDAO.authFetch(roleKey);
+        return getDeleteTaskIds(role, resourceNames, noPropResourceNames);
+    }
+
+    protected List<PropagationTask> getDeleteTaskIds(
+            final Subject<?, ?, ?> subject,
+            final Set<String> resourceNames,
+            final Collection<String> noPropResourceNames) {
+
+        final PropagationByResource propByRes = new PropagationByResource();
+        propByRes.set(ResourceOperation.DELETE, resourceNames);
+        if (noPropResourceNames != null && !noPropResourceNames.isEmpty()) {
+            propByRes.get(ResourceOperation.DELETE).removeAll(noPropResourceNames);
+        }
+        return createTasks(subject, null, false, null, null, null, null, false, true, propByRes);
+    }
+
+    /**
+     * Create propagation tasks.
+     *
+     * @param subject user / role to be provisioned
+     * @param password cleartext password to be provisioned
+     * @param changePwd whether password should be included for propagation attributes or not
+     * @param vAttrsToBeRemoved virtual attributes to be removed
+     * @param vAttrsToBeUpdated virtual attributes to be added
+     * @param membVAttrsToBeRemoved membership virtual attributes to be removed
+     * @param membVAttrsToBeUpdatedMap membership virtual attributes to be added
+     * @param enable whether user must be enabled or not
+     * @param deleteOnResource whether user / role must be deleted anyway from external resource or not
+     * @param propByRes operation to be performed per resource
+     * @return list of propagation tasks created
+     */
+    protected List<PropagationTask> createTasks(final Subject<?, ?, ?> subject,
+            final String password, final boolean changePwd,
+            final Set<String> vAttrsToBeRemoved, final Map<String, AttrMod> vAttrsToBeUpdated,
+            final Set<String> membVAttrsToBeRemoved, final Map<String, AttrMod> membVAttrsToBeUpdatedMap,
+            final Boolean enable, final boolean deleteOnResource, final PropagationByResource propByRes) {
+
+        LOG.debug("Provisioning subject {}:\n{}", subject, propByRes);
+
+        final AttributableUtil attrUtil = attrUtilFactory.getInstance(subject);
+
+        if (!propByRes.get(ResourceOperation.CREATE).isEmpty()
+                && vAttrsToBeRemoved != null && vAttrsToBeUpdated != null) {
+
+            connObjectUtil.retrieveVirAttrValues(subject, attrUtil);
+
+            // update vAttrsToBeUpdated as well
+            for (VirAttr virAttr : subject.getVirAttrs()) {
+                final String schema = virAttr.getSchema().getKey();
+
+                final AttrMod attributeMod = new AttrMod();
+                attributeMod.setSchema(schema);
+                attributeMod.getValuesToBeAdded().addAll(virAttr.getValues());
+
+                vAttrsToBeUpdated.put(schema, attributeMod);
+            }
+        }
+
+        // Avoid duplicates - see javadoc
+        propByRes.purge();
+        LOG.debug("After purge: {}", propByRes);
+
+        final List<PropagationTask> tasks = new ArrayList<>();
+
+        for (ResourceOperation operation : ResourceOperation.values()) {
+            for (String resourceName : propByRes.get(operation)) {
+                final ExternalResource resource = resourceDAO.find(resourceName);
+                if (resource == null) {
+                    LOG.error("Invalid resource name specified: {}, ignoring...", resourceName);
+                } else if (attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION).isEmpty()) {
+                    LOG.warn("Requesting propagation for {} but no propagation mapping provided for {}",
+                            attrUtil.getType(), resource);
+                } else {
+                    PropagationTask task = entityFactory.newEntity(PropagationTask.class);
+                    task.setResource(resource);
+                    task.setObjectClassName(connObjectUtil.fromSubject(subject).getObjectClassValue());
+                    task.setSubjectType(attrUtil.getType());
+                    if (!deleteOnResource) {
+                        task.setSubjectKey(subject.getKey());
+                    }
+                    task.setPropagationOperation(operation);
+                    task.setPropagationMode(resource.getPropagationMode());
+                    task.setOldAccountId(propByRes.getOldAccountId(resource.getKey()));
+
+                    Map.Entry<String, Set<Attribute>> preparedAttrs = MappingUtil.prepareAttributes(attrUtil, subject,
+                            password, changePwd, vAttrsToBeRemoved, vAttrsToBeUpdated, membVAttrsToBeRemoved,
+                            membVAttrsToBeUpdatedMap, enable, resource);
+                    task.setAccountId(preparedAttrs.getKey());
+
+                    // Check if any of mandatory attributes (in the mapping) is missing or not received any value: 
+                    // if so, add special attributes that will be evaluated by PropagationTaskExecutor
+                    List<String> mandatoryMissing = new ArrayList<>();
+                    List<String> mandatoryNullOrEmpty = new ArrayList<>();
+                    for (MappingItem item : attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION)) {
+                        if (!item.isAccountid()
+                                && JexlUtil.evaluateMandatoryCondition(item.getMandatoryCondition(), subject)) {
+
+                            Attribute attr = AttributeUtil.find(item.getExtAttrName(), preparedAttrs.getValue());
+                            if (attr == null) {
+                                mandatoryMissing.add(item.getExtAttrName());
+                            } else if (attr.getValue() == null || attr.getValue().isEmpty()) {
+                                mandatoryNullOrEmpty.add(item.getExtAttrName());
+                            }
+                        }
+                    }
+                    if (!mandatoryMissing.isEmpty()) {
+                        preparedAttrs.getValue().add(AttributeBuilder.build(
+                                PropagationTaskExecutor.MANDATORY_MISSING_ATTR_NAME, mandatoryMissing));
+                    }
+                    if (!mandatoryNullOrEmpty.isEmpty()) {
+                        preparedAttrs.getValue().add(AttributeBuilder.build(
+                                PropagationTaskExecutor.MANDATORY_NULL_OR_EMPTY_ATTR_NAME, mandatoryNullOrEmpty));
+                    }
+
+                    task.setAttributes(preparedAttrs.getValue());
+                    tasks.add(task);
+
+                    LOG.debug("PropagationTask created: {}", task);
+                }
+            }
+        }
+
+        return tasks;
+    }
+
+    protected MembershipTO findMembershipTO(final Membership membership, final Collection<MembershipTO> memberships) {
+        for (MembershipTO membershipTO : memberships) {
+            if (membershipTO.getRoleId() == membership.getRole().getKey()) {
+                return membershipTO;
+            }
+        }
+        LOG.error("No MembershipTO found for membership {}", membership);
+        return null;
+    }
+
+    protected MembershipMod findMembershipMod(final Membership membership, final Set<MembershipMod> membershipMods) {
+        for (MembershipMod membershipMod : membershipMods) {
+            if (membershipMod.getRole() == membership.getRole().getKey()) {
+                return membershipMod;
+            }
+        }
+        LOG.error("No MembershipMod found for membership {}", membership);
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/AbstractProvisioningJob.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/AbstractProvisioningJob.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/AbstractProvisioningJob.java
new file mode 100644
index 0000000..f99e179
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/AbstractProvisioningJob.java
@@ -0,0 +1,372 @@
+/*
+ * 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.sync;
+
+import java.lang.reflect.ParameterizedType;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.syncope.common.lib.types.TraceLevel;
+import org.apache.syncope.server.persistence.api.dao.EntitlementDAO;
+import org.apache.syncope.server.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.server.persistence.api.dao.PolicyDAO;
+import org.apache.syncope.server.persistence.api.entity.Entitlement;
+import org.apache.syncope.server.persistence.api.entity.role.RMapping;
+import org.apache.syncope.server.persistence.api.entity.task.ProvisioningTask;
+import org.apache.syncope.server.persistence.api.entity.task.PushTask;
+import org.apache.syncope.server.persistence.api.entity.task.SyncTask;
+import org.apache.syncope.server.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.server.persistence.api.entity.user.UMapping;
+import org.apache.syncope.server.provisioning.api.Connector;
+import org.apache.syncope.server.provisioning.api.ConnectorFactory;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningActions;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningResult;
+import org.apache.syncope.server.provisioning.java.job.AbstractTaskJob;
+import org.quartz.JobExecutionException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+
+/**
+ * Job for executing synchronization tasks.
+ *
+ * @see AbstractTaskJob
+ * @see SyncTask
+ * @see PushTask
+ */
+public abstract class AbstractProvisioningJob<T extends ProvisioningTask, A extends ProvisioningActions>
+        extends AbstractTaskJob {
+
+    /**
+     * ConnInstance loader.
+     */
+    @Autowired
+    protected ConnectorFactory connFactory;
+
+    /**
+     * Resource DAO.
+     */
+    @Autowired
+    protected ExternalResourceDAO resourceDAO;
+
+    /**
+     * Entitlement DAO.
+     */
+    @Autowired
+    protected EntitlementDAO entitlementDAO;
+
+    /**
+     * Policy DAO.
+     */
+    @Autowired
+    protected PolicyDAO policyDAO;
+
+    /**
+     * SyncJob actions.
+     */
+    protected List<A> actions;
+
+    public void setActions(final List<A> actions) {
+        this.actions = actions;
+    }
+
+    /**
+     * Create a textual report of the synchronization, based on the trace level.
+     *
+     * @param provResults Sync results
+     * @param syncTraceLevel Sync trace level
+     * @param dryRun dry run?
+     * @return report as string
+     */
+    protected String createReport(final List<ProvisioningResult> provResults, final TraceLevel syncTraceLevel,
+            final boolean dryRun) {
+
+        if (syncTraceLevel == TraceLevel.NONE) {
+            return null;
+        }
+
+        StringBuilder report = new StringBuilder();
+
+        if (dryRun) {
+            report.append("==>Dry run only, no modifications were made<==\n\n");
+        }
+
+        List<ProvisioningResult> uSuccCreate = new ArrayList<>();
+        List<ProvisioningResult> uFailCreate = new ArrayList<>();
+        List<ProvisioningResult> uSuccUpdate = new ArrayList<>();
+        List<ProvisioningResult> uFailUpdate = new ArrayList<>();
+        List<ProvisioningResult> uSuccDelete = new ArrayList<>();
+        List<ProvisioningResult> uFailDelete = new ArrayList<>();
+        List<ProvisioningResult> rSuccCreate = new ArrayList<>();
+        List<ProvisioningResult> rFailCreate = new ArrayList<>();
+        List<ProvisioningResult> rSuccUpdate = new ArrayList<>();
+        List<ProvisioningResult> rFailUpdate = new ArrayList<>();
+        List<ProvisioningResult> rSuccDelete = new ArrayList<>();
+        List<ProvisioningResult> rFailDelete = new ArrayList<>();
+
+        for (ProvisioningResult provResult : provResults) {
+            switch (provResult.getStatus()) {
+                case SUCCESS:
+                    switch (provResult.getOperation()) {
+                        case CREATE:
+                            switch (provResult.getSubjectType()) {
+                                case USER:
+                                    uSuccCreate.add(provResult);
+                                    break;
+
+                                case ROLE:
+                                    rSuccCreate.add(provResult);
+                                    break;
+
+                                default:
+                            }
+                            break;
+
+                        case UPDATE:
+                            switch (provResult.getSubjectType()) {
+                                case USER:
+                                    uSuccUpdate.add(provResult);
+                                    break;
+
+                                case ROLE:
+                                    rSuccUpdate.add(provResult);
+                                    break;
+
+                                default:
+                            }
+                            break;
+
+                        case DELETE:
+                            switch (provResult.getSubjectType()) {
+                                case USER:
+                                    uSuccDelete.add(provResult);
+                                    break;
+
+                                case ROLE:
+                                    rSuccDelete.add(provResult);
+                                    break;
+
+                                default:
+                            }
+                            break;
+
+                        default:
+                    }
+                    break;
+
+                case FAILURE:
+                    switch (provResult.getOperation()) {
+                        case CREATE:
+                            switch (provResult.getSubjectType()) {
+                                case USER:
+                                    uFailCreate.add(provResult);
+                                    break;
+
+                                case ROLE:
+                                    rFailCreate.add(provResult);
+                                    break;
+
+                                default:
+                            }
+                            break;
+
+                        case UPDATE:
+                            switch (provResult.getSubjectType()) {
+                                case USER:
+                                    uFailUpdate.add(provResult);
+                                    break;
+
+                                case ROLE:
+                                    rFailUpdate.add(provResult);
+                                    break;
+
+                                default:
+                            }
+                            break;
+
+                        case DELETE:
+                            switch (provResult.getSubjectType()) {
+                                case USER:
+                                    uFailDelete.add(provResult);
+                                    break;
+
+                                case ROLE:
+                                    rFailDelete.add(provResult);
+                                    break;
+
+                                default:
+                            }
+                            break;
+
+                        default:
+                    }
+                    break;
+
+                default:
+            }
+        }
+
+        // Summary, also to be included for FAILURE and ALL, so create it anyway.
+        report.append("Users ").
+                append("[created/failures]: ").append(uSuccCreate.size()).append('/').append(uFailCreate.size()).
+                append(' ').
+                append("[updated/failures]: ").append(uSuccUpdate.size()).append('/').append(uFailUpdate.size()).
+                append(' ').
+                append("[deleted/failures]: ").append(uSuccDelete.size()).append('/').append(uFailDelete.size()).
+                append('\n');
+        report.append("Roles ").
+                append("[created/failures]: ").append(rSuccCreate.size()).append('/').append(rFailCreate.size()).
+                append(' ').
+                append("[updated/failures]: ").append(rSuccUpdate.size()).append('/').append(rFailUpdate.size()).
+                append(' ').
+                append("[deleted/failures]: ").append(rSuccDelete.size()).append('/').append(rFailDelete.size());
+
+        // Failures
+        if (syncTraceLevel == TraceLevel.FAILURES || syncTraceLevel == TraceLevel.ALL) {
+            if (!uFailCreate.isEmpty()) {
+                report.append("\n\nUsers failed to create: ");
+                report.append(ProvisioningResult.produceReport(uFailCreate, syncTraceLevel));
+            }
+            if (!uFailUpdate.isEmpty()) {
+                report.append("\nUsers failed to update: ");
+                report.append(ProvisioningResult.produceReport(uFailUpdate, syncTraceLevel));
+            }
+            if (!uFailDelete.isEmpty()) {
+                report.append("\nUsers failed to delete: ");
+                report.append(ProvisioningResult.produceReport(uFailDelete, syncTraceLevel));
+            }
+
+            if (!rFailCreate.isEmpty()) {
+                report.append("\n\nRoles failed to create: ");
+                report.append(ProvisioningResult.produceReport(rFailCreate, syncTraceLevel));
+            }
+            if (!rFailUpdate.isEmpty()) {
+                report.append("\nRoles failed to update: ");
+                report.append(ProvisioningResult.produceReport(rFailUpdate, syncTraceLevel));
+            }
+            if (!rFailDelete.isEmpty()) {
+                report.append("\nRoles failed to delete: ");
+                report.append(ProvisioningResult.produceReport(rFailDelete, syncTraceLevel));
+            }
+        }
+
+        // Succeeded, only if on 'ALL' level
+        if (syncTraceLevel == TraceLevel.ALL) {
+            report.append("\n\nUsers created:\n")
+                    .append(ProvisioningResult.produceReport(uSuccCreate, syncTraceLevel))
+                    .append("\nUsers updated:\n")
+                    .append(ProvisioningResult.produceReport(uSuccUpdate, syncTraceLevel))
+                    .append("\nUsers deleted:\n")
+                    .append(ProvisioningResult.produceReport(uSuccDelete, syncTraceLevel));
+            report.append("\n\nRoles created:\n")
+                    .append(ProvisioningResult.produceReport(rSuccCreate, syncTraceLevel))
+                    .append("\nRoles updated:\n")
+                    .append(ProvisioningResult.produceReport(rSuccUpdate, syncTraceLevel))
+                    .append("\nRoles deleted:\n")
+                    .append(ProvisioningResult.produceReport(rSuccDelete, syncTraceLevel));
+        }
+
+        return report.toString();
+    }
+
+    @Override
+    protected String doExecute(final boolean dryRun) throws JobExecutionException {
+        // PRE: grant all authorities (i.e. setup the SecurityContextHolder)
+        final List<GrantedAuthority> authorities = new ArrayList<>();
+
+        for (Entitlement entitlement : entitlementDAO.findAll()) {
+            authorities.add(new SimpleGrantedAuthority(entitlement.getKey()));
+        }
+
+        final UserDetails userDetails = new User("admin", "FAKE_PASSWORD", true, true, true, true, authorities);
+
+        SecurityContextHolder.getContext().setAuthentication(
+                new UsernamePasswordAuthenticationToken(userDetails, "FAKE_PASSWORD", authorities));
+
+        try {
+            final Class<T> clazz = getTaskClassReference();
+            if (!clazz.isAssignableFrom(task.getClass())) {
+                throw new JobExecutionException("Task " + taskId + " isn't a SyncTask");
+            }
+
+            final T syncTask = clazz.cast(this.task);
+
+            final Connector connector;
+            try {
+                connector = connFactory.getConnector(syncTask.getResource());
+            } catch (Exception e) {
+                final String msg = String.
+                        format("Connector instance bean for resource %s and connInstance %s not found",
+                                syncTask.getResource(), syncTask.getResource().getConnector());
+
+                throw new JobExecutionException(msg, e);
+            }
+
+            final UMapping uMapping = syncTask.getResource().getUmapping();
+            if (uMapping != null && uMapping.getAccountIdItem() == null) {
+                throw new JobExecutionException(
+                        "Invalid user account id mapping for resource " + syncTask.getResource());
+            }
+            final RMapping rMapping = syncTask.getResource().getRmapping();
+            if (rMapping != null && rMapping.getAccountIdItem() == null) {
+                throw new JobExecutionException(
+                        "Invalid role account id mapping for resource " + syncTask.getResource());
+            }
+            if (uMapping == null && rMapping == null) {
+                return "No mapping configured for both users and roles: aborting...";
+            }
+
+            return executeWithSecurityContext(
+                    syncTask,
+                    connector,
+                    uMapping,
+                    rMapping,
+                    dryRun);
+
+        } finally {
+            // POST: clean up the SecurityContextHolder
+            SecurityContextHolder.clearContext();
+        }
+    }
+
+    protected abstract String executeWithSecurityContext(
+            final T task,
+            final Connector connector,
+            final UMapping uMapping,
+            final RMapping rMapping,
+            final boolean dryRun) throws JobExecutionException;
+
+    @Override
+    protected boolean hasToBeRegistered(final TaskExec execution) {
+        final ProvisioningTask provTask = (ProvisioningTask) task;
+
+        // True if either failed and failures have to be registered, or if ALL has to be registered.
+        return (Status.valueOf(execution.getStatus()) == Status.FAILURE
+                && provTask.getResource().getSyncTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal())
+                || provTask.getResource().getSyncTraceLevel().ordinal() >= TraceLevel.SUMMARY.ordinal();
+    }
+
+    @SuppressWarnings("unchecked")
+    private Class<T> getTaskClassReference() {
+        return (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/AbstractSubjectPushResultHandler.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/AbstractSubjectPushResultHandler.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/AbstractSubjectPushResultHandler.java
new file mode 100644
index 0000000..f0b70cd
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/AbstractSubjectPushResultHandler.java
@@ -0,0 +1,371 @@
+/*
+ * 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.sync;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.apache.syncope.common.lib.mod.AttrMod;
+import org.apache.syncope.common.lib.mod.MembershipMod;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditElements.Result;
+import org.apache.syncope.common.lib.types.IntMappingType;
+import org.apache.syncope.common.lib.types.MatchingRule;
+import org.apache.syncope.common.lib.types.PropagationByResource;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.common.lib.types.UnmatchingRule;
+import org.apache.syncope.server.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.server.persistence.api.entity.Mapping;
+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.membership.Membership;
+import org.apache.syncope.server.persistence.api.entity.task.PushTask;
+import org.apache.syncope.server.persistence.api.entity.user.User;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningResult;
+import org.apache.syncope.server.provisioning.api.sync.PushActions;
+import org.apache.syncope.server.misc.MappingUtil;
+import org.identityconnectors.framework.common.objects.ConnectorObject;
+import org.quartz.JobExecutionException;
+import org.springframework.transaction.annotation.Transactional;
+
+public abstract class AbstractSubjectPushResultHandler extends AbstractSyncopeResultHandler<PushTask, PushActions> {
+
+    protected abstract String getName(final Subject<?, ?, ?> subject);
+
+    protected abstract Mapping<?> getMapping();
+
+    protected abstract AbstractSubjectTO getSubjectTO(final long key);
+
+    protected abstract Subject<?, ?, ?> getSubject(final long key);
+
+    protected abstract Subject<?, ?, ?> deprovision(final Subject<?, ?, ?> sbj);
+
+    protected abstract Subject<?, ?, ?> provision(final Subject<?, ?, ?> sbj, final Boolean enabled);
+
+    protected abstract Subject<?, ?, ?> link(final Subject<?, ?, ?> sbj, final Boolean unlink);
+
+    protected abstract Subject<?, ?, ?> unassign(final Subject<?, ?, ?> sbj);
+
+    protected abstract Subject<?, ?, ?> assign(final Subject<?, ?, ?> sbj, Boolean enabled);
+
+    protected abstract ConnectorObject getRemoteObject(final String accountId);
+
+    @Transactional
+    public boolean handle(final long subjectId) {
+        try {
+            doHandle(subjectId);
+            return true;
+        } catch (JobExecutionException e) {
+            LOG.error("Synchronization failed", e);
+            return false;
+        }
+    }
+
+    protected final void doHandle(final long subjectId)
+            throws JobExecutionException {
+
+        final Subject<?, ?, ?> subject = getSubject(subjectId);
+
+        final AttributableUtil attrUtil = attrUtilFactory.getInstance(subject);
+
+        final ProvisioningResult result = new ProvisioningResult();
+        profile.getResults().add(result);
+
+        result.setId(subject.getKey());
+        result.setSubjectType(attrUtil.getType());
+        result.setName(getName(subject));
+
+        final Boolean enabled = subject instanceof User && profile.getTask().isSyncStatus()
+                ? ((User) subject).isSuspended() ? Boolean.FALSE : Boolean.TRUE
+                : null;
+
+        LOG.debug("Propagating {} with key {} towards {}",
+                attrUtil.getType(), subject.getKey(), profile.getTask().getResource());
+
+        Object output = null;
+        Result resultStatus = null;
+        ConnectorObject beforeObj = null;
+        String operation = null;
+
+        // Try to read remote object (user / group) BEFORE any actual operation
+        final String accountId = MappingUtil.getAccountIdValue(
+                subject, profile.getTask().getResource(), getMapping().getAccountIdItem());
+
+        beforeObj = getRemoteObject(accountId);
+
+        Boolean status = profile.getTask().isSyncStatus() ? enabled : null;
+
+        if (profile.isDryRun()) {
+            if (beforeObj == null) {
+                result.setOperation(getResourceOperation(profile.getTask().getUnmatchingRule()));
+            } else {
+                result.setOperation(getResourceOperation(profile.getTask().getMatchingRule()));
+            }
+            result.setStatus(ProvisioningResult.Status.SUCCESS);
+        } else {
+            try {
+                if (beforeObj == null) {
+                    operation = profile.getTask().getUnmatchingRule().name().toLowerCase();
+                    result.setOperation(getResourceOperation(profile.getTask().getUnmatchingRule()));
+
+                    switch (profile.getTask().getUnmatchingRule()) {
+                        case ASSIGN:
+                            for (PushActions action : profile.getActions()) {
+                                action.beforeAssign(this.getProfile(), subject);
+                            }
+
+                            if (!profile.getTask().isPerformCreate()) {
+                                LOG.debug("PushTask not configured for create");
+                            } else {
+                                assign(subject, status);
+                            }
+
+                            break;
+                        case PROVISION:
+                            for (PushActions action : profile.getActions()) {
+                                action.beforeProvision(this.getProfile(), subject);
+                            }
+
+                            if (!profile.getTask().isPerformCreate()) {
+                                LOG.debug("PushTask not configured for create");
+                            } else {
+                                provision(subject, status);
+                            }
+
+                            break;
+                        case UNLINK:
+                            for (PushActions action : profile.getActions()) {
+                                action.beforeUnlink(this.getProfile(), subject);
+                            }
+
+                            if (!profile.getTask().isPerformUpdate()) {
+                                LOG.debug("PushTask not configured for update");
+                            } else {
+                                link(subject, true);
+                            }
+
+                            break;
+                        default:
+                        // do nothing
+                    }
+
+                } else {
+                    operation = profile.getTask().getMatchingRule().name().toLowerCase();
+                    result.setOperation(getResourceOperation(profile.getTask().getMatchingRule()));
+
+                    switch (profile.getTask().getMatchingRule()) {
+                        case UPDATE:
+                            for (PushActions action : profile.getActions()) {
+                                action.beforeUpdate(this.getProfile(), subject);
+                            }
+                            if (!profile.getTask().isPerformUpdate()) {
+                                LOG.debug("PushTask not configured for update");
+                            } else {
+                                update(subject, status);
+                            }
+
+                            break;
+                        case DEPROVISION:
+                            for (PushActions action : profile.getActions()) {
+                                action.beforeDeprovision(this.getProfile(), subject);
+                            }
+
+                            if (!profile.getTask().isPerformDelete()) {
+                                LOG.debug("PushTask not configured for delete");
+                            } else {
+                                deprovision(subject);
+                            }
+
+                            break;
+                        case UNASSIGN:
+                            for (PushActions action : profile.getActions()) {
+                                action.beforeUnassign(this.getProfile(), subject);
+                            }
+
+                            if (!profile.getTask().isPerformDelete()) {
+                                LOG.debug("PushTask not configured for delete");
+                            } else {
+                                unassign(subject);
+                            }
+
+                            break;
+                        case LINK:
+                            for (PushActions action : profile.getActions()) {
+                                action.beforeLink(this.getProfile(), subject);
+                            }
+
+                            if (!profile.getTask().isPerformUpdate()) {
+                                LOG.debug("PushTask not configured for update");
+                            } else {
+                                link(subject, false);
+                            }
+
+                            break;
+                        case UNLINK:
+                            for (PushActions action : profile.getActions()) {
+                                action.beforeUnlink(this.getProfile(), subject);
+                            }
+
+                            if (!profile.getTask().isPerformUpdate()) {
+                                LOG.debug("PushTask not configured for update");
+                            } else {
+                                link(subject, true);
+                            }
+
+                            break;
+                        default:
+                        // do nothing
+                    }
+                }
+
+                for (PushActions action : profile.getActions()) {
+                    action.after(this.getProfile(), subject, result);
+                }
+
+                result.setStatus(ProvisioningResult.Status.SUCCESS);
+                resultStatus = AuditElements.Result.SUCCESS;
+                output = getRemoteObject(accountId);
+            } catch (Exception e) {
+                result.setStatus(ProvisioningResult.Status.FAILURE);
+                result.setMessage(ExceptionUtils.getRootCauseMessage(e));
+                resultStatus = AuditElements.Result.FAILURE;
+                output = e;
+
+                LOG.warn("Error pushing {} towards {}", subject, profile.getTask().getResource(), e);
+                throw new JobExecutionException(e);
+            } finally {
+                notificationManager.createTasks(
+                        AuditElements.EventCategoryType.PUSH,
+                        AttributableType.USER.name().toLowerCase(),
+                        profile.getTask().getResource().getKey(),
+                        operation,
+                        resultStatus,
+                        beforeObj,
+                        output,
+                        subject);
+                auditManager.audit(
+                        AuditElements.EventCategoryType.PUSH,
+                        AttributableType.USER.name().toLowerCase(),
+                        profile.getTask().getResource().getKey(),
+                        operation,
+                        resultStatus,
+                        beforeObj,
+                        output,
+                        subject);
+            }
+        }
+    }
+
+    private ResourceOperation getResourceOperation(final UnmatchingRule rule) {
+        switch (rule) {
+            case ASSIGN:
+            case PROVISION:
+                return ResourceOperation.CREATE;
+            default:
+                return ResourceOperation.NONE;
+        }
+    }
+
+    private ResourceOperation getResourceOperation(final MatchingRule rule) {
+        switch (rule) {
+            case UPDATE:
+                return ResourceOperation.UPDATE;
+            case DEPROVISION:
+            case UNASSIGN:
+                return ResourceOperation.DELETE;
+            default:
+                return ResourceOperation.NONE;
+        }
+    }
+
+    protected Subject<?, ?, ?> update(final Subject<?, ?, ?> sbj, final Boolean enabled) {
+
+        final Set<MembershipMod> membsToAdd = new HashSet<>();
+        final Set<String> vattrToBeRemoved = new HashSet<>();
+        final Set<String> membVattrToBeRemoved = new HashSet<>();
+        final Set<AttrMod> vattrToBeUpdated = new HashSet<>();
+
+        // Search for all mapped vattrs
+        final Mapping<?> umapping = getMapping();
+        for (MappingItem mappingItem : umapping.getItems()) {
+            if (mappingItem.getIntMappingType() == IntMappingType.UserVirtualSchema) {
+                vattrToBeRemoved.add(mappingItem.getIntAttrName());
+            } else if (mappingItem.getIntMappingType() == IntMappingType.MembershipVirtualSchema) {
+                membVattrToBeRemoved.add(mappingItem.getIntAttrName());
+            }
+        }
+
+        // Search for all user's vattrs and:
+        // 1. add mapped vattrs not owned by the user to the set of vattrs to be removed
+        // 2. add all vattrs owned by the user to the set of vattrs to be update
+        for (VirAttr vattr : sbj.getVirAttrs()) {
+            vattrToBeRemoved.remove(vattr.getSchema().getKey());
+            final AttrMod mod = new AttrMod();
+            mod.setSchema(vattr.getSchema().getKey());
+            mod.getValuesToBeAdded().addAll(vattr.getValues());
+            vattrToBeUpdated.add(mod);
+        }
+
+        final boolean changepwd;
+
+        if (sbj instanceof User) {
+            changepwd = true;
+
+            // Search for memberships
+            for (Membership membership : User.class.cast(sbj).getMemberships()) {
+                final MembershipMod membershipMod = new MembershipMod();
+                membershipMod.setKey(membership.getKey());
+                membershipMod.setRole(membership.getRole().getKey());
+
+                for (VirAttr vattr : membership.getVirAttrs()) {
+                    membVattrToBeRemoved.remove(vattr.getSchema().getKey());
+                    final AttrMod mod = new AttrMod();
+                    mod.setSchema(vattr.getSchema().getKey());
+                    mod.getValuesToBeAdded().addAll(vattr.getValues());
+                    membershipMod.getVirAttrsToUpdate().add(mod);
+                }
+
+                membsToAdd.add(membershipMod);
+            }
+
+            if (!membsToAdd.isEmpty()) {
+                membsToAdd.iterator().next().getVirAttrsToRemove().addAll(membVattrToBeRemoved);
+            }
+        } else {
+            changepwd = false;
+        }
+
+        final List<String> noPropResources = new ArrayList<>(sbj.getResourceNames());
+        noPropResources.remove(profile.getTask().getResource().getKey());
+
+        final PropagationByResource propByRes = new PropagationByResource();
+        propByRes.add(ResourceOperation.CREATE, profile.getTask().getResource().getKey());
+
+        taskExecutor.execute(propagationManager.getUpdateTaskIds(
+                sbj, null, changepwd, enabled, vattrToBeRemoved, vattrToBeUpdated, propByRes, noPropResources,
+                membsToAdd));
+
+        return userDAO.authFetch(sbj.getKey());
+    }
+}