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:44 UTC

[05/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/sync/AbstractSubjectSyncResultHandler.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/AbstractSubjectSyncResultHandler.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/AbstractSubjectSyncResultHandler.java
new file mode 100644
index 0000000..3c49eee
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/AbstractSubjectSyncResultHandler.java
@@ -0,0 +1,624 @@
+/*
+ * 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.Collection;
+import java.util.Collections;
+import java.util.List;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.apache.syncope.common.lib.mod.AbstractSubjectMod;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditElements.Result;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.server.persistence.api.dao.NotFoundException;
+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.task.SyncTask;
+import org.apache.syncope.server.provisioning.api.AttributableTransformer;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationException;
+import org.apache.syncope.server.provisioning.api.sync.SyncActions;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningResult;
+import org.apache.syncope.server.misc.security.UnauthorizedRoleException;
+import org.identityconnectors.framework.common.objects.SyncDelta;
+import org.identityconnectors.framework.common.objects.SyncDeltaType;
+import org.identityconnectors.framework.common.objects.SyncResultsHandler;
+import org.quartz.JobExecutionException;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public abstract class AbstractSubjectSyncResultHandler extends AbstractSyncopeResultHandler<SyncTask, SyncActions>
+        implements SyncResultsHandler {
+
+    @Autowired
+    protected SyncUtilities syncUtilities;
+
+    @Autowired
+    protected AttributableTransformer attrTransformer;
+
+    protected abstract AttributableUtil getAttributableUtil();
+
+    protected abstract String getName(AbstractSubjectTO subjectTO);
+
+    protected abstract AbstractSubjectTO getSubjectTO(long key);
+
+    protected abstract AbstractSubjectMod getSubjectMod(AbstractSubjectTO subjectTO, SyncDelta delta);
+
+    protected abstract AbstractSubjectTO create(AbstractSubjectTO subjectTO, SyncDelta _delta, ProvisioningResult result);
+
+    protected abstract AbstractSubjectTO link(AbstractSubjectTO before, ProvisioningResult result, boolean unlink);
+
+    protected abstract AbstractSubjectTO update(AbstractSubjectTO before, AbstractSubjectMod subjectMod,
+            SyncDelta delta, ProvisioningResult result);
+
+    protected abstract void deprovision(Long key, boolean unlink);
+
+    protected abstract void delete(Long key);
+
+    @Override
+    public boolean handle(final SyncDelta delta) {
+        try {
+            doHandle(delta, profile.getResults());
+            return true;
+        } catch (JobExecutionException e) {
+            LOG.error("Synchronization failed", e);
+            return false;
+        }
+    }
+
+    protected List<ProvisioningResult> assign(final SyncDelta delta, final AttributableUtil attrUtil)
+            throws JobExecutionException {
+        if (!profile.getTask().isPerformCreate()) {
+            LOG.debug("SyncTask not configured for create");
+            return Collections.<ProvisioningResult>emptyList();
+        }
+
+        final AbstractSubjectTO subjectTO =
+                connObjectUtil.getSubjectTO(delta.getObject(), profile.getTask(), attrUtil);
+
+        subjectTO.getResources().add(profile.getTask().getResource().getKey());
+
+        final ProvisioningResult result = new ProvisioningResult();
+        result.setOperation(ResourceOperation.CREATE);
+        result.setSubjectType(attrUtil.getType());
+        result.setStatus(ProvisioningResult.Status.SUCCESS);
+
+        // Attributable transformation (if configured)
+        AbstractSubjectTO transformed = attrTransformer.transform(subjectTO);
+        LOG.debug("Transformed: {}", transformed);
+
+        result.setName(getName(transformed));
+
+        if (profile.isDryRun()) {
+            result.setId(0L);
+        } else {
+            SyncDelta _delta = delta;
+            for (SyncActions action : profile.getActions()) {
+                _delta = action.beforeAssign(this.getProfile(), _delta, transformed);
+            }
+
+            create(transformed, _delta, attrUtil, "assign", result);
+        }
+
+        return Collections.singletonList(result);
+    }
+
+    protected List<ProvisioningResult> create(final SyncDelta delta, final AttributableUtil attrUtil)
+            throws JobExecutionException {
+
+        if (!profile.getTask().isPerformCreate()) {
+            LOG.debug("SyncTask not configured for create");
+            return Collections.<ProvisioningResult>emptyList();
+        }
+
+        final AbstractSubjectTO subjectTO =
+                connObjectUtil.getSubjectTO(delta.getObject(), profile.getTask(), attrUtil);
+
+        // Attributable transformation (if configured)
+        AbstractSubjectTO transformed = attrTransformer.transform(subjectTO);
+        LOG.debug("Transformed: {}", transformed);
+
+        final ProvisioningResult result = new ProvisioningResult();
+        result.setOperation(ResourceOperation.CREATE);
+        result.setSubjectType(attrUtil.getType());
+        result.setStatus(ProvisioningResult.Status.SUCCESS);
+
+        result.setName(getName(transformed));
+
+        if (profile.isDryRun()) {
+            result.setId(0L);
+        } else {
+            SyncDelta _delta = delta;
+            for (SyncActions action : profile.getActions()) {
+                _delta = action.beforeProvision(this.getProfile(), _delta, transformed);
+            }
+
+            create(transformed, _delta, attrUtil, "provision", result);
+        }
+
+        return Collections.<ProvisioningResult>singletonList(result);
+    }
+
+    private void create(
+            final AbstractSubjectTO subjectTO,
+            final SyncDelta delta,
+            final AttributableUtil attrUtil,
+            final String operation,
+            final ProvisioningResult result)
+            throws JobExecutionException {
+
+        Object output;
+        Result resultStatus;
+
+        try {
+            AbstractSubjectTO actual = create(subjectTO, delta, result);
+            result.setName(getName(actual));
+            output = actual;
+            resultStatus = Result.SUCCESS;
+
+            for (SyncActions action : profile.getActions()) {
+                action.after(this.getProfile(), delta, actual, result);
+            }
+        } catch (PropagationException e) {
+            // A propagation failure doesn't imply a synchronization failure.
+            // The propagation exception status will be reported into the propagation task execution.
+            LOG.error("Could not propagate {} {}", attrUtil.getType(), delta.getUid().getUidValue(), e);
+            output = e;
+            resultStatus = Result.FAILURE;
+        } catch (Exception e) {
+            result.setStatus(ProvisioningResult.Status.FAILURE);
+            result.setMessage(ExceptionUtils.getRootCauseMessage(e));
+            LOG.error("Could not create {} {} ", attrUtil.getType(), delta.getUid().getUidValue(), e);
+            output = e;
+            resultStatus = Result.FAILURE;
+        }
+
+        audit(operation, resultStatus, null, output, delta);
+    }
+
+    protected List<ProvisioningResult> update(SyncDelta delta, final List<Long> subjects,
+            final AttributableUtil attrUtil)
+            throws JobExecutionException {
+
+        if (!profile.getTask().isPerformUpdate()) {
+            LOG.debug("SyncTask not configured for update");
+            return Collections.<ProvisioningResult>emptyList();
+        }
+
+        LOG.debug("About to update {}", subjects);
+
+        List<ProvisioningResult> updResults = new ArrayList<>();
+
+        for (Long id : subjects) {
+            LOG.debug("About to update {}", id);
+
+            Object output;
+            AbstractSubjectTO before = null;
+            Result resultStatus;
+
+            final ProvisioningResult result = new ProvisioningResult();
+            result.setOperation(ResourceOperation.UPDATE);
+            result.setSubjectType(attrUtil.getType());
+            result.setStatus(ProvisioningResult.Status.SUCCESS);
+            result.setId(id);
+
+            before = getSubjectTO(id);
+
+            if (before == null) {
+                result.setStatus(ProvisioningResult.Status.FAILURE);
+                result.setMessage(String.format("Subject '%s(%d)' not found", attrUtil.getType().name(), id));
+            } else {
+                result.setName(getName(before));
+            }
+
+            if (!profile.isDryRun()) {
+                if (before == null) {
+                    resultStatus = Result.FAILURE;
+                    output = null;
+                } else {
+                    try {
+                        final AbstractSubjectMod attributableMod = getSubjectMod(before, delta);
+
+                        // Attribute value transformation (if configured)
+                        final AbstractSubjectMod actual = attrTransformer.transform(attributableMod);
+                        LOG.debug("Transformed: {}", actual);
+
+                        for (SyncActions action : profile.getActions()) {
+                            delta = action.beforeUpdate(this.getProfile(), delta, before, attributableMod);
+                        }
+
+                        final AbstractSubjectTO updated = update(before, attributableMod, delta, result);
+
+                        for (SyncActions action : profile.getActions()) {
+                            action.after(this.getProfile(), delta, updated, result);
+                        }
+
+                        output = updated;
+                        resultStatus = Result.SUCCESS;
+                        result.setName(getName(updated));
+                        LOG.debug("{} {} successfully updated", attrUtil.getType(), id);
+                    } catch (PropagationException e) {
+                        // A propagation failure doesn't imply a synchronization failure.
+                        // The propagation exception status will be reported into the propagation task execution.
+                        LOG.error("Could not propagate {} {}", attrUtil.getType(), delta.getUid().getUidValue(), e);
+                        output = e;
+                        resultStatus = Result.FAILURE;
+                    } catch (Exception e) {
+                        result.setStatus(ProvisioningResult.Status.FAILURE);
+                        result.setMessage(ExceptionUtils.getRootCauseMessage(e));
+                        LOG.error("Could not update {} {}", attrUtil.getType(), delta.getUid().getUidValue(), e);
+                        output = e;
+                        resultStatus = Result.FAILURE;
+                    }
+                }
+                audit("update", resultStatus, before, output, delta);
+            }
+            updResults.add(result);
+        }
+        return updResults;
+    }
+
+    protected List<ProvisioningResult> deprovision(
+            SyncDelta delta,
+            final List<Long> subjects,
+            final AttributableUtil attrUtil,
+            final boolean unlink)
+            throws JobExecutionException {
+
+        if (!profile.getTask().isPerformUpdate()) {
+            LOG.debug("SyncTask not configured for update");
+            return Collections.<ProvisioningResult>emptyList();
+        }
+
+        LOG.debug("About to update {}", subjects);
+
+        final List<ProvisioningResult> updResults = new ArrayList<>();
+
+        for (Long id : subjects) {
+            LOG.debug("About to unassign resource {}", id);
+
+            Object output;
+            Result resultStatus;
+
+            final ProvisioningResult result = new ProvisioningResult();
+            result.setOperation(ResourceOperation.DELETE);
+            result.setSubjectType(attrUtil.getType());
+            result.setStatus(ProvisioningResult.Status.SUCCESS);
+            result.setId(id);
+
+            final AbstractSubjectTO before = getSubjectTO(id);
+
+            if (before == null) {
+                result.setStatus(ProvisioningResult.Status.FAILURE);
+                result.setMessage(String.format("Subject '%s(%d)' not found", attrUtil.getType().name(), id));
+            }
+
+            if (!profile.isDryRun()) {
+                if (before == null) {
+                    resultStatus = Result.FAILURE;
+                    output = null;
+                } else {
+                    result.setName(getName(before));
+
+                    try {
+                        if (unlink) {
+                            for (SyncActions action : profile.getActions()) {
+                                action.beforeUnassign(this.getProfile(), delta, before);
+                            }
+                        } else {
+                            for (SyncActions action : profile.getActions()) {
+                                action.beforeDeprovision(this.getProfile(), delta, before);
+                            }
+                        }
+
+                        deprovision(id, unlink);
+                        output = getSubjectTO(id);
+
+                        for (SyncActions action : profile.getActions()) {
+                            action.after(this.getProfile(), delta, AbstractSubjectTO.class.cast(output), result);
+                        }
+
+                        resultStatus = Result.SUCCESS;
+                        LOG.debug("{} {} successfully updated", attrUtil.getType(), id);
+                    } catch (PropagationException e) {
+                        // A propagation failure doesn't imply a synchronization failure.
+                        // The propagation exception status will be reported into the propagation task execution.
+                        LOG.error("Could not propagate {} {}", attrUtil.getType(), delta.getUid().getUidValue(), e);
+                        output = e;
+                        resultStatus = Result.FAILURE;
+                    } catch (Exception e) {
+                        result.setStatus(ProvisioningResult.Status.FAILURE);
+                        result.setMessage(ExceptionUtils.getRootCauseMessage(e));
+                        LOG.error("Could not update {} {}", attrUtil.getType(), delta.getUid().getUidValue(), e);
+                        output = e;
+                        resultStatus = Result.FAILURE;
+                    }
+                }
+                audit(unlink ? "unassign" : "deprovision", resultStatus, before, output, delta);
+            }
+            updResults.add(result);
+        }
+
+        return updResults;
+    }
+
+    protected List<ProvisioningResult> link(
+            SyncDelta delta,
+            final List<Long> subjects,
+            final AttributableUtil attrUtil,
+            final boolean unlink)
+            throws JobExecutionException {
+
+        if (!profile.getTask().isPerformUpdate()) {
+            LOG.debug("SyncTask not configured for update");
+            return Collections.<ProvisioningResult>emptyList();
+        }
+
+        LOG.debug("About to update {}", subjects);
+
+        final List<ProvisioningResult> updResults = new ArrayList<>();
+
+        for (Long id : subjects) {
+            LOG.debug("About to unassign resource {}", id);
+
+            Object output;
+            Result resultStatus;
+
+            final ProvisioningResult result = new ProvisioningResult();
+            result.setOperation(ResourceOperation.NONE);
+            result.setSubjectType(attrUtil.getType());
+            result.setStatus(ProvisioningResult.Status.SUCCESS);
+            result.setId(id);
+
+            final AbstractSubjectTO before = getSubjectTO(id);
+
+            if (before == null) {
+                result.setStatus(ProvisioningResult.Status.FAILURE);
+                result.setMessage(String.format("Subject '%s(%d)' not found", attrUtil.getType().name(), id));
+            }
+
+            if (!profile.isDryRun()) {
+                if (before == null) {
+                    resultStatus = Result.FAILURE;
+                    output = null;
+                } else {
+                    result.setName(getName(before));
+
+                    try {
+                        if (unlink) {
+                            for (SyncActions action : profile.getActions()) {
+                                action.beforeUnlink(this.getProfile(), delta, before);
+                            }
+                        } else {
+                            for (SyncActions action : profile.getActions()) {
+                                action.beforeLink(this.getProfile(), delta, before);
+                            }
+                        }
+
+                        output = link(before, result, unlink);
+
+                        for (SyncActions action : profile.getActions()) {
+                            action.after(this.getProfile(), delta, AbstractSubjectTO.class.cast(output), result);
+                        }
+
+                        resultStatus = Result.SUCCESS;
+                        LOG.debug("{} {} successfully updated", attrUtil.getType(), id);
+                    } catch (PropagationException e) {
+                        // A propagation failure doesn't imply a synchronization failure.
+                        // The propagation exception status will be reported into the propagation task execution.
+                        LOG.error("Could not propagate {} {}", attrUtil.getType(), delta.getUid().getUidValue(), e);
+                        output = e;
+                        resultStatus = Result.FAILURE;
+                    } catch (Exception e) {
+                        result.setStatus(ProvisioningResult.Status.FAILURE);
+                        result.setMessage(ExceptionUtils.getRootCauseMessage(e));
+                        LOG.error("Could not update {} {}", attrUtil.getType(), delta.getUid().getUidValue(), e);
+                        output = e;
+                        resultStatus = Result.FAILURE;
+                    }
+                }
+                audit(unlink ? "unlink" : "link", resultStatus, before, output, delta);
+            }
+            updResults.add(result);
+        }
+
+        return updResults;
+    }
+
+    protected List<ProvisioningResult> delete(
+            SyncDelta delta, final List<Long> subjects, final AttributableUtil attrUtil)
+            throws JobExecutionException {
+
+        if (!profile.getTask().isPerformDelete()) {
+            LOG.debug("SyncTask not configured for delete");
+            return Collections.<ProvisioningResult>emptyList();
+        }
+
+        LOG.debug("About to delete {}", subjects);
+
+        List<ProvisioningResult> delResults = new ArrayList<>();
+
+        for (Long id : subjects) {
+            Object output;
+            Result resultStatus = Result.FAILURE;
+
+            AbstractSubjectTO before = null;
+            final ProvisioningResult result = new ProvisioningResult();
+
+            try {
+                before = getSubjectTO(id);
+
+                result.setId(id);
+                result.setName(getName(before));
+                result.setOperation(ResourceOperation.DELETE);
+                result.setSubjectType(attrUtil.getType());
+                result.setStatus(ProvisioningResult.Status.SUCCESS);
+
+                if (!profile.isDryRun()) {
+                    for (SyncActions action : profile.getActions()) {
+                        delta = action.beforeDelete(this.getProfile(), delta, before);
+                    }
+
+                    try {
+                        delete(id);
+                        output = null;
+                        resultStatus = Result.SUCCESS;
+                    } catch (Exception e) {
+                        result.setStatus(ProvisioningResult.Status.FAILURE);
+                        result.setMessage(ExceptionUtils.getRootCauseMessage(e));
+                        LOG.error("Could not delete {} {}", attrUtil.getType(), id, e);
+                        output = e;
+                    }
+
+                    for (SyncActions action : profile.getActions()) {
+                        action.after(this.getProfile(), delta, before, result);
+                    }
+
+                    audit("delete", resultStatus, before, output, delta);
+                }
+
+                delResults.add(result);
+
+            } catch (NotFoundException e) {
+                LOG.error("Could not find {} {}", attrUtil.getType(), id, e);
+            } catch (UnauthorizedRoleException e) {
+                LOG.error("Not allowed to read {} {}", attrUtil.getType(), id, e);
+            } catch (Exception e) {
+                LOG.error("Could not delete {} {}", attrUtil.getType(), id, e);
+            }
+        }
+
+        return delResults;
+    }
+
+    /**
+     * Look into SyncDelta and take necessary profile.getActions() (create / update / delete) on user(s)/role(s).
+     *
+     * @param delta returned by the underlying profile.getConnector()
+     * @throws JobExecutionException in case of synchronization failure.
+     */
+    protected final void doHandle(final SyncDelta delta, final Collection<ProvisioningResult> syncResults)
+            throws JobExecutionException {
+
+        final AttributableUtil attrUtil = getAttributableUtil();
+
+        LOG.debug("Process {} for {} as {}",
+                delta.getDeltaType(), delta.getUid().getUidValue(), delta.getObject().getObjectClass());
+
+        final String uid = delta.getPreviousUid() == null
+                ? delta.getUid().getUidValue()
+                : delta.getPreviousUid().getUidValue();
+
+        try {
+            List<Long> subjectIds = syncUtilities.findExisting(
+                    uid, delta.getObject(), profile.getTask().getResource(), attrUtil);
+
+            if (subjectIds.size() > 1) {
+                switch (profile.getResAct()) {
+                    case IGNORE:
+                        throw new IllegalStateException("More than one match " + subjectIds);
+
+                    case FIRSTMATCH:
+                        subjectIds = subjectIds.subList(0, 1);
+                        break;
+
+                    case LASTMATCH:
+                        subjectIds = subjectIds.subList(subjectIds.size() - 1, subjectIds.size());
+                        break;
+
+                    default:
+                    // keep subjectIds as is
+                }
+            }
+
+            if (SyncDeltaType.CREATE_OR_UPDATE == delta.getDeltaType()) {
+                if (subjectIds.isEmpty()) {
+                    switch (profile.getTask().getUnmatchingRule()) {
+                        case ASSIGN:
+                            profile.getResults().addAll(assign(delta, attrUtil));
+                            break;
+                        case PROVISION:
+                            profile.getResults().addAll(create(delta, attrUtil));
+                            break;
+                        default:
+                        // do nothing
+                    }
+                } else {
+                    switch (profile.getTask().getMatchingRule()) {
+                        case UPDATE:
+                            profile.getResults().addAll(update(delta, subjectIds, attrUtil));
+                            break;
+                        case DEPROVISION:
+                            profile.getResults().addAll(deprovision(delta, subjectIds, attrUtil, false));
+                            break;
+                        case UNASSIGN:
+                            profile.getResults().addAll(deprovision(delta, subjectIds, attrUtil, true));
+                            break;
+                        case LINK:
+                            profile.getResults().addAll(link(delta, subjectIds, attrUtil, false));
+                            break;
+                        case UNLINK:
+                            profile.getResults().addAll(link(delta, subjectIds, attrUtil, true));
+                            break;
+                        default:
+                        // do nothing
+                    }
+                }
+            } else if (SyncDeltaType.DELETE == delta.getDeltaType()) {
+                if (subjectIds.isEmpty()) {
+                    LOG.debug("No match found for deletion");
+                } else {
+                    profile.getResults().addAll(delete(delta, subjectIds, attrUtil));
+                }
+            }
+        } catch (IllegalStateException e) {
+            LOG.warn(e.getMessage());
+        } catch (IllegalArgumentException e) {
+            LOG.warn(e.getMessage());
+        }
+    }
+
+    private void audit(
+            final String event,
+            final Result result,
+            final Object before,
+            final Object output,
+            final Object... input) {
+
+        notificationManager.createTasks(
+                AuditElements.EventCategoryType.SYNCHRONIZATION,
+                getAttributableUtil().getType().name().toLowerCase(),
+                profile.getTask().getResource().getKey(),
+                event,
+                result,
+                before,
+                output,
+                input);
+
+        auditManager.audit(
+                AuditElements.EventCategoryType.SYNCHRONIZATION,
+                getAttributableUtil().getType().name().toLowerCase(),
+                profile.getTask().getResource().getKey(),
+                event,
+                result,
+                before,
+                output,
+                input);
+    }
+}

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/AbstractSyncopeResultHandler.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/AbstractSyncopeResultHandler.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/AbstractSyncopeResultHandler.java
new file mode 100644
index 0000000..fbd5b7e
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/AbstractSyncopeResultHandler.java
@@ -0,0 +1,126 @@
+/*
+ * 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 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.AttributableUtilFactory;
+import org.apache.syncope.server.persistence.api.entity.task.ProvisioningTask;
+import org.apache.syncope.server.provisioning.api.RoleProvisioningManager;
+import org.apache.syncope.server.provisioning.api.data.RoleDataBinder;
+import org.apache.syncope.server.provisioning.api.UserProvisioningManager;
+import org.apache.syncope.server.provisioning.api.data.UserDataBinder;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationManager;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationTaskExecutor;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningActions;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningProfile;
+import org.apache.syncope.server.provisioning.api.sync.SyncopeResultHandler;
+import org.apache.syncope.server.misc.AuditManager;
+import org.apache.syncope.server.provisioning.java.notification.NotificationManager;
+import org.apache.syncope.server.misc.ConnObjectUtil;
+import org.apache.syncope.server.workflow.api.RoleWorkflowAdapter;
+import org.apache.syncope.server.workflow.api.UserWorkflowAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public abstract class AbstractSyncopeResultHandler<T extends ProvisioningTask, A extends ProvisioningActions>
+        implements SyncopeResultHandler<T, A> {
+
+    /**
+     * Logger.
+     */
+    protected static final Logger LOG = LoggerFactory.getLogger(AbstractSyncopeResultHandler.class);
+
+    @Autowired
+    protected UserDAO userDAO;
+
+    @Autowired
+    protected RoleDAO roleDAO;
+
+    /**
+     * ConnectorObject util.
+     */
+    @Autowired
+    protected ConnObjectUtil connObjectUtil;
+
+    /**
+     * Notification Manager.
+     */
+    @Autowired
+    protected NotificationManager notificationManager;
+
+    /**
+     * Audit Manager.
+     */
+    @Autowired
+    protected AuditManager auditManager;
+
+    /**
+     * Propagation manager.
+     */
+    @Autowired
+    protected PropagationManager propagationManager;
+
+    /**
+     * Task executor.
+     */
+    @Autowired
+    protected PropagationTaskExecutor taskExecutor;
+
+    /**
+     * User workflow adapter.
+     */
+    @Autowired
+    protected UserWorkflowAdapter uwfAdapter;
+
+    /**
+     * Role workflow adapter.
+     */
+    @Autowired
+    protected RoleWorkflowAdapter rwfAdapter;
+
+    @Autowired
+    protected UserDataBinder userTransfer;
+
+    @Autowired
+    protected RoleDataBinder roleTransfer;
+
+    @Autowired
+    protected UserProvisioningManager userProvisioningManager;
+
+    @Autowired
+    protected RoleProvisioningManager roleProvisioningManager;
+
+    @Autowired
+    protected AttributableUtilFactory attrUtilFactory;
+
+    /**
+     * Sync profile.
+     */
+    protected ProvisioningProfile<T, A> profile;
+
+    public void setProfile(final ProvisioningProfile<T, A> profile) {
+        this.profile = profile;
+    }
+
+    public ProvisioningProfile<T, A> getProfile() {
+        return profile;
+    }
+}

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/DBPasswordSyncActions.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/DBPasswordSyncActions.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/DBPasswordSyncActions.java
new file mode 100644
index 0000000..fbd60ba
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/DBPasswordSyncActions.java
@@ -0,0 +1,139 @@
+/*
+ * 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.Iterator;
+import org.apache.syncope.common.lib.mod.AbstractSubjectMod;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.CipherAlgorithm;
+import org.apache.syncope.common.lib.types.ConnConfProperty;
+import org.apache.syncope.server.persistence.api.dao.UserDAO;
+import org.apache.syncope.server.persistence.api.entity.ConnInstance;
+import org.apache.syncope.server.persistence.api.entity.user.User;
+import org.apache.syncope.server.provisioning.api.Connector;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningProfile;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningResult;
+import org.identityconnectors.framework.common.objects.SyncDelta;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * A SyncActions implementation which allows the ability to import passwords from a Database
+ * backend, where the passwords are hashed according to the password cipher algorithm property
+ * of the (DB) Connector and HEX-encoded.
+ */
+public class DBPasswordSyncActions extends DefaultSyncActions {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DBPasswordSyncActions.class);
+
+    private static final String CLEARTEXT = "CLEARTEXT";
+
+    @Autowired
+    private UserDAO userDAO;
+
+    private String encodedPassword;
+
+    private CipherAlgorithm cipher;
+
+    @Transactional(readOnly = true)
+    @Override
+    public <T extends AbstractSubjectTO> SyncDelta beforeProvision(
+            final ProvisioningProfile<?, ?> profile,
+            final SyncDelta delta,
+            final T subject) throws JobExecutionException {
+
+        if (subject instanceof UserTO) {
+            String password = ((UserTO) subject).getPassword();
+            parseEncodedPassword(password, profile.getConnector());
+        }
+
+        return delta;
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public <T extends AbstractSubjectTO, K extends AbstractSubjectMod> SyncDelta beforeUpdate(
+            final ProvisioningProfile<?, ?> profile,
+            final SyncDelta delta,
+            final T subject,
+            final K subjectMod) throws JobExecutionException {
+
+        if (subjectMod instanceof UserMod) {
+            String modPassword = ((UserMod) subjectMod).getPassword();
+            parseEncodedPassword(modPassword, profile.getConnector());
+        }
+
+        return delta;
+    }
+
+    private void parseEncodedPassword(final String password, final Connector connector) {
+        if (password != null) {
+            ConnInstance connInstance = connector.getActiveConnInstance();
+
+            String cipherAlgorithm = getCipherAlgorithm(connInstance);
+            if (!CLEARTEXT.equals(cipherAlgorithm)) {
+                try {
+                    encodedPassword = password;
+                    cipher = CipherAlgorithm.valueOf(cipherAlgorithm);
+                } catch (IllegalArgumentException e) {
+                    LOG.error("Cipher algorithm not allowed: {}", cipherAlgorithm, e);
+                    encodedPassword = null;
+                }
+            }
+        }
+    }
+
+    private String getCipherAlgorithm(final ConnInstance connInstance) {
+        String cipherAlgorithm = CLEARTEXT;
+        for (Iterator<ConnConfProperty> propertyIterator = connInstance.getConfiguration().iterator();
+                propertyIterator.hasNext();) {
+
+            ConnConfProperty property = propertyIterator.next();
+            if ("cipherAlgorithm".equals(property.getSchema().getName())
+                    && property.getValues() != null && !property.getValues().isEmpty()) {
+
+                return (String) property.getValues().get(0);
+            }
+        }
+        return cipherAlgorithm;
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public <T extends AbstractSubjectTO> void after(
+            final ProvisioningProfile<?, ?> profile,
+            final SyncDelta delta,
+            final T subject,
+            final ProvisioningResult result) throws JobExecutionException {
+
+        if (subject instanceof UserTO && encodedPassword != null && cipher != null) {
+            User syncopeUser = userDAO.find(subject.getKey());
+            if (syncopeUser != null) {
+                syncopeUser.setEncodedPassword(encodedPassword.toUpperCase(), cipher);
+            }
+            encodedPassword = null;
+            cipher = 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/DefaultPushActions.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/DefaultPushActions.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/DefaultPushActions.java
new file mode 100644
index 0000000..a9ba96e
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/DefaultPushActions.java
@@ -0,0 +1,88 @@
+/*
+ * 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.List;
+import org.apache.syncope.server.persistence.api.entity.Subject;
+import org.apache.syncope.server.provisioning.api.sync.PushActions;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningProfile;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningResult;
+import org.quartz.JobExecutionException;
+
+/**
+ * Default (empty) implementation of PushActions.
+ */
+public abstract class DefaultPushActions implements PushActions {
+
+    @Override
+    public void beforeAll(final ProvisioningProfile<?, ?> profile) throws JobExecutionException {
+    }
+
+    @Override
+    public <T extends Subject<?, ?, ?>> T beforeAssign(final ProvisioningProfile<?, ?> profile, final T subject)
+            throws JobExecutionException {
+
+        return subject;
+    }
+
+    @Override
+    public <T extends Subject<?, ?, ?>> T beforeProvision(final ProvisioningProfile<?, ?> profile, final T subject)
+            throws JobExecutionException {
+
+        return subject;
+    }
+
+    @Override
+    public <T extends Subject<?, ?, ?>> T beforeLink(final ProvisioningProfile<?, ?> profile, final T subject)
+            throws JobExecutionException {
+
+        return subject;
+    }
+
+    @Override
+    public <T extends Subject<?, ?, ?>> T beforeUnassign(final ProvisioningProfile<?, ?> profile, final T subject)
+            throws JobExecutionException {
+
+        return subject;
+    }
+
+    @Override
+    public <T extends Subject<?, ?, ?>> T beforeDeprovision(final ProvisioningProfile<?, ?> profile, final T subject)
+            throws JobExecutionException {
+
+        return subject;
+    }
+
+    @Override
+    public <T extends Subject<?, ?, ?>> T beforeUnlink(final ProvisioningProfile<?, ?> profile, final T subject)
+            throws JobExecutionException {
+
+        return subject;
+    }
+
+    @Override
+    public <T extends Subject<?, ?, ?>> void after(
+            final ProvisioningProfile<?, ?> profile, final T subject, final ProvisioningResult result) throws JobExecutionException {
+    }
+
+    @Override
+    public void afterAll(final ProvisioningProfile<?, ?> profile, final List<ProvisioningResult> results)
+            throws JobExecutionException {
+    }
+}

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/DefaultSyncActions.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/DefaultSyncActions.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/DefaultSyncActions.java
new file mode 100644
index 0000000..b957d2a
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/DefaultSyncActions.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.server.provisioning.java.sync;
+
+import java.util.List;
+import org.apache.syncope.common.lib.mod.AbstractSubjectMod;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.server.provisioning.api.sync.SyncActions;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningProfile;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningResult;
+import org.identityconnectors.framework.common.objects.SyncDelta;
+import org.quartz.JobExecutionException;
+
+/**
+ * Default (empty) implementation of SyncActions.
+ */
+public abstract class DefaultSyncActions implements SyncActions {
+
+    @Override
+    public void beforeAll(final ProvisioningProfile<?, ?> profile) throws JobExecutionException {
+    }
+
+    @Override
+    public <T extends AbstractSubjectTO, K extends AbstractSubjectMod> SyncDelta beforeUpdate(
+            final ProvisioningProfile<?, ?> profile,
+            final SyncDelta delta,
+            final T subject,
+            final K subjectMod) throws JobExecutionException {
+
+        return delta;
+    }
+
+    @Override
+    public <T extends AbstractSubjectTO> SyncDelta beforeDelete(
+            final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final T subject)
+            throws JobExecutionException {
+
+        return delta;
+    }
+
+    @Override
+    public <T extends AbstractSubjectTO> SyncDelta beforeAssign(
+            final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final T subject)
+            throws JobExecutionException {
+
+        return delta;
+    }
+
+    @Override
+    public <T extends AbstractSubjectTO> SyncDelta beforeProvision(
+            final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final T subject)
+            throws JobExecutionException {
+
+        return delta;
+    }
+
+    @Override
+    public <T extends AbstractSubjectTO> SyncDelta beforeLink(
+            final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final T subject)
+            throws JobExecutionException {
+
+        return delta;
+    }
+
+    @Override
+    public <T extends AbstractSubjectTO> SyncDelta beforeUnassign(
+            final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final T subject)
+            throws JobExecutionException {
+
+        return delta;
+    }
+
+    @Override
+    public <T extends AbstractSubjectTO> SyncDelta beforeDeprovision(
+            final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final T subject)
+            throws JobExecutionException {
+
+        return delta;
+    }
+
+    @Override
+    public <T extends AbstractSubjectTO> SyncDelta beforeUnlink(
+            final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final T subject)
+            throws JobExecutionException {
+
+        return delta;
+    }
+
+    @Override
+    public <T extends AbstractSubjectTO> void after(
+            final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final T subject, final ProvisioningResult result)
+            throws JobExecutionException {
+    }
+
+    @Override
+    public void afterAll(final ProvisioningProfile<?, ?> profile, final List<ProvisioningResult> results)
+            throws JobExecutionException {
+    }
+}

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/LDAPMembershipSyncActions.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/LDAPMembershipSyncActions.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/LDAPMembershipSyncActions.java
new file mode 100644
index 0000000..b705c64
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/LDAPMembershipSyncActions.java
@@ -0,0 +1,309 @@
+/*
+ * 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.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import org.apache.syncope.common.lib.mod.AbstractSubjectMod;
+import org.apache.syncope.common.lib.mod.MembershipMod;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditElements.Result;
+import org.apache.syncope.common.lib.types.ConnConfProperty;
+import org.apache.syncope.server.persistence.api.dao.RoleDAO;
+import org.apache.syncope.server.persistence.api.entity.ConnInstance;
+import org.apache.syncope.server.persistence.api.entity.ExternalResource;
+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.task.ProvisioningTask;
+import org.apache.syncope.server.persistence.api.entity.task.SyncTask;
+import org.apache.syncope.server.provisioning.api.Connector;
+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.PropagationTaskExecutor;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningProfile;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningResult;
+import org.apache.syncope.server.misc.AuditManager;
+import org.apache.syncope.server.provisioning.java.notification.NotificationManager;
+import org.apache.syncope.server.workflow.api.UserWorkflowAdapter;
+import org.identityconnectors.framework.common.objects.Attribute;
+import org.identityconnectors.framework.common.objects.ObjectClass;
+import org.identityconnectors.framework.common.objects.OperationOptionsBuilder;
+import org.identityconnectors.framework.common.objects.SyncDelta;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Simple action for synchronizing LDAP groups memberships to Syncope role memberships, when the same resource is
+ * configured for both users and roles.
+ *
+ * @see org.apache.syncope.core.propagation.impl.LDAPMembershipPropagationActions
+ */
+public class LDAPMembershipSyncActions extends DefaultSyncActions {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(LDAPMembershipSyncActions.class);
+
+    @Autowired
+    protected RoleDAO roleDAO;
+
+    @Autowired
+    protected UserWorkflowAdapter uwfAdapter;
+
+    @Autowired
+    protected PropagationManager propagationManager;
+
+    @Autowired
+    private PropagationTaskExecutor taskExecutor;
+
+    @Autowired
+    private NotificationManager notificationManager;
+
+    @Autowired
+    private AuditManager auditManager;
+
+    @Autowired
+    private SyncUtilities syncUtilities;
+
+    protected Map<Long, Long> membersBeforeRoleUpdate = Collections.<Long, Long>emptyMap();
+
+    /**
+     * Allows easy subclassing for the ConnId AD connector bundle.
+     *
+     * @param connector A Connector instance to query for the groupMemberAttribute property name
+     * @return the name of the attribute used to keep track of group memberships
+     */
+    protected String getGroupMembershipAttrName(final Connector connector) {
+        ConnInstance connInstance = connector.getActiveConnInstance();
+        Iterator<ConnConfProperty> propertyIterator = connInstance.getConfiguration().iterator();
+        String groupMembershipName = "uniquemember";
+        while (propertyIterator.hasNext()) {
+            ConnConfProperty property = propertyIterator.next();
+            if ("groupMemberAttribute".equals(property.getSchema().getName())
+                    && property.getValues() != null && !property.getValues().isEmpty()) {
+
+                groupMembershipName = (String) property.getValues().get(0);
+                break;
+            }
+        }
+
+        return groupMembershipName;
+    }
+
+    /**
+     * Keep track of members of the role being updated <b>before</b> actual update takes place. This is not needed on
+     * <ul> <li>beforeProvision() - because the synchronizing role does not exist yet on Syncope</li> <li>beforeDelete()
+     * -
+     * because role delete cascades as membership removal for all users involved</li> </ul>
+     *
+     * {@inheritDoc}
+     */
+    @Override
+    public <T extends AbstractSubjectTO, K extends AbstractSubjectMod> SyncDelta beforeUpdate(
+            final ProvisioningProfile<?, ?> profile,
+            final SyncDelta delta, final T subject, final K subjectMod) throws JobExecutionException {
+
+        if (subject instanceof RoleTO) {
+            // search for all users assigned to given role
+            Role role = roleDAO.find(subject.getKey());
+            if (role != null) {
+                List<Membership> membs = roleDAO.findMemberships(role);
+                // save memberships before role update takes place
+                membersBeforeRoleUpdate = new HashMap<>(membs.size());
+                for (Membership memb : membs) {
+                    membersBeforeRoleUpdate.put(memb.getUser().getKey(), memb.getKey());
+                }
+            }
+        }
+
+        return super.beforeUpdate(profile, delta, subject, subjectMod);
+    }
+
+    /**
+     * Build UserMod for adding membership to given user, for given role.
+     *
+     * @param userId user to be assigned membership to given role
+     * @param roleTO role for adding membership
+     * @return UserMod for user update
+     */
+    protected UserMod getUserMod(final Long userId, final RoleTO roleTO) {
+        UserMod userMod = new UserMod();
+        // no actual modification takes place when user has already the role assigned
+        if (membersBeforeRoleUpdate.containsKey(userId)) {
+            membersBeforeRoleUpdate.remove(userId);
+        } else {
+            userMod.setKey(userId);
+
+            MembershipMod membershipMod = new MembershipMod();
+            membershipMod.setRole(roleTO.getKey());
+            userMod.getMembershipsToAdd().add(membershipMod);
+        }
+
+        return userMod;
+    }
+
+    /**
+     * Read values of attribute returned by getGroupMembershipAttrName(); if not present in the given delta, perform an
+     * additional read on the underlying connector.
+     *
+     * @param delta representing the synchronizing role
+     * @param connector associated to the current resource
+     * @return value of attribute returned by
+     * {@link #getGroupMembershipAttrName(org.apache.syncope.core.propagation.Connector) }
+     */
+    protected List<Object> getMembAttrValues(final SyncDelta delta, final Connector connector) {
+        List<Object> result = Collections.<Object>emptyList();
+        String groupMemberName = getGroupMembershipAttrName(connector);
+
+        // first, try to read the configured attribute from delta, returned by the ongoing synchronization
+        Attribute membAttr = delta.getObject().getAttributeByName(groupMemberName);
+        // if not found, perform an additional read on the underlying connector for the same connector object
+        if (membAttr == null) {
+            final OperationOptionsBuilder oob = new OperationOptionsBuilder();
+            oob.setAttributesToGet(groupMemberName);
+            membAttr = connector.getObjectAttribute(ObjectClass.GROUP, delta.getUid(), oob.build(), groupMemberName);
+        }
+        if (membAttr != null && membAttr.getValue() != null) {
+            result = membAttr.getValue();
+        }
+
+        return result;
+    }
+
+    /**
+     * Perform actual modifications (i.e. membership add / remove) for the given role on the given resource.
+     *
+     * @param userMod modifications to perform on the user
+     * @param resourceName resource to be propagated for changes
+     */
+    protected void userUpdate(final UserMod userMod, final String resourceName) {
+        if (userMod.getKey() == 0) {
+            return;
+        }
+
+        Result result;
+
+        WorkflowResult<Map.Entry<UserMod, Boolean>> updated = null;
+
+        try {
+            updated = uwfAdapter.update(userMod);
+
+            List<PropagationTask> tasks = propagationManager.getUserUpdateTaskIds(
+                    updated, false, Collections.singleton(resourceName));
+
+            taskExecutor.execute(tasks);
+            result = Result.SUCCESS;
+        } catch (PropagationException e) {
+            result = Result.FAILURE;
+            LOG.error("Could not propagate {}", userMod, e);
+        } catch (Exception e) {
+            result = Result.FAILURE;
+            LOG.error("Could not perform update {}", userMod, e);
+        }
+
+        notificationManager.createTasks(
+                AuditElements.EventCategoryType.SYNCHRONIZATION,
+                this.getClass().getSimpleName(),
+                null,
+                "update",
+                result,
+                null, // searching for before object is too much expensive ... 
+                updated == null ? null : updated.getResult().getKey(),
+                userMod,
+                resourceName);
+
+        auditManager.audit(
+                AuditElements.EventCategoryType.SYNCHRONIZATION,
+                this.getClass().getSimpleName(),
+                null,
+                "update",
+                result,
+                null, // searching for before object is too much expensive ... 
+                updated == null ? null : updated.getResult().getKey(),
+                userMod,
+                resourceName);
+    }
+
+    /**
+     * Synchronize Syncope memberships with the situation read on the external resource's group.
+     *
+     * @param profile sync profile
+     * @param delta representing the synchronizing role
+     * @param roleTO role after modification performed by the handler
+     * @throws JobExecutionException if anything goes wrong
+     */
+    protected void synchronizeMemberships(
+            final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final RoleTO roleTO) throws
+            JobExecutionException {
+
+        final ProvisioningTask task = profile.getTask();
+        final ExternalResource resource = task.getResource();
+        final Connector connector = profile.getConnector();
+
+        for (Object membValue : getMembAttrValues(delta, connector)) {
+            Long userId = syncUtilities.findMatchingAttributableId(
+                    ObjectClass.ACCOUNT,
+                    membValue.toString(),
+                    profile.getTask().getResource(),
+                    profile.getConnector());
+            if (userId != null) {
+                UserMod userMod = getUserMod(userId, roleTO);
+                userUpdate(userMod, resource.getKey());
+            }
+        }
+
+        // finally remove any residual membership that was present before role update but not any more
+        for (Map.Entry<Long, Long> member : membersBeforeRoleUpdate.entrySet()) {
+            UserMod userMod = new UserMod();
+            userMod.setKey(member.getKey());
+            userMod.getMembershipsToRemove().add(member.getValue());
+            userUpdate(userMod, resource.getKey());
+        }
+    }
+
+    /**
+     * Synchronize membership at role synchronization time (because SyncJob first synchronize users then roles).
+     * {@inheritDoc}
+     */
+    @Override
+    public <T extends AbstractSubjectTO> void after(
+            final ProvisioningProfile<?, ?> profile,
+            final SyncDelta delta,
+            final T subject,
+            final ProvisioningResult result) throws JobExecutionException {
+
+        if (!(profile.getTask() instanceof SyncTask)) {
+            return;
+        }
+
+        if (!(subject instanceof RoleTO) || profile.getTask().getResource().getUmapping() == null) {
+            super.after(profile, delta, subject, result);
+        } else {
+            synchronizeMemberships(profile, delta, (RoleTO) subject);
+        }
+    }
+}

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/LDAPPasswordSyncActions.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/LDAPPasswordSyncActions.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/LDAPPasswordSyncActions.java
new file mode 100644
index 0000000..5508cee
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/LDAPPasswordSyncActions.java
@@ -0,0 +1,123 @@
+/*
+ * 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 org.apache.syncope.common.lib.mod.AbstractSubjectMod;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.CipherAlgorithm;
+import org.apache.syncope.server.persistence.api.dao.UserDAO;
+import org.apache.syncope.server.persistence.api.entity.user.User;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningProfile;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningResult;
+import org.identityconnectors.framework.common.objects.SyncDelta;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.crypto.codec.Base64;
+import org.springframework.security.crypto.codec.Hex;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * A SyncActions implementation which allows the ability to import passwords from an LDAP backend
+ * that are hashed.
+ */
+public class LDAPPasswordSyncActions extends DefaultSyncActions {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(LDAPPasswordSyncActions.class);
+
+    @Autowired
+    private UserDAO userDAO;
+
+    private String encodedPassword;
+
+    private CipherAlgorithm cipher;
+
+    @Transactional(readOnly = true)
+    @Override
+    public <T extends AbstractSubjectTO> SyncDelta beforeProvision(
+            final ProvisioningProfile<?, ?> profile,
+            final SyncDelta delta,
+            final T subject) throws JobExecutionException {
+
+        if (subject instanceof UserTO) {
+            String password = ((UserTO) subject).getPassword();
+            parseEncodedPassword(password);
+        }
+
+        return delta;
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public <T extends AbstractSubjectTO, K extends AbstractSubjectMod> SyncDelta beforeUpdate(
+            final ProvisioningProfile<?, ?> profile,
+            final SyncDelta delta,
+            final T subject,
+            final K subjectMod) throws JobExecutionException {
+
+        if (subjectMod instanceof UserMod) {
+            String modPassword = ((UserMod) subjectMod).getPassword();
+            parseEncodedPassword(modPassword);
+        }
+
+        return delta;
+    }
+
+    private void parseEncodedPassword(String password) {
+        if (password != null && password.startsWith("{")) {
+            int closingBracketIndex = password.indexOf('}');
+            String digest = password.substring(1, password.indexOf('}'));
+            if (digest != null) {
+                digest = digest.toUpperCase();
+            }
+            try {
+                encodedPassword = password.substring(closingBracketIndex + 1);
+                cipher = CipherAlgorithm.valueOf(digest);
+            } catch (IllegalArgumentException e) {
+                LOG.error("Cipher algorithm not allowed: {}", digest, e);
+                encodedPassword = null;
+            }
+        }
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public <T extends AbstractSubjectTO> void after(
+            final ProvisioningProfile<?, ?> profile,
+            final SyncDelta delta,
+            final T subject,
+            final ProvisioningResult result) throws JobExecutionException {
+
+        if (subject instanceof UserTO && encodedPassword != null && cipher != null) {
+            User syncopeUser = userDAO.find(subject.getKey());
+            if (syncopeUser != null) {
+                byte[] encodedPasswordBytes = Base64.decode(encodedPassword.getBytes());
+                char[] encodedHex = Hex.encode(encodedPasswordBytes);
+                String encodedHexStr = new String(encodedHex).toUpperCase();
+
+                syncopeUser.setEncodedPassword(encodedHexStr, cipher);
+            }
+            encodedPassword = null;
+            cipher = 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/PushJobImpl.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/PushJobImpl.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/PushJobImpl.java
new file mode 100644
index 0000000..d40b4be
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/PushJobImpl.java
@@ -0,0 +1,176 @@
+/*
+ * 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.Collections;
+import java.util.List;
+import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.types.SubjectType;
+import org.apache.syncope.server.persistence.api.RoleEntitlementUtil;
+import org.apache.syncope.server.persistence.api.dao.RoleDAO;
+import org.apache.syncope.server.persistence.api.dao.SubjectSearchDAO;
+import org.apache.syncope.server.persistence.api.dao.UserDAO;
+import org.apache.syncope.server.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.server.persistence.api.entity.role.RMapping;
+import org.apache.syncope.server.persistence.api.entity.role.Role;
+import org.apache.syncope.server.persistence.api.entity.task.PushTask;
+import org.apache.syncope.server.persistence.api.entity.user.UMapping;
+import org.apache.syncope.server.persistence.api.entity.user.User;
+import org.apache.syncope.server.provisioning.api.Connector;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningProfile;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningResult;
+import org.apache.syncope.server.provisioning.api.sync.PushActions;
+import org.apache.syncope.server.misc.spring.ApplicationContextProvider;
+import org.apache.syncope.server.misc.search.SearchCondConverter;
+import org.apache.syncope.server.provisioning.api.job.PushJob;
+import org.quartz.JobExecutionException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+
+/**
+ * Job for executing synchronization (towards external resource) tasks.
+ *
+ * @see AbstractProvisioningJob
+ * @see PushTask
+ */
+public class PushJobImpl extends AbstractProvisioningJob<PushTask, PushActions> implements PushJob {
+
+    /**
+     * User DAO.
+     */
+    @Autowired
+    private UserDAO userDAO;
+
+    /**
+     * Search DAO.
+     */
+    @Autowired
+    private SubjectSearchDAO searchDAO;
+
+    /**
+     * Role DAO.
+     */
+    @Autowired
+    private RoleDAO roleDAO;
+
+    private final int PAGE_SIZE = 1000;
+
+    @Override
+    protected String executeWithSecurityContext(
+            final PushTask pushTask,
+            final Connector connector,
+            final UMapping uMapping,
+            final RMapping rMapping,
+            final boolean dryRun) throws JobExecutionException {
+        LOG.debug("Execute synchronization (push) with resource {}", pushTask.getResource());
+
+        final List<ProvisioningResult> results = new ArrayList<>();
+
+        final Set<Long> authorizations = RoleEntitlementUtil.getRoleKeys(entitlementDAO.findAll());
+
+        final ProvisioningProfile<PushTask, PushActions> profile = new ProvisioningProfile<>(connector, pushTask);
+        profile.getActions().addAll(actions);
+        profile.setDryRun(dryRun);
+        profile.setResAct(null);
+        profile.getResults().addAll(results);
+
+        final UserPushResultHandler uhandler =
+                (UserPushResultHandler) ApplicationContextProvider.getApplicationContext().getBeanFactory().createBean(
+                        UserPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
+        uhandler.setProfile(profile);
+
+        final RolePushResultHandler rhandler =
+                (RolePushResultHandler) ApplicationContextProvider.getApplicationContext().getBeanFactory().createBean(
+                        RolePushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
+        rhandler.setProfile(profile);
+
+        if (!profile.isDryRun()) {
+            for (PushActions action : actions) {
+                action.beforeAll(profile);
+            }
+        }
+
+        if (uMapping != null) {
+            final int count = userDAO.count(authorizations);
+            for (int page = 1; page <= (count / PAGE_SIZE) + 1; page++) {
+                final List<User> localUsers = getUsers(authorizations, pushTask, page);
+
+                for (User localUser : localUsers) {
+                    try {
+                        // user propagation
+                        uhandler.handle(localUser.getKey());
+                    } catch (Exception e) {
+                        LOG.warn("Failure pushing user '{}' on '{}'", localUser, pushTask.getResource(), e);
+                        throw new JobExecutionException("While pushing users on connector", e);
+                    }
+                }
+            }
+        }
+
+        if (rMapping != null) {
+            final List<Role> localRoles = getRoles(authorizations, pushTask);
+
+            for (Role localRole : localRoles) {
+                try {
+                    // role propagation
+                    rhandler.handle(localRole.getKey());
+                } catch (Exception e) {
+                    LOG.warn("Failure pushing role '{}' on '{}'", localRole, pushTask.getResource(), e);
+                    throw new JobExecutionException("While pushing roles on connector", e);
+                }
+            }
+        }
+
+        if (!profile.isDryRun()) {
+            for (PushActions action : actions) {
+                action.afterAll(profile, results);
+            }
+        }
+
+        final String result = createReport(results, pushTask.getResource().getSyncTraceLevel(), dryRun);
+
+        LOG.debug("Sync result: {}", result);
+
+        return result;
+    }
+
+    private List<User> getUsers(final Set<Long> authorizations, final PushTask pushTask, final int page) {
+        final String filter = pushTask.getUserFilter();
+        if (StringUtils.isBlank(filter)) {
+            return userDAO.findAll(authorizations, page, PAGE_SIZE);
+        } else {
+            return searchDAO.<User>search(
+                    authorizations, SearchCondConverter.convert(filter),
+                    Collections.<OrderByClause>emptyList(), SubjectType.USER);
+        }
+    }
+
+    private List<Role> getRoles(final Set<Long> authorizations, final PushTask pushTask) {
+        final String filter = pushTask.getRoleFilter();
+        if (StringUtils.isBlank(filter)) {
+            return roleDAO.findAll();
+        } else {
+            return searchDAO.<Role>search(
+                    authorizations, SearchCondConverter.convert(filter),
+                    Collections.<OrderByClause>emptyList(), SubjectType.ROLE);
+        }
+    }
+}

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/RolePushResultHandler.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/RolePushResultHandler.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/RolePushResultHandler.java
new file mode 100644
index 0000000..60d6bdb
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/RolePushResultHandler.java
@@ -0,0 +1,154 @@
+/*
+ * 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.Collections;
+import java.util.List;
+import org.apache.syncope.common.lib.mod.RoleMod;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.types.PropagationByResource;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+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.role.Role;
+import org.apache.syncope.server.provisioning.api.TimeoutException;
+import org.identityconnectors.framework.common.objects.ConnectorObject;
+import org.identityconnectors.framework.common.objects.ObjectClass;
+import org.identityconnectors.framework.common.objects.Uid;
+
+public class RolePushResultHandler extends AbstractSubjectPushResultHandler {
+
+    @Override
+    protected Subject<?, ?, ?> deprovision(final Subject<?, ?, ?> sbj) {
+        final RoleTO before = roleTransfer.getRoleTO(Role.class.cast(sbj));
+
+        final List<String> noPropResources = new ArrayList<>(before.getResources());
+        noPropResources.remove(profile.getTask().getResource().getKey());
+
+        taskExecutor.execute(propagationManager.getRoleDeleteTaskIds(before.getKey(), noPropResources));
+
+        return roleDAO.authFetch(before.getKey());
+    }
+
+    @Override
+    protected Subject<?, ?, ?> provision(final Subject<?, ?, ?> sbj, final Boolean enabled) {
+        final RoleTO before = roleTransfer.getRoleTO(Role.class.cast(sbj));
+
+        final List<String> noPropResources = new ArrayList<>(before.getResources());
+        noPropResources.remove(profile.getTask().getResource().getKey());
+
+        final PropagationByResource propByRes = new PropagationByResource();
+        propByRes.add(ResourceOperation.CREATE, profile.getTask().getResource().getKey());
+
+        taskExecutor.execute(propagationManager.getRoleCreateTaskIds(
+                before.getKey(),
+                Collections.unmodifiableCollection(before.getVirAttrs()),
+                propByRes,
+                noPropResources));
+
+        return roleDAO.authFetch(before.getKey());
+    }
+
+    @Override
+    protected Subject<?, ?, ?> link(final Subject<?, ?, ?> sbj, final Boolean unlink) {
+        final RoleMod roleMod = new RoleMod();
+        roleMod.setKey(sbj.getKey());
+
+        if (unlink) {
+            roleMod.getResourcesToRemove().add(profile.getTask().getResource().getKey());
+        } else {
+            roleMod.getResourcesToAdd().add(profile.getTask().getResource().getKey());
+        }
+
+        rwfAdapter.update(roleMod);
+
+        return roleDAO.authFetch(sbj.getKey());
+    }
+
+    @Override
+    protected Subject<?, ?, ?> unassign(final Subject<?, ?, ?> sbj) {
+        final RoleMod roleMod = new RoleMod();
+        roleMod.setKey(sbj.getKey());
+        roleMod.getResourcesToRemove().add(profile.getTask().getResource().getKey());
+        rwfAdapter.update(roleMod);
+        return deprovision(sbj);
+    }
+
+    @Override
+    protected Subject<?, ?, ?> assign(final Subject<?, ?, ?> sbj, final Boolean enabled) {
+        final RoleMod roleMod = new RoleMod();
+        roleMod.setKey(sbj.getKey());
+        roleMod.getResourcesToAdd().add(profile.getTask().getResource().getKey());
+        rwfAdapter.update(roleMod);
+        return provision(sbj, enabled);
+    }
+
+    @Override
+    protected String getName(final Subject<?, ?, ?> subject) {
+        return Role.class.cast(subject).getName();
+    }
+
+    @Override
+    protected AbstractSubjectTO getSubjectTO(final long key) {
+        try {
+            return roleTransfer.getRoleTO(roleDAO.authFetch(key));
+        } catch (Exception e) {
+            LOG.warn("Error retrieving user {}", key, e);
+            return null;
+        }
+    }
+
+    @Override
+    protected Subject<?, ?, ?> getSubject(final long key) {
+        try {
+            return roleDAO.authFetch(key);
+        } catch (Exception e) {
+            LOG.warn("Error retrieving role {}", key, e);
+            return null;
+        }
+    }
+
+    @Override
+    protected ConnectorObject getRemoteObject(final String accountId) {
+        ConnectorObject obj = null;
+
+        try {
+            final Uid uid = new Uid(accountId);
+
+            obj = profile.getConnector().getObject(
+                    ObjectClass.GROUP,
+                    uid,
+                    profile.getConnector().getOperationOptions(Collections.<MappingItem>emptySet()));
+        } catch (TimeoutException toe) {
+            LOG.debug("Request timeout", toe);
+            throw toe;
+        } catch (RuntimeException ignore) {
+            LOG.debug("While resolving {}", accountId, ignore);
+        }
+        return obj;
+    }
+
+    @Override
+    protected Mapping<?> getMapping() {
+        return profile.getTask().getResource().getRmapping();
+    }
+}

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/RoleSyncResultHandler.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/RoleSyncResultHandler.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/RoleSyncResultHandler.java
new file mode 100644
index 0000000..d172077
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/RoleSyncResultHandler.java
@@ -0,0 +1,167 @@
+/*
+ * 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.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.syncope.common.lib.mod.AbstractSubjectMod;
+import org.apache.syncope.common.lib.mod.AttrMod;
+import org.apache.syncope.common.lib.mod.RoleMod;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.to.PropagationStatus;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.server.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningResult;
+import org.identityconnectors.framework.common.objects.SyncDelta;
+
+public class RoleSyncResultHandler extends AbstractSubjectSyncResultHandler {
+
+    protected Map<Long, String> roleOwnerMap = new HashMap<>();
+
+    public Map<Long, String> getRoleOwnerMap() {
+        return this.roleOwnerMap;
+    }
+
+    @Override
+    protected AttributableUtil getAttributableUtil() {
+        return attrUtilFactory.getInstance(AttributableType.ROLE);
+    }
+
+    @Override
+    protected String getName(final AbstractSubjectTO subjectTO) {
+        return RoleTO.class.cast(subjectTO).getName();
+    }
+
+    @Override
+    protected AbstractSubjectTO getSubjectTO(final long key) {
+        try {
+            return roleTransfer.getRoleTO(roleDAO.authFetch(key));
+        } catch (Exception e) {
+            LOG.warn("Error retrieving role {}", key, e);
+            return null;
+        }
+    }
+
+    @Override
+    protected AbstractSubjectMod getSubjectMod(
+            final AbstractSubjectTO subjectTO, final SyncDelta delta) {
+
+        return connObjectUtil.getAttributableMod(
+                subjectTO.getKey(),
+                delta.getObject(),
+                subjectTO,
+                profile.getTask(),
+                attrUtilFactory.getInstance(AttributableType.ROLE));
+    }
+
+    @Override
+    protected AbstractSubjectTO create(
+            final AbstractSubjectTO subjectTO, final SyncDelta _delta, final ProvisioningResult result) {
+
+        RoleTO roleTO = RoleTO.class.cast(subjectTO);
+
+        Map.Entry<Long, List<PropagationStatus>> created = roleProvisioningManager.create(roleTO, roleOwnerMap,
+                Collections.singleton(profile.getTask().getResource().getKey()));
+
+        roleTO = roleTransfer.getRoleTO(roleDAO.authFetch(created.getKey()));
+
+        result.setId(created.getKey());
+        result.setName(getName(subjectTO));
+
+        return roleTO;
+    }
+
+    @Override
+    protected AbstractSubjectTO link(
+            final AbstractSubjectTO before,
+            final ProvisioningResult result,
+            final boolean unlink) {
+
+        final RoleMod roleMod = new RoleMod();
+        roleMod.setKey(before.getKey());
+
+        if (unlink) {
+            roleMod.getResourcesToRemove().add(profile.getTask().getResource().getKey());
+        } else {
+            roleMod.getResourcesToAdd().add(profile.getTask().getResource().getKey());
+        }
+
+        return roleTransfer.getRoleTO(roleDAO.authFetch(rwfAdapter.update(roleMod).getResult()));
+    }
+
+    @Override
+    protected AbstractSubjectTO update(
+            final AbstractSubjectTO before,
+            final AbstractSubjectMod subjectMod,
+            final SyncDelta delta,
+            final ProvisioningResult result) {
+
+        RoleMod roleMod = RoleMod.class.cast(subjectMod);
+
+        Map.Entry<Long, List<PropagationStatus>> updated = roleProvisioningManager.update(roleMod);
+
+        //moved after role provisioning manager
+        String roleOwner = null;
+        for (AttrMod attrMod : roleMod.getAttrsToUpdate()) {
+            if (attrMod.getSchema().isEmpty()) {
+                roleOwner = attrMod.getValuesToBeAdded().iterator().next();
+            }
+        }
+        if (roleOwner != null) {
+            roleOwnerMap.put(updated.getKey(), roleOwner);
+        }
+
+        final RoleTO after = roleTransfer.getRoleTO(roleDAO.authFetch(updated.getKey()));
+
+        result.setName(getName(after));
+
+        return after;
+    }
+
+    @Override
+    protected void deprovision(final Long id, final boolean unlink) {
+
+        taskExecutor.execute(
+                propagationManager.getRoleDeleteTaskIds(id, profile.getTask().getResource().getKey()));
+
+        if (unlink) {
+            final UserMod userMod = new UserMod();
+            userMod.setKey(id);
+            userMod.getResourcesToRemove().add(profile.getTask().getResource().getKey());
+        }
+    }
+
+    @Override
+    protected void delete(final Long id) {
+        try {
+            taskExecutor.execute(
+                    propagationManager.getRoleDeleteTaskIds(id, profile.getTask().getResource().getKey()));
+        } catch (Exception e) {
+            // A propagation failure doesn't imply a synchronization failure.
+            // The propagation exception status will be reported into the propagation task execution.
+            LOG.error("Could not propagate user " + id, e);
+        }
+
+        roleProvisioningManager.delete(id);
+    }
+}