You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2015/01/12 17:31:47 UTC

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

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/data/TaskDataBinderImpl.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/data/TaskDataBinderImpl.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/data/TaskDataBinderImpl.java
new file mode 100644
index 0000000..9ebfbce
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/data/TaskDataBinderImpl.java
@@ -0,0 +1,343 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.server.provisioning.java.data;
+
+import org.apache.syncope.server.provisioning.api.data.TaskDataBinder;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.AbstractAttributableTO;
+import org.apache.syncope.common.lib.to.AbstractProvisioningTaskTO;
+import org.apache.syncope.common.lib.to.AbstractTaskTO;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.MembershipTO;
+import org.apache.syncope.common.lib.to.PropagationTaskTO;
+import org.apache.syncope.common.lib.to.PushTaskTO;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.to.SchedTaskTO;
+import org.apache.syncope.common.lib.to.SyncTaskTO;
+import org.apache.syncope.common.lib.to.TaskExecTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.MatchingRule;
+import org.apache.syncope.common.lib.types.TaskType;
+import org.apache.syncope.common.lib.types.UnmatchingRule;
+import org.apache.syncope.server.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.server.persistence.api.dao.NotFoundException;
+import org.apache.syncope.server.persistence.api.dao.TaskExecDAO;
+import org.apache.syncope.server.persistence.api.entity.ExternalResource;
+import org.apache.syncope.server.persistence.api.entity.task.NotificationTask;
+import org.apache.syncope.server.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.server.persistence.api.entity.task.ProvisioningTask;
+import org.apache.syncope.server.persistence.api.entity.task.PushTask;
+import org.apache.syncope.server.persistence.api.entity.task.SchedTask;
+import org.apache.syncope.server.persistence.api.entity.task.SyncTask;
+import org.apache.syncope.server.persistence.api.entity.task.Task;
+import org.apache.syncope.server.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.server.persistence.api.entity.task.TaskUtil;
+import org.apache.syncope.server.provisioning.api.job.JobNamer;
+import org.apache.syncope.server.misc.spring.BeanUtils;
+import org.apache.syncope.server.misc.jexl.JexlUtil;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.Trigger;
+import org.quartz.TriggerKey;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.quartz.SchedulerFactoryBean;
+import org.springframework.stereotype.Component;
+
+@Component
+public class TaskDataBinderImpl implements TaskDataBinder {
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(TaskDataBinder.class);
+
+    private static final String[] IGNORE_TASK_PROPERTIES = {
+        "executions", "resource", "matchingRule", "unmatchingRule" };
+
+    private static final String[] IGNORE_TASK_EXECUTION_PROPERTIES = { "key", "task" };
+
+    @Autowired
+    private ExternalResourceDAO resourceDAO;
+
+    @Autowired
+    private TaskExecDAO taskExecDAO;
+
+    @Autowired
+    private SchedulerFactoryBean scheduler;
+
+    private void checkJexl(final AbstractAttributableTO attributableTO, final SyncopeClientException sce) {
+        for (AttrTO attrTO : attributableTO.getPlainAttrs()) {
+            if (!attrTO.getValues().isEmpty() && !JexlUtil.isExpressionValid(attrTO.getValues().get(0))) {
+                sce.getElements().add("Invalid JEXL: " + attrTO.getValues().get(0));
+            }
+        }
+
+        for (AttrTO attrTO : attributableTO.getVirAttrs()) {
+            if (!attrTO.getValues().isEmpty() && !JexlUtil.isExpressionValid(attrTO.getValues().get(0))) {
+                sce.getElements().add("Invalid JEXL: " + attrTO.getValues().get(0));
+            }
+        }
+    }
+
+    private void fill(final ProvisioningTask task, final AbstractProvisioningTaskTO taskTO) {
+        SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidSyncTask);
+
+        if (task instanceof PushTask && taskTO instanceof PushTaskTO) {
+            final PushTask pushTask = (PushTask) task;
+            final PushTaskTO pushTaskTO = (PushTaskTO) taskTO;
+
+            pushTask.setUserFilter(pushTaskTO.getUserFilter());
+            pushTask.setRoleFilter(pushTaskTO.getRoleFilter());
+
+            pushTask.setMatchingRule(pushTaskTO.getMatchingRule() == null
+                    ? MatchingRule.LINK : pushTaskTO.getMatchingRule());
+
+            pushTask.setUnmatchingRule(pushTaskTO.getUnmatchingRule() == null
+                    ? UnmatchingRule.ASSIGN : pushTaskTO.getUnmatchingRule());
+
+        } else if (task instanceof SyncTask && taskTO instanceof SyncTaskTO) {
+            final SyncTask syncTask = (SyncTask) task;
+            final SyncTaskTO syncTaskTO = (SyncTaskTO) taskTO;
+
+            syncTask.setMatchingRule(syncTaskTO.getMatchingRule() == null
+                    ? MatchingRule.UPDATE : syncTaskTO.getMatchingRule());
+
+            syncTask.setUnmatchingRule(syncTaskTO.getUnmatchingRule() == null
+                    ? UnmatchingRule.PROVISION : syncTaskTO.getUnmatchingRule());
+
+            // 1. validate JEXL expressions in user and role templates
+            if (syncTaskTO.getUserTemplate() != null) {
+                UserTO template = syncTaskTO.getUserTemplate();
+
+                if (StringUtils.isNotBlank(template.getUsername())
+                        && !JexlUtil.isExpressionValid(template.getUsername())) {
+
+                    sce.getElements().add("Invalid JEXL: " + template.getUsername());
+                }
+                if (StringUtils.isNotBlank(template.getPassword())
+                        && !JexlUtil.isExpressionValid(template.getPassword())) {
+
+                    sce.getElements().add("Invalid JEXL: " + template.getPassword());
+                }
+
+                checkJexl(template, sce);
+
+                for (MembershipTO memb : template.getMemberships()) {
+                    checkJexl(memb, sce);
+                }
+            }
+            if (syncTaskTO.getRoleTemplate() != null) {
+                RoleTO template = syncTaskTO.getRoleTemplate();
+
+                if (StringUtils.isNotBlank(template.getName()) && !JexlUtil.isExpressionValid(template.getName())) {
+                    sce.getElements().add("Invalid JEXL: " + template.getName());
+                }
+
+                checkJexl(template, sce);
+            }
+            if (!sce.isEmpty()) {
+                throw sce;
+            }
+
+            // 2. all JEXL expressions are valid: accept user and role templates
+            syncTask.setUserTemplate(syncTaskTO.getUserTemplate());
+            syncTask.setRoleTemplate(syncTaskTO.getRoleTemplate());
+
+            syncTask.setFullReconciliation(syncTaskTO.isFullReconciliation());
+        }
+
+        // 3. fill the remaining fields
+        task.setPerformCreate(taskTO.isPerformCreate());
+        task.setPerformUpdate(taskTO.isPerformUpdate());
+        task.setPerformDelete(taskTO.isPerformDelete());
+        task.setSyncStatus(taskTO.isSyncStatus());
+        task.getActionsClassNames().clear();
+        task.getActionsClassNames().addAll(taskTO.getActionsClassNames());
+    }
+
+    @Override
+    public SchedTask createSchedTask(final SchedTaskTO taskTO, final TaskUtil taskUtil) {
+        final Class<? extends AbstractTaskTO> taskTOClass = taskUtil.taskTOClass();
+
+        if (taskTOClass == null || !taskTOClass.equals(taskTO.getClass())) {
+            throw new ClassCastException(
+                    String.format("taskUtil is type %s but task is not: %s", taskTOClass, taskTO.getClass()));
+        }
+
+        SchedTask task = taskUtil.newTask();
+        task.setCronExpression(taskTO.getCronExpression());
+        task.setName(taskTO.getName());
+        task.setDescription(taskTO.getDescription());
+
+        if (taskUtil.getType() == TaskType.SCHEDULED) {
+            task.setJobClassName(taskTO.getJobClassName());
+        } else if (taskTO instanceof AbstractProvisioningTaskTO) {
+            final AbstractProvisioningTaskTO provisioningTaskTO = (AbstractProvisioningTaskTO) taskTO;
+
+            ExternalResource resource = resourceDAO.find(provisioningTaskTO.getResource());
+            if (resource == null) {
+                throw new NotFoundException("Resource " + provisioningTaskTO.getResource());
+            }
+            ((ProvisioningTask) task).setResource(resource);
+
+            fill((ProvisioningTask) task, provisioningTaskTO);
+        }
+
+        return task;
+    }
+
+    @Override
+    public void updateSchedTask(final SchedTask task, final SchedTaskTO taskTO, final TaskUtil taskUtil) {
+        Class<? extends Task> taskClass = taskUtil.taskClass();
+        Class<? extends AbstractTaskTO> taskTOClass = taskUtil.taskTOClass();
+
+        if (taskClass == null || !taskClass.equals(task.getClass())) {
+            throw new ClassCastException(
+                    String.format("taskUtil is type %s but task is not: %s", taskClass, task.getClass()));
+        }
+
+        if (taskTOClass == null || !taskTOClass.equals(taskTO.getClass())) {
+            throw new ClassCastException(
+                    String.format("taskUtil is type %s but task is not: %s", taskTOClass, taskTO.getClass()));
+        }
+
+        task.setCronExpression(taskTO.getCronExpression());
+        if (StringUtils.isNotBlank(taskTO.getName())) {
+            task.setName(taskTO.getName());
+        }
+        if (StringUtils.isNotBlank(taskTO.getDescription())) {
+            task.setDescription(taskTO.getDescription());
+        }
+
+        if (task instanceof ProvisioningTask) {
+            fill((ProvisioningTask) task, (AbstractProvisioningTaskTO) taskTO);
+        }
+    }
+
+    @Override
+    public TaskExecTO getTaskExecTO(final TaskExec execution) {
+        TaskExecTO executionTO = new TaskExecTO();
+        BeanUtils.copyProperties(execution, executionTO, IGNORE_TASK_EXECUTION_PROPERTIES);
+
+        if (execution.getKey() != null) {
+            executionTO.setKey(execution.getKey());
+        }
+
+        if (execution.getTask() != null && execution.getTask().getKey() != null) {
+            executionTO.setTask(execution.getTask().getKey());
+        }
+
+        return executionTO;
+    }
+
+    private void setExecTime(final SchedTaskTO taskTO, final Task task) {
+        String triggerName = JobNamer.getTriggerName(JobNamer.getJobName(task));
+
+        Trigger trigger = null;
+        try {
+            trigger = scheduler.getScheduler().getTrigger(new TriggerKey(triggerName, Scheduler.DEFAULT_GROUP));
+        } catch (SchedulerException e) {
+            LOG.warn("While trying to get to " + triggerName, e);
+        }
+
+        if (trigger != null) {
+            taskTO.setLastExec(trigger.getPreviousFireTime());
+            taskTO.setNextExec(trigger.getNextFireTime());
+        }
+    }
+
+    @Override
+    public <T extends AbstractTaskTO> T getTaskTO(final Task task, final TaskUtil taskUtil) {
+        T taskTO = taskUtil.newTaskTO();
+        BeanUtils.copyProperties(task, taskTO, IGNORE_TASK_PROPERTIES);
+
+        TaskExec latestExec = taskExecDAO.findLatestStarted(task);
+        taskTO.setLatestExecStatus(latestExec == null ? "" : latestExec.getStatus());
+        taskTO.setStartDate(latestExec == null ? null : latestExec.getStartDate());
+        taskTO.setEndDate(latestExec == null ? null : latestExec.getEndDate());
+
+        for (TaskExec execution : task.getExecs()) {
+            taskTO.getExecutions().add(getTaskExecTO(execution));
+        }
+
+        switch (taskUtil.getType()) {
+            case PROPAGATION:
+                if (!(task instanceof PropagationTask)) {
+                    throw new ClassCastException("taskUtil is type Propagation but task is not PropagationTask: "
+                            + task.getClass().getName());
+                }
+                ((PropagationTaskTO) taskTO).setResource(((PropagationTask) task).getResource().getKey());
+                break;
+
+            case SCHEDULED:
+                if (!(task instanceof SchedTask)) {
+                    throw new ClassCastException("taskUtil is type Sched but task is not SchedTask: "
+                            + task.getClass().getName());
+                }
+                setExecTime((SchedTaskTO) taskTO, task);
+                ((SchedTaskTO) taskTO).setName(((SchedTask) task).getName());
+                ((SchedTaskTO) taskTO).setDescription(((SchedTask) task).getDescription());
+                break;
+
+            case SYNCHRONIZATION:
+                if (!(task instanceof SyncTask)) {
+                    throw new ClassCastException("taskUtil is type Sync but task is not SyncTask: "
+                            + task.getClass().getName());
+                }
+                setExecTime((SchedTaskTO) taskTO, task);
+                ((SyncTaskTO) taskTO).setName(((SyncTask) task).getName());
+                ((SyncTaskTO) taskTO).setDescription(((SyncTask) task).getDescription());
+                ((SyncTaskTO) taskTO).setResource(((SyncTask) task).getResource().getKey());
+                ((SyncTaskTO) taskTO).setMatchingRule(((SyncTask) task).getMatchingRule() == null
+                        ? MatchingRule.UPDATE : ((SyncTask) task).getMatchingRule());
+                ((SyncTaskTO) taskTO).setUnmatchingRule(((SyncTask) task).getUnmatchingRule() == null
+                        ? UnmatchingRule.PROVISION : ((SyncTask) task).getUnmatchingRule());
+                break;
+
+            case PUSH:
+                if (!(task instanceof PushTask)) {
+                    throw new ClassCastException("taskUtil is type Push but task is not PushTask: "
+                            + task.getClass().getName());
+                }
+                setExecTime((SchedTaskTO) taskTO, task);
+                ((PushTaskTO) taskTO).setName(((PushTask) task).getName());
+                ((PushTaskTO) taskTO).setDescription(((PushTask) task).getDescription());
+                ((PushTaskTO) taskTO).setResource(((PushTask) task).getResource().getKey());
+                ((PushTaskTO) taskTO).setMatchingRule(((PushTask) task).getMatchingRule() == null
+                        ? MatchingRule.LINK : ((PushTask) task).getMatchingRule());
+                ((PushTaskTO) taskTO).setUnmatchingRule(((PushTask) task).getUnmatchingRule() == null
+                        ? UnmatchingRule.ASSIGN : ((PushTask) task).getUnmatchingRule());
+                break;
+
+            case NOTIFICATION:
+                if (((NotificationTask) task).isExecuted() && StringUtils.isBlank(taskTO.getLatestExecStatus())) {
+                    taskTO.setLatestExecStatus("[EXECUTED]");
+                }
+                break;
+
+            default:
+        }
+
+        return taskTO;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/data/UserDataBinderImpl.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/data/UserDataBinderImpl.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/data/UserDataBinderImpl.java
new file mode 100644
index 0000000..4fdb0b8
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/data/UserDataBinderImpl.java
@@ -0,0 +1,407 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.server.provisioning.java.data;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+import javax.annotation.Resource;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.SyncopeClientCompositeException;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.mod.MembershipMod;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.MembershipTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.common.lib.types.CipherAlgorithm;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.IntMappingType;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.server.persistence.api.dao.ConfDAO;
+import org.apache.syncope.server.persistence.api.dao.SecurityQuestionDAO;
+import org.apache.syncope.server.persistence.api.entity.DerAttr;
+import org.apache.syncope.server.persistence.api.entity.ExternalResource;
+import org.apache.syncope.server.persistence.api.entity.MappingItem;
+import org.apache.syncope.server.persistence.api.entity.PlainAttr;
+import org.apache.syncope.server.persistence.api.entity.VirAttr;
+import org.apache.syncope.server.persistence.api.entity.membership.MDerAttr;
+import org.apache.syncope.server.persistence.api.entity.membership.MPlainAttr;
+import org.apache.syncope.server.persistence.api.entity.membership.MVirAttr;
+import org.apache.syncope.server.persistence.api.entity.membership.Membership;
+import org.apache.syncope.server.persistence.api.entity.role.Role;
+import org.apache.syncope.server.persistence.api.entity.user.SecurityQuestion;
+import org.apache.syncope.server.persistence.api.entity.user.User;
+import org.apache.syncope.common.lib.types.PropagationByResource;
+import org.apache.syncope.server.provisioning.api.data.UserDataBinder;
+import org.apache.syncope.server.misc.security.AuthContextUtil;
+import org.apache.syncope.server.misc.security.Encryptor;
+import org.apache.syncope.server.misc.spring.BeanUtils;
+import org.apache.syncope.server.misc.ConnObjectUtil;
+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 UserDataBinderImpl extends AbstractAttributableDataBinder implements UserDataBinder {
+
+    private static final String[] IGNORE_USER_PROPERTIES = {
+        "memberships", "plainAttrs", "derAttrs", "virAttrs", "resources", "securityQuestion", "securityAnswer"
+    };
+
+    @Autowired
+    private ConfDAO confDAO;
+
+    @Autowired
+    private ConnObjectUtil connObjectUtil;
+
+    @Autowired
+    private SecurityQuestionDAO securityQuestionDAO;
+
+    @Resource(name = "adminUser")
+    private String adminUser;
+
+    @Resource(name = "anonymousUser")
+    private String anonymousUser;
+
+    private final Encryptor encryptor = Encryptor.getInstance();
+
+    @Transactional(readOnly = true)
+    @Override
+    public UserTO getAuthenticatedUserTO() {
+        final UserTO authUserTO;
+
+        final String authUsername = AuthContextUtil.getAuthenticatedUsername();
+        if (anonymousUser.equals(authUsername)) {
+            authUserTO = new UserTO();
+            authUserTO.setKey(-2);
+            authUserTO.setUsername(anonymousUser);
+        } else if (adminUser.equals(authUsername)) {
+            authUserTO = new UserTO();
+            authUserTO.setKey(-1);
+            authUserTO.setUsername(adminUser);
+        } else {
+            User authUser = userDAO.find(authUsername);
+            authUserTO = getUserTO(authUser);
+        }
+
+        return authUserTO;
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public boolean verifyPassword(final String username, final String password) {
+        return verifyPassword(userDAO.authFetch(username), password);
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public boolean verifyPassword(final User user, final String password) {
+        return encryptor.verify(password, user.getCipherAlgorithm(), user.getPassword());
+    }
+
+    private void setPassword(final User user, final String password,
+            final SyncopeClientCompositeException scce) {
+
+        try {
+            final String algorithm = confDAO.find(
+                    "password.cipher.algorithm", CipherAlgorithm.AES.name()).getValues().get(0).getStringValue();
+            CipherAlgorithm predefined = CipherAlgorithm.valueOf(algorithm);
+            user.setPassword(password, predefined);
+        } catch (IllegalArgumentException e) {
+            final SyncopeClientException invalidCiperAlgorithm =
+                    SyncopeClientException.build(ClientExceptionType.NotFound);
+            invalidCiperAlgorithm.getElements().add(e.getMessage());
+            scce.addException(invalidCiperAlgorithm);
+
+            throw scce;
+        }
+    }
+
+    @Override
+    public void create(final User user, final UserTO userTO, final boolean storePassword) {
+        SyncopeClientCompositeException scce = SyncopeClientException.buildComposite();
+
+        // memberships
+        Role role;
+        for (MembershipTO membershipTO : userTO.getMemberships()) {
+            role = roleDAO.find(membershipTO.getRoleId());
+
+            if (role == null) {
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("Ignoring invalid role " + membershipTO.getRoleName());
+                }
+            } else {
+                Membership membership = null;
+                if (user.getKey() != null) {
+                    membership = user.getMembership(role.getKey()) == null
+                            ? membershipDAO.find(user, role)
+                            : user.getMembership(role.getKey());
+                }
+                if (membership == null) {
+                    membership = entityFactory.newEntity(Membership.class);
+                    membership.setRole(role);
+                    membership.setUser(user);
+
+                    user.addMembership(membership);
+                }
+
+                fill(membership, membershipTO, attrUtilFactory.getInstance(AttributableType.MEMBERSHIP), scce);
+            }
+        }
+
+        // attributes, derived attributes, virtual attributes and resources
+        fill(user, userTO, attrUtilFactory.getInstance(AttributableType.USER), scce);
+
+        // set password
+        if (StringUtils.isBlank(userTO.getPassword()) || !storePassword) {
+            LOG.debug("Password was not provided or not required to be stored");
+        } else {
+            setPassword(user, userTO.getPassword(), scce);
+        }
+
+        // set username
+        user.setUsername(userTO.getUsername());
+
+        // security question / answer
+        if (userTO.getSecurityQuestion() != null) {
+            SecurityQuestion securityQuestion = securityQuestionDAO.find(userTO.getSecurityQuestion());
+            if (securityQuestion != null) {
+                user.setSecurityQuestion(securityQuestion);
+            }
+        }
+        user.setSecurityAnswer(userTO.getSecurityAnswer());
+    }
+
+    /**
+     * Update user, given UserMod.
+     *
+     * @param toBeUpdated user to be updated
+     * @param userMod bean containing update request
+     * @return updated user + propagation by resource
+     * @see PropagationByResource
+     */
+    @Override
+    public PropagationByResource update(final User toBeUpdated, final UserMod userMod) {
+        // Re-merge any pending change from workflow tasks
+        User user = userDAO.save(toBeUpdated);
+
+        PropagationByResource propByRes = new PropagationByResource();
+
+        SyncopeClientCompositeException scce = SyncopeClientException.buildComposite();
+
+        Set<String> currentResources = user.getResourceNames();
+
+        // password
+        if (StringUtils.isNotBlank(userMod.getPassword())) {
+            setPassword(user, userMod.getPassword(), scce);
+            user.setChangePwdDate(new Date());
+            propByRes.addAll(ResourceOperation.UPDATE, currentResources);
+        }
+
+        // username
+        if (userMod.getUsername() != null && !userMod.getUsername().equals(user.getUsername())) {
+            String oldUsername = user.getUsername();
+
+            user.setUsername(userMod.getUsername());
+            propByRes.addAll(ResourceOperation.UPDATE, currentResources);
+
+            for (ExternalResource resource : user.getResources()) {
+                for (MappingItem mapItem : resource.getUmapping().getItems()) {
+                    if (mapItem.isAccountid() && mapItem.getIntMappingType() == IntMappingType.Username) {
+                        propByRes.addOldAccountId(resource.getKey(), oldUsername);
+                    }
+                }
+            }
+        }
+
+        // security question / answer:
+        // userMod.getSecurityQuestion() is null => remove user security question and answer
+        // userMod.getSecurityQuestion() == 0 => don't change anything
+        // userMod.getSecurityQuestion() > 0 => update user security question and answer
+        if (userMod.getSecurityQuestion() == null) {
+            user.setSecurityQuestion(null);
+            user.setSecurityAnswer(null);
+        } else if (userMod.getSecurityQuestion() > 0) {
+            SecurityQuestion securityQuestion = securityQuestionDAO.find(userMod.getSecurityQuestion());
+            if (securityQuestion != null) {
+                user.setSecurityQuestion(securityQuestion);
+                user.setSecurityAnswer(userMod.getSecurityAnswer());
+            }
+        }
+
+        // attributes, derived attributes, virtual attributes and resources
+        propByRes.merge(fill(user, userMod, attrUtilFactory.getInstance(AttributableType.USER), scce));
+
+        // store the role ids of membership required to be added
+        Set<Long> membershipToBeAddedRoleIds = new HashSet<>();
+        for (MembershipMod membToBeAdded : userMod.getMembershipsToAdd()) {
+            membershipToBeAddedRoleIds.add(membToBeAdded.getRole());
+        }
+
+        final Set<String> toBeDeprovisioned = new HashSet<>();
+        final Set<String> toBeProvisioned = new HashSet<>();
+
+        // memberships to be removed
+        for (Long membershipId : userMod.getMembershipsToRemove()) {
+            LOG.debug("Membership to be removed: {}", membershipId);
+
+            Membership membership = membershipDAO.find(membershipId);
+            if (membership == null) {
+                LOG.debug("Invalid membership id specified to be removed: {}", membershipId);
+            } else {
+                if (!membershipToBeAddedRoleIds.contains(membership.getRole().getKey())) {
+                    toBeDeprovisioned.addAll(membership.getRole().getResourceNames());
+                }
+
+                // In order to make the removeMembership() below to work,
+                // we need to be sure to take exactly the same membership
+                // of the user object currently in memory (which has potentially
+                // some modifications compared to the one stored in the DB
+                membership = user.getMembership(membership.getRole().getKey());
+                if (membershipToBeAddedRoleIds.contains(membership.getRole().getKey())) {
+                    Set<Long> attributeIds = new HashSet<>(membership.getPlainAttrs().size());
+                    for (PlainAttr attribute : membership.getPlainAttrs()) {
+                        attributeIds.add(attribute.getKey());
+                    }
+                    for (Long attributeId : attributeIds) {
+                        plainAttrDAO.delete(attributeId, MPlainAttr.class);
+                    }
+                    attributeIds.clear();
+
+                    // remove derived attributes
+                    for (DerAttr derAttr : membership.getDerAttrs()) {
+                        attributeIds.add(derAttr.getKey());
+                    }
+                    for (Long derAttrId : attributeIds) {
+                        derAttrDAO.delete(derAttrId, MDerAttr.class);
+                    }
+                    attributeIds.clear();
+
+                    // remove virtual attributes
+                    for (VirAttr virAttr : membership.getVirAttrs()) {
+                        attributeIds.add(virAttr.getKey());
+                    }
+                    for (Long virAttrId : attributeIds) {
+                        virAttrDAO.delete(virAttrId, MVirAttr.class);
+                    }
+                    attributeIds.clear();
+                } else {
+                    user.removeMembership(membership);
+
+                    membershipDAO.delete(membershipId);
+                }
+            }
+        }
+
+        // memberships to be added
+        for (MembershipMod membershipMod : userMod.getMembershipsToAdd()) {
+            LOG.debug("Membership to be added: role({})", membershipMod.getRole());
+
+            Role role = roleDAO.find(membershipMod.getRole());
+            if (role == null) {
+                LOG.debug("Ignoring invalid role {}", membershipMod.getRole());
+            } else {
+                Membership membership = user.getMembership(role.getKey());
+                if (membership == null) {
+                    membership = entityFactory.newEntity(Membership.class);
+                    membership.setRole(role);
+                    membership.setUser(user);
+
+                    user.addMembership(membership);
+
+                    toBeProvisioned.addAll(role.getResourceNames());
+                }
+
+                propByRes.merge(fill(membership, membershipMod,
+                        attrUtilFactory.getInstance(AttributableType.MEMBERSHIP), scce));
+            }
+        }
+
+        propByRes.addAll(ResourceOperation.DELETE, toBeDeprovisioned);
+        propByRes.addAll(ResourceOperation.UPDATE, toBeProvisioned);
+
+        /**
+         * In case of new memberships all the current resources have to be updated in order to propagate new role and
+         * membership attribute values.
+         */
+        if (!toBeDeprovisioned.isEmpty() || !toBeProvisioned.isEmpty()) {
+            currentResources.removeAll(toBeDeprovisioned);
+            propByRes.addAll(ResourceOperation.UPDATE, currentResources);
+        }
+
+        return propByRes;
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public UserTO getUserTO(final User user) {
+        UserTO userTO = new UserTO();
+
+        BeanUtils.copyProperties(user, userTO, IGNORE_USER_PROPERTIES);
+
+        if (user.getSecurityQuestion() != null) {
+            userTO.setSecurityQuestion(user.getSecurityQuestion().getKey());
+        }
+
+        connObjectUtil.retrieveVirAttrValues(user, attrUtilFactory.getInstance(AttributableType.USER));
+        fillTO(userTO, user.getPlainAttrs(), user.getDerAttrs(), user.getVirAttrs(), user.getResources());
+
+        MembershipTO membershipTO;
+        for (Membership membership : user.getMemberships()) {
+            membershipTO = new MembershipTO();
+
+            // set sys info
+            membershipTO.setCreator(membership.getCreator());
+            membershipTO.setCreationDate(membership.getCreationDate());
+            membershipTO.setLastModifier(membership.getLastModifier());
+            membershipTO.setLastChangeDate(membership.getLastChangeDate());
+
+            membershipTO.setKey(membership.getKey());
+            membershipTO.setRoleId(membership.getRole().getKey());
+            membershipTO.setRoleName(membership.getRole().getName());
+
+            // SYNCOPE-458 retrieve also membership virtual attributes
+            connObjectUtil.retrieveVirAttrValues(membership, attrUtilFactory.getInstance(AttributableType.MEMBERSHIP));
+
+            fillTO(membershipTO,
+                    membership.getPlainAttrs(), membership.getDerAttrs(), membership.getVirAttrs(),
+                    Collections.<ExternalResource>emptyList());
+
+            userTO.getMemberships().add(membershipTO);
+        }
+
+        return userTO;
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public UserTO getUserTO(final String username) {
+        return getUserTO(userDAO.authFetch(username));
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public UserTO getUserTO(final Long key) {
+        return getUserTO(userDAO.authFetch(key));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/job/AbstractTaskJob.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/job/AbstractTaskJob.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/job/AbstractTaskJob.java
new file mode 100644
index 0000000..22ce042
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/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.server.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.server.persistence.api.dao.TaskDAO;
+import org.apache.syncope.server.persistence.api.dao.TaskExecDAO;
+import org.apache.syncope.server.persistence.api.entity.EntityFactory;
+import org.apache.syncope.server.persistence.api.entity.task.Task;
+import org.apache.syncope.server.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.server.provisioning.api.job.TaskJob;
+import org.apache.syncope.server.misc.AuditManager;
+import org.apache.syncope.server.provisioning.java.notification.NotificationManager;
+import org.apache.syncope.server.misc.ExceptionUtil;
+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/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/job/AbstractTransactionalTaskJob.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/job/AbstractTransactionalTaskJob.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/job/AbstractTransactionalTaskJob.java
new file mode 100644
index 0000000..a8731ae
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/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.server.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/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/job/SampleJob.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/job/SampleJob.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/job/SampleJob.java
new file mode 100644
index 0000000..2093fc0
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/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.server.provisioning.java.job;
+
+import org.apache.syncope.server.persistence.api.entity.task.SchedTask;
+import org.apache.syncope.server.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/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/notification/NotificationManager.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/notification/NotificationManager.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/notification/NotificationManager.java
new file mode 100644
index 0000000..59379b9
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/notification/NotificationManager.java
@@ -0,0 +1,437 @@
+/*
+ * 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.notification;
+
+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.server.persistence.api.RoleEntitlementUtil;
+import org.apache.syncope.server.persistence.api.dao.ConfDAO;
+import org.apache.syncope.server.persistence.api.dao.EntitlementDAO;
+import org.apache.syncope.server.persistence.api.dao.NotificationDAO;
+import org.apache.syncope.server.persistence.api.dao.RoleDAO;
+import org.apache.syncope.server.persistence.api.dao.SubjectSearchDAO;
+import org.apache.syncope.server.persistence.api.dao.TaskDAO;
+import org.apache.syncope.server.persistence.api.dao.UserDAO;
+import org.apache.syncope.server.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.server.persistence.api.entity.Attributable;
+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.Notification;
+import org.apache.syncope.server.persistence.api.entity.PlainAttr;
+import org.apache.syncope.server.persistence.api.entity.Subject;
+import org.apache.syncope.server.persistence.api.entity.role.Role;
+import org.apache.syncope.server.persistence.api.entity.task.NotificationTask;
+import org.apache.syncope.server.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.server.persistence.api.entity.user.UDerAttr;
+import org.apache.syncope.server.persistence.api.entity.user.UPlainAttr;
+import org.apache.syncope.server.persistence.api.entity.user.UVirAttr;
+import org.apache.syncope.server.persistence.api.entity.user.User;
+import org.apache.syncope.server.provisioning.api.data.RoleDataBinder;
+import org.apache.syncope.server.provisioning.api.data.UserDataBinder;
+import org.apache.syncope.server.misc.ConnObjectUtil;
+import org.apache.syncope.server.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;
+
+/**
+ * Create notification tasks that will be executed by NotificationJob.
+ *
+ * @see NotificationTask
+ */
+@Component
+@Transactional(rollbackFor = { Throwable.class })
+public class 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)
+    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);
+    }
+
+    /**
+     * Create notification tasks for each notification matching the given user id and (some of) tasks performed.
+     */
+    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 UserSchema:
+                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;
+    }
+
+    /**
+     * Store execution of a NotificationTask.
+     *
+     * @param execution task execution.
+     * @return merged task execution.
+     */
+    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;
+    }
+
+    /**
+     * Set execution state of NotificationTask with provided id.
+     *
+     * @param taskId task to be updated
+     * @param executed execution state
+     */
+    public void setTaskExecuted(final Long taskId, final boolean executed) {
+        NotificationTask task = taskDAO.find(taskId);
+        task.setExecuted(executed);
+        taskDAO.save(task);
+    }
+
+    /**
+     * Count the number of task executions of a given task with a given status.
+     *
+     * @param taskId task id
+     * @param status status
+     * @return number of task executions
+     */
+    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/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/notification/SpringVelocityResourceLoader.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/notification/SpringVelocityResourceLoader.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/notification/SpringVelocityResourceLoader.java
new file mode 100644
index 0000000..e876845
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/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.server.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/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/notification/VelocityEngineFactoryBean.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/notification/VelocityEngineFactoryBean.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/notification/VelocityEngineFactoryBean.java
new file mode 100644
index 0000000..8110a4d
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/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.server.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;
+    }
+
+}