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 2018/09/25 12:32:05 UTC

[07/34] syncope git commit: [SYNCOPE-1369] Flowable support refactored as extension + code review (now with variable cleanup)

http://git-wip-us.apache.org/repos/asf/syncope/blob/3cabe08a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserWorkflowAdapter.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserWorkflowAdapter.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserWorkflowAdapter.java
new file mode 100644
index 0000000..f3f1880
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserWorkflowAdapter.java
@@ -0,0 +1,488 @@
+/*
+ * 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.flowable.impl;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.common.lib.patch.UserPatch;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.to.WorkflowTask;
+import org.apache.syncope.common.lib.to.WorkflowTaskExecInput;
+import org.apache.syncope.core.provisioning.api.PropagationByResource;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.core.flowable.api.UserRequestHandler;
+import org.apache.syncope.core.flowable.api.WorkflowTaskManager;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.apache.syncope.core.spring.BeanUtils;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.api.WorkflowResult;
+import org.apache.syncope.core.flowable.support.DomainProcessEngine;
+import org.apache.syncope.core.workflow.api.WorkflowException;
+import org.apache.syncope.core.workflow.java.AbstractUserWorkflowAdapter;
+import org.flowable.bpmn.model.FlowElement;
+import org.flowable.bpmn.model.Gateway;
+import org.flowable.bpmn.model.SequenceFlow;
+import org.flowable.bpmn.model.Process;
+import org.flowable.common.engine.api.FlowableException;
+import org.flowable.engine.runtime.ProcessInstance;
+import org.flowable.task.api.Task;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public class FlowableUserWorkflowAdapter extends AbstractUserWorkflowAdapter implements WorkflowTaskManager {
+
+    @Autowired
+    protected DomainProcessEngine engine;
+
+    @Autowired
+    protected UserRequestHandler userRequestHandler;
+
+    @Override
+    public String getPrefix() {
+        return "ACT_";
+    }
+
+    @Override
+    public <T> T getVariable(final String executionId, final String variableName, final Class<T> variableClass) {
+        return engine.getRuntimeService().getVariable(executionId, variableName, variableClass);
+    }
+
+    @Override
+    public void setVariable(final String executionId, final String variableName, final Object value) {
+        engine.getRuntimeService().setVariable(executionId, variableName, value);
+    }
+
+    protected User lazyLoad(final User user) {
+        // 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));
+        return user;
+    }
+
+    @Override
+    protected WorkflowResult<Pair<String, Boolean>> doCreate(
+            final UserTO userTO,
+            final boolean disablePwdPolicyCheck,
+            final Boolean enabled,
+            final boolean storePassword) {
+
+        Map<String, Object> variables = new HashMap<>();
+        variables.put(FlowableRuntimeUtils.WF_EXECUTOR, AuthContextUtils.getUsername());
+        variables.put(FlowableRuntimeUtils.USER_TO, userTO);
+        variables.put(FlowableRuntimeUtils.ENABLED, enabled);
+        variables.put(FlowableRuntimeUtils.STORE_PASSWORD, storePassword);
+
+        ProcessInstance procInst = null;
+        try {
+            procInst = engine.getRuntimeService().
+                    startProcessInstanceByKey(FlowableRuntimeUtils.WF_PROCESS_ID, variables);
+        } catch (FlowableException e) {
+            FlowableRuntimeUtils.throwException(
+                    e, "While starting " + FlowableRuntimeUtils.WF_PROCESS_ID + " instance");
+        }
+
+        engine.getRuntimeService().removeVariable(
+                procInst.getProcessInstanceId(), FlowableRuntimeUtils.WF_EXECUTOR);
+        engine.getRuntimeService().removeVariable(
+                procInst.getProcessInstanceId(), FlowableRuntimeUtils.USER_TO);
+        engine.getRuntimeService().removeVariable(
+                procInst.getProcessInstanceId(), FlowableRuntimeUtils.STORE_PASSWORD);
+
+        User user = engine.getRuntimeService().
+                getVariable(procInst.getProcessInstanceId(), FlowableRuntimeUtils.USER, User.class);
+        engine.getRuntimeService().removeVariable(
+                procInst.getProcessInstanceId(), FlowableRuntimeUtils.USER);
+
+        Boolean updatedEnabled = engine.getRuntimeService().
+                getVariable(procInst.getProcessInstanceId(), FlowableRuntimeUtils.ENABLED, Boolean.class);
+        engine.getRuntimeService().removeVariable(
+                procInst.getProcessInstanceId(), FlowableRuntimeUtils.ENABLED);
+        if (updatedEnabled != null) {
+            user.setSuspended(!updatedEnabled);
+        }
+
+        // this will make UserValidator not to consider password policies at all
+        if (disablePwdPolicyCheck) {
+            user.removeClearPassword();
+        }
+
+        FlowableRuntimeUtils.updateStatus(engine, procInst.getProcessInstanceId(), user);
+        User created = userDAO.save(user);
+
+        engine.getRuntimeService().updateBusinessKey(
+                procInst.getProcessInstanceId(), FlowableRuntimeUtils.getWFProcBusinessKey(created.getKey()));
+
+        Boolean propagateEnable = engine.getRuntimeService().getVariable(
+                procInst.getProcessInstanceId(), FlowableRuntimeUtils.PROPAGATE_ENABLE, Boolean.class);
+        engine.getRuntimeService().removeVariable(
+                procInst.getProcessInstanceId(), FlowableRuntimeUtils.PROPAGATE_ENABLE);
+        if (propagateEnable == null) {
+            propagateEnable = enabled;
+        }
+
+        PropagationByResource propByRes = new PropagationByResource();
+        propByRes.set(ResourceOperation.CREATE, userDAO.findAllResourceKeys(created.getKey()));
+
+        FlowableRuntimeUtils.saveForFormSubmit(
+                engine,
+                procInst.getProcessInstanceId(),
+                created,
+                dataBinder.getUserTO(created, true),
+                userTO.getPassword(),
+                enabled,
+                propByRes);
+
+        Set<String> tasks = FlowableRuntimeUtils.getPerformedTasks(engine, procInst.getProcessInstanceId(), user);
+
+        return new WorkflowResult<>(Pair.of(created.getKey(), propagateEnable), propByRes, tasks);
+    }
+
+    protected Set<String> doExecuteNextTask(
+            final String procInstID,
+            final User user,
+            final Map<String, Object> moreVariables) {
+
+        Set<String> preTasks = FlowableRuntimeUtils.getPerformedTasks(engine, procInstID, user);
+
+        Map<String, Object> variables = new HashMap<>();
+        variables.put(FlowableRuntimeUtils.WF_EXECUTOR, AuthContextUtils.getUsername());
+        variables.put(FlowableRuntimeUtils.USER, lazyLoad(user));
+
+        if (moreVariables != null && !moreVariables.isEmpty()) {
+            variables.putAll(moreVariables);
+        }
+
+        List<Task> tasks = FlowableRuntimeUtils.createTaskQuery(engine, false).processInstanceId(procInstID).list();
+        String task = null;
+        if (tasks.size() == 1) {
+            try {
+                engine.getTaskService().complete(tasks.get(0).getId(), variables);
+                task = tasks.get(0).getTaskDefinitionKey();
+            } catch (FlowableException e) {
+                FlowableRuntimeUtils.throwException(
+                        e, "While completing task '" + tasks.get(0).getName() + "' for " + user);
+            }
+        } else {
+            LOG.warn("Expected a single task, found {}", tasks.size());
+        }
+
+        Set<String> postTasks = FlowableRuntimeUtils.getPerformedTasks(engine, procInstID, user);
+        postTasks.removeAll(preTasks);
+        if (task != null) {
+            postTasks.add(task);
+        }
+
+        return postTasks;
+    }
+
+    @Override
+    protected WorkflowResult<String> doActivate(final User user, final String token) {
+        String procInstID = FlowableRuntimeUtils.getWFProcInstID(engine, user.getKey());
+
+        Map<String, Object> variables = new HashMap<>(2);
+        variables.put(FlowableRuntimeUtils.TOKEN, token);
+        variables.put(FlowableRuntimeUtils.TASK, "activate");
+
+        Set<String> tasks = doExecuteNextTask(procInstID, user, variables);
+
+        FlowableRuntimeUtils.updateStatus(engine, procInstID, user);
+        User updated = userDAO.save(user);
+
+        variables.keySet().forEach(key -> engine.getRuntimeService().removeVariable(procInstID, key));
+        engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.USER);
+        engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.WF_EXECUTOR);
+
+        return new WorkflowResult<>(updated.getKey(), null, tasks);
+    }
+
+    @Override
+    protected WorkflowResult<Pair<UserPatch, Boolean>> doUpdate(final User user, final UserPatch userPatch) {
+        String procInstID = FlowableRuntimeUtils.getWFProcInstID(engine, user.getKey());
+
+        // save some existing variable values for later processing, after actual update is made 
+        UserPatch patchBeforeUpdate = engine.getRuntimeService().getVariable(
+                procInstID, FlowableRuntimeUtils.USER_PATCH, UserPatch.class);
+        PropagationByResource propByResBeforeUpdate = engine.getRuntimeService().getVariable(
+                procInstID, FlowableRuntimeUtils.PROP_BY_RESOURCE, PropagationByResource.class);
+
+        // whether the initial status is a form task
+        boolean inFormTask = FlowableRuntimeUtils.getFormTask(engine, procInstID) != null;
+
+        Map<String, Object> variables = new HashMap<>(2);
+        variables.put(FlowableRuntimeUtils.USER_PATCH, userPatch);
+        variables.put(FlowableRuntimeUtils.TASK, "update");
+
+        Set<String> tasks = doExecuteNextTask(procInstID, user, variables);
+
+        FlowableRuntimeUtils.updateStatus(engine, procInstID, user);
+        User updated = userDAO.save(user);
+
+        engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.USER);
+        engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.WF_EXECUTOR);
+        engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.TASK);
+
+        // if the original status was a form task, restore the patch as before the process started
+        if (inFormTask) {
+            if (patchBeforeUpdate == null) {
+                engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.USER_PATCH);
+            } else {
+                engine.getRuntimeService().setVariable(procInstID, FlowableRuntimeUtils.USER_PATCH, patchBeforeUpdate);
+            }
+        }
+
+        // whether the after status is a form task
+        inFormTask = FlowableRuntimeUtils.getFormTask(engine, procInstID) != null;
+        if (!inFormTask) {
+            engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.USER_PATCH);
+        }
+
+        PropagationByResource propByRes = engine.getRuntimeService().getVariable(
+                procInstID, FlowableRuntimeUtils.PROP_BY_RESOURCE, PropagationByResource.class);
+        engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.PROP_BY_RESOURCE);
+
+        FlowableRuntimeUtils.saveForFormSubmit(
+                engine,
+                procInstID,
+                updated,
+                dataBinder.getUserTO(updated, true),
+                userPatch.getPassword() == null ? null : userPatch.getPassword().getValue(),
+                null,
+                propByResBeforeUpdate == null ? propByRes : propByResBeforeUpdate);
+
+        Boolean propagateEnable = engine.getRuntimeService().getVariable(
+                procInstID, FlowableRuntimeUtils.PROPAGATE_ENABLE, Boolean.class);
+        engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.PROPAGATE_ENABLE);
+
+        return new WorkflowResult<>(Pair.of(userPatch, propagateEnable), propByRes, tasks);
+    }
+
+    @Override
+    protected WorkflowResult<String> doSuspend(final User user) {
+        String procInstID = FlowableRuntimeUtils.getWFProcInstID(engine, user.getKey());
+
+        Set<String> performedTasks =
+                doExecuteNextTask(procInstID, user, Collections.singletonMap(FlowableRuntimeUtils.TASK, "suspend"));
+        FlowableRuntimeUtils.updateStatus(engine, procInstID, user);
+        User updated = userDAO.save(user);
+
+        engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.TASK);
+        engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.USER);
+        engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.WF_EXECUTOR);
+
+        return new WorkflowResult<>(updated.getKey(), null, performedTasks);
+    }
+
+    @Override
+    protected WorkflowResult<String> doReactivate(final User user) {
+        String procInstID = FlowableRuntimeUtils.getWFProcInstID(engine, user.getKey());
+
+        Set<String> performedTasks =
+                doExecuteNextTask(procInstID, user, Collections.singletonMap(FlowableRuntimeUtils.TASK, "reactivate"));
+        FlowableRuntimeUtils.updateStatus(engine, procInstID, user);
+
+        User updated = userDAO.save(user);
+
+        engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.TASK);
+        engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.USER);
+        engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.WF_EXECUTOR);
+
+        return new WorkflowResult<>(updated.getKey(), null, performedTasks);
+    }
+
+    @Override
+    protected void doRequestPasswordReset(final User user) {
+        Map<String, Object> variables = new HashMap<>(3);
+        variables.put(FlowableRuntimeUtils.USER_TO, dataBinder.getUserTO(user, true));
+        variables.put(FlowableRuntimeUtils.TASK, "requestPasswordReset");
+        variables.put(FlowableRuntimeUtils.EVENT, "requestPasswordReset");
+
+        String procInstID = FlowableRuntimeUtils.getWFProcInstID(engine, user.getKey());
+
+        doExecuteNextTask(procInstID, user, variables);
+        userDAO.save(user);
+
+        variables.keySet().forEach(key -> engine.getRuntimeService().removeVariable(procInstID, key));
+        engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.USER);
+        engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.WF_EXECUTOR);
+    }
+
+    @Override
+    protected WorkflowResult<Pair<UserPatch, Boolean>> doConfirmPasswordReset(
+            final User user, final String token, final String password) {
+
+        Map<String, Object> variables = new HashMap<>(5);
+        variables.put(FlowableRuntimeUtils.TOKEN, token);
+        variables.put(FlowableRuntimeUtils.PASSWORD, password);
+        variables.put(FlowableRuntimeUtils.USER_TO, dataBinder.getUserTO(user, true));
+        variables.put(FlowableRuntimeUtils.TASK, "confirmPasswordReset");
+        variables.put(FlowableRuntimeUtils.EVENT, "confirmPasswordReset");
+
+        String procInstID = FlowableRuntimeUtils.getWFProcInstID(engine, user.getKey());
+
+        Set<String> tasks = doExecuteNextTask(procInstID, user, variables);
+
+        userDAO.save(user);
+
+        variables.keySet().forEach(key -> engine.getRuntimeService().removeVariable(procInstID, key));
+        engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.USER);
+        engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.WF_EXECUTOR);
+
+        PropagationByResource propByRes = engine.getRuntimeService().getVariable(
+                procInstID, FlowableRuntimeUtils.PROP_BY_RESOURCE, PropagationByResource.class);
+        engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.PROP_BY_RESOURCE);
+        UserPatch updatedPatch = engine.getRuntimeService().getVariable(
+                procInstID, FlowableRuntimeUtils.USER_PATCH, UserPatch.class);
+        engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.USER_PATCH);
+        Boolean propagateEnable = engine.getRuntimeService().getVariable(
+                procInstID, FlowableRuntimeUtils.PROPAGATE_ENABLE, Boolean.class);
+        engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.PROPAGATE_ENABLE);
+
+        return new WorkflowResult<>(Pair.of(updatedPatch, propagateEnable), propByRes, tasks);
+    }
+
+    @Override
+    protected void doDelete(final User user) {
+        String procInstID = FlowableRuntimeUtils.getWFProcInstID(engine, user.getKey());
+
+        doExecuteNextTask(procInstID, user, Collections.singletonMap(FlowableRuntimeUtils.TASK, "delete"));
+
+        PropagationByResource propByRes = new PropagationByResource();
+        propByRes.set(ResourceOperation.DELETE, userDAO.findAllResourceKeys(user.getKey()));
+
+        if (engine.getRuntimeService().createProcessInstanceQuery().
+                processInstanceId(procInstID).active().list().isEmpty()) {
+
+            userDAO.delete(user.getKey());
+
+            if (!engine.getHistoryService().createHistoricProcessInstanceQuery().
+                    processInstanceId(procInstID).list().isEmpty()) {
+
+                engine.getHistoryService().deleteHistoricProcessInstance(procInstID);
+            }
+        } else {
+            FlowableRuntimeUtils.saveForFormSubmit(
+                    engine,
+                    procInstID,
+                    user,
+                    dataBinder.getUserTO(user, true),
+                    null,
+                    null,
+                    propByRes);
+
+            FlowableRuntimeUtils.updateStatus(engine, procInstID, user);
+            userDAO.save(user);
+
+            engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.TASK);
+            engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.USER);
+            engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.WF_EXECUTOR);
+        }
+    }
+
+    @Override
+    public WorkflowResult<String> executeNextTask(final WorkflowTaskExecInput workflowTaskExecInput) {
+        User user = userDAO.authFind(workflowTaskExecInput.getUserKey());
+
+        String procInstID = FlowableRuntimeUtils.getWFProcInstID(engine, user.getKey());
+
+        Map<String, Object> variables = new HashMap<>();
+        variables.put(FlowableRuntimeUtils.USER_TO, dataBinder.getUserTO(user, true));
+        variables.putAll(workflowTaskExecInput.getVariables());
+
+        Set<String> performedTasks = doExecuteNextTask(procInstID, user, variables);
+        FlowableRuntimeUtils.updateStatus(engine, procInstID, user);
+        user = userDAO.save(user);
+
+        engine.getRuntimeService().setVariable(
+                procInstID, FlowableRuntimeUtils.USER_TO, dataBinder.getUserTO(user, true));
+
+        if (engine.getRuntimeService().createProcessInstanceQuery().
+                processInstanceId(procInstID).active().list().isEmpty()) {
+
+            userDAO.delete(user.getKey());
+
+            if (!engine.getHistoryService().createHistoricProcessInstanceQuery().
+                    processInstanceId(procInstID).list().isEmpty()) {
+
+                engine.getHistoryService().deleteHistoricProcessInstance(procInstID);
+            }
+        } else {
+            PropagationByResource propByRes = engine.getRuntimeService().
+                    getVariable(procInstID, FlowableRuntimeUtils.PROP_BY_RESOURCE, PropagationByResource.class);
+
+            FlowableRuntimeUtils.saveForFormSubmit(
+                    engine,
+                    procInstID,
+                    user,
+                    dataBinder.getUserTO(user, true),
+                    null,
+                    null,
+                    propByRes);
+        }
+
+        return new WorkflowResult<>(user.getKey(), null, performedTasks);
+    }
+
+    protected void navigateAvailableTasks(final FlowElement flow, final List<String> availableTasks) {
+        if (flow instanceof Gateway) {
+            ((Gateway) flow).getOutgoingFlows().forEach(subflow -> navigateAvailableTasks(subflow, availableTasks));
+        } else if (flow instanceof SequenceFlow) {
+            navigateAvailableTasks(((SequenceFlow) flow).getTargetFlowElement(), availableTasks);
+        } else if (flow instanceof org.flowable.bpmn.model.Task) {
+            availableTasks.add(flow.getId());
+        } else {
+            LOG.debug("Unexpected flow found: {}", flow);
+        }
+    }
+
+    @Override
+    public List<WorkflowTask> getAvailableTasks(final String userKey) {
+        String procInstID = FlowableRuntimeUtils.getWFProcInstID(engine, userKey);
+
+        List<String> availableTasks = new ArrayList<>();
+        try {
+            Task currentTask = FlowableRuntimeUtils.createTaskQuery(engine, false).
+                    processInstanceId(procInstID).singleResult();
+
+            Process process = engine.getRepositoryService().
+                    getBpmnModel(FlowableRuntimeUtils.getLatestProcDefByKey(
+                            engine, FlowableRuntimeUtils.WF_PROCESS_ID).getId()).getProcesses().get(0);
+            process.getFlowElements().stream().
+                    filter(SequenceFlow.class::isInstance).
+                    map(SequenceFlow.class::cast).
+                    filter(sequenceFlow -> sequenceFlow.getSourceRef().equals(currentTask.getTaskDefinitionKey())).
+                    forEach(sequenceFlow -> {
+                        navigateAvailableTasks(sequenceFlow.getTargetFlowElement(), availableTasks);
+                    });
+        } catch (FlowableException e) {
+            throw new WorkflowException(
+                    "While reading available tasks for workflow instance " + procInstID, e);
+        }
+
+        return availableTasks.stream().map(input -> {
+            WorkflowTask workflowTaskTO = new WorkflowTask();
+            workflowTaskTO.setName(input);
+            return workflowTaskTO;
+        }).collect(Collectors.toList());
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/3cabe08a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableWorkflowUtils.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableWorkflowUtils.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableWorkflowUtils.java
new file mode 100644
index 0000000..d5fcd41
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableWorkflowUtils.java
@@ -0,0 +1,47 @@
+/*
+ * 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.flowable.impl;
+
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.core.flowable.support.DomainProcessEngine;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+public class FlowableWorkflowUtils {
+
+    @Autowired
+    protected DomainProcessEngine engine;
+
+    @Transactional(readOnly = true)
+    public boolean isUserIngroup(final UserTO user, final String groupName) {
+        return user.getMemberships().stream().
+                anyMatch(membership -> groupName != null && groupName.equals(membership.getGroupName()));
+    }
+
+    public void setUserWorkflowVariable(final String variableName, final Object value, final User user) {
+        engine.getRuntimeService().
+                setVariable(FlowableRuntimeUtils.getWFProcInstID(engine, user.getKey()), variableName, value);
+    }
+
+    public Object getUserWorkflowVariable(final String variableName, final User user) {
+        return engine.getRuntimeService().
+                getVariable(FlowableRuntimeUtils.getWFProcInstID(engine, user.getKey()), variableName);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/3cabe08a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/SyncopeTaskQueryImpl.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/SyncopeTaskQueryImpl.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/SyncopeTaskQueryImpl.java
new file mode 100644
index 0000000..eb36838
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/SyncopeTaskQueryImpl.java
@@ -0,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.flowable.impl;
+
+import org.flowable.common.engine.impl.interceptor.CommandExecutor;
+import org.flowable.task.api.TaskQuery;
+import org.flowable.task.service.impl.TaskQueryImpl;
+
+public class SyncopeTaskQueryImpl extends TaskQueryImpl {
+
+    private static final long serialVersionUID = 734215641378485689L;
+
+    protected boolean withFormKey;
+
+    protected SyncopeTaskQueryImpl currentOrQueryObject;
+
+    public SyncopeTaskQueryImpl(final CommandExecutor commandExecutor) {
+        super(commandExecutor);
+    }
+
+    public TaskQuery taskWithFormKey() {
+        if (orActive) {
+            currentOrQueryObject.withFormKey = true;
+        } else {
+            this.withFormKey = true;
+        }
+        return this;
+    }
+
+    public boolean isWithFormKey() {
+        return withFormKey;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/3cabe08a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DomainProcessEngine.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DomainProcessEngine.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DomainProcessEngine.java
new file mode 100644
index 0000000..1b056c1
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DomainProcessEngine.java
@@ -0,0 +1,114 @@
+/*
+ * 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.flowable.support;
+
+import java.util.Collections;
+import java.util.Map;
+import javax.sql.DataSource;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.flowable.engine.DynamicBpmnService;
+import org.flowable.engine.FormService;
+import org.flowable.engine.HistoryService;
+import org.flowable.engine.IdentityService;
+import org.flowable.engine.ManagementService;
+import org.flowable.engine.ProcessEngine;
+import org.flowable.engine.ProcessEngineConfiguration;
+import org.flowable.engine.RepositoryService;
+import org.flowable.engine.RuntimeService;
+import org.flowable.engine.TaskService;
+import org.flowable.engine.impl.ProcessEngineImpl;
+
+/**
+ * {@link ProcessEngine} delegating actual method invocation to the inner map of {@link ProcessEngine} instances,
+ * one for each Syncope domain.
+ */
+public class DomainProcessEngine implements ProcessEngine {
+
+    private final Map<String, ProcessEngine> engines;
+
+    public DomainProcessEngine(final Map<String, ProcessEngine> engines) {
+        this.engines = Collections.synchronizedMap(engines);
+    }
+
+    public Map<String, ProcessEngine> getEngines() {
+        return engines;
+    }
+
+    @Override
+    public String getName() {
+        return engines.get(AuthContextUtils.getDomain()).getName();
+    }
+
+    @Override
+    public void close() {
+        engines.values().forEach(engine -> {
+            engine.close();
+        });
+    }
+
+    @Override
+    public RepositoryService getRepositoryService() {
+        return engines.get(AuthContextUtils.getDomain()).getRepositoryService();
+    }
+
+    @Override
+    public RuntimeService getRuntimeService() {
+        return engines.get(AuthContextUtils.getDomain()).getRuntimeService();
+    }
+
+    @Override
+    public FormService getFormService() {
+        return engines.get(AuthContextUtils.getDomain()).getFormService();
+    }
+
+    @Override
+    public TaskService getTaskService() {
+        return engines.get(AuthContextUtils.getDomain()).getTaskService();
+    }
+
+    @Override
+    public HistoryService getHistoryService() {
+        return engines.get(AuthContextUtils.getDomain()).getHistoryService();
+    }
+
+    @Override
+    public IdentityService getIdentityService() {
+        return engines.get(AuthContextUtils.getDomain()).getIdentityService();
+    }
+
+    @Override
+    public ManagementService getManagementService() {
+        return engines.get(AuthContextUtils.getDomain()).getManagementService();
+    }
+
+    @Override
+    public ProcessEngineConfiguration getProcessEngineConfiguration() {
+        return engines.get(AuthContextUtils.getDomain()).getProcessEngineConfiguration();
+    }
+
+    @Override
+    public DynamicBpmnService getDynamicBpmnService() {
+        return engines.get(AuthContextUtils.getDomain()).getDynamicBpmnService();
+    }
+
+    public DataSource getDataSource() {
+        ProcessEngineImpl engine = (ProcessEngineImpl) engines.get(AuthContextUtils.getDomain());
+        return engine.getProcessEngineConfiguration().getDataSource();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/3cabe08a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DomainProcessEngineConfiguration.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DomainProcessEngineConfiguration.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DomainProcessEngineConfiguration.java
new file mode 100644
index 0000000..d1c3299
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DomainProcessEngineConfiguration.java
@@ -0,0 +1,32 @@
+/*
+ * 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.flowable.support;
+
+import java.io.InputStream;
+import org.flowable.spring.SpringProcessEngineConfiguration;
+
+public class DomainProcessEngineConfiguration extends SpringProcessEngineConfiguration {
+
+    private static final String SYNCOPE_MYBATIS_MAPPING_FILE = "org/apache/syncope/ext/flowable/mappings.xml";
+
+    @Override
+    public InputStream getMyBatisXmlConfigurationStream() {
+        return getResourceAsStream(SYNCOPE_MYBATIS_MAPPING_FILE);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/3cabe08a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DomainProcessEngineFactoryBean.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DomainProcessEngineFactoryBean.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DomainProcessEngineFactoryBean.java
new file mode 100644
index 0000000..162b113
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DomainProcessEngineFactoryBean.java
@@ -0,0 +1,112 @@
+/*
+ * 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.flowable.support;
+
+import java.util.HashMap;
+import java.util.Map;
+import javax.sql.DataSource;
+import org.apache.commons.lang3.StringUtils;
+import org.flowable.engine.ProcessEngine;
+import org.flowable.common.engine.impl.cfg.SpringBeanFactoryProxyMap;
+import org.flowable.common.engine.impl.interceptor.EngineConfigurationConstants;
+import org.flowable.engine.impl.util.EngineServiceUtil;
+import org.flowable.idm.spring.SpringIdmEngineConfiguration;
+import org.flowable.spring.SpringExpressionManager;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.PlatformTransactionManager;
+
+/**
+ * Spring factory for {@link DomainProcessEngine} which takes the provided {@link DomainProcessEngineConfiguration} as
+ * template for each of the configured Syncope domains.
+ */
+@Component
+public class DomainProcessEngineFactoryBean
+        implements FactoryBean<DomainProcessEngine>, DisposableBean, ApplicationContextAware {
+
+    private ApplicationContext ctx;
+
+    private DomainProcessEngine engine;
+
+    @Override
+    public void setApplicationContext(final ApplicationContext ctx) throws BeansException {
+        this.ctx = ctx;
+    }
+
+    @Override
+    public DomainProcessEngine getObject() throws Exception {
+        if (engine == null) {
+            Map<String, ProcessEngine> engines = new HashMap<>();
+
+            ctx.getBeansOfType(DataSource.class).forEach((key, dataSource) -> {
+                if (!key.startsWith("local")) {
+                    String domain = StringUtils.substringBefore(key, DataSource.class.getSimpleName());
+                    PlatformTransactionManager transactionManager = ctx.getBean(
+                            domain + "TransactionManager", PlatformTransactionManager.class);
+                    Object entityManagerFactory = ctx.getBean(domain + "EntityManagerFactory");
+
+                    DomainProcessEngineConfiguration conf = ctx.getBean(DomainProcessEngineConfiguration.class);
+                    conf.setDataSource(dataSource);
+                    conf.setTransactionManager(transactionManager);
+                    conf.setTransactionsExternallyManaged(true);
+                    conf.setJpaEntityManagerFactory(entityManagerFactory);
+                    if (conf.getBeans() == null) {
+                        conf.setBeans(new SpringBeanFactoryProxyMap(ctx));
+                    }
+                    if (conf.getExpressionManager() == null) {
+                        conf.setExpressionManager(new SpringExpressionManager(ctx, conf.getBeans()));
+                    }
+                    if (EngineServiceUtil.getIdmEngineConfiguration(conf) == null) {
+                        conf.addEngineConfiguration(
+                                EngineConfigurationConstants.KEY_IDM_ENGINE_CONFIG,
+                                ctx.getBean(SpringIdmEngineConfiguration.class));
+                    }
+
+                    engines.put(domain, conf.buildProcessEngine());
+                }
+            });
+
+            engine = new DomainProcessEngine(engines);
+        }
+
+        return engine;
+    }
+
+    @Override
+    public Class<DomainProcessEngine> getObjectType() {
+        return DomainProcessEngine.class;
+    }
+
+    @Override
+    public boolean isSingleton() {
+        return true;
+    }
+
+    @Override
+    public void destroy() throws Exception {
+        if (engine != null) {
+            engine.close();
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/3cabe08a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeEntitiesVariableType.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeEntitiesVariableType.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeEntitiesVariableType.java
new file mode 100644
index 0000000..9009abb
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeEntitiesVariableType.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.flowable.support;
+
+import org.apache.syncope.core.persistence.api.entity.Entity;
+import org.flowable.variable.service.impl.types.SerializableType;
+
+/**
+ * Flowable variable type for handling Syncope entities as Flowable variables.
+ * Main purpose: avoid Flowable to handle Syncope entities as JPA entities,
+ * since this can cause troubles with transactions.
+ */
+public class SyncopeEntitiesVariableType extends SerializableType {
+
+    @Override
+    public boolean isAbleToStore(final Object value) {
+        return value instanceof Entity;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/3cabe08a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeGroupQueryImpl.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeGroupQueryImpl.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeGroupQueryImpl.java
new file mode 100644
index 0000000..e7437dd
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeGroupQueryImpl.java
@@ -0,0 +1,89 @@
+/*
+ * 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.flowable.support;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.flowable.idm.api.Group;
+import org.flowable.idm.engine.impl.GroupQueryImpl;
+import org.flowable.idm.engine.impl.persistence.entity.GroupEntity;
+import org.flowable.idm.engine.impl.persistence.entity.GroupEntityImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+public class SyncopeGroupQueryImpl extends GroupQueryImpl {
+
+    private static final long serialVersionUID = -2595069675443343682L;
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Autowired
+    private GroupDAO groupDAO;
+
+    private List<Group> result;
+
+    private Group fromSyncopeGroup(final String name) {
+        GroupEntity group = new GroupEntityImpl();
+        group.setId(name);
+        return group;
+    }
+
+    private void execute() {
+        if (id != null) {
+            org.apache.syncope.core.persistence.api.entity.group.Group syncopeGroup = groupDAO.findByName(id);
+            if (syncopeGroup == null) {
+                result = Collections.emptyList();
+            } else {
+                result = Collections.singletonList(fromSyncopeGroup(syncopeGroup.getName()));
+            }
+        } else if (userId != null) {
+            result = userDAO.findAllGroupNames(userDAO.findByUsername(userId)).stream().
+                    map(groupName -> fromSyncopeGroup(groupName)).
+                    collect(Collectors.toList());
+        }
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public long count() {
+        checkQueryOk();
+
+        this.resultType = ResultType.COUNT;
+        if (result == null) {
+            execute();
+        }
+        return result.size();
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public List<Group> list() {
+        checkQueryOk();
+
+        this.resultType = ResultType.LIST;
+        if (result == null) {
+            execute();
+        }
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/3cabe08a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeIdmIdentityService.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeIdmIdentityService.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeIdmIdentityService.java
new file mode 100644
index 0000000..6b9c4f6
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeIdmIdentityService.java
@@ -0,0 +1,51 @@
+/*
+ * 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.flowable.support;
+
+import org.flowable.idm.api.GroupQuery;
+import org.flowable.idm.api.UserQuery;
+import org.flowable.idm.engine.impl.IdmIdentityServiceImpl;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.ConfigurableApplicationContext;
+
+public class SyncopeIdmIdentityService extends IdmIdentityServiceImpl implements ApplicationContextAware {
+
+    private ConfigurableApplicationContext ctx;
+
+    @Override
+    public void setApplicationContext(final ApplicationContext ctx) throws BeansException {
+        this.ctx = (ConfigurableApplicationContext) ctx;
+    }
+
+    @Override
+    public UserQuery createUserQuery() {
+        return (UserQuery) ctx.getBeanFactory().
+                createBean(SyncopeUserQueryImpl.class, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
+    }
+
+    @Override
+    public GroupQuery createGroupQuery() {
+        return (GroupQuery) ctx.getBeanFactory().
+                createBean(SyncopeGroupQueryImpl.class, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/3cabe08a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeSession.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeSession.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeSession.java
new file mode 100644
index 0000000..56e6c88
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeSession.java
@@ -0,0 +1,26 @@
+/*
+ * 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.flowable.support;
+
+import org.flowable.common.engine.impl.interceptor.Session;
+
+public interface SyncopeSession extends Session {
+
+    Class<?> getType();
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/3cabe08a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeSessionFactory.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeSessionFactory.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeSessionFactory.java
new file mode 100644
index 0000000..0c1789b
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeSessionFactory.java
@@ -0,0 +1,46 @@
+/*
+ * 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.flowable.support;
+
+import org.flowable.common.engine.impl.interceptor.CommandContext;
+import org.flowable.common.engine.impl.interceptor.Session;
+import org.flowable.common.engine.impl.interceptor.SessionFactory;
+
+public class SyncopeSessionFactory implements SessionFactory {
+
+    private SyncopeSession syncopeSession;
+
+    @Override
+    public Class<?> getSessionType() {
+        return syncopeSession.getType();
+    }
+
+    @Override
+    public Session openSession(final CommandContext cc) {
+        return syncopeSession;
+    }
+
+    public SyncopeSession getSyncopeSession() {
+        return syncopeSession;
+    }
+
+    public void setSyncopeSession(final SyncopeSession syncopeSession) {
+        this.syncopeSession = syncopeSession;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/3cabe08a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeUserQueryImpl.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeUserQueryImpl.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeUserQueryImpl.java
new file mode 100644
index 0000000..25d9ab5
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeUserQueryImpl.java
@@ -0,0 +1,101 @@
+/*
+ * 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.flowable.support;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.group.Group;
+import org.apache.syncope.core.persistence.api.entity.user.UMembership;
+import org.flowable.idm.api.User;
+import org.flowable.idm.engine.impl.UserQueryImpl;
+import org.flowable.idm.engine.impl.persistence.entity.UserEntity;
+import org.flowable.idm.engine.impl.persistence.entity.UserEntityImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+public class SyncopeUserQueryImpl extends UserQueryImpl {
+
+    private static final long serialVersionUID = 4403344392227706318L;
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Autowired
+    private GroupDAO groupDAO;
+
+    private List<User> result;
+
+    private User fromSyncopeUser(final org.apache.syncope.core.persistence.api.entity.user.User syncopeUser) {
+        UserEntity user = new UserEntityImpl();
+        user.setId(syncopeUser.getUsername());
+        return user;
+    }
+
+    private void execute() {
+        if (id != null) {
+            org.apache.syncope.core.persistence.api.entity.user.User user = userDAO.findByUsername(id);
+            if (user == null) {
+                result = Collections.<User>emptyList();
+            } else if (groupId == null || userDAO.findAllGroupNames(user).contains(groupId)) {
+                result = Collections.singletonList(fromSyncopeUser(user));
+            }
+        } else if (groupId != null) {
+            Group group = groupDAO.findByName(groupId);
+            if (group == null) {
+                result = Collections.<User>emptyList();
+            } else {
+                result = new ArrayList<>();
+                List<UMembership> memberships = groupDAO.findUMemberships(group);
+                memberships.stream().map(membership -> fromSyncopeUser(membership.getLeftEnd())).
+                        filter(user -> (!result.contains(user))).
+                        forEachOrdered(user -> {
+                            result.add(user);
+                        });
+            }
+        }
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public long count() {
+        checkQueryOk();
+
+        this.resultType = ResultType.COUNT;
+        if (result == null) {
+            execute();
+        }
+        return result.size();
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public List<User> list() {
+        checkQueryOk();
+
+        this.resultType = ResultType.LIST;
+        if (result == null) {
+            execute();
+        }
+        return result;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/3cabe08a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/AbstractFlowableServiceTask.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/AbstractFlowableServiceTask.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/AbstractFlowableServiceTask.java
new file mode 100644
index 0000000..692a161
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/AbstractFlowableServiceTask.java
@@ -0,0 +1,45 @@
+/*
+ * 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.flowable.task;
+
+import org.flowable.engine.ProcessEngine;
+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;
+
+/**
+ * Abstract base class for Flowable's service tasks in Syncope, with Spring support.
+ */
+@Component
+public abstract class AbstractFlowableServiceTask {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(AbstractFlowableServiceTask.class);
+
+    @Autowired
+    protected ProcessEngine engine;
+
+    @Transactional(rollbackFor = { Throwable.class })
+    public void execute(final String executionId) {
+        doExecute(executionId);
+    }
+
+    protected abstract void doExecute(final String executionId);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/3cabe08a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/AutoActivate.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/AutoActivate.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/AutoActivate.java
new file mode 100644
index 0000000..3febfa0
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/AutoActivate.java
@@ -0,0 +1,60 @@
+/*
+ * 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.flowable.task;
+
+import org.apache.syncope.common.lib.AnyOperations;
+import org.apache.syncope.common.lib.patch.UserPatch;
+import org.apache.syncope.common.lib.to.UserTO;
+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.flowable.impl.FlowableRuntimeUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AutoActivate extends AbstractFlowableServiceTask {
+
+    @Autowired
+    private UserDataBinder dataBinder;
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Override
+    protected void doExecute(final String executionId) {
+        User user = engine.getRuntimeService().
+                getVariable(executionId, FlowableRuntimeUtils.USER, User.class);
+        UserTO userTO = engine.getRuntimeService().
+                getVariable(executionId, FlowableRuntimeUtils.USER_TO, UserTO.class);
+        if (userTO != null && userTO.getKey() != null && user.getKey() != null) {
+            user = userDAO.save(user);
+
+            UserPatch userPatch = AnyOperations.diff(userTO, dataBinder.getUserTO(user, true), false);
+            // don't mess with password, as the cleartext values was already properly saved
+            userPatch.setPassword(null);
+
+            dataBinder.update(user, userPatch);
+
+            engine.getRuntimeService().setVariable(executionId, FlowableRuntimeUtils.USER, user);
+        }
+
+        engine.getRuntimeService().setVariable(executionId, FlowableRuntimeUtils.PROPAGATE_ENABLE, Boolean.TRUE);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/3cabe08a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Create.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Create.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Create.java
new file mode 100644
index 0000000..75bf0ac
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Create.java
@@ -0,0 +1,55 @@
+/*
+ * 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.flowable.task;
+
+import org.apache.syncope.common.lib.to.UserTO;
+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.data.UserDataBinder;
+import org.apache.syncope.core.flowable.impl.FlowableRuntimeUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class Create extends AbstractFlowableServiceTask {
+
+    @Autowired
+    private UserDataBinder dataBinder;
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    @Override
+    protected void doExecute(final String executionId) {
+        UserTO userTO = engine.getRuntimeService().
+                getVariable(executionId, FlowableRuntimeUtils.USER_TO, UserTO.class);
+        Boolean storePassword = engine.getRuntimeService().
+                getVariable(executionId, FlowableRuntimeUtils.STORE_PASSWORD, Boolean.class);
+
+        // create user
+        User user = entityFactory.newEntity(User.class);
+        dataBinder.create(user, userTO, storePassword == null ? true : storePassword);
+
+        // report user as result
+        engine.getRuntimeService().
+                setVariable(executionId, FlowableRuntimeUtils.USER, user);
+        engine.getRuntimeService().
+                setVariable(executionId, FlowableRuntimeUtils.USER_TO, dataBinder.getUserTO(user, true));
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/3cabe08a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Delete.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Delete.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Delete.java
new file mode 100644
index 0000000..7f3acf6
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Delete.java
@@ -0,0 +1,41 @@
+/*
+ * 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.flowable.task;
+
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.flowable.impl.FlowableRuntimeUtils;
+import org.springframework.stereotype.Component;
+
+@Component
+public class Delete extends AbstractFlowableServiceTask {
+
+    @Override
+    protected void doExecute(final String executionId) {
+        User user = engine.getRuntimeService().
+                getVariable(executionId, FlowableRuntimeUtils.USER, User.class);
+
+        // Do something with user...
+        if (user != null) {
+            user.checkToken("");
+        }
+
+        // remove user variable
+        engine.getRuntimeService().removeVariable(executionId, FlowableRuntimeUtils.USER);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/3cabe08a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/GenerateToken.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/GenerateToken.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/GenerateToken.java
new file mode 100644
index 0000000..aee2b73
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/GenerateToken.java
@@ -0,0 +1,44 @@
+/*
+ * 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.flowable.task;
+
+import org.apache.syncope.core.persistence.api.dao.ConfDAO;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.flowable.impl.FlowableRuntimeUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class GenerateToken extends AbstractFlowableServiceTask {
+
+    @Autowired
+    private ConfDAO confDAO;
+
+    @Override
+    protected void doExecute(final String executionId) {
+        User user = engine.getRuntimeService().
+                getVariable(executionId, FlowableRuntimeUtils.USER, User.class);
+
+        user.generateToken(
+                confDAO.find("token.length", 256L).intValue(),
+                confDAO.find("token.expireTime", 60L).intValue());
+
+        engine.getRuntimeService().setVariable(executionId, FlowableRuntimeUtils.USER, user);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/3cabe08a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Notify.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Notify.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Notify.java
new file mode 100644
index 0000000..d64032f
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Notify.java
@@ -0,0 +1,64 @@
+/*
+ * 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.flowable.task;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
+import org.apache.syncope.core.flowable.impl.FlowableRuntimeUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * General-purpose notification task for usage within workflow.
+ * It requires a pre-existing Notification with category {@code CUSTOM} and result {@code SUCCESS}.
+ * An {@code event} workflow variable needs to be provided as well.
+ */
+@Component
+public class Notify extends AbstractFlowableServiceTask {
+
+    @Autowired
+    private NotificationManager notificationManager;
+
+    @Override
+    protected void doExecute(final String executionId) {
+        User user = engine.getRuntimeService().
+                getVariable(executionId, FlowableRuntimeUtils.USER, User.class);
+        UserTO userTO = engine.getRuntimeService().
+                getVariable(executionId, FlowableRuntimeUtils.USER_TO, UserTO.class);
+        String event = engine.getRuntimeService().
+                getVariable(executionId, FlowableRuntimeUtils.EVENT, String.class);
+
+        if (StringUtils.isNotBlank(event)) {
+            notificationManager.createTasks(
+                    AuditElements.EventCategoryType.CUSTOM,
+                    null,
+                    null,
+                    event,
+                    AuditElements.Result.SUCCESS,
+                    userTO,
+                    null,
+                    user.getToken());
+        } else {
+            LOG.debug("Not sending any notification since no event was found");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/3cabe08a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/PasswordReset.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/PasswordReset.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/PasswordReset.java
new file mode 100644
index 0000000..1c36202
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/PasswordReset.java
@@ -0,0 +1,70 @@
+/*
+ * 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.flowable.task;
+
+import org.apache.syncope.common.lib.patch.PasswordPatch;
+import org.apache.syncope.common.lib.patch.UserPatch;
+import org.apache.syncope.core.provisioning.api.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.flowable.impl.FlowableRuntimeUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class PasswordReset extends AbstractFlowableServiceTask {
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Autowired
+    private UserDataBinder dataBinder;
+
+    @Override
+    protected void doExecute(final String executionId) {
+        User user = engine.getRuntimeService().
+                getVariable(executionId, FlowableRuntimeUtils.USER, User.class);
+        String token = engine.getRuntimeService().
+                getVariable(executionId, FlowableRuntimeUtils.TOKEN, String.class);
+        String password = engine.getRuntimeService().
+                getVariable(executionId, FlowableRuntimeUtils.PASSWORD, String.class);
+
+        if (!user.checkToken(token)) {
+            throw new WorkflowException(new IllegalArgumentException("Wrong token: " + token + " for " + user));
+        }
+
+        user.removeToken();
+
+        UserPatch userPatch = new UserPatch();
+        userPatch.setKey(user.getKey());
+        userPatch.setPassword(new PasswordPatch.Builder().
+                onSyncope(true).
+                resources(userDAO.findAllResourceKeys(user.getKey())).
+                value(password).build());
+
+        PropagationByResource propByRes = dataBinder.update(user, userPatch);
+
+        // report updated user and propagation by resource as result
+        engine.getRuntimeService().setVariable(executionId, FlowableRuntimeUtils.USER, user);
+        engine.getRuntimeService().setVariable(executionId, FlowableRuntimeUtils.USER_PATCH, userPatch);
+        engine.getRuntimeService().setVariable(executionId, FlowableRuntimeUtils.PROP_BY_RESOURCE, propByRes);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/3cabe08a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Reactivate.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Reactivate.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Reactivate.java
new file mode 100644
index 0000000..d6b2c27
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Reactivate.java
@@ -0,0 +1,29 @@
+/*
+ * 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.flowable.task;
+
+import org.springframework.stereotype.Component;
+
+@Component
+public class Reactivate extends AbstractFlowableServiceTask {
+
+    @Override
+    protected void doExecute(final String executionId) {
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/3cabe08a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Suspend.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Suspend.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Suspend.java
new file mode 100644
index 0000000..8e9439b
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Suspend.java
@@ -0,0 +1,29 @@
+/*
+ * 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.flowable.task;
+
+import org.springframework.stereotype.Component;
+
+@Component
+public class Suspend extends AbstractFlowableServiceTask {
+
+    @Override
+    protected void doExecute(final String executionId) {
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/3cabe08a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Update.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Update.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Update.java
new file mode 100644
index 0000000..c7007ad
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/task/Update.java
@@ -0,0 +1,58 @@
+/*
+ * 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.flowable.task;
+
+import org.apache.syncope.common.lib.patch.UserPatch;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.provisioning.api.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.flowable.impl.FlowableRuntimeUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class Update extends AbstractFlowableServiceTask {
+
+    @Autowired
+    private UserDataBinder dataBinder;
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Override
+    protected void doExecute(final String executionId) {
+        UserPatch userPatch = engine.getRuntimeService().
+                getVariable(executionId, FlowableRuntimeUtils.USER_PATCH, UserPatch.class);
+        if (userPatch == null || userPatch.isEmpty()) {
+            LOG.warn("No actual update is to be performed: null patch");
+        } else {
+            User user = engine.getRuntimeService().
+                    getVariable(executionId, FlowableRuntimeUtils.USER, User.class);
+
+            user = userDAO.save(user);
+
+            PropagationByResource propByRes = dataBinder.update(user, userPatch);
+
+            // report updated user and propagation by resource as result
+            engine.getRuntimeService().setVariable(executionId, FlowableRuntimeUtils.USER, user);
+            engine.getRuntimeService().setVariable(executionId, FlowableRuntimeUtils.PROP_BY_RESOURCE, propByRes);
+        }
+    }
+}