http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractTaskJob.java
----------------------------------------------------------------------
diff --git 
a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractTaskJob.java
 
b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractTaskJob.java
new file mode 100644
index 0000000..2eddca1
--- /dev/null
+++ 
b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractTaskJob.java
@@ -0,0 +1,183 @@
+/*
+ * 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.core.provisioning.java.job;
+
+import java.util.Date;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditElements.Result;
+import org.apache.syncope.core.persistence.api.dao.TaskDAO;
+import org.apache.syncope.core.persistence.api.dao.TaskExecDAO;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.task.Task;
+import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.core.provisioning.api.job.TaskJob;
+import org.apache.syncope.core.misc.AuditManager;
+import org.apache.syncope.core.misc.ExceptionUtil;
+import 
org.apache.syncope.core.provisioning.api.notification.NotificationManager;
+import org.quartz.DisallowConcurrentExecution;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Abstract job implementation that delegates to concrete implementation the 
actual job execution and provides some
+ * base features.
+ * <strong>Extending this class will not provide support transaction 
management.</strong><br/>
+ * Extend <tt>AbstractTransactionalTaskJob</tt> for this purpose.
+ *
+ * @see AbstractTransactionalTaskJob
+ */
+@DisallowConcurrentExecution
+public abstract class AbstractTaskJob implements TaskJob {
+
+    public static final String DRY_RUN_JOBDETAIL_KEY = "dryRun";
+
+    /**
+     * Task execution status.
+     */
+    public enum Status {
+
+        SUCCESS,
+        FAILURE
+
+    }
+
+    /**
+     * Logger.
+     */
+    protected static final Logger LOG = 
LoggerFactory.getLogger(AbstractTaskJob.class);
+
+    /**
+     * Task DAO.
+     */
+    @Autowired
+    protected TaskDAO taskDAO;
+
+    /**
+     * Task execution DAO.
+     */
+    @Autowired
+    private TaskExecDAO taskExecDAO;
+
+    /**
+     * Notification manager.
+     */
+    @Autowired
+    private NotificationManager notificationManager;
+
+    /**
+     * Audit manager.
+     */
+    @Autowired
+    private AuditManager auditManager;
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    /**
+     * Id, set by the caller, for identifying the task to be executed.
+     */
+    protected Long taskId;
+
+    /**
+     * The actual task to be executed.
+     */
+    protected Task task;
+
+    /**
+     * Task id setter.
+     *
+     * @param taskId to be set
+     */
+    @Override
+    public void setTaskId(final Long taskId) {
+        this.taskId = taskId;
+    }
+
+    @Override
+    public void execute(final JobExecutionContext context) throws 
JobExecutionException {
+        task = taskDAO.find(taskId);
+        if (task == null) {
+            throw new JobExecutionException("Task " + taskId + " not found");
+        }
+
+        TaskExec execution = entityFactory.newEntity(TaskExec.class);
+        execution.setStartDate(new Date());
+        execution.setTask(task);
+
+        Result result;
+
+        try {
+            
execution.setMessage(doExecute(context.getMergedJobDataMap().getBoolean(DRY_RUN_JOBDETAIL_KEY)));
+            execution.setStatus(Status.SUCCESS.name());
+            result = Result.SUCCESS;
+        } catch (JobExecutionException e) {
+            LOG.error("While executing task " + taskId, e);
+            result = Result.FAILURE;
+
+            execution.setMessage(ExceptionUtil.getFullStackTrace(e));
+            execution.setStatus(Status.FAILURE.name());
+        }
+        execution.setEndDate(new Date());
+
+        if (hasToBeRegistered(execution)) {
+            taskExecDAO.saveAndAdd(taskId, execution);
+        }
+        task = taskDAO.save(task);
+
+        notificationManager.createTasks(
+                AuditElements.EventCategoryType.TASK,
+                this.getClass().getSimpleName(),
+                null,
+                this.getClass().getSimpleName(), // searching for before 
object is too much expensive ...
+                result,
+                task,
+                execution);
+
+        auditManager.audit(
+                AuditElements.EventCategoryType.TASK,
+                task.getClass().getSimpleName(),
+                null,
+                null, // searching for before object is too much expensive ...
+                result,
+                task,
+                (Object[]) null);
+    }
+
+    /**
+     * The actual execution, delegated to child classes.
+     *
+     * @param dryRun whether to actually touch the data
+     * @return the task execution status to be set
+     * @throws JobExecutionException if anything goes wrong
+     */
+    protected abstract String doExecute(boolean dryRun) throws 
JobExecutionException;
+
+    /**
+     * Template method to determine whether this job's task execution has to 
be persisted or not.
+     *
+     * @param execution task execution
+     * @return wether to persist or not
+     */
+    protected boolean hasToBeRegistered(final TaskExec execution) {
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractTransactionalTaskJob.java
----------------------------------------------------------------------
diff --git 
a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractTransactionalTaskJob.java
 
b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractTransactionalTaskJob.java
new file mode 100644
index 0000000..b90c142
--- /dev/null
+++ 
b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/AbstractTransactionalTaskJob.java
@@ -0,0 +1,35 @@
+/*
+ * 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.core.provisioning.java.job;
+
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Abstract job implementation for transactional execution.
+ */
+public abstract class AbstractTransactionalTaskJob extends AbstractTaskJob {
+
+    @Transactional
+    @Override
+    public void execute(final JobExecutionContext context) throws 
JobExecutionException {
+        super.execute(context);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SampleJob.java
----------------------------------------------------------------------
diff --git 
a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SampleJob.java
 
b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SampleJob.java
new file mode 100644
index 0000000..e031905
--- /dev/null
+++ 
b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SampleJob.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java.job;
+
+import org.apache.syncope.core.persistence.api.entity.task.SchedTask;
+import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
+import org.quartz.JobExecutionException;
+
+/**
+ * Sample implementation for execution a scheduled task.
+ *
+ * @see SchedTask
+ */
+public class SampleJob extends AbstractTaskJob {
+
+    @Override
+    protected String doExecute(final boolean dryRun) throws 
JobExecutionException {
+        if (!(task instanceof SchedTask)) {
+            throw new JobExecutionException("Task " + taskId + " isn't a 
SchedTask");
+        }
+        final SchedTask schedTask = (SchedTask) this.task;
+
+        LOG.info("SampleJob {}running [SchedTask {}]", (dryRun
+                ? "dry "
+                : ""), schedTask.getKey());
+
+        return (dryRun
+                ? "DRY "
+                : "") + "RUNNING";
+    }
+
+    @Override
+    protected boolean hasToBeRegistered(final TaskExec execution) {
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SpringBeanJobFactory.java
----------------------------------------------------------------------
diff --git 
a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SpringBeanJobFactory.java
 
b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SpringBeanJobFactory.java
new file mode 100644
index 0000000..e54739b
--- /dev/null
+++ 
b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SpringBeanJobFactory.java
@@ -0,0 +1,97 @@
+/*
+ * 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.core.provisioning.java.job;
+
+import org.apache.syncope.core.provisioning.api.job.JobNamer;
+import org.apache.syncope.core.provisioning.api.job.JobInstanceLoader;
+import org.quartz.SchedulerContext;
+import org.quartz.spi.TriggerFiredBundle;
+import org.springframework.beans.BeanWrapper;
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.beans.PropertyAccessorFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ConfigurableApplicationContext;
+
+public class SpringBeanJobFactory extends 
org.springframework.scheduling.quartz.SpringBeanJobFactory {
+
+    private String[] ignoredUnknownProperties;
+
+    private SchedulerContext schedulerContext;
+
+    @Override
+    public void setIgnoredUnknownProperties(final String[] 
ignoredUnknownProperties) {
+        String[] defensiveCopy = ignoredUnknownProperties.clone();
+        super.setIgnoredUnknownProperties(defensiveCopy);
+        this.ignoredUnknownProperties = defensiveCopy;
+    }
+
+    @Override
+    public void setSchedulerContext(final SchedulerContext schedulerContext) {
+        super.setSchedulerContext(schedulerContext);
+        this.schedulerContext = schedulerContext;
+    }
+
+    /**
+     * An implementation of SpringBeanJobFactory that retrieves the bean from 
the Spring context so that autowiring and
+     * transactions work.
+     *
+     * {@inheritDoc}
+     */
+    @Override
+    protected Object createJobInstance(final TriggerFiredBundle bundle) throws 
Exception {
+        final ApplicationContext ctx = ((ConfigurableApplicationContext) 
schedulerContext.get("applicationContext"));
+
+        // Try to re-create job bean from underlying task (useful for managing 
failover scenarios)
+        if (!ctx.containsBean(bundle.getJobDetail().getKey().getName())) {
+            Long taskId = 
JobNamer.getTaskIdFromJobName(bundle.getJobDetail().getKey().getName());
+            if (taskId != null) {
+                JobInstanceLoader jobInstanceLoader = 
ctx.getBean(JobInstanceLoader.class);
+                jobInstanceLoader.registerTaskJob(taskId);
+            }
+
+            Long reportId = 
JobNamer.getReportIdFromJobName(bundle.getJobDetail().getKey().getName());
+            if (reportId != null) {
+                JobInstanceLoader jobInstanceLoader = 
ctx.getBean(JobInstanceLoader.class);
+                jobInstanceLoader.registerReportJob(reportId);
+            }
+        }
+
+        final Object job = 
ctx.getBean(bundle.getJobDetail().getKey().getName());
+        final BeanWrapper wrapper = 
PropertyAccessorFactory.forBeanPropertyAccess(job);
+        if (isEligibleForPropertyPopulation(wrapper.getWrappedInstance())) {
+            final MutablePropertyValues pvs = new MutablePropertyValues();
+            if (this.schedulerContext != null) {
+                pvs.addPropertyValues(this.schedulerContext);
+            }
+            pvs.addPropertyValues(bundle.getJobDetail().getJobDataMap());
+            pvs.addPropertyValues(bundle.getTrigger().getJobDataMap());
+            if (this.ignoredUnknownProperties == null) {
+                wrapper.setPropertyValues(pvs, true);
+            } else {
+                for (String propName : this.ignoredUnknownProperties) {
+                    if (pvs.contains(propName) && 
!wrapper.isWritableProperty(propName)) {
+                        pvs.removePropertyValue(propName);
+                    }
+                }
+                wrapper.setPropertyValues(pvs);
+            }
+        }
+        return job;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/NotificationManagerImpl.java
----------------------------------------------------------------------
diff --git 
a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/NotificationManagerImpl.java
 
b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/NotificationManagerImpl.java
new file mode 100644
index 0000000..67d5a24
--- /dev/null
+++ 
b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/NotificationManagerImpl.java
@@ -0,0 +1,416 @@
+/*
+ * 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.core.provisioning.java.notification;
+
+import 
org.apache.syncope.core.provisioning.api.notification.NotificationManager;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditElements.Result;
+import org.apache.syncope.common.lib.types.AuditLoggerName;
+import org.apache.syncope.common.lib.types.IntMappingType;
+import org.apache.syncope.common.lib.types.SubjectType;
+import org.apache.syncope.core.persistence.api.RoleEntitlementUtil;
+import org.apache.syncope.core.persistence.api.dao.ConfDAO;
+import org.apache.syncope.core.persistence.api.dao.EntitlementDAO;
+import org.apache.syncope.core.persistence.api.dao.NotificationDAO;
+import org.apache.syncope.core.persistence.api.dao.RoleDAO;
+import org.apache.syncope.core.persistence.api.dao.SubjectSearchDAO;
+import org.apache.syncope.core.persistence.api.dao.TaskDAO;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.core.persistence.api.entity.Attributable;
+import org.apache.syncope.core.persistence.api.entity.AttributableUtilFactory;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.Notification;
+import org.apache.syncope.core.persistence.api.entity.PlainAttr;
+import org.apache.syncope.core.persistence.api.entity.Subject;
+import org.apache.syncope.core.persistence.api.entity.role.Role;
+import org.apache.syncope.core.persistence.api.entity.task.NotificationTask;
+import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.core.persistence.api.entity.user.UDerAttr;
+import org.apache.syncope.core.persistence.api.entity.user.UPlainAttr;
+import org.apache.syncope.core.persistence.api.entity.user.UVirAttr;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.api.data.RoleDataBinder;
+import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
+import org.apache.syncope.core.misc.ConnObjectUtil;
+import org.apache.syncope.core.misc.search.SearchCondConverter;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.tools.ToolManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+@Transactional(rollbackFor = { Throwable.class })
+public class NotificationManagerImpl implements NotificationManager {
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG = 
LoggerFactory.getLogger(NotificationManager.class);
+
+    public static final String MAIL_TEMPLATES = "mailTemplates/";
+
+    public static final String MAIL_TEMPLATE_HTML_SUFFIX = ".html.vm";
+
+    public static final String MAIL_TEMPLATE_TEXT_SUFFIX = ".txt.vm";
+
+    /**
+     * Notification DAO.
+     */
+    @Autowired
+    private NotificationDAO notificationDAO;
+
+    /**
+     * Configuration DAO.
+     */
+    @Autowired
+    private ConfDAO confDAO;
+
+    /**
+     * User DAO.
+     */
+    @Autowired
+    private UserDAO userDAO;
+
+    /**
+     * Role DAO.
+     */
+    @Autowired
+    private RoleDAO roleDAO;
+
+    /**
+     * User Search DAO.
+     */
+    @Autowired
+    private SubjectSearchDAO searchDAO;
+
+    /**
+     * Task DAO.
+     */
+    @Autowired
+    private TaskDAO taskDAO;
+
+    /**
+     * Velocity template engine.
+     */
+    @Autowired
+    private VelocityEngine velocityEngine;
+
+    /**
+     * Velocity tool manager.
+     */
+    @Autowired
+    private ToolManager velocityToolManager;
+
+    @Autowired
+    private EntitlementDAO entitlementDAO;
+
+    @Autowired
+    private ConnObjectUtil connObjectUtil;
+
+    @Autowired
+    private UserDataBinder userDataBinder;
+
+    @Autowired
+    private RoleDataBinder roleDataBinder;
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    @Autowired
+    private AttributableUtilFactory attrUtilFactory;
+
+    @Transactional(readOnly = true)
+    @Override
+    public long getMaxRetries() {
+        return confDAO.find("notification.maxRetries", 
"0").getValues().get(0).getLongValue();
+    }
+
+    /**
+     * Create a notification task.
+     *
+     * @param notification notification to take as model
+     * @param attributable the user this task is about
+     * @param model Velocity model
+     * @return notification task, fully populated
+     */
+    private NotificationTask getNotificationTask(
+            final Notification notification,
+            final Attributable<?, ?, ?> attributable,
+            final Map<String, Object> model) {
+
+        if (attributable != null) {
+            connObjectUtil.retrieveVirAttrValues(attributable,
+                    attrUtilFactory.getInstance(
+                            attributable instanceof User ? 
AttributableType.USER : AttributableType.ROLE));
+        }
+
+        final List<User> recipients = new ArrayList<>();
+
+        if (notification.getRecipients() != null) {
+            
recipients.addAll(searchDAO.<User>search(RoleEntitlementUtil.getRoleKeys(entitlementDAO.findAll()),
+                    SearchCondConverter.convert(notification.getRecipients()),
+                    Collections.<OrderByClause>emptyList(), SubjectType.USER));
+        }
+
+        if (notification.isSelfAsRecipient() && attributable instanceof User) {
+            recipients.add((User) attributable);
+        }
+
+        final Set<String> recipientEmails = new HashSet<>();
+        final List<UserTO> recipientTOs = new ArrayList<>(recipients.size());
+        for (User recipient : recipients) {
+            connObjectUtil.retrieveVirAttrValues(recipient, 
attrUtilFactory.getInstance(AttributableType.USER));
+
+            String email = 
getRecipientEmail(notification.getRecipientAttrType(),
+                    notification.getRecipientAttrName(), recipient);
+            if (email == null) {
+                LOG.warn("{} cannot be notified: {} not found", recipient, 
notification.getRecipientAttrName());
+            } else {
+                recipientEmails.add(email);
+                recipientTOs.add(userDataBinder.getUserTO(recipient));
+            }
+        }
+
+        if (notification.getStaticRecipients() != null) {
+            recipientEmails.addAll(notification.getStaticRecipients());
+        }
+
+        model.put("recipients", recipientTOs);
+        model.put("syncopeConf", this.findAllSyncopeConfs());
+        model.put("events", notification.getEvents());
+
+        NotificationTask task = 
entityFactory.newEntity(NotificationTask.class);
+        task.setTraceLevel(notification.getTraceLevel());
+        task.getRecipients().addAll(recipientEmails);
+        task.setSender(notification.getSender());
+        task.setSubject(notification.getSubject());
+
+        String htmlBody = mergeTemplateIntoString(
+                MAIL_TEMPLATES + notification.getTemplate() + 
MAIL_TEMPLATE_HTML_SUFFIX, model);
+        String textBody = mergeTemplateIntoString(
+                MAIL_TEMPLATES + notification.getTemplate() + 
MAIL_TEMPLATE_TEXT_SUFFIX, model);
+
+        task.setHtmlBody(htmlBody);
+        task.setTextBody(textBody);
+
+        return task;
+    }
+
+    private String mergeTemplateIntoString(final String templateLocation, 
final Map<String, Object> model) {
+        StringWriter result = new StringWriter();
+        try {
+            Context velocityContext = createVelocityContext(model);
+            velocityEngine.mergeTemplate(templateLocation, 
SyncopeConstants.DEFAULT_ENCODING, velocityContext, result);
+        } catch (VelocityException e) {
+            LOG.error("Could not get mail body", e);
+        } catch (RuntimeException e) {
+            // ensure same behaviour as by using Spring 
VelocityEngineUtils.mergeTemplateIntoString()
+            throw e;
+        } catch (Exception e) {
+            LOG.error("Could not get mail body", e);
+        }
+
+        return result.toString();
+    }
+
+    /**
+     * Create a Velocity Context for the given model, to be passed to the 
template for merging.
+     *
+     * @param model Velocity model
+     * @return Velocity context
+     */
+    protected Context createVelocityContext(Map<String, Object> model) {
+        Context toolContext = velocityToolManager.createContext();
+        return new VelocityContext(model, toolContext);
+    }
+
+    @Override
+    public void createTasks(
+            final AuditElements.EventCategoryType type,
+            final String category,
+            final String subcategory,
+            final String event,
+            final Result condition,
+            final Object before,
+            final Object output,
+            final Object... input) {
+
+        SubjectType subjectType = null;
+        Subject<?, ?, ?> subject = null;
+
+        if (before instanceof UserTO) {
+            subjectType = SubjectType.USER;
+            subject = userDAO.find(((UserTO) before).getKey());
+        } else if (output instanceof UserTO) {
+            subjectType = SubjectType.USER;
+            subject = userDAO.find(((UserTO) output).getKey());
+        } else if (before instanceof RoleTO) {
+            subjectType = SubjectType.ROLE;
+            subject = roleDAO.find(((RoleTO) before).getKey());
+        } else if (output instanceof RoleTO) {
+            subjectType = SubjectType.ROLE;
+            subject = roleDAO.find(((RoleTO) output).getKey());
+        }
+
+        LOG.debug("Search notification for [{}]{}", subjectType, subject);
+
+        for (Notification notification : notificationDAO.findAll()) {
+            LOG.debug("Notification available user about {}", 
notification.getUserAbout());
+            LOG.debug("Notification available role about {}", 
notification.getRoleAbout());
+            if (notification.isActive()) {
+
+                final Set<String> events = new 
HashSet<>(notification.getEvents());
+                
events.retainAll(Collections.<String>singleton(AuditLoggerName.buildEvent(
+                        type, category, subcategory, event, condition)));
+
+                if (events.isEmpty()) {
+                    LOG.debug("No events found about {}", subject);
+                } else if (subjectType == null || subject == null
+                        || notification.getUserAbout() == null || 
notification.getRoleAbout() == null
+                        || searchDAO.matches(subject,
+                                
SearchCondConverter.convert(notification.getUserAbout()), subjectType)
+                        || searchDAO.matches(subject,
+                                
SearchCondConverter.convert(notification.getRoleAbout()), subjectType)) {
+
+                    LOG.debug("Creating notification task for events {} about 
{}", events, subject);
+
+                    final Map<String, Object> model = new HashMap<>();
+                    model.put("type", type);
+                    model.put("category", category);
+                    model.put("subcategory", subcategory);
+                    model.put("event", event);
+                    model.put("condition", condition);
+                    model.put("before", before);
+                    model.put("output", output);
+                    model.put("input", input);
+
+                    if (subject instanceof User) {
+                        model.put("user", userDataBinder.getUserTO((User) 
subject));
+                    } else if (subject instanceof Role) {
+                        model.put("role", roleDataBinder.getRoleTO((Role) 
subject));
+                    }
+
+                    taskDAO.save(getNotificationTask(notification, subject, 
model));
+                }
+            } else {
+                LOG.debug("Notification {}, userAbout {}, roleAbout {} is 
deactivated, "
+                        + "notification task will not be created", 
notification.getKey(),
+                        notification.getUserAbout(), 
notification.getRoleAbout());
+            }
+        }
+    }
+
+    private String getRecipientEmail(
+            final IntMappingType recipientAttrType, final String 
recipientAttrName, final User user) {
+
+        String email = null;
+
+        switch (recipientAttrType) {
+            case Username:
+                email = user.getUsername();
+                break;
+
+            case UserPlainSchema:
+                UPlainAttr attr = user.getPlainAttr(recipientAttrName);
+                if (attr != null && !attr.getValuesAsStrings().isEmpty()) {
+                    email = attr.getValuesAsStrings().get(0);
+                }
+                break;
+
+            case UserVirtualSchema:
+                UVirAttr virAttr = user.getVirAttr(recipientAttrName);
+                if (virAttr != null && !virAttr.getValues().isEmpty()) {
+                    email = virAttr.getValues().get(0);
+                }
+                break;
+
+            case UserDerivedSchema:
+                UDerAttr derAttr = user.getDerAttr(recipientAttrName);
+                if (derAttr != null) {
+                    email = derAttr.getValue(user.getPlainAttrs());
+                }
+                break;
+
+            default:
+        }
+
+        return email;
+    }
+
+    @Override
+    public TaskExec storeExec(final TaskExec execution) {
+        NotificationTask task = taskDAO.find(execution.getTask().getKey());
+        task.addExec(execution);
+        task.setExecuted(true);
+        taskDAO.save(task);
+        // this flush call is needed to generate a value for the execution id
+        taskDAO.flush();
+        return execution;
+    }
+
+    @Override
+    public void setTaskExecuted(final Long taskId, final boolean executed) {
+        NotificationTask task = taskDAO.find(taskId);
+        task.setExecuted(executed);
+        taskDAO.save(task);
+    }
+
+    @Override
+    public long countExecutionsWithStatus(final Long taskId, final String 
status) {
+        NotificationTask task = taskDAO.find(taskId);
+        long count = 0;
+        for (TaskExec taskExec : task.getExecs()) {
+            if (status == null) {
+                if (taskExec.getStatus() == null) {
+                    count++;
+                }
+            } else if (status.equals(taskExec.getStatus())) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    protected Map<String, String> findAllSyncopeConfs() {
+        Map<String, String> syncopeConfMap = new HashMap<>();
+        for (PlainAttr attr : confDAO.get().getPlainAttrs()) {
+            syncopeConfMap.put(attr.getSchema().getKey(), 
attr.getValuesAsStrings().get(0));
+        }
+        return syncopeConfMap;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/SpringVelocityResourceLoader.java
----------------------------------------------------------------------
diff --git 
a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/SpringVelocityResourceLoader.java
 
b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/SpringVelocityResourceLoader.java
new file mode 100644
index 0000000..f1b072b
--- /dev/null
+++ 
b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/SpringVelocityResourceLoader.java
@@ -0,0 +1,84 @@
+/*
+ * 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.core.provisioning.java.notification;
+
+import java.io.IOException;
+import java.io.InputStream;
+import org.apache.commons.collections.ExtendedProperties;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.runtime.resource.Resource;
+import org.apache.velocity.runtime.resource.loader.ResourceLoader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Velocity ResourceLoader adapter that loads via a Spring ResourceLoader.
+ * Similar to <tt>org.springframework.ui.velocity.SpringResourceLoader</tt> 
but more integrated with
+ * {@link VelocityEngineFactoryBean}.
+ */
+public class SpringVelocityResourceLoader extends ResourceLoader {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(SpringVelocityResourceLoader.class);
+
+    public static final String NAME = "spring";
+
+    public static final String SPRING_RESOURCE_LOADER_CLASS = 
"spring.resource.loader.class";
+
+    public static final String SPRING_RESOURCE_LOADER_CACHE = 
"spring.resource.loader.cache";
+
+    public static final String SPRING_RESOURCE_LOADER = 
"spring.resource.loader";
+
+    private org.springframework.core.io.ResourceLoader resourceLoader;
+
+    @Override
+    public void init(ExtendedProperties configuration) {
+        this.resourceLoader =
+                (org.springframework.core.io.ResourceLoader) 
this.rsvc.getApplicationAttribute(SPRING_RESOURCE_LOADER);
+        if (this.resourceLoader == null) {
+            throw new IllegalArgumentException(
+                    "'" + SPRING_RESOURCE_LOADER + "' application attribute 
must be present for SpringResourceLoader");
+        }
+
+        LOG.info("SpringResourceLoader for Velocity: using resource loader [" 
+ this.resourceLoader + "]");
+    }
+
+    @Override
+    public InputStream getResourceStream(final String source) throws 
ResourceNotFoundException {
+        LOG.debug("Looking for Velocity resource with name [{}]", source);
+
+        org.springframework.core.io.Resource resource = 
this.resourceLoader.getResource(source);
+        try {
+            return resource.getInputStream();
+        } catch (IOException e) {
+            LOG.debug("Could not find Velocity resource: " + resource, e);
+        }
+        throw new ResourceNotFoundException("Could not find resource [" + 
source + "]");
+    }
+
+    @Override
+    public boolean isSourceModified(final Resource resource) {
+        return false;
+    }
+
+    @Override
+    public long getLastModified(final Resource resource) {
+        return 0;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/VelocityEngineFactoryBean.java
----------------------------------------------------------------------
diff --git 
a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/VelocityEngineFactoryBean.java
 
b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/VelocityEngineFactoryBean.java
new file mode 100644
index 0000000..fae1504
--- /dev/null
+++ 
b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/VelocityEngineFactoryBean.java
@@ -0,0 +1,105 @@
+/*
+ * 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.core.provisioning.java.notification;
+
+import java.io.IOException;
+
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.log.CommonsLogLogChute;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.ResourceLoader;
+
+/**
+ * Similar to Spring's equivalent 
(<tt>org.springframework.ui.velocity.VelocityEngineFactoryBean</tt>), does not
+ * implement {@link org.springframework.context.ResourceLoaderAware} thus 
allowing custom injection.
+ */
+public class VelocityEngineFactoryBean implements FactoryBean<VelocityEngine>, 
InitializingBean {
+
+    private ResourceLoader resourceLoader = new DefaultResourceLoader();
+
+    private boolean overrideLogging = true;
+
+    private VelocityEngine velocityEngine;
+
+    public ResourceLoader getResourceLoader() {
+        return resourceLoader;
+    }
+
+    public void setResourceLoader(final ResourceLoader resourceLoader) {
+        this.resourceLoader = resourceLoader;
+    }
+
+    public boolean isOverrideLogging() {
+        return overrideLogging;
+    }
+
+    /**
+     * Configure Velocity to use Commons Logging (true by default).
+     *
+     * @param overrideLogging whether default Velocity logging should be 
overriden or not.
+     */
+    public void setOverrideLogging(final boolean overrideLogging) {
+        this.overrideLogging = overrideLogging;
+    }
+
+    private void createVelocityEngine() throws IOException, VelocityException {
+        velocityEngine = new VelocityEngine();
+
+        velocityEngine.setProperty(
+                RuntimeConstants.RESOURCE_LOADER, 
SpringVelocityResourceLoader.NAME);
+        velocityEngine.setProperty(
+                SpringVelocityResourceLoader.SPRING_RESOURCE_LOADER_CLASS,
+                SpringVelocityResourceLoader.class.getName());
+        velocityEngine.setProperty(
+                SpringVelocityResourceLoader.SPRING_RESOURCE_LOADER_CACHE, 
"true");
+        velocityEngine.setApplicationAttribute(
+                SpringVelocityResourceLoader.SPRING_RESOURCE_LOADER, 
getResourceLoader());
+
+        if (this.overrideLogging) {
+            velocityEngine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, 
new CommonsLogLogChute());
+        }
+
+        velocityEngine.init();
+    }
+
+    @Override
+    public void afterPropertiesSet() throws IOException, VelocityException {
+        createVelocityEngine();
+    }
+
+    @Override
+    public VelocityEngine getObject() {
+        return this.velocityEngine;
+    }
+
+    @Override
+    public Class<? extends VelocityEngine> getObjectType() {
+        return VelocityEngine.class;
+    }
+
+    @Override
+    public boolean isSingleton() {
+        return true;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
----------------------------------------------------------------------
diff --git 
a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
 
b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
new file mode 100644
index 0000000..83e14fa
--- /dev/null
+++ 
b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/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.core.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.core.persistence.api.dao.RoleDAO;
+import org.apache.syncope.core.persistence.api.dao.TaskDAO;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.AttributableUtilFactory;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.Subject;
+import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.core.provisioning.api.Connector;
+import org.apache.syncope.core.provisioning.api.ConnectorFactory;
+import org.apache.syncope.core.provisioning.api.TimeoutException;
+import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
+import 
org.apache.syncope.core.provisioning.api.propagation.PropagationReporter;
+import 
org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
+import org.apache.syncope.core.misc.AuditManager;
+import org.apache.syncope.core.misc.spring.ApplicationContextProvider;
+import org.apache.syncope.core.misc.ConnObjectUtil;
+import org.apache.syncope.core.misc.ExceptionUtil;
+import 
org.apache.syncope.core.provisioning.api.notification.NotificationManager;
+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/d30c8526/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DBPasswordPropagationActions.java
----------------------------------------------------------------------
diff --git 
a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DBPasswordPropagationActions.java
 
b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DBPasswordPropagationActions.java
new file mode 100644
index 0000000..fd4b3dc
--- /dev/null
+++ 
b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/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.core.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.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.ConnInstance;
+import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import 
org.apache.syncope.core.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/d30c8526/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationActions.java
----------------------------------------------------------------------
diff --git 
a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationActions.java
 
b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationActions.java
new file mode 100644
index 0000000..379326d
--- /dev/null
+++ 
b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/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.core.provisioning.java.propagation;
+
+import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.core.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/d30c8526/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationReporter.java
----------------------------------------------------------------------
diff --git 
a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationReporter.java
 
b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationReporter.java
new file mode 100644
index 0000000..df655a4
--- /dev/null
+++ 
b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/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.core.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.core.persistence.api.entity.task.PropagationTask;
+import 
org.apache.syncope.core.provisioning.api.propagation.PropagationReporter;
+import org.apache.syncope.core.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/d30c8526/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPMembershipPropagationActions.java
----------------------------------------------------------------------
diff --git 
a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPMembershipPropagationActions.java
 
b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPMembershipPropagationActions.java
new file mode 100644
index 0000000..b3dfc69
--- /dev/null
+++ 
b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/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.core.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.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.role.Role;
+import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.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");
+        }
+    }
+}

Reply via email to