syncope-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ilgro...@apache.org
Subject [07/52] [abbrv] [partial] syncope git commit: [SYNCOPE-620] Unit tests all in
Date Mon, 12 Jan 2015 16:31:46 GMT
http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/AbstractPropagationTaskExecutor.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
new file mode 100644
index 0000000..86df210
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
@@ -0,0 +1,540 @@
+/*
+ * 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.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditElements.Result;
+import org.apache.syncope.common.lib.types.MappingPurpose;
+import org.apache.syncope.common.lib.types.PropagationMode;
+import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
+import org.apache.syncope.common.lib.types.TraceLevel;
+import org.apache.syncope.server.persistence.api.dao.RoleDAO;
+import org.apache.syncope.server.persistence.api.dao.TaskDAO;
+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.EntityFactory;
+import org.apache.syncope.server.persistence.api.entity.ExternalResource;
+import org.apache.syncope.server.persistence.api.entity.Subject;
+import org.apache.syncope.server.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.server.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.server.provisioning.api.Connector;
+import org.apache.syncope.server.provisioning.api.ConnectorFactory;
+import org.apache.syncope.server.provisioning.api.TimeoutException;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationActions;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationReporter;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationTaskExecutor;
+import org.apache.syncope.server.misc.AuditManager;
+import org.apache.syncope.server.provisioning.java.notification.NotificationManager;
+import org.apache.syncope.server.misc.spring.ApplicationContextProvider;
+import org.apache.syncope.server.misc.ConnObjectUtil;
+import org.apache.syncope.server.misc.ExceptionUtil;
+import org.identityconnectors.framework.common.exceptions.ConnectorException;
+import org.identityconnectors.framework.common.objects.Attribute;
+import org.identityconnectors.framework.common.objects.AttributeUtil;
+import org.identityconnectors.framework.common.objects.ConnectorObject;
+import org.identityconnectors.framework.common.objects.Name;
+import org.identityconnectors.framework.common.objects.ObjectClass;
+import org.identityconnectors.framework.common.objects.Uid;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional(rollbackFor = { Throwable.class })
+public abstract class AbstractPropagationTaskExecutor implements PropagationTaskExecutor {
+
+    /**
+     * Logger.
+     */
+    protected static final Logger LOG = LoggerFactory.getLogger(PropagationTaskExecutor.class);
+
+    /**
+     * Connector factory.
+     */
+    @Autowired
+    protected ConnectorFactory connFactory;
+
+    /**
+     * ConnObjectUtil.
+     */
+    @Autowired
+    protected ConnObjectUtil connObjectUtil;
+
+    /**
+     * User DAO.
+     */
+    @Autowired
+    protected UserDAO userDAO;
+
+    /**
+     * User DAO.
+     */
+    @Autowired
+    protected RoleDAO roleDAO;
+
+    /**
+     * Task DAO.
+     */
+    @Autowired
+    protected TaskDAO taskDAO;
+
+    /**
+     * Notification Manager.
+     */
+    @Autowired
+    protected NotificationManager notificationManager;
+
+    /**
+     * Audit Manager.
+     */
+    @Autowired
+    protected AuditManager auditManager;
+
+    @Autowired
+    protected AttributableUtilFactory attrUtilFactory;
+
+    @Autowired
+    protected EntityFactory entityFactory;
+
+    @Override
+    public TaskExec execute(final PropagationTask task) {
+        return execute(task, null);
+    }
+
+    protected List<PropagationActions> getPropagationActions(final ExternalResource resource) {
+        List<PropagationActions> result = new ArrayList<>();
+
+        if (!resource.getPropagationActionsClassNames().isEmpty()) {
+            for (String className : resource.getPropagationActionsClassNames()) {
+                try {
+                    Class<?> actionsClass = Class.forName(className);
+                    result.add((PropagationActions) ApplicationContextProvider.getBeanFactory().
+                            createBean(actionsClass, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, true));
+                } catch (ClassNotFoundException e) {
+                    LOG.error("Invalid PropagationAction class name '{}' for resource {}", resource, className, e);
+                }
+            }
+        }
+
+        return result;
+    }
+
+    public static void createOrUpdate(
+            final ObjectClass oclass,
+            final String accountId,
+            final Set<Attribute> attrs,
+            final String resource,
+            final PropagationMode propagationMode,
+            final ConnectorObject beforeObj,
+            final Connector connector,
+            final Set<String> propagationAttempted,
+            final ConnObjectUtil connObjectUtil) {
+
+        // set of attributes to be propagated
+        final Set<Attribute> attributes = new HashSet<>(attrs);
+
+        // check if there is any missing or null / empty mandatory attribute
+        List<Object> mandatoryAttrNames = new ArrayList<>();
+        Attribute mandatoryMissing = AttributeUtil.find(MANDATORY_MISSING_ATTR_NAME, attrs);
+        if (mandatoryMissing != null) {
+            attributes.remove(mandatoryMissing);
+
+            if (beforeObj == null) {
+                mandatoryAttrNames.addAll(mandatoryMissing.getValue());
+            }
+        }
+        Attribute mandatoryNullOrEmpty = AttributeUtil.find(MANDATORY_NULL_OR_EMPTY_ATTR_NAME, attrs);
+        if (mandatoryNullOrEmpty != null) {
+            attributes.remove(mandatoryNullOrEmpty);
+
+            mandatoryAttrNames.addAll(mandatoryNullOrEmpty.getValue());
+        }
+        if (!mandatoryAttrNames.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "Not attempted because there are mandatory attributes without value(s): " + mandatoryAttrNames);
+        }
+
+        if (beforeObj == null) {
+            LOG.debug("Create {} on {}", attributes, resource);
+            connector.create(propagationMode, oclass, attributes, null, propagationAttempted);
+        } else {
+            // 1. check if rename is really required
+            final Name newName = (Name) AttributeUtil.find(Name.NAME, attributes);
+
+            LOG.debug("Rename required with value {}", newName);
+
+            if (newName != null && newName.equals(beforeObj.getName())
+                    && !newName.getNameValue().equals(beforeObj.getUid().getUidValue())) {
+
+                LOG.debug("Remote object name unchanged");
+                attributes.remove(newName);
+            }
+
+            // 2. check wether anything is actually needing to be propagated, i.e. if there is attribute
+            // difference between beforeObj - just read above from the connector - and the values to be propagated
+            Map<String, Attribute> originalAttrMap = connObjectUtil.toMap(beforeObj.getAttributes());
+            Map<String, Attribute> updateAttrMap = connObjectUtil.toMap(attributes);
+
+            // Only compare attribute from beforeObj that are also being updated
+            Set<String> skipAttrNames = originalAttrMap.keySet();
+            skipAttrNames.removeAll(updateAttrMap.keySet());
+            for (String attrName : new HashSet<>(skipAttrNames)) {
+                originalAttrMap.remove(attrName);
+            }
+
+            Set<Attribute> originalAttrs = new HashSet<>(originalAttrMap.values());
+
+            if (originalAttrs.equals(attributes)) {
+                LOG.debug("Don't need to propagate anything: {} is equal to {}", originalAttrs, attributes);
+            } else {
+                LOG.debug("Attributes that would be updated {}", attributes);
+
+                Set<Attribute> strictlyModified = new HashSet<>();
+                for (Attribute attr : attributes) {
+                    if (!originalAttrs.contains(attr)) {
+                        strictlyModified.add(attr);
+                    }
+                }
+
+                // 3. provision entry
+                LOG.debug("Update {} on {}", strictlyModified, resource);
+
+                connector.update(propagationMode, beforeObj.getObjectClass(),
+                        beforeObj.getUid(), strictlyModified, null, propagationAttempted);
+            }
+        }
+    }
+
+    protected void createOrUpdate(
+            final PropagationTask task,
+            final ConnectorObject beforeObj,
+            final Connector connector,
+            final Set<String> propagationAttempted) {
+
+        createOrUpdate(
+                new ObjectClass(task.getObjectClassName()),
+                task.getAccountId(),
+                task.getAttributes(),
+                task.getResource().getKey(),
+                task.getResource().getPropagationMode(),
+                beforeObj,
+                connector,
+                propagationAttempted,
+                connObjectUtil);
+    }
+
+    protected Subject<?, ?, ?> getSubject(final PropagationTask task) {
+        Subject<?, ?, ?> subject = null;
+
+        if (task.getSubjectKey() != null) {
+            switch (task.getSubjectType()) {
+                case USER:
+                    try {
+                        subject = userDAO.authFetch(task.getSubjectKey());
+                    } catch (Exception e) {
+                        LOG.error("Could not read user {}", task.getSubjectKey(), e);
+                    }
+                    break;
+
+                case ROLE:
+                    try {
+                        subject = roleDAO.authFetch(task.getSubjectKey());
+                    } catch (Exception e) {
+                        LOG.error("Could not read role {}", task.getSubjectKey(), e);
+                    }
+                    break;
+
+                case MEMBERSHIP:
+                default:
+            }
+        }
+
+        return subject;
+    }
+
+    protected void delete(final PropagationTask task, final ConnectorObject beforeObj,
+            final Connector connector, final Set<String> propagationAttempted) {
+
+        if (beforeObj == null) {
+            LOG.debug("{} not found on external resource: ignoring delete", task.getAccountId());
+        } else {
+            /*
+             * We must choose here whether to
+             * a. actually delete the provided user / role from the external resource
+             * b. just update the provided user / role data onto the external resource
+             *
+             * (a) happens when either there is no user / role associated with the PropagationTask (this takes place
+             * when the task is generated via UserController.delete() / RoleController.delete()) or the provided updated
+             * user / role hasn't the current resource assigned (when the task is generated via
+             * UserController.update() / RoleController.update()).
+             *
+             * (b) happens when the provided updated user / role does have the current resource assigned (when the task
+             * is generated via UserController.update() / RoleController.updae()): this basically means that before such
+             * update, this user / role used to have the current resource assigned by more than one mean (for example,
+             * two different memberships with the same resource).
+             */
+            Subject<?, ?, ?> subject = getSubject(task);
+            if (subject == null || !subject.getResourceNames().contains(task.getResource().getKey())) {
+                LOG.debug("Delete {} on {}", beforeObj.getUid(), task.getResource().getKey());
+
+                connector.delete(
+                        task.getPropagationMode(),
+                        beforeObj.getObjectClass(),
+                        beforeObj.getUid(),
+                        null,
+                        propagationAttempted);
+            } else {
+                createOrUpdate(task, beforeObj, connector, propagationAttempted);
+            }
+        }
+    }
+
+    @Override
+    public TaskExec execute(final PropagationTask task, final PropagationReporter reporter) {
+        final List<PropagationActions> actions = getPropagationActions(task.getResource());
+
+        final Date startDate = new Date();
+
+        final TaskExec execution = entityFactory.newEntity(TaskExec.class);
+        execution.setStatus(PropagationTaskExecStatus.CREATED.name());
+
+        String taskExecutionMessage = null;
+        String failureReason = null;
+
+        // Flag to state whether any propagation has been attempted
+        Set<String> propagationAttempted = new HashSet<>();
+
+        ConnectorObject beforeObj = null;
+        ConnectorObject afterObj = null;
+
+        Connector connector = null;
+        Result result;
+        try {
+            connector = connFactory.getConnector(task.getResource());
+
+            // Try to read remote object (user / group) BEFORE any actual operation
+            beforeObj = getRemoteObject(task, connector, false);
+
+            for (PropagationActions action : actions) {
+                action.before(task, beforeObj);
+            }
+
+            switch (task.getPropagationOperation()) {
+                case CREATE:
+                case UPDATE:
+                    createOrUpdate(task, beforeObj, connector, propagationAttempted);
+                    break;
+
+                case DELETE:
+                    delete(task, beforeObj, connector, propagationAttempted);
+                    break;
+
+                default:
+            }
+
+            execution.setStatus(task.getPropagationMode() == PropagationMode.ONE_PHASE
+                    ? PropagationTaskExecStatus.SUCCESS.name()
+                    : PropagationTaskExecStatus.SUBMITTED.name());
+
+            LOG.debug("Successfully propagated to {}", task.getResource());
+            result = Result.SUCCESS;
+        } catch (Exception e) {
+            result = Result.FAILURE;
+            LOG.error("Exception during provision on resource " + task.getResource().getKey(), e);
+
+            if (e instanceof ConnectorException && e.getCause() != null) {
+                taskExecutionMessage = e.getCause().getMessage();
+                if (e.getCause().getMessage() == null) {
+                    failureReason = e.getMessage();
+                } else {
+                    failureReason = e.getMessage() + "\n\n Cause: " + e.getCause().getMessage().split("\n")[0];
+                }
+            } else {
+                taskExecutionMessage = ExceptionUtil.getFullStackTrace(e);
+                if (e.getCause() == null) {
+                    failureReason = e.getMessage();
+                } else {
+                    failureReason = e.getMessage() + "\n\n Cause: " + e.getCause().getMessage().split("\n")[0];
+                }
+            }
+
+            try {
+                execution.setStatus(task.getPropagationMode() == PropagationMode.ONE_PHASE
+                        ? PropagationTaskExecStatus.FAILURE.name()
+                        : PropagationTaskExecStatus.UNSUBMITTED.name());
+            } catch (Exception wft) {
+                LOG.error("While executing KO action on {}", execution, wft);
+            }
+
+            propagationAttempted.add(task.getPropagationOperation().name().toLowerCase());
+        } finally {
+            // Try to read remote object (user / group) AFTER any actual operation
+            if (connector != null) {
+                try {
+                    afterObj = getRemoteObject(task, connector, true);
+                } catch (Exception ignore) {
+                    // ignore exception
+                    LOG.error("Error retrieving after object", ignore);
+                }
+            }
+
+            LOG.debug("Update execution for {}", task);
+
+            execution.setStartDate(startDate);
+            execution.setMessage(taskExecutionMessage);
+            execution.setEndDate(new Date());
+
+            if (hasToBeregistered(task, execution)) {
+                if (propagationAttempted.isEmpty()) {
+                    LOG.debug("No propagation attempted for {}", execution);
+                } else {
+                    execution.setTask(task);
+                    task.addExec(execution);
+
+                    LOG.debug("Execution finished: {}", execution);
+                }
+
+                taskDAO.save(task);
+
+                // this flush call is needed to generate a value for the execution id
+                taskDAO.flush();
+            }
+
+            if (reporter != null) {
+                reporter.onSuccessOrSecondaryResourceFailures(
+                        task.getResource().getKey(),
+                        PropagationTaskExecStatus.valueOf(execution.getStatus()),
+                        failureReason,
+                        beforeObj,
+                        afterObj);
+            }
+        }
+
+        for (PropagationActions action : actions) {
+            action.after(task, execution, afterObj);
+        }
+
+        notificationManager.createTasks(
+                AuditElements.EventCategoryType.PROPAGATION,
+                task.getSubjectType().name().toLowerCase(),
+                task.getResource().getKey(),
+                task.getPropagationOperation().name().toLowerCase(),
+                result,
+                beforeObj, // searching for before object is too much expensive ... 
+                new Object[] { execution, afterObj },
+                task);
+
+        auditManager.audit(
+                AuditElements.EventCategoryType.PROPAGATION,
+                task.getSubjectType().name().toLowerCase(),
+                task.getResource().getKey(),
+                task.getPropagationOperation().name().toLowerCase(),
+                result,
+                beforeObj, // searching for before object is too much expensive ... 
+                new Object[] { execution, afterObj },
+                task);
+
+        return execution;
+    }
+
+    @Override
+    public void execute(final Collection<PropagationTask> tasks) {
+        execute(tasks, null);
+    }
+
+    @Override
+    public abstract void execute(Collection<PropagationTask> tasks, final PropagationReporter reporter);
+
+    /**
+     * Check whether an execution has to be stored, for a given task.
+     *
+     * @param task execution's task
+     * @param execution to be decide whether to store or not
+     * @return true if execution has to be store, false otherwise
+     */
+    protected boolean hasToBeregistered(final PropagationTask task, final TaskExec execution) {
+        boolean result;
+
+        final boolean failed = !PropagationTaskExecStatus.valueOf(execution.getStatus()).isSuccessful();
+
+        switch (task.getPropagationOperation()) {
+
+            case CREATE:
+                result = (failed && task.getResource().getCreateTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal())
+                        || task.getResource().getCreateTraceLevel() == TraceLevel.ALL;
+                break;
+
+            case UPDATE:
+                result = (failed && task.getResource().getUpdateTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal())
+                        || task.getResource().getUpdateTraceLevel() == TraceLevel.ALL;
+                break;
+
+            case DELETE:
+                result = (failed && task.getResource().getDeleteTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal())
+                        || task.getResource().getDeleteTraceLevel() == TraceLevel.ALL;
+                break;
+
+            default:
+                result = false;
+        }
+
+        return result;
+    }
+
+    /**
+     * Get remote object for given task.
+     *
+     * @param connector connector facade proxy.
+     * @param task current propagation task.
+     * @param latest 'FALSE' to retrieve object using old accountId if not null.
+     * @return remote connector object.
+     */
+    protected ConnectorObject getRemoteObject(final PropagationTask task, final Connector connector,
+            final boolean latest) {
+
+        String accountId = latest || task.getOldAccountId() == null
+                ? task.getAccountId()
+                : task.getOldAccountId();
+
+        ConnectorObject obj = null;
+        try {
+            obj = connector.getObject(task.getPropagationMode(),
+                    task.getPropagationOperation(),
+                    new ObjectClass(task.getObjectClassName()),
+                    new Uid(accountId),
+                    connector.getOperationOptions(attrUtilFactory.getInstance(task.getSubjectType()).
+                            getMappingItems(task.getResource(), MappingPurpose.PROPAGATION)));
+        } catch (TimeoutException toe) {
+            LOG.debug("Request timeout", toe);
+            throw toe;
+        } catch (RuntimeException ignore) {
+            LOG.debug("While resolving {}", accountId, ignore);
+        }
+
+        return obj;
+    }
+}

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/DBPasswordPropagationActions.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/DBPasswordPropagationActions.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/DBPasswordPropagationActions.java
new file mode 100644
index 0000000..d403607
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/DBPasswordPropagationActions.java
@@ -0,0 +1,120 @@
+/*
+ * 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.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import org.apache.syncope.common.lib.types.AttributableType;
+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.task.PropagationTask;
+import org.apache.syncope.server.persistence.api.entity.user.User;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationTaskExecutor;
+import org.identityconnectors.common.security.GuardedString;
+import org.identityconnectors.framework.common.objects.Attribute;
+import org.identityconnectors.framework.common.objects.AttributeBuilder;
+import org.identityconnectors.framework.common.objects.AttributeUtil;
+import org.identityconnectors.framework.common.objects.ConnectorObject;
+import org.identityconnectors.framework.common.objects.OperationalAttributes;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Propagate a non-cleartext password out to a resource, if the PropagationManager has not already
+ * added a password. The CipherAlgorithm associated with the password must match the password
+ * cipher algorithm property of the DB Connector.
+ */
+public class DBPasswordPropagationActions extends DefaultPropagationActions {
+
+    private static final String CLEARTEXT = "CLEARTEXT";
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Transactional(readOnly = true)
+    @Override
+    public void before(final PropagationTask task, final ConnectorObject beforeObj) {
+        super.before(task, beforeObj);
+
+        if (AttributableType.USER == task.getSubjectType()) {
+            User user = userDAO.find(task.getSubjectKey());
+
+            if (user != null && user.getPassword() != null) {
+                Attribute missing = AttributeUtil.find(
+                        PropagationTaskExecutor.MANDATORY_MISSING_ATTR_NAME,
+                        task.getAttributes());
+
+                ConnInstance connInstance = task.getResource().getConnector();
+                if (missing != null && missing.getValue() != null && missing.getValue().size() == 1
+                        && missing.getValue().get(0).equals(OperationalAttributes.PASSWORD_NAME)
+                        && cipherAlgorithmMatches(getCipherAlgorithm(connInstance), user.getCipherAlgorithm())) {
+
+                    Attribute passwordAttribute = AttributeBuilder.buildPassword(
+                            new GuardedString(user.getPassword().toCharArray()));
+
+                    Set<Attribute> attributes = new HashSet<Attribute>(task.getAttributes());
+                    attributes.add(passwordAttribute);
+                    attributes.remove(missing);
+
+                    Attribute hashedPasswordAttribute = AttributeBuilder.build(
+                            AttributeUtil.createSpecialName("HASHED_PASSWORD"), Boolean.TRUE);
+                    attributes.add(hashedPasswordAttribute);
+
+                    task.setAttributes(attributes);
+                }
+            }
+        }
+    }
+
+    private String getCipherAlgorithm(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;
+    }
+
+    private boolean cipherAlgorithmMatches(String connectorAlgorithm, CipherAlgorithm userAlgorithm) {
+        if (userAlgorithm == null) {
+            return false;
+        }
+
+        if (connectorAlgorithm.equals(userAlgorithm.name())) {
+            return true;
+        }
+
+        // Special check for "SHA" (user sync'd from LDAP)
+        if ("SHA1".equals(connectorAlgorithm) && "SHA".equals(userAlgorithm.name())) {
+            return true;
+        }
+
+        return false;
+    }
+
+}

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/DefaultPropagationActions.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/DefaultPropagationActions.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/DefaultPropagationActions.java
new file mode 100644
index 0000000..5e7ba34
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/DefaultPropagationActions.java
@@ -0,0 +1,38 @@
+/*
+ * 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 org.apache.syncope.server.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.server.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationActions;
+import org.identityconnectors.framework.common.objects.ConnectorObject;
+
+/**
+ * Default (empty) implementation of PropagationActions.
+ */
+public abstract class DefaultPropagationActions implements PropagationActions {
+
+    @Override
+    public void before(final PropagationTask task, final ConnectorObject beforeObj) {
+    }
+
+    @Override
+    public void after(final PropagationTask task, final TaskExec execution, final ConnectorObject afterObj) {
+    }
+}

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/DefaultPropagationReporter.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/DefaultPropagationReporter.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/DefaultPropagationReporter.java
new file mode 100644
index 0000000..a286037
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/DefaultPropagationReporter.java
@@ -0,0 +1,94 @@
+/*
+ * 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.List;
+import org.apache.syncope.common.lib.to.PropagationStatus;
+import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
+import org.apache.syncope.server.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationReporter;
+import org.apache.syncope.server.misc.ConnObjectUtil;
+import org.identityconnectors.framework.common.objects.ConnectorObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public class DefaultPropagationReporter implements PropagationReporter {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(DefaultPropagationReporter.class);
+
+    @Autowired
+    protected ConnObjectUtil connObjectUtil;
+
+    protected final List<PropagationStatus> statuses = new ArrayList<>();
+
+    @Override
+    public void onSuccessOrSecondaryResourceFailures(final String resource,
+            final PropagationTaskExecStatus executionStatus,
+            final String failureReason, final ConnectorObject beforeObj, final ConnectorObject afterObj) {
+
+        final PropagationStatus propagation = new PropagationStatus();
+        propagation.setResource(resource);
+        propagation.setStatus(executionStatus);
+        propagation.setFailureReason(failureReason);
+
+        if (beforeObj != null) {
+            propagation.setBeforeObj(connObjectUtil.getConnObjectTO(beforeObj));
+        }
+
+        if (afterObj != null) {
+            propagation.setAfterObj(connObjectUtil.getConnObjectTO(afterObj));
+        }
+
+        statuses.add(propagation);
+    }
+
+    private boolean containsPropagationStatusTO(final String resourceName) {
+        for (PropagationStatus status : statuses) {
+            if (resourceName.equals(status.getResource())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void onPrimaryResourceFailure(final List<PropagationTask> tasks) {
+        final String failedResource = statuses.get(statuses.size() - 1).getResource();
+
+        LOG.debug("Propagation error: {} primary resource failed to propagate", failedResource);
+
+        for (PropagationTask propagationTask : tasks) {
+            if (!containsPropagationStatusTO(propagationTask.getResource().getKey())) {
+                final PropagationStatus propagationStatusTO = new PropagationStatus();
+                propagationStatusTO.setResource(propagationTask.getResource().getKey());
+                propagationStatusTO.setStatus(PropagationTaskExecStatus.FAILURE);
+                propagationStatusTO.setFailureReason(
+                        "Propagation error: " + failedResource + " primary resource failed to propagate.");
+                statuses.add(propagationStatusTO);
+            }
+        }
+    }
+
+    @Override
+    public List<PropagationStatus> getStatuses() {
+        return statuses;
+    }
+}

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/LDAPMembershipPropagationActions.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/LDAPMembershipPropagationActions.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/LDAPMembershipPropagationActions.java
new file mode 100644
index 0000000..a98a3e5
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/LDAPMembershipPropagationActions.java
@@ -0,0 +1,113 @@
+/*
+ * 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.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.apache.commons.jexl2.JexlContext;
+import org.apache.commons.jexl2.MapContext;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.server.persistence.api.dao.UserDAO;
+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.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.identityconnectors.framework.common.objects.ConnectorObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Simple action for propagating role memberships to LDAP groups, when the same resource is configured for both users
+ * and roles.
+ *
+ * @see org.apache.syncope.core.sync.impl.LDAPMembershipSyncActions
+ */
+public class LDAPMembershipPropagationActions extends DefaultPropagationActions {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(LDAPMembershipPropagationActions.class);
+
+    @Autowired
+    protected UserDAO userDAO;
+
+    /**
+     * Allows easy subclassing for the ConnId AD connector bundle.
+     *
+     * @return the name of the attribute used to keep track of group memberships
+     */
+    protected String getGroupMembershipAttrName() {
+        return "ldapGroups";
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public void before(final PropagationTask task, final ConnectorObject beforeObj) {
+        super.before(task, beforeObj);
+
+        if (AttributableType.USER == task.getSubjectType() && task.getResource().getRmapping() != null) {
+            User user = userDAO.find(task.getSubjectKey());
+            if (user != null) {
+                List<String> roleAccountLinks = new ArrayList<>();
+                for (Role role : user.getRoles()) {
+                    if (role.getResourceNames().contains(task.getResource().getKey())
+                            && StringUtils.isNotBlank(task.getResource().getRmapping().getAccountLink())) {
+
+                        LOG.debug("Evaluating accountLink for {}", role);
+
+                        final JexlContext jexlContext = new MapContext();
+                        JexlUtil.addFieldsToContext(role, jexlContext);
+                        JexlUtil.addAttrsToContext(role.getPlainAttrs(), jexlContext);
+                        JexlUtil.addDerAttrsToContext(role.getDerAttrs(), role.getPlainAttrs(), jexlContext);
+
+                        final String roleAccountLink =
+                                JexlUtil.evaluate(task.getResource().getRmapping().getAccountLink(), jexlContext);
+                        LOG.debug("AccountLink for {} is '{}'", role, roleAccountLink);
+                        if (StringUtils.isNotBlank(roleAccountLink)) {
+                            roleAccountLinks.add(roleAccountLink);
+                        }
+                    }
+                }
+                LOG.debug("Role accountLinks to propagate for membership: {}", roleAccountLinks);
+
+                Set<Attribute> attributes = new HashSet<Attribute>(task.getAttributes());
+
+                Set<String> groups = new HashSet<String>(roleAccountLinks);
+                Attribute ldapGroups = AttributeUtil.find(getGroupMembershipAttrName(), attributes);
+
+                if (ldapGroups != null) {
+                    for (Object obj : ldapGroups.getValue()) {
+                        groups.add(obj.toString());
+                    }
+                }
+
+                attributes.add(AttributeBuilder.build(getGroupMembershipAttrName(), groups));
+                task.setAttributes(attributes);
+            }
+        } else {
+            LOG.debug("Not about user, or role mapping missing for resource: not doing anything");
+        }
+    }
+}

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/LDAPPasswordPropagationActions.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/LDAPPasswordPropagationActions.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/LDAPPasswordPropagationActions.java
new file mode 100644
index 0000000..95ed87f
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/LDAPPasswordPropagationActions.java
@@ -0,0 +1,125 @@
+/*
+ * 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.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import org.apache.syncope.common.lib.types.AttributableType;
+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.task.PropagationTask;
+import org.apache.syncope.server.persistence.api.entity.user.User;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationTaskExecutor;
+import org.identityconnectors.common.security.GuardedString;
+import org.identityconnectors.framework.common.objects.Attribute;
+import org.identityconnectors.framework.common.objects.AttributeBuilder;
+import org.identityconnectors.framework.common.objects.AttributeUtil;
+import org.identityconnectors.framework.common.objects.ConnectorObject;
+import org.identityconnectors.framework.common.objects.OperationalAttributes;
+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;
+
+/**
+ * Propagate a non-cleartext password out to a resource, if the PropagationManager has not already
+ * added a password. The CipherAlgorithm associated with the password must match the password
+ * hash algorithm property of the LDAP Connector.
+ */
+public class LDAPPasswordPropagationActions extends DefaultPropagationActions {
+
+    private static final String CLEARTEXT = "CLEARTEXT";
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Transactional(readOnly = true)
+    @Override
+    public void before(final PropagationTask task, final ConnectorObject beforeObj) {
+        super.before(task, beforeObj);
+
+        if (AttributableType.USER == task.getSubjectType()) {
+            User user = userDAO.find(task.getSubjectKey());
+
+            if (user != null && user.getPassword() != null) {
+                Attribute missing = AttributeUtil.find(
+                        PropagationTaskExecutor.MANDATORY_MISSING_ATTR_NAME,
+                        task.getAttributes());
+
+                ConnInstance connInstance = task.getResource().getConnector();
+                String cipherAlgorithm = getCipherAlgorithm(connInstance);
+                if (missing != null && missing.getValue() != null && missing.getValue().size() == 1
+                        && missing.getValue().get(0).equals(OperationalAttributes.PASSWORD_NAME)
+                        && cipherAlgorithmMatches(getCipherAlgorithm(connInstance), user.getCipherAlgorithm())) {
+
+                    String password = user.getPassword().toLowerCase();
+                    byte[] decodedPassword = Hex.decode(password);
+                    byte[] base64EncodedPassword = Base64.encode(decodedPassword);
+
+                    String cipherPlusPassword =
+                            ("{" + cipherAlgorithm.toLowerCase() + "}" + new String(base64EncodedPassword));
+
+                    Attribute passwordAttribute = AttributeBuilder.buildPassword(
+                            new GuardedString(cipherPlusPassword.toCharArray()));
+
+                    Set<Attribute> attributes = new HashSet<Attribute>(task.getAttributes());
+                    attributes.add(passwordAttribute);
+                    attributes.remove(missing);
+
+                    task.setAttributes(attributes);
+                }
+            }
+        }
+    }
+
+    private String getCipherAlgorithm(ConnInstance connInstance) {
+        String cipherAlgorithm = CLEARTEXT;
+        for (Iterator<ConnConfProperty> propertyIterator = connInstance.getConfiguration().iterator();
+                propertyIterator.hasNext();) {
+
+            ConnConfProperty property = propertyIterator.next();
+            if ("passwordHashAlgorithm".equals(property.getSchema().getName())
+                    && property.getValues() != null && !property.getValues().isEmpty()) {
+                return (String) property.getValues().get(0);
+            }
+        }
+        return cipherAlgorithm;
+    }
+
+    private boolean cipherAlgorithmMatches(String connectorAlgorithm, CipherAlgorithm userAlgorithm) {
+        if (userAlgorithm == null) {
+            return false;
+        }
+
+        if (connectorAlgorithm.equals(userAlgorithm.name())) {
+            return true;
+        }
+
+        // Special check for "SHA" (user sync'd from LDAP)
+        if ("SHA".equals(connectorAlgorithm) && "SHA1".equals(userAlgorithm.name())) {
+            return true;
+        }
+
+        return false;
+    }
+
+}

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/PriorityPropagationTaskExecutor.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/PriorityPropagationTaskExecutor.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/PriorityPropagationTaskExecutor.java
new file mode 100644
index 0000000..7016a0b
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/propagation/PriorityPropagationTaskExecutor.java
@@ -0,0 +1,119 @@
+/*
+ * 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.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditElements.Result;
+import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
+import org.apache.syncope.server.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.server.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationException;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationReporter;
+import org.springframework.stereotype.Component;
+
+@Component
+public class PriorityPropagationTaskExecutor extends AbstractPropagationTaskExecutor {
+
+    /**
+     * Sort the given collection by looking at related ExternalResource's priority, then execute.
+     * {@inheritDoc}
+     */
+    @Override
+    public void execute(final Collection<PropagationTask> tasks, final PropagationReporter reporter) {
+        final List<PropagationTask> prioritizedTasks = new ArrayList<>(tasks);
+        Collections.sort(prioritizedTasks, new PriorityComparator());
+
+        Result result = Result.SUCCESS;
+
+        try {
+            for (PropagationTask task : prioritizedTasks) {
+                LOG.debug("Execution started for {}", task);
+
+                TaskExec execution = execute(task, reporter);
+
+                LOG.debug("Execution finished for {}, {}", task, execution);
+
+                // Propagation is interrupted as soon as the result of the
+                // communication with a primary resource is in error
+                PropagationTaskExecStatus execStatus;
+                try {
+                    execStatus = PropagationTaskExecStatus.valueOf(execution.getStatus());
+                } catch (IllegalArgumentException e) {
+                    LOG.error("Unexpected execution status found {}", execution.getStatus());
+                    execStatus = PropagationTaskExecStatus.FAILURE;
+                }
+                if (task.getResource().isPropagationPrimary() && !execStatus.isSuccessful()) {
+                    result = Result.FAILURE;
+                    throw new PropagationException(task.getResource().getKey(), execution.getMessage());
+                }
+            }
+        } finally {
+            notificationManager.createTasks(
+                    AuditElements.EventCategoryType.PROPAGATION,
+                    null,
+                    null,
+                    null,
+                    result,
+                    reporter.getStatuses(),
+                    tasks);
+
+            auditManager.audit(
+                    AuditElements.EventCategoryType.PROPAGATION,
+                    null,
+                    null,
+                    null,
+                    result,
+                    reporter.getStatuses(),
+                    tasks);
+        }
+    }
+
+    /**
+     * Compare propagation tasks according to related ExternalResource's priority.
+     *
+     * @see PropagationTask
+     * @see org.apache.syncope.core.persistence.beans.ExternalResource#propagationPriority
+     */
+    protected static class PriorityComparator implements Comparator<PropagationTask>, Serializable {
+
+        private static final long serialVersionUID = -1969355670784448878L;
+
+        @Override
+        public int compare(final PropagationTask task1, final PropagationTask task2) {
+            int prop1 = task1.getResource().getPropagationPriority() == null
+                    ? Integer.MIN_VALUE
+                    : task1.getResource().getPropagationPriority();
+            int prop2 = task2.getResource().getPropagationPriority() == null
+                    ? Integer.MIN_VALUE
+                    : task2.getResource().getPropagationPriority();
+
+            return prop1 > prop2
+                    ? 1
+                    : prop1 == prop2
+                            ? 0
+                            : -1;
+        }
+    }
+}


Mime
View raw message