You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by fm...@apache.org on 2015/10/23 12:29:05 UTC

[04/54] [abbrv] syncope git commit: [SYNCOPE-710] Merge from 1_2_X

http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/ActivitiUserWorkflowAdapter.java
----------------------------------------------------------------------
diff --cc core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/ActivitiUserWorkflowAdapter.java
index 6b4f838,0000000..48092ee
mode 100644,000000..100644
--- a/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/ActivitiUserWorkflowAdapter.java
+++ b/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/ActivitiUserWorkflowAdapter.java
@@@ -1,853 -1,0 +1,866 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one
 + * or more contributor license agreements.  See the NOTICE file
 + * distributed with this work for additional information
 + * regarding copyright ownership.  The ASF licenses this file
 + * to you under the Apache License, Version 2.0 (the
 + * "License"); you may not use this file except in compliance
 + * with the License.  You may obtain a copy of the License at
 + *
 + *   http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing,
 + * software distributed under the License is distributed on an
 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + * KIND, either express or implied.  See the License for the
 + * specific language governing permissions and limitations
 + * under the License.
 + */
 +package org.apache.syncope.core.workflow.activiti;
 +
 +import com.fasterxml.jackson.databind.JsonNode;
 +import com.fasterxml.jackson.databind.ObjectMapper;
 +import com.fasterxml.jackson.databind.node.ObjectNode;
 +import java.io.IOException;
 +import java.io.InputStream;
 +import java.io.OutputStream;
 +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 javax.annotation.Resource;
 +import javax.ws.rs.NotFoundException;
 +import org.activiti.bpmn.converter.BpmnXMLConverter;
 +import org.activiti.bpmn.model.BpmnModel;
 +import org.activiti.editor.constants.ModelDataJsonConstants;
 +import org.activiti.editor.language.json.converter.BpmnJsonConverter;
 +import org.activiti.engine.ActivitiException;
 +import org.activiti.engine.ProcessEngine;
 +import org.activiti.engine.form.FormProperty;
 +import org.activiti.engine.form.FormType;
 +import org.activiti.engine.form.TaskFormData;
 +import org.activiti.engine.history.HistoricActivityInstance;
 +import org.activiti.engine.history.HistoricDetail;
 +import org.activiti.engine.history.HistoricTaskInstance;
 +import org.activiti.engine.impl.persistence.entity.HistoricFormPropertyEntity;
 +import org.activiti.engine.query.Query;
 +import org.activiti.engine.repository.Model;
 +import org.activiti.engine.repository.ProcessDefinition;
 +import org.activiti.engine.runtime.ProcessInstance;
 +import org.activiti.engine.task.Task;
 +import org.apache.commons.io.IOUtils;
 +import org.apache.commons.lang3.StringUtils;
 +import org.apache.commons.lang3.tuple.ImmutablePair;
 +import org.apache.commons.lang3.tuple.Pair;
 +import org.apache.syncope.common.lib.SyncopeClientException;
 +import org.apache.syncope.common.lib.patch.PasswordPatch;
 +import org.apache.syncope.common.lib.patch.UserPatch;
 +import org.apache.syncope.common.lib.to.UserTO;
 +import org.apache.syncope.common.lib.to.WorkflowFormPropertyTO;
 +import org.apache.syncope.common.lib.to.WorkflowFormTO;
 +import org.apache.syncope.common.lib.types.PropagationByResource;
 +import org.apache.syncope.common.lib.types.ResourceOperation;
 +import org.apache.syncope.common.lib.types.WorkflowFormPropertyType;
 +import org.apache.syncope.core.misc.security.AuthContextUtils;
 +import org.apache.syncope.core.misc.spring.BeanUtils;
 +import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException;
 +import org.apache.syncope.core.persistence.api.attrvalue.validation.ParsingValidationException;
 +import org.apache.syncope.core.persistence.api.entity.user.User;
 +import org.apache.syncope.core.provisioning.api.WorkflowResult;
 +import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
 +import org.apache.syncope.core.workflow.api.WorkflowDefinitionFormat;
 +import org.apache.syncope.core.workflow.api.WorkflowException;
 +import org.apache.syncope.core.workflow.java.AbstractUserWorkflowAdapter;
 +import org.springframework.beans.factory.annotation.Autowired;
 +import org.springframework.transaction.annotation.Transactional;
 +
 +/**
 + * Activiti {@link http://www.activiti.org/} based implementation.
 + */
 +public class ActivitiUserWorkflowAdapter extends AbstractUserWorkflowAdapter {
 +
 +    protected static final String[] PROPERTY_IGNORE_PROPS = { "type" };
 +
 +    public static final String WF_PROCESS_ID = "userWorkflow";
 +
 +    public static final String WF_PROCESS_RESOURCE = "userWorkflow.bpmn20.xml";
 +
 +    public static final String WF_DGRM_RESOURCE = "userWorkflow.userWorkflow.png";
 +
 +    public static final String USER = "user";
 +
 +    public static final String WF_EXECUTOR = "wfExecutor";
 +
 +    public static final String FORM_SUBMITTER = "formSubmitter";
 +
 +    public static final String USER_TO = "userTO";
 +
 +    public static final String ENABLED = "enabled";
 +
 +    public static final String USER_PATCH = "userPatch";
 +
 +    public static final String EMAIL_KIND = "emailKind";
 +
 +    public static final String TASK = "task";
 +
 +    public static final String TOKEN = "token";
 +
 +    public static final String PASSWORD = "password";
 +
 +    public static final String PROP_BY_RESOURCE = "propByResource";
 +
 +    public static final String PROPAGATE_ENABLE = "propagateEnable";
 +
 +    public static final String ENCRYPTED_PWD = "encryptedPwd";
 +
 +    public static final String TASK_IS_FORM = "taskIsForm";
 +
 +    public static final String MODEL_DATA_JSON_MODEL = "model";
 +
 +    public static final String STORE_PASSWORD = "storePassword";
 +
 +    public static final String EVENT = "event";
 +
 +    @Resource(name = "adminUser")
 +    protected String adminUser;
 +
 +    @Autowired
 +    protected ProcessEngine engine;
 +
 +    @Autowired
 +    protected UserDataBinder userDataBinder;
 +
 +    @Override
 +    public String getPrefix() {
 +        return "ACT_";
 +    }
 +
 +    protected void throwException(final ActivitiException e, final String defaultMessage) {
 +        if (e.getCause() != null) {
 +            if (e.getCause().getCause() instanceof SyncopeClientException) {
 +                throw (SyncopeClientException) e.getCause().getCause();
 +            } else if (e.getCause().getCause() instanceof ParsingValidationException) {
 +                throw (ParsingValidationException) e.getCause().getCause();
 +            } else if (e.getCause().getCause() instanceof InvalidEntityException) {
 +                throw (InvalidEntityException) e.getCause().getCause();
 +            }
 +        }
 +
 +        throw new WorkflowException(defaultMessage, e);
 +    }
 +
 +    protected void updateStatus(final User user) {
 +        List<Task> tasks = engine.getTaskService().createTaskQuery().processInstanceId(user.getWorkflowId()).list();
 +        if (tasks.isEmpty() || tasks.size() > 1) {
 +            LOG.warn("While setting user status: unexpected task number ({})", tasks.size());
 +        } else {
 +            user.setStatus(tasks.get(0).getTaskDefinitionKey());
 +        }
 +    }
 +
 +    protected String getFormTask(final User user) {
 +        String result = null;
 +
 +        List<Task> tasks = engine.getTaskService().createTaskQuery().processInstanceId(user.getWorkflowId()).list();
 +        if (tasks.isEmpty() || tasks.size() > 1) {
 +            LOG.warn("While checking if form task: unexpected task number ({})", tasks.size());
 +        } else {
 +            try {
 +                TaskFormData formData = engine.getFormService().getTaskFormData(tasks.get(0).getId());
 +                if (formData != null && !formData.getFormProperties().isEmpty()) {
 +                    result = tasks.get(0).getId();
 +                }
 +            } catch (ActivitiException e) {
 +                LOG.warn("Could not get task form data", e);
 +            }
 +        }
 +
 +        return result;
 +    }
 +
 +    protected Set<String> getPerformedTasks(final User user) {
 +        final Set<String> result = new HashSet<>();
 +
 +        for (HistoricActivityInstance task : engine.getHistoryService().createHistoricActivityInstanceQuery().
 +                executionId(user.getWorkflowId()).list()) {
 +
 +            result.add(task.getActivityId());
 +        }
 +
 +        return result;
 +    }
 +
 +    /**
 +     * Saves resources to be propagated and password for later - after form submission - propagation.
 +     */
 +    protected void saveForFormSubmit(final User user, final String password, final PropagationByResource propByRes) {
 +        String formTaskId = getFormTask(user);
 +        if (formTaskId != null) {
 +            // SYNCOPE-238: This is needed to simplify the task query in this.getForms()
 +            engine.getTaskService().setVariableLocal(formTaskId, TASK_IS_FORM, Boolean.TRUE);
 +            engine.getRuntimeService().setVariable(user.getWorkflowId(), PROP_BY_RESOURCE, propByRes);
 +            if (propByRes != null) {
 +                propByRes.clear();
 +            }
 +
 +            if (StringUtils.isNotBlank(password)) {
 +                engine.getRuntimeService().setVariable(user.getWorkflowId(), ENCRYPTED_PWD, encrypt(password));
 +            }
 +        }
 +    }
 +
 +    @Override
 +    public WorkflowResult<Pair<Long, Boolean>> create(final UserTO userTO, final boolean disablePwdPolicyCheck,
 +            final boolean storePassword) {
 +
 +        return create(userTO, disablePwdPolicyCheck, null, storePassword);
 +    }
 +
 +    @Override
 +    public WorkflowResult<Pair<Long, Boolean>> create(final UserTO userTO, final boolean storePassword) {
 +        return create(userTO, false, storePassword);
 +    }
 +
 +    @Override
 +    public WorkflowResult<Pair<Long, Boolean>> create(final UserTO userTO, final boolean disablePwdPolicyCheck,
 +            final Boolean enabled, final boolean storePassword) {
 +
 +        Map<String, Object> variables = new HashMap<>();
 +        variables.put(WF_EXECUTOR, AuthContextUtils.getUsername());
 +        variables.put(USER_TO, userTO);
 +        variables.put(ENABLED, enabled);
 +        variables.put(STORE_PASSWORD, storePassword);
 +
 +        ProcessInstance processInstance = null;
 +        try {
 +            processInstance = engine.getRuntimeService().startProcessInstanceByKey(WF_PROCESS_ID, variables);
 +        } catch (ActivitiException e) {
 +            throwException(e, "While starting " + WF_PROCESS_ID + " instance");
 +        }
 +
 +        User user = engine.getRuntimeService().getVariable(processInstance.getProcessInstanceId(), USER, User.class);
 +
 +        Boolean updatedEnabled =
 +                engine.getRuntimeService().getVariable(processInstance.getProcessInstanceId(), ENABLED, Boolean.class);
 +        if (updatedEnabled != null) {
 +            user.setSuspended(!updatedEnabled);
 +        }
 +
 +        // this will make UserValidator not to consider password policies at all
 +        if (disablePwdPolicyCheck) {
 +            user.removeClearPassword();
 +        }
 +
 +        updateStatus(user);
 +        user = userDAO.save(user);
 +
 +        Boolean propagateEnable = engine.getRuntimeService().getVariable(
 +                processInstance.getProcessInstanceId(), PROPAGATE_ENABLE, Boolean.class);
 +        if (propagateEnable == null) {
 +            propagateEnable = enabled;
 +        }
 +
 +        PropagationByResource propByRes = new PropagationByResource();
 +        propByRes.set(ResourceOperation.CREATE, userDAO.findAllResourceNames(user));
 +
 +        saveForFormSubmit(user, userTO.getPassword(), propByRes);
 +
 +        return new WorkflowResult<Pair<Long, Boolean>>(
 +                new ImmutablePair<>(user.getKey(), propagateEnable), propByRes, getPerformedTasks(user));
 +    }
 +
 +    protected Set<String> doExecuteTask(final User user, final String task, final Map<String, Object> moreVariables) {
 +        Set<String> preTasks = getPerformedTasks(user);
 +
 +        Map<String, Object> variables = new HashMap<>();
 +        variables.put(WF_EXECUTOR, AuthContextUtils.getUsername());
 +        variables.put(TASK, task);
 +
 +        // using BeanUtils to access all user's properties and trigger lazy loading - we are about to
 +        // serialize a User instance for availability within workflow tasks, and this breaks transactions
 +        BeanUtils.copyProperties(user, entityFactory.newEntity(User.class));
 +        variables.put(USER, user);
 +
 +        if (moreVariables != null && !moreVariables.isEmpty()) {
 +            variables.putAll(moreVariables);
 +        }
 +
 +        if (StringUtils.isBlank(user.getWorkflowId())) {
 +            throw new WorkflowException(new NotFoundException("Empty workflow id for " + user));
 +        }
 +
 +        List<Task> tasks = engine.getTaskService().createTaskQuery().processInstanceId(user.getWorkflowId()).list();
 +        if (tasks.size() == 1) {
 +            try {
 +                engine.getTaskService().complete(tasks.get(0).getId(), variables);
 +            } catch (ActivitiException e) {
 +                throwException(e, "While completing task '" + tasks.get(0).getName() + "' for " + user);
 +            }
 +        } else {
 +            LOG.warn("Expected a single task, found {}", tasks.size());
 +        }
 +
 +        Set<String> postTasks = getPerformedTasks(user);
 +        postTasks.removeAll(preTasks);
 +        postTasks.add(task);
 +        return postTasks;
 +    }
 +
 +    @Override
 +    protected WorkflowResult<Long> doActivate(final User user, final String token) {
 +        Set<String> tasks = doExecuteTask(user, "activate", Collections.singletonMap(TOKEN, (Object) token));
 +
 +        updateStatus(user);
 +        User updated = userDAO.save(user);
 +
 +        return new WorkflowResult<>(updated.getKey(), null, tasks);
 +    }
 +
 +    @Override
 +    protected WorkflowResult<Pair<UserPatch, Boolean>> doUpdate(final User user, final UserPatch userPatch) {
 +        Set<String> tasks = doExecuteTask(user, "update", Collections.singletonMap(USER_PATCH, (Object) userPatch));
 +
 +        updateStatus(user);
 +        User updated = userDAO.save(user);
 +
 +        PropagationByResource propByRes = engine.getRuntimeService().getVariable(
 +                user.getWorkflowId(), PROP_BY_RESOURCE, PropagationByResource.class);
 +        UserPatch updatedPatch = engine.getRuntimeService().getVariable(
 +                user.getWorkflowId(), USER_PATCH, UserPatch.class);
 +
 +        saveForFormSubmit(
 +                updated, updatedPatch.getPassword() == null ? null : updatedPatch.getPassword().getValue(), propByRes);
 +
 +        Boolean propagateEnable = engine.getRuntimeService().getVariable(
 +                user.getWorkflowId(), PROPAGATE_ENABLE, Boolean.class);
 +
 +        return new WorkflowResult<Pair<UserPatch, Boolean>>(
 +                new ImmutablePair<>(updatedPatch, propagateEnable), propByRes, tasks);
 +    }
 +
 +    @Override
 +    protected WorkflowResult<Long> doSuspend(final User user) {
 +        Set<String> performedTasks = doExecuteTask(user, "suspend", null);
 +        updateStatus(user);
 +        User updated = userDAO.save(user);
 +
 +        return new WorkflowResult<>(updated.getKey(), null, performedTasks);
 +    }
 +
 +    @Override
 +    protected WorkflowResult<Long> doReactivate(final User user) {
 +        Set<String> performedTasks = doExecuteTask(user, "reactivate", null);
 +        updateStatus(user);
 +
 +        User updated = userDAO.save(user);
 +
 +        return new WorkflowResult<>(updated.getKey(), null, performedTasks);
 +    }
 +
 +    @Override
 +    protected void doRequestPasswordReset(final User user) {
 +        Map<String, Object> variables = new HashMap<>(2);
 +        variables.put(USER_TO, userDataBinder.getUserTO(user, true));
 +        variables.put(EVENT, "requestPasswordReset");
 +
 +        doExecuteTask(user, "requestPasswordReset", variables);
 +        userDAO.save(user);
 +    }
 +
 +    @Override
-     protected void doConfirmPasswordReset(final User user, final String token, final String password) {
++    protected WorkflowResult<Pair<UserPatch, Boolean>> doConfirmPasswordReset(
++            final User user, final String token, final String password) {
++
 +        Map<String, Object> variables = new HashMap<>(4);
 +        variables.put(TOKEN, token);
 +        variables.put(PASSWORD, password);
 +        variables.put(USER_TO, userDataBinder.getUserTO(user, true));
 +        variables.put(EVENT, "confirmPasswordReset");
 +
-         doExecuteTask(user, "confirmPasswordReset", variables);
++        Set<String> tasks = doExecuteTask(user, "confirmPasswordReset", variables);
++
 +        userDAO.save(user);
++
++        PropagationByResource propByRes = engine.getRuntimeService().getVariable(
++                user.getWorkflowId(), PROP_BY_RESOURCE, PropagationByResource.class);
++        UserPatch updatedPatch = engine.getRuntimeService().getVariable(
++                user.getWorkflowId(), USER_PATCH, UserPatch.class);
++        Boolean propagateEnable = engine.getRuntimeService().getVariable(
++                user.getWorkflowId(), PROPAGATE_ENABLE, Boolean.class);
++
++        return new WorkflowResult<Pair<UserPatch, Boolean>>(
++                new ImmutablePair<>(updatedPatch, propagateEnable), propByRes, tasks);
 +    }
 +
 +    @Override
 +    protected void doDelete(final User user) {
 +        doExecuteTask(user, "delete", null);
 +
 +        PropagationByResource propByRes = new PropagationByResource();
 +        propByRes.set(ResourceOperation.DELETE, userDAO.findAllResourceNames(user));
 +
 +        saveForFormSubmit(user, null, propByRes);
 +
 +        if (engine.getRuntimeService().createProcessInstanceQuery().
 +                processInstanceId(user.getWorkflowId()).active().list().isEmpty()) {
 +
 +            userDAO.delete(user.getKey());
 +
 +            if (!engine.getHistoryService().createHistoricProcessInstanceQuery().
 +                    processInstanceId(user.getWorkflowId()).list().isEmpty()) {
 +
 +                engine.getHistoryService().deleteHistoricProcessInstance(user.getWorkflowId());
 +            }
 +        } else {
 +            updateStatus(user);
 +            userDAO.save(user);
 +        }
 +    }
 +
 +    @Override
 +    public WorkflowResult<Long> execute(final UserTO userTO, final String taskId) {
 +        User user = userDAO.authFind(userTO.getKey());
 +
 +        final Map<String, Object> variables = new HashMap<>();
 +        variables.put(USER_TO, userTO);
 +
 +        Set<String> performedTasks = doExecuteTask(user, taskId, variables);
 +        updateStatus(user);
 +        User updated = userDAO.save(user);
 +
 +        return new WorkflowResult<>(updated.getKey(), null, performedTasks);
 +    }
 +
 +    protected ProcessDefinition getProcessDefinition() {
 +        try {
 +            return engine.getRepositoryService().createProcessDefinitionQuery().processDefinitionKey(
 +                    ActivitiUserWorkflowAdapter.WF_PROCESS_ID).latestVersion().singleResult();
 +        } catch (ActivitiException e) {
 +            throw new WorkflowException("While accessing process " + ActivitiUserWorkflowAdapter.WF_PROCESS_ID, e);
 +        }
 +
 +    }
 +
 +    protected Model getModel(final ProcessDefinition procDef) {
 +        try {
 +            Model model = engine.getRepositoryService().createModelQuery().
 +                    deploymentId(procDef.getDeploymentId()).singleResult();
 +            if (model == null) {
 +                throw new NotFoundException("Could not find Model for deployment " + procDef.getDeploymentId());
 +            }
 +            return model;
 +        } catch (Exception e) {
 +            throw new WorkflowException("While accessing process " + ActivitiUserWorkflowAdapter.WF_PROCESS_ID, e);
 +        }
 +    }
 +
 +    protected void exportProcessResource(final String resourceName, final OutputStream os) {
 +        ProcessDefinition procDef = getProcessDefinition();
 +
 +        InputStream procDefIS = engine.getRepositoryService().getResourceAsStream(procDef.getDeploymentId(),
 +                resourceName);
 +        try {
 +            IOUtils.copy(procDefIS, os);
 +        } catch (IOException e) {
 +            LOG.error("While exporting workflow definition {}", procDef.getKey(), e);
 +        } finally {
 +            IOUtils.closeQuietly(procDefIS);
 +        }
 +    }
 +
 +    protected void exportProcessModel(final OutputStream os) {
 +        Model model = getModel(getProcessDefinition());
 +
 +        ObjectMapper objectMapper = new ObjectMapper();
 +        try {
 +            ObjectNode modelNode = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
 +            modelNode.put(ModelDataJsonConstants.MODEL_ID, model.getId());
 +            modelNode.replace(MODEL_DATA_JSON_MODEL,
 +                    objectMapper.readTree(engine.getRepositoryService().getModelEditorSource(model.getId())));
 +
 +            os.write(modelNode.toString().getBytes());
 +        } catch (IOException e) {
 +            LOG.error("While exporting workflow definition {}", model.getId(), e);
 +        }
 +    }
 +
 +    @Override
 +    public void exportDefinition(final WorkflowDefinitionFormat format, final OutputStream os) {
 +        switch (format) {
 +            case JSON:
 +                exportProcessModel(os);
 +                break;
 +
 +            case XML:
 +            default:
 +                exportProcessResource(WF_PROCESS_RESOURCE, os);
 +        }
 +    }
 +
 +    @Override
 +    public void exportDiagram(final OutputStream os) {
 +        exportProcessResource(WF_DGRM_RESOURCE, os);
 +    }
 +
 +    @Override
 +    public void importDefinition(final WorkflowDefinitionFormat format, final String definition) {
 +        Model model = getModel(getProcessDefinition());
 +        switch (format) {
 +            case JSON:
 +                JsonNode definitionNode;
 +                try {
 +                    definitionNode = new ObjectMapper().readTree(definition);
 +                    if (definitionNode.has(MODEL_DATA_JSON_MODEL)) {
 +                        definitionNode = definitionNode.get(MODEL_DATA_JSON_MODEL);
 +                    }
 +                    if (!definitionNode.has(BpmnJsonConverter.EDITOR_CHILD_SHAPES)) {
 +                        throw new IllegalArgumentException(
 +                                "Could not find JSON node " + BpmnJsonConverter.EDITOR_CHILD_SHAPES);
 +                    }
 +
 +                    BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(definitionNode);
 +                    ActivitiImportUtils.fromXML(engine, new BpmnXMLConverter().convertToXML(bpmnModel));
 +                } catch (Exception e) {
 +                    throw new WorkflowException("While updating process "
 +                            + ActivitiUserWorkflowAdapter.WF_PROCESS_RESOURCE, e);
 +                }
 +
 +                ActivitiImportUtils.fromJSON(
 +                        engine, definitionNode.toString().getBytes(), getProcessDefinition(), model);
 +                break;
 +
 +            case XML:
 +            default:
 +                ActivitiImportUtils.fromXML(engine, definition.getBytes());
 +
 +                ActivitiImportUtils.fromJSON(engine, getProcessDefinition(), model);
 +        }
 +    }
 +
 +    protected WorkflowFormPropertyType fromActivitiFormType(final FormType activitiFormType) {
 +        WorkflowFormPropertyType result = WorkflowFormPropertyType.String;
 +
 +        if ("string".equals(activitiFormType.getName())) {
 +            result = WorkflowFormPropertyType.String;
 +        }
 +        if ("long".equals(activitiFormType.getName())) {
 +            result = WorkflowFormPropertyType.Long;
 +        }
 +        if ("enum".equals(activitiFormType.getName())) {
 +            result = WorkflowFormPropertyType.Enum;
 +        }
 +        if ("date".equals(activitiFormType.getName())) {
 +            result = WorkflowFormPropertyType.Date;
 +        }
 +        if ("boolean".equals(activitiFormType.getName())) {
 +            result = WorkflowFormPropertyType.Boolean;
 +        }
 +
 +        return result;
 +    }
 +
 +    protected WorkflowFormTO getFormTO(final Task task) {
 +        return getFormTO(task, engine.getFormService().getTaskFormData(task.getId()));
 +    }
 +
 +    protected WorkflowFormTO getFormTO(final Task task, final TaskFormData fd) {
 +        final WorkflowFormTO formTO =
 +                getFormTO(task.getProcessInstanceId(), task.getId(), fd.getFormKey(), fd.getFormProperties());
 +
 +        BeanUtils.copyProperties(task, formTO);
 +        return formTO;
 +    }
 +
 +    protected WorkflowFormTO getFormTO(final HistoricTaskInstance task) {
 +        final List<HistoricFormPropertyEntity> props = new ArrayList<>();
 +
 +        for (HistoricDetail historicDetail
 +                : engine.getHistoryService().createHistoricDetailQuery().taskId(task.getId()).list()) {
 +
 +            if (historicDetail instanceof HistoricFormPropertyEntity) {
 +                props.add((HistoricFormPropertyEntity) historicDetail);
 +            }
 +        }
 +
 +        final WorkflowFormTO formTO = getHistoricFormTO(
 +                task.getProcessInstanceId(), task.getId(), task.getFormKey(), props);
 +        BeanUtils.copyProperties(task, formTO);
 +
 +        final HistoricActivityInstance historicActivityInstance = engine.getHistoryService().
 +                createHistoricActivityInstanceQuery().
 +                executionId(task.getExecutionId()).activityType("userTask").activityName(task.getName()).singleResult();
 +
 +        if (historicActivityInstance != null) {
 +            formTO.setCreateTime(historicActivityInstance.getStartTime());
 +            formTO.setDueDate(historicActivityInstance.getEndTime());
 +        }
 +
 +        return formTO;
 +    }
 +
 +    protected WorkflowFormTO getHistoricFormTO(
 +            final String processInstanceId,
 +            final String taskId,
 +            final String formKey,
 +            final List<HistoricFormPropertyEntity> props) {
 +
 +        WorkflowFormTO formTO = new WorkflowFormTO();
 +
 +        User user = userDAO.findByWorkflowId(processInstanceId);
 +        if (user == null) {
 +            throw new NotFoundException("User with workflow id " + processInstanceId);
 +        }
 +        formTO.setUserKey(user.getKey());
 +
 +        formTO.setTaskId(taskId);
 +        formTO.setKey(formKey);
 +
 +        for (HistoricFormPropertyEntity prop : props) {
 +            WorkflowFormPropertyTO propertyTO = new WorkflowFormPropertyTO();
 +            propertyTO.setId(prop.getPropertyId());
 +            propertyTO.setName(prop.getPropertyId());
 +            propertyTO.setValue(prop.getPropertyValue());
 +            formTO.addProperty(propertyTO);
 +        }
 +
 +        return formTO;
 +    }
 +
 +    @SuppressWarnings("unchecked")
 +    protected WorkflowFormTO getFormTO(
 +            final String processInstanceId,
 +            final String taskId,
 +            final String formKey,
 +            final List<FormProperty> properties) {
 +
 +        WorkflowFormTO formTO = new WorkflowFormTO();
 +
 +        User user = userDAO.findByWorkflowId(processInstanceId);
 +        if (user == null) {
 +            throw new NotFoundException("User with workflow id " + processInstanceId);
 +        }
 +        formTO.setUserKey(user.getKey());
 +
 +        formTO.setTaskId(taskId);
 +        formTO.setKey(formKey);
 +
 +        for (FormProperty fProp : properties) {
 +            WorkflowFormPropertyTO propertyTO = new WorkflowFormPropertyTO();
 +            BeanUtils.copyProperties(fProp, propertyTO, PROPERTY_IGNORE_PROPS);
 +            propertyTO.setType(fromActivitiFormType(fProp.getType()));
 +
 +            if (propertyTO.getType() == WorkflowFormPropertyType.Date) {
 +                propertyTO.setDatePattern((String) fProp.getType().getInformation("datePattern"));
 +            }
 +            if (propertyTO.getType() == WorkflowFormPropertyType.Enum) {
 +                propertyTO.getEnumValues().putAll((Map<String, String>) fProp.getType().getInformation("values"));
 +            }
 +
 +            formTO.addProperty(propertyTO);
 +        }
 +
 +        return formTO;
 +    }
 +
 +    @Transactional(readOnly = true)
 +    @Override
 +    public List<WorkflowFormTO> getForms() {
 +        List<WorkflowFormTO> forms = new ArrayList<>();
 +
 +        String authUser = AuthContextUtils.getUsername();
 +        if (adminUser.equals(authUser)) {
 +            forms.addAll(getForms(engine.getTaskService().createTaskQuery().
 +                    taskVariableValueEquals(TASK_IS_FORM, Boolean.TRUE)));
 +        } else {
 +            User user = userDAO.find(authUser);
 +            if (user == null) {
 +                throw new NotFoundException("Syncope User " + authUser);
 +            }
 +
 +            forms.addAll(getForms(engine.getTaskService().createTaskQuery().
 +                    taskVariableValueEquals(TASK_IS_FORM, Boolean.TRUE).
 +                    taskCandidateOrAssigned(user.getKey().toString())));
 +
 +            List<String> candidateGroups = new ArrayList<>();
 +            for (Long groupId : userDAO.findAllGroupKeys(user)) {
 +                candidateGroups.add(groupId.toString());
 +            }
 +            if (!candidateGroups.isEmpty()) {
 +                forms.addAll(getForms(engine.getTaskService().createTaskQuery().
 +                        taskVariableValueEquals(TASK_IS_FORM, Boolean.TRUE).
 +                        taskCandidateGroupIn(candidateGroups)));
 +            }
 +        }
 +
 +        return forms;
 +    }
 +
 +    @Override
 +    public List<WorkflowFormTO> getForms(final String workflowId, final String name) {
 +        List<WorkflowFormTO> forms = getForms(
 +                engine.getTaskService().createTaskQuery().processInstanceId(workflowId).taskName(name).
 +                taskVariableValueEquals(TASK_IS_FORM, Boolean.TRUE));
 +
 +        forms.addAll(getForms(engine.getHistoryService().createHistoricTaskInstanceQuery().taskName(name).
 +                taskVariableValueEquals(TASK_IS_FORM, Boolean.TRUE)));
 +
 +        return forms;
 +    }
 +
 +    protected <T extends Query<?, ?>, U extends Object> List<WorkflowFormTO> getForms(final Query<T, U> query) {
 +        List<WorkflowFormTO> forms = new ArrayList<>();
 +
 +        for (U obj : query.list()) {
 +            try {
 +                if (obj instanceof HistoricTaskInstance) {
 +                    forms.add(getFormTO((HistoricTaskInstance) obj));
 +                } else if (obj instanceof Task) {
 +                    forms.add(getFormTO((Task) obj));
 +                } else {
 +                    throw new ActivitiException(
 +                            "Failure retrieving form", new IllegalArgumentException("Invalid task type"));
 +                }
 +            } catch (ActivitiException e) {
 +                LOG.debug("No form found for task {}", obj, e);
 +            }
 +        }
 +
 +        return forms;
 +    }
 +
 +    @Override
 +    public WorkflowFormTO getForm(final String workflowId) {
 +        Task task;
 +        try {
 +            task = engine.getTaskService().createTaskQuery().processInstanceId(workflowId).singleResult();
 +        } catch (ActivitiException e) {
 +            throw new WorkflowException("While reading form for workflow instance " + workflowId, e);
 +        }
 +
 +        TaskFormData formData;
 +        try {
 +            formData = engine.getFormService().getTaskFormData(task.getId());
 +        } catch (ActivitiException e) {
 +            LOG.debug("No form found for task {}", task.getId(), e);
 +            formData = null;
 +        }
 +
 +        WorkflowFormTO result = null;
 +        if (formData != null && !formData.getFormProperties().isEmpty()) {
 +            result = getFormTO(task);
 +        }
 +
 +        return result;
 +    }
 +
 +    protected Pair<Task, TaskFormData> checkTask(final String taskId, final String authUser) {
 +        Task task;
 +        try {
 +            task = engine.getTaskService().createTaskQuery().taskId(taskId).singleResult();
 +        } catch (ActivitiException e) {
 +            throw new NotFoundException("Activiti Task " + taskId, e);
 +        }
 +
 +        TaskFormData formData;
 +        try {
 +            formData = engine.getFormService().getTaskFormData(task.getId());
 +        } catch (ActivitiException e) {
 +            throw new NotFoundException("Form for Activiti Task " + taskId, e);
 +        }
 +
 +        if (!adminUser.equals(authUser)) {
 +            User user = userDAO.find(authUser);
 +            if (user == null) {
 +                throw new NotFoundException("Syncope User " + authUser);
 +            }
 +        }
 +
 +        return new ImmutablePair<>(task, formData);
 +    }
 +
 +    @Override
 +    public WorkflowFormTO claimForm(final String taskId) {
 +        String authUser = AuthContextUtils.getUsername();
 +        Pair<Task, TaskFormData> checked = checkTask(taskId, authUser);
 +
 +        if (!adminUser.equals(authUser)) {
 +            List<Task> tasksForUser = engine.getTaskService().createTaskQuery().taskId(taskId).taskCandidateUser(
 +                    authUser).list();
 +            if (tasksForUser.isEmpty()) {
 +                throw new WorkflowException(
 +                        new IllegalArgumentException(authUser + " is not candidate for task " + taskId));
 +            }
 +        }
 +
 +        Task task;
 +        try {
 +            engine.getTaskService().setOwner(taskId, authUser);
 +            task = engine.getTaskService().createTaskQuery().taskId(taskId).singleResult();
 +        } catch (ActivitiException e) {
 +            throw new WorkflowException("While reading task " + taskId, e);
 +        }
 +
 +        return getFormTO(task, checked.getValue());
 +    }
 +
 +    @Override
 +    public WorkflowResult<UserPatch> submitForm(final WorkflowFormTO form) {
 +        String authUser = AuthContextUtils.getUsername();
 +        Pair<Task, TaskFormData> checked = checkTask(form.getTaskId(), authUser);
 +
 +        if (!checked.getKey().getOwner().equals(authUser)) {
 +            throw new WorkflowException(new IllegalArgumentException("Task " + form.getTaskId() + " assigned to "
 +                    + checked.getKey().getOwner() + " but submitted by " + authUser));
 +        }
 +
 +        User user = userDAO.findByWorkflowId(checked.getKey().getProcessInstanceId());
 +        if (user == null) {
 +            throw new NotFoundException("User with workflow id " + checked.getKey().getProcessInstanceId());
 +        }
 +
 +        Set<String> preTasks = getPerformedTasks(user);
 +        try {
 +            engine.getFormService().submitTaskFormData(form.getTaskId(), form.getPropertiesForSubmit());
 +            engine.getRuntimeService().setVariable(user.getWorkflowId(), FORM_SUBMITTER, authUser);
 +        } catch (ActivitiException e) {
 +            throwException(e, "While submitting form for task " + form.getTaskId());
 +        }
 +
 +        Set<String> postTasks = getPerformedTasks(user);
 +        postTasks.removeAll(preTasks);
 +        postTasks.add(form.getTaskId());
 +
 +        updateStatus(user);
 +        User updated = userDAO.save(user);
 +
 +        // see if there is any propagation to be done
 +        PropagationByResource propByRes = engine.getRuntimeService().getVariable(
 +                user.getWorkflowId(), PROP_BY_RESOURCE, PropagationByResource.class);
 +
 +        // fetch - if available - the encrypted password
 +        String clearPassword = null;
 +        String encryptedPwd = engine.getRuntimeService().getVariable(user.getWorkflowId(), ENCRYPTED_PWD, String.class);
 +        if (StringUtils.isNotBlank(encryptedPwd)) {
 +            clearPassword = decrypt(encryptedPwd);
 +        }
 +
 +        // supports approval chains
 +        saveForFormSubmit(user, clearPassword, propByRes);
 +
 +        UserPatch userPatch = engine.getRuntimeService().getVariable(user.getWorkflowId(), USER_PATCH, UserPatch.class);
 +        if (userPatch == null) {
 +            userPatch = new UserPatch();
 +            userPatch.setKey(updated.getKey());
 +            userPatch.setPassword(new PasswordPatch.Builder().onSyncope(true).value(clearPassword).build());
 +
 +            if (propByRes != null) {
 +                userPatch.getPassword().getResources().addAll(propByRes.get(ResourceOperation.CREATE));
 +            }
 +        }
 +
 +        return new WorkflowResult<>(userPatch, propByRes, postTasks);
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/task/PasswordReset.java
----------------------------------------------------------------------
diff --cc core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/task/PasswordReset.java
index 10e9afe,0000000..91b4bf4
mode 100644,000000..100644
--- a/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/task/PasswordReset.java
+++ b/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/task/PasswordReset.java
@@@ -1,47 -1,0 +1,69 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one
 + * or more contributor license agreements.  See the NOTICE file
 + * distributed with this work for additional information
 + * regarding copyright ownership.  The ASF licenses this file
 + * to you under the Apache License, Version 2.0 (the
 + * "License"); you may not use this file except in compliance
 + * with the License.  You may obtain a copy of the License at
 + *
 + *   http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing,
 + * software distributed under the License is distributed on an
 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + * KIND, either express or implied.  See the License for the
 + * specific language governing permissions and limitations
 + * under the License.
 + */
 +package org.apache.syncope.core.workflow.activiti.task;
 +
++import org.apache.syncope.common.lib.patch.PasswordPatch;
++import org.apache.syncope.common.lib.patch.UserPatch;
++import org.apache.syncope.common.lib.types.PropagationByResource;
++import org.apache.syncope.core.persistence.api.dao.UserDAO;
 +import org.apache.syncope.core.persistence.api.entity.user.User;
++import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
 +import org.apache.syncope.core.workflow.api.WorkflowException;
 +import org.apache.syncope.core.workflow.activiti.ActivitiUserWorkflowAdapter;
++import org.springframework.beans.factory.annotation.Autowired;
 +import org.springframework.stereotype.Component;
 +
 +@Component
 +public class PasswordReset extends AbstractActivitiServiceTask {
 +
++    @Autowired
++    private UserDAO userDAO;
++
++    @Autowired
++    private UserDataBinder dataBinder;
++
 +    @Override
 +    protected void doExecute(final String executionId) {
 +        User user = engine.getRuntimeService().
 +                getVariable(executionId, ActivitiUserWorkflowAdapter.USER, User.class);
 +        String token = engine.getRuntimeService().
 +                getVariable(executionId, ActivitiUserWorkflowAdapter.TOKEN, String.class);
 +        String password = engine.getRuntimeService().
 +                getVariable(executionId, ActivitiUserWorkflowAdapter.PASSWORD, String.class);
 +
 +        if (!user.checkToken(token)) {
 +            throw new WorkflowException(new IllegalArgumentException("Wrong token: " + token + " for " + user));
 +        }
 +
 +        user.removeToken();
-         user.setPassword(password, user.getCipherAlgorithm());
++
++        UserPatch userPatch = new UserPatch();
++        userPatch.setKey(user.getKey());
++        userPatch.setPassword(new PasswordPatch.Builder().
++                onSyncope(true).resources(userDAO.findAllResourceNames(user)).value(password).build());
++
++        PropagationByResource propByRes = dataBinder.update(user, userPatch);
++
++        // report updated user and propagation by resource as result
 +        engine.getRuntimeService().setVariable(executionId, ActivitiUserWorkflowAdapter.USER, user);
++        engine.getRuntimeService().setVariable(executionId, ActivitiUserWorkflowAdapter.USER_PATCH, userPatch);
++        engine.getRuntimeService().setVariable(executionId, ActivitiUserWorkflowAdapter.PROP_BY_RESOURCE, propByRes);
 +    }
 +
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/task/Update.java
----------------------------------------------------------------------
diff --cc core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/task/Update.java
index 21498ac,0000000..4448938
mode 100644,000000..100644
--- a/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/task/Update.java
+++ b/core/workflow-activiti/src/main/java/org/apache/syncope/core/workflow/activiti/task/Update.java
@@@ -1,66 -1,0 +1,49 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one
 + * or more contributor license agreements.  See the NOTICE file
 + * distributed with this work for additional information
 + * regarding copyright ownership.  The ASF licenses this file
 + * to you under the Apache License, Version 2.0 (the
 + * "License"); you may not use this file except in compliance
 + * with the License.  You may obtain a copy of the License at
 + *
 + *   http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing,
 + * software distributed under the License is distributed on an
 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + * KIND, either express or implied.  See the License for the
 + * specific language governing permissions and limitations
 + * under the License.
 + */
 +package org.apache.syncope.core.workflow.activiti.task;
 +
- import org.apache.commons.lang3.SerializationUtils;
- import org.apache.syncope.common.lib.patch.PasswordPatch;
 +import org.apache.syncope.common.lib.patch.UserPatch;
 +import org.apache.syncope.common.lib.types.PropagationByResource;
 +import org.apache.syncope.core.persistence.api.entity.user.User;
 +import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
 +import org.apache.syncope.core.workflow.activiti.ActivitiUserWorkflowAdapter;
 +import org.springframework.beans.factory.annotation.Autowired;
 +import org.springframework.stereotype.Component;
 +
 +@Component
 +public class Update extends AbstractActivitiServiceTask {
 +
 +    @Autowired
 +    private UserDataBinder dataBinder;
 +
 +    @Override
 +    protected void doExecute(final String executionId) {
 +        User user = engine.getRuntimeService().
 +                getVariable(executionId, ActivitiUserWorkflowAdapter.USER, User.class);
 +        UserPatch userPatch = engine.getRuntimeService().
 +                getVariable(executionId, ActivitiUserWorkflowAdapter.USER_PATCH, UserPatch.class);
 +
-         // update password internally only if required
-         UserPatch updatedPatch = SerializationUtils.clone(userPatch);
-         PasswordPatch updatedPwd = updatedPatch.getPassword();
-         if (updatedPatch.getPassword() != null && !updatedPatch.getPassword().isOnSyncope()) {
-             updatedPatch.setPassword(null);
-         }
-         // update user
-         PropagationByResource propByRes = dataBinder.update(user, updatedPatch);
-         if (updatedPatch.getPassword() != null && !updatedPatch.getPassword().getResources().isEmpty()) {
-             if (updatedPwd == null) {
-                 updatedPwd = updatedPatch.getPassword();
-             } else {
-                 updatedPwd.getResources().addAll(updatedPatch.getPassword().getResources());
-             }
-         }
-         updatedPatch.setPassword(updatedPwd);
++        PropagationByResource propByRes = dataBinder.update(user, userPatch);
 +
 +        // report updated user and propagation by resource as result
 +        engine.getRuntimeService().setVariable(executionId, ActivitiUserWorkflowAdapter.USER, user);
-         engine.getRuntimeService().setVariable(executionId, ActivitiUserWorkflowAdapter.USER_PATCH, updatedPatch);
++        engine.getRuntimeService().setVariable(executionId, ActivitiUserWorkflowAdapter.USER_PATCH, userPatch);
 +        engine.getRuntimeService().setVariable(executionId, ActivitiUserWorkflowAdapter.PROP_BY_RESOURCE, propByRes);
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/core/workflow-api/src/main/java/org/apache/syncope/core/workflow/api/UserWorkflowAdapter.java
----------------------------------------------------------------------
diff --cc core/workflow-api/src/main/java/org/apache/syncope/core/workflow/api/UserWorkflowAdapter.java
index 51c3e77,0000000..91a83aa
mode 100644,000000..100644
--- a/core/workflow-api/src/main/java/org/apache/syncope/core/workflow/api/UserWorkflowAdapter.java
+++ b/core/workflow-api/src/main/java/org/apache/syncope/core/workflow/api/UserWorkflowAdapter.java
@@@ -1,135 -1,0 +1,136 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one
 + * or more contributor license agreements.  See the NOTICE file
 + * distributed with this work for additional information
 + * regarding copyright ownership.  The ASF licenses this file
 + * to you under the Apache License, Version 2.0 (the
 + * "License"); you may not use this file except in compliance
 + * with the License.  You may obtain a copy of the License at
 + *
 + *   http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing,
 + * software distributed under the License is distributed on an
 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + * KIND, either express or implied.  See the License for the
 + * specific language governing permissions and limitations
 + * under the License.
 + */
 +package org.apache.syncope.core.workflow.api;
 +
 +import org.apache.commons.lang3.tuple.Pair;
 +import org.apache.syncope.common.lib.patch.UserPatch;
 +import org.apache.syncope.core.provisioning.api.WorkflowResult;
 +import org.apache.syncope.common.lib.to.UserTO;
 +
 +/**
 + * Interface for calling underlying workflow implementations.
 + */
 +public interface UserWorkflowAdapter extends WorkflowAdapter {
 +
 +    /**
 +     * Create an user.
 +     *
 +     * @param userTO user to be created and whether to propagate it as active
 +     * @param storePassword whether password shall be stored into the internal storage
 +     * @return user just created
 +     */
 +    WorkflowResult<Pair<Long, Boolean>> create(UserTO userTO, boolean storePassword);
 +
 +    /**
 +     * Create an user, optionally disabling password policy check.
 +     *
 +     * @param userTO user to be created and whether to propagate it as active
 +     * @param disablePwdPolicyCheck disable password policy check?
 +     * @param storePassword whether password shall be stored into the internal storage
 +     * @return user just created
 +     */
 +    WorkflowResult<Pair<Long, Boolean>> create(
 +            UserTO userTO, boolean disablePwdPolicyCheck, boolean storePassword);
 +
 +    /**
 +     * Create an user, optionally disabling password policy check.
 +     *
 +     * @param userTO user to be created and whether to propagate it as active
 +     * @param disablePwdPolicyCheck disable password policy check?
 +     * @param enabled specify true/false to force active/supended status
 +     * @param storePassword whether password shall be stored into the internal storage
 +     * @return user just created
 +     */
 +    WorkflowResult<Pair<Long, Boolean>> create(
 +            UserTO userTO, boolean disablePwdPolicyCheck, final Boolean enabled, boolean storePassword);
 +
 +    /**
 +     * Execute a task on an user.
 +     *
 +     * @param userTO user to be subject to task
 +     * @param taskId to be executed
 +     * @return user just updated
 +     */
 +    WorkflowResult<Long> execute(UserTO userTO, String taskId);
 +
 +    /**
 +     * Activate an user.
 +     *
 +     * @param userKey user to be activated
 +     * @param token to be verified for activation
 +     * @return user just updated
 +     */
 +    WorkflowResult<Long> activate(Long userKey, String token);
 +
 +    /**
 +     * Update an user.
 +     *
 +     * @param userPatch modification set to be performed
 +     * @return user just updated and propagations to be performed
 +     */
 +    WorkflowResult<Pair<UserPatch, Boolean>> update(UserPatch userPatch);
 +
 +    /**
 +     * Suspend an user.
 +     *
 +     * @param key to be suspended
 +     * @return user just suspended
 +     */
 +    WorkflowResult<Long> suspend(Long key);
 +
 +    /**
 +     * Suspend an user (used by internal authentication process)
 +     *
 +     * @param key to be suspended
 +     * @return user just suspended and information whether to propagate suspension
 +     */
 +    Pair<WorkflowResult<Long>, Boolean> internalSuspend(Long key);
 +
 +    /**
 +     * Reactivate an user.
 +     *
 +     * @param userKey user to be reactivated
 +     * @return user just reactivated
 +     */
 +    WorkflowResult<Long> reactivate(Long userKey);
 +
 +    /**
 +     * Request password reset for an user.
 +     *
 +     * @param userKey user requesting password reset
 +     */
 +    void requestPasswordReset(Long userKey);
 +
 +    /**
 +     * Confirm password reset for an user.
 +     *
 +     * @param userKey user confirming password reset
 +     * @param token security token
 +     * @param password new password value
++     * @return user just updated and propagations to be performed
 +     */
-     void confirmPasswordReset(Long userKey, String token, String password);
++    WorkflowResult<Pair<UserPatch, Boolean>> confirmPasswordReset(Long userKey, String token, String password);
 +
 +    /**
 +     * Delete an user.
 +     *
 +     * @param userKey user to be deleted
 +     */
 +    void delete(Long userKey);
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java
----------------------------------------------------------------------
diff --cc core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java
index 4236cff,0000000..716090f
mode 100644,000000..100644
--- a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java
+++ b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java
@@@ -1,150 -1,0 +1,153 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one
 + * or more contributor license agreements.  See the NOTICE file
 + * distributed with this work for additional information
 + * regarding copyright ownership.  The ASF licenses this file
 + * to you under the Apache License, Version 2.0 (the
 + * "License"); you may not use this file except in compliance
 + * with the License.  You may obtain a copy of the License at
 + *
 + *   http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing,
 + * software distributed under the License is distributed on an
 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + * KIND, either express or implied.  See the License for the
 + * specific language governing permissions and limitations
 + * under the License.
 + */
 +package org.apache.syncope.core.workflow.java;
 +
 +import org.apache.commons.lang3.tuple.ImmutablePair;
 +import org.apache.commons.lang3.tuple.Pair;
 +import org.apache.syncope.common.lib.patch.UserPatch;
 +import org.apache.syncope.core.persistence.api.dao.UserDAO;
 +import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 +import org.apache.syncope.core.persistence.api.entity.user.User;
 +import org.apache.syncope.core.provisioning.api.WorkflowResult;
 +import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
 +import org.apache.syncope.core.workflow.api.UserWorkflowAdapter;
 +import org.identityconnectors.common.Base64;
 +import org.identityconnectors.common.security.EncryptorFactory;
 +import org.slf4j.Logger;
 +import org.slf4j.LoggerFactory;
 +import org.springframework.beans.factory.annotation.Autowired;
 +import org.springframework.transaction.annotation.Propagation;
 +import org.springframework.transaction.annotation.Transactional;
 +
 +@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = { Throwable.class })
 +public abstract class AbstractUserWorkflowAdapter implements UserWorkflowAdapter {
 +
 +    protected static final Logger LOG = LoggerFactory.getLogger(UserWorkflowAdapter.class);
 +
 +    @Autowired
 +    protected UserDataBinder dataBinder;
 +
 +    @Autowired
 +    protected UserDAO userDAO;
 +
 +    @Autowired
 +    protected EntityFactory entityFactory;
 +
 +    public static String encrypt(final String clear) {
 +        byte[] encryptedBytes = EncryptorFactory.getInstance().getDefaultEncryptor().encrypt(clear.getBytes());
 +        return Base64.encode(encryptedBytes);
 +    }
 +
 +    public static String decrypt(final String crypted) {
 +        byte[] decryptedBytes = EncryptorFactory.getInstance().getDefaultEncryptor().decrypt(Base64.decode(crypted));
 +        return new String(decryptedBytes);
 +    }
 +
 +    @Override
 +    public String getPrefix() {
 +        return null;
 +    }
 +
 +    protected abstract WorkflowResult<Long> doActivate(User user, String token);
 +
 +    @Override
 +    public WorkflowResult<Long> activate(final Long key, final String token) {
 +        return doActivate(userDAO.authFind(key), token);
 +    }
 +
 +    protected abstract WorkflowResult<Pair<UserPatch, Boolean>> doUpdate(User user, UserPatch userPatch);
 +
 +    @Override
 +    public WorkflowResult<Pair<UserPatch, Boolean>> update(final UserPatch userPatch) {
 +        return doUpdate(userDAO.authFind(userPatch.getKey()), userPatch);
 +    }
 +
 +    protected abstract WorkflowResult<Long> doSuspend(User user);
 +
 +    @Override
 +    public WorkflowResult<Long> suspend(final Long key) {
 +        User user = userDAO.authFind(key);
 +
 +        // set suspended flag
 +        user.setSuspended(Boolean.TRUE);
 +
 +        return doSuspend(user);
 +    }
 +
 +    @Override
 +    public Pair<WorkflowResult<Long>, Boolean> internalSuspend(final Long key) {
 +        User user = userDAO.authFind(key);
 +
 +        Pair<WorkflowResult<Long>, Boolean> result = null;
 +
 +        Pair<Boolean, Boolean> enforce = userDAO.enforcePolicies(user);
 +        if (enforce.getKey()) {
 +            LOG.debug("User {} {} is over the max failed logins", user.getKey(), user.getUsername());
 +
 +            // reduce failed logins number to avoid multiple request       
 +            user.setFailedLogins(user.getFailedLogins() - 1);
 +
 +            // set suspended flag
 +            user.setSuspended(Boolean.TRUE);
 +
 +            result = ImmutablePair.of(doSuspend(user), enforce.getValue());
 +        }
 +
 +        return result;
 +    }
 +
 +    protected abstract WorkflowResult<Long> doReactivate(User user);
 +
 +    @Override
 +    public WorkflowResult<Long> reactivate(final Long key) {
 +        User user = userDAO.authFind(key);
 +
 +        // reset failed logins
 +        user.setFailedLogins(0);
 +
 +        // reset suspended flag
 +        user.setSuspended(Boolean.FALSE);
 +
 +        return doReactivate(user);
 +    }
 +
 +    protected abstract void doRequestPasswordReset(User user);
 +
 +    @Override
 +    public void requestPasswordReset(final Long key) {
 +        doRequestPasswordReset(userDAO.authFind(key));
 +    }
 +
-     protected abstract void doConfirmPasswordReset(User user, String token, String password);
++    protected abstract WorkflowResult<Pair<UserPatch, Boolean>> doConfirmPasswordReset(
++            User user, String token, String password);
 +
 +    @Override
-     public void confirmPasswordReset(final Long key, final String token, final String password) {
-         doConfirmPasswordReset(userDAO.authFind(key), token, password);
++    public WorkflowResult<Pair<UserPatch, Boolean>> confirmPasswordReset(
++            final Long key, final String token, final String password) {
++
++        return doConfirmPasswordReset(userDAO.authFind(key), token, password);
 +    }
 +
 +    protected abstract void doDelete(User user);
 +
 +    @Override
 +    public void delete(final Long key) {
 +        doDelete(userDAO.authFind(key));
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java
----------------------------------------------------------------------
diff --cc core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java
index 1d70394,0000000..5592eef
mode 100644,000000..100644
--- a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java
+++ b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/DefaultUserWorkflowAdapter.java
@@@ -1,218 -1,0 +1,209 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one
 + * or more contributor license agreements.  See the NOTICE file
 + * distributed with this work for additional information
 + * regarding copyright ownership.  The ASF licenses this file
 + * to you under the Apache License, Version 2.0 (the
 + * "License"); you may not use this file except in compliance
 + * with the License.  You may obtain a copy of the License at
 + *
 + *   http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing,
 + * software distributed under the License is distributed on an
 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + * KIND, either express or implied.  See the License for the
 + * specific language governing permissions and limitations
 + * under the License.
 + */
 +package org.apache.syncope.core.workflow.java;
 +
 +import java.io.OutputStream;
 +import java.util.Collections;
 +import java.util.List;
- import org.apache.commons.lang3.SerializationUtils;
 +import org.apache.commons.lang3.tuple.ImmutablePair;
 +import org.apache.commons.lang3.tuple.Pair;
 +import org.apache.syncope.common.lib.patch.PasswordPatch;
 +import org.apache.syncope.common.lib.patch.UserPatch;
 +import org.apache.syncope.common.lib.to.UserTO;
 +import org.apache.syncope.common.lib.to.WorkflowFormTO;
 +import org.apache.syncope.common.lib.types.PropagationByResource;
 +import org.apache.syncope.common.lib.types.ResourceOperation;
 +import org.apache.syncope.core.persistence.api.dao.ConfDAO;
 +import org.apache.syncope.core.persistence.api.entity.user.User;
 +import org.apache.syncope.core.provisioning.api.WorkflowResult;
 +import org.apache.syncope.core.workflow.api.WorkflowDefinitionFormat;
 +import org.apache.syncope.core.workflow.api.WorkflowException;
 +import org.springframework.beans.factory.annotation.Autowired;
 +
 +/**
 + * Simple implementation basically not involving any workflow engine.
 + */
 +public class DefaultUserWorkflowAdapter extends AbstractUserWorkflowAdapter {
 +
 +    @Autowired
 +    private ConfDAO confDAO;
 +
 +    @Override
 +    public WorkflowResult<Pair<Long, Boolean>> create(final UserTO userTO, final boolean storePassword) {
 +        return create(userTO, false, true);
 +    }
 +
 +    @Override
 +    public WorkflowResult<Pair<Long, Boolean>> create(final UserTO userTO, final boolean disablePwdPolicyCheck,
 +            final boolean storePassword) {
 +
 +        return create(userTO, disablePwdPolicyCheck, null, storePassword);
 +    }
 +
 +    @Override
 +    public WorkflowResult<Pair<Long, Boolean>> create(final UserTO userTO, final boolean disablePwdPolicyCheck,
 +            final Boolean enabled, final boolean storePassword) {
 +
 +        User user = entityFactory.newEntity(User.class);
 +        dataBinder.create(user, userTO, storePassword);
 +
 +        // this will make UserValidator not to consider password policies at all
 +        if (disablePwdPolicyCheck) {
 +            user.removeClearPassword();
 +        }
 +
 +        String status;
 +        boolean propagateEnable;
 +        if (enabled == null) {
 +            status = "created";
 +            propagateEnable = true;
 +        } else {
 +            status = enabled
 +                    ? "active"
 +                    : "suspended";
 +            propagateEnable = enabled;
 +            user.setSuspended(!enabled);
 +        }
 +
 +        user.setStatus(status);
 +        user = userDAO.save(user);
 +
 +        final PropagationByResource propByRes = new PropagationByResource();
 +        propByRes.set(ResourceOperation.CREATE, userDAO.findAllResourceNames(user));
 +
 +        return new WorkflowResult<Pair<Long, Boolean>>(
 +                new ImmutablePair<>(user.getKey(), propagateEnable), propByRes, "create");
 +    }
 +
 +    @Override
 +    protected WorkflowResult<Long> doActivate(final User user, final String token) {
 +        if (!user.checkToken(token)) {
 +            throw new WorkflowException(new IllegalArgumentException("Wrong token: " + token + " for " + user));
 +        }
 +
 +        user.removeToken();
 +        user.setStatus("active");
 +        User updated = userDAO.save(user);
 +
 +        return new WorkflowResult<>(updated.getKey(), null, "activate");
 +    }
 +
 +    @Override
 +    protected WorkflowResult<Pair<UserPatch, Boolean>> doUpdate(final User user, final UserPatch userPatch) {
-         // update password internally only if required
-         UserPatch updatedPatch = SerializationUtils.clone(userPatch);
-         PasswordPatch updatedPwd = updatedPatch.getPassword();
-         if (updatedPatch.getPassword() != null && !updatedPatch.getPassword().isOnSyncope()) {
-             updatedPatch.setPassword(null);
-         }
-         // update User
-         PropagationByResource propByRes = dataBinder.update(user, updatedPatch);
-         if (updatedPatch.getPassword() != null && !updatedPatch.getPassword().getResources().isEmpty()) {
-             if (updatedPwd == null) {
-                 updatedPwd = updatedPatch.getPassword();
-             } else {
-                 updatedPwd.getResources().addAll(updatedPatch.getPassword().getResources());
-             }
-         }
-         updatedPatch.setPassword(updatedPwd);
++        PropagationByResource propByRes = dataBinder.update(user, userPatch);
 +
 +        userDAO.save(user);
 +
 +        return new WorkflowResult<Pair<UserPatch, Boolean>>(
-                 new ImmutablePair<>(updatedPatch, !user.isSuspended()), propByRes, "update");
++                new ImmutablePair<>(userPatch, !user.isSuspended()), propByRes, "update");
 +    }
 +
 +    @Override
 +    protected WorkflowResult<Long> doSuspend(final User user) {
 +        user.setStatus("suspended");
 +        User updated = userDAO.save(user);
 +
 +        return new WorkflowResult<>(updated.getKey(), null, "suspend");
 +    }
 +
 +    @Override
 +    protected WorkflowResult<Long> doReactivate(final User user) {
 +        user.setStatus("active");
 +        User updated = userDAO.save(user);
 +
 +        return new WorkflowResult<>(updated.getKey(), null, "reactivate");
 +    }
 +
 +    @Override
 +    protected void doRequestPasswordReset(final User user) {
 +        user.generateToken(
 +                confDAO.find("token.length", "256").getValues().get(0).getLongValue().intValue(),
 +                confDAO.find("token.expireTime", "60").getValues().get(0).getLongValue().intValue());
 +        userDAO.save(user);
 +    }
 +
 +    @Override
-     protected void doConfirmPasswordReset(final User user, final String token, final String password) {
++    protected WorkflowResult<Pair<UserPatch, Boolean>> doConfirmPasswordReset(
++            final User user, final String token, final String password) {
++
 +        if (!user.checkToken(token)) {
 +            throw new WorkflowException(new IllegalArgumentException("Wrong token: " + token + " for " + user));
 +        }
 +
 +        user.removeToken();
-         user.setPassword(password, user.getCipherAlgorithm());
-         userDAO.save(user);
++
++        UserPatch userPatch = new UserPatch();
++        userPatch.setKey(user.getKey());
++        userPatch.setPassword(new PasswordPatch.Builder().
++                onSyncope(true).resources(userDAO.findAllResourceNames(user)).value(password).build());
++
++        return doUpdate(user, userPatch);
 +    }
 +
 +    @Override
 +    protected void doDelete(final User user) {
 +        userDAO.delete(user);
 +    }
 +
 +    @Override
 +    public WorkflowResult<Long> execute(final UserTO userTO, final String taskId) {
 +        throw new WorkflowException(new UnsupportedOperationException("Not supported."));
 +    }
 +
 +    @Override
 +    public void exportDefinition(final WorkflowDefinitionFormat format, final OutputStream os) {
 +        throw new WorkflowException(new UnsupportedOperationException("Not supported."));
 +    }
 +
 +    @Override
 +    public void exportDiagram(final OutputStream os) {
 +        throw new WorkflowException(new UnsupportedOperationException("Not supported."));
 +    }
 +
 +    @Override
 +    public void importDefinition(final WorkflowDefinitionFormat format, final String definition) {
 +        throw new WorkflowException(new UnsupportedOperationException("Not supported."));
 +    }
 +
 +    @Override
 +    public List<WorkflowFormTO> getForms() {
 +        return Collections.emptyList();
 +    }
 +
 +    @Override
 +    public List<WorkflowFormTO> getForms(final String workflowId, final String name) {
 +        return Collections.emptyList();
 +    }
 +
 +    @Override
 +    public WorkflowFormTO getForm(final String workflowId) {
 +        return null;
 +    }
 +
 +    @Override
 +    public WorkflowFormTO claimForm(final String taskId) {
 +        throw new WorkflowException(new UnsupportedOperationException("Not supported."));
 +    }
 +
 +    @Override
 +    public WorkflowResult<UserPatch> submitForm(final WorkflowFormTO form) {
 +        throw new WorkflowException(new UnsupportedOperationException("Not supported."));
 +    }
 +
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/AnyObjectUpdateProcessor.java
----------------------------------------------------------------------
diff --cc ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/AnyObjectUpdateProcessor.java
index 978388d,0000000..158cc09
mode 100644,000000..100644
--- a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/AnyObjectUpdateProcessor.java
+++ b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/AnyObjectUpdateProcessor.java
@@@ -1,102 -1,0 +1,95 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one
 + * or more contributor license agreements.  See the NOTICE file
 + * distributed with this work for additional information
 + * regarding copyright ownership.  The ASF licenses this file
 + * to you under the Apache License, Version 2.0 (the
 + * "License"); you may not use this file except in compliance
 + * with the License.  You may obtain a copy of the License at
 + *
 + *   http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing,
 + * software distributed under the License is distributed on an
 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + * KIND, either express or implied.  See the License for the
 + * specific language governing permissions and limitations
 + * under the License.
 + */
 +package org.apache.syncope.core.provisioning.camel.processor;
 +
- import java.util.Collections;
 +import java.util.List;
 +import java.util.Set;
 +import org.apache.camel.Exchange;
 +import org.apache.camel.Processor;
 +import org.apache.commons.lang3.tuple.ImmutablePair;
 +import org.apache.syncope.common.lib.patch.AnyObjectPatch;
 +import org.apache.syncope.common.lib.types.AnyTypeKind;
 +import org.apache.syncope.common.lib.types.PropagationByResource;
 +import org.apache.syncope.core.misc.spring.ApplicationContextProvider;
 +import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
 +import org.apache.syncope.core.provisioning.api.VirAttrHandler;
 +import org.apache.syncope.core.provisioning.api.WorkflowResult;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationException;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationManager;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
 +import org.slf4j.Logger;
 +import org.slf4j.LoggerFactory;
 +import org.springframework.beans.factory.annotation.Autowired;
 +import org.springframework.stereotype.Component;
 +
 +@Component
 +public class AnyObjectUpdateProcessor implements Processor {
 +
 +    private static final Logger LOG = LoggerFactory.getLogger(UserUpdateProcessor.class);
 +
 +    @Autowired
 +    protected PropagationManager propagationManager;
 +
 +    @Autowired
 +    protected PropagationTaskExecutor taskExecutor;
 +
 +    @Autowired
 +    protected VirAttrHandler virtAttrHandler;
 +
 +    @SuppressWarnings("unchecked")
 +    @Override
 +    public void process(final Exchange exchange) {
 +        WorkflowResult<Long> updated = (WorkflowResult) exchange.getIn().getBody();
 +        AnyObjectPatch anyObjectPatch = exchange.getProperty("anyPatch", AnyObjectPatch.class);
 +        Set<String> excludedResources = exchange.getProperty("excludedResources", Set.class);
 +
++        // SYNCOPE-459: take care of user virtual attributes ...
++        PropagationByResource propByResVirAttr = virtAttrHandler.updateVirtual(
++                updated.getResult(),
++                AnyTypeKind.ANY_OBJECT,
++                anyObjectPatch.getVirAttrs());
++        if (updated.getPropByRes() == null) {
++            updated.setPropByRes(propByResVirAttr);
++        } else {
++            updated.getPropByRes().merge(propByResVirAttr);
++        }
++
 +        List<PropagationTask> tasks = propagationManager.getUpdateTasks(
 +                AnyTypeKind.ANY_OBJECT,
 +                updated.getResult(),
 +                false,
 +                null,
 +                updated.getPropByRes(),
 +                anyObjectPatch.getVirAttrs(),
 +                excludedResources);
-         if (tasks.isEmpty()) {
-             // SYNCOPE-459: take care of user virtual attributes ...
-             PropagationByResource propByResVirAttr = virtAttrHandler.updateVirtual(
-                     updated.getResult(),
-                     AnyTypeKind.ANY_OBJECT,
-                     anyObjectPatch.getVirAttrs());
-             tasks.addAll(!propByResVirAttr.isEmpty()
-                     ? propagationManager.getUpdateTasks(
-                             AnyTypeKind.ANY_OBJECT,
-                             updated.getResult(),
-                             false,
-                             null,
-                             updated.getPropByRes(),
-                             anyObjectPatch.getVirAttrs(),
-                             null)
-                     : Collections.<PropagationTask>emptyList());
-         }
 +
 +        PropagationReporter propagationReporter =
 +                ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class);
 +        try {
 +            taskExecutor.execute(tasks, propagationReporter);
 +        } catch (PropagationException e) {
 +            LOG.error("Error propagation primary resource", e);
 +            propagationReporter.onPrimaryResourceFailure(tasks);
 +        }
 +
 +        exchange.getOut().setBody(new ImmutablePair<>(updated.getResult(), propagationReporter.getStatuses()));
 +    }
 +
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/GroupUpdateProcessor.java
----------------------------------------------------------------------
diff --cc ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/GroupUpdateProcessor.java
index ad9ed63,0000000..22868bf
mode 100644,000000..100644
--- a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/GroupUpdateProcessor.java
+++ b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/GroupUpdateProcessor.java
@@@ -1,102 -1,0 +1,95 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one
 + * or more contributor license agreements.  See the NOTICE file
 + * distributed with this work for additional information
 + * regarding copyright ownership.  The ASF licenses this file
 + * to you under the Apache License, Version 2.0 (the
 + * "License"); you may not use this file except in compliance
 + * with the License.  You may obtain a copy of the License at
 + *
 + *   http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing,
 + * software distributed under the License is distributed on an
 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + * KIND, either express or implied.  See the License for the
 + * specific language governing permissions and limitations
 + * under the License.
 + */
 +package org.apache.syncope.core.provisioning.camel.processor;
 +
- import java.util.Collections;
 +import java.util.List;
 +import java.util.Set;
 +import org.apache.camel.Exchange;
 +import org.apache.camel.Processor;
 +import org.apache.commons.lang3.tuple.ImmutablePair;
 +import org.apache.syncope.common.lib.patch.GroupPatch;
 +import org.apache.syncope.common.lib.types.AnyTypeKind;
 +import org.apache.syncope.common.lib.types.PropagationByResource;
 +import org.apache.syncope.core.misc.spring.ApplicationContextProvider;
 +import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
 +import org.apache.syncope.core.provisioning.api.VirAttrHandler;
 +import org.apache.syncope.core.provisioning.api.WorkflowResult;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationException;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationManager;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
 +import org.slf4j.Logger;
 +import org.slf4j.LoggerFactory;
 +import org.springframework.beans.factory.annotation.Autowired;
 +import org.springframework.stereotype.Component;
 +
 +@Component
 +public class GroupUpdateProcessor implements Processor {
 +
 +    private static final Logger LOG = LoggerFactory.getLogger(UserUpdateProcessor.class);
 +
 +    @Autowired
 +    protected PropagationManager propagationManager;
 +
 +    @Autowired
 +    protected PropagationTaskExecutor taskExecutor;
 +
 +    @Autowired
 +    protected VirAttrHandler virtAttrHandler;
 +
 +    @SuppressWarnings("unchecked")
 +    @Override
 +    public void process(final Exchange exchange) {
 +        WorkflowResult<Long> updated = (WorkflowResult) exchange.getIn().getBody();
 +        GroupPatch groupPatch = exchange.getProperty("anyPatch", GroupPatch.class);
 +        Set<String> excludedResources = exchange.getProperty("excludedResources", Set.class);
 +
++        // SYNCOPE-459: take care of user virtual attributes ...
++        PropagationByResource propByResVirAttr = virtAttrHandler.updateVirtual(
++                updated.getResult(),
++                AnyTypeKind.GROUP,
++                groupPatch.getVirAttrs());
++        if (updated.getPropByRes() == null) {
++            updated.setPropByRes(propByResVirAttr);
++        } else {
++            updated.getPropByRes().merge(propByResVirAttr);
++        }
++
 +        List<PropagationTask> tasks = propagationManager.getUpdateTasks(
 +                AnyTypeKind.GROUP,
 +                updated.getResult(),
 +                false,
 +                null,
 +                updated.getPropByRes(),
 +                groupPatch.getVirAttrs(),
 +                excludedResources);
-         if (tasks.isEmpty()) {
-             // SYNCOPE-459: take care of user virtual attributes ...
-             PropagationByResource propByResVirAttr = virtAttrHandler.updateVirtual(
-                     updated.getResult(),
-                     AnyTypeKind.GROUP,
-                     groupPatch.getVirAttrs());
-             tasks.addAll(!propByResVirAttr.isEmpty()
-                     ? propagationManager.getUpdateTasks(
-                             AnyTypeKind.GROUP,
-                             updated.getResult(),
-                             false,
-                             null,
-                             updated.getPropByRes(),
-                             groupPatch.getVirAttrs(),
-                             excludedResources)
-                     : Collections.<PropagationTask>emptyList());
-         }
 +
 +        PropagationReporter propagationReporter =
 +                ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class);
 +        try {
 +            taskExecutor.execute(tasks, propagationReporter);
 +        } catch (PropagationException e) {
 +            LOG.error("Error propagation primary resource", e);
 +            propagationReporter.onPrimaryResourceFailure(tasks);
 +        }
 +
 +        exchange.getOut().setBody(new ImmutablePair<>(updated.getResult(), propagationReporter.getStatuses()));
 +    }
 +
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserConfirmPwdResetProcessor.java
----------------------------------------------------------------------
diff --cc ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserConfirmPwdResetProcessor.java
index 1e02187,0000000..8a19609
mode 100644,000000..100644
--- a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserConfirmPwdResetProcessor.java
+++ b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserConfirmPwdResetProcessor.java
@@@ -1,73 -1,0 +1,65 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one
 + * or more contributor license agreements.  See the NOTICE file
 + * distributed with this work for additional information
 + * regarding copyright ownership.  The ASF licenses this file
 + * to you under the Apache License, Version 2.0 (the
 + * "License"); you may not use this file except in compliance
 + * with the License.  You may obtain a copy of the License at
 + *
 + *   http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing,
 + * software distributed under the License is distributed on an
 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + * KIND, either express or implied.  See the License for the
 + * specific language governing permissions and limitations
 + * under the License.
 + */
 +package org.apache.syncope.core.provisioning.camel.processor;
 +
 +import java.util.List;
 +import org.apache.camel.Exchange;
 +import org.apache.camel.Processor;
- import org.apache.commons.lang3.tuple.ImmutablePair;
 +import org.apache.commons.lang3.tuple.Pair;
- import org.apache.syncope.common.lib.patch.PasswordPatch;
 +import org.apache.syncope.common.lib.patch.UserPatch;
 +import org.apache.syncope.core.misc.spring.ApplicationContextProvider;
 +import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
 +import org.apache.syncope.core.provisioning.api.WorkflowResult;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationException;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationManager;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
 +import org.slf4j.Logger;
 +import org.slf4j.LoggerFactory;
 +import org.springframework.beans.factory.annotation.Autowired;
 +import org.springframework.stereotype.Component;
 +
 +@Component
 +public class UserConfirmPwdResetProcessor implements Processor {
 +
 +    private static final Logger LOG = LoggerFactory.getLogger(UserConfirmPwdResetProcessor.class);
 +
 +    @Autowired
 +    protected PropagationManager propagationManager;
 +
 +    @Autowired
 +    protected PropagationTaskExecutor taskExecutor;
 +
 +    @Override
 +    public void process(final Exchange exchange) {
-         Long key = exchange.getProperty("userKey", Long.class);
++        @SuppressWarnings("unchecked")
++        WorkflowResult<Pair<UserPatch, Boolean>> updated = (WorkflowResult) exchange.getIn().getBody();
 +
-         UserPatch userPatch = new UserPatch();
-         userPatch.setKey(key);
-         userPatch.setPassword(new PasswordPatch.Builder().
-                 value(exchange.getProperty("password", String.class)).build());
++        List<PropagationTask> tasks = propagationManager.getUserUpdateTasks(updated);
 +
-         List<PropagationTask> tasks = propagationManager.getUserUpdateTasks(
-                 new WorkflowResult<Pair<UserPatch, Boolean>>(
-                         new ImmutablePair<UserPatch, Boolean>(userPatch, null), null, "confirmPasswordReset"),
-                 true, null);
 +        PropagationReporter propReporter =
 +                ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class);
 +        try {
 +            taskExecutor.execute(tasks, propReporter);
 +        } catch (PropagationException e) {
 +            LOG.error("Error propagation primary resource", e);
 +            propReporter.onPrimaryResourceFailure(tasks);
 +        }
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserStatusPropagationProcessor.java
----------------------------------------------------------------------
diff --cc ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserStatusPropagationProcessor.java
index da6e745,0000000..068f003
mode 100644,000000..100644
--- a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserStatusPropagationProcessor.java
+++ b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/processor/UserStatusPropagationProcessor.java
@@@ -1,87 -1,0 +1,86 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one
 + * or more contributor license agreements.  See the NOTICE file
 + * distributed with this work for additional information
 + * regarding copyright ownership.  The ASF licenses this file
 + * to you under the Apache License, Version 2.0 (the
 + * "License"); you may not use this file except in compliance
 + * with the License.  You may obtain a copy of the License at
 + *
 + *   http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing,
 + * software distributed under the License is distributed on an
 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + * KIND, either express or implied.  See the License for the
 + * specific language governing permissions and limitations
 + * under the License.
 + */
 +package org.apache.syncope.core.provisioning.camel.processor;
 +
- import java.util.Collection;
 +import java.util.List;
 +import org.apache.camel.Exchange;
 +import org.apache.camel.Processor;
- import org.apache.commons.collections4.CollectionUtils;
 +import org.apache.commons.lang3.tuple.ImmutablePair;
 +import org.apache.syncope.common.lib.patch.StatusPatch;
 +import org.apache.syncope.common.lib.types.AnyTypeKind;
++import org.apache.syncope.common.lib.types.PropagationByResource;
++import org.apache.syncope.common.lib.types.ResourceOperation;
 +import org.apache.syncope.common.lib.types.StatusPatchType;
 +import org.apache.syncope.core.misc.spring.ApplicationContextProvider;
 +import org.apache.syncope.core.persistence.api.dao.UserDAO;
 +import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
 +import org.apache.syncope.core.provisioning.api.WorkflowResult;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationException;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationManager;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
 +import org.slf4j.Logger;
 +import org.slf4j.LoggerFactory;
 +import org.springframework.beans.factory.annotation.Autowired;
 +import org.springframework.stereotype.Component;
 +
 +@Component
 +public class UserStatusPropagationProcessor implements Processor {
 +
 +    private static final Logger LOG = LoggerFactory.getLogger(UserStatusPropagationProcessor.class);
 +
 +    @Autowired
 +    protected PropagationManager propagationManager;
 +
 +    @Autowired
 +    protected PropagationTaskExecutor taskExecutor;
 +
 +    @Autowired
 +    protected UserDAO userDAO;
 +
 +    @SuppressWarnings("unchecked")
 +    @Override
 +    public void process(final Exchange exchange) {
 +        WorkflowResult<Long> updated = (WorkflowResult) exchange.getIn().getBody();
 +
-         Long key = exchange.getProperty("userKey", Long.class);
 +        StatusPatch statusPatch = exchange.getProperty("statusPatch", StatusPatch.class);
 +
-         Collection<String> noPropResourceNames = CollectionUtils.removeAll(
-                 userDAO.findAllResourceNames(userDAO.find(key)), statusPatch.getResources());
- 
++        PropagationByResource propByRes = new PropagationByResource();
++        propByRes.addAll(ResourceOperation.UPDATE, statusPatch.getResources());
 +        List<PropagationTask> tasks = propagationManager.getUpdateTasks(
 +                AnyTypeKind.USER,
 +                statusPatch.getKey(),
 +                false,
 +                statusPatch.getType() != StatusPatchType.SUSPEND,
++                propByRes,
 +                null,
-                 null,
-                 noPropResourceNames);
++                null);
++
 +        PropagationReporter propReporter =
 +                ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class);
 +        try {
 +            taskExecutor.execute(tasks, propReporter);
 +        } catch (PropagationException e) {
 +            LOG.error("Error propagation primary resource", e);
 +            propReporter.onPrimaryResourceFailure(tasks);
 +        }
 +
 +        exchange.getOut().setBody(new ImmutablePair<>(updated.getResult(), propReporter.getStatuses()));
 +    }
 +}