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;
+ }
+
+}