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/10/02 12:32:17 UTC

[1/6] syncope git commit: [SYNCOPE-1369] User requests forms now support dropdowns - via Flowable customization

Repository: syncope
Updated Branches:
  refs/heads/2_1_X 35d46cfe3 -> 6f6d91569
  refs/heads/master fba9bce62 -> fee1317dc


http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/UserRequestLogic.java
----------------------------------------------------------------------
diff --git a/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/UserRequestLogic.java b/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/UserRequestLogic.java
index 1acc1da..686677e 100644
--- a/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/UserRequestLogic.java
+++ b/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/UserRequestLogic.java
@@ -25,13 +25,12 @@ import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.patch.UserPatch;
 import org.apache.syncope.common.lib.to.PropagationTaskTO;
-import org.apache.syncope.common.lib.to.UserRequestTO;
+import org.apache.syncope.common.lib.to.UserRequest;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.to.UserRequestForm;
 import org.apache.syncope.common.lib.types.BpmnProcessFormat;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.FlowableEntitlement;
-import org.apache.syncope.common.lib.types.StandardEntitlement;
 import org.apache.syncope.core.flowable.api.BpmnProcessManager;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
@@ -41,6 +40,7 @@ import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecu
 import org.apache.syncope.core.provisioning.api.WorkflowResult;
 import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
 import org.apache.syncope.core.flowable.api.UserRequestHandler;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.flowable.engine.runtime.ProcessInstance;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -52,24 +52,49 @@ import org.springframework.transaction.annotation.Transactional;
 public class UserRequestLogic extends AbstractTransactionalLogic<UserRequestForm> {
 
     @Autowired
-    private BpmnProcessManager bpmnProcessManager;
+    protected BpmnProcessManager bpmnProcessManager;
 
     @Autowired
-    private UserRequestHandler userRequestHandler;
+    protected UserRequestHandler userRequestHandler;
 
     @Autowired
-    private PropagationManager propagationManager;
+    protected PropagationManager propagationManager;
 
     @Autowired
-    private PropagationTaskExecutor taskExecutor;
+    protected PropagationTaskExecutor taskExecutor;
 
     @Autowired
-    private UserDataBinder binder;
+    protected UserDataBinder binder;
 
     @Autowired
-    private UserDAO userDAO;
+    protected UserDAO userDAO;
 
-    protected UserRequestTO doStart(final String bpmnProcess, final User user) {
+    @PreAuthorize("isAuthenticated()")
+    @Transactional(readOnly = true)
+    public Pair<Integer, List<UserRequest>> list(final String userKey,
+            final int page,
+            final int size,
+            final List<OrderByClause> orderByClauses) {
+
+        if (userKey == null) {
+            securityChecks(null,
+                    FlowableEntitlement.USER_REQUEST_LIST,
+                    "Listing user requests not allowed");
+        } else {
+            User user = userDAO.find(userKey);
+            if (user == null) {
+                throw new NotFoundException("User " + userKey);
+            }
+
+            securityChecks(user.getUsername(),
+                    FlowableEntitlement.USER_REQUEST_LIST,
+                    "Listing requests for user" + user.getUsername() + " not allowed");
+        }
+
+        return userRequestHandler.getUserRequests(userKey, page, size, orderByClauses);
+    }
+
+    protected UserRequest doStart(final String bpmnProcess, final User user) {
         // check if BPMN process exists
         bpmnProcessManager.exportProcess(bpmnProcess, BpmnProcessFormat.XML, new NullOutputStream());
 
@@ -77,56 +102,84 @@ public class UserRequestLogic extends AbstractTransactionalLogic<UserRequestForm
     }
 
     @PreAuthorize("isAuthenticated()")
-    public UserRequestTO start(final String bpmnProcess) {
+    public UserRequest start(final String bpmnProcess) {
         return doStart(bpmnProcess, userDAO.findByUsername(AuthContextUtils.getUsername()));
     }
 
     @PreAuthorize("hasRole('" + FlowableEntitlement.USER_REQUEST_START + "')")
-    public UserRequestTO start(final String bpmnProcess, final String userKey) {
+    public UserRequest start(final String bpmnProcess, final String userKey) {
         return doStart(bpmnProcess, userDAO.authFind(userKey));
     }
 
-    @PreAuthorize("isAuthenticated()")
-    public void cancel(final String executionId, final String reason) {
-        Pair<ProcessInstance, String> parsed = userRequestHandler.parse(executionId);
-
-        if (!AuthContextUtils.getUsername().equals(userDAO.find(parsed.getRight()).getUsername())
+    protected void securityChecks(final String username, final String entitlement, final String errorMessage) {
+        if (!AuthContextUtils.getUsername().equals(username)
                 && !AuthContextUtils.getAuthorities().stream().
-                        anyMatch(auth -> FlowableEntitlement.USER_REQUEST_CANCEL.equals(auth.getAuthority()))) {
+                        anyMatch(auth -> entitlement.equals(auth.getAuthority()))) {
 
             SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.DelegatedAdministration);
-            sce.getElements().add("Canceling " + executionId + " not allowed");
+            sce.getElements().add(errorMessage);
             throw sce;
         }
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    public void cancel(final String executionId, final String reason) {
+        Pair<ProcessInstance, String> parsed = userRequestHandler.parse(executionId);
+
+        securityChecks(userDAO.find(parsed.getRight()).getUsername(),
+                FlowableEntitlement.USER_REQUEST_CANCEL,
+                "Canceling " + executionId + " not allowed");
 
         userRequestHandler.cancel(parsed.getLeft(), reason);
     }
 
-    @PreAuthorize("hasRole('" + FlowableEntitlement.WORKFLOW_FORM_CLAIM + "')")
+    @PreAuthorize("isAuthenticated()")
     public UserRequestForm claimForm(final String taskId) {
-        return userRequestHandler.claimForm(taskId);
-    }
-
-    @PreAuthorize("hasRole('" + FlowableEntitlement.WORKFLOW_FORM_READ + "') "
-            + "and hasRole('" + StandardEntitlement.USER_READ + "')")
-    @Transactional(readOnly = true)
-    public List<UserRequestForm> getForms(final String key) {
-        User user = userDAO.authFind(key);
-        return userRequestHandler.getForms(user.getKey());
+        UserRequestForm form = userRequestHandler.claimForm(taskId);
+        securityChecks(form.getUsername(),
+                FlowableEntitlement.USER_REQUEST_FORM_CLAIM,
+                "Claiming form " + taskId + " not allowed");
+        return form;
     }
 
-    @PreAuthorize("hasRole('" + FlowableEntitlement.WORKFLOW_FORM_LIST + "')")
+    @PreAuthorize("isAuthenticated()")
     @Transactional(readOnly = true)
     public Pair<Integer, List<UserRequestForm>> getForms(
+            final String userKey,
             final int page,
             final int size,
             final List<OrderByClause> orderByClauses) {
 
-        return userRequestHandler.getForms(page, size, orderByClauses);
+        if (userKey == null) {
+            securityChecks(null,
+                    FlowableEntitlement.USER_REQUEST_FORM_LIST,
+                    "Listing forms not allowed");
+        } else {
+            User user = userDAO.find(userKey);
+            if (user == null) {
+                throw new NotFoundException("User " + userKey);
+            }
+
+            securityChecks(user.getUsername(),
+                    FlowableEntitlement.USER_REQUEST_FORM_LIST,
+                    "Listing forms for user" + user.getUsername() + " not allowed");
+        }
+
+        return userRequestHandler.getForms(userKey, page, size, orderByClauses);
     }
 
-    @PreAuthorize("hasRole('" + FlowableEntitlement.WORKFLOW_FORM_SUBMIT + "')")
+    @PreAuthorize("isAuthenticated()")
     public UserTO submitForm(final UserRequestForm form) {
+        if (form.getUsername() == null) {
+            securityChecks(null,
+                    FlowableEntitlement.USER_REQUEST_FORM_SUBMIT,
+                    "Submitting forms not allowed");
+        } else {
+            securityChecks(form.getUsername(),
+                    FlowableEntitlement.USER_REQUEST_FORM_SUBMIT,
+                    "Submitting forms for user" + form.getUsername() + " not allowed");
+        }
+
         WorkflowResult<UserPatch> wfResult = userRequestHandler.submitForm(form);
 
         // propByRes can be made empty by the workflow definition if no propagation should occur 

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/UserRequestFormQuery.java
----------------------------------------------------------------------
diff --git a/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/UserRequestFormQuery.java b/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/UserRequestFormQuery.java
index 4097afd..c4ac3a0 100644
--- a/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/UserRequestFormQuery.java
+++ b/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/UserRequestFormQuery.java
@@ -18,6 +18,8 @@
  */
 package org.apache.syncope.common.rest.api.beans;
 
+import javax.ws.rs.QueryParam;
+
 public class UserRequestFormQuery extends AbstractQuery {
 
     private static final long serialVersionUID = -4762457303770028554L;
@@ -28,5 +30,21 @@ public class UserRequestFormQuery extends AbstractQuery {
         protected UserRequestFormQuery newInstance() {
             return new UserRequestFormQuery();
         }
+
+        public Builder user(final String user) {
+            getInstance().setUser(user);
+            return this;
+        }
+    }
+
+    private String user;
+
+    public String getUser() {
+        return user;
+    }
+
+    @QueryParam("user")
+    public void setUser(final String user) {
+        this.user = user;
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/UserRequestQuery.java
----------------------------------------------------------------------
diff --git a/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/UserRequestQuery.java b/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/UserRequestQuery.java
new file mode 100644
index 0000000..3f53629
--- /dev/null
+++ b/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/UserRequestQuery.java
@@ -0,0 +1,50 @@
+/*
+ * 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.common.rest.api.beans;
+
+import javax.ws.rs.QueryParam;
+
+public class UserRequestQuery extends AbstractQuery {
+
+    private static final long serialVersionUID = 427312034580076640L;
+
+    public static class Builder extends AbstractQuery.Builder<UserRequestQuery, Builder> {
+
+        @Override
+        protected UserRequestQuery newInstance() {
+            return new UserRequestQuery();
+        }
+
+        public Builder user(final String user) {
+            getInstance().setUser(user);
+            return this;
+        }
+    }
+
+    private String user;
+
+    public String getUser() {
+        return user;
+    }
+
+    @QueryParam("user")
+    public void setUser(final String user) {
+        this.user = user;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/BpmnProcessService.java
----------------------------------------------------------------------
diff --git a/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/BpmnProcessService.java b/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/BpmnProcessService.java
index 06cb056..bb34fdd 100644
--- a/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/BpmnProcessService.java
+++ b/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/BpmnProcessService.java
@@ -19,6 +19,9 @@
 package org.apache.syncope.common.rest.api.service;
 
 import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.ArraySchema;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.responses.ApiResponse;
 import io.swagger.v3.oas.annotations.responses.ApiResponses;
 import io.swagger.v3.oas.annotations.security.SecurityRequirement;
@@ -35,7 +38,7 @@ import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
-import org.apache.syncope.common.lib.to.BpmnProcessTO;
+import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 
 /**
@@ -51,11 +54,16 @@ public interface BpmnProcessService extends JAXRSService {
     /**
      * Lists the available BPMN processes.
      *
-     * @return available BPMN processs, for the given any object type
+     * @return available BPMN processs
      */
+    @ApiResponses(
+            @ApiResponse(responseCode = "200", description = "available BPMN processes", content =
+                    @Content(array =
+                            @ArraySchema(schema =
+                                    @Schema(implementation = BpmnProcess.class)))))
     @GET
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
-    List<BpmnProcessTO> list();
+    List<BpmnProcess> list();
 
     /**
      * Exports the BPMN process for matching key.
@@ -63,6 +71,8 @@ public interface BpmnProcessService extends JAXRSService {
      * @param key BPMN process key
      * @return BPMN process for matching key
      */
+    @ApiResponses(
+            @ApiResponse(responseCode = "200", description = "BPMN process for matching key"))
     @GET
     @Path("{key}")
     @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@@ -72,8 +82,10 @@ public interface BpmnProcessService extends JAXRSService {
      * Exports the BPMN diagram representation (if available), for matching key.
      *
      * @param key BPMN process key
-     * @return workflow diagram representation
+     * @return BPMN diagram representation
      */
+    @ApiResponses(
+            @ApiResponse(responseCode = "200", description = "BPMN diagram representation"))
     @GET
     @Path("{key}/diagram.png")
     @Produces({ RESTHeaders.MEDIATYPE_IMAGE_PNG })

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserRequestService.java
----------------------------------------------------------------------
diff --git a/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserRequestService.java b/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserRequestService.java
index 701772d..5ad7b12 100644
--- a/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserRequestService.java
+++ b/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserRequestService.java
@@ -18,10 +18,13 @@
  */
 package org.apache.syncope.common.rest.api.service;
 
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
 import io.swagger.v3.oas.annotations.security.SecurityRequirement;
 import io.swagger.v3.oas.annotations.security.SecurityRequirements;
 import io.swagger.v3.oas.annotations.tags.Tag;
-import java.util.List;
 import javax.validation.constraints.NotNull;
 import javax.ws.rs.BeanParam;
 import javax.ws.rs.Consumes;
@@ -34,11 +37,12 @@ import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.MediaType;
 import org.apache.syncope.common.lib.to.PagedResult;
-import org.apache.syncope.common.lib.to.UserRequestTO;
+import org.apache.syncope.common.lib.to.UserRequest;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.to.UserRequestForm;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.beans.UserRequestFormQuery;
+import org.apache.syncope.common.rest.api.beans.UserRequestQuery;
 
 /**
  * REST operations related to user workflow.
@@ -51,19 +55,37 @@ import org.apache.syncope.common.rest.api.beans.UserRequestFormQuery;
 public interface UserRequestService extends JAXRSService {
 
     /**
-     * Starts a new user request, for the given BOMN Process and
-     * user (if provided) or requesting user (if not provided).
+     * Returns a list of running user requests matching the given query.
+     *
+     * @param query query conditions
+     * @return list of all running user requests
+     */
+    @ApiResponses(
+            @ApiResponse(responseCode = "200", description = "list of all running user requests", content =
+                    @Content(schema =
+                            @Schema(implementation = PagedResult.class))))
+    @GET
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    PagedResult<UserRequest> list(@BeanParam UserRequestQuery query);
+
+    /**
+     * Starts a new request for the given BPMN Process and user (if provided) or requesting user (if not provided).
      *
      * @param bpmnProcess BPMN process
-     * @param userKey user key
+     * @param user if value looks like a UUID then it is interpreted as key otherwise as a username
      * @return data about the started request service, including execution id
      */
+    @ApiResponses(
+            @ApiResponse(responseCode = "200",
+                    description = "data about the started request service, including execution id", content =
+                    @Content(schema =
+                            @Schema(implementation = UserRequest.class))))
     @POST
     @Path("start/{bpmnProcess}")
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
-    UserRequestTO start(
+    UserRequest start(
             @NotNull @PathParam("bpmnProcess") String bpmnProcess,
-            @QueryParam("userKey") String userKey);
+            @QueryParam("user") String user);
 
     /**
      * Cancel a running user request.
@@ -71,6 +93,8 @@ public interface UserRequestService extends JAXRSService {
      * @param executionId execution id
      * @param reason reason to cancel the user request
      */
+    @ApiResponses(
+            @ApiResponse(responseCode = "204", description = "Operation was successful"))
     @DELETE
     @Path("{executionId}")
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
@@ -79,44 +103,45 @@ public interface UserRequestService extends JAXRSService {
             @QueryParam("reason") String reason);
 
     /**
-     * Returns a list of all available workflow forms.
+     * Returns a list of user request forms matching the given query.
      *
      * @param query query conditions
-     * @return list of all available workflow forms
+     * @return list of all available user request forms
      */
+    @ApiResponses(
+            @ApiResponse(responseCode = "200", description = "list of all available user request forms", content =
+                    @Content(schema =
+                            @Schema(implementation = PagedResult.class))))
     @GET
     @Path("forms")
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
     PagedResult<UserRequestForm> getForms(@BeanParam UserRequestFormQuery query);
 
     /**
-     * Returns a list of available forms for the given user key.
-     *
-     * @param userKey user key
-     * @return list of available forms for the given user key
-     */
-    @GET
-    @Path("forms/{userKey}")
-    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
-    List<UserRequestForm> getForms(@NotNull @PathParam("userKey") String userKey);
-
-    /**
-     * Claims the form for the given task id.
+     * Requests to manage the form for the given task id.
      *
      * @param taskId workflow task id
-     * @return the workflow form for the given task id
+     * @return the form for the given task id
      */
+    @ApiResponses(
+            @ApiResponse(responseCode = "200", description = "the form for the given task id", content =
+                    @Content(schema =
+                            @Schema(implementation = UserRequestForm.class))))
     @POST
     @Path("forms/{taskId}/claim")
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
     UserRequestForm claimForm(@NotNull @PathParam("taskId") String taskId);
 
     /**
-     * Submits a workflow form.
+     * Submits a user request form.
      *
-     * @param form workflow form.
+     * @param form user request form.
      * @return updated user
      */
+    @ApiResponses(
+            @ApiResponse(responseCode = "200", description = "updated user", content =
+                    @Content(schema =
+                            @Schema(implementation = UserTO.class))))
     @POST
     @Path("forms")
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserWorkflowTaskService.java
----------------------------------------------------------------------
diff --git a/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserWorkflowTaskService.java b/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserWorkflowTaskService.java
index 54f38d0..b396d6e 100644
--- a/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserWorkflowTaskService.java
+++ b/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserWorkflowTaskService.java
@@ -18,6 +18,11 @@
  */
 package org.apache.syncope.common.rest.api.service;
 
+import io.swagger.v3.oas.annotations.media.ArraySchema;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
 import io.swagger.v3.oas.annotations.security.SecurityRequirement;
 import io.swagger.v3.oas.annotations.security.SecurityRequirements;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -48,6 +53,11 @@ public interface UserWorkflowTaskService extends JAXRSService {
      * @param userKey user key
      * @return list of available tasks for the given user key
      */
+    @ApiResponses(
+            @ApiResponse(responseCode = "200", description = "list of available tasks for the given user key", content =
+                    @Content(array =
+                            @ArraySchema(schema =
+                                    @Schema(implementation = WorkflowTask.class)))))
     @GET
     @Path("tasks/{userKey}")
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
@@ -59,6 +69,10 @@ public interface UserWorkflowTaskService extends JAXRSService {
      * @param workflowTaskExecInput input for task execution
      * @return updated user
      */
+    @ApiResponses(
+            @ApiResponse(responseCode = "200", description = "updated user", content =
+                    @Content(schema =
+                            @Schema(implementation = UserTO.class))))
     @POST
     @Path("tasks/{userKey}")
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/BpmnProcessServiceImpl.java
----------------------------------------------------------------------
diff --git a/ext/flowable/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/BpmnProcessServiceImpl.java b/ext/flowable/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/BpmnProcessServiceImpl.java
index dd1aea8..4f976b5 100644
--- a/ext/flowable/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/BpmnProcessServiceImpl.java
+++ b/ext/flowable/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/BpmnProcessServiceImpl.java
@@ -22,7 +22,7 @@ import java.util.List;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.StreamingOutput;
-import org.apache.syncope.common.lib.to.BpmnProcessTO;
+import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.syncope.common.lib.types.BpmnProcessFormat;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.core.logic.BpmnProcessLogic;
@@ -37,7 +37,7 @@ public class BpmnProcessServiceImpl extends AbstractServiceImpl implements BpmnP
     private BpmnProcessLogic logic;
 
     @Override
-    public List<BpmnProcessTO> list() {
+    public List<BpmnProcess> list() {
         return logic.list();
     }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserRequestServiceImpl.java
----------------------------------------------------------------------
diff --git a/ext/flowable/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserRequestServiceImpl.java b/ext/flowable/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserRequestServiceImpl.java
index 2f174a0..188d22e 100644
--- a/ext/flowable/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserRequestServiceImpl.java
+++ b/ext/flowable/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserRequestServiceImpl.java
@@ -21,14 +21,16 @@ package org.apache.syncope.core.rest.cxf.service;
 import java.util.List;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.to.PagedResult;
-import org.apache.syncope.common.lib.to.UserRequestTO;
+import org.apache.syncope.common.lib.to.UserRequest;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.to.UserRequestForm;
 import org.apache.syncope.common.rest.api.beans.UserRequestFormQuery;
+import org.apache.syncope.common.rest.api.beans.UserRequestQuery;
 import org.apache.syncope.core.logic.UserRequestLogic;
+import org.apache.syncope.common.rest.api.service.UserRequestService;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
-import org.apache.syncope.common.rest.api.service.UserRequestService;
 
 @Service
 public class UserRequestServiceImpl extends AbstractServiceImpl implements UserRequestService {
@@ -36,11 +38,25 @@ public class UserRequestServiceImpl extends AbstractServiceImpl implements UserR
     @Autowired
     private UserRequestLogic logic;
 
+    @Autowired
+    private UserDAO userDAO;
+
+    @Override
+    public PagedResult<UserRequest> list(final UserRequestQuery query) {
+        if (query.getUser() != null) {
+            query.setUser(getActualKey(userDAO, query.getUser()));
+        }
+
+        Pair<Integer, List<UserRequest>> result = logic.list(
+                query.getUser(), query.getPage(), query.getSize(), getOrderByClauses(query.getOrderBy()));
+        return buildPagedResult(result.getRight(), query.getPage(), query.getSize(), result.getLeft());
+    }
+
     @Override
-    public UserRequestTO start(final String bpmnProcess, final String userKey) {
-        return userKey == null
+    public UserRequest start(final String bpmnProcess, final String user) {
+        return user == null
                 ? logic.start(bpmnProcess)
-                : logic.start(bpmnProcess, userKey);
+                : logic.start(bpmnProcess, getActualKey(userDAO, user));
     }
 
     @Override
@@ -54,14 +70,13 @@ public class UserRequestServiceImpl extends AbstractServiceImpl implements UserR
     }
 
     @Override
-    public List<UserRequestForm> getForms(final String userKey) {
-        return logic.getForms(userKey);
-    }
-
-    @Override
     public PagedResult<UserRequestForm> getForms(final UserRequestFormQuery query) {
+        if (query.getUser() != null) {
+            query.setUser(getActualKey(userDAO, query.getUser()));
+        }
+
         Pair<Integer, List<UserRequestForm>> result = logic.getForms(
-                query.getPage(), query.getSize(), getOrderByClauses(query.getOrderBy()));
+                query.getUser(), query.getPage(), query.getSize(), getOrderByClauses(query.getOrderBy()));
         return buildPagedResult(result.getRight(), query.getPage(), query.getSize(), result.getLeft());
     }
 
@@ -69,5 +84,4 @@ public class UserRequestServiceImpl extends AbstractServiceImpl implements UserR
     public UserTO submitForm(final UserRequestForm form) {
         return logic.submitForm(form);
     }
-
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/fit/core-reference/pom.xml
----------------------------------------------------------------------
diff --git a/fit/core-reference/pom.xml b/fit/core-reference/pom.xml
index cc8b494..47a142a 100644
--- a/fit/core-reference/pom.xml
+++ b/fit/core-reference/pom.xml
@@ -1541,6 +1541,26 @@ under the License.
       
       <build>
         <plugins>
+          <!-- Adds Flowable custom classes -->
+          <plugin>
+            <groupId>org.codehaus.mojo</groupId>
+            <artifactId>build-helper-maven-plugin</artifactId>
+            <executions>
+              <execution>
+                <id>add-flowable-custom-classes</id>
+                <phase>initialize</phase>
+                <goals>
+                  <goal>add-source</goal>
+                </goals>
+                <configuration>
+                  <sources>
+                    <source>${basedir}/src/main/java-all</source>
+                  </sources>
+                </configuration>
+              </execution>
+            </executions>
+          </plugin>
+
           <!-- Adds Flowable test content -->
           <plugin>
             <groupId>org.codehaus.mojo</groupId>

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/AssignDirectorGroup.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/AssignDirectorGroup.java b/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/AssignDirectorGroup.java
new file mode 100644
index 0000000..4db7ac2
--- /dev/null
+++ b/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/AssignDirectorGroup.java
@@ -0,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.fit.core.reference.flowable;
+
+import org.apache.syncope.common.lib.patch.MembershipPatch;
+import org.apache.syncope.common.lib.patch.UserPatch;
+import org.apache.syncope.core.flowable.impl.FlowableRuntimeUtils;
+import org.apache.syncope.core.flowable.task.AbstractFlowableServiceTask;
+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.PropagationByResource;
+import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AssignDirectorGroup 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);
+
+        Boolean secondLevelApprove = engine.getRuntimeService().
+                getVariable(executionId, "secondLevelApprove", Boolean.class);
+        if (Boolean.TRUE.equals(secondLevelApprove)) {
+            user = userDAO.save(user);
+
+            UserPatch userPatch = new UserPatch();
+            userPatch.setKey(user.getKey());
+            userPatch.getMemberships().add(new MembershipPatch.Builder().
+                    group("ebf97068-aa4b-4a85-9f01-680e8c4cf227").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.PROP_BY_RESOURCE, propByRes);
+        } else {
+            LOG.info("Second level was not approved, not assigning the director group to " + user.getUsername());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/CreateARelationship.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/CreateARelationship.java b/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/CreateARelationship.java
new file mode 100644
index 0000000..b0c5261
--- /dev/null
+++ b/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/CreateARelationship.java
@@ -0,0 +1,71 @@
+/*
+ * 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.fit.core.reference.flowable;
+
+import org.apache.syncope.common.lib.patch.RelationshipPatch;
+import org.apache.syncope.common.lib.patch.UserPatch;
+import org.apache.syncope.common.lib.to.RelationshipTO;
+import org.apache.syncope.core.flowable.impl.FlowableRuntimeUtils;
+import org.apache.syncope.core.flowable.task.AbstractFlowableServiceTask;
+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.PropagationByResource;
+import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class CreateARelationship 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);
+
+        Boolean approve = engine.getRuntimeService().
+                getVariable(executionId, "approve", Boolean.class);
+        if (Boolean.TRUE.equals(approve)) {
+            user = userDAO.save(user);
+
+            String printer = engine.getRuntimeService().
+                    getVariable(executionId, "printer", String.class);
+
+            UserPatch userPatch = new UserPatch();
+            userPatch.setKey(user.getKey());
+            userPatch.getRelationships().add(new RelationshipPatch.Builder().
+                    relationshipTO(new RelationshipTO.Builder().
+                            otherEnd("PRINTER", printer).type("neighborhood").build()).
+                    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.PROP_BY_RESOURCE, propByRes);
+        } else {
+            LOG.info("Printer assignment to " + user.getUsername() + " was not approved");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/PrintersValueProvider.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/PrintersValueProvider.java b/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/PrintersValueProvider.java
new file mode 100644
index 0000000..82e9693
--- /dev/null
+++ b/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/PrintersValueProvider.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.fit.core.reference.flowable;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.core.flowable.api.DropdownValueProvider;
+import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
+import org.apache.syncope.core.persistence.api.dao.search.AnyTypeCond;
+import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class PrintersValueProvider implements DropdownValueProvider {
+
+    private static final SearchCond PRINTER_COND;
+
+    private static final List<OrderByClause> ORDER_BY;
+
+    static {
+        AnyTypeCond anyTypeCond = new AnyTypeCond();
+        anyTypeCond.setAnyTypeKey("PRINTER");
+        PRINTER_COND = SearchCond.getLeafCond(anyTypeCond);
+
+        OrderByClause orderByNameAsc = new OrderByClause();
+        orderByNameAsc.setField("name");
+        orderByNameAsc.setDirection(OrderByClause.Direction.ASC);
+        ORDER_BY = Collections.singletonList(orderByNameAsc);
+    }
+
+    @Autowired
+    private AnySearchDAO anySearchDAO;
+
+    @Transactional(readOnly = true)
+    @Override
+    public Map<String, String> getValues() {
+        return anySearchDAO.<AnyObject>search(PRINTER_COND, ORDER_BY, AnyTypeKind.ANY_OBJECT).stream().
+                collect(Collectors.toMap(
+                        AnyObject::getKey,
+                        AnyObject::getName,
+                        (u, v) -> {
+                            throw new IllegalStateException(String.format("Duplicate key %s", u));
+                        },
+                        LinkedHashMap::new));
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/AssignDirectorGroup.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/AssignDirectorGroup.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/AssignDirectorGroup.java
deleted file mode 100644
index abdfcb6..0000000
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/AssignDirectorGroup.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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.fit.core.reference;
-
-import org.apache.syncope.common.lib.patch.MembershipPatch;
-import org.apache.syncope.common.lib.patch.UserPatch;
-import org.apache.syncope.core.flowable.impl.FlowableRuntimeUtils;
-import org.apache.syncope.core.flowable.task.AbstractFlowableServiceTask;
-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.PropagationByResource;
-import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
-import org.springframework.beans.factory.annotation.Autowired;
-
-public class AssignDirectorGroup 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);
-
-        Boolean secondLevelApprove = engine.getRuntimeService().
-                getVariable(executionId, "secondLevelApprove", Boolean.class);
-        if (Boolean.TRUE.equals(secondLevelApprove)) {
-            user = userDAO.save(user);
-
-            UserPatch userPatch = new UserPatch();
-            userPatch.setKey(user.getKey());
-            userPatch.getMemberships().add(new MembershipPatch.Builder().
-                    group("ebf97068-aa4b-4a85-9f01-680e8c4cf227").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.PROP_BY_RESOURCE, propByRes);
-        } else {
-            LOG.info("Second level was not approved, not assigning the director group to " + user.getUsername());
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/fit/core-reference/src/main/resources/all/workflowTestContext.xml
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/main/resources/all/workflowTestContext.xml b/fit/core-reference/src/main/resources/all/workflowTestContext.xml
index 2329c53..b14490a 100644
--- a/fit/core-reference/src/main/resources/all/workflowTestContext.xml
+++ b/fit/core-reference/src/main/resources/all/workflowTestContext.xml
@@ -19,9 +19,12 @@ under the License.
 -->
 <beans xmlns="http://www.springframework.org/schema/beans" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
-                           http://www.springframework.org/schema/beans/spring-beans.xsd">
+                           http://www.springframework.org/schema/beans/spring-beans.xsd
+                           http://www.springframework.org/schema/context
+                           http://www.springframework.org/schema/context/spring-context.xsd">
 
-  <bean id="assignDirectorGroup" class="org.apache.syncope.fit.core.reference.AssignDirectorGroup"/>
+  <context:component-scan base-package="org.apache.syncope.fit.core.reference.flowable"/>
 
 </beans>

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/fit/core-reference/src/main/resources/assignPrinterRequest.bpmn20.xml
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/main/resources/assignPrinterRequest.bpmn20.xml b/fit/core-reference/src/main/resources/assignPrinterRequest.bpmn20.xml
new file mode 100644
index 0000000..c9ecb9c
--- /dev/null
+++ b/fit/core-reference/src/main/resources/assignPrinterRequest.bpmn20.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" 
+             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
+             xmlns:flowable="http://flowable.org/bpmn"
+             xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" 
+             xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" 
+             xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" 
+             typeLanguage="http://www.w3.org/2001/XMLSchema" 
+             expressionLanguage="http://www.w3.org/1999/XPath" 
+             targetNamespace="http://www.flowable.org/processdef">
+
+  <process id="assignPrinterRequest" name="Assign printer" isExecutable="true">
+    <startEvent id="startevent1" name="Start"/>
+    <endEvent id="endevent1" name="End"/>
+    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="selectPrinter"/>
+    <userTask id="selectPrinter" name="Select printer" flowable:formKey="selectPrinter" flowable:assignee="${wfExecutor}">
+      <extensionElements>
+        <flowable:formProperty id="printer" name="Printer" variable="printer" type="dropdown" required="true">
+          <flowable:value id="dropdownValueProvider" name="printersValueProvider"/>
+        </flowable:formProperty>
+        <flowable:formProperty id="printMode" name="Preferred print mode?" type="enum">
+          <flowable:value id="bw" name="Black / White"/>
+          <flowable:value id="color" name="Color"/>
+        </flowable:formProperty>
+      </extensionElements>
+    </userTask>
+    <userTask id="approvePrinter" name="Approve printer" flowable:formKey="approvePrinter">
+      <extensionElements>
+        <flowable:formProperty id="username" name="Username" type="string" expression="${userTO.username}" writable="false"/>
+        <flowable:formProperty id="printer" name="Selected printer" type="string" expression="${printer}" writable="false"/>
+        <flowable:formProperty id="approve" name="Approve?" type="boolean" variable="approve" required="true"/>
+      </extensionElements>
+    </userTask>
+    <sequenceFlow id="sid-D7047714-8E57-46B8-B6D4-4844DE330329" sourceRef="selectPrinter" targetRef="approvePrinter"/>
+    <serviceTask id="createARelationship" name="Create ARelationship" flowable:expression="#{createARelationship.execute(execution.processInstanceId)}"/>
+    <sequenceFlow id="sid-33880AE7-35C6-4A39-8E5B-12D8BA53F042" sourceRef="approvePrinter" targetRef="createARelationship"/>
+    <sequenceFlow id="sid-831E1896-EDF9-4F7D-AA42-E86CC1F8C5D3" sourceRef="createARelationship" targetRef="endevent1"/>
+  </process>
+  <bpmndi:BPMNDiagram id="BPMNDiagram_assignPrinterRequest">
+    <bpmndi:BPMNPlane bpmnElement="assignPrinterRequest" id="BPMNPlane_assignPrinterRequest">
+      <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
+        <omgdc:Bounds height="30.0" width="30.0" x="180.0" y="115.0"/>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
+        <omgdc:Bounds height="28.0" width="28.0" x="885.0" y="116.0"/>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="selectPrinter" id="BPMNShape_selectPrinter">
+        <omgdc:Bounds height="80.0" width="100.0" x="330.0" y="90.0"/>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="approvePrinter" id="BPMNShape_approvePrinter">
+        <omgdc:Bounds height="80.0" width="100.0" x="495.5" y="90.0"/>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="createARelationship" id="BPMNShape_createARelationship">
+        <omgdc:Bounds height="80.0" width="100.0" x="675.0" y="90.0"/>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
+        <omgdi:waypoint x="209.94999901196195" y="130.0"/>
+        <omgdi:waypoint x="330.0" y="130.0"/>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge bpmnElement="sid-D7047714-8E57-46B8-B6D4-4844DE330329" id="BPMNEdge_sid-D7047714-8E57-46B8-B6D4-4844DE330329">
+        <omgdi:waypoint x="429.95000000000005" y="130.0"/>
+        <omgdi:waypoint x="495.5" y="130.0"/>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge bpmnElement="sid-33880AE7-35C6-4A39-8E5B-12D8BA53F042" id="BPMNEdge_sid-33880AE7-35C6-4A39-8E5B-12D8BA53F042">
+        <omgdi:waypoint x="595.4499999999068" y="130.0"/>
+        <omgdi:waypoint x="675.0" y="130.0"/>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge bpmnElement="sid-831E1896-EDF9-4F7D-AA42-E86CC1F8C5D3" id="BPMNEdge_sid-831E1896-EDF9-4F7D-AA42-E86CC1F8C5D3">
+        <omgdi:waypoint x="774.9499999999266" y="130.0"/>
+        <omgdi:waypoint x="885.0" y="130.0"/>
+      </bpmndi:BPMNEdge>
+    </bpmndi:BPMNPlane>
+  </bpmndi:BPMNDiagram>
+</definitions>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthenticationITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthenticationITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthenticationITCase.java
index e2f351b..cbdb19b 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthenticationITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthenticationITCase.java
@@ -66,6 +66,7 @@ import org.apache.syncope.common.lib.types.StandardEntitlement;
 import org.apache.syncope.common.lib.types.StatusPatchType;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.beans.AnyQuery;
+import org.apache.syncope.common.rest.api.beans.UserRequestFormQuery;
 import org.apache.syncope.common.rest.api.service.AnyObjectService;
 import org.apache.syncope.common.rest.api.service.SchemaService;
 import org.apache.syncope.common.rest.api.service.UserService;
@@ -495,7 +496,8 @@ public class AuthenticationITCase extends AbstractITCase {
         }
 
         // 3. approve user
-        UserRequestForm form = userRequestService.getForms(userTO.getKey()).get(0);
+        UserRequestForm form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(userTO.getKey()).build()).getResult().get(0);
         form = userRequestService.claimForm(form.getTaskId());
         form.getProperty("approveCreate").get().setValue(Boolean.TRUE.toString());
         userTO = userRequestService.submitForm(form);

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BpmnProcessITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BpmnProcessITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BpmnProcessITCase.java
index 443cd83..21fb4fd 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BpmnProcessITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BpmnProcessITCase.java
@@ -28,7 +28,7 @@ import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import javax.ws.rs.core.Response;
 import org.apache.commons.io.IOUtils;
-import org.apache.syncope.common.lib.to.BpmnProcessTO;
+import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.syncope.fit.AbstractITCase;
 import org.apache.syncope.fit.FlowableDetector;
 import org.junit.jupiter.api.BeforeAll;
@@ -42,7 +42,7 @@ public class BpmnProcessITCase extends AbstractITCase {
     public static void findDefault() {
         assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
         bpmnProcessService.list().stream().
-                filter(BpmnProcessTO::isUserWorkflow).findAny().
+                filter(BpmnProcess::isUserWorkflow).findAny().
                 ifPresent(process -> {
                     userWorkflowKey = process.getKey();
                 });

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserRequestITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserRequestITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserRequestITCase.java
index 5446894..9281bce 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserRequestITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserRequestITCase.java
@@ -25,14 +25,19 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assumptions.assumeTrue;
 
 import java.io.IOException;
+import java.util.List;
 import javax.ws.rs.core.MediaType;
 import org.apache.cxf.helpers.IOUtils;
 import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.common.lib.to.PagedResult;
+import org.apache.syncope.common.lib.to.RelationshipTO;
 import org.apache.syncope.common.lib.to.UserRequestForm;
-import org.apache.syncope.common.lib.to.UserRequestTO;
+import org.apache.syncope.common.lib.to.UserRequest;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.rest.api.beans.UserRequestFormQuery;
+import org.apache.syncope.common.rest.api.beans.UserRequestQuery;
+import org.apache.syncope.common.rest.api.service.UserRequestService;
 import org.apache.syncope.fit.AbstractITCase;
 import org.apache.syncope.fit.FlowableDetector;
 import org.junit.jupiter.api.BeforeAll;
@@ -42,9 +47,13 @@ public class UserRequestITCase extends AbstractITCase {
 
     @BeforeAll
     public static void loadBpmnProcesses() throws IOException {
+        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
+
         WebClient.client(bpmnProcessService).type(MediaType.APPLICATION_XML_TYPE);
         bpmnProcessService.set("directorGroupRequest",
                 IOUtils.toString(UserRequestITCase.class.getResourceAsStream("/directorGroupRequest.bpmn20.xml")));
+        bpmnProcessService.set("assignPrinterRequest",
+                IOUtils.toString(UserRequestITCase.class.getResourceAsStream("/assignPrinterRequest.bpmn20.xml")));
     }
 
     @Test
@@ -56,17 +65,29 @@ public class UserRequestITCase extends AbstractITCase {
         assertFalse(user.getMembership("ebf97068-aa4b-4a85-9f01-680e8c4cf227").isPresent());
 
         // start request
-        UserRequestTO req = userRequestService.start("directorGroupRequest", user.getKey());
+        UserRequest req = userRequestService.start("directorGroupRequest", user.getKey());
         assertNotNull(req);
+        assertEquals("directorGroupRequest", req.getBpmnProcess());
+        assertNotNull(req.getExecutionId());
+        assertEquals(req.getUser(), user.getKey());
+        
+        // check that user can see the ongoing request
+        SyncopeClient client = clientFactory.create(user.getUsername(), "password123");
+        PagedResult<UserRequest> requests = client.getService(UserRequestService.class).
+                list(new UserRequestQuery.Builder().user(user.getKey()).build());
+        assertEquals(1, requests.getTotalCount());
+        assertEquals("directorGroupRequest", requests.getResult().get(0).getBpmnProcess());
 
         // 1st approval -> reject
-        UserRequestForm form = userRequestService.getForms(user.getKey()).get(0);
+        UserRequestForm form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(user.getKey()).build()).getResult().get(0);
         form = userRequestService.claimForm(form.getTaskId());
         form.getProperty("firstLevelApprove").get().setValue(Boolean.FALSE.toString());
         userRequestService.submitForm(form);
 
         // no more forms, group not assigned
-        assertTrue(userRequestService.getForms(user.getKey()).isEmpty());
+        assertTrue(userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(user.getKey()).build()).getResult().isEmpty());
         assertFalse(userService.read(user.getKey()).getMembership("ebf97068-aa4b-4a85-9f01-680e8c4cf227").isPresent());
 
         // start request again
@@ -74,19 +95,22 @@ public class UserRequestITCase extends AbstractITCase {
         assertNotNull(req);
 
         // 1st approval -> accept
-        form = userRequestService.getForms(user.getKey()).get(0);
+        form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(user.getKey()).build()).getResult().get(0);
         form = userRequestService.claimForm(form.getTaskId());
         form.getProperty("firstLevelApprove").get().setValue(Boolean.TRUE.toString());
         userRequestService.submitForm(form);
 
         // 2nd approval -> reject
-        form = userRequestService.getForms(user.getKey()).get(0);
+        form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(user.getKey()).build()).getResult().get(0);
         form = userRequestService.claimForm(form.getTaskId());
         form.getProperty("secondLevelApprove").get().setValue(Boolean.FALSE.toString());
         user = userRequestService.submitForm(form);
 
         // no more forms, group not assigned
-        assertTrue(userRequestService.getForms(user.getKey()).isEmpty());
+        assertTrue(userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(user.getKey()).build()).getResult().isEmpty());
         assertFalse(userService.read(user.getKey()).getMembership("ebf97068-aa4b-4a85-9f01-680e8c4cf227").isPresent());
 
         // start request again
@@ -94,13 +118,15 @@ public class UserRequestITCase extends AbstractITCase {
         assertNotNull(req);
 
         // 1st approval -> accept
-        form = userRequestService.getForms(user.getKey()).get(0);
+        form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(user.getKey()).build()).getResult().get(0);
         form = userRequestService.claimForm(form.getTaskId());
         form.getProperty("firstLevelApprove").get().setValue(Boolean.TRUE.toString());
         userRequestService.submitForm(form);
 
         // 2nd approval -> accept
-        form = userRequestService.getForms(user.getKey()).get(0);
+        form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(user.getKey()).build()).getResult().get(0);
         form = userRequestService.claimForm(form.getTaskId());
         form.getProperty("secondLevelApprove").get().setValue(Boolean.TRUE.toString());
         user = userRequestService.submitForm(form);
@@ -115,7 +141,7 @@ public class UserRequestITCase extends AbstractITCase {
         assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
 
         PagedResult<UserRequestForm> forms =
-                userRequestService.getForms(new UserRequestFormQuery.Builder().page(1).size(1000).build());
+                userRequestService.getForms(new UserRequestFormQuery.Builder().build());
         int preForms = forms.getTotalCount();
 
         UserTO user = createUser(UserITCase.getUniqueSampleTO("twoLevelsApproval@tirasa.net")).getEntity();
@@ -123,22 +149,101 @@ public class UserRequestITCase extends AbstractITCase {
         assertFalse(user.getMembership("ebf97068-aa4b-4a85-9f01-680e8c4cf227").isPresent());
 
         // start request
-        UserRequestTO req = userRequestService.start("directorGroupRequest", user.getKey());
+        UserRequest req = userRequestService.start("directorGroupRequest", user.getKey());
         assertNotNull(req);
 
         // check that form was generated
-        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().page(1).size(1000).build());
+        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().build());
         assertEquals(preForms + 1, forms.getTotalCount());
 
-        assertEquals(1, userRequestService.getForms(user.getKey()).size());
+        assertEquals(1, userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(user.getKey()).build()).getResult().size());
 
         // cancel request
         userRequestService.cancel(req.getExecutionId(), "nothing in particular");
 
         // check that form was removed
-        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().page(1).size(1000).build());
+        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().build());
+        assertEquals(preForms, forms.getTotalCount());
+
+        assertTrue(userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(user.getKey()).build()).getResult().isEmpty());
+    }
+
+    @Test
+    public void userSelection() {
+        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
+
+        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
+
+        PagedResult<UserRequestForm> forms =
+                userRequestService.getForms(new UserRequestFormQuery.Builder().build());
+        int preForms = forms.getTotalCount();
+
+        UserTO user = createUser(UserITCase.getUniqueSampleTO("userSelection@tirasa.net")).getEntity();
+        assertNotNull(user);
+        List<RelationshipTO> relationships = userService.read(user.getKey()).getRelationships();
+        assertTrue(relationships.isEmpty());
+
+        SyncopeClient client = clientFactory.create(user.getUsername(), "password123");
+
+        // start request as user
+        UserRequest req = client.getService(UserRequestService.class).start("assignPrinterRequest", null);
+        assertNotNull(req);
+
+        // check (as admin) that a new form is available
+        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().build());
+        assertEquals(preForms + 1, forms.getTotalCount());
+
+        // get (as user) the form, claim and submit
+        PagedResult<UserRequestForm> userForms = client.getService(UserRequestService.class).
+                getForms(new UserRequestFormQuery.Builder().user(user.getKey()).build());
+        assertEquals(1, userForms.getTotalCount());
+
+        UserRequestForm form = userForms.getResult().get(0);
+        assertEquals("assignPrinterRequest", form.getBpmnProcess());
+        form = client.getService(UserRequestService.class).claimForm(form.getTaskId());
+
+        assertFalse(form.getProperty("printer").get().getDropdownValues().isEmpty());
+        form.getProperty("printer").ifPresent(printer -> printer.setValue("8559d14d-58c2-46eb-a2d4-a7d35161e8f8"));
+
+        assertFalse(form.getProperty("printMode").get().getEnumValues().isEmpty());
+        form.getProperty("printMode").ifPresent(printMode -> printMode.setValue("color"));
+
+        client.getService(UserRequestService.class).submitForm(form);
+
+        userForms = client.getService(UserRequestService.class).getForms(
+                new UserRequestFormQuery.Builder().user(user.getKey()).build());
+        assertEquals(0, userForms.getTotalCount());
+
+        // check that user can see the ongoing request
+        PagedResult<UserRequest> requests = client.getService(UserRequestService.class).
+                list(new UserRequestQuery.Builder().user(user.getKey()).build());
+        assertEquals(1, requests.getTotalCount());
+        assertEquals("assignPrinterRequest", requests.getResult().get(0).getBpmnProcess());
+
+        // get (as admin) the new form, claim and submit
+        form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(user.getKey()).build()).getResult().get(0);
+        assertEquals("assignPrinterRequest", form.getBpmnProcess());
+        form = userRequestService.claimForm(form.getTaskId());
+
+        assertEquals("8559d14d-58c2-46eb-a2d4-a7d35161e8f8", form.getProperty("printer").get().getValue());
+
+        form.getProperty("approve").get().setValue(Boolean.TRUE.toString());
+        userRequestService.submitForm(form);
+
+        // no more forms available
+        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().build());
         assertEquals(preForms, forms.getTotalCount());
 
-        assertTrue(userRequestService.getForms(user.getKey()).isEmpty());
+        assertTrue(client.getService(UserRequestService.class).
+                list(new UserRequestQuery.Builder().user(user.getKey()).build()).getResult().isEmpty());
+
+        // check that relationship was made effective by approval
+        relationships = userService.read(user.getKey()).getRelationships();
+        assertFalse(relationships.isEmpty());
+        assertTrue(relationships.stream().
+                anyMatch(relationship -> "8559d14d-58c2-46eb-a2d4-a7d35161e8f8".equals(relationship.getOtherEndKey())));
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserSelfITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserSelfITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserSelfITCase.java
index 0e48cf2..63db20e 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserSelfITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserSelfITCase.java
@@ -130,7 +130,8 @@ public class UserSelfITCase extends AbstractITCase {
         }
 
         // now approve and verify that propagation has happened
-        UserRequestForm form = userRequestService.getForms(userTO.getKey()).get(0);
+        UserRequestForm form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(userTO.getKey()).build()).getResult().get(0);
         form = userRequestService.claimForm(form.getTaskId());
         form.getProperty("approveCreate").get().setValue(Boolean.TRUE.toString());
         userTO = userRequestService.submitForm(form);
@@ -228,7 +229,8 @@ public class UserSelfITCase extends AbstractITCase {
         }
 
         // 3. approve self-update as admin
-        UserRequestForm form = userRequestService.getForms(updated.getKey()).get(0);
+        UserRequestForm form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(updated.getKey()).build()).getResult().get(0);
         form = userRequestService.claimForm(form.getTaskId());
         form.getProperty("approveUpdate").get().setValue(Boolean.TRUE.toString());
         updated = userRequestService.submitForm(form);
@@ -420,7 +422,8 @@ public class UserSelfITCase extends AbstractITCase {
         assertEquals("createApproval", userTO.getStatus());
 
         // 2. request if there is any pending task for user just created
-        UserRequestForm form = userRequestService.getForms(userTO.getKey()).get(0);
+        UserRequestForm form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(userTO.getKey()).build()).getResult().get(0);
         assertNotNull(form);
         assertNotNull(form.getUsername());
         assertEquals(userTO.getUsername(), form.getUsername());
@@ -480,8 +483,7 @@ public class UserSelfITCase extends AbstractITCase {
         assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
 
         // read forms *before* any operation
-        PagedResult<UserRequestForm> forms =
-                userRequestService.getForms(new UserRequestFormQuery.Builder().page(1).size(1000).build());
+        PagedResult<UserRequestForm> forms = userRequestService.getForms(new UserRequestFormQuery.Builder().build());
         int preForms = forms.getTotalCount();
 
         UserTO userTO = UserITCase.getUniqueSampleTO("createWithApproval@syncope.apache.org");
@@ -514,7 +516,7 @@ public class UserSelfITCase extends AbstractITCase {
         assertNotNull(exception);
 
         // 2. request if there is any pending form for user just created
-        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().page(1).size(1000).build());
+        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().build());
         assertEquals(preForms + 1, forms.getTotalCount());
 
         // 3. as admin, update user: still pending approval
@@ -524,7 +526,8 @@ public class UserSelfITCase extends AbstractITCase {
         userPatch.setUsername(new StringReplacePatchItem.Builder().value(updatedUsername).build());
         updateUser(userPatch);
 
-        UserRequestForm form = userRequestService.getForms(userTO.getKey()).get(0);
+        UserRequestForm form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(userTO.getKey()).build()).getResult().get(0);
         assertNotNull(form);
         assertNotNull(form.getTaskId());
         assertNotNull(form.getUserTO());
@@ -567,8 +570,8 @@ public class UserSelfITCase extends AbstractITCase {
         assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
 
         // read forms *before* any operation
-        PagedResult<UserRequestForm> forms = userRequestService.getForms(new UserRequestFormQuery.Builder().
-                page(1).size(1000).build());
+        PagedResult<UserRequestForm> forms = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().build());
         int preForms = forms.getTotalCount();
 
         UserTO created = createUser(UserITCase.getUniqueSampleTO("updateApproval@syncope.apache.org")).getEntity();
@@ -585,10 +588,11 @@ public class UserSelfITCase extends AbstractITCase {
         assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
         assertEquals("updateApproval", userService.read(created.getKey()).getStatus());
 
-        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().page(1).size(1000).build());
+        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().build());
         assertEquals(preForms + 1, forms.getTotalCount());
 
-        UserRequestForm form = userRequestService.getForms(created.getKey()).get(0);
+        UserRequestForm form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(created.getKey()).build()).getResult().get(0);
         assertNotNull(form);
         assertNotNull(form.getTaskId());
         assertNull(form.getOwner());
@@ -607,7 +611,8 @@ public class UserSelfITCase extends AbstractITCase {
         assertEquals(0, updated.getMemberships().size());
 
         // the patch is not updated in the approval form
-        form = userRequestService.getForms(created.getKey()).get(0);
+        form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(created.getKey()).build()).getResult().get(0);
         assertEquals(patch, form.getUserPatch());
 
         // approve the user
@@ -642,8 +647,7 @@ public class UserSelfITCase extends AbstractITCase {
         assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
 
         // read forms *before* any operation
-        PagedResult<UserRequestForm> forms = userRequestService.getForms(new UserRequestFormQuery.Builder().
-                page(1).size(1000).build());
+        PagedResult<UserRequestForm> forms = userRequestService.getForms(new UserRequestFormQuery.Builder().build());
         int preForms = forms.getTotalCount();
 
         UserTO userTO = UserITCase.getUniqueSampleTO("issueSYNCOPE15@syncope.apache.org");
@@ -667,10 +671,11 @@ public class UserSelfITCase extends AbstractITCase {
         assertEquals(userTO.getCreationDate(), userTO.getLastChangeDate());
 
         // 2. request if there is any pending form for user just created
-        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().page(1).size(1000).build());
+        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().build());
         assertEquals(preForms + 1, forms.getTotalCount());
 
-        UserRequestForm form = userRequestService.getForms(userTO.getKey()).get(0);
+        UserRequestForm form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(userTO.getKey()).build()).getResult().get(0);
         assertNotNull(form);
 
         // 3. first claim by bellini ....
@@ -692,12 +697,12 @@ public class UserSelfITCase extends AbstractITCase {
         userTO = userRequestService.submitForm(form);
         assertNotNull(userTO);
         assertEquals(preForms,
-                userRequestService.getForms(new UserRequestFormQuery.Builder().page(1).size(1000).build()).
-                        getTotalCount());
-        assertTrue(userRequestService.getForms(userTO.getKey()).isEmpty());
+                userRequestService.getForms(new UserRequestFormQuery.Builder().build()).getTotalCount());
+        assertTrue(userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(userTO.getKey()).build()).getResult().isEmpty());
 
         // 7.check that no more forms are still to be processed
-        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().page(1).size(1000).build());
+        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().build());
         assertEquals(preForms, forms.getTotalCount());
     }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index b4c82ae..db39a89 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1748,6 +1748,12 @@ under the License.
         </plugin>
         
         <plugin>
+          <groupId>org.codehaus.mojo</groupId>
+          <artifactId>build-helper-maven-plugin</artifactId>
+          <version>3.0.0</version>
+        </plugin>
+
+        <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-javadoc-plugin</artifactId>
           <version>2.10.4</version>          
@@ -2305,8 +2311,8 @@ under the License.
             <link>https://docs.spring.io/spring-security/site/docs/5.0.x/api/</link>
             <link>http://www.flowable.org/docs/javadocs/</link>
             <link>https://build.shibboleth.net/nexus/content/sites/site/java-opensaml/3.3.0/apidocs/</link>
-	    <link>https://artifacts.elastic.co/javadoc/org/elasticsearch/elasticsearch/6.4.1/index.html</link>
-	    <link>http://docs.swagger.io/swagger-core/v2.0.5/apidocs/</link>
+            <link>https://artifacts.elastic.co/javadoc/org/elasticsearch/elasticsearch/6.4.1/index.html</link>
+            <link>http://docs.swagger.io/swagger-core/v2.0.5/apidocs/</link>
           </links>
         </configuration>
         <reportSets>


[6/6] syncope git commit: [SYNCOPE-1369] User requests forms now support dropdowns - via Flowable customization

Posted by il...@apache.org.
[SYNCOPE-1369] User requests forms now support dropdowns - via Flowable customization


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/fee1317d
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/fee1317d
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/fee1317d

Branch: refs/heads/master
Commit: fee1317dc3e32d89fcbab5f5e748f49ce5e04f83
Parents: fba9bce
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Tue Oct 2 14:31:40 2018 +0200
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Tue Oct 2 14:32:07 2018 +0200

----------------------------------------------------------------------
 .../console/commons/MapChoiceRenderer.java      |  21 +-
 .../html/form/AjaxDropDownChoicePanel.java      |   3 +-
 .../console/wizards/any/Relationships.java      |   5 +-
 .../wizards/any/Relationships.properties        |   3 +
 .../wizards/any/Relationships_it.properties     |   3 +
 .../wizards/any/Relationships_ja.properties     |   3 +
 .../wizards/any/Relationships_pt_BR.properties  |   3 +
 .../wizards/any/Relationships_ru.properties     |   3 +
 .../syncope/common/lib/to/RelationshipTO.java   |  16 +
 .../core/persistence/api/dao/AnyObjectDAO.java  |   2 +-
 .../persistence/jpa/dao/JPAAnyObjectDAO.java    |  12 +-
 .../test/resources/domains/MasterContent.xml    |   6 +-
 .../provisioning/java/MappingManagerImpl.java   |  47 +--
 .../java/data/AbstractAnyDataBinder.java        |   5 +-
 .../java/data/AnyObjectDataBinderImpl.java      |  11 +-
 .../java/data/UserDataBinderImpl.java           |   7 +-
 ext/flowable/client-console/pom.xml             |  32 +-
 .../client/console/approvals/Approval.java      |  29 +-
 .../approvals/ApprovalDirectoryPanel.java       |  19 +-
 .../syncope/client/console/pages/Flowable.java  |   4 +-
 .../panels/BpmnProcessDirectoryPanel.java       |  48 +--
 .../resources/AbstractBpmnProcessResource.java  |   6 +-
 .../resources/BpmnProcessGETResource.java       |   4 +-
 .../resources/BpmnProcessPUTResource.java       |   4 +-
 .../console/rest/BpmnProcessRestClient.java     |   4 +-
 .../console/rest/UserRequestRestClient.java     |  11 +-
 .../client/console/widgets/ApprovalsWidget.java |  10 +-
 .../src/main/resources/dropdown.diff            | 139 ++++++++
 .../client/console/pages/Approvals.properties   |   2 +-
 .../console/pages/Approvals_it.properties       |   2 +-
 .../console/pages/Approvals_ja.properties       |   2 +-
 .../console/pages/Approvals_pt_BR.properties    |   2 +-
 .../console/pages/Approvals_ru.properties       |   2 +-
 .../syncope/common/lib/to/BpmnProcess.java      |  72 ++++
 .../syncope/common/lib/to/BpmnProcessTO.java    |  72 ----
 .../syncope/common/lib/to/UserRequest.java      |  70 ++++
 .../syncope/common/lib/to/UserRequestForm.java  |  26 +-
 .../common/lib/to/UserRequestFormProperty.java  |   8 +
 .../syncope/common/lib/to/UserRequestTO.java    |  70 ----
 .../common/lib/types/FlowableEntitlement.java   |  14 +-
 .../lib/types/UserRequestFormPropertyType.java  |   3 +-
 .../core/flowable/api/BpmnProcessManager.java   |   4 +-
 .../flowable/api/DropdownValueProvider.java     |  31 ++
 .../core/flowable/api/UserRequestHandler.java   |  30 +-
 .../impl/FlowableBpmnProcessManager.java        |   9 +-
 .../flowable/impl/FlowableRuntimeUtils.java     |  50 +--
 .../impl/FlowableUserRequestHandler.java        | 345 ++++++++++++-------
 .../support/DomainProcessEngineFactoryBean.java |   6 +
 .../support/DropdownAwareJsonConverter.java     |  31 ++
 .../DropdownAwareUserTaskJsonConverter.java     |  98 ++++++
 .../core/flowable/support/DropdownFormType.java |  59 ++++
 .../support/SyncopeFormHandlerHelper.java       |  56 +++
 .../support/SyncopeTaskFormHandler.java         | 112 ++++++
 .../main/resources/workflowFlowableContext.xml  |   4 +
 .../syncope/core/logic/BpmnProcessLogic.java    |   8 +-
 .../syncope/core/logic/UserRequestLogic.java    | 115 +++++--
 .../rest/api/beans/UserRequestFormQuery.java    |  18 +
 .../common/rest/api/beans/UserRequestQuery.java |  50 +++
 .../rest/api/service/BpmnProcessService.java    |  20 +-
 .../rest/api/service/UserRequestService.java    |  73 ++--
 .../api/service/UserWorkflowTaskService.java    |  14 +
 .../cxf/service/BpmnProcessServiceImpl.java     |   4 +-
 .../cxf/service/UserRequestServiceImpl.java     |  38 +-
 fit/core-reference/pom.xml                      |  20 ++
 .../reference/flowable/AssignDirectorGroup.java |  65 ++++
 .../reference/flowable/CreateARelationship.java |  71 ++++
 .../flowable/PrintersValueProvider.java         |  70 ++++
 .../fit/core/reference/AssignDirectorGroup.java |  63 ----
 .../main/resources/all/workflowTestContext.xml  |   7 +-
 .../resources/assignPrinterRequest.bpmn20.xml   |  92 +++++
 .../syncope/fit/core/AuthenticationITCase.java  |   4 +-
 .../syncope/fit/core/BpmnProcessITCase.java     |   4 +-
 .../syncope/fit/core/UserRequestITCase.java     | 135 +++++++-
 .../apache/syncope/fit/core/UserSelfITCase.java |  45 +--
 pom.xml                                         |  10 +-
 75 files changed, 1909 insertions(+), 657 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/client/console/src/main/java/org/apache/syncope/client/console/commons/MapChoiceRenderer.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/MapChoiceRenderer.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/MapChoiceRenderer.java
index 2845105..92d060d 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/commons/MapChoiceRenderer.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/MapChoiceRenderer.java
@@ -23,33 +23,28 @@ import java.util.Map;
 import org.apache.wicket.markup.html.form.IChoiceRenderer;
 import org.apache.wicket.model.IModel;
 
-public class MapChoiceRenderer<T, K> implements IChoiceRenderer<T> {
+public class MapChoiceRenderer implements IChoiceRenderer<String> {
 
     private static final long serialVersionUID = -7452881117778186644L;
 
-    private final Map<T, K> map;
+    private final Map<String, String> map;
 
-    public MapChoiceRenderer(final Map<T, K> map) {
+    public MapChoiceRenderer(final Map<String, String> map) {
         this.map = map;
     }
 
     @Override
-    public Object getDisplayValue(final T key) {
+    public Object getDisplayValue(final String key) {
         return map.get(key);
     }
 
     @Override
-    public String getIdValue(final T key, final int index) {
-        return key.toString();
+    public String getIdValue(final String key, final int index) {
+        return key;
     }
 
     @Override
-    public T getObject(final String id, final IModel<? extends List<? extends T>> choices) {
-        for (Map.Entry<T, K> entry : map.entrySet()) {
-            if (entry.getValue() != null && entry.getValue().toString().equals(id)) {
-                return entry.getKey();
-            }
-        }
-        return null;
+    public String getObject(final String id, final IModel<? extends List<? extends String>> choices) {
+        return id;
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDropDownChoicePanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDropDownChoicePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDropDownChoicePanel.java
index 193c0ad..c52efac 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDropDownChoicePanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDropDownChoicePanel.java
@@ -44,8 +44,7 @@ public class AjaxDropDownChoicePanel<T extends Serializable> extends FieldPanel<
 
         super(id, name, model);
 
-        field = new BootstrapSelect<>(
-                "dropDownChoiceField", model, Collections.<T>emptyList(), new ChoiceRenderer<>());
+        field = new BootstrapSelect<>("dropDownChoiceField", model, Collections.<T>emptyList(), new ChoiceRenderer<>());
         add(field.setLabel(new Model<>(name)).setOutputMarkupId(true));
 
         if (enableOnBlur) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java
index 06e126b..bfbdc1d 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java
@@ -136,14 +136,13 @@ public class Relationships extends WizardStep implements ICondition {
                 public Panel getPanel(final String panelId) {
                     return new ListViewPanel.Builder<>(RelationshipTO.class, pageRef).
                             setItems(relationships.get(relationship)).
-                            includes("otherEndType", "otherEndKey").
+                            includes("otherEndType", "otherEndKey", "otherEndName").
                             addAction(new ActionLink<RelationshipTO>() {
 
                                 private static final long serialVersionUID = -6847033126124401556L;
 
                                 @Override
-                                public void onClick(
-                                        final AjaxRequestTarget target, final RelationshipTO modelObject) {
+                                public void onClick(final AjaxRequestTarget target, final RelationshipTO modelObject) {
                                     removeRelationships(relationships, modelObject);
                                     send(Relationships.this, Broadcast.DEPTH, new ListViewReload<>(target));
                                 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships.properties
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships.properties b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships.properties
index cfefe54..45e8a78 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships.properties
@@ -16,3 +16,6 @@
 # under the License.
 relationships.empty.list=No relationships defined
 any.relationships=Relationships
+otherEndType=AnyType
+otherEndKey=Key
+otherEndName=Name

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_it.properties
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_it.properties b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_it.properties
index 5260a62..cdc900f 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_it.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_it.properties
@@ -16,3 +16,6 @@
 # under the License.
 relationships.empty.list=Nessuna relazione specificata
 any.relationships=Relazioni
+otherEndType=AnyType
+otherEndKey=Chiave
+otherEndName=Nome

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_ja.properties
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_ja.properties b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_ja.properties
index c0f26ae..5561099 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_ja.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_ja.properties
@@ -16,3 +16,6 @@
 # under the License.
 relationships.empty.list=\u95a2\u4fc2\u306f\u5b9a\u7fa9\u3055\u308c\u3066\u3044\u307e\u305b\u3093
 any.relationships=\u95a2\u4fc2
+otherEndType=AnyType
+otherEndKey=Key
+otherEndName=Name

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_pt_BR.properties
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_pt_BR.properties b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_pt_BR.properties
index 44f4ac8..c0c71dc 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_pt_BR.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_pt_BR.properties
@@ -16,3 +16,6 @@
 # under the License.
 relationships.empty.list=N\u00e3o h\u00e1 relacionamentos definidos
 any.relationships=Relationships
+otherEndType=AnyType
+otherEndKey=Key
+otherEndName=Name

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_ru.properties
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_ru.properties b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_ru.properties
index 93a15fb..9fcb63a 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_ru.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_ru.properties
@@ -17,3 +17,6 @@
 
 relationships.empty.list=\u0421\u0432\u044f\u0437\u0438 \u043d\u0435 \u0437\u0430\u0434\u0430\u043d\u044b
 any.relationships=\u0421\u0432\u044f\u0437\u0438
+otherEndType=AnyType
+otherEndKey=Key
+otherEndName=Name

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/common/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTO.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTO.java
index 015e18c..06dbd0d 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTO.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTO.java
@@ -43,6 +43,13 @@ public class RelationshipTO extends AbstractBaseBean {
             return this;
         }
 
+        public Builder otherEnd(final String otherEndType, final String otherEndKey, final String otherEndName) {
+            instance.setOtherEndType(otherEndType);
+            instance.setOtherEndKey(otherEndKey);
+            instance.setOtherEndName(otherEndName);
+            return this;
+        }
+
         public RelationshipTO build() {
             return instance;
         }
@@ -54,6 +61,8 @@ public class RelationshipTO extends AbstractBaseBean {
 
     private String otherEndKey;
 
+    private String otherEndName;
+
     public String getType() {
         return type;
     }
@@ -78,4 +87,11 @@ public class RelationshipTO extends AbstractBaseBean {
         this.otherEndKey = otherEndKey;
     }
 
+    public String getOtherEndName() {
+        return otherEndName;
+    }
+
+    public void setOtherEndName(final String otherEndName) {
+        this.otherEndName = otherEndName;
+    }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnyObjectDAO.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnyObjectDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnyObjectDAO.java
index f001204..f8f05f9 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnyObjectDAO.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnyObjectDAO.java
@@ -46,7 +46,7 @@ public interface AnyObjectDAO extends AnyDAO<AnyObject> {
 
     List<Group> findDynGroups(String key);
 
-    List<Relationship<Any<?>, Any<?>>> findAllRelationships(AnyObject anyObject);
+    List<Relationship<Any<?>, AnyObject>> findAllRelationships(AnyObject anyObject);
 
     Collection<Group> findAllGroups(AnyObject anyObject);
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java
index 4d8aaea..a7211de 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java
@@ -168,20 +168,20 @@ public class JPAAnyObjectDAO extends AbstractAnyDAO<AnyObject> implements AnyObj
     }
 
     @Override
-    public List<Relationship<Any<?>, Any<?>>> findAllRelationships(final AnyObject anyObject) {
-        List<Relationship<Any<?>, Any<?>>> result = new ArrayList<>();
+    public List<Relationship<Any<?>, AnyObject>> findAllRelationships(final AnyObject anyObject) {
+        List<Relationship<Any<?>, AnyObject>> result = new ArrayList<>();
 
         @SuppressWarnings("unchecked")
-        TypedQuery<Relationship<Any<?>, Any<?>>> aquery =
-                (TypedQuery<Relationship<Any<?>, Any<?>>>) entityManager().createQuery(
+        TypedQuery<Relationship<Any<?>, AnyObject>> aquery =
+                (TypedQuery<Relationship<Any<?>, AnyObject>>) entityManager().createQuery(
                         "SELECT e FROM " + JPAARelationship.class.getSimpleName()
                         + " e WHERE e.rightEnd=:anyObject OR e.leftEnd=:anyObject");
         aquery.setParameter("anyObject", anyObject);
         result.addAll(aquery.getResultList());
 
         @SuppressWarnings("unchecked")
-        TypedQuery<Relationship<Any<?>, Any<?>>> uquery =
-                (TypedQuery<Relationship<Any<?>, Any<?>>>) entityManager().createQuery(
+        TypedQuery<Relationship<Any<?>, AnyObject>> uquery =
+                (TypedQuery<Relationship<Any<?>, AnyObject>>) entityManager().createQuery(
                         "SELECT e FROM " + JPAURelationship.class.getSimpleName()
                         + " e WHERE e.rightEnd=:anyObject");
         uquery.setParameter("anyObject", anyObject);

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
index fb05d94..5a41085 100644
--- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
@@ -253,14 +253,14 @@ under the License.
   <SyncopeRole_entitlements entitlement="ANYTYPE_READ" role_id="User manager"/>
   <SyncopeRole_entitlements entitlement="ANYTYPECLASS_LIST" role_id="User manager"/>
   <SyncopeRole_entitlements entitlement="ANYTYPECLASS_READ" role_id="User manager"/>
-  <SyncopeRole_entitlements entitlement="WORKFLOW_FORM_CLAIM" role_id="User manager"/>
-  <SyncopeRole_entitlements entitlement="WORKFLOW_FORM_SUBMIT" role_id="User manager"/>
+  <SyncopeRole_entitlements entitlement="USER_REQUEST_FORM_CLAIM" role_id="User manager"/>
+  <SyncopeRole_entitlements entitlement="USER_REQUEST_FORM_SUBMIT" role_id="User manager"/>
   <SyncopeRole_Realm role_id="User manager" realm_id="e4c28e7a-9dbf-4ee7-9441-93812a0d4a28"/>
 
   <SyncopeRole id="Other"/>
   <SyncopeRole_entitlements entitlement="SCHEMA_READ" role_id="Other"/>
   <SyncopeRole_entitlements entitlement="GROUP_READ" role_id="Other"/>
-  <SyncopeRole_entitlements entitlement="WORKFLOW_FORM_CLAIM" role_id="Other"/>
+  <SyncopeRole_entitlements entitlement="USER_REQUEST_FORM_CLAIM" role_id="Other"/>
   <SyncopeRole_Realm role_id="Other" realm_id="722f3d84-9c2b-4525-8f6e-e4b82c55a36c"/>
   <SyncopeRole_Privilege role_id="Other" privilege_id="postMighty"/>
   

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/MappingManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/MappingManagerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/MappingManagerImpl.java
index b6c63e3..33077fe 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/MappingManagerImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/MappingManagerImpl.java
@@ -770,11 +770,14 @@ public class MappingManagerImpl implements MappingManager {
                 default:
             }
         } else if (intAttrName.getSchemaType() != null && attr != null) {
-            GroupableRelatableTO groupableTO = null;
-            Group group = null;
+            GroupableRelatableTO groupableTO;
+            Group group;
             if (anyTO instanceof GroupableRelatableTO && intAttrName.getMembershipOfGroup() != null) {
                 groupableTO = (GroupableRelatableTO) anyTO;
                 group = groupDAO.findByName(intAttrName.getMembershipOfGroup());
+            } else {
+                groupableTO = null;
+                group = null;
             }
 
             switch (intAttrName.getSchemaType()) {
@@ -798,29 +801,28 @@ public class MappingManagerImpl implements MappingManager {
                     if (groupableTO == null || group == null) {
                         anyTO.getPlainAttrs().add(attrTO);
                     } else {
-                        Optional<MembershipTO> membership = groupableTO.getMembership(group.getKey());
-                        if (!membership.isPresent()) {
-                            membership = Optional.of(
-                                    new MembershipTO.Builder().group(group.getKey(), group.getName()).build());
-                            groupableTO.getMemberships().add(membership.get());
-                        }
-                        membership.get().getPlainAttrs().add(attrTO);
+                        MembershipTO membership = groupableTO.getMembership(group.getKey()).orElseGet(() -> {
+                            MembershipTO newMemb = new MembershipTO.Builder().group(group.getKey()).build();
+                            groupableTO.getMemberships().add(newMemb);
+                            return newMemb;
+                        });
+                        membership.getPlainAttrs().add(attrTO);
                     }
                     break;
 
                 case DERIVED:
                     attrTO = new AttrTO();
                     attrTO.setSchema(intAttrName.getSchemaName());
+
                     if (groupableTO == null || group == null) {
                         anyTO.getDerAttrs().add(attrTO);
                     } else {
-                        Optional<MembershipTO> membership = groupableTO.getMembership(group.getKey());
-                        if (!membership.isPresent()) {
-                            membership = Optional.of(
-                                    new MembershipTO.Builder().group(group.getKey(), group.getName()).build());
-                            groupableTO.getMemberships().add(membership.get());
-                        }
-                        membership.get().getDerAttrs().add(attrTO);
+                        MembershipTO membership = groupableTO.getMembership(group.getKey()).orElseGet(() -> {
+                            MembershipTO newMemb = new MembershipTO.Builder().group(group.getKey()).build();
+                            groupableTO.getMemberships().add(newMemb);
+                            return newMemb;
+                        });
+                        membership.getDerAttrs().add(attrTO);
                     }
                     break;
 
@@ -838,13 +840,12 @@ public class MappingManagerImpl implements MappingManager {
                     if (groupableTO == null || group == null) {
                         anyTO.getVirAttrs().add(attrTO);
                     } else {
-                        Optional<MembershipTO> membership = groupableTO.getMembership(group.getKey());
-                        if (!membership.isPresent()) {
-                            membership = Optional.of(
-                                    new MembershipTO.Builder().group(group.getKey(), group.getName()).build());
-                            groupableTO.getMemberships().add(membership.get());
-                        }
-                        membership.get().getVirAttrs().add(attrTO);
+                        MembershipTO membership = groupableTO.getMembership(group.getKey()).orElseGet(() -> {
+                            MembershipTO newMemb = new MembershipTO.Builder().group(group.getKey()).build();
+                            groupableTO.getMemberships().add(newMemb);
+                            return newMemb;
+                        });
+                        membership.getVirAttrs().add(attrTO);
                     }
                     break;
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
index 5c134df..c7499a8 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
@@ -69,6 +69,7 @@ import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.VirSchema;
+import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
@@ -571,9 +572,9 @@ abstract class AbstractAnyDataBinder {
         anyTO.getResources().addAll(resources.stream().map(Entity::getKey).collect(Collectors.toSet()));
     }
 
-    protected RelationshipTO getRelationshipTO(final String relationshipType, final Any<?> otherEnd) {
+    protected RelationshipTO getRelationshipTO(final String relationshipType, final AnyObject otherEnd) {
         return new RelationshipTO.Builder().
-                type(relationshipType).otherEnd(otherEnd.getType().getKey(), otherEnd.getKey()).
+                type(relationshipType).otherEnd(otherEnd.getType().getKey(), otherEnd.getKey(), otherEnd.getName()).
                 build();
     }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
index 129d0e4..1dd6194 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
@@ -101,11 +101,12 @@ public class AnyObjectDataBinderImpl extends AbstractAnyDataBinder implements An
 
             // relationships
             anyObjectTO.getRelationships().addAll(
-                    anyObjectDAO.findAllRelationships(anyObject).stream().map(relationship -> getRelationshipTO(
-                    relationship.getType().getKey(),
-                    relationship.getLeftEnd().getKey().equals(anyObject.getKey())
-                    ? relationship.getRightEnd()
-                    : relationship.getLeftEnd())).
+                    anyObjectDAO.findAllRelationships(anyObject).stream().
+                            map(relationship -> getRelationshipTO(
+                            relationship.getType().getKey(),
+                            relationship.getLeftEnd().getKey().equals(anyObject.getKey())
+                            ? relationship.getRightEnd()
+                            : anyObject)).
                             collect(Collectors.toList()));
 
             // memberships

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
index d328c43..58c2d7d 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
@@ -602,10 +602,9 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat
                     flatMap(role -> role.getPrivileges().stream()).map(Entity::getKey).collect(Collectors.toSet()));
 
             // relationships
-            userTO.getRelationships().addAll(
-                    user.getRelationships().stream().map(relationship -> getRelationshipTO(
-                    relationship.getType().getKey(), relationship.getRightEnd())).
-                            collect(Collectors.toList()));
+            userTO.getRelationships().addAll(user.getRelationships().stream().
+                    map(relationship -> getRelationshipTO(relationship.getType().getKey(), relationship.getRightEnd())).
+                    collect(Collectors.toList()));
 
             // memberships
             userTO.getMemberships().addAll(

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/client-console/pom.xml
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/pom.xml b/ext/flowable/client-console/pom.xml
index ef2e989..f076186 100644
--- a/ext/flowable/client-console/pom.xml
+++ b/ext/flowable/client-console/pom.xml
@@ -83,35 +83,37 @@ under the License.
               <target>
                 <unzip src="${settings.localRepository}/org/flowable/flowable-ui-modeler-app/${flowable.version}/flowable-ui-modeler-app-${flowable.version}.war" dest="${flowable-modeler.directory}">
                   <patternset>
-                    <include name="WEB-INF/classes/static/**" />
-                    <include name="WEB-INF/lib/flowable-ui-modeler-logic-${flowable.version}.jar" />
+                    <include name="WEB-INF/classes/static/**"/>
+                    <include name="WEB-INF/lib/flowable-ui-modeler-logic-${flowable.version}.jar"/>
                   </patternset>
                 </unzip>
                 
                 <unzip src="${flowable-modeler.directory}/WEB-INF/lib/flowable-ui-modeler-logic-${flowable.version}.jar" dest="${flowable-modeler.directory}">
                   <patternset>
-                    <include name="stencilset_bpmn.json" />
+                    <include name="stencilset_bpmn.json"/>
                   </patternset>
                 </unzip>
                 
                 <move todir="${flowable-modeler.directory}">
                   <fileset dir="${flowable-modeler.directory}/WEB-INF/classes/static/">
-                    <include name="**" />
+                    <include name="**"/>
                   </fileset>
                 </move>
-                <delete dir="${flowable-modeler.directory}/WEB-INF" />
+                <delete dir="${flowable-modeler.directory}/WEB-INF"/>
                 
-                <replace file="${flowable-modeler.directory}/index.html" token="&lt;/head&gt;" value="&lt;script type=&quot;text/javascript&quot;&gt;window.onunload = refreshParent; function refreshParent() { window.opener.location.reload(); }&lt;/script&gt;&lt;/head&gt;" />
-                <replace file="${flowable-modeler.directory}/index.html" token=" ng-click=&quot;backToLanding()&quot;" value=" disabled=&quot;disabled&quot;" />
-                <replace file="${flowable-modeler.directory}/index.html" token="&lt;ul class=&quot;nav navbar-nav&quot; id=&quot;main-nav&quot;" value="&lt;ul class=&quot;nav navbar-nav&quot; id=&quot;main-nav&quot; style=&quot;display: none;&quot;" />
-                <replace file="${flowable-modeler.directory}/index.html" token="&lt;div class=&quot;pull-right" value="&lt;div style=&quot;display: none;&quot; class=&quot;pull-right" />
-                <replace file="${flowable-modeler.directory}/editor-app/editor.html" token="&lt;div class=&quot;btn-group pull-right&quot;" value="&lt;div style=&quot;display: none;&quot; class=&quot;btn-group pull-right&quot;" />
-                <replace file="${flowable-modeler.directory}/editor-app/configuration/toolbar-default-actions.js" token="$location.path('/processes');" value="window.close();" />
+                <replace file="${flowable-modeler.directory}/index.html" token="&lt;/head&gt;" value="&lt;script type=&quot;text/javascript&quot;&gt;window.onunload = refreshParent; function refreshParent() { window.opener.location.reload(); }&lt;/script&gt;&lt;/head&gt;"/>
+                <replace file="${flowable-modeler.directory}/index.html" token=" ng-click=&quot;backToLanding()&quot;" value=" disabled=&quot;disabled&quot;"/>
+                <replace file="${flowable-modeler.directory}/index.html" token="&lt;ul class=&quot;nav navbar-nav&quot; id=&quot;main-nav&quot;" value="&lt;ul class=&quot;nav navbar-nav&quot; id=&quot;main-nav&quot; style=&quot;display: none;&quot;"/>
+                <replace file="${flowable-modeler.directory}/index.html" token="&lt;div class=&quot;pull-right" value="&lt;div style=&quot;display: none;&quot; class=&quot;pull-right"/>
+                <replace file="${flowable-modeler.directory}/editor-app/editor.html" token="&lt;div class=&quot;btn-group pull-right&quot;" value="&lt;div style=&quot;display: none;&quot; class=&quot;btn-group pull-right&quot;"/>
+                <replace file="${flowable-modeler.directory}/editor-app/configuration/toolbar-default-actions.js" token="$location.path('/processes');" value="window.close();"/>
  
-                <copy file="${basedir}/src/main/resources/app-cfg.js" todir="${flowable-modeler.directory}/scripts" overwrite="true" />
-                <copy file="${basedir}/src/main/resources/url-config.js" todir="${flowable-modeler.directory}/editor-app/configuration" overwrite="true" />
-                <copy file="${basedir}/src/main/resources/toolbar.js" todir="${flowable-modeler.directory}/editor-app/configuration" overwrite="true" />
-                <copy file="${basedir}/src/main/resources/save-model.html" todir="${flowable-modeler.directory}/editor-app/popups" overwrite="true" />
+                <copy file="${basedir}/src/main/resources/app-cfg.js" todir="${flowable-modeler.directory}/scripts" overwrite="true"/>
+                <copy file="${basedir}/src/main/resources/url-config.js" todir="${flowable-modeler.directory}/editor-app/configuration" overwrite="true"/>
+                <copy file="${basedir}/src/main/resources/toolbar.js" todir="${flowable-modeler.directory}/editor-app/configuration" overwrite="true"/>
+                <copy file="${basedir}/src/main/resources/save-model.html" todir="${flowable-modeler.directory}/editor-app/popups" overwrite="true"/>
+
+                <patch patchfile="${basedir}/src/main/resources/dropdown.diff" dir="${flowable-modeler.directory}" strip="1"/>
               </target>
             </configuration>
             <goals>

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/approvals/Approval.java
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/approvals/Approval.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/approvals/Approval.java
index 3bea1a9..6a66afc 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/approvals/Approval.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/approvals/Approval.java
@@ -45,7 +45,6 @@ import org.apache.wicket.markup.html.list.ListView;
 import org.apache.wicket.markup.html.panel.Panel;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.LoadableDetachableModel;
-import org.apache.wicket.model.Model;
 import org.apache.wicket.model.PropertyModel;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -103,7 +102,7 @@ public abstract class Approval extends Panel {
                         break;
 
                     case Date:
-                        final FastDateFormat formatter = FastDateFormat.getInstance(prop.getDatePattern());
+                        FastDateFormat formatter = FastDateFormat.getInstance(prop.getDatePattern());
                         field = new AjaxDateTimeFieldPanel("value", label, new PropertyModel<Date>(prop, "value") {
 
                             private static final long serialVersionUID = -3743432456095828573L;
@@ -111,11 +110,9 @@ public abstract class Approval extends Panel {
                             @Override
                             public Date getObject() {
                                 try {
-                                    if (StringUtils.isBlank(prop.getValue())) {
-                                        return null;
-                                    } else {
-                                        return formatter.parse(prop.getValue());
-                                    }
+                                    return StringUtils.isBlank(prop.getValue())
+                                            ? null
+                                            : formatter.parse(prop.getValue());
                                 } catch (ParseException e) {
                                     LOG.error("Unparsable date: {}", prop.getValue(), e);
                                     return null;
@@ -131,19 +128,17 @@ public abstract class Approval extends Panel {
                         break;
 
                     case Enum:
-                        MapChoiceRenderer<String, String> enumCR = new MapChoiceRenderer<>(prop.getEnumValues());
-
                         field = new AjaxDropDownChoicePanel(
                                 "value", label, new PropertyModel<String>(prop, "value"), false).
-                                setChoiceRenderer(enumCR).setChoices(new Model<ArrayList<String>>() {
-
-                            private static final long serialVersionUID = -858521070366432018L;
+                                setChoiceRenderer(new MapChoiceRenderer(prop.getEnumValues())).
+                                setChoices(new ArrayList<>(prop.getEnumValues().keySet()));
+                        break;
 
-                            @Override
-                            public ArrayList<String> getObject() {
-                                return new ArrayList<>(prop.getEnumValues().keySet());
-                            }
-                        });
+                    case Dropdown:
+                        field = new AjaxDropDownChoicePanel(
+                                "value", label, new PropertyModel<String>(prop, "value"), false).
+                                setChoiceRenderer(new MapChoiceRenderer(prop.getDropdownValues())).
+                                setChoices(new ArrayList<>(prop.getDropdownValues().keySet()));
                         break;
 
                     case Long:

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalDirectoryPanel.java
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalDirectoryPanel.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalDirectoryPanel.java
index e16f5c2..577ea65 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalDirectoryPanel.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalDirectoryPanel.java
@@ -113,7 +113,7 @@ public class ApprovalDirectoryPanel
 
         initResultTable();
 
-        MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, FlowableEntitlement.WORKFLOW_FORM_SUBMIT);
+        MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, FlowableEntitlement.USER_REQUEST_FORM_SUBMIT);
     }
 
     @Override
@@ -121,7 +121,7 @@ public class ApprovalDirectoryPanel
         List<IColumn<UserRequestForm, String>> columns = new ArrayList<>();
 
         columns.add(new PropertyColumn<>(
-                new ResourceModel("taskId"), "taskId", "taskId"));
+                new ResourceModel("bpmnProcess"), "bpmnProcess", "bpmnProcess"));
         columns.add(new PropertyColumn<>(
                 new ResourceModel("key"), "formKey", "formKey"));
         columns.add(new PropertyColumn<>(
@@ -151,7 +151,7 @@ public class ApprovalDirectoryPanel
                 ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
                 target.add(container);
             }
-        }, ActionLink.ActionType.CLAIM, FlowableEntitlement.WORKFLOW_FORM_CLAIM);
+        }, ActionLink.ActionType.CLAIM, FlowableEntitlement.USER_REQUEST_FORM_CLAIM);
 
         panel.add(new ActionLink<UserRequestForm>() {
 
@@ -190,7 +190,7 @@ public class ApprovalDirectoryPanel
                         equals(model.getObject().getOwner());
             }
 
-        }, ActionLink.ActionType.MANAGE_APPROVAL, FlowableEntitlement.WORKFLOW_FORM_READ);
+        }, ActionLink.ActionType.MANAGE_APPROVAL, FlowableEntitlement.USER_REQUEST_FORM_SUBMIT);
 
         // SYNCOPE-1200 edit user while in approval state
         panel.add(new ActionLink<UserRequestForm>() {
@@ -239,7 +239,7 @@ public class ApprovalDirectoryPanel
                         equals(model.getObject().getOwner());
             }
 
-        }, ActionLink.ActionType.EDIT_APPROVAL, FlowableEntitlement.WORKFLOW_FORM_SUBMIT);
+        }, ActionLink.ActionType.EDIT_APPROVAL, FlowableEntitlement.USER_REQUEST_FORM_SUBMIT);
 
         return panel;
     }
@@ -326,8 +326,6 @@ public class ApprovalDirectoryPanel
         protected Serializable onApplyInternal(final AnyWrapper<UserTO> modelObject) {
             UserTO inner = modelObject.getInnerObject();
 
-            ProvisioningResult<UserTO> result;
-
             UserPatch patch = AnyOperations.diff(inner, formTO.getUserTO(), false);
 
             if (StringUtils.isNotBlank(inner.getPassword())) {
@@ -337,16 +335,15 @@ public class ApprovalDirectoryPanel
                         build();
                 patch.setPassword(passwordPatch);
             }
+
             // update just if it is changed
+            ProvisioningResult<UserTO> result;
             if (patch.isEmpty()) {
                 result = new ProvisioningResult<>();
                 result.setEntity(inner);
             } else {
                 result = userRestClient.update(getOriginalItem().getInnerObject().getETagValue(), patch);
-                UserRequestForm workFlowTO = restClient.getForms(result.getEntity().getKey()).get(0);
-                if (workFlowTO != null) {
-                    claimForm(workFlowTO.getTaskId());
-                }
+                restClient.getForm(result.getEntity().getKey()).ifPresent(form -> claimForm(form.getTaskId()));
             }
 
             return result;

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/pages/Flowable.java
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/pages/Flowable.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/pages/Flowable.java
index 562aad1..8062a77 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/pages/Flowable.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/pages/Flowable.java
@@ -23,7 +23,7 @@ import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.annotations.ExtPage;
 import org.apache.syncope.client.console.panels.BpmnProcessDirectoryPanel;
 import org.apache.syncope.client.console.wizards.WizardMgtPanel;
-import org.apache.syncope.common.lib.to.BpmnProcessTO;
+import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.syncope.common.lib.types.FlowableEntitlement;
 import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
 import org.apache.wicket.markup.html.WebMarkupContainer;
@@ -48,7 +48,7 @@ public class Flowable extends BaseExtPage {
         disabled.setOutputMarkupPlaceholderTag(true);
         content.add(disabled);
 
-        WizardMgtPanel<BpmnProcessTO> bpmnProcessesPanel = new BpmnProcessDirectoryPanel.Builder(getPageReference()) {
+        WizardMgtPanel<BpmnProcess> bpmnProcessesPanel = new BpmnProcessDirectoryPanel.Builder(getPageReference()) {
 
             private static final long serialVersionUID = -5960765294082359003L;
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/BpmnProcessDirectoryPanel.java
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/BpmnProcessDirectoryPanel.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/BpmnProcessDirectoryPanel.java
index b7914bb..8f4d566 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/BpmnProcessDirectoryPanel.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/BpmnProcessDirectoryPanel.java
@@ -43,7 +43,7 @@ import org.apache.syncope.client.console.wicket.markup.html.form.XMLEditorPanel;
 import org.apache.syncope.client.console.wizards.AjaxWizardBuilder;
 import org.apache.syncope.client.console.wizards.WizardMgtPanel;
 import org.apache.syncope.common.lib.SyncopeClientException;
-import org.apache.syncope.common.lib.to.BpmnProcessTO;
+import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.syncope.common.lib.types.FlowableEntitlement;
 import org.apache.wicket.Page;
 import org.apache.wicket.PageReference;
@@ -65,7 +65,7 @@ import org.apache.wicket.request.mapper.parameter.PageParameters;
 import org.apache.wicket.util.io.IOUtils;
 
 public class BpmnProcessDirectoryPanel extends DirectoryPanel<
-        BpmnProcessTO, BpmnProcessTO, BpmProcessDataProvider, BpmnProcessRestClient> {
+        BpmnProcess, BpmnProcess, BpmProcessDataProvider, BpmnProcessRestClient> {
 
     private static final long serialVersionUID = 2705668831139984998L;
 
@@ -76,13 +76,13 @@ public class BpmnProcessDirectoryPanel extends DirectoryPanel<
     protected BpmnProcessDirectoryPanel(final String id, final Builder builder) {
         super(id, builder);
 
-        this.addNewItemPanelBuilder(new AjaxWizardBuilder<BpmnProcessTO>(new BpmnProcessTO(), pageRef) {
+        this.addNewItemPanelBuilder(new AjaxWizardBuilder<BpmnProcess>(new BpmnProcess(), pageRef) {
 
             private static final long serialVersionUID = 1633859795677053912L;
 
             @Override
             protected WizardModel buildModelSteps(
-                    final BpmnProcessTO modelObject, final WizardModel wizardModel) {
+                    final BpmnProcess modelObject, final WizardModel wizardModel) {
 
                 return wizardModel;
             }
@@ -133,8 +133,8 @@ public class BpmnProcessDirectoryPanel extends DirectoryPanel<
     }
 
     @Override
-    protected List<IColumn<BpmnProcessTO, String>> getColumns() {
-        List<IColumn<BpmnProcessTO, String>> columns = new ArrayList<>();
+    protected List<IColumn<BpmnProcess, String>> getColumns() {
+        List<IColumn<BpmnProcess, String>> columns = new ArrayList<>();
 
         columns.add(new PropertyColumn<>(new ResourceModel("key"), "key"));
         columns.add(new PropertyColumn<>(new ResourceModel("name"), "name", "name"));
@@ -143,15 +143,15 @@ public class BpmnProcessDirectoryPanel extends DirectoryPanel<
     }
 
     @Override
-    public ActionsPanel<BpmnProcessTO> getActions(final IModel<BpmnProcessTO> model) {
-        final ActionsPanel<BpmnProcessTO> panel = super.getActions(model);
+    public ActionsPanel<BpmnProcess> getActions(final IModel<BpmnProcess> model) {
+        final ActionsPanel<BpmnProcess> panel = super.getActions(model);
 
-        panel.add(new ActionLink<BpmnProcessTO>() {
+        panel.add(new ActionLink<BpmnProcess>() {
 
             private static final long serialVersionUID = -184018732772021627L;
 
             @Override
-            public void onClick(final AjaxRequestTarget target, final BpmnProcessTO ignore) {
+            public void onClick(final AjaxRequestTarget target, final BpmnProcess ignore) {
                 final IModel<String> wfDefinition = new Model<>();
                 try {
                     wfDefinition.setObject(IOUtils.toString(restClient.getDefinition(
@@ -189,12 +189,12 @@ public class BpmnProcessDirectoryPanel extends DirectoryPanel<
             }
         }, ActionLink.ActionType.EDIT, FlowableEntitlement.BPMN_PROCESS_SET);
 
-        panel.add(new ActionLink<BpmnProcessTO>() {
+        panel.add(new ActionLink<BpmnProcess>() {
 
             private static final long serialVersionUID = 3109256773218160485L;
 
             @Override
-            public void onClick(final AjaxRequestTarget target, final BpmnProcessTO ignore) {
+            public void onClick(final AjaxRequestTarget target, final BpmnProcess ignore) {
                 modal.header(Model.of(model.getObject().getKey()));
                 modal.setContent(new ImageModalPanel<>(
                         modal, restClient.getDiagram(model.getObject().getKey()), pageRef));
@@ -203,7 +203,7 @@ public class BpmnProcessDirectoryPanel extends DirectoryPanel<
             }
         }, ActionLink.ActionType.VIEW, FlowableEntitlement.BPMN_PROCESS_GET);
 
-        panel.add(new ActionLink<BpmnProcessTO>() {
+        panel.add(new ActionLink<BpmnProcess>() {
 
             private static final long serialVersionUID = -184018732772021627L;
 
@@ -222,22 +222,22 @@ public class BpmnProcessDirectoryPanel extends DirectoryPanel<
             }
 
             @Override
-            public void onClick(final AjaxRequestTarget target, final BpmnProcessTO ignore) {
+            public void onClick(final AjaxRequestTarget target, final BpmnProcess ignore) {
                 // do nothing
             }
         }, ActionLink.ActionType.EXTERNAL_EDITOR, FlowableEntitlement.BPMN_PROCESS_SET);
 
-        panel.add(new ActionLink<BpmnProcessTO>() {
+        panel.add(new ActionLink<BpmnProcess>() {
 
             private static final long serialVersionUID = -7978723352517770644L;
 
             @Override
-            protected boolean statusCondition(final BpmnProcessTO modelObject) {
+            protected boolean statusCondition(final BpmnProcess modelObject) {
                 return !modelObject.isUserWorkflow();
             }
 
             @Override
-            public void onClick(final AjaxRequestTarget target, final BpmnProcessTO ignore) {
+            public void onClick(final AjaxRequestTarget target, final BpmnProcess ignore) {
                 try {
                     restClient.deleteDefinition(model.getObject().getKey());
                     SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED));
@@ -260,7 +260,7 @@ public class BpmnProcessDirectoryPanel extends DirectoryPanel<
     }
 
     public abstract static class Builder
-            extends DirectoryPanel.Builder<BpmnProcessTO, BpmnProcessTO, BpmnProcessRestClient> {
+            extends DirectoryPanel.Builder<BpmnProcess, BpmnProcess, BpmnProcessRestClient> {
 
         private static final long serialVersionUID = 5088962796986706805L;
 
@@ -269,16 +269,16 @@ public class BpmnProcessDirectoryPanel extends DirectoryPanel<
         }
 
         @Override
-        protected WizardMgtPanel<BpmnProcessTO> newInstance(final String id, final boolean wizardInModal) {
+        protected WizardMgtPanel<BpmnProcess> newInstance(final String id, final boolean wizardInModal) {
             return new BpmnProcessDirectoryPanel(id, this);
         }
     }
 
-    protected class BpmProcessDataProvider extends DirectoryDataProvider<BpmnProcessTO> {
+    protected class BpmProcessDataProvider extends DirectoryDataProvider<BpmnProcess> {
 
         private static final long serialVersionUID = 1764153405387687592L;
 
-        private final SortableDataProviderComparator<BpmnProcessTO> comparator;
+        private final SortableDataProviderComparator<BpmnProcess> comparator;
 
         private final BpmnProcessRestClient restClient = new BpmnProcessRestClient();
 
@@ -289,8 +289,8 @@ public class BpmnProcessDirectoryPanel extends DirectoryPanel<
         }
 
         @Override
-        public Iterator<BpmnProcessTO> iterator(final long first, final long count) {
-            List<BpmnProcessTO> result = restClient.getDefinitions();
+        public Iterator<BpmnProcess> iterator(final long first, final long count) {
+            List<BpmnProcess> result = restClient.getDefinitions();
             Collections.sort(result, comparator);
             return result.subList((int) first, (int) first + (int) count).iterator();
         }
@@ -301,7 +301,7 @@ public class BpmnProcessDirectoryPanel extends DirectoryPanel<
         }
 
         @Override
-        public IModel<BpmnProcessTO> model(final BpmnProcessTO object) {
+        public IModel<BpmnProcess> model(final BpmnProcess object) {
             return new CompoundPropertyModel<>(object);
         }
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/AbstractBpmnProcessResource.java
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/AbstractBpmnProcessResource.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/AbstractBpmnProcessResource.java
index 91a42e3..7ad5f24 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/AbstractBpmnProcessResource.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/AbstractBpmnProcessResource.java
@@ -21,7 +21,7 @@ package org.apache.syncope.client.console.resources;
 import javax.ws.rs.NotFoundException;
 import org.apache.syncope.client.console.commons.Constants;
 import org.apache.syncope.client.console.rest.BpmnProcessRestClient;
-import org.apache.syncope.common.lib.to.BpmnProcessTO;
+import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.wicket.request.resource.AbstractResource;
 import org.apache.wicket.util.string.StringValue;
 import org.slf4j.Logger;
@@ -35,10 +35,10 @@ abstract class AbstractBpmnProcessResource extends AbstractResource {
 
     protected final BpmnProcessRestClient restClient = new BpmnProcessRestClient();
 
-    protected BpmnProcessTO getBpmnProcess(final Attributes attributes) {
+    protected BpmnProcess getBpmnProcess(final Attributes attributes) {
         StringValue modelId = attributes.getRequest().getQueryParameters().getParameterValue(Constants.MODEL_ID_PARAM);
 
-        BpmnProcessTO bpmnProcess = modelId == null || modelId.isNull()
+        BpmnProcess bpmnProcess = modelId == null || modelId.isNull()
                 ? null
                 : restClient.getDefinitions().stream().
                         filter(object -> modelId.toString().equals(object.getModelId())).findAny().orElse(null);

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/BpmnProcessGETResource.java
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/BpmnProcessGETResource.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/BpmnProcessGETResource.java
index ba34502..dfe633e 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/BpmnProcessGETResource.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/BpmnProcessGETResource.java
@@ -23,7 +23,7 @@ import java.nio.charset.StandardCharsets;
 import javax.ws.rs.core.MediaType;
 import org.apache.syncope.client.console.annotations.Resource;
 import org.apache.syncope.client.console.rest.BpmnProcessRestClient;
-import org.apache.syncope.common.lib.to.BpmnProcessTO;
+import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.wicket.util.io.IOUtils;
 
 /**
@@ -36,7 +36,7 @@ public class BpmnProcessGETResource extends AbstractBpmnProcessResource {
 
     @Override
     protected ResourceResponse newResourceResponse(final Attributes attributes) {
-        final BpmnProcessTO toGet = getBpmnProcess(attributes);
+        final BpmnProcess toGet = getBpmnProcess(attributes);
 
         ResourceResponse response = new ResourceResponse();
         response.disableCaching();

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/BpmnProcessPUTResource.java
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/BpmnProcessPUTResource.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/BpmnProcessPUTResource.java
index d583396..1031ce8 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/BpmnProcessPUTResource.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/BpmnProcessPUTResource.java
@@ -24,7 +24,7 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import org.apache.cxf.common.util.UrlUtils;
 import org.apache.syncope.client.console.annotations.Resource;
-import org.apache.syncope.common.lib.to.BpmnProcessTO;
+import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.wicket.util.io.IOUtils;
 
 /**
@@ -52,7 +52,7 @@ public class BpmnProcessPUTResource extends AbstractBpmnProcessResource {
             LOG.error("Could not extract BPMN process", e);
         }
 
-        BpmnProcessTO toSet = getBpmnProcess(attributes);
+        BpmnProcess toSet = getBpmnProcess(attributes);
 
         if (definition == null || toSet == null) {
             return new ResourceResponse().setStatusCode(Response.Status.BAD_REQUEST.getStatusCode()).

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/rest/BpmnProcessRestClient.java
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/rest/BpmnProcessRestClient.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/rest/BpmnProcessRestClient.java
index 2b7c368..9e88431 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/rest/BpmnProcessRestClient.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/rest/BpmnProcessRestClient.java
@@ -25,7 +25,7 @@ import javax.ws.rs.core.Response;
 import org.apache.cxf.helpers.IOUtils;
 import org.apache.cxf.jaxrs.client.WebClient;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
-import org.apache.syncope.common.lib.to.BpmnProcessTO;
+import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.service.BpmnProcessService;
 
@@ -37,7 +37,7 @@ public class BpmnProcessRestClient extends BaseRestClient {
         return SyncopeConsoleSession.get().getService(mediaType, BpmnProcessService.class);
     }
 
-    public List<BpmnProcessTO> getDefinitions() {
+    public List<BpmnProcess> getDefinitions() {
         return getService(BpmnProcessService.class).list();
     }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/rest/UserRequestRestClient.java
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/rest/UserRequestRestClient.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/rest/UserRequestRestClient.java
index 555555b..e3a179f 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/rest/UserRequestRestClient.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/rest/UserRequestRestClient.java
@@ -19,6 +19,8 @@
 package org.apache.syncope.client.console.rest;
 
 import java.util.List;
+import java.util.Optional;
+import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.to.UserRequestForm;
 import org.apache.syncope.common.rest.api.beans.UserRequestFormQuery;
 import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
@@ -40,8 +42,13 @@ public class UserRequestRestClient extends BaseRestClient {
                 getResult();
     }
 
-    public List<UserRequestForm> getForms(final String userKey) {
-        return getService(UserRequestService.class).getForms(userKey);
+    public Optional<UserRequestForm> getForm(final String userKey) {
+        PagedResult<UserRequestForm> forms = getService(UserRequestService.class).
+                getForms(new UserRequestFormQuery.Builder().user(userKey).page(1).size(1).build());
+        UserRequestForm form = forms.getResult().isEmpty()
+                ? null
+                : forms.getResult().get(0);
+        return Optional.ofNullable(form);
     }
 
     public UserRequestForm claimForm(final String taskKey) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/widgets/ApprovalsWidget.java
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/widgets/ApprovalsWidget.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/widgets/ApprovalsWidget.java
index 8f8329e..9a9fd2d 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/widgets/ApprovalsWidget.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/widgets/ApprovalsWidget.java
@@ -87,8 +87,7 @@ public class ApprovalsWidget extends ExtAlertWidget<UserRequestForm> {
 
     @Override
     protected int getLatestAlertsSize() {
-        return SyncopeConsoleSession.get().owns(FlowableEntitlement.WORKFLOW_FORM_LIST)
-                && SyncopeConsoleSession.get().owns(FlowableEntitlement.WORKFLOW_FORM_READ)
+        return SyncopeConsoleSession.get().owns(FlowableEntitlement.USER_REQUEST_FORM_LIST)
                 ? restClient.countForms()
                 : 0;
     }
@@ -102,9 +101,7 @@ public class ApprovalsWidget extends ExtAlertWidget<UserRequestForm> {
             @Override
             public List<UserRequestForm> getObject() {
                 List<UserRequestForm> updatedApprovals;
-                if (SyncopeConsoleSession.get().owns(FlowableEntitlement.WORKFLOW_FORM_LIST)
-                        && SyncopeConsoleSession.get().owns(FlowableEntitlement.WORKFLOW_FORM_READ)) {
-
+                if (SyncopeConsoleSession.get().owns(FlowableEntitlement.USER_REQUEST_FORM_LIST)) {
                     updatedApprovals = restClient.getForms(1, MAX_SIZE, new SortParam<>("createTime", true));
                 } else {
                     updatedApprovals = Collections.<UserRequestForm>emptyList();
@@ -118,7 +115,8 @@ public class ApprovalsWidget extends ExtAlertWidget<UserRequestForm> {
     @Override
     protected AbstractLink getEventsLink(final String linkid) {
         BookmarkablePageLink<Approvals> approvals = BookmarkablePageLinkBuilder.build(linkid, Approvals.class);
-        MetaDataRoleAuthorizationStrategy.authorize(approvals, WebPage.ENABLE, FlowableEntitlement.WORKFLOW_FORM_LIST);
+        MetaDataRoleAuthorizationStrategy.authorize(
+                approvals, WebPage.ENABLE, FlowableEntitlement.USER_REQUEST_FORM_LIST);
         return approvals;
     }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/client-console/src/main/resources/dropdown.diff
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/resources/dropdown.diff b/ext/flowable/client-console/src/main/resources/dropdown.diff
new file mode 100644
index 0000000..80fe1e7
--- /dev/null
+++ b/ext/flowable/client-console/src/main/resources/dropdown.diff
@@ -0,0 +1,139 @@
+# 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.
+diff --git a/editor-app/configuration/properties-form-properties-controller.js b/editor-app/configuration/properties-form-properties-controller.js
+index ca8f051..fc02151 100644
+--- a/editor-app/configuration/properties-form-properties-controller.js
++++ b/editor-app/configuration/properties-form-properties-controller.js
+@@ -102,6 +102,20 @@ angular.module('flowableModeler').controller('FlowableFormPropertiesPopupCtrl',
+                 { field: 'name', displayName: $scope.labels.nameLabel}]
+             }
+ 
++            $scope.dropdownGridOptions = {
++    		    data: $scope.enumValues,
++                headerRowHeight: 28,
++                enableRowSelection: true,
++                enableRowHeaderSelection: false,
++                multiSelect: false,
++                modifierKeysToMultiSelect: false,
++                enableHorizontalScrollbar: 0,
++                enableColumnMenus: false,
++                enableSorting: false,
++                columnDefs: [{ field: 'id', displayName: $scope.labels.idLabel },
++                { field: 'name', displayName: $scope.labels.nameLabel}]
++            }
++
+             $scope.gridOptions.onRegisterApi = function (gridApi) {
+                 //set gridApi on scope
+                 $scope.gridApi = gridApi;
+@@ -124,6 +138,13 @@ angular.module('flowableModeler').controller('FlowableFormPropertiesPopupCtrl',
+                     $scope.selectedEnumValue = row.entity;
+                 });
+             };
++            $scope.dropdownGridOptions.onRegisterApi = function (gridApi) {
++                //set gridApi on scope
++                $scope.dropdownGridApi = gridApi;
++                gridApi.selection.on.rowSelectionChanged($scope, function (row) {
++                    $scope.selectedDropdownValue = row.entity;
++                });
++            };
+         });
+ 
+         // Handler for when the value of the type dropdown changes
+@@ -143,7 +164,12 @@ angular.module('flowableModeler').controller('FlowableFormPropertiesPopupCtrl',
+                 for (var i = 0; i < $scope.selectedProperty.enumValues.length; i++) {
+                     $scope.enumValues.push($scope.selectedProperty.enumValues[i]);
+                 }
+-                
++            } else if ($scope.selectedProperty.type === 'dropdown') {
++                $scope.selectedProperty.enumValues = [ {id: 'dropdownValueProvider', name: 'provider'}];
++                $scope.enumValues.length = 0;
++                for (var i = 0; i < $scope.selectedProperty.enumValues.length; i++) {
++                    $scope.enumValues.push($scope.selectedProperty.enumValues[i]);
++                }
+             } else {
+                 delete $scope.selectedProperty.enumValues;
+                 $scope.enumValues.length = 0;
+@@ -324,4 +350,4 @@ angular.module('flowableModeler').controller('FlowableFormPropertiesPopupCtrl',
+         };
+ 
+     }])
+-;
+\ No newline at end of file
++;
+diff --git a/editor-app/configuration/properties/form-properties-popup.html b/editor-app/configuration/properties/form-properties-popup.html
+index 17c5ca8..57a24e5 100644
+--- a/editor-app/configuration/properties/form-properties-popup.html
++++ b/editor-app/configuration/properties/form-properties-popup.html
+@@ -42,6 +42,7 @@
+                                     <option>boolean</option>
+                                     <option>date</option>
+                                     <option>enum</option>
++                                    <option>dropdown</option>
+                                 </select>
+             				</div>
+                            	<div class="form-group" ng-show="selectedProperty.datePattern">
+@@ -80,6 +81,38 @@
+                                     </div>
+                                 </div>    
+             				</div>
++                            <div ng-show="selectedProperty.type == 'dropdown'" style="padding-bottom:10px">
++                                <div class="row row-no-gutter">
++                                    <div class="col-xs-6">
++                                        <div ng-if="translationsRetrieved" class="kis-listener-grid" ui-grid="dropdownGridOptions" ui-grid-selection ui-grid-auto-resize></div>
++                                        <!--<div class="pull-right">
++                                            <div class="btn-group">
++                                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.MOVE.UP | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="moveEnumValueUp()"><i class="glyphicon glyphicon-arrow-up"></i></a>
++                                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.MOVE.DOWN | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="moveEnumValueDown()"><i class="glyphicon glyphicon-arrow-down"></i></a>
++                                            </div>
++                                            <div class="btn-group">
++                                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.ADD | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="addNewEnumValue()"><i class="glyphicon glyphicon-plus"></i></a>
++                                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.REMOVE | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="removeEnumValue()"><i class="glyphicon glyphicon-minus"></i></a>
++                                            </div>
++                                        </div>-->
++                                    </div>
++                            
++                                    <div class="col-xs-6">
++                                        <div ng-show="selectedDropdownValue">
++                            
++                                            <div class="form-group">
++                                                <label for="classField">{{'PROPERTY.FORMPROPERTIES.VALUES.ID' | translate}}</label>
++                                                <input type="text" id="classField" class="form-control" ng-model="selectedDropdownValue.id" placeholder="{{'PROPERTY.FORMPROPERTIES.VALUES.ID.PLACEHOLDER' | translate}}" />
++                                            </div>
++                                            <div class="form-group">
++                                                <label for="classField">{{'PROPERTY.FORMPROPERTIES.VALUES.NAME' | translate}}</label>
++                                                <input type="text" id="classField" class="form-control" ng-model="selectedDropdownValue.name" placeholder="{{'PROPERTY.FORMPROPERTIES.VALUES.NAME.PLACEHOLDER' | translate}}" />
++                                            </div>
++                                        </div>
++                                        <div ng-show="!selectedDropdownValue" class="muted no-property-selected" translate>PROPERTY.FORMPROPERTIES.DROPDOWNVALUES.EMPTY</div>
++                                    </div>
++                                </div>    
++            				</div>
+                             <div class="form-group">
+             			   		<label for="expressionField">{{'PROPERTY.FORMPROPERTIES.EXPRESSION' | translate}}</label>
+             			   		<input id="expressionField" class="form-control" type="text" ng-model="selectedProperty.expression" placeholder="{{'PROPERTY.FORMPROPERTIES.EXPRESSION.PLACEHOLDER' | translate }}" />
+diff --git a/i18n/en.json b/i18n/en.json
+index 7d0e40a..1067dad 100644
+--- a/i18n/en.json
++++ b/i18n/en.json
+@@ -824,6 +824,7 @@
+     "PROPERTY.FORMPROPERTIES.DATEPATTERN.PLACEHOLDER" : "Enter date pattern",
+     "PROPERTY.FORMPROPERTIES.VALUES" : "Values",
+     "PROPERTY.FORMPROPERTIES.ENUMVALUES.EMPTY" : "No enum value selected",
++    "PROPERTY.FORMPROPERTIES.DROPDOWNVALUES.EMPTY" : "No dropdown value selected",
+     "PROPERTY.FORMPROPERTIES.VALUES.ID" : "Id",
+     "PROPERTY.FORMPROPERTIES.VALUES.NAME" : "Name",
+     "PROPERTY.FORMPROPERTIES.VALUES.ID.PLACEHOLDER" : "Enter id of a value",

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals.properties
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals.properties b/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals.properties
index 1ea8063..84b9c85 100644
--- a/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals.properties
+++ b/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals.properties
@@ -14,7 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-taskId=Task
+bpmnProcess=User Request
 key=Key
 description=Description
 createTime=Create Time

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_it.properties
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_it.properties b/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_it.properties
index 7e12ce5..f25abd8 100644
--- a/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_it.properties
+++ b/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_it.properties
@@ -14,7 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-taskId=Task
+bpmnProcess=User Request
 key=Chiave
 description=Descrizione
 createTime=Data di creazione

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ja.properties
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ja.properties b/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ja.properties
index b2ff626..dcca6e1 100644
--- a/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ja.properties
+++ b/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ja.properties
@@ -14,7 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-taskId=\u30bf\u30b9\u30af
+bpmnProcess=User Request
 key=\u30ad\u30fc
 description=\u8aac\u660e
 createTime=\u4f5c\u6210\u6642\u523b

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_pt_BR.properties
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_pt_BR.properties b/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_pt_BR.properties
index e5ff74c..88a9e05 100644
--- a/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_pt_BR.properties
+++ b/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_pt_BR.properties
@@ -14,7 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-taskId=Tarefa
+bpmnProcess=User Request
 key=Chave
 description=Descri\u00e7\u00e3o
 createTime=Tempo de Cria\u00e7\u00e3o

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ru.properties
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ru.properties b/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ru.properties
index 6f4fb58..2e7a199 100644
--- a/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ru.properties
+++ b/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ru.properties
@@ -16,7 +16,7 @@
 # under the License.
 #
 # taskId=\u00d0\u0097\u00d0\u00b0\u00d1\u008f\u00d0\u00b2\u00d0\u00ba\u00d0\u00b0
-taskId=\u0417\u0430\u044f\u0432\u043a\u0430
+bpmnProcess=User Request
 # key=\u00d0\u0098\u00d0\u00b4\u00d0\u00b5\u00d0\u00bd\u00d1\u0082\u00d0\u00b8\u00d1\u0084\u00d0\u00b8\u00d0\u00ba\u00d0\u00b0\u00d1\u0082\u00d0\u00be\u00d1\u0080
 key=\u0418\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440
 # description=\u00d0\u009e\u00d0\u00bf\u00d0\u00b8\u00d1\u0081\u00d0\u00b0\u00d0\u00bd\u00d0\u00b8\u00d0\u00b5

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/BpmnProcess.java
----------------------------------------------------------------------
diff --git a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/BpmnProcess.java b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/BpmnProcess.java
new file mode 100644
index 0000000..35a7883
--- /dev/null
+++ b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/BpmnProcess.java
@@ -0,0 +1,72 @@
+/*
+ * 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.common.lib.to;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+
+@XmlRootElement(name = "bpmnProcess")
+@XmlType
+public class BpmnProcess extends AbstractBaseBean implements EntityTO {
+
+    private static final long serialVersionUID = -7044543391316529128L;
+
+    private String key;
+
+    private String modelId;
+
+    private String name;
+
+    private boolean userWorkflow;
+
+    @Override
+    public String getKey() {
+        return key;
+    }
+
+    @Override
+    public void setKey(final String key) {
+        this.key = key;
+    }
+
+    public String getModelId() {
+        return modelId;
+    }
+
+    public void setModelId(final String modelId) {
+        this.modelId = modelId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    public boolean isUserWorkflow() {
+        return userWorkflow;
+    }
+
+    public void setUserWorkflow(final boolean userWorkflow) {
+        this.userWorkflow = userWorkflow;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/BpmnProcessTO.java
----------------------------------------------------------------------
diff --git a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/BpmnProcessTO.java b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/BpmnProcessTO.java
deleted file mode 100644
index bb67e3e..0000000
--- a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/BpmnProcessTO.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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.common.lib.to;
-
-import javax.xml.bind.annotation.XmlRootElement;
-import javax.xml.bind.annotation.XmlType;
-import org.apache.syncope.common.lib.AbstractBaseBean;
-
-@XmlRootElement(name = "bpmnProcess")
-@XmlType
-public class BpmnProcessTO extends AbstractBaseBean implements EntityTO {
-
-    private static final long serialVersionUID = -7044543391316529128L;
-
-    private String key;
-
-    private String modelId;
-
-    private String name;
-
-    private boolean userWorkflow;
-
-    @Override
-    public String getKey() {
-        return key;
-    }
-
-    @Override
-    public void setKey(final String key) {
-        this.key = key;
-    }
-
-    public String getModelId() {
-        return modelId;
-    }
-
-    public void setModelId(final String modelId) {
-        this.modelId = modelId;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public void setName(final String name) {
-        this.name = name;
-    }
-
-    public boolean isUserWorkflow() {
-        return userWorkflow;
-    }
-
-    public void setUserWorkflow(final boolean userWorkflow) {
-        this.userWorkflow = userWorkflow;
-    }
-}


[2/6] syncope git commit: [SYNCOPE-1369] User requests forms now support dropdowns - via Flowable customization

Posted by il...@apache.org.
http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequest.java
----------------------------------------------------------------------
diff --git a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequest.java b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequest.java
new file mode 100644
index 0000000..251eb89
--- /dev/null
+++ b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequest.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.common.lib.to;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+
+@XmlRootElement(name = "userRequest")
+@XmlType
+public class UserRequest extends AbstractBaseBean {
+
+    private static final long serialVersionUID = -8430826310789942133L;
+
+    private String bpmnProcess;
+
+    private String user;
+
+    private String executionId;
+
+    private String activityId;
+
+    public String getBpmnProcess() {
+        return bpmnProcess;
+    }
+
+    public void setBpmnProcess(final String bpmnProcess) {
+        this.bpmnProcess = bpmnProcess;
+    }
+
+    public String getUser() {
+        return user;
+    }
+
+    public void setUser(final String user) {
+        this.user = user;
+    }
+
+    public String getExecutionId() {
+        return executionId;
+    }
+
+    public void setExecutionId(final String executionId) {
+        this.executionId = executionId;
+    }
+
+    public String getActivityId() {
+        return activityId;
+    }
+
+    public void setActivityId(final String activityId) {
+        this.activityId = activityId;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestForm.java
----------------------------------------------------------------------
diff --git a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestForm.java b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestForm.java
index 897cfc0..22118b5 100644
--- a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestForm.java
+++ b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestForm.java
@@ -37,8 +37,12 @@ public class UserRequestForm extends AbstractBaseBean {
 
     private static final long serialVersionUID = -7044543391316529128L;
 
+    private String bpmnProcess;
+
     private String username;
 
+    private String executionId;
+
     private String taskId;
 
     private String formKey;
@@ -55,6 +59,14 @@ public class UserRequestForm extends AbstractBaseBean {
 
     private final List<UserRequestFormProperty> properties = new ArrayList<>();
 
+    public String getBpmnProcess() {
+        return bpmnProcess;
+    }
+
+    public void setBpmnProcess(final String bpmnProcess) {
+        this.bpmnProcess = bpmnProcess;
+    }
+
     public String getUsername() {
         return username;
     }
@@ -63,6 +75,14 @@ public class UserRequestForm extends AbstractBaseBean {
         this.username = username;
     }
 
+    public String getExecutionId() {
+        return executionId;
+    }
+
+    public void setExecutionId(final String executionId) {
+        this.executionId = executionId;
+    }
+
     public String getTaskId() {
         return taskId;
     }
@@ -138,9 +158,9 @@ public class UserRequestForm extends AbstractBaseBean {
         return properties.stream().filter(property -> id.equals(property.getId())).findFirst();
     }
 
-    @XmlElementWrapper(name = "workflowFormProperties")
-    @XmlElement(name = "workflowFormProperty")
-    @JsonProperty("workflowFormProperties")
+    @XmlElementWrapper(name = "properties")
+    @XmlElement(name = "property")
+    @JsonProperty("properties")
     public List<UserRequestFormProperty> getProperties() {
         return properties;
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestFormProperty.java
----------------------------------------------------------------------
diff --git a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestFormProperty.java b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestFormProperty.java
index 0a431c6..95f2fec 100644
--- a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestFormProperty.java
+++ b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestFormProperty.java
@@ -51,6 +51,9 @@ public class UserRequestFormProperty extends AbstractBaseBean {
     @XmlJavaTypeAdapter(XmlGenericMapAdapter.class)
     private final Map<String, String> enumValues = new HashMap<>();
 
+    @XmlJavaTypeAdapter(XmlGenericMapAdapter.class)
+    private final Map<String, String> dropdownValues = new HashMap<>();
+
     private String value;
 
     public String getId() {
@@ -114,6 +117,11 @@ public class UserRequestFormProperty extends AbstractBaseBean {
         return enumValues;
     }
 
+    @JsonProperty
+    public Map<String, String> getDropdownValues() {
+        return dropdownValues;
+    }
+
     public String getValue() {
         return value;
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestTO.java
----------------------------------------------------------------------
diff --git a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestTO.java b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestTO.java
deleted file mode 100644
index d5fad6c..0000000
--- a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestTO.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.common.lib.to;
-
-import javax.xml.bind.annotation.XmlRootElement;
-import javax.xml.bind.annotation.XmlType;
-import org.apache.syncope.common.lib.AbstractBaseBean;
-
-@XmlRootElement(name = "userRequest")
-@XmlType
-public class UserRequestTO extends AbstractBaseBean {
-
-    private static final long serialVersionUID = -8430826310789942133L;
-
-    private String processInstanceId;
-
-    private String executionId;
-
-    private String bpmnProcess;
-
-    private String user;
-
-    public String getProcessInstanceId() {
-        return processInstanceId;
-    }
-
-    public void setProcessInstanceId(final String processInstanceId) {
-        this.processInstanceId = processInstanceId;
-    }
-
-    public String getExecutionId() {
-        return executionId;
-    }
-
-    public void setExecutionId(final String executionId) {
-        this.executionId = executionId;
-    }
-
-    public String getBpmnProcess() {
-        return bpmnProcess;
-    }
-
-    public void setBpmnProcess(final String bpmnProcess) {
-        this.bpmnProcess = bpmnProcess;
-    }
-
-    public String getUser() {
-        return user;
-    }
-
-    public void setUser(final String user) {
-        this.user = user;
-    }
-}

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/FlowableEntitlement.java
----------------------------------------------------------------------
diff --git a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/FlowableEntitlement.java b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/FlowableEntitlement.java
index 43825a4..fbf5b02 100644
--- a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/FlowableEntitlement.java
+++ b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/FlowableEntitlement.java
@@ -36,19 +36,13 @@ public final class FlowableEntitlement {
 
     public static final String WORKFLOW_TASK_LIST = "WORKFLOW_TASK_LIST";
 
-    public static final String WORKFLOW_FORM_LIST = "WORKFLOW_FORM_LIST";
+    public static final String USER_REQUEST_LIST = "USER_REQUEST_LIST";
 
-    public static final String WORKFLOW_FORM_READ = "WORKFLOW_FORM_READ";
+    public static final String USER_REQUEST_FORM_LIST = "USER_REQUEST_FORM_LIST";
 
-    public static final String WORKFLOW_FORM_CLAIM = "WORKFLOW_FORM_CLAIM";
+    public static final String USER_REQUEST_FORM_CLAIM = "USER_REQUEST_FORM_CLAIM";
 
-    public static final String WORKFLOW_FORM_SUBMIT = "WORKFLOW_FORM_SUBMIT";
-
-    public static final String USER_REQUEST_DEF_CREATE = "USER_REQUEST_DEF_CREATE";
-
-    public static final String USER_REQUEST_DEF_UPDATE = "USER_REQUEST_DEF_UPDATE";
-
-    public static final String USER_REQUEST_DEF_DELETE = "USER_REQUEST_DEF_DELETE";
+    public static final String USER_REQUEST_FORM_SUBMIT = "USER_REQUEST_FORM_SUBMIT";
 
     public static final String USER_REQUEST_START = "USER_REQUEST_START";
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/UserRequestFormPropertyType.java
----------------------------------------------------------------------
diff --git a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/UserRequestFormPropertyType.java b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/UserRequestFormPropertyType.java
index 9565f99..227296b 100644
--- a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/UserRequestFormPropertyType.java
+++ b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/UserRequestFormPropertyType.java
@@ -27,6 +27,7 @@ public enum UserRequestFormPropertyType {
     Long,
     Enum,
     Date,
-    Boolean
+    Boolean,
+    Dropdown
 
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/BpmnProcessManager.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/BpmnProcessManager.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/BpmnProcessManager.java
index 9247fd4..f7d1747 100644
--- a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/BpmnProcessManager.java
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/BpmnProcessManager.java
@@ -20,7 +20,7 @@ package org.apache.syncope.core.flowable.api;
 
 import java.io.OutputStream;
 import java.util.List;
-import org.apache.syncope.common.lib.to.BpmnProcessTO;
+import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.syncope.common.lib.types.BpmnProcessFormat;
 
 public interface BpmnProcessManager {
@@ -28,7 +28,7 @@ public interface BpmnProcessManager {
     /**
      * @return all available workflow processes.
      */
-    List<BpmnProcessTO> getProcesses();
+    List<BpmnProcess> getProcesses();
 
     /**
      * Export the process for the given key, in the requested format.

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/DropdownValueProvider.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/DropdownValueProvider.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/DropdownValueProvider.java
new file mode 100644
index 0000000..b6c79cf
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/DropdownValueProvider.java
@@ -0,0 +1,31 @@
+/*
+ * 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.api;
+
+import java.util.Map;
+
+/**
+ * Implementations of this interface are used with {@link org.apache.syncope.core.flowable.support.DropdownFormType}.
+ */
+public interface DropdownValueProvider {
+
+    String NAME = "dropdownValueProvider";
+
+    Map<String, String> getValues();
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/UserRequestHandler.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/UserRequestHandler.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/UserRequestHandler.java
index b11b97c..c3c6f0d 100644
--- a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/UserRequestHandler.java
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/UserRequestHandler.java
@@ -21,7 +21,7 @@ package org.apache.syncope.core.flowable.api;
 import java.util.List;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.patch.UserPatch;
-import org.apache.syncope.common.lib.to.UserRequestTO;
+import org.apache.syncope.common.lib.to.UserRequest;
 import org.apache.syncope.common.lib.to.UserRequestForm;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.entity.user.User;
@@ -33,13 +33,25 @@ import org.springframework.transaction.event.TransactionalEventListener;
 public interface UserRequestHandler {
 
     /**
+     * Get the running user requests matching the provided parameters.
+     *
+     * @param userKey user key (optional)
+     * @param page result page
+     * @param size items per page
+     * @param orderByClauses sort conditions
+     * @return total number of user requests, list of user requests matching the provided parameters
+     */
+    Pair<Integer, List<UserRequest>> getUserRequests(
+            String userKey, int page, int size, List<OrderByClause> orderByClauses);
+
+    /**
      * Starts a new user request, for the given BPMN process and user.
      *
      * @param bpmnProcess BPMN process
      * @param user user
      * @return data about the started request service, including execution id
      */
-    UserRequestTO start(String bpmnProcess, User user);
+    UserRequest start(String bpmnProcess, User user);
 
     /**
      * Parses the given execution id to find matching user request and owner.
@@ -73,22 +85,16 @@ public interface UserRequestHandler {
     void cancelByUser(AnyDeletedEvent event);
 
     /**
-     * Get the forms for current workflow process instances matching the provided parameters.
+     * Get the forms matching the provided parameters.
      *
+     * @param userKey user key (optional)
      * @param page result page
      * @param size items per page
      * @param orderByClauses sort conditions
      * @return total number of forms, list of forms matching the provided parameters
      */
-    Pair<Integer, List<UserRequestForm>> getForms(int page, int size, List<OrderByClause> orderByClauses);
-
-    /**
-     * Get forms for given user (if present).
-     *
-     * @param userKey user key
-     * @return form (if present), otherwise null
-     */
-    List<UserRequestForm> getForms(String userKey);
+    Pair<Integer, List<UserRequestForm>> getForms(
+            String userKey, int page, int size, List<OrderByClause> orderByClauses);
 
     /**
      * Claim a form for a given object.

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableBpmnProcessManager.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableBpmnProcessManager.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableBpmnProcessManager.java
index bca8b25..50b078b 100644
--- a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableBpmnProcessManager.java
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableBpmnProcessManager.java
@@ -28,9 +28,10 @@ import java.io.OutputStream;
 import java.util.List;
 import java.util.stream.Collectors;
 import org.apache.commons.io.IOUtils;
-import org.apache.syncope.common.lib.to.BpmnProcessTO;
+import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.syncope.common.lib.types.BpmnProcessFormat;
 import org.apache.syncope.core.flowable.support.DomainProcessEngine;
+import org.apache.syncope.core.flowable.support.DropdownAwareJsonConverter;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 import org.apache.syncope.core.workflow.api.WorkflowException;
 import org.flowable.bpmn.converter.BpmnXMLConverter;
@@ -73,11 +74,11 @@ public class FlowableBpmnProcessManager implements BpmnProcessManager {
     }
 
     @Override
-    public List<BpmnProcessTO> getProcesses() {
+    public List<BpmnProcess> getProcesses() {
         try {
             return engine.getRepositoryService().createProcessDefinitionQuery().latestVersion().list().stream().
                     map(procDef -> {
-                        BpmnProcessTO defTO = new BpmnProcessTO();
+                        BpmnProcess defTO = new BpmnProcess();
                         defTO.setKey(procDef.getKey());
                         defTO.setName(procDef.getName());
 
@@ -163,7 +164,7 @@ public class FlowableBpmnProcessManager implements BpmnProcessManager {
                                 "Could not find JSON node " + BpmnJsonConverter.EDITOR_CHILD_SHAPES);
                     }
 
-                    BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(definitionNode);
+                    BpmnModel bpmnModel = new DropdownAwareJsonConverter().convertToBpmnModel(definitionNode);
                     deployment = FlowableDeployUtils.deployDefinition(
                             engine,
                             resourceName,

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableRuntimeUtils.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableRuntimeUtils.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableRuntimeUtils.java
index 12d8faa..cdf035d 100644
--- a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableRuntimeUtils.java
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableRuntimeUtils.java
@@ -22,6 +22,7 @@ import java.util.Base64;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.core.flowable.support.DomainProcessEngine;
@@ -33,7 +34,6 @@ import org.apache.syncope.core.workflow.api.WorkflowException;
 import org.flowable.common.engine.api.FlowableException;
 import org.flowable.engine.history.HistoricActivityInstance;
 import org.flowable.engine.impl.RuntimeServiceImpl;
-import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
 import org.flowable.engine.repository.ProcessDefinition;
 import org.flowable.engine.runtime.ProcessInstance;
 import org.flowable.task.api.Task;
@@ -97,8 +97,17 @@ public final class FlowableRuntimeUtils {
         return procInst == null ? null : procInst.getId();
     }
 
-    public static String getProcBusinessKey(final String processDefinitionId, final String userKey) {
-        return processDefinitionId + ":" + userKey;
+    public static String getProcBusinessKey(final String procDefId, final String userKey) {
+        return procDefId + ":" + userKey;
+    }
+
+    public static Pair<String, String> splitProcBusinessKey(final String procBusinessKey) {
+        String[] split = procBusinessKey.split(":");
+        if (split == null || split.length != 2) {
+            throw new WorkflowException(new IllegalArgumentException("Unexpected business key: " + procBusinessKey));
+        }
+
+        return Pair.of(split[0], split[1]);
     }
 
     public static ProcessDefinition getLatestProcDefByKey(final DomainProcessEngine engine, final String key) {
@@ -111,17 +120,17 @@ public final class FlowableRuntimeUtils {
     }
 
     public static Set<String> getPerformedTasks(
-            final DomainProcessEngine engine, final String procInstID, final User user) {
+            final DomainProcessEngine engine, final String procInstId, final User user) {
 
         return engine.getHistoryService().createHistoricActivityInstanceQuery().
-                executionId(procInstID).
+                executionId(procInstId).
                 list().stream().
                 map(HistoricActivityInstance::getActivityId).
                 collect(Collectors.toSet());
     }
 
-    public static void updateStatus(final DomainProcessEngine engine, final String procInstID, final User user) {
-        List<Task> tasks = createTaskQuery(engine, false).processInstanceId(procInstID).list();
+    public static void updateStatus(final DomainProcessEngine engine, final String procInstId, final User user) {
+        List<Task> tasks = createTaskQuery(engine, false).processInstanceId(procInstId).list();
         if (tasks.isEmpty() || tasks.size() > 1) {
             LOG.warn("While setting user status: unexpected task number ({})", tasks.size());
         } else {
@@ -129,13 +138,6 @@ public final class FlowableRuntimeUtils {
         }
     }
 
-    public static List<ProcessInstance> getProcessInstances(final DomainProcessEngine engine, final String userKey) {
-        return engine.getRuntimeService().createNativeProcessInstanceQuery().
-                sql("SELECT ID_,PROC_INST_ID_ FROM " + engine.getManagementService().getTableName(ExecutionEntity.class)
-                        + " WHERE BUSINESS_KEY_ LIKE '" + getProcBusinessKey("%", userKey) + "'"
-                        + " AND PARENT_ID_ IS NULL").list();
-    }
-
     public static TaskQuery createTaskQuery(final DomainProcessEngine engine, final boolean onlyFormTasks) {
         SyncopeTaskQueryImpl taskQuery = new SyncopeTaskQueryImpl(
                 ((RuntimeServiceImpl) engine.getRuntimeService()).getCommandExecutor());
@@ -145,10 +147,10 @@ public final class FlowableRuntimeUtils {
         return taskQuery;
     }
 
-    public static String getFormTask(final DomainProcessEngine engine, final String procInstID) {
+    public static String getFormTask(final DomainProcessEngine engine, final String procInstId) {
         String result = null;
 
-        List<Task> tasks = createTaskQuery(engine, true).processInstanceId(procInstID).list();
+        List<Task> tasks = createTaskQuery(engine, true).processInstanceId(procInstId).list();
         if (tasks.isEmpty() || tasks.size() > 1) {
             LOG.debug("While checking if form task: unexpected task number ({})", tasks.size());
         } else {
@@ -162,7 +164,7 @@ public final class FlowableRuntimeUtils {
      * Saves resources to be propagated and password for later - after form submission - propagation.
      *
      * @param engine Flowable engine
-     * @param procInstID process instance id
+     * @param procInstId process instance id
      * @param user user JPA entity
      * @param userTO user transfer object
      * @param password password
@@ -171,35 +173,35 @@ public final class FlowableRuntimeUtils {
      */
     public static void saveForFormSubmit(
             final DomainProcessEngine engine,
-            final String procInstID,
+            final String procInstId,
             final User user,
             final UserTO userTO,
             final String password,
             final Boolean enabled,
             final PropagationByResource propByRes) {
 
-        String formTaskId = getFormTask(engine, procInstID);
+        String formTaskId = getFormTask(engine, procInstId);
         if (formTaskId == null) {
             return;
         }
 
-        engine.getRuntimeService().setVariable(procInstID, FlowableRuntimeUtils.USER_TO, userTO);
+        engine.getRuntimeService().setVariable(procInstId, FlowableRuntimeUtils.USER_TO, userTO);
 
         if (password == null) {
             String encryptedPwd = engine.getRuntimeService().
-                    getVariable(procInstID, FlowableRuntimeUtils.ENCRYPTED_PWD, String.class);
+                    getVariable(procInstId, FlowableRuntimeUtils.ENCRYPTED_PWD, String.class);
             if (encryptedPwd != null) {
                 userTO.setPassword(decrypt(encryptedPwd));
             }
         } else {
             userTO.setPassword(password);
             engine.getRuntimeService().
-                    setVariable(procInstID, FlowableRuntimeUtils.ENCRYPTED_PWD, encrypt(password));
+                    setVariable(procInstId, FlowableRuntimeUtils.ENCRYPTED_PWD, encrypt(password));
         }
 
-        engine.getRuntimeService().setVariable(procInstID, FlowableRuntimeUtils.ENABLED, enabled);
+        engine.getRuntimeService().setVariable(procInstId, FlowableRuntimeUtils.ENABLED, enabled);
 
-        engine.getRuntimeService().setVariable(procInstID, FlowableRuntimeUtils.PROP_BY_RESOURCE, propByRes);
+        engine.getRuntimeService().setVariable(procInstId, FlowableRuntimeUtils.PROP_BY_RESOURCE, propByRes);
         if (propByRes != null) {
             propByRes.clear();
         }

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserRequestHandler.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserRequestHandler.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserRequestHandler.java
index 1c198a8..45a834b 100644
--- a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserRequestHandler.java
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserRequestHandler.java
@@ -31,13 +31,14 @@ import org.apache.commons.lang3.StringUtils;
 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.UserRequestTO;
+import org.apache.syncope.common.lib.to.UserRequest;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.to.UserRequestFormProperty;
 import org.apache.syncope.common.lib.to.UserRequestForm;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.common.lib.types.UserRequestFormPropertyType;
+import org.apache.syncope.core.flowable.api.DropdownValueProvider;
 import org.apache.syncope.core.flowable.api.WorkflowTaskManager;
 import org.apache.syncope.core.flowable.support.DomainProcessEngine;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
@@ -49,16 +50,21 @@ import org.apache.syncope.core.provisioning.api.PropagationByResource;
 import org.apache.syncope.core.provisioning.api.WorkflowResult;
 import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
 import org.apache.syncope.core.provisioning.api.event.AnyDeletedEvent;
+import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.apache.syncope.core.spring.BeanUtils;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.apache.syncope.core.workflow.api.WorkflowException;
 import org.flowable.common.engine.api.FlowableException;
+import org.flowable.common.engine.api.FlowableIllegalArgumentException;
 import org.flowable.engine.form.FormProperty;
 import org.flowable.engine.form.FormType;
 import org.flowable.engine.form.TaskFormData;
 import org.flowable.engine.history.HistoricActivityInstance;
+import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
 import org.flowable.engine.impl.persistence.entity.HistoricFormPropertyEntity;
+import org.flowable.engine.runtime.NativeProcessInstanceQuery;
 import org.flowable.engine.runtime.ProcessInstance;
+import org.flowable.engine.runtime.ProcessInstanceQuery;
 import org.flowable.task.api.Task;
 import org.flowable.task.api.TaskQuery;
 import org.flowable.task.api.history.HistoricTaskInstance;
@@ -93,6 +99,93 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
     @Autowired
     protected EntityFactory entityFactory;
 
+    protected NativeProcessInstanceQuery createProcessInstanceQuery(final String userKey) {
+        return engine.getRuntimeService().createNativeProcessInstanceQuery().
+                sql("SELECT DISTINCT ID_,BUSINESS_KEY_,ACT_ID_ FROM "
+                        + engine.getManagementService().getTableName(ExecutionEntity.class)
+                        + " WHERE BUSINESS_KEY_ LIKE '"
+                        + FlowableRuntimeUtils.getProcBusinessKey("%", userKey) + "'"
+                        + " AND BUSINESS_KEY_ NOT LIKE '"
+                        + FlowableRuntimeUtils.getProcBusinessKey(FlowableRuntimeUtils.WF_PROCESS_ID, "%") + "'"
+                        + " AND PARENT_ID_ IS NULL");
+    }
+
+    protected int countProcessInstances(final String userKey) {
+        return (int) engine.getRuntimeService().createNativeProcessInstanceQuery().
+                sql("SELECT COUNT(ID_) FROM "
+                        + engine.getManagementService().getTableName(ExecutionEntity.class)
+                        + " WHERE BUSINESS_KEY_ LIKE '"
+                        + FlowableRuntimeUtils.getProcBusinessKey("%", userKey) + "'"
+                        + " AND BUSINESS_KEY_ NOT LIKE '"
+                        + FlowableRuntimeUtils.getProcBusinessKey(FlowableRuntimeUtils.WF_PROCESS_ID, "%") + "'"
+                        + " AND PARENT_ID_ IS NULL").count();
+    }
+
+    protected UserRequest getUserRequest(final ProcessInstance procInst) {
+        Pair<String, String> split = FlowableRuntimeUtils.splitProcBusinessKey(procInst.getBusinessKey());
+
+        UserRequest userRequest = new UserRequest();
+        userRequest.setBpmnProcess(split.getLeft());
+        userRequest.setUser(split.getRight());
+        userRequest.setExecutionId(procInst.getId());
+        userRequest.setActivityId(procInst.getActivityId());
+        return userRequest;
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public Pair<Integer, List<UserRequest>> getUserRequests(
+            final String userKey,
+            final int page,
+            final int size,
+            final List<OrderByClause> orderByClauses) {
+
+        Integer count = null;
+        List<UserRequest> result = null;
+        if (userKey == null) {
+            ProcessInstanceQuery query = engine.getRuntimeService().createProcessInstanceQuery().active();
+            for (OrderByClause clause : orderByClauses) {
+                boolean sorted = true;
+                switch (clause.getField().trim()) {
+                    case "processDefinitionId":
+                        query.orderByProcessDefinitionId();
+                        break;
+
+                    case "processDefinitionKey":
+                        query.orderByProcessDefinitionKey();
+                        break;
+
+                    case "processInstanceId":
+                        query.orderByProcessInstanceId();
+                        break;
+
+                    default:
+                        LOG.warn("User request sort request by {}: unsupported, ignoring", clause.getField().trim());
+                        sorted = false;
+                }
+                if (sorted) {
+                    if (clause.getDirection() == OrderByClause.Direction.ASC) {
+                        query.asc();
+                    } else {
+                        query.desc();
+                    }
+                }
+
+                count = (int) query.count();
+                result = query.listPage(size * (page <= 0 ? 0 : page - 1), size).stream().
+                        map(procInst -> getUserRequest(procInst)).
+                        collect(Collectors.toList());
+            }
+        } else {
+            count = countProcessInstances(userKey);
+            result = createProcessInstanceQuery(userKey).listPage(size * (page <= 0 ? 0 : page - 1), size).stream().
+                    map(procInst -> getUserRequest(procInst)).
+                    collect(Collectors.toList());
+        }
+
+        return Pair.of(count, result);
+    }
+
     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
@@ -101,8 +194,8 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
     }
 
     @Override
-    public UserRequestTO start(final String bpmnProcess, final User user) {
-        Map<String, Object> variables = new HashMap<>(2);
+    public UserRequest start(final String bpmnProcess, final User user) {
+        Map<String, Object> variables = new HashMap<>();
         variables.put(FlowableRuntimeUtils.WF_EXECUTOR, AuthContextUtils.getUsername());
         variables.put(FlowableRuntimeUtils.USER, lazyLoad(user));
         variables.put(FlowableRuntimeUtils.USER_TO, dataBinder.getUserTO(user, true));
@@ -118,12 +211,8 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
                 procInst.getProcessInstanceId(),
                 FlowableRuntimeUtils.getProcBusinessKey(bpmnProcess, user.getKey()));
 
-        UserRequestTO userRequestTO = new UserRequestTO();
-        userRequestTO.setProcessInstanceId(procInst.getProcessInstanceId());
-        userRequestTO.setExecutionId(procInst.getId());
-        userRequestTO.setBpmnProcess(bpmnProcess);
-        userRequestTO.setUser(user.getKey());
-        return userRequestTO;
+        return getUserRequest(engine.getRuntimeService().createProcessInstanceQuery().
+                processInstanceId(procInst.getProcessInstanceId()).singleResult());
     }
 
     @Override
@@ -132,7 +221,11 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
         try {
             procInst = engine.getRuntimeService().
                     createProcessInstanceQuery().processInstanceId(executionId).singleResult();
+            if (procInst == null) {
+                throw new FlowableIllegalArgumentException("ProcessInstance with id " + executionId);
+            }
         } catch (FlowableException e) {
+            LOG.error("Could find execution ProcessInstance with id {}", executionId, e);
             throw new NotFoundException("User request execution with id " + executionId);
         }
 
@@ -163,7 +256,7 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
     public void cancelByUser(final AnyDeletedEvent event) {
         if (AuthContextUtils.getDomain().equals(event.getDomain()) && event.getAnyTypeKind() == AnyTypeKind.USER) {
             String username = event.getAnyName();
-            FlowableRuntimeUtils.getProcessInstances(engine, event.getAnyKey()).
+            createProcessInstanceQuery(event.getAnyKey()).list().
                     forEach(procInst -> {
                         engine.getRuntimeService().deleteProcessInstance(
                                 procInst.getId(), "Cascade Delete user " + username);
@@ -192,6 +285,10 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
                     result = UserRequestFormPropertyType.Boolean;
                     break;
 
+                case "dropdown":
+                    result = UserRequestFormPropertyType.Dropdown;
+                    break;
+
                 case "string":
                 default:
                     break;
@@ -201,19 +298,19 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
         return result;
     }
 
-    protected UserRequestForm getFormTO(final Task task) {
-        return getFormTO(task, engine.getFormService().getTaskFormData(task.getId()));
+    protected UserRequestForm getForm(final Task task) {
+        return FlowableUserRequestHandler.this.getForm(task, engine.getFormService().getTaskFormData(task.getId()));
     }
 
-    protected UserRequestForm getFormTO(final Task task, final TaskFormData fd) {
+    protected UserRequestForm getForm(final Task task, final TaskFormData fd) {
         UserRequestForm formTO =
-                getFormTO(task.getProcessInstanceId(), task.getId(), fd.getFormKey(), fd.getFormProperties());
+                getForm(task.getProcessInstanceId(), task.getId(), fd.getFormKey(), fd.getFormProperties());
         BeanUtils.copyProperties(task, formTO);
 
         return formTO;
     }
 
-    protected UserRequestForm getFormTO(final HistoricTaskInstance task) {
+    protected UserRequestForm getForm(final HistoricTaskInstance task) {
         List<HistoricFormPropertyEntity> props = engine.getHistoryService().
                 createHistoricDetailQuery().taskId(task.getId()).list().stream().
                 filter(HistoricFormPropertyEntity.class::isInstance).
@@ -244,16 +341,19 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
     }
 
     protected UserRequestForm getHistoricFormTO(
-            final String procInstID,
+            final String procInstId,
             final String taskId,
             final String formKey,
             final List<HistoricFormPropertyEntity> props) {
 
         UserRequestForm formTO = new UserRequestForm();
 
-        User user = userDAO.find(getUserKey(procInstID));
+        formTO.setBpmnProcess(engine.getRuntimeService().createProcessInstanceQuery().
+                processInstanceId(procInstId).singleResult().getProcessDefinitionKey());
+
+        User user = userDAO.find(getUserKey(procInstId));
         if (user == null) {
-            throw new NotFoundException("User for process instance id " + procInstID);
+            throw new NotFoundException("User for process instance id " + procInstId);
         }
         formTO.setUsername(user.getUsername());
 
@@ -261,9 +361,9 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
         formTO.setFormKey(formKey);
 
         formTO.setUserTO(engine.getRuntimeService().
-                getVariable(procInstID, FlowableRuntimeUtils.USER_TO, UserTO.class));
+                getVariable(procInstId, FlowableRuntimeUtils.USER_TO, UserTO.class));
         formTO.setUserPatch(engine.getRuntimeService().
-                getVariable(procInstID, FlowableRuntimeUtils.USER_PATCH, UserPatch.class));
+                getVariable(procInstId, FlowableRuntimeUtils.USER_PATCH, UserPatch.class));
 
         formTO.getProperties().addAll(props.stream().map(prop -> {
             UserRequestFormProperty propertyTO = new UserRequestFormProperty();
@@ -277,37 +377,58 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
     }
 
     @SuppressWarnings("unchecked")
-    protected UserRequestForm getFormTO(
-            final String procInstID,
+    protected UserRequestForm getForm(
+            final String procInstId,
             final String taskId,
             final String formKey,
             final List<FormProperty> props) {
 
         UserRequestForm formTO = new UserRequestForm();
 
-        User user = userDAO.find(getUserKey(procInstID));
+        formTO.setBpmnProcess(engine.getRuntimeService().createProcessInstanceQuery().
+                processInstanceId(procInstId).singleResult().getProcessDefinitionKey());
+
+        User user = userDAO.find(getUserKey(procInstId));
         if (user == null) {
-            throw new NotFoundException("User for process instance id " + procInstID);
+            throw new NotFoundException("User for process instance id " + procInstId);
         }
         formTO.setUsername(user.getUsername());
 
+        formTO.setExecutionId(procInstId);
         formTO.setTaskId(taskId);
         formTO.setFormKey(formKey);
 
         formTO.setUserTO(engine.getRuntimeService().
-                getVariable(procInstID, FlowableRuntimeUtils.USER_TO, UserTO.class));
+                getVariable(procInstId, FlowableRuntimeUtils.USER_TO, UserTO.class));
         formTO.setUserPatch(engine.getRuntimeService().
-                getVariable(procInstID, FlowableRuntimeUtils.USER_PATCH, UserPatch.class));
+                getVariable(procInstId, FlowableRuntimeUtils.USER_PATCH, UserPatch.class));
 
         formTO.getProperties().addAll(props.stream().map(fProp -> {
             UserRequestFormProperty propertyTO = new UserRequestFormProperty();
             BeanUtils.copyProperties(fProp, propertyTO, PROPERTY_IGNORE_PROPS);
             propertyTO.setType(fromFlowableFormType(fProp.getType()));
-            if (propertyTO.getType() == UserRequestFormPropertyType.Date) {
-                propertyTO.setDatePattern((String) fProp.getType().getInformation("datePattern"));
-            }
-            if (propertyTO.getType() == UserRequestFormPropertyType.Enum) {
-                propertyTO.getEnumValues().putAll((Map<String, String>) fProp.getType().getInformation("values"));
+            switch (propertyTO.getType()) {
+                case Date:
+                    propertyTO.setDatePattern((String) fProp.getType().getInformation("datePattern"));
+                    break;
+
+                case Enum:
+                    propertyTO.getEnumValues().putAll((Map<String, String>) fProp.getType().getInformation("values"));
+                    break;
+
+                case Dropdown:
+                    String valueProviderBean = (String) fProp.getType().getInformation(DropdownValueProvider.NAME);
+                    try {
+                        DropdownValueProvider valueProvider = ApplicationContextProvider.getApplicationContext().
+                                getBean(valueProviderBean, DropdownValueProvider.class);
+                        propertyTO.getDropdownValues().putAll(valueProvider.getValues());
+                    } catch (Exception e) {
+                        LOG.error("Could not find bean {} of type {} for form property {}",
+                                valueProviderBean, DropdownValueProvider.class.getName(), propertyTO.getId(), e);
+                    }
+                    break;
+
+                default:
             }
             return propertyTO;
         }).collect(Collectors.toList()));
@@ -318,26 +439,28 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
     @Transactional(readOnly = true)
     @Override
     public Pair<Integer, List<UserRequestForm>> getForms(
-            final int page, final int size, final List<OrderByClause> orderByClauses) {
+            final String userKey,
+            final int page,
+            final int size,
+            final List<OrderByClause> orderByClauses) {
 
-        Pair<Integer, List<UserRequestForm>> forms = null;
+        Pair<Integer, List<UserRequestForm>> forms;
 
-        TaskQuery formTaskQuery = FlowableRuntimeUtils.createTaskQuery(engine, true);
+        TaskQuery query = FlowableRuntimeUtils.createTaskQuery(engine, true);
+        if (userKey != null) {
+            query.processInstanceBusinessKeyLike(FlowableRuntimeUtils.getProcBusinessKey("%", userKey));
+        }
 
         String authUser = AuthContextUtils.getUsername();
         if (adminUser.equals(authUser)) {
-            forms = getForms(formTaskQuery, page, size, orderByClauses);
+            forms = getForms(query, page, size, orderByClauses);
         } else {
             User user = userDAO.findByUsername(authUser);
-            if (user == null) {
-                throw new NotFoundException("Syncope User " + authUser);
-            }
-
-            forms = getForms(formTaskQuery.taskCandidateOrAssigned(user.getUsername()), page, size, orderByClauses);
+            forms = getForms(query.taskCandidateOrAssigned(user.getUsername()), page, size, orderByClauses);
 
             List<String> candidateGroups = new ArrayList<>(userDAO.findAllGroupNames(user));
             if (!candidateGroups.isEmpty()) {
-                forms = getForms(formTaskQuery.taskCandidateGroupIn(candidateGroups), page, size, orderByClauses);
+                forms = getForms(query.taskCandidateGroupIn(candidateGroups), page, size, orderByClauses);
             }
         }
 
@@ -349,75 +472,56 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
     protected Pair<Integer, List<UserRequestForm>> getForms(
             final TaskQuery query, final int page, final int size, final List<OrderByClause> orderByClauses) {
 
-        TaskQuery sortedQuery = query;
         for (OrderByClause clause : orderByClauses) {
-            boolean ack = true;
+            boolean sorted = true;
             switch (clause.getField().trim()) {
+                case "bpmnProcess":
+                    query.orderByProcessDefinitionId();
+                    break;
+
+                case "executionId":
+                    query.orderByExecutionId();
+                    break;
+
                 case "taskId":
-                    sortedQuery = sortedQuery.orderByTaskId();
+                    query.orderByTaskId();
                     break;
 
                 case "createTime":
-                    sortedQuery = sortedQuery.orderByTaskCreateTime();
+                    query.orderByTaskCreateTime();
                     break;
 
                 case "dueDate":
-                    sortedQuery = sortedQuery.orderByTaskDueDate();
+                    query.orderByTaskDueDate();
                     break;
 
                 case "owner":
-                    sortedQuery = sortedQuery.orderByTaskOwner();
+                    query.orderByTaskOwner();
                     break;
 
                 default:
                     LOG.warn("Form sort request by {}: unsupported, ignoring", clause.getField().trim());
-                    ack = false;
+                    sorted = false;
             }
-            if (ack) {
-                sortedQuery = clause.getDirection() == OrderByClause.Direction.ASC
-                        ? sortedQuery.asc()
-                        : sortedQuery.desc();
+            if (sorted) {
+                if (clause.getDirection() == OrderByClause.Direction.ASC) {
+                    query.asc();
+                } else {
+                    query.desc();
+                }
             }
         }
 
-        List<UserRequestForm> result = sortedQuery.listPage(size * (page <= 0 ? 0 : page - 1), size).stream().
+        List<UserRequestForm> result = query.listPage(size * (page <= 0 ? 0 : page - 1), size).stream().
                 map(task -> task instanceof HistoricTaskInstance
-                ? getFormTO((HistoricTaskInstance) task) : getFormTO(task)).
+                ? FlowableUserRequestHandler.this.getForm((HistoricTaskInstance) task)
+                : FlowableUserRequestHandler.this.getForm(task)).
                 collect(Collectors.toList());
 
         return Pair.of((int) query.count(), result);
     }
 
-    @Override
-    public List<UserRequestForm> getForms(final String userKey) {
-        List<UserRequestForm> result = new ArrayList<>();
-        FlowableRuntimeUtils.getProcessInstances(engine, userKey).forEach(procInst -> {
-            Task task;
-            try {
-                task = FlowableRuntimeUtils.createTaskQuery(engine, true).
-                        processInstanceId(procInst.getProcessInstanceId()).singleResult();
-            } catch (FlowableException e) {
-                throw new WorkflowException("While reading form for process instance "
-                        + procInst.getProcessInstanceId(), e);
-            }
-
-            if (task != null) {
-                TaskFormData formData;
-                try {
-                    formData = engine.getFormService().getTaskFormData(task.getId());
-                } catch (FlowableException e) {
-                    throw new WorkflowException("Error while getting form data for task " + task.getId(), e);
-                }
-                if (formData != null && !formData.getFormProperties().isEmpty()) {
-                    result.add(getFormTO(task, formData));
-                }
-            }
-        });
-
-        return result;
-    }
-
-    protected Pair<Task, TaskFormData> checkTask(final String taskId, final String authUser) {
+    protected Pair<Task, TaskFormData> parseTask(final String taskId) {
         Task task;
         try {
             task = FlowableRuntimeUtils.createTaskQuery(engine, true).taskId(taskId).singleResult();
@@ -435,27 +539,20 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
             throw new NotFoundException("Form for Flowable Task " + taskId, e);
         }
 
-        if (!adminUser.equals(authUser)) {
-            User user = userDAO.findByUsername(authUser);
-            if (user == null) {
-                throw new NotFoundException("Syncope User " + authUser);
-            }
-        }
-
         return Pair.of(task, formData);
     }
 
     @Override
     public UserRequestForm claimForm(final String taskId) {
-        String authUser = AuthContextUtils.getUsername();
-        Pair<Task, TaskFormData> checked = checkTask(taskId, authUser);
+        Pair<Task, TaskFormData> parsed = parseTask(taskId);
 
+        String authUser = AuthContextUtils.getUsername();
         if (!adminUser.equals(authUser)) {
             List<Task> tasksForUser = FlowableRuntimeUtils.createTaskQuery(engine, true).
-                    taskId(taskId).taskCandidateUser(authUser).list();
+                    taskId(taskId).taskCandidateOrAssigned(authUser).list();
             if (tasksForUser.isEmpty()) {
                 throw new WorkflowException(
-                        new IllegalArgumentException(authUser + " is not candidate for task " + taskId));
+                        new IllegalArgumentException(authUser + " is not candidate nor assignee of task " + taskId));
             }
         }
 
@@ -467,7 +564,7 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
             throw new WorkflowException("While reading task " + taskId, e);
         }
 
-        return getFormTO(task, checked.getRight());
+        return FlowableUserRequestHandler.this.getForm(task, parsed.getRight());
     }
 
     private Map<String, String> getPropertiesForSubmit(final UserRequestForm form) {
@@ -482,36 +579,36 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
 
     @Override
     public WorkflowResult<UserPatch> submitForm(final UserRequestForm form) {
-        String authUser = AuthContextUtils.getUsername();
-        Pair<Task, TaskFormData> checked = checkTask(form.getTaskId(), authUser);
+        Pair<Task, TaskFormData> parsed = parseTask(form.getTaskId());
 
-        if (!checked.getLeft().getOwner().equals(authUser)) {
+        String authUser = AuthContextUtils.getUsername();
+        if (!parsed.getLeft().getOwner().equals(authUser)) {
             throw new WorkflowException(new IllegalArgumentException("Task " + form.getTaskId() + " assigned to "
-                    + checked.getLeft().getOwner() + " but submitted by " + authUser));
+                    + parsed.getLeft().getOwner() + " but submitted by " + authUser));
         }
 
-        String procInstID = checked.getLeft().getProcessInstanceId();
+        String procInstId = parsed.getLeft().getProcessInstanceId();
 
-        User user = userDAO.find(getUserKey(procInstID));
+        User user = userDAO.find(getUserKey(procInstId));
         if (user == null) {
-            throw new NotFoundException("User with key " + getUserKey(procInstID));
+            throw new NotFoundException("User with key " + getUserKey(procInstId));
         }
 
-        Set<String> preTasks = FlowableRuntimeUtils.getPerformedTasks(engine, procInstID, user);
+        Set<String> preTasks = FlowableRuntimeUtils.getPerformedTasks(engine, procInstId, user);
 
-        engine.getRuntimeService().setVariable(procInstID, FlowableRuntimeUtils.TASK, "submit");
-        engine.getRuntimeService().setVariable(procInstID, FlowableRuntimeUtils.FORM_SUBMITTER, authUser);
-        engine.getRuntimeService().setVariable(procInstID, FlowableRuntimeUtils.USER, lazyLoad(user));
+        engine.getRuntimeService().setVariable(procInstId, FlowableRuntimeUtils.TASK, "submit");
+        engine.getRuntimeService().setVariable(procInstId, FlowableRuntimeUtils.FORM_SUBMITTER, authUser);
+        engine.getRuntimeService().setVariable(procInstId, FlowableRuntimeUtils.USER, lazyLoad(user));
         try {
             engine.getFormService().submitTaskFormData(form.getTaskId(), getPropertiesForSubmit(form));
         } catch (FlowableException e) {
             FlowableRuntimeUtils.throwException(e, "While submitting form for task " + form.getTaskId());
         }
-        Set<String> postTasks = FlowableRuntimeUtils.getPerformedTasks(engine, procInstID, user);
+        Set<String> postTasks = FlowableRuntimeUtils.getPerformedTasks(engine, procInstId, user);
         postTasks.removeAll(preTasks);
         postTasks.add(form.getTaskId());
-        if (procInstID.equals(FlowableRuntimeUtils.getWFProcInstID(engine, user.getKey()))) {
-            FlowableRuntimeUtils.updateStatus(engine, procInstID, user);
+        if (procInstId.equals(FlowableRuntimeUtils.getWFProcInstID(engine, user.getKey()))) {
+            FlowableRuntimeUtils.updateStatus(engine, procInstId, user);
         }
 
         user = userDAO.save(user);
@@ -521,34 +618,34 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
         PropagationByResource propByRes = null;
 
         ProcessInstance afterSubmitPI = engine.getRuntimeService().
-                createProcessInstanceQuery().processInstanceId(procInstID).singleResult();
+                createProcessInstanceQuery().processInstanceId(procInstId).singleResult();
         if (afterSubmitPI != null) {
-            engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.TASK);
-            engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.FORM_SUBMITTER);
-            engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.USER);
-            engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.USER_TO);
+            engine.getRuntimeService().removeVariable(procInstId, FlowableRuntimeUtils.TASK);
+            engine.getRuntimeService().removeVariable(procInstId, FlowableRuntimeUtils.FORM_SUBMITTER);
+            engine.getRuntimeService().removeVariable(procInstId, FlowableRuntimeUtils.USER);
+            engine.getRuntimeService().removeVariable(procInstId, FlowableRuntimeUtils.USER_TO);
 
             // see if there is any propagation to be done
             propByRes = engine.getRuntimeService().
-                    getVariable(procInstID, FlowableRuntimeUtils.PROP_BY_RESOURCE, PropagationByResource.class);
-            engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.PROP_BY_RESOURCE);
+                    getVariable(procInstId, FlowableRuntimeUtils.PROP_BY_RESOURCE, PropagationByResource.class);
+            engine.getRuntimeService().removeVariable(procInstId, FlowableRuntimeUtils.PROP_BY_RESOURCE);
 
             // fetch - if available - the encrypted password
             String encryptedPwd = engine.getRuntimeService().
-                    getVariable(procInstID, FlowableRuntimeUtils.ENCRYPTED_PWD, String.class);
-            engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.ENCRYPTED_PWD);
+                    getVariable(procInstId, FlowableRuntimeUtils.ENCRYPTED_PWD, String.class);
+            engine.getRuntimeService().removeVariable(procInstId, FlowableRuntimeUtils.ENCRYPTED_PWD);
             if (StringUtils.isNotBlank(encryptedPwd)) {
                 clearPassword = FlowableRuntimeUtils.decrypt(encryptedPwd);
             }
 
             Boolean enabled = engine.getRuntimeService().
-                    getVariable(procInstID, FlowableRuntimeUtils.ENABLED, Boolean.class);
-            engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.ENABLED);
+                    getVariable(procInstId, FlowableRuntimeUtils.ENABLED, Boolean.class);
+            engine.getRuntimeService().removeVariable(procInstId, FlowableRuntimeUtils.ENABLED);
 
             // supports approval chains
             FlowableRuntimeUtils.saveForFormSubmit(
                     engine,
-                    procInstID,
+                    procInstId,
                     user,
                     dataBinder.getUserTO(user, true),
                     clearPassword,
@@ -556,8 +653,8 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
                     propByRes);
 
             userPatch = engine.getRuntimeService().
-                    getVariable(procInstID, FlowableRuntimeUtils.USER_PATCH, UserPatch.class);
-            engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.USER_PATCH);
+                    getVariable(procInstId, FlowableRuntimeUtils.USER_PATCH, UserPatch.class);
+            engine.getRuntimeService().removeVariable(procInstId, FlowableRuntimeUtils.USER_PATCH);
         }
         if (userPatch == null) {
             userPatch = new UserPatch();

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/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
index 162b113..de2bbfe 100644
--- 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
@@ -18,13 +18,16 @@
  */
 package org.apache.syncope.core.flowable.support;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 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.form.AbstractFormType;
 import org.flowable.engine.impl.util.EngineServiceUtil;
 import org.flowable.idm.spring.SpringIdmEngineConfiguration;
 import org.flowable.spring.SpringExpressionManager;
@@ -81,6 +84,9 @@ public class DomainProcessEngineFactoryBean
                                 EngineConfigurationConstants.KEY_IDM_ENGINE_CONFIG,
                                 ctx.getBean(SpringIdmEngineConfiguration.class));
                     }
+                    List<AbstractFormType> customFormTypes = new ArrayList<>();
+                    customFormTypes.add(new DropdownFormType(null));
+                    conf.setCustomFormTypes(customFormTypes);
 
                     engines.put(domain, conf.buildProcessEngine());
                 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DropdownAwareJsonConverter.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DropdownAwareJsonConverter.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DropdownAwareJsonConverter.java
new file mode 100644
index 0000000..ad29e17
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DropdownAwareJsonConverter.java
@@ -0,0 +1,31 @@
+/*
+ * 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.bpmn.model.UserTask;
+import org.flowable.editor.constants.StencilConstants;
+import org.flowable.editor.language.json.converter.BpmnJsonConverter;
+
+public class DropdownAwareJsonConverter extends BpmnJsonConverter {
+
+    public DropdownAwareJsonConverter() {
+        convertersToBpmnMap.put(StencilConstants.STENCIL_TASK_USER, DropdownAwareUserTaskJsonConverter.class);
+        convertersToJsonMap.put(UserTask.class, DropdownAwareUserTaskJsonConverter.class);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DropdownAwareUserTaskJsonConverter.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DropdownAwareUserTaskJsonConverter.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DropdownAwareUserTaskJsonConverter.java
new file mode 100644
index 0000000..838201c
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DropdownAwareUserTaskJsonConverter.java
@@ -0,0 +1,98 @@
+/*
+ * 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 com.fasterxml.jackson.databind.JsonNode;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+import org.flowable.bpmn.model.BaseElement;
+import org.flowable.bpmn.model.FormProperty;
+import org.flowable.bpmn.model.FormValue;
+import org.flowable.bpmn.model.StartEvent;
+import org.flowable.bpmn.model.UserTask;
+import org.flowable.editor.language.json.converter.BpmnJsonConverterUtil;
+import org.flowable.editor.language.json.converter.UserTaskJsonConverter;
+
+public class DropdownAwareUserTaskJsonConverter extends UserTaskJsonConverter {
+
+    @Override
+    protected void convertJsonToFormProperties(final JsonNode objectNode, final BaseElement element) {
+        JsonNode formPropertiesNode = getProperty(PROPERTY_FORM_PROPERTIES, objectNode);
+        if (formPropertiesNode != null) {
+            formPropertiesNode = BpmnJsonConverterUtil.validateIfNodeIsTextual(formPropertiesNode);
+            JsonNode propertiesArray = formPropertiesNode.get("formProperties");
+            if (propertiesArray != null) {
+                for (JsonNode formNode : propertiesArray) {
+                    JsonNode formIdNode = formNode.get(PROPERTY_FORM_ID);
+                    if (formIdNode != null && StringUtils.isNotEmpty(formIdNode.asText())) {
+
+                        FormProperty formProperty = new FormProperty();
+                        formProperty.setId(formIdNode.asText());
+                        formProperty.setName(getValueAsString(PROPERTY_FORM_NAME, formNode));
+                        formProperty.setType(getValueAsString(PROPERTY_FORM_TYPE, formNode));
+                        formProperty.setExpression(getValueAsString(PROPERTY_FORM_EXPRESSION, formNode));
+                        formProperty.setVariable(getValueAsString(PROPERTY_FORM_VARIABLE, formNode));
+
+                        if ("date".equalsIgnoreCase(formProperty.getType())) {
+                            formProperty.setDatePattern(getValueAsString(PROPERTY_FORM_DATE_PATTERN, formNode));
+
+                        } else if ("enum".equalsIgnoreCase(formProperty.getType())
+                                || "dropdown".equalsIgnoreCase(formProperty.getType())) {
+
+                            JsonNode enumValuesNode = formNode.get(PROPERTY_FORM_ENUM_VALUES);
+                            if (enumValuesNode != null) {
+                                List<FormValue> formValueList = new ArrayList<>();
+                                for (JsonNode enumNode : enumValuesNode) {
+                                    if (enumNode.get(PROPERTY_FORM_ENUM_VALUES_ID) != null && !enumNode.get(
+                                            PROPERTY_FORM_ENUM_VALUES_ID).isNull() && enumNode.get(
+                                                    PROPERTY_FORM_ENUM_VALUES_NAME) != null
+                                            && !enumNode.get(PROPERTY_FORM_ENUM_VALUES_NAME).isNull()) {
+
+                                        FormValue formValue = new FormValue();
+                                        formValue.setId(enumNode.get(PROPERTY_FORM_ENUM_VALUES_ID).asText());
+                                        formValue.setName(enumNode.get(PROPERTY_FORM_ENUM_VALUES_NAME).asText());
+                                        formValueList.add(formValue);
+
+                                    } else if (enumNode.get("value") != null && !enumNode.get("value").isNull()) {
+                                        FormValue formValue = new FormValue();
+                                        formValue.setId(enumNode.get("value").asText());
+                                        formValue.setName(enumNode.get("value").asText());
+                                        formValueList.add(formValue);
+                                    }
+                                }
+                                formProperty.setFormValues(formValueList);
+                            }
+                        }
+
+                        formProperty.setRequired(getValueAsBoolean(PROPERTY_FORM_REQUIRED, formNode));
+                        formProperty.setReadable(getValueAsBoolean(PROPERTY_FORM_READABLE, formNode));
+                        formProperty.setWriteable(getValueAsBoolean(PROPERTY_FORM_WRITABLE, formNode));
+
+                        if (element instanceof StartEvent) {
+                            ((StartEvent) element).getFormProperties().add(formProperty);
+                        } else if (element instanceof UserTask) {
+                            ((UserTask) element).getFormProperties().add(formProperty);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DropdownFormType.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DropdownFormType.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DropdownFormType.java
new file mode 100644
index 0000000..6c612f6
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DropdownFormType.java
@@ -0,0 +1,59 @@
+/*
+ * 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.engine.form.AbstractFormType;
+
+/**
+ * Extension to predefined Flowable form types relying on the provided
+ * {@link org.apache.syncope.core.flowable.api.DropdownValueProvider} bean to populate values.
+ */
+public class DropdownFormType extends AbstractFormType {
+
+    private static final long serialVersionUID = -3549337216346168946L;
+
+    protected final String dropdownValueProvider;
+
+    public DropdownFormType(final String dropdownValueProvider) {
+        this.dropdownValueProvider = dropdownValueProvider;
+    }
+
+    @Override
+    public String getName() {
+        return "dropdown";
+    }
+
+    @Override
+    public Object getInformation(final String key) {
+        if (key.equals("dropdownValueProvider")) {
+            return dropdownValueProvider;
+        }
+        return null;
+    }
+
+    @Override
+    public Object convertFormValueToModelValue(final String propertyValue) {
+        return propertyValue;
+    }
+
+    @Override
+    public String convertModelValueToFormValue(final Object modelValue) {
+        return modelValue == null ? null : modelValue.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeFormHandlerHelper.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeFormHandlerHelper.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeFormHandlerHelper.java
new file mode 100644
index 0000000..9f6e751
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeFormHandlerHelper.java
@@ -0,0 +1,56 @@
+/*
+ * 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.bpmn.model.FlowElement;
+import org.flowable.bpmn.model.UserTask;
+import org.flowable.bpmn.model.Process;
+import org.flowable.engine.impl.form.FormHandlerHelper;
+import org.flowable.engine.impl.form.TaskFormHandler;
+import org.flowable.engine.impl.persistence.entity.DeploymentEntity;
+import org.flowable.engine.impl.util.CommandContextUtil;
+import org.flowable.engine.impl.util.ProcessDefinitionUtil;
+import org.flowable.engine.repository.ProcessDefinition;
+
+/**
+ * Used to inject {@link SyncopeTaskFormHandler} rather than
+ * {@link org.flowable.engine.impl.form.DefaultTaskFormHandler}.
+ */
+public class SyncopeFormHandlerHelper extends FormHandlerHelper {
+
+    @Override
+    public TaskFormHandler getTaskFormHandlder(final String procDefId, final String taskId) {
+        Process process = ProcessDefinitionUtil.getProcess(procDefId);
+        FlowElement flowElement = process.getFlowElement(taskId, true);
+        if (flowElement instanceof UserTask) {
+            UserTask userTask = (UserTask) flowElement;
+
+            ProcessDefinition processDefinitionEntity = ProcessDefinitionUtil.getProcessDefinition(procDefId);
+            DeploymentEntity deploymentEntity = CommandContextUtil.getProcessEngineConfiguration().
+                    getDeploymentEntityManager().findById(processDefinitionEntity.getDeploymentId());
+
+            TaskFormHandler taskFormHandler = new SyncopeTaskFormHandler();
+            taskFormHandler.parseConfiguration(
+                    userTask.getFormProperties(), userTask.getFormKey(), deploymentEntity, processDefinitionEntity);
+            return taskFormHandler;
+        }
+
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeTaskFormHandler.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeTaskFormHandler.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeTaskFormHandler.java
new file mode 100644
index 0000000..69c5e50
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeTaskFormHandler.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.List;
+import java.util.Optional;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.core.flowable.api.DropdownValueProvider;
+import org.flowable.bpmn.model.FormProperty;
+import org.flowable.common.engine.api.delegate.Expression;
+import org.flowable.common.engine.impl.el.ExpressionManager;
+import org.flowable.engine.form.AbstractFormType;
+import org.flowable.engine.impl.form.DefaultTaskFormHandler;
+import org.flowable.engine.impl.form.FormPropertyHandler;
+import org.flowable.engine.impl.form.FormTypes;
+import org.flowable.engine.impl.persistence.entity.DeploymentEntity;
+import org.flowable.engine.impl.util.CommandContextUtil;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Extends {@link DefaultTaskFormHandler} with purpose of supporting more form types than Flowable's default.
+ */
+public class SyncopeTaskFormHandler extends DefaultTaskFormHandler {
+
+    private static final long serialVersionUID = -5271243544388455797L;
+
+    protected static final Logger LOG = LoggerFactory.getLogger(SyncopeTaskFormHandler.class);
+
+    protected Optional<AbstractFormType> parseFormPropertyType(
+            final FormProperty formProperty, final ExpressionManager expressionManager) {
+
+        AbstractFormType formType = null;
+
+        switch (formProperty.getType()) {
+            case "dropdown":
+                if (formProperty.getFormValues().isEmpty()
+                        || !DropdownValueProvider.NAME.equals(formProperty.getFormValues().get(0).getId())) {
+
+                    LOG.warn("A single value with id '" + DropdownValueProvider.NAME + "' was expected, ignoring");
+                } else {
+                    formType = new DropdownFormType(formProperty.getFormValues().get(0).getName());
+                }
+                break;
+
+            default:
+        }
+
+        return Optional.ofNullable(formType);
+    }
+
+    @Override
+    public void parseConfiguration(
+            final List<FormProperty> formProperties,
+            final String formKey,
+            final DeploymentEntity deployment,
+            final ProcessDefinition processDefinition) {
+
+        this.deploymentId = deployment.getId();
+
+        ExpressionManager expressionManager = CommandContextUtil.getProcessEngineConfiguration().getExpressionManager();
+
+        if (StringUtils.isNotEmpty(formKey)) {
+            this.formKey = expressionManager.createExpression(formKey);
+        }
+
+        FormTypes formTypes = CommandContextUtil.getProcessEngineConfiguration().getFormTypes();
+
+        formProperties.forEach(formProperty -> {
+            FormPropertyHandler formPropertyHandler = new FormPropertyHandler();
+            formPropertyHandler.setId(formProperty.getId());
+            formPropertyHandler.setName(formProperty.getName());
+
+            AbstractFormType type = parseFormPropertyType(formProperty, expressionManager).
+                    orElse(formTypes.parseFormPropertyType(formProperty));
+            formPropertyHandler.setType(type);
+            formPropertyHandler.setRequired(formProperty.isRequired());
+            formPropertyHandler.setReadable(formProperty.isReadable());
+            formPropertyHandler.setWritable(formProperty.isWriteable());
+            formPropertyHandler.setVariableName(formProperty.getVariable());
+
+            if (StringUtils.isNotEmpty(formProperty.getExpression())) {
+                Expression expression = expressionManager.createExpression(formProperty.getExpression());
+                formPropertyHandler.setVariableExpression(expression);
+            }
+
+            if (StringUtils.isNotEmpty(formProperty.getDefaultExpression())) {
+                Expression defaultExpression = expressionManager.createExpression(formProperty.getDefaultExpression());
+                formPropertyHandler.setDefaultExpression(defaultExpression);
+            }
+
+            formPropertyHandlers.add(formPropertyHandler);
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/flowable-bpmn/src/main/resources/workflowFlowableContext.xml
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/resources/workflowFlowableContext.xml b/ext/flowable/flowable-bpmn/src/main/resources/workflowFlowableContext.xml
index 9e27f47..4f34237 100644
--- a/ext/flowable/flowable-bpmn/src/main/resources/workflowFlowableContext.xml
+++ b/ext/flowable/flowable-bpmn/src/main/resources/workflowFlowableContext.xml
@@ -38,6 +38,8 @@ under the License.
     <property name="idmEngineConfiguration" ref="syncopeIdmEngineConfiguration"/> 
   </bean>
 
+  <bean id="syncopeFormHandlerHelper" class="org.apache.syncope.core.flowable.support.SyncopeFormHandlerHelper"/>
+
   <bean class="org.apache.syncope.core.flowable.support.DomainProcessEngineConfiguration" scope="prototype">
     <property name="databaseSchemaUpdate" value="true"/>
 
@@ -52,6 +54,8 @@ under the License.
         <bean class="org.apache.syncope.core.flowable.support.SyncopeEntitiesVariableType"/>
       </list>
     </property>
+    
+    <property name="formHandlerHelper" ref="syncopeFormHandlerHelper"/>
   </bean>
 
   <bean id="bpmnProcessManager" class="org.apache.syncope.core.flowable.impl.FlowableBpmnProcessManager"/>

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/BpmnProcessLogic.java
----------------------------------------------------------------------
diff --git a/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/BpmnProcessLogic.java b/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/BpmnProcessLogic.java
index 8d280ac..ad76605 100644
--- a/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/BpmnProcessLogic.java
+++ b/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/BpmnProcessLogic.java
@@ -21,7 +21,7 @@ package org.apache.syncope.core.logic;
 import java.io.OutputStream;
 import java.lang.reflect.Method;
 import java.util.List;
-import org.apache.syncope.common.lib.to.BpmnProcessTO;
+import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.syncope.common.lib.types.FlowableEntitlement;
 import org.apache.syncope.common.lib.types.BpmnProcessFormat;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -31,14 +31,14 @@ import org.springframework.transaction.annotation.Transactional;
 import org.apache.syncope.core.flowable.api.BpmnProcessManager;
 
 @Component
-public class BpmnProcessLogic extends AbstractTransactionalLogic<BpmnProcessTO> {
+public class BpmnProcessLogic extends AbstractTransactionalLogic<BpmnProcess> {
 
     @Autowired
     private BpmnProcessManager bpmnProcessManager;
 
     @PreAuthorize("hasRole('" + FlowableEntitlement.BPMN_PROCESS_LIST + "')")
     @Transactional(readOnly = true)
-    public List<BpmnProcessTO> list() {
+    public List<BpmnProcess> list() {
         return bpmnProcessManager.getProcesses();
     }
 
@@ -65,7 +65,7 @@ public class BpmnProcessLogic extends AbstractTransactionalLogic<BpmnProcessTO>
     }
 
     @Override
-    protected BpmnProcessTO resolveReference(final Method method, final Object... args)
+    protected BpmnProcess resolveReference(final Method method, final Object... args)
             throws UnresolvedReferenceException {
 
         throw new UnresolvedReferenceException();


[3/6] syncope git commit: [SYNCOPE-1369] User requests forms now support dropdowns - via Flowable customization

Posted by il...@apache.org.
[SYNCOPE-1369] User requests forms now support dropdowns - via Flowable customization


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/6f6d9156
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/6f6d9156
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/6f6d9156

Branch: refs/heads/2_1_X
Commit: 6f6d91569d94112023f74b42898354d190d7d180
Parents: 35d46cf
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Tue Oct 2 14:31:40 2018 +0200
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Tue Oct 2 14:31:40 2018 +0200

----------------------------------------------------------------------
 .../console/commons/MapChoiceRenderer.java      |  21 +-
 .../html/form/AjaxDropDownChoicePanel.java      |   3 +-
 .../console/wizards/any/Relationships.java      |   5 +-
 .../wizards/any/Relationships.properties        |   3 +
 .../wizards/any/Relationships_it.properties     |   3 +
 .../wizards/any/Relationships_ja.properties     |   3 +
 .../wizards/any/Relationships_pt_BR.properties  |   3 +
 .../wizards/any/Relationships_ru.properties     |   3 +
 .../syncope/common/lib/to/RelationshipTO.java   |  16 +
 .../core/persistence/api/dao/AnyObjectDAO.java  |   2 +-
 .../persistence/jpa/dao/JPAAnyObjectDAO.java    |  12 +-
 .../test/resources/domains/MasterContent.xml    |   6 +-
 .../provisioning/java/MappingManagerImpl.java   |  47 +--
 .../java/data/AbstractAnyDataBinder.java        |   5 +-
 .../java/data/AnyObjectDataBinderImpl.java      |  11 +-
 .../java/data/UserDataBinderImpl.java           |   7 +-
 ext/flowable/client-console/pom.xml             |  32 +-
 .../client/console/approvals/Approval.java      |  29 +-
 .../approvals/ApprovalDirectoryPanel.java       |  19 +-
 .../syncope/client/console/pages/Flowable.java  |   4 +-
 .../panels/BpmnProcessDirectoryPanel.java       |  48 +--
 .../resources/AbstractBpmnProcessResource.java  |   6 +-
 .../resources/BpmnProcessGETResource.java       |   4 +-
 .../resources/BpmnProcessPUTResource.java       |   4 +-
 .../console/rest/BpmnProcessRestClient.java     |   4 +-
 .../console/rest/UserRequestRestClient.java     |  11 +-
 .../client/console/widgets/ApprovalsWidget.java |  10 +-
 .../src/main/resources/dropdown.diff            | 139 ++++++++
 .../client/console/pages/Approvals.properties   |   2 +-
 .../console/pages/Approvals_it.properties       |   2 +-
 .../console/pages/Approvals_ja.properties       |   2 +-
 .../console/pages/Approvals_pt_BR.properties    |   2 +-
 .../console/pages/Approvals_ru.properties       |   2 +-
 .../syncope/common/lib/to/BpmnProcess.java      |  72 ++++
 .../syncope/common/lib/to/BpmnProcessTO.java    |  72 ----
 .../syncope/common/lib/to/UserRequest.java      |  70 ++++
 .../syncope/common/lib/to/UserRequestForm.java  |  26 +-
 .../common/lib/to/UserRequestFormProperty.java  |   8 +
 .../syncope/common/lib/to/UserRequestTO.java    |  70 ----
 .../common/lib/types/FlowableEntitlement.java   |  14 +-
 .../lib/types/UserRequestFormPropertyType.java  |   3 +-
 .../core/flowable/api/BpmnProcessManager.java   |   4 +-
 .../flowable/api/DropdownValueProvider.java     |  31 ++
 .../core/flowable/api/UserRequestHandler.java   |  30 +-
 .../impl/FlowableBpmnProcessManager.java        |   9 +-
 .../flowable/impl/FlowableRuntimeUtils.java     |  50 +--
 .../impl/FlowableUserRequestHandler.java        | 345 ++++++++++++-------
 .../support/DomainProcessEngineFactoryBean.java |   6 +
 .../support/DropdownAwareJsonConverter.java     |  31 ++
 .../DropdownAwareUserTaskJsonConverter.java     |  98 ++++++
 .../core/flowable/support/DropdownFormType.java |  59 ++++
 .../support/SyncopeFormHandlerHelper.java       |  56 +++
 .../support/SyncopeTaskFormHandler.java         | 112 ++++++
 .../main/resources/workflowFlowableContext.xml  |   4 +
 .../syncope/core/logic/BpmnProcessLogic.java    |   8 +-
 .../syncope/core/logic/UserRequestLogic.java    | 115 +++++--
 .../rest/api/beans/UserRequestFormQuery.java    |  18 +
 .../common/rest/api/beans/UserRequestQuery.java |  50 +++
 .../rest/api/service/BpmnProcessService.java    |  20 +-
 .../rest/api/service/UserRequestService.java    |  73 ++--
 .../api/service/UserWorkflowTaskService.java    |  14 +
 .../cxf/service/BpmnProcessServiceImpl.java     |   4 +-
 .../cxf/service/UserRequestServiceImpl.java     |  38 +-
 fit/core-reference/pom.xml                      |  20 ++
 .../reference/flowable/AssignDirectorGroup.java |  65 ++++
 .../reference/flowable/CreateARelationship.java |  71 ++++
 .../flowable/PrintersValueProvider.java         |  70 ++++
 .../fit/core/reference/AssignDirectorGroup.java |  63 ----
 .../main/resources/all/workflowTestContext.xml  |   7 +-
 .../resources/assignPrinterRequest.bpmn20.xml   |  92 +++++
 .../syncope/fit/core/AuthenticationITCase.java  |   4 +-
 .../syncope/fit/core/BpmnProcessITCase.java     |   4 +-
 .../syncope/fit/core/UserRequestITCase.java     | 135 +++++++-
 .../apache/syncope/fit/core/UserSelfITCase.java |  45 +--
 pom.xml                                         |  10 +-
 75 files changed, 1909 insertions(+), 657 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/client/console/src/main/java/org/apache/syncope/client/console/commons/MapChoiceRenderer.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/MapChoiceRenderer.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/MapChoiceRenderer.java
index 2845105..92d060d 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/commons/MapChoiceRenderer.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/MapChoiceRenderer.java
@@ -23,33 +23,28 @@ import java.util.Map;
 import org.apache.wicket.markup.html.form.IChoiceRenderer;
 import org.apache.wicket.model.IModel;
 
-public class MapChoiceRenderer<T, K> implements IChoiceRenderer<T> {
+public class MapChoiceRenderer implements IChoiceRenderer<String> {
 
     private static final long serialVersionUID = -7452881117778186644L;
 
-    private final Map<T, K> map;
+    private final Map<String, String> map;
 
-    public MapChoiceRenderer(final Map<T, K> map) {
+    public MapChoiceRenderer(final Map<String, String> map) {
         this.map = map;
     }
 
     @Override
-    public Object getDisplayValue(final T key) {
+    public Object getDisplayValue(final String key) {
         return map.get(key);
     }
 
     @Override
-    public String getIdValue(final T key, final int index) {
-        return key.toString();
+    public String getIdValue(final String key, final int index) {
+        return key;
     }
 
     @Override
-    public T getObject(final String id, final IModel<? extends List<? extends T>> choices) {
-        for (Map.Entry<T, K> entry : map.entrySet()) {
-            if (entry.getValue() != null && entry.getValue().toString().equals(id)) {
-                return entry.getKey();
-            }
-        }
-        return null;
+    public String getObject(final String id, final IModel<? extends List<? extends String>> choices) {
+        return id;
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDropDownChoicePanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDropDownChoicePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDropDownChoicePanel.java
index 193c0ad..c52efac 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDropDownChoicePanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxDropDownChoicePanel.java
@@ -44,8 +44,7 @@ public class AjaxDropDownChoicePanel<T extends Serializable> extends FieldPanel<
 
         super(id, name, model);
 
-        field = new BootstrapSelect<>(
-                "dropDownChoiceField", model, Collections.<T>emptyList(), new ChoiceRenderer<>());
+        field = new BootstrapSelect<>("dropDownChoiceField", model, Collections.<T>emptyList(), new ChoiceRenderer<>());
         add(field.setLabel(new Model<>(name)).setOutputMarkupId(true));
 
         if (enableOnBlur) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java
index 06e126b..bfbdc1d 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java
@@ -136,14 +136,13 @@ public class Relationships extends WizardStep implements ICondition {
                 public Panel getPanel(final String panelId) {
                     return new ListViewPanel.Builder<>(RelationshipTO.class, pageRef).
                             setItems(relationships.get(relationship)).
-                            includes("otherEndType", "otherEndKey").
+                            includes("otherEndType", "otherEndKey", "otherEndName").
                             addAction(new ActionLink<RelationshipTO>() {
 
                                 private static final long serialVersionUID = -6847033126124401556L;
 
                                 @Override
-                                public void onClick(
-                                        final AjaxRequestTarget target, final RelationshipTO modelObject) {
+                                public void onClick(final AjaxRequestTarget target, final RelationshipTO modelObject) {
                                     removeRelationships(relationships, modelObject);
                                     send(Relationships.this, Broadcast.DEPTH, new ListViewReload<>(target));
                                 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships.properties
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships.properties b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships.properties
index cfefe54..45e8a78 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships.properties
@@ -16,3 +16,6 @@
 # under the License.
 relationships.empty.list=No relationships defined
 any.relationships=Relationships
+otherEndType=AnyType
+otherEndKey=Key
+otherEndName=Name

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_it.properties
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_it.properties b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_it.properties
index 5260a62..cdc900f 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_it.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_it.properties
@@ -16,3 +16,6 @@
 # under the License.
 relationships.empty.list=Nessuna relazione specificata
 any.relationships=Relazioni
+otherEndType=AnyType
+otherEndKey=Chiave
+otherEndName=Nome

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_ja.properties
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_ja.properties b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_ja.properties
index c0f26ae..5561099 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_ja.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_ja.properties
@@ -16,3 +16,6 @@
 # under the License.
 relationships.empty.list=\u95a2\u4fc2\u306f\u5b9a\u7fa9\u3055\u308c\u3066\u3044\u307e\u305b\u3093
 any.relationships=\u95a2\u4fc2
+otherEndType=AnyType
+otherEndKey=Key
+otherEndName=Name

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_pt_BR.properties
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_pt_BR.properties b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_pt_BR.properties
index 44f4ac8..c0c71dc 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_pt_BR.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_pt_BR.properties
@@ -16,3 +16,6 @@
 # under the License.
 relationships.empty.list=N\u00e3o h\u00e1 relacionamentos definidos
 any.relationships=Relationships
+otherEndType=AnyType
+otherEndKey=Key
+otherEndName=Name

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_ru.properties
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_ru.properties b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_ru.properties
index 93a15fb..9fcb63a 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_ru.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/any/Relationships_ru.properties
@@ -17,3 +17,6 @@
 
 relationships.empty.list=\u0421\u0432\u044f\u0437\u0438 \u043d\u0435 \u0437\u0430\u0434\u0430\u043d\u044b
 any.relationships=\u0421\u0432\u044f\u0437\u0438
+otherEndType=AnyType
+otherEndKey=Key
+otherEndName=Name

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/common/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTO.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTO.java
index 015e18c..06dbd0d 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTO.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/RelationshipTO.java
@@ -43,6 +43,13 @@ public class RelationshipTO extends AbstractBaseBean {
             return this;
         }
 
+        public Builder otherEnd(final String otherEndType, final String otherEndKey, final String otherEndName) {
+            instance.setOtherEndType(otherEndType);
+            instance.setOtherEndKey(otherEndKey);
+            instance.setOtherEndName(otherEndName);
+            return this;
+        }
+
         public RelationshipTO build() {
             return instance;
         }
@@ -54,6 +61,8 @@ public class RelationshipTO extends AbstractBaseBean {
 
     private String otherEndKey;
 
+    private String otherEndName;
+
     public String getType() {
         return type;
     }
@@ -78,4 +87,11 @@ public class RelationshipTO extends AbstractBaseBean {
         this.otherEndKey = otherEndKey;
     }
 
+    public String getOtherEndName() {
+        return otherEndName;
+    }
+
+    public void setOtherEndName(final String otherEndName) {
+        this.otherEndName = otherEndName;
+    }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnyObjectDAO.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnyObjectDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnyObjectDAO.java
index f001204..f8f05f9 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnyObjectDAO.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnyObjectDAO.java
@@ -46,7 +46,7 @@ public interface AnyObjectDAO extends AnyDAO<AnyObject> {
 
     List<Group> findDynGroups(String key);
 
-    List<Relationship<Any<?>, Any<?>>> findAllRelationships(AnyObject anyObject);
+    List<Relationship<Any<?>, AnyObject>> findAllRelationships(AnyObject anyObject);
 
     Collection<Group> findAllGroups(AnyObject anyObject);
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java
index 4d8aaea..a7211de 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java
@@ -168,20 +168,20 @@ public class JPAAnyObjectDAO extends AbstractAnyDAO<AnyObject> implements AnyObj
     }
 
     @Override
-    public List<Relationship<Any<?>, Any<?>>> findAllRelationships(final AnyObject anyObject) {
-        List<Relationship<Any<?>, Any<?>>> result = new ArrayList<>();
+    public List<Relationship<Any<?>, AnyObject>> findAllRelationships(final AnyObject anyObject) {
+        List<Relationship<Any<?>, AnyObject>> result = new ArrayList<>();
 
         @SuppressWarnings("unchecked")
-        TypedQuery<Relationship<Any<?>, Any<?>>> aquery =
-                (TypedQuery<Relationship<Any<?>, Any<?>>>) entityManager().createQuery(
+        TypedQuery<Relationship<Any<?>, AnyObject>> aquery =
+                (TypedQuery<Relationship<Any<?>, AnyObject>>) entityManager().createQuery(
                         "SELECT e FROM " + JPAARelationship.class.getSimpleName()
                         + " e WHERE e.rightEnd=:anyObject OR e.leftEnd=:anyObject");
         aquery.setParameter("anyObject", anyObject);
         result.addAll(aquery.getResultList());
 
         @SuppressWarnings("unchecked")
-        TypedQuery<Relationship<Any<?>, Any<?>>> uquery =
-                (TypedQuery<Relationship<Any<?>, Any<?>>>) entityManager().createQuery(
+        TypedQuery<Relationship<Any<?>, AnyObject>> uquery =
+                (TypedQuery<Relationship<Any<?>, AnyObject>>) entityManager().createQuery(
                         "SELECT e FROM " + JPAURelationship.class.getSimpleName()
                         + " e WHERE e.rightEnd=:anyObject");
         uquery.setParameter("anyObject", anyObject);

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
index fb05d94..5a41085 100644
--- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
@@ -253,14 +253,14 @@ under the License.
   <SyncopeRole_entitlements entitlement="ANYTYPE_READ" role_id="User manager"/>
   <SyncopeRole_entitlements entitlement="ANYTYPECLASS_LIST" role_id="User manager"/>
   <SyncopeRole_entitlements entitlement="ANYTYPECLASS_READ" role_id="User manager"/>
-  <SyncopeRole_entitlements entitlement="WORKFLOW_FORM_CLAIM" role_id="User manager"/>
-  <SyncopeRole_entitlements entitlement="WORKFLOW_FORM_SUBMIT" role_id="User manager"/>
+  <SyncopeRole_entitlements entitlement="USER_REQUEST_FORM_CLAIM" role_id="User manager"/>
+  <SyncopeRole_entitlements entitlement="USER_REQUEST_FORM_SUBMIT" role_id="User manager"/>
   <SyncopeRole_Realm role_id="User manager" realm_id="e4c28e7a-9dbf-4ee7-9441-93812a0d4a28"/>
 
   <SyncopeRole id="Other"/>
   <SyncopeRole_entitlements entitlement="SCHEMA_READ" role_id="Other"/>
   <SyncopeRole_entitlements entitlement="GROUP_READ" role_id="Other"/>
-  <SyncopeRole_entitlements entitlement="WORKFLOW_FORM_CLAIM" role_id="Other"/>
+  <SyncopeRole_entitlements entitlement="USER_REQUEST_FORM_CLAIM" role_id="Other"/>
   <SyncopeRole_Realm role_id="Other" realm_id="722f3d84-9c2b-4525-8f6e-e4b82c55a36c"/>
   <SyncopeRole_Privilege role_id="Other" privilege_id="postMighty"/>
   

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/MappingManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/MappingManagerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/MappingManagerImpl.java
index b6c63e3..33077fe 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/MappingManagerImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/MappingManagerImpl.java
@@ -770,11 +770,14 @@ public class MappingManagerImpl implements MappingManager {
                 default:
             }
         } else if (intAttrName.getSchemaType() != null && attr != null) {
-            GroupableRelatableTO groupableTO = null;
-            Group group = null;
+            GroupableRelatableTO groupableTO;
+            Group group;
             if (anyTO instanceof GroupableRelatableTO && intAttrName.getMembershipOfGroup() != null) {
                 groupableTO = (GroupableRelatableTO) anyTO;
                 group = groupDAO.findByName(intAttrName.getMembershipOfGroup());
+            } else {
+                groupableTO = null;
+                group = null;
             }
 
             switch (intAttrName.getSchemaType()) {
@@ -798,29 +801,28 @@ public class MappingManagerImpl implements MappingManager {
                     if (groupableTO == null || group == null) {
                         anyTO.getPlainAttrs().add(attrTO);
                     } else {
-                        Optional<MembershipTO> membership = groupableTO.getMembership(group.getKey());
-                        if (!membership.isPresent()) {
-                            membership = Optional.of(
-                                    new MembershipTO.Builder().group(group.getKey(), group.getName()).build());
-                            groupableTO.getMemberships().add(membership.get());
-                        }
-                        membership.get().getPlainAttrs().add(attrTO);
+                        MembershipTO membership = groupableTO.getMembership(group.getKey()).orElseGet(() -> {
+                            MembershipTO newMemb = new MembershipTO.Builder().group(group.getKey()).build();
+                            groupableTO.getMemberships().add(newMemb);
+                            return newMemb;
+                        });
+                        membership.getPlainAttrs().add(attrTO);
                     }
                     break;
 
                 case DERIVED:
                     attrTO = new AttrTO();
                     attrTO.setSchema(intAttrName.getSchemaName());
+
                     if (groupableTO == null || group == null) {
                         anyTO.getDerAttrs().add(attrTO);
                     } else {
-                        Optional<MembershipTO> membership = groupableTO.getMembership(group.getKey());
-                        if (!membership.isPresent()) {
-                            membership = Optional.of(
-                                    new MembershipTO.Builder().group(group.getKey(), group.getName()).build());
-                            groupableTO.getMemberships().add(membership.get());
-                        }
-                        membership.get().getDerAttrs().add(attrTO);
+                        MembershipTO membership = groupableTO.getMembership(group.getKey()).orElseGet(() -> {
+                            MembershipTO newMemb = new MembershipTO.Builder().group(group.getKey()).build();
+                            groupableTO.getMemberships().add(newMemb);
+                            return newMemb;
+                        });
+                        membership.getDerAttrs().add(attrTO);
                     }
                     break;
 
@@ -838,13 +840,12 @@ public class MappingManagerImpl implements MappingManager {
                     if (groupableTO == null || group == null) {
                         anyTO.getVirAttrs().add(attrTO);
                     } else {
-                        Optional<MembershipTO> membership = groupableTO.getMembership(group.getKey());
-                        if (!membership.isPresent()) {
-                            membership = Optional.of(
-                                    new MembershipTO.Builder().group(group.getKey(), group.getName()).build());
-                            groupableTO.getMemberships().add(membership.get());
-                        }
-                        membership.get().getVirAttrs().add(attrTO);
+                        MembershipTO membership = groupableTO.getMembership(group.getKey()).orElseGet(() -> {
+                            MembershipTO newMemb = new MembershipTO.Builder().group(group.getKey()).build();
+                            groupableTO.getMemberships().add(newMemb);
+                            return newMemb;
+                        });
+                        membership.getVirAttrs().add(attrTO);
                     }
                     break;
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
index 5c134df..c7499a8 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
@@ -69,6 +69,7 @@ import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.VirSchema;
+import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
@@ -571,9 +572,9 @@ abstract class AbstractAnyDataBinder {
         anyTO.getResources().addAll(resources.stream().map(Entity::getKey).collect(Collectors.toSet()));
     }
 
-    protected RelationshipTO getRelationshipTO(final String relationshipType, final Any<?> otherEnd) {
+    protected RelationshipTO getRelationshipTO(final String relationshipType, final AnyObject otherEnd) {
         return new RelationshipTO.Builder().
-                type(relationshipType).otherEnd(otherEnd.getType().getKey(), otherEnd.getKey()).
+                type(relationshipType).otherEnd(otherEnd.getType().getKey(), otherEnd.getKey(), otherEnd.getName()).
                 build();
     }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
index 129d0e4..1dd6194 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
@@ -101,11 +101,12 @@ public class AnyObjectDataBinderImpl extends AbstractAnyDataBinder implements An
 
             // relationships
             anyObjectTO.getRelationships().addAll(
-                    anyObjectDAO.findAllRelationships(anyObject).stream().map(relationship -> getRelationshipTO(
-                    relationship.getType().getKey(),
-                    relationship.getLeftEnd().getKey().equals(anyObject.getKey())
-                    ? relationship.getRightEnd()
-                    : relationship.getLeftEnd())).
+                    anyObjectDAO.findAllRelationships(anyObject).stream().
+                            map(relationship -> getRelationshipTO(
+                            relationship.getType().getKey(),
+                            relationship.getLeftEnd().getKey().equals(anyObject.getKey())
+                            ? relationship.getRightEnd()
+                            : anyObject)).
                             collect(Collectors.toList()));
 
             // memberships

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
index d328c43..58c2d7d 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
@@ -602,10 +602,9 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat
                     flatMap(role -> role.getPrivileges().stream()).map(Entity::getKey).collect(Collectors.toSet()));
 
             // relationships
-            userTO.getRelationships().addAll(
-                    user.getRelationships().stream().map(relationship -> getRelationshipTO(
-                    relationship.getType().getKey(), relationship.getRightEnd())).
-                            collect(Collectors.toList()));
+            userTO.getRelationships().addAll(user.getRelationships().stream().
+                    map(relationship -> getRelationshipTO(relationship.getType().getKey(), relationship.getRightEnd())).
+                    collect(Collectors.toList()));
 
             // memberships
             userTO.getMemberships().addAll(

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/client-console/pom.xml
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/pom.xml b/ext/flowable/client-console/pom.xml
index 6f8020f..1d609b1 100644
--- a/ext/flowable/client-console/pom.xml
+++ b/ext/flowable/client-console/pom.xml
@@ -83,35 +83,37 @@ under the License.
               <target>
                 <unzip src="${settings.localRepository}/org/flowable/flowable-ui-modeler-app/${flowable.version}/flowable-ui-modeler-app-${flowable.version}.war" dest="${flowable-modeler.directory}">
                   <patternset>
-                    <include name="WEB-INF/classes/static/**" />
-                    <include name="WEB-INF/lib/flowable-ui-modeler-logic-${flowable.version}.jar" />
+                    <include name="WEB-INF/classes/static/**"/>
+                    <include name="WEB-INF/lib/flowable-ui-modeler-logic-${flowable.version}.jar"/>
                   </patternset>
                 </unzip>
                 
                 <unzip src="${flowable-modeler.directory}/WEB-INF/lib/flowable-ui-modeler-logic-${flowable.version}.jar" dest="${flowable-modeler.directory}">
                   <patternset>
-                    <include name="stencilset_bpmn.json" />
+                    <include name="stencilset_bpmn.json"/>
                   </patternset>
                 </unzip>
                 
                 <move todir="${flowable-modeler.directory}">
                   <fileset dir="${flowable-modeler.directory}/WEB-INF/classes/static/">
-                    <include name="**" />
+                    <include name="**"/>
                   </fileset>
                 </move>
-                <delete dir="${flowable-modeler.directory}/WEB-INF" />
+                <delete dir="${flowable-modeler.directory}/WEB-INF"/>
                 
-                <replace file="${flowable-modeler.directory}/index.html" token="&lt;/head&gt;" value="&lt;script type=&quot;text/javascript&quot;&gt;window.onunload = refreshParent; function refreshParent() { window.opener.location.reload(); }&lt;/script&gt;&lt;/head&gt;" />
-                <replace file="${flowable-modeler.directory}/index.html" token=" ng-click=&quot;backToLanding()&quot;" value=" disabled=&quot;disabled&quot;" />
-                <replace file="${flowable-modeler.directory}/index.html" token="&lt;ul class=&quot;nav navbar-nav&quot; id=&quot;main-nav&quot;" value="&lt;ul class=&quot;nav navbar-nav&quot; id=&quot;main-nav&quot; style=&quot;display: none;&quot;" />
-                <replace file="${flowable-modeler.directory}/index.html" token="&lt;div class=&quot;pull-right" value="&lt;div style=&quot;display: none;&quot; class=&quot;pull-right" />
-                <replace file="${flowable-modeler.directory}/editor-app/editor.html" token="&lt;div class=&quot;btn-group pull-right&quot;" value="&lt;div style=&quot;display: none;&quot; class=&quot;btn-group pull-right&quot;" />
-                <replace file="${flowable-modeler.directory}/editor-app/configuration/toolbar-default-actions.js" token="$location.path('/processes');" value="window.close();" />
+                <replace file="${flowable-modeler.directory}/index.html" token="&lt;/head&gt;" value="&lt;script type=&quot;text/javascript&quot;&gt;window.onunload = refreshParent; function refreshParent() { window.opener.location.reload(); }&lt;/script&gt;&lt;/head&gt;"/>
+                <replace file="${flowable-modeler.directory}/index.html" token=" ng-click=&quot;backToLanding()&quot;" value=" disabled=&quot;disabled&quot;"/>
+                <replace file="${flowable-modeler.directory}/index.html" token="&lt;ul class=&quot;nav navbar-nav&quot; id=&quot;main-nav&quot;" value="&lt;ul class=&quot;nav navbar-nav&quot; id=&quot;main-nav&quot; style=&quot;display: none;&quot;"/>
+                <replace file="${flowable-modeler.directory}/index.html" token="&lt;div class=&quot;pull-right" value="&lt;div style=&quot;display: none;&quot; class=&quot;pull-right"/>
+                <replace file="${flowable-modeler.directory}/editor-app/editor.html" token="&lt;div class=&quot;btn-group pull-right&quot;" value="&lt;div style=&quot;display: none;&quot; class=&quot;btn-group pull-right&quot;"/>
+                <replace file="${flowable-modeler.directory}/editor-app/configuration/toolbar-default-actions.js" token="$location.path('/processes');" value="window.close();"/>
  
-                <copy file="${basedir}/src/main/resources/app-cfg.js" todir="${flowable-modeler.directory}/scripts" overwrite="true" />
-                <copy file="${basedir}/src/main/resources/url-config.js" todir="${flowable-modeler.directory}/editor-app/configuration" overwrite="true" />
-                <copy file="${basedir}/src/main/resources/toolbar.js" todir="${flowable-modeler.directory}/editor-app/configuration" overwrite="true" />
-                <copy file="${basedir}/src/main/resources/save-model.html" todir="${flowable-modeler.directory}/editor-app/popups" overwrite="true" />
+                <copy file="${basedir}/src/main/resources/app-cfg.js" todir="${flowable-modeler.directory}/scripts" overwrite="true"/>
+                <copy file="${basedir}/src/main/resources/url-config.js" todir="${flowable-modeler.directory}/editor-app/configuration" overwrite="true"/>
+                <copy file="${basedir}/src/main/resources/toolbar.js" todir="${flowable-modeler.directory}/editor-app/configuration" overwrite="true"/>
+                <copy file="${basedir}/src/main/resources/save-model.html" todir="${flowable-modeler.directory}/editor-app/popups" overwrite="true"/>
+
+                <patch patchfile="${basedir}/src/main/resources/dropdown.diff" dir="${flowable-modeler.directory}" strip="1"/>
               </target>
             </configuration>
             <goals>

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/approvals/Approval.java
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/approvals/Approval.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/approvals/Approval.java
index 3bea1a9..6a66afc 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/approvals/Approval.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/approvals/Approval.java
@@ -45,7 +45,6 @@ import org.apache.wicket.markup.html.list.ListView;
 import org.apache.wicket.markup.html.panel.Panel;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.LoadableDetachableModel;
-import org.apache.wicket.model.Model;
 import org.apache.wicket.model.PropertyModel;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -103,7 +102,7 @@ public abstract class Approval extends Panel {
                         break;
 
                     case Date:
-                        final FastDateFormat formatter = FastDateFormat.getInstance(prop.getDatePattern());
+                        FastDateFormat formatter = FastDateFormat.getInstance(prop.getDatePattern());
                         field = new AjaxDateTimeFieldPanel("value", label, new PropertyModel<Date>(prop, "value") {
 
                             private static final long serialVersionUID = -3743432456095828573L;
@@ -111,11 +110,9 @@ public abstract class Approval extends Panel {
                             @Override
                             public Date getObject() {
                                 try {
-                                    if (StringUtils.isBlank(prop.getValue())) {
-                                        return null;
-                                    } else {
-                                        return formatter.parse(prop.getValue());
-                                    }
+                                    return StringUtils.isBlank(prop.getValue())
+                                            ? null
+                                            : formatter.parse(prop.getValue());
                                 } catch (ParseException e) {
                                     LOG.error("Unparsable date: {}", prop.getValue(), e);
                                     return null;
@@ -131,19 +128,17 @@ public abstract class Approval extends Panel {
                         break;
 
                     case Enum:
-                        MapChoiceRenderer<String, String> enumCR = new MapChoiceRenderer<>(prop.getEnumValues());
-
                         field = new AjaxDropDownChoicePanel(
                                 "value", label, new PropertyModel<String>(prop, "value"), false).
-                                setChoiceRenderer(enumCR).setChoices(new Model<ArrayList<String>>() {
-
-                            private static final long serialVersionUID = -858521070366432018L;
+                                setChoiceRenderer(new MapChoiceRenderer(prop.getEnumValues())).
+                                setChoices(new ArrayList<>(prop.getEnumValues().keySet()));
+                        break;
 
-                            @Override
-                            public ArrayList<String> getObject() {
-                                return new ArrayList<>(prop.getEnumValues().keySet());
-                            }
-                        });
+                    case Dropdown:
+                        field = new AjaxDropDownChoicePanel(
+                                "value", label, new PropertyModel<String>(prop, "value"), false).
+                                setChoiceRenderer(new MapChoiceRenderer(prop.getDropdownValues())).
+                                setChoices(new ArrayList<>(prop.getDropdownValues().keySet()));
                         break;
 
                     case Long:

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalDirectoryPanel.java
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalDirectoryPanel.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalDirectoryPanel.java
index e16f5c2..577ea65 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalDirectoryPanel.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalDirectoryPanel.java
@@ -113,7 +113,7 @@ public class ApprovalDirectoryPanel
 
         initResultTable();
 
-        MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, FlowableEntitlement.WORKFLOW_FORM_SUBMIT);
+        MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, FlowableEntitlement.USER_REQUEST_FORM_SUBMIT);
     }
 
     @Override
@@ -121,7 +121,7 @@ public class ApprovalDirectoryPanel
         List<IColumn<UserRequestForm, String>> columns = new ArrayList<>();
 
         columns.add(new PropertyColumn<>(
-                new ResourceModel("taskId"), "taskId", "taskId"));
+                new ResourceModel("bpmnProcess"), "bpmnProcess", "bpmnProcess"));
         columns.add(new PropertyColumn<>(
                 new ResourceModel("key"), "formKey", "formKey"));
         columns.add(new PropertyColumn<>(
@@ -151,7 +151,7 @@ public class ApprovalDirectoryPanel
                 ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
                 target.add(container);
             }
-        }, ActionLink.ActionType.CLAIM, FlowableEntitlement.WORKFLOW_FORM_CLAIM);
+        }, ActionLink.ActionType.CLAIM, FlowableEntitlement.USER_REQUEST_FORM_CLAIM);
 
         panel.add(new ActionLink<UserRequestForm>() {
 
@@ -190,7 +190,7 @@ public class ApprovalDirectoryPanel
                         equals(model.getObject().getOwner());
             }
 
-        }, ActionLink.ActionType.MANAGE_APPROVAL, FlowableEntitlement.WORKFLOW_FORM_READ);
+        }, ActionLink.ActionType.MANAGE_APPROVAL, FlowableEntitlement.USER_REQUEST_FORM_SUBMIT);
 
         // SYNCOPE-1200 edit user while in approval state
         panel.add(new ActionLink<UserRequestForm>() {
@@ -239,7 +239,7 @@ public class ApprovalDirectoryPanel
                         equals(model.getObject().getOwner());
             }
 
-        }, ActionLink.ActionType.EDIT_APPROVAL, FlowableEntitlement.WORKFLOW_FORM_SUBMIT);
+        }, ActionLink.ActionType.EDIT_APPROVAL, FlowableEntitlement.USER_REQUEST_FORM_SUBMIT);
 
         return panel;
     }
@@ -326,8 +326,6 @@ public class ApprovalDirectoryPanel
         protected Serializable onApplyInternal(final AnyWrapper<UserTO> modelObject) {
             UserTO inner = modelObject.getInnerObject();
 
-            ProvisioningResult<UserTO> result;
-
             UserPatch patch = AnyOperations.diff(inner, formTO.getUserTO(), false);
 
             if (StringUtils.isNotBlank(inner.getPassword())) {
@@ -337,16 +335,15 @@ public class ApprovalDirectoryPanel
                         build();
                 patch.setPassword(passwordPatch);
             }
+
             // update just if it is changed
+            ProvisioningResult<UserTO> result;
             if (patch.isEmpty()) {
                 result = new ProvisioningResult<>();
                 result.setEntity(inner);
             } else {
                 result = userRestClient.update(getOriginalItem().getInnerObject().getETagValue(), patch);
-                UserRequestForm workFlowTO = restClient.getForms(result.getEntity().getKey()).get(0);
-                if (workFlowTO != null) {
-                    claimForm(workFlowTO.getTaskId());
-                }
+                restClient.getForm(result.getEntity().getKey()).ifPresent(form -> claimForm(form.getTaskId()));
             }
 
             return result;

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/pages/Flowable.java
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/pages/Flowable.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/pages/Flowable.java
index 562aad1..8062a77 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/pages/Flowable.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/pages/Flowable.java
@@ -23,7 +23,7 @@ import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.annotations.ExtPage;
 import org.apache.syncope.client.console.panels.BpmnProcessDirectoryPanel;
 import org.apache.syncope.client.console.wizards.WizardMgtPanel;
-import org.apache.syncope.common.lib.to.BpmnProcessTO;
+import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.syncope.common.lib.types.FlowableEntitlement;
 import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
 import org.apache.wicket.markup.html.WebMarkupContainer;
@@ -48,7 +48,7 @@ public class Flowable extends BaseExtPage {
         disabled.setOutputMarkupPlaceholderTag(true);
         content.add(disabled);
 
-        WizardMgtPanel<BpmnProcessTO> bpmnProcessesPanel = new BpmnProcessDirectoryPanel.Builder(getPageReference()) {
+        WizardMgtPanel<BpmnProcess> bpmnProcessesPanel = new BpmnProcessDirectoryPanel.Builder(getPageReference()) {
 
             private static final long serialVersionUID = -5960765294082359003L;
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/BpmnProcessDirectoryPanel.java
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/BpmnProcessDirectoryPanel.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/BpmnProcessDirectoryPanel.java
index b7914bb..8f4d566 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/BpmnProcessDirectoryPanel.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/BpmnProcessDirectoryPanel.java
@@ -43,7 +43,7 @@ import org.apache.syncope.client.console.wicket.markup.html.form.XMLEditorPanel;
 import org.apache.syncope.client.console.wizards.AjaxWizardBuilder;
 import org.apache.syncope.client.console.wizards.WizardMgtPanel;
 import org.apache.syncope.common.lib.SyncopeClientException;
-import org.apache.syncope.common.lib.to.BpmnProcessTO;
+import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.syncope.common.lib.types.FlowableEntitlement;
 import org.apache.wicket.Page;
 import org.apache.wicket.PageReference;
@@ -65,7 +65,7 @@ import org.apache.wicket.request.mapper.parameter.PageParameters;
 import org.apache.wicket.util.io.IOUtils;
 
 public class BpmnProcessDirectoryPanel extends DirectoryPanel<
-        BpmnProcessTO, BpmnProcessTO, BpmProcessDataProvider, BpmnProcessRestClient> {
+        BpmnProcess, BpmnProcess, BpmProcessDataProvider, BpmnProcessRestClient> {
 
     private static final long serialVersionUID = 2705668831139984998L;
 
@@ -76,13 +76,13 @@ public class BpmnProcessDirectoryPanel extends DirectoryPanel<
     protected BpmnProcessDirectoryPanel(final String id, final Builder builder) {
         super(id, builder);
 
-        this.addNewItemPanelBuilder(new AjaxWizardBuilder<BpmnProcessTO>(new BpmnProcessTO(), pageRef) {
+        this.addNewItemPanelBuilder(new AjaxWizardBuilder<BpmnProcess>(new BpmnProcess(), pageRef) {
 
             private static final long serialVersionUID = 1633859795677053912L;
 
             @Override
             protected WizardModel buildModelSteps(
-                    final BpmnProcessTO modelObject, final WizardModel wizardModel) {
+                    final BpmnProcess modelObject, final WizardModel wizardModel) {
 
                 return wizardModel;
             }
@@ -133,8 +133,8 @@ public class BpmnProcessDirectoryPanel extends DirectoryPanel<
     }
 
     @Override
-    protected List<IColumn<BpmnProcessTO, String>> getColumns() {
-        List<IColumn<BpmnProcessTO, String>> columns = new ArrayList<>();
+    protected List<IColumn<BpmnProcess, String>> getColumns() {
+        List<IColumn<BpmnProcess, String>> columns = new ArrayList<>();
 
         columns.add(new PropertyColumn<>(new ResourceModel("key"), "key"));
         columns.add(new PropertyColumn<>(new ResourceModel("name"), "name", "name"));
@@ -143,15 +143,15 @@ public class BpmnProcessDirectoryPanel extends DirectoryPanel<
     }
 
     @Override
-    public ActionsPanel<BpmnProcessTO> getActions(final IModel<BpmnProcessTO> model) {
-        final ActionsPanel<BpmnProcessTO> panel = super.getActions(model);
+    public ActionsPanel<BpmnProcess> getActions(final IModel<BpmnProcess> model) {
+        final ActionsPanel<BpmnProcess> panel = super.getActions(model);
 
-        panel.add(new ActionLink<BpmnProcessTO>() {
+        panel.add(new ActionLink<BpmnProcess>() {
 
             private static final long serialVersionUID = -184018732772021627L;
 
             @Override
-            public void onClick(final AjaxRequestTarget target, final BpmnProcessTO ignore) {
+            public void onClick(final AjaxRequestTarget target, final BpmnProcess ignore) {
                 final IModel<String> wfDefinition = new Model<>();
                 try {
                     wfDefinition.setObject(IOUtils.toString(restClient.getDefinition(
@@ -189,12 +189,12 @@ public class BpmnProcessDirectoryPanel extends DirectoryPanel<
             }
         }, ActionLink.ActionType.EDIT, FlowableEntitlement.BPMN_PROCESS_SET);
 
-        panel.add(new ActionLink<BpmnProcessTO>() {
+        panel.add(new ActionLink<BpmnProcess>() {
 
             private static final long serialVersionUID = 3109256773218160485L;
 
             @Override
-            public void onClick(final AjaxRequestTarget target, final BpmnProcessTO ignore) {
+            public void onClick(final AjaxRequestTarget target, final BpmnProcess ignore) {
                 modal.header(Model.of(model.getObject().getKey()));
                 modal.setContent(new ImageModalPanel<>(
                         modal, restClient.getDiagram(model.getObject().getKey()), pageRef));
@@ -203,7 +203,7 @@ public class BpmnProcessDirectoryPanel extends DirectoryPanel<
             }
         }, ActionLink.ActionType.VIEW, FlowableEntitlement.BPMN_PROCESS_GET);
 
-        panel.add(new ActionLink<BpmnProcessTO>() {
+        panel.add(new ActionLink<BpmnProcess>() {
 
             private static final long serialVersionUID = -184018732772021627L;
 
@@ -222,22 +222,22 @@ public class BpmnProcessDirectoryPanel extends DirectoryPanel<
             }
 
             @Override
-            public void onClick(final AjaxRequestTarget target, final BpmnProcessTO ignore) {
+            public void onClick(final AjaxRequestTarget target, final BpmnProcess ignore) {
                 // do nothing
             }
         }, ActionLink.ActionType.EXTERNAL_EDITOR, FlowableEntitlement.BPMN_PROCESS_SET);
 
-        panel.add(new ActionLink<BpmnProcessTO>() {
+        panel.add(new ActionLink<BpmnProcess>() {
 
             private static final long serialVersionUID = -7978723352517770644L;
 
             @Override
-            protected boolean statusCondition(final BpmnProcessTO modelObject) {
+            protected boolean statusCondition(final BpmnProcess modelObject) {
                 return !modelObject.isUserWorkflow();
             }
 
             @Override
-            public void onClick(final AjaxRequestTarget target, final BpmnProcessTO ignore) {
+            public void onClick(final AjaxRequestTarget target, final BpmnProcess ignore) {
                 try {
                     restClient.deleteDefinition(model.getObject().getKey());
                     SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED));
@@ -260,7 +260,7 @@ public class BpmnProcessDirectoryPanel extends DirectoryPanel<
     }
 
     public abstract static class Builder
-            extends DirectoryPanel.Builder<BpmnProcessTO, BpmnProcessTO, BpmnProcessRestClient> {
+            extends DirectoryPanel.Builder<BpmnProcess, BpmnProcess, BpmnProcessRestClient> {
 
         private static final long serialVersionUID = 5088962796986706805L;
 
@@ -269,16 +269,16 @@ public class BpmnProcessDirectoryPanel extends DirectoryPanel<
         }
 
         @Override
-        protected WizardMgtPanel<BpmnProcessTO> newInstance(final String id, final boolean wizardInModal) {
+        protected WizardMgtPanel<BpmnProcess> newInstance(final String id, final boolean wizardInModal) {
             return new BpmnProcessDirectoryPanel(id, this);
         }
     }
 
-    protected class BpmProcessDataProvider extends DirectoryDataProvider<BpmnProcessTO> {
+    protected class BpmProcessDataProvider extends DirectoryDataProvider<BpmnProcess> {
 
         private static final long serialVersionUID = 1764153405387687592L;
 
-        private final SortableDataProviderComparator<BpmnProcessTO> comparator;
+        private final SortableDataProviderComparator<BpmnProcess> comparator;
 
         private final BpmnProcessRestClient restClient = new BpmnProcessRestClient();
 
@@ -289,8 +289,8 @@ public class BpmnProcessDirectoryPanel extends DirectoryPanel<
         }
 
         @Override
-        public Iterator<BpmnProcessTO> iterator(final long first, final long count) {
-            List<BpmnProcessTO> result = restClient.getDefinitions();
+        public Iterator<BpmnProcess> iterator(final long first, final long count) {
+            List<BpmnProcess> result = restClient.getDefinitions();
             Collections.sort(result, comparator);
             return result.subList((int) first, (int) first + (int) count).iterator();
         }
@@ -301,7 +301,7 @@ public class BpmnProcessDirectoryPanel extends DirectoryPanel<
         }
 
         @Override
-        public IModel<BpmnProcessTO> model(final BpmnProcessTO object) {
+        public IModel<BpmnProcess> model(final BpmnProcess object) {
             return new CompoundPropertyModel<>(object);
         }
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/AbstractBpmnProcessResource.java
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/AbstractBpmnProcessResource.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/AbstractBpmnProcessResource.java
index 91a42e3..7ad5f24 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/AbstractBpmnProcessResource.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/AbstractBpmnProcessResource.java
@@ -21,7 +21,7 @@ package org.apache.syncope.client.console.resources;
 import javax.ws.rs.NotFoundException;
 import org.apache.syncope.client.console.commons.Constants;
 import org.apache.syncope.client.console.rest.BpmnProcessRestClient;
-import org.apache.syncope.common.lib.to.BpmnProcessTO;
+import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.wicket.request.resource.AbstractResource;
 import org.apache.wicket.util.string.StringValue;
 import org.slf4j.Logger;
@@ -35,10 +35,10 @@ abstract class AbstractBpmnProcessResource extends AbstractResource {
 
     protected final BpmnProcessRestClient restClient = new BpmnProcessRestClient();
 
-    protected BpmnProcessTO getBpmnProcess(final Attributes attributes) {
+    protected BpmnProcess getBpmnProcess(final Attributes attributes) {
         StringValue modelId = attributes.getRequest().getQueryParameters().getParameterValue(Constants.MODEL_ID_PARAM);
 
-        BpmnProcessTO bpmnProcess = modelId == null || modelId.isNull()
+        BpmnProcess bpmnProcess = modelId == null || modelId.isNull()
                 ? null
                 : restClient.getDefinitions().stream().
                         filter(object -> modelId.toString().equals(object.getModelId())).findAny().orElse(null);

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/BpmnProcessGETResource.java
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/BpmnProcessGETResource.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/BpmnProcessGETResource.java
index ba34502..dfe633e 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/BpmnProcessGETResource.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/BpmnProcessGETResource.java
@@ -23,7 +23,7 @@ import java.nio.charset.StandardCharsets;
 import javax.ws.rs.core.MediaType;
 import org.apache.syncope.client.console.annotations.Resource;
 import org.apache.syncope.client.console.rest.BpmnProcessRestClient;
-import org.apache.syncope.common.lib.to.BpmnProcessTO;
+import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.wicket.util.io.IOUtils;
 
 /**
@@ -36,7 +36,7 @@ public class BpmnProcessGETResource extends AbstractBpmnProcessResource {
 
     @Override
     protected ResourceResponse newResourceResponse(final Attributes attributes) {
-        final BpmnProcessTO toGet = getBpmnProcess(attributes);
+        final BpmnProcess toGet = getBpmnProcess(attributes);
 
         ResourceResponse response = new ResourceResponse();
         response.disableCaching();

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/BpmnProcessPUTResource.java
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/BpmnProcessPUTResource.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/BpmnProcessPUTResource.java
index d583396..1031ce8 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/BpmnProcessPUTResource.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/resources/BpmnProcessPUTResource.java
@@ -24,7 +24,7 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import org.apache.cxf.common.util.UrlUtils;
 import org.apache.syncope.client.console.annotations.Resource;
-import org.apache.syncope.common.lib.to.BpmnProcessTO;
+import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.wicket.util.io.IOUtils;
 
 /**
@@ -52,7 +52,7 @@ public class BpmnProcessPUTResource extends AbstractBpmnProcessResource {
             LOG.error("Could not extract BPMN process", e);
         }
 
-        BpmnProcessTO toSet = getBpmnProcess(attributes);
+        BpmnProcess toSet = getBpmnProcess(attributes);
 
         if (definition == null || toSet == null) {
             return new ResourceResponse().setStatusCode(Response.Status.BAD_REQUEST.getStatusCode()).

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/rest/BpmnProcessRestClient.java
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/rest/BpmnProcessRestClient.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/rest/BpmnProcessRestClient.java
index 2b7c368..9e88431 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/rest/BpmnProcessRestClient.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/rest/BpmnProcessRestClient.java
@@ -25,7 +25,7 @@ import javax.ws.rs.core.Response;
 import org.apache.cxf.helpers.IOUtils;
 import org.apache.cxf.jaxrs.client.WebClient;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
-import org.apache.syncope.common.lib.to.BpmnProcessTO;
+import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.service.BpmnProcessService;
 
@@ -37,7 +37,7 @@ public class BpmnProcessRestClient extends BaseRestClient {
         return SyncopeConsoleSession.get().getService(mediaType, BpmnProcessService.class);
     }
 
-    public List<BpmnProcessTO> getDefinitions() {
+    public List<BpmnProcess> getDefinitions() {
         return getService(BpmnProcessService.class).list();
     }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/rest/UserRequestRestClient.java
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/rest/UserRequestRestClient.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/rest/UserRequestRestClient.java
index 555555b..e3a179f 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/rest/UserRequestRestClient.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/rest/UserRequestRestClient.java
@@ -19,6 +19,8 @@
 package org.apache.syncope.client.console.rest;
 
 import java.util.List;
+import java.util.Optional;
+import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.to.UserRequestForm;
 import org.apache.syncope.common.rest.api.beans.UserRequestFormQuery;
 import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
@@ -40,8 +42,13 @@ public class UserRequestRestClient extends BaseRestClient {
                 getResult();
     }
 
-    public List<UserRequestForm> getForms(final String userKey) {
-        return getService(UserRequestService.class).getForms(userKey);
+    public Optional<UserRequestForm> getForm(final String userKey) {
+        PagedResult<UserRequestForm> forms = getService(UserRequestService.class).
+                getForms(new UserRequestFormQuery.Builder().user(userKey).page(1).size(1).build());
+        UserRequestForm form = forms.getResult().isEmpty()
+                ? null
+                : forms.getResult().get(0);
+        return Optional.ofNullable(form);
     }
 
     public UserRequestForm claimForm(final String taskKey) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/widgets/ApprovalsWidget.java
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/widgets/ApprovalsWidget.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/widgets/ApprovalsWidget.java
index 8f8329e..9a9fd2d 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/widgets/ApprovalsWidget.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/widgets/ApprovalsWidget.java
@@ -87,8 +87,7 @@ public class ApprovalsWidget extends ExtAlertWidget<UserRequestForm> {
 
     @Override
     protected int getLatestAlertsSize() {
-        return SyncopeConsoleSession.get().owns(FlowableEntitlement.WORKFLOW_FORM_LIST)
-                && SyncopeConsoleSession.get().owns(FlowableEntitlement.WORKFLOW_FORM_READ)
+        return SyncopeConsoleSession.get().owns(FlowableEntitlement.USER_REQUEST_FORM_LIST)
                 ? restClient.countForms()
                 : 0;
     }
@@ -102,9 +101,7 @@ public class ApprovalsWidget extends ExtAlertWidget<UserRequestForm> {
             @Override
             public List<UserRequestForm> getObject() {
                 List<UserRequestForm> updatedApprovals;
-                if (SyncopeConsoleSession.get().owns(FlowableEntitlement.WORKFLOW_FORM_LIST)
-                        && SyncopeConsoleSession.get().owns(FlowableEntitlement.WORKFLOW_FORM_READ)) {
-
+                if (SyncopeConsoleSession.get().owns(FlowableEntitlement.USER_REQUEST_FORM_LIST)) {
                     updatedApprovals = restClient.getForms(1, MAX_SIZE, new SortParam<>("createTime", true));
                 } else {
                     updatedApprovals = Collections.<UserRequestForm>emptyList();
@@ -118,7 +115,8 @@ public class ApprovalsWidget extends ExtAlertWidget<UserRequestForm> {
     @Override
     protected AbstractLink getEventsLink(final String linkid) {
         BookmarkablePageLink<Approvals> approvals = BookmarkablePageLinkBuilder.build(linkid, Approvals.class);
-        MetaDataRoleAuthorizationStrategy.authorize(approvals, WebPage.ENABLE, FlowableEntitlement.WORKFLOW_FORM_LIST);
+        MetaDataRoleAuthorizationStrategy.authorize(
+                approvals, WebPage.ENABLE, FlowableEntitlement.USER_REQUEST_FORM_LIST);
         return approvals;
     }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/client-console/src/main/resources/dropdown.diff
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/resources/dropdown.diff b/ext/flowable/client-console/src/main/resources/dropdown.diff
new file mode 100644
index 0000000..80fe1e7
--- /dev/null
+++ b/ext/flowable/client-console/src/main/resources/dropdown.diff
@@ -0,0 +1,139 @@
+# 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.
+diff --git a/editor-app/configuration/properties-form-properties-controller.js b/editor-app/configuration/properties-form-properties-controller.js
+index ca8f051..fc02151 100644
+--- a/editor-app/configuration/properties-form-properties-controller.js
++++ b/editor-app/configuration/properties-form-properties-controller.js
+@@ -102,6 +102,20 @@ angular.module('flowableModeler').controller('FlowableFormPropertiesPopupCtrl',
+                 { field: 'name', displayName: $scope.labels.nameLabel}]
+             }
+ 
++            $scope.dropdownGridOptions = {
++    		    data: $scope.enumValues,
++                headerRowHeight: 28,
++                enableRowSelection: true,
++                enableRowHeaderSelection: false,
++                multiSelect: false,
++                modifierKeysToMultiSelect: false,
++                enableHorizontalScrollbar: 0,
++                enableColumnMenus: false,
++                enableSorting: false,
++                columnDefs: [{ field: 'id', displayName: $scope.labels.idLabel },
++                { field: 'name', displayName: $scope.labels.nameLabel}]
++            }
++
+             $scope.gridOptions.onRegisterApi = function (gridApi) {
+                 //set gridApi on scope
+                 $scope.gridApi = gridApi;
+@@ -124,6 +138,13 @@ angular.module('flowableModeler').controller('FlowableFormPropertiesPopupCtrl',
+                     $scope.selectedEnumValue = row.entity;
+                 });
+             };
++            $scope.dropdownGridOptions.onRegisterApi = function (gridApi) {
++                //set gridApi on scope
++                $scope.dropdownGridApi = gridApi;
++                gridApi.selection.on.rowSelectionChanged($scope, function (row) {
++                    $scope.selectedDropdownValue = row.entity;
++                });
++            };
+         });
+ 
+         // Handler for when the value of the type dropdown changes
+@@ -143,7 +164,12 @@ angular.module('flowableModeler').controller('FlowableFormPropertiesPopupCtrl',
+                 for (var i = 0; i < $scope.selectedProperty.enumValues.length; i++) {
+                     $scope.enumValues.push($scope.selectedProperty.enumValues[i]);
+                 }
+-                
++            } else if ($scope.selectedProperty.type === 'dropdown') {
++                $scope.selectedProperty.enumValues = [ {id: 'dropdownValueProvider', name: 'provider'}];
++                $scope.enumValues.length = 0;
++                for (var i = 0; i < $scope.selectedProperty.enumValues.length; i++) {
++                    $scope.enumValues.push($scope.selectedProperty.enumValues[i]);
++                }
+             } else {
+                 delete $scope.selectedProperty.enumValues;
+                 $scope.enumValues.length = 0;
+@@ -324,4 +350,4 @@ angular.module('flowableModeler').controller('FlowableFormPropertiesPopupCtrl',
+         };
+ 
+     }])
+-;
+\ No newline at end of file
++;
+diff --git a/editor-app/configuration/properties/form-properties-popup.html b/editor-app/configuration/properties/form-properties-popup.html
+index 17c5ca8..57a24e5 100644
+--- a/editor-app/configuration/properties/form-properties-popup.html
++++ b/editor-app/configuration/properties/form-properties-popup.html
+@@ -42,6 +42,7 @@
+                                     <option>boolean</option>
+                                     <option>date</option>
+                                     <option>enum</option>
++                                    <option>dropdown</option>
+                                 </select>
+             				</div>
+                            	<div class="form-group" ng-show="selectedProperty.datePattern">
+@@ -80,6 +81,38 @@
+                                     </div>
+                                 </div>    
+             				</div>
++                            <div ng-show="selectedProperty.type == 'dropdown'" style="padding-bottom:10px">
++                                <div class="row row-no-gutter">
++                                    <div class="col-xs-6">
++                                        <div ng-if="translationsRetrieved" class="kis-listener-grid" ui-grid="dropdownGridOptions" ui-grid-selection ui-grid-auto-resize></div>
++                                        <!--<div class="pull-right">
++                                            <div class="btn-group">
++                                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.MOVE.UP | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="moveEnumValueUp()"><i class="glyphicon glyphicon-arrow-up"></i></a>
++                                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.MOVE.DOWN | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="moveEnumValueDown()"><i class="glyphicon glyphicon-arrow-down"></i></a>
++                                            </div>
++                                            <div class="btn-group">
++                                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.ADD | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="addNewEnumValue()"><i class="glyphicon glyphicon-plus"></i></a>
++                                                <a class="btn btn-icon btn-lg" rel="tooltip" data-title="{{ACTION.REMOVE | translate}}" data-placement="bottom" data-original-title="" title="" ng-click="removeEnumValue()"><i class="glyphicon glyphicon-minus"></i></a>
++                                            </div>
++                                        </div>-->
++                                    </div>
++                            
++                                    <div class="col-xs-6">
++                                        <div ng-show="selectedDropdownValue">
++                            
++                                            <div class="form-group">
++                                                <label for="classField">{{'PROPERTY.FORMPROPERTIES.VALUES.ID' | translate}}</label>
++                                                <input type="text" id="classField" class="form-control" ng-model="selectedDropdownValue.id" placeholder="{{'PROPERTY.FORMPROPERTIES.VALUES.ID.PLACEHOLDER' | translate}}" />
++                                            </div>
++                                            <div class="form-group">
++                                                <label for="classField">{{'PROPERTY.FORMPROPERTIES.VALUES.NAME' | translate}}</label>
++                                                <input type="text" id="classField" class="form-control" ng-model="selectedDropdownValue.name" placeholder="{{'PROPERTY.FORMPROPERTIES.VALUES.NAME.PLACEHOLDER' | translate}}" />
++                                            </div>
++                                        </div>
++                                        <div ng-show="!selectedDropdownValue" class="muted no-property-selected" translate>PROPERTY.FORMPROPERTIES.DROPDOWNVALUES.EMPTY</div>
++                                    </div>
++                                </div>    
++            				</div>
+                             <div class="form-group">
+             			   		<label for="expressionField">{{'PROPERTY.FORMPROPERTIES.EXPRESSION' | translate}}</label>
+             			   		<input id="expressionField" class="form-control" type="text" ng-model="selectedProperty.expression" placeholder="{{'PROPERTY.FORMPROPERTIES.EXPRESSION.PLACEHOLDER' | translate }}" />
+diff --git a/i18n/en.json b/i18n/en.json
+index 7d0e40a..1067dad 100644
+--- a/i18n/en.json
++++ b/i18n/en.json
+@@ -824,6 +824,7 @@
+     "PROPERTY.FORMPROPERTIES.DATEPATTERN.PLACEHOLDER" : "Enter date pattern",
+     "PROPERTY.FORMPROPERTIES.VALUES" : "Values",
+     "PROPERTY.FORMPROPERTIES.ENUMVALUES.EMPTY" : "No enum value selected",
++    "PROPERTY.FORMPROPERTIES.DROPDOWNVALUES.EMPTY" : "No dropdown value selected",
+     "PROPERTY.FORMPROPERTIES.VALUES.ID" : "Id",
+     "PROPERTY.FORMPROPERTIES.VALUES.NAME" : "Name",
+     "PROPERTY.FORMPROPERTIES.VALUES.ID.PLACEHOLDER" : "Enter id of a value",

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals.properties
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals.properties b/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals.properties
index 1ea8063..84b9c85 100644
--- a/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals.properties
+++ b/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals.properties
@@ -14,7 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-taskId=Task
+bpmnProcess=User Request
 key=Key
 description=Description
 createTime=Create Time

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_it.properties
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_it.properties b/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_it.properties
index 7e12ce5..f25abd8 100644
--- a/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_it.properties
+++ b/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_it.properties
@@ -14,7 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-taskId=Task
+bpmnProcess=User Request
 key=Chiave
 description=Descrizione
 createTime=Data di creazione

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ja.properties
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ja.properties b/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ja.properties
index b2ff626..dcca6e1 100644
--- a/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ja.properties
+++ b/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ja.properties
@@ -14,7 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-taskId=\u30bf\u30b9\u30af
+bpmnProcess=User Request
 key=\u30ad\u30fc
 description=\u8aac\u660e
 createTime=\u4f5c\u6210\u6642\u523b

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_pt_BR.properties
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_pt_BR.properties b/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_pt_BR.properties
index e5ff74c..88a9e05 100644
--- a/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_pt_BR.properties
+++ b/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_pt_BR.properties
@@ -14,7 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-taskId=Tarefa
+bpmnProcess=User Request
 key=Chave
 description=Descri\u00e7\u00e3o
 createTime=Tempo de Cria\u00e7\u00e3o

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ru.properties
----------------------------------------------------------------------
diff --git a/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ru.properties b/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ru.properties
index 6f4fb58..2e7a199 100644
--- a/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ru.properties
+++ b/ext/flowable/client-console/src/main/resources/org/apache/syncope/client/console/pages/Approvals_ru.properties
@@ -16,7 +16,7 @@
 # under the License.
 #
 # taskId=\u00d0\u0097\u00d0\u00b0\u00d1\u008f\u00d0\u00b2\u00d0\u00ba\u00d0\u00b0
-taskId=\u0417\u0430\u044f\u0432\u043a\u0430
+bpmnProcess=User Request
 # key=\u00d0\u0098\u00d0\u00b4\u00d0\u00b5\u00d0\u00bd\u00d1\u0082\u00d0\u00b8\u00d1\u0084\u00d0\u00b8\u00d0\u00ba\u00d0\u00b0\u00d1\u0082\u00d0\u00be\u00d1\u0080
 key=\u0418\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440
 # description=\u00d0\u009e\u00d0\u00bf\u00d0\u00b8\u00d1\u0081\u00d0\u00b0\u00d0\u00bd\u00d0\u00b8\u00d0\u00b5

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/BpmnProcess.java
----------------------------------------------------------------------
diff --git a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/BpmnProcess.java b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/BpmnProcess.java
new file mode 100644
index 0000000..35a7883
--- /dev/null
+++ b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/BpmnProcess.java
@@ -0,0 +1,72 @@
+/*
+ * 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.common.lib.to;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+
+@XmlRootElement(name = "bpmnProcess")
+@XmlType
+public class BpmnProcess extends AbstractBaseBean implements EntityTO {
+
+    private static final long serialVersionUID = -7044543391316529128L;
+
+    private String key;
+
+    private String modelId;
+
+    private String name;
+
+    private boolean userWorkflow;
+
+    @Override
+    public String getKey() {
+        return key;
+    }
+
+    @Override
+    public void setKey(final String key) {
+        this.key = key;
+    }
+
+    public String getModelId() {
+        return modelId;
+    }
+
+    public void setModelId(final String modelId) {
+        this.modelId = modelId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    public boolean isUserWorkflow() {
+        return userWorkflow;
+    }
+
+    public void setUserWorkflow(final boolean userWorkflow) {
+        this.userWorkflow = userWorkflow;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/6f6d9156/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/BpmnProcessTO.java
----------------------------------------------------------------------
diff --git a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/BpmnProcessTO.java b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/BpmnProcessTO.java
deleted file mode 100644
index bb67e3e..0000000
--- a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/BpmnProcessTO.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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.common.lib.to;
-
-import javax.xml.bind.annotation.XmlRootElement;
-import javax.xml.bind.annotation.XmlType;
-import org.apache.syncope.common.lib.AbstractBaseBean;
-
-@XmlRootElement(name = "bpmnProcess")
-@XmlType
-public class BpmnProcessTO extends AbstractBaseBean implements EntityTO {
-
-    private static final long serialVersionUID = -7044543391316529128L;
-
-    private String key;
-
-    private String modelId;
-
-    private String name;
-
-    private boolean userWorkflow;
-
-    @Override
-    public String getKey() {
-        return key;
-    }
-
-    @Override
-    public void setKey(final String key) {
-        this.key = key;
-    }
-
-    public String getModelId() {
-        return modelId;
-    }
-
-    public void setModelId(final String modelId) {
-        this.modelId = modelId;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public void setName(final String name) {
-        this.name = name;
-    }
-
-    public boolean isUserWorkflow() {
-        return userWorkflow;
-    }
-
-    public void setUserWorkflow(final boolean userWorkflow) {
-        this.userWorkflow = userWorkflow;
-    }
-}


[4/6] syncope git commit: [SYNCOPE-1369] User requests forms now support dropdowns - via Flowable customization

Posted by il...@apache.org.
http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/UserRequestLogic.java
----------------------------------------------------------------------
diff --git a/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/UserRequestLogic.java b/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/UserRequestLogic.java
index 1acc1da..686677e 100644
--- a/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/UserRequestLogic.java
+++ b/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/UserRequestLogic.java
@@ -25,13 +25,12 @@ import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.patch.UserPatch;
 import org.apache.syncope.common.lib.to.PropagationTaskTO;
-import org.apache.syncope.common.lib.to.UserRequestTO;
+import org.apache.syncope.common.lib.to.UserRequest;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.to.UserRequestForm;
 import org.apache.syncope.common.lib.types.BpmnProcessFormat;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.FlowableEntitlement;
-import org.apache.syncope.common.lib.types.StandardEntitlement;
 import org.apache.syncope.core.flowable.api.BpmnProcessManager;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
@@ -41,6 +40,7 @@ import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecu
 import org.apache.syncope.core.provisioning.api.WorkflowResult;
 import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
 import org.apache.syncope.core.flowable.api.UserRequestHandler;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.flowable.engine.runtime.ProcessInstance;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -52,24 +52,49 @@ import org.springframework.transaction.annotation.Transactional;
 public class UserRequestLogic extends AbstractTransactionalLogic<UserRequestForm> {
 
     @Autowired
-    private BpmnProcessManager bpmnProcessManager;
+    protected BpmnProcessManager bpmnProcessManager;
 
     @Autowired
-    private UserRequestHandler userRequestHandler;
+    protected UserRequestHandler userRequestHandler;
 
     @Autowired
-    private PropagationManager propagationManager;
+    protected PropagationManager propagationManager;
 
     @Autowired
-    private PropagationTaskExecutor taskExecutor;
+    protected PropagationTaskExecutor taskExecutor;
 
     @Autowired
-    private UserDataBinder binder;
+    protected UserDataBinder binder;
 
     @Autowired
-    private UserDAO userDAO;
+    protected UserDAO userDAO;
 
-    protected UserRequestTO doStart(final String bpmnProcess, final User user) {
+    @PreAuthorize("isAuthenticated()")
+    @Transactional(readOnly = true)
+    public Pair<Integer, List<UserRequest>> list(final String userKey,
+            final int page,
+            final int size,
+            final List<OrderByClause> orderByClauses) {
+
+        if (userKey == null) {
+            securityChecks(null,
+                    FlowableEntitlement.USER_REQUEST_LIST,
+                    "Listing user requests not allowed");
+        } else {
+            User user = userDAO.find(userKey);
+            if (user == null) {
+                throw new NotFoundException("User " + userKey);
+            }
+
+            securityChecks(user.getUsername(),
+                    FlowableEntitlement.USER_REQUEST_LIST,
+                    "Listing requests for user" + user.getUsername() + " not allowed");
+        }
+
+        return userRequestHandler.getUserRequests(userKey, page, size, orderByClauses);
+    }
+
+    protected UserRequest doStart(final String bpmnProcess, final User user) {
         // check if BPMN process exists
         bpmnProcessManager.exportProcess(bpmnProcess, BpmnProcessFormat.XML, new NullOutputStream());
 
@@ -77,56 +102,84 @@ public class UserRequestLogic extends AbstractTransactionalLogic<UserRequestForm
     }
 
     @PreAuthorize("isAuthenticated()")
-    public UserRequestTO start(final String bpmnProcess) {
+    public UserRequest start(final String bpmnProcess) {
         return doStart(bpmnProcess, userDAO.findByUsername(AuthContextUtils.getUsername()));
     }
 
     @PreAuthorize("hasRole('" + FlowableEntitlement.USER_REQUEST_START + "')")
-    public UserRequestTO start(final String bpmnProcess, final String userKey) {
+    public UserRequest start(final String bpmnProcess, final String userKey) {
         return doStart(bpmnProcess, userDAO.authFind(userKey));
     }
 
-    @PreAuthorize("isAuthenticated()")
-    public void cancel(final String executionId, final String reason) {
-        Pair<ProcessInstance, String> parsed = userRequestHandler.parse(executionId);
-
-        if (!AuthContextUtils.getUsername().equals(userDAO.find(parsed.getRight()).getUsername())
+    protected void securityChecks(final String username, final String entitlement, final String errorMessage) {
+        if (!AuthContextUtils.getUsername().equals(username)
                 && !AuthContextUtils.getAuthorities().stream().
-                        anyMatch(auth -> FlowableEntitlement.USER_REQUEST_CANCEL.equals(auth.getAuthority()))) {
+                        anyMatch(auth -> entitlement.equals(auth.getAuthority()))) {
 
             SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.DelegatedAdministration);
-            sce.getElements().add("Canceling " + executionId + " not allowed");
+            sce.getElements().add(errorMessage);
             throw sce;
         }
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    public void cancel(final String executionId, final String reason) {
+        Pair<ProcessInstance, String> parsed = userRequestHandler.parse(executionId);
+
+        securityChecks(userDAO.find(parsed.getRight()).getUsername(),
+                FlowableEntitlement.USER_REQUEST_CANCEL,
+                "Canceling " + executionId + " not allowed");
 
         userRequestHandler.cancel(parsed.getLeft(), reason);
     }
 
-    @PreAuthorize("hasRole('" + FlowableEntitlement.WORKFLOW_FORM_CLAIM + "')")
+    @PreAuthorize("isAuthenticated()")
     public UserRequestForm claimForm(final String taskId) {
-        return userRequestHandler.claimForm(taskId);
-    }
-
-    @PreAuthorize("hasRole('" + FlowableEntitlement.WORKFLOW_FORM_READ + "') "
-            + "and hasRole('" + StandardEntitlement.USER_READ + "')")
-    @Transactional(readOnly = true)
-    public List<UserRequestForm> getForms(final String key) {
-        User user = userDAO.authFind(key);
-        return userRequestHandler.getForms(user.getKey());
+        UserRequestForm form = userRequestHandler.claimForm(taskId);
+        securityChecks(form.getUsername(),
+                FlowableEntitlement.USER_REQUEST_FORM_CLAIM,
+                "Claiming form " + taskId + " not allowed");
+        return form;
     }
 
-    @PreAuthorize("hasRole('" + FlowableEntitlement.WORKFLOW_FORM_LIST + "')")
+    @PreAuthorize("isAuthenticated()")
     @Transactional(readOnly = true)
     public Pair<Integer, List<UserRequestForm>> getForms(
+            final String userKey,
             final int page,
             final int size,
             final List<OrderByClause> orderByClauses) {
 
-        return userRequestHandler.getForms(page, size, orderByClauses);
+        if (userKey == null) {
+            securityChecks(null,
+                    FlowableEntitlement.USER_REQUEST_FORM_LIST,
+                    "Listing forms not allowed");
+        } else {
+            User user = userDAO.find(userKey);
+            if (user == null) {
+                throw new NotFoundException("User " + userKey);
+            }
+
+            securityChecks(user.getUsername(),
+                    FlowableEntitlement.USER_REQUEST_FORM_LIST,
+                    "Listing forms for user" + user.getUsername() + " not allowed");
+        }
+
+        return userRequestHandler.getForms(userKey, page, size, orderByClauses);
     }
 
-    @PreAuthorize("hasRole('" + FlowableEntitlement.WORKFLOW_FORM_SUBMIT + "')")
+    @PreAuthorize("isAuthenticated()")
     public UserTO submitForm(final UserRequestForm form) {
+        if (form.getUsername() == null) {
+            securityChecks(null,
+                    FlowableEntitlement.USER_REQUEST_FORM_SUBMIT,
+                    "Submitting forms not allowed");
+        } else {
+            securityChecks(form.getUsername(),
+                    FlowableEntitlement.USER_REQUEST_FORM_SUBMIT,
+                    "Submitting forms for user" + form.getUsername() + " not allowed");
+        }
+
         WorkflowResult<UserPatch> wfResult = userRequestHandler.submitForm(form);
 
         // propByRes can be made empty by the workflow definition if no propagation should occur 

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/UserRequestFormQuery.java
----------------------------------------------------------------------
diff --git a/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/UserRequestFormQuery.java b/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/UserRequestFormQuery.java
index 4097afd..c4ac3a0 100644
--- a/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/UserRequestFormQuery.java
+++ b/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/UserRequestFormQuery.java
@@ -18,6 +18,8 @@
  */
 package org.apache.syncope.common.rest.api.beans;
 
+import javax.ws.rs.QueryParam;
+
 public class UserRequestFormQuery extends AbstractQuery {
 
     private static final long serialVersionUID = -4762457303770028554L;
@@ -28,5 +30,21 @@ public class UserRequestFormQuery extends AbstractQuery {
         protected UserRequestFormQuery newInstance() {
             return new UserRequestFormQuery();
         }
+
+        public Builder user(final String user) {
+            getInstance().setUser(user);
+            return this;
+        }
+    }
+
+    private String user;
+
+    public String getUser() {
+        return user;
+    }
+
+    @QueryParam("user")
+    public void setUser(final String user) {
+        this.user = user;
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/UserRequestQuery.java
----------------------------------------------------------------------
diff --git a/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/UserRequestQuery.java b/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/UserRequestQuery.java
new file mode 100644
index 0000000..3f53629
--- /dev/null
+++ b/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/UserRequestQuery.java
@@ -0,0 +1,50 @@
+/*
+ * 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.common.rest.api.beans;
+
+import javax.ws.rs.QueryParam;
+
+public class UserRequestQuery extends AbstractQuery {
+
+    private static final long serialVersionUID = 427312034580076640L;
+
+    public static class Builder extends AbstractQuery.Builder<UserRequestQuery, Builder> {
+
+        @Override
+        protected UserRequestQuery newInstance() {
+            return new UserRequestQuery();
+        }
+
+        public Builder user(final String user) {
+            getInstance().setUser(user);
+            return this;
+        }
+    }
+
+    private String user;
+
+    public String getUser() {
+        return user;
+    }
+
+    @QueryParam("user")
+    public void setUser(final String user) {
+        this.user = user;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/BpmnProcessService.java
----------------------------------------------------------------------
diff --git a/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/BpmnProcessService.java b/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/BpmnProcessService.java
index 06cb056..bb34fdd 100644
--- a/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/BpmnProcessService.java
+++ b/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/BpmnProcessService.java
@@ -19,6 +19,9 @@
 package org.apache.syncope.common.rest.api.service;
 
 import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.ArraySchema;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.responses.ApiResponse;
 import io.swagger.v3.oas.annotations.responses.ApiResponses;
 import io.swagger.v3.oas.annotations.security.SecurityRequirement;
@@ -35,7 +38,7 @@ import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
-import org.apache.syncope.common.lib.to.BpmnProcessTO;
+import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 
 /**
@@ -51,11 +54,16 @@ public interface BpmnProcessService extends JAXRSService {
     /**
      * Lists the available BPMN processes.
      *
-     * @return available BPMN processs, for the given any object type
+     * @return available BPMN processs
      */
+    @ApiResponses(
+            @ApiResponse(responseCode = "200", description = "available BPMN processes", content =
+                    @Content(array =
+                            @ArraySchema(schema =
+                                    @Schema(implementation = BpmnProcess.class)))))
     @GET
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
-    List<BpmnProcessTO> list();
+    List<BpmnProcess> list();
 
     /**
      * Exports the BPMN process for matching key.
@@ -63,6 +71,8 @@ public interface BpmnProcessService extends JAXRSService {
      * @param key BPMN process key
      * @return BPMN process for matching key
      */
+    @ApiResponses(
+            @ApiResponse(responseCode = "200", description = "BPMN process for matching key"))
     @GET
     @Path("{key}")
     @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@@ -72,8 +82,10 @@ public interface BpmnProcessService extends JAXRSService {
      * Exports the BPMN diagram representation (if available), for matching key.
      *
      * @param key BPMN process key
-     * @return workflow diagram representation
+     * @return BPMN diagram representation
      */
+    @ApiResponses(
+            @ApiResponse(responseCode = "200", description = "BPMN diagram representation"))
     @GET
     @Path("{key}/diagram.png")
     @Produces({ RESTHeaders.MEDIATYPE_IMAGE_PNG })

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserRequestService.java
----------------------------------------------------------------------
diff --git a/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserRequestService.java b/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserRequestService.java
index 701772d..5ad7b12 100644
--- a/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserRequestService.java
+++ b/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserRequestService.java
@@ -18,10 +18,13 @@
  */
 package org.apache.syncope.common.rest.api.service;
 
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
 import io.swagger.v3.oas.annotations.security.SecurityRequirement;
 import io.swagger.v3.oas.annotations.security.SecurityRequirements;
 import io.swagger.v3.oas.annotations.tags.Tag;
-import java.util.List;
 import javax.validation.constraints.NotNull;
 import javax.ws.rs.BeanParam;
 import javax.ws.rs.Consumes;
@@ -34,11 +37,12 @@ import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.MediaType;
 import org.apache.syncope.common.lib.to.PagedResult;
-import org.apache.syncope.common.lib.to.UserRequestTO;
+import org.apache.syncope.common.lib.to.UserRequest;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.to.UserRequestForm;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.beans.UserRequestFormQuery;
+import org.apache.syncope.common.rest.api.beans.UserRequestQuery;
 
 /**
  * REST operations related to user workflow.
@@ -51,19 +55,37 @@ import org.apache.syncope.common.rest.api.beans.UserRequestFormQuery;
 public interface UserRequestService extends JAXRSService {
 
     /**
-     * Starts a new user request, for the given BOMN Process and
-     * user (if provided) or requesting user (if not provided).
+     * Returns a list of running user requests matching the given query.
+     *
+     * @param query query conditions
+     * @return list of all running user requests
+     */
+    @ApiResponses(
+            @ApiResponse(responseCode = "200", description = "list of all running user requests", content =
+                    @Content(schema =
+                            @Schema(implementation = PagedResult.class))))
+    @GET
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    PagedResult<UserRequest> list(@BeanParam UserRequestQuery query);
+
+    /**
+     * Starts a new request for the given BPMN Process and user (if provided) or requesting user (if not provided).
      *
      * @param bpmnProcess BPMN process
-     * @param userKey user key
+     * @param user if value looks like a UUID then it is interpreted as key otherwise as a username
      * @return data about the started request service, including execution id
      */
+    @ApiResponses(
+            @ApiResponse(responseCode = "200",
+                    description = "data about the started request service, including execution id", content =
+                    @Content(schema =
+                            @Schema(implementation = UserRequest.class))))
     @POST
     @Path("start/{bpmnProcess}")
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
-    UserRequestTO start(
+    UserRequest start(
             @NotNull @PathParam("bpmnProcess") String bpmnProcess,
-            @QueryParam("userKey") String userKey);
+            @QueryParam("user") String user);
 
     /**
      * Cancel a running user request.
@@ -71,6 +93,8 @@ public interface UserRequestService extends JAXRSService {
      * @param executionId execution id
      * @param reason reason to cancel the user request
      */
+    @ApiResponses(
+            @ApiResponse(responseCode = "204", description = "Operation was successful"))
     @DELETE
     @Path("{executionId}")
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
@@ -79,44 +103,45 @@ public interface UserRequestService extends JAXRSService {
             @QueryParam("reason") String reason);
 
     /**
-     * Returns a list of all available workflow forms.
+     * Returns a list of user request forms matching the given query.
      *
      * @param query query conditions
-     * @return list of all available workflow forms
+     * @return list of all available user request forms
      */
+    @ApiResponses(
+            @ApiResponse(responseCode = "200", description = "list of all available user request forms", content =
+                    @Content(schema =
+                            @Schema(implementation = PagedResult.class))))
     @GET
     @Path("forms")
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
     PagedResult<UserRequestForm> getForms(@BeanParam UserRequestFormQuery query);
 
     /**
-     * Returns a list of available forms for the given user key.
-     *
-     * @param userKey user key
-     * @return list of available forms for the given user key
-     */
-    @GET
-    @Path("forms/{userKey}")
-    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
-    List<UserRequestForm> getForms(@NotNull @PathParam("userKey") String userKey);
-
-    /**
-     * Claims the form for the given task id.
+     * Requests to manage the form for the given task id.
      *
      * @param taskId workflow task id
-     * @return the workflow form for the given task id
+     * @return the form for the given task id
      */
+    @ApiResponses(
+            @ApiResponse(responseCode = "200", description = "the form for the given task id", content =
+                    @Content(schema =
+                            @Schema(implementation = UserRequestForm.class))))
     @POST
     @Path("forms/{taskId}/claim")
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
     UserRequestForm claimForm(@NotNull @PathParam("taskId") String taskId);
 
     /**
-     * Submits a workflow form.
+     * Submits a user request form.
      *
-     * @param form workflow form.
+     * @param form user request form.
      * @return updated user
      */
+    @ApiResponses(
+            @ApiResponse(responseCode = "200", description = "updated user", content =
+                    @Content(schema =
+                            @Schema(implementation = UserTO.class))))
     @POST
     @Path("forms")
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserWorkflowTaskService.java
----------------------------------------------------------------------
diff --git a/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserWorkflowTaskService.java b/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserWorkflowTaskService.java
index 54f38d0..b396d6e 100644
--- a/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserWorkflowTaskService.java
+++ b/ext/flowable/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/UserWorkflowTaskService.java
@@ -18,6 +18,11 @@
  */
 package org.apache.syncope.common.rest.api.service;
 
+import io.swagger.v3.oas.annotations.media.ArraySchema;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
 import io.swagger.v3.oas.annotations.security.SecurityRequirement;
 import io.swagger.v3.oas.annotations.security.SecurityRequirements;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -48,6 +53,11 @@ public interface UserWorkflowTaskService extends JAXRSService {
      * @param userKey user key
      * @return list of available tasks for the given user key
      */
+    @ApiResponses(
+            @ApiResponse(responseCode = "200", description = "list of available tasks for the given user key", content =
+                    @Content(array =
+                            @ArraySchema(schema =
+                                    @Schema(implementation = WorkflowTask.class)))))
     @GET
     @Path("tasks/{userKey}")
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
@@ -59,6 +69,10 @@ public interface UserWorkflowTaskService extends JAXRSService {
      * @param workflowTaskExecInput input for task execution
      * @return updated user
      */
+    @ApiResponses(
+            @ApiResponse(responseCode = "200", description = "updated user", content =
+                    @Content(schema =
+                            @Schema(implementation = UserTO.class))))
     @POST
     @Path("tasks/{userKey}")
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/BpmnProcessServiceImpl.java
----------------------------------------------------------------------
diff --git a/ext/flowable/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/BpmnProcessServiceImpl.java b/ext/flowable/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/BpmnProcessServiceImpl.java
index dd1aea8..4f976b5 100644
--- a/ext/flowable/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/BpmnProcessServiceImpl.java
+++ b/ext/flowable/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/BpmnProcessServiceImpl.java
@@ -22,7 +22,7 @@ import java.util.List;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.StreamingOutput;
-import org.apache.syncope.common.lib.to.BpmnProcessTO;
+import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.syncope.common.lib.types.BpmnProcessFormat;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.core.logic.BpmnProcessLogic;
@@ -37,7 +37,7 @@ public class BpmnProcessServiceImpl extends AbstractServiceImpl implements BpmnP
     private BpmnProcessLogic logic;
 
     @Override
-    public List<BpmnProcessTO> list() {
+    public List<BpmnProcess> list() {
         return logic.list();
     }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserRequestServiceImpl.java
----------------------------------------------------------------------
diff --git a/ext/flowable/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserRequestServiceImpl.java b/ext/flowable/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserRequestServiceImpl.java
index 2f174a0..188d22e 100644
--- a/ext/flowable/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserRequestServiceImpl.java
+++ b/ext/flowable/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserRequestServiceImpl.java
@@ -21,14 +21,16 @@ package org.apache.syncope.core.rest.cxf.service;
 import java.util.List;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.to.PagedResult;
-import org.apache.syncope.common.lib.to.UserRequestTO;
+import org.apache.syncope.common.lib.to.UserRequest;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.to.UserRequestForm;
 import org.apache.syncope.common.rest.api.beans.UserRequestFormQuery;
+import org.apache.syncope.common.rest.api.beans.UserRequestQuery;
 import org.apache.syncope.core.logic.UserRequestLogic;
+import org.apache.syncope.common.rest.api.service.UserRequestService;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
-import org.apache.syncope.common.rest.api.service.UserRequestService;
 
 @Service
 public class UserRequestServiceImpl extends AbstractServiceImpl implements UserRequestService {
@@ -36,11 +38,25 @@ public class UserRequestServiceImpl extends AbstractServiceImpl implements UserR
     @Autowired
     private UserRequestLogic logic;
 
+    @Autowired
+    private UserDAO userDAO;
+
+    @Override
+    public PagedResult<UserRequest> list(final UserRequestQuery query) {
+        if (query.getUser() != null) {
+            query.setUser(getActualKey(userDAO, query.getUser()));
+        }
+
+        Pair<Integer, List<UserRequest>> result = logic.list(
+                query.getUser(), query.getPage(), query.getSize(), getOrderByClauses(query.getOrderBy()));
+        return buildPagedResult(result.getRight(), query.getPage(), query.getSize(), result.getLeft());
+    }
+
     @Override
-    public UserRequestTO start(final String bpmnProcess, final String userKey) {
-        return userKey == null
+    public UserRequest start(final String bpmnProcess, final String user) {
+        return user == null
                 ? logic.start(bpmnProcess)
-                : logic.start(bpmnProcess, userKey);
+                : logic.start(bpmnProcess, getActualKey(userDAO, user));
     }
 
     @Override
@@ -54,14 +70,13 @@ public class UserRequestServiceImpl extends AbstractServiceImpl implements UserR
     }
 
     @Override
-    public List<UserRequestForm> getForms(final String userKey) {
-        return logic.getForms(userKey);
-    }
-
-    @Override
     public PagedResult<UserRequestForm> getForms(final UserRequestFormQuery query) {
+        if (query.getUser() != null) {
+            query.setUser(getActualKey(userDAO, query.getUser()));
+        }
+
         Pair<Integer, List<UserRequestForm>> result = logic.getForms(
-                query.getPage(), query.getSize(), getOrderByClauses(query.getOrderBy()));
+                query.getUser(), query.getPage(), query.getSize(), getOrderByClauses(query.getOrderBy()));
         return buildPagedResult(result.getRight(), query.getPage(), query.getSize(), result.getLeft());
     }
 
@@ -69,5 +84,4 @@ public class UserRequestServiceImpl extends AbstractServiceImpl implements UserR
     public UserTO submitForm(final UserRequestForm form) {
         return logic.submitForm(form);
     }
-
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/fit/core-reference/pom.xml
----------------------------------------------------------------------
diff --git a/fit/core-reference/pom.xml b/fit/core-reference/pom.xml
index c89241e..dc5e8d1 100644
--- a/fit/core-reference/pom.xml
+++ b/fit/core-reference/pom.xml
@@ -1541,6 +1541,26 @@ under the License.
       
       <build>
         <plugins>
+          <!-- Adds Flowable custom classes -->
+          <plugin>
+            <groupId>org.codehaus.mojo</groupId>
+            <artifactId>build-helper-maven-plugin</artifactId>
+            <executions>
+              <execution>
+                <id>add-flowable-custom-classes</id>
+                <phase>initialize</phase>
+                <goals>
+                  <goal>add-source</goal>
+                </goals>
+                <configuration>
+                  <sources>
+                    <source>${basedir}/src/main/java-all</source>
+                  </sources>
+                </configuration>
+              </execution>
+            </executions>
+          </plugin>
+
           <!-- Adds Flowable test content -->
           <plugin>
             <groupId>org.codehaus.mojo</groupId>

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/AssignDirectorGroup.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/AssignDirectorGroup.java b/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/AssignDirectorGroup.java
new file mode 100644
index 0000000..4db7ac2
--- /dev/null
+++ b/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/AssignDirectorGroup.java
@@ -0,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.fit.core.reference.flowable;
+
+import org.apache.syncope.common.lib.patch.MembershipPatch;
+import org.apache.syncope.common.lib.patch.UserPatch;
+import org.apache.syncope.core.flowable.impl.FlowableRuntimeUtils;
+import org.apache.syncope.core.flowable.task.AbstractFlowableServiceTask;
+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.PropagationByResource;
+import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AssignDirectorGroup 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);
+
+        Boolean secondLevelApprove = engine.getRuntimeService().
+                getVariable(executionId, "secondLevelApprove", Boolean.class);
+        if (Boolean.TRUE.equals(secondLevelApprove)) {
+            user = userDAO.save(user);
+
+            UserPatch userPatch = new UserPatch();
+            userPatch.setKey(user.getKey());
+            userPatch.getMemberships().add(new MembershipPatch.Builder().
+                    group("ebf97068-aa4b-4a85-9f01-680e8c4cf227").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.PROP_BY_RESOURCE, propByRes);
+        } else {
+            LOG.info("Second level was not approved, not assigning the director group to " + user.getUsername());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/CreateARelationship.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/CreateARelationship.java b/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/CreateARelationship.java
new file mode 100644
index 0000000..b0c5261
--- /dev/null
+++ b/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/CreateARelationship.java
@@ -0,0 +1,71 @@
+/*
+ * 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.fit.core.reference.flowable;
+
+import org.apache.syncope.common.lib.patch.RelationshipPatch;
+import org.apache.syncope.common.lib.patch.UserPatch;
+import org.apache.syncope.common.lib.to.RelationshipTO;
+import org.apache.syncope.core.flowable.impl.FlowableRuntimeUtils;
+import org.apache.syncope.core.flowable.task.AbstractFlowableServiceTask;
+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.PropagationByResource;
+import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class CreateARelationship 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);
+
+        Boolean approve = engine.getRuntimeService().
+                getVariable(executionId, "approve", Boolean.class);
+        if (Boolean.TRUE.equals(approve)) {
+            user = userDAO.save(user);
+
+            String printer = engine.getRuntimeService().
+                    getVariable(executionId, "printer", String.class);
+
+            UserPatch userPatch = new UserPatch();
+            userPatch.setKey(user.getKey());
+            userPatch.getRelationships().add(new RelationshipPatch.Builder().
+                    relationshipTO(new RelationshipTO.Builder().
+                            otherEnd("PRINTER", printer).type("neighborhood").build()).
+                    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.PROP_BY_RESOURCE, propByRes);
+        } else {
+            LOG.info("Printer assignment to " + user.getUsername() + " was not approved");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/PrintersValueProvider.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/PrintersValueProvider.java b/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/PrintersValueProvider.java
new file mode 100644
index 0000000..82e9693
--- /dev/null
+++ b/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/PrintersValueProvider.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.fit.core.reference.flowable;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.core.flowable.api.DropdownValueProvider;
+import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
+import org.apache.syncope.core.persistence.api.dao.search.AnyTypeCond;
+import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class PrintersValueProvider implements DropdownValueProvider {
+
+    private static final SearchCond PRINTER_COND;
+
+    private static final List<OrderByClause> ORDER_BY;
+
+    static {
+        AnyTypeCond anyTypeCond = new AnyTypeCond();
+        anyTypeCond.setAnyTypeKey("PRINTER");
+        PRINTER_COND = SearchCond.getLeafCond(anyTypeCond);
+
+        OrderByClause orderByNameAsc = new OrderByClause();
+        orderByNameAsc.setField("name");
+        orderByNameAsc.setDirection(OrderByClause.Direction.ASC);
+        ORDER_BY = Collections.singletonList(orderByNameAsc);
+    }
+
+    @Autowired
+    private AnySearchDAO anySearchDAO;
+
+    @Transactional(readOnly = true)
+    @Override
+    public Map<String, String> getValues() {
+        return anySearchDAO.<AnyObject>search(PRINTER_COND, ORDER_BY, AnyTypeKind.ANY_OBJECT).stream().
+                collect(Collectors.toMap(
+                        AnyObject::getKey,
+                        AnyObject::getName,
+                        (u, v) -> {
+                            throw new IllegalStateException(String.format("Duplicate key %s", u));
+                        },
+                        LinkedHashMap::new));
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/AssignDirectorGroup.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/AssignDirectorGroup.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/AssignDirectorGroup.java
deleted file mode 100644
index abdfcb6..0000000
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/AssignDirectorGroup.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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.fit.core.reference;
-
-import org.apache.syncope.common.lib.patch.MembershipPatch;
-import org.apache.syncope.common.lib.patch.UserPatch;
-import org.apache.syncope.core.flowable.impl.FlowableRuntimeUtils;
-import org.apache.syncope.core.flowable.task.AbstractFlowableServiceTask;
-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.PropagationByResource;
-import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
-import org.springframework.beans.factory.annotation.Autowired;
-
-public class AssignDirectorGroup 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);
-
-        Boolean secondLevelApprove = engine.getRuntimeService().
-                getVariable(executionId, "secondLevelApprove", Boolean.class);
-        if (Boolean.TRUE.equals(secondLevelApprove)) {
-            user = userDAO.save(user);
-
-            UserPatch userPatch = new UserPatch();
-            userPatch.setKey(user.getKey());
-            userPatch.getMemberships().add(new MembershipPatch.Builder().
-                    group("ebf97068-aa4b-4a85-9f01-680e8c4cf227").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.PROP_BY_RESOURCE, propByRes);
-        } else {
-            LOG.info("Second level was not approved, not assigning the director group to " + user.getUsername());
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/fit/core-reference/src/main/resources/all/workflowTestContext.xml
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/main/resources/all/workflowTestContext.xml b/fit/core-reference/src/main/resources/all/workflowTestContext.xml
index 2329c53..b14490a 100644
--- a/fit/core-reference/src/main/resources/all/workflowTestContext.xml
+++ b/fit/core-reference/src/main/resources/all/workflowTestContext.xml
@@ -19,9 +19,12 @@ under the License.
 -->
 <beans xmlns="http://www.springframework.org/schema/beans" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
-                           http://www.springframework.org/schema/beans/spring-beans.xsd">
+                           http://www.springframework.org/schema/beans/spring-beans.xsd
+                           http://www.springframework.org/schema/context
+                           http://www.springframework.org/schema/context/spring-context.xsd">
 
-  <bean id="assignDirectorGroup" class="org.apache.syncope.fit.core.reference.AssignDirectorGroup"/>
+  <context:component-scan base-package="org.apache.syncope.fit.core.reference.flowable"/>
 
 </beans>

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/fit/core-reference/src/main/resources/assignPrinterRequest.bpmn20.xml
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/main/resources/assignPrinterRequest.bpmn20.xml b/fit/core-reference/src/main/resources/assignPrinterRequest.bpmn20.xml
new file mode 100644
index 0000000..c9ecb9c
--- /dev/null
+++ b/fit/core-reference/src/main/resources/assignPrinterRequest.bpmn20.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" 
+             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
+             xmlns:flowable="http://flowable.org/bpmn"
+             xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" 
+             xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" 
+             xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" 
+             typeLanguage="http://www.w3.org/2001/XMLSchema" 
+             expressionLanguage="http://www.w3.org/1999/XPath" 
+             targetNamespace="http://www.flowable.org/processdef">
+
+  <process id="assignPrinterRequest" name="Assign printer" isExecutable="true">
+    <startEvent id="startevent1" name="Start"/>
+    <endEvent id="endevent1" name="End"/>
+    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="selectPrinter"/>
+    <userTask id="selectPrinter" name="Select printer" flowable:formKey="selectPrinter" flowable:assignee="${wfExecutor}">
+      <extensionElements>
+        <flowable:formProperty id="printer" name="Printer" variable="printer" type="dropdown" required="true">
+          <flowable:value id="dropdownValueProvider" name="printersValueProvider"/>
+        </flowable:formProperty>
+        <flowable:formProperty id="printMode" name="Preferred print mode?" type="enum">
+          <flowable:value id="bw" name="Black / White"/>
+          <flowable:value id="color" name="Color"/>
+        </flowable:formProperty>
+      </extensionElements>
+    </userTask>
+    <userTask id="approvePrinter" name="Approve printer" flowable:formKey="approvePrinter">
+      <extensionElements>
+        <flowable:formProperty id="username" name="Username" type="string" expression="${userTO.username}" writable="false"/>
+        <flowable:formProperty id="printer" name="Selected printer" type="string" expression="${printer}" writable="false"/>
+        <flowable:formProperty id="approve" name="Approve?" type="boolean" variable="approve" required="true"/>
+      </extensionElements>
+    </userTask>
+    <sequenceFlow id="sid-D7047714-8E57-46B8-B6D4-4844DE330329" sourceRef="selectPrinter" targetRef="approvePrinter"/>
+    <serviceTask id="createARelationship" name="Create ARelationship" flowable:expression="#{createARelationship.execute(execution.processInstanceId)}"/>
+    <sequenceFlow id="sid-33880AE7-35C6-4A39-8E5B-12D8BA53F042" sourceRef="approvePrinter" targetRef="createARelationship"/>
+    <sequenceFlow id="sid-831E1896-EDF9-4F7D-AA42-E86CC1F8C5D3" sourceRef="createARelationship" targetRef="endevent1"/>
+  </process>
+  <bpmndi:BPMNDiagram id="BPMNDiagram_assignPrinterRequest">
+    <bpmndi:BPMNPlane bpmnElement="assignPrinterRequest" id="BPMNPlane_assignPrinterRequest">
+      <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
+        <omgdc:Bounds height="30.0" width="30.0" x="180.0" y="115.0"/>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
+        <omgdc:Bounds height="28.0" width="28.0" x="885.0" y="116.0"/>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="selectPrinter" id="BPMNShape_selectPrinter">
+        <omgdc:Bounds height="80.0" width="100.0" x="330.0" y="90.0"/>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="approvePrinter" id="BPMNShape_approvePrinter">
+        <omgdc:Bounds height="80.0" width="100.0" x="495.5" y="90.0"/>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape bpmnElement="createARelationship" id="BPMNShape_createARelationship">
+        <omgdc:Bounds height="80.0" width="100.0" x="675.0" y="90.0"/>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
+        <omgdi:waypoint x="209.94999901196195" y="130.0"/>
+        <omgdi:waypoint x="330.0" y="130.0"/>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge bpmnElement="sid-D7047714-8E57-46B8-B6D4-4844DE330329" id="BPMNEdge_sid-D7047714-8E57-46B8-B6D4-4844DE330329">
+        <omgdi:waypoint x="429.95000000000005" y="130.0"/>
+        <omgdi:waypoint x="495.5" y="130.0"/>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge bpmnElement="sid-33880AE7-35C6-4A39-8E5B-12D8BA53F042" id="BPMNEdge_sid-33880AE7-35C6-4A39-8E5B-12D8BA53F042">
+        <omgdi:waypoint x="595.4499999999068" y="130.0"/>
+        <omgdi:waypoint x="675.0" y="130.0"/>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge bpmnElement="sid-831E1896-EDF9-4F7D-AA42-E86CC1F8C5D3" id="BPMNEdge_sid-831E1896-EDF9-4F7D-AA42-E86CC1F8C5D3">
+        <omgdi:waypoint x="774.9499999999266" y="130.0"/>
+        <omgdi:waypoint x="885.0" y="130.0"/>
+      </bpmndi:BPMNEdge>
+    </bpmndi:BPMNPlane>
+  </bpmndi:BPMNDiagram>
+</definitions>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthenticationITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthenticationITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthenticationITCase.java
index e2f351b..cbdb19b 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthenticationITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthenticationITCase.java
@@ -66,6 +66,7 @@ import org.apache.syncope.common.lib.types.StandardEntitlement;
 import org.apache.syncope.common.lib.types.StatusPatchType;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.beans.AnyQuery;
+import org.apache.syncope.common.rest.api.beans.UserRequestFormQuery;
 import org.apache.syncope.common.rest.api.service.AnyObjectService;
 import org.apache.syncope.common.rest.api.service.SchemaService;
 import org.apache.syncope.common.rest.api.service.UserService;
@@ -495,7 +496,8 @@ public class AuthenticationITCase extends AbstractITCase {
         }
 
         // 3. approve user
-        UserRequestForm form = userRequestService.getForms(userTO.getKey()).get(0);
+        UserRequestForm form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(userTO.getKey()).build()).getResult().get(0);
         form = userRequestService.claimForm(form.getTaskId());
         form.getProperty("approveCreate").get().setValue(Boolean.TRUE.toString());
         userTO = userRequestService.submitForm(form);

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BpmnProcessITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BpmnProcessITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BpmnProcessITCase.java
index 443cd83..21fb4fd 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BpmnProcessITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BpmnProcessITCase.java
@@ -28,7 +28,7 @@ import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import javax.ws.rs.core.Response;
 import org.apache.commons.io.IOUtils;
-import org.apache.syncope.common.lib.to.BpmnProcessTO;
+import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.syncope.fit.AbstractITCase;
 import org.apache.syncope.fit.FlowableDetector;
 import org.junit.jupiter.api.BeforeAll;
@@ -42,7 +42,7 @@ public class BpmnProcessITCase extends AbstractITCase {
     public static void findDefault() {
         assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
         bpmnProcessService.list().stream().
-                filter(BpmnProcessTO::isUserWorkflow).findAny().
+                filter(BpmnProcess::isUserWorkflow).findAny().
                 ifPresent(process -> {
                     userWorkflowKey = process.getKey();
                 });

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserRequestITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserRequestITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserRequestITCase.java
index 5446894..9281bce 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserRequestITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserRequestITCase.java
@@ -25,14 +25,19 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assumptions.assumeTrue;
 
 import java.io.IOException;
+import java.util.List;
 import javax.ws.rs.core.MediaType;
 import org.apache.cxf.helpers.IOUtils;
 import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.common.lib.to.PagedResult;
+import org.apache.syncope.common.lib.to.RelationshipTO;
 import org.apache.syncope.common.lib.to.UserRequestForm;
-import org.apache.syncope.common.lib.to.UserRequestTO;
+import org.apache.syncope.common.lib.to.UserRequest;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.rest.api.beans.UserRequestFormQuery;
+import org.apache.syncope.common.rest.api.beans.UserRequestQuery;
+import org.apache.syncope.common.rest.api.service.UserRequestService;
 import org.apache.syncope.fit.AbstractITCase;
 import org.apache.syncope.fit.FlowableDetector;
 import org.junit.jupiter.api.BeforeAll;
@@ -42,9 +47,13 @@ public class UserRequestITCase extends AbstractITCase {
 
     @BeforeAll
     public static void loadBpmnProcesses() throws IOException {
+        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
+
         WebClient.client(bpmnProcessService).type(MediaType.APPLICATION_XML_TYPE);
         bpmnProcessService.set("directorGroupRequest",
                 IOUtils.toString(UserRequestITCase.class.getResourceAsStream("/directorGroupRequest.bpmn20.xml")));
+        bpmnProcessService.set("assignPrinterRequest",
+                IOUtils.toString(UserRequestITCase.class.getResourceAsStream("/assignPrinterRequest.bpmn20.xml")));
     }
 
     @Test
@@ -56,17 +65,29 @@ public class UserRequestITCase extends AbstractITCase {
         assertFalse(user.getMembership("ebf97068-aa4b-4a85-9f01-680e8c4cf227").isPresent());
 
         // start request
-        UserRequestTO req = userRequestService.start("directorGroupRequest", user.getKey());
+        UserRequest req = userRequestService.start("directorGroupRequest", user.getKey());
         assertNotNull(req);
+        assertEquals("directorGroupRequest", req.getBpmnProcess());
+        assertNotNull(req.getExecutionId());
+        assertEquals(req.getUser(), user.getKey());
+        
+        // check that user can see the ongoing request
+        SyncopeClient client = clientFactory.create(user.getUsername(), "password123");
+        PagedResult<UserRequest> requests = client.getService(UserRequestService.class).
+                list(new UserRequestQuery.Builder().user(user.getKey()).build());
+        assertEquals(1, requests.getTotalCount());
+        assertEquals("directorGroupRequest", requests.getResult().get(0).getBpmnProcess());
 
         // 1st approval -> reject
-        UserRequestForm form = userRequestService.getForms(user.getKey()).get(0);
+        UserRequestForm form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(user.getKey()).build()).getResult().get(0);
         form = userRequestService.claimForm(form.getTaskId());
         form.getProperty("firstLevelApprove").get().setValue(Boolean.FALSE.toString());
         userRequestService.submitForm(form);
 
         // no more forms, group not assigned
-        assertTrue(userRequestService.getForms(user.getKey()).isEmpty());
+        assertTrue(userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(user.getKey()).build()).getResult().isEmpty());
         assertFalse(userService.read(user.getKey()).getMembership("ebf97068-aa4b-4a85-9f01-680e8c4cf227").isPresent());
 
         // start request again
@@ -74,19 +95,22 @@ public class UserRequestITCase extends AbstractITCase {
         assertNotNull(req);
 
         // 1st approval -> accept
-        form = userRequestService.getForms(user.getKey()).get(0);
+        form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(user.getKey()).build()).getResult().get(0);
         form = userRequestService.claimForm(form.getTaskId());
         form.getProperty("firstLevelApprove").get().setValue(Boolean.TRUE.toString());
         userRequestService.submitForm(form);
 
         // 2nd approval -> reject
-        form = userRequestService.getForms(user.getKey()).get(0);
+        form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(user.getKey()).build()).getResult().get(0);
         form = userRequestService.claimForm(form.getTaskId());
         form.getProperty("secondLevelApprove").get().setValue(Boolean.FALSE.toString());
         user = userRequestService.submitForm(form);
 
         // no more forms, group not assigned
-        assertTrue(userRequestService.getForms(user.getKey()).isEmpty());
+        assertTrue(userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(user.getKey()).build()).getResult().isEmpty());
         assertFalse(userService.read(user.getKey()).getMembership("ebf97068-aa4b-4a85-9f01-680e8c4cf227").isPresent());
 
         // start request again
@@ -94,13 +118,15 @@ public class UserRequestITCase extends AbstractITCase {
         assertNotNull(req);
 
         // 1st approval -> accept
-        form = userRequestService.getForms(user.getKey()).get(0);
+        form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(user.getKey()).build()).getResult().get(0);
         form = userRequestService.claimForm(form.getTaskId());
         form.getProperty("firstLevelApprove").get().setValue(Boolean.TRUE.toString());
         userRequestService.submitForm(form);
 
         // 2nd approval -> accept
-        form = userRequestService.getForms(user.getKey()).get(0);
+        form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(user.getKey()).build()).getResult().get(0);
         form = userRequestService.claimForm(form.getTaskId());
         form.getProperty("secondLevelApprove").get().setValue(Boolean.TRUE.toString());
         user = userRequestService.submitForm(form);
@@ -115,7 +141,7 @@ public class UserRequestITCase extends AbstractITCase {
         assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
 
         PagedResult<UserRequestForm> forms =
-                userRequestService.getForms(new UserRequestFormQuery.Builder().page(1).size(1000).build());
+                userRequestService.getForms(new UserRequestFormQuery.Builder().build());
         int preForms = forms.getTotalCount();
 
         UserTO user = createUser(UserITCase.getUniqueSampleTO("twoLevelsApproval@tirasa.net")).getEntity();
@@ -123,22 +149,101 @@ public class UserRequestITCase extends AbstractITCase {
         assertFalse(user.getMembership("ebf97068-aa4b-4a85-9f01-680e8c4cf227").isPresent());
 
         // start request
-        UserRequestTO req = userRequestService.start("directorGroupRequest", user.getKey());
+        UserRequest req = userRequestService.start("directorGroupRequest", user.getKey());
         assertNotNull(req);
 
         // check that form was generated
-        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().page(1).size(1000).build());
+        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().build());
         assertEquals(preForms + 1, forms.getTotalCount());
 
-        assertEquals(1, userRequestService.getForms(user.getKey()).size());
+        assertEquals(1, userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(user.getKey()).build()).getResult().size());
 
         // cancel request
         userRequestService.cancel(req.getExecutionId(), "nothing in particular");
 
         // check that form was removed
-        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().page(1).size(1000).build());
+        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().build());
+        assertEquals(preForms, forms.getTotalCount());
+
+        assertTrue(userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(user.getKey()).build()).getResult().isEmpty());
+    }
+
+    @Test
+    public void userSelection() {
+        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
+
+        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
+
+        PagedResult<UserRequestForm> forms =
+                userRequestService.getForms(new UserRequestFormQuery.Builder().build());
+        int preForms = forms.getTotalCount();
+
+        UserTO user = createUser(UserITCase.getUniqueSampleTO("userSelection@tirasa.net")).getEntity();
+        assertNotNull(user);
+        List<RelationshipTO> relationships = userService.read(user.getKey()).getRelationships();
+        assertTrue(relationships.isEmpty());
+
+        SyncopeClient client = clientFactory.create(user.getUsername(), "password123");
+
+        // start request as user
+        UserRequest req = client.getService(UserRequestService.class).start("assignPrinterRequest", null);
+        assertNotNull(req);
+
+        // check (as admin) that a new form is available
+        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().build());
+        assertEquals(preForms + 1, forms.getTotalCount());
+
+        // get (as user) the form, claim and submit
+        PagedResult<UserRequestForm> userForms = client.getService(UserRequestService.class).
+                getForms(new UserRequestFormQuery.Builder().user(user.getKey()).build());
+        assertEquals(1, userForms.getTotalCount());
+
+        UserRequestForm form = userForms.getResult().get(0);
+        assertEquals("assignPrinterRequest", form.getBpmnProcess());
+        form = client.getService(UserRequestService.class).claimForm(form.getTaskId());
+
+        assertFalse(form.getProperty("printer").get().getDropdownValues().isEmpty());
+        form.getProperty("printer").ifPresent(printer -> printer.setValue("8559d14d-58c2-46eb-a2d4-a7d35161e8f8"));
+
+        assertFalse(form.getProperty("printMode").get().getEnumValues().isEmpty());
+        form.getProperty("printMode").ifPresent(printMode -> printMode.setValue("color"));
+
+        client.getService(UserRequestService.class).submitForm(form);
+
+        userForms = client.getService(UserRequestService.class).getForms(
+                new UserRequestFormQuery.Builder().user(user.getKey()).build());
+        assertEquals(0, userForms.getTotalCount());
+
+        // check that user can see the ongoing request
+        PagedResult<UserRequest> requests = client.getService(UserRequestService.class).
+                list(new UserRequestQuery.Builder().user(user.getKey()).build());
+        assertEquals(1, requests.getTotalCount());
+        assertEquals("assignPrinterRequest", requests.getResult().get(0).getBpmnProcess());
+
+        // get (as admin) the new form, claim and submit
+        form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(user.getKey()).build()).getResult().get(0);
+        assertEquals("assignPrinterRequest", form.getBpmnProcess());
+        form = userRequestService.claimForm(form.getTaskId());
+
+        assertEquals("8559d14d-58c2-46eb-a2d4-a7d35161e8f8", form.getProperty("printer").get().getValue());
+
+        form.getProperty("approve").get().setValue(Boolean.TRUE.toString());
+        userRequestService.submitForm(form);
+
+        // no more forms available
+        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().build());
         assertEquals(preForms, forms.getTotalCount());
 
-        assertTrue(userRequestService.getForms(user.getKey()).isEmpty());
+        assertTrue(client.getService(UserRequestService.class).
+                list(new UserRequestQuery.Builder().user(user.getKey()).build()).getResult().isEmpty());
+
+        // check that relationship was made effective by approval
+        relationships = userService.read(user.getKey()).getRelationships();
+        assertFalse(relationships.isEmpty());
+        assertTrue(relationships.stream().
+                anyMatch(relationship -> "8559d14d-58c2-46eb-a2d4-a7d35161e8f8".equals(relationship.getOtherEndKey())));
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserSelfITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserSelfITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserSelfITCase.java
index 0e48cf2..63db20e 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserSelfITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserSelfITCase.java
@@ -130,7 +130,8 @@ public class UserSelfITCase extends AbstractITCase {
         }
 
         // now approve and verify that propagation has happened
-        UserRequestForm form = userRequestService.getForms(userTO.getKey()).get(0);
+        UserRequestForm form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(userTO.getKey()).build()).getResult().get(0);
         form = userRequestService.claimForm(form.getTaskId());
         form.getProperty("approveCreate").get().setValue(Boolean.TRUE.toString());
         userTO = userRequestService.submitForm(form);
@@ -228,7 +229,8 @@ public class UserSelfITCase extends AbstractITCase {
         }
 
         // 3. approve self-update as admin
-        UserRequestForm form = userRequestService.getForms(updated.getKey()).get(0);
+        UserRequestForm form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(updated.getKey()).build()).getResult().get(0);
         form = userRequestService.claimForm(form.getTaskId());
         form.getProperty("approveUpdate").get().setValue(Boolean.TRUE.toString());
         updated = userRequestService.submitForm(form);
@@ -420,7 +422,8 @@ public class UserSelfITCase extends AbstractITCase {
         assertEquals("createApproval", userTO.getStatus());
 
         // 2. request if there is any pending task for user just created
-        UserRequestForm form = userRequestService.getForms(userTO.getKey()).get(0);
+        UserRequestForm form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(userTO.getKey()).build()).getResult().get(0);
         assertNotNull(form);
         assertNotNull(form.getUsername());
         assertEquals(userTO.getUsername(), form.getUsername());
@@ -480,8 +483,7 @@ public class UserSelfITCase extends AbstractITCase {
         assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
 
         // read forms *before* any operation
-        PagedResult<UserRequestForm> forms =
-                userRequestService.getForms(new UserRequestFormQuery.Builder().page(1).size(1000).build());
+        PagedResult<UserRequestForm> forms = userRequestService.getForms(new UserRequestFormQuery.Builder().build());
         int preForms = forms.getTotalCount();
 
         UserTO userTO = UserITCase.getUniqueSampleTO("createWithApproval@syncope.apache.org");
@@ -514,7 +516,7 @@ public class UserSelfITCase extends AbstractITCase {
         assertNotNull(exception);
 
         // 2. request if there is any pending form for user just created
-        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().page(1).size(1000).build());
+        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().build());
         assertEquals(preForms + 1, forms.getTotalCount());
 
         // 3. as admin, update user: still pending approval
@@ -524,7 +526,8 @@ public class UserSelfITCase extends AbstractITCase {
         userPatch.setUsername(new StringReplacePatchItem.Builder().value(updatedUsername).build());
         updateUser(userPatch);
 
-        UserRequestForm form = userRequestService.getForms(userTO.getKey()).get(0);
+        UserRequestForm form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(userTO.getKey()).build()).getResult().get(0);
         assertNotNull(form);
         assertNotNull(form.getTaskId());
         assertNotNull(form.getUserTO());
@@ -567,8 +570,8 @@ public class UserSelfITCase extends AbstractITCase {
         assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
 
         // read forms *before* any operation
-        PagedResult<UserRequestForm> forms = userRequestService.getForms(new UserRequestFormQuery.Builder().
-                page(1).size(1000).build());
+        PagedResult<UserRequestForm> forms = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().build());
         int preForms = forms.getTotalCount();
 
         UserTO created = createUser(UserITCase.getUniqueSampleTO("updateApproval@syncope.apache.org")).getEntity();
@@ -585,10 +588,11 @@ public class UserSelfITCase extends AbstractITCase {
         assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
         assertEquals("updateApproval", userService.read(created.getKey()).getStatus());
 
-        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().page(1).size(1000).build());
+        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().build());
         assertEquals(preForms + 1, forms.getTotalCount());
 
-        UserRequestForm form = userRequestService.getForms(created.getKey()).get(0);
+        UserRequestForm form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(created.getKey()).build()).getResult().get(0);
         assertNotNull(form);
         assertNotNull(form.getTaskId());
         assertNull(form.getOwner());
@@ -607,7 +611,8 @@ public class UserSelfITCase extends AbstractITCase {
         assertEquals(0, updated.getMemberships().size());
 
         // the patch is not updated in the approval form
-        form = userRequestService.getForms(created.getKey()).get(0);
+        form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(created.getKey()).build()).getResult().get(0);
         assertEquals(patch, form.getUserPatch());
 
         // approve the user
@@ -642,8 +647,7 @@ public class UserSelfITCase extends AbstractITCase {
         assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
 
         // read forms *before* any operation
-        PagedResult<UserRequestForm> forms = userRequestService.getForms(new UserRequestFormQuery.Builder().
-                page(1).size(1000).build());
+        PagedResult<UserRequestForm> forms = userRequestService.getForms(new UserRequestFormQuery.Builder().build());
         int preForms = forms.getTotalCount();
 
         UserTO userTO = UserITCase.getUniqueSampleTO("issueSYNCOPE15@syncope.apache.org");
@@ -667,10 +671,11 @@ public class UserSelfITCase extends AbstractITCase {
         assertEquals(userTO.getCreationDate(), userTO.getLastChangeDate());
 
         // 2. request if there is any pending form for user just created
-        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().page(1).size(1000).build());
+        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().build());
         assertEquals(preForms + 1, forms.getTotalCount());
 
-        UserRequestForm form = userRequestService.getForms(userTO.getKey()).get(0);
+        UserRequestForm form = userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(userTO.getKey()).build()).getResult().get(0);
         assertNotNull(form);
 
         // 3. first claim by bellini ....
@@ -692,12 +697,12 @@ public class UserSelfITCase extends AbstractITCase {
         userTO = userRequestService.submitForm(form);
         assertNotNull(userTO);
         assertEquals(preForms,
-                userRequestService.getForms(new UserRequestFormQuery.Builder().page(1).size(1000).build()).
-                        getTotalCount());
-        assertTrue(userRequestService.getForms(userTO.getKey()).isEmpty());
+                userRequestService.getForms(new UserRequestFormQuery.Builder().build()).getTotalCount());
+        assertTrue(userRequestService.getForms(
+                new UserRequestFormQuery.Builder().user(userTO.getKey()).build()).getResult().isEmpty());
 
         // 7.check that no more forms are still to be processed
-        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().page(1).size(1000).build());
+        forms = userRequestService.getForms(new UserRequestFormQuery.Builder().build());
         assertEquals(preForms, forms.getTotalCount());
     }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index edc38b0..a04a9e5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1748,6 +1748,12 @@ under the License.
         </plugin>
         
         <plugin>
+          <groupId>org.codehaus.mojo</groupId>
+          <artifactId>build-helper-maven-plugin</artifactId>
+          <version>3.0.0</version>
+        </plugin>
+
+        <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-javadoc-plugin</artifactId>
           <version>2.10.4</version>          
@@ -2305,8 +2311,8 @@ under the License.
             <link>https://docs.spring.io/spring-security/site/docs/5.0.x/api/</link>
             <link>http://www.flowable.org/docs/javadocs/</link>
             <link>https://build.shibboleth.net/nexus/content/sites/site/java-opensaml/3.3.0/apidocs/</link>
-	    <link>https://artifacts.elastic.co/javadoc/org/elasticsearch/elasticsearch/6.4.1/index.html</link>
-	    <link>http://docs.swagger.io/swagger-core/v2.0.5/apidocs/</link>
+            <link>https://artifacts.elastic.co/javadoc/org/elasticsearch/elasticsearch/6.4.1/index.html</link>
+            <link>http://docs.swagger.io/swagger-core/v2.0.5/apidocs/</link>
           </links>
         </configuration>
         <reportSets>


[5/6] syncope git commit: [SYNCOPE-1369] User requests forms now support dropdowns - via Flowable customization

Posted by il...@apache.org.
http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequest.java
----------------------------------------------------------------------
diff --git a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequest.java b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequest.java
new file mode 100644
index 0000000..251eb89
--- /dev/null
+++ b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequest.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.common.lib.to;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+
+@XmlRootElement(name = "userRequest")
+@XmlType
+public class UserRequest extends AbstractBaseBean {
+
+    private static final long serialVersionUID = -8430826310789942133L;
+
+    private String bpmnProcess;
+
+    private String user;
+
+    private String executionId;
+
+    private String activityId;
+
+    public String getBpmnProcess() {
+        return bpmnProcess;
+    }
+
+    public void setBpmnProcess(final String bpmnProcess) {
+        this.bpmnProcess = bpmnProcess;
+    }
+
+    public String getUser() {
+        return user;
+    }
+
+    public void setUser(final String user) {
+        this.user = user;
+    }
+
+    public String getExecutionId() {
+        return executionId;
+    }
+
+    public void setExecutionId(final String executionId) {
+        this.executionId = executionId;
+    }
+
+    public String getActivityId() {
+        return activityId;
+    }
+
+    public void setActivityId(final String activityId) {
+        this.activityId = activityId;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestForm.java
----------------------------------------------------------------------
diff --git a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestForm.java b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestForm.java
index 897cfc0..22118b5 100644
--- a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestForm.java
+++ b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestForm.java
@@ -37,8 +37,12 @@ public class UserRequestForm extends AbstractBaseBean {
 
     private static final long serialVersionUID = -7044543391316529128L;
 
+    private String bpmnProcess;
+
     private String username;
 
+    private String executionId;
+
     private String taskId;
 
     private String formKey;
@@ -55,6 +59,14 @@ public class UserRequestForm extends AbstractBaseBean {
 
     private final List<UserRequestFormProperty> properties = new ArrayList<>();
 
+    public String getBpmnProcess() {
+        return bpmnProcess;
+    }
+
+    public void setBpmnProcess(final String bpmnProcess) {
+        this.bpmnProcess = bpmnProcess;
+    }
+
     public String getUsername() {
         return username;
     }
@@ -63,6 +75,14 @@ public class UserRequestForm extends AbstractBaseBean {
         this.username = username;
     }
 
+    public String getExecutionId() {
+        return executionId;
+    }
+
+    public void setExecutionId(final String executionId) {
+        this.executionId = executionId;
+    }
+
     public String getTaskId() {
         return taskId;
     }
@@ -138,9 +158,9 @@ public class UserRequestForm extends AbstractBaseBean {
         return properties.stream().filter(property -> id.equals(property.getId())).findFirst();
     }
 
-    @XmlElementWrapper(name = "workflowFormProperties")
-    @XmlElement(name = "workflowFormProperty")
-    @JsonProperty("workflowFormProperties")
+    @XmlElementWrapper(name = "properties")
+    @XmlElement(name = "property")
+    @JsonProperty("properties")
     public List<UserRequestFormProperty> getProperties() {
         return properties;
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestFormProperty.java
----------------------------------------------------------------------
diff --git a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestFormProperty.java b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestFormProperty.java
index 0a431c6..95f2fec 100644
--- a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestFormProperty.java
+++ b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestFormProperty.java
@@ -51,6 +51,9 @@ public class UserRequestFormProperty extends AbstractBaseBean {
     @XmlJavaTypeAdapter(XmlGenericMapAdapter.class)
     private final Map<String, String> enumValues = new HashMap<>();
 
+    @XmlJavaTypeAdapter(XmlGenericMapAdapter.class)
+    private final Map<String, String> dropdownValues = new HashMap<>();
+
     private String value;
 
     public String getId() {
@@ -114,6 +117,11 @@ public class UserRequestFormProperty extends AbstractBaseBean {
         return enumValues;
     }
 
+    @JsonProperty
+    public Map<String, String> getDropdownValues() {
+        return dropdownValues;
+    }
+
     public String getValue() {
         return value;
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestTO.java
----------------------------------------------------------------------
diff --git a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestTO.java b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestTO.java
deleted file mode 100644
index d5fad6c..0000000
--- a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/to/UserRequestTO.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.common.lib.to;
-
-import javax.xml.bind.annotation.XmlRootElement;
-import javax.xml.bind.annotation.XmlType;
-import org.apache.syncope.common.lib.AbstractBaseBean;
-
-@XmlRootElement(name = "userRequest")
-@XmlType
-public class UserRequestTO extends AbstractBaseBean {
-
-    private static final long serialVersionUID = -8430826310789942133L;
-
-    private String processInstanceId;
-
-    private String executionId;
-
-    private String bpmnProcess;
-
-    private String user;
-
-    public String getProcessInstanceId() {
-        return processInstanceId;
-    }
-
-    public void setProcessInstanceId(final String processInstanceId) {
-        this.processInstanceId = processInstanceId;
-    }
-
-    public String getExecutionId() {
-        return executionId;
-    }
-
-    public void setExecutionId(final String executionId) {
-        this.executionId = executionId;
-    }
-
-    public String getBpmnProcess() {
-        return bpmnProcess;
-    }
-
-    public void setBpmnProcess(final String bpmnProcess) {
-        this.bpmnProcess = bpmnProcess;
-    }
-
-    public String getUser() {
-        return user;
-    }
-
-    public void setUser(final String user) {
-        this.user = user;
-    }
-}

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/FlowableEntitlement.java
----------------------------------------------------------------------
diff --git a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/FlowableEntitlement.java b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/FlowableEntitlement.java
index 43825a4..fbf5b02 100644
--- a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/FlowableEntitlement.java
+++ b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/FlowableEntitlement.java
@@ -36,19 +36,13 @@ public final class FlowableEntitlement {
 
     public static final String WORKFLOW_TASK_LIST = "WORKFLOW_TASK_LIST";
 
-    public static final String WORKFLOW_FORM_LIST = "WORKFLOW_FORM_LIST";
+    public static final String USER_REQUEST_LIST = "USER_REQUEST_LIST";
 
-    public static final String WORKFLOW_FORM_READ = "WORKFLOW_FORM_READ";
+    public static final String USER_REQUEST_FORM_LIST = "USER_REQUEST_FORM_LIST";
 
-    public static final String WORKFLOW_FORM_CLAIM = "WORKFLOW_FORM_CLAIM";
+    public static final String USER_REQUEST_FORM_CLAIM = "USER_REQUEST_FORM_CLAIM";
 
-    public static final String WORKFLOW_FORM_SUBMIT = "WORKFLOW_FORM_SUBMIT";
-
-    public static final String USER_REQUEST_DEF_CREATE = "USER_REQUEST_DEF_CREATE";
-
-    public static final String USER_REQUEST_DEF_UPDATE = "USER_REQUEST_DEF_UPDATE";
-
-    public static final String USER_REQUEST_DEF_DELETE = "USER_REQUEST_DEF_DELETE";
+    public static final String USER_REQUEST_FORM_SUBMIT = "USER_REQUEST_FORM_SUBMIT";
 
     public static final String USER_REQUEST_START = "USER_REQUEST_START";
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/UserRequestFormPropertyType.java
----------------------------------------------------------------------
diff --git a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/UserRequestFormPropertyType.java b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/UserRequestFormPropertyType.java
index 9565f99..227296b 100644
--- a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/UserRequestFormPropertyType.java
+++ b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/UserRequestFormPropertyType.java
@@ -27,6 +27,7 @@ public enum UserRequestFormPropertyType {
     Long,
     Enum,
     Date,
-    Boolean
+    Boolean,
+    Dropdown
 
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/BpmnProcessManager.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/BpmnProcessManager.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/BpmnProcessManager.java
index 9247fd4..f7d1747 100644
--- a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/BpmnProcessManager.java
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/BpmnProcessManager.java
@@ -20,7 +20,7 @@ package org.apache.syncope.core.flowable.api;
 
 import java.io.OutputStream;
 import java.util.List;
-import org.apache.syncope.common.lib.to.BpmnProcessTO;
+import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.syncope.common.lib.types.BpmnProcessFormat;
 
 public interface BpmnProcessManager {
@@ -28,7 +28,7 @@ public interface BpmnProcessManager {
     /**
      * @return all available workflow processes.
      */
-    List<BpmnProcessTO> getProcesses();
+    List<BpmnProcess> getProcesses();
 
     /**
      * Export the process for the given key, in the requested format.

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/DropdownValueProvider.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/DropdownValueProvider.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/DropdownValueProvider.java
new file mode 100644
index 0000000..b6c79cf
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/DropdownValueProvider.java
@@ -0,0 +1,31 @@
+/*
+ * 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.api;
+
+import java.util.Map;
+
+/**
+ * Implementations of this interface are used with {@link org.apache.syncope.core.flowable.support.DropdownFormType}.
+ */
+public interface DropdownValueProvider {
+
+    String NAME = "dropdownValueProvider";
+
+    Map<String, String> getValues();
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/UserRequestHandler.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/UserRequestHandler.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/UserRequestHandler.java
index b11b97c..c3c6f0d 100644
--- a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/UserRequestHandler.java
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/api/UserRequestHandler.java
@@ -21,7 +21,7 @@ package org.apache.syncope.core.flowable.api;
 import java.util.List;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.patch.UserPatch;
-import org.apache.syncope.common.lib.to.UserRequestTO;
+import org.apache.syncope.common.lib.to.UserRequest;
 import org.apache.syncope.common.lib.to.UserRequestForm;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.entity.user.User;
@@ -33,13 +33,25 @@ import org.springframework.transaction.event.TransactionalEventListener;
 public interface UserRequestHandler {
 
     /**
+     * Get the running user requests matching the provided parameters.
+     *
+     * @param userKey user key (optional)
+     * @param page result page
+     * @param size items per page
+     * @param orderByClauses sort conditions
+     * @return total number of user requests, list of user requests matching the provided parameters
+     */
+    Pair<Integer, List<UserRequest>> getUserRequests(
+            String userKey, int page, int size, List<OrderByClause> orderByClauses);
+
+    /**
      * Starts a new user request, for the given BPMN process and user.
      *
      * @param bpmnProcess BPMN process
      * @param user user
      * @return data about the started request service, including execution id
      */
-    UserRequestTO start(String bpmnProcess, User user);
+    UserRequest start(String bpmnProcess, User user);
 
     /**
      * Parses the given execution id to find matching user request and owner.
@@ -73,22 +85,16 @@ public interface UserRequestHandler {
     void cancelByUser(AnyDeletedEvent event);
 
     /**
-     * Get the forms for current workflow process instances matching the provided parameters.
+     * Get the forms matching the provided parameters.
      *
+     * @param userKey user key (optional)
      * @param page result page
      * @param size items per page
      * @param orderByClauses sort conditions
      * @return total number of forms, list of forms matching the provided parameters
      */
-    Pair<Integer, List<UserRequestForm>> getForms(int page, int size, List<OrderByClause> orderByClauses);
-
-    /**
-     * Get forms for given user (if present).
-     *
-     * @param userKey user key
-     * @return form (if present), otherwise null
-     */
-    List<UserRequestForm> getForms(String userKey);
+    Pair<Integer, List<UserRequestForm>> getForms(
+            String userKey, int page, int size, List<OrderByClause> orderByClauses);
 
     /**
      * Claim a form for a given object.

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableBpmnProcessManager.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableBpmnProcessManager.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableBpmnProcessManager.java
index bca8b25..50b078b 100644
--- a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableBpmnProcessManager.java
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableBpmnProcessManager.java
@@ -28,9 +28,10 @@ import java.io.OutputStream;
 import java.util.List;
 import java.util.stream.Collectors;
 import org.apache.commons.io.IOUtils;
-import org.apache.syncope.common.lib.to.BpmnProcessTO;
+import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.syncope.common.lib.types.BpmnProcessFormat;
 import org.apache.syncope.core.flowable.support.DomainProcessEngine;
+import org.apache.syncope.core.flowable.support.DropdownAwareJsonConverter;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 import org.apache.syncope.core.workflow.api.WorkflowException;
 import org.flowable.bpmn.converter.BpmnXMLConverter;
@@ -73,11 +74,11 @@ public class FlowableBpmnProcessManager implements BpmnProcessManager {
     }
 
     @Override
-    public List<BpmnProcessTO> getProcesses() {
+    public List<BpmnProcess> getProcesses() {
         try {
             return engine.getRepositoryService().createProcessDefinitionQuery().latestVersion().list().stream().
                     map(procDef -> {
-                        BpmnProcessTO defTO = new BpmnProcessTO();
+                        BpmnProcess defTO = new BpmnProcess();
                         defTO.setKey(procDef.getKey());
                         defTO.setName(procDef.getName());
 
@@ -163,7 +164,7 @@ public class FlowableBpmnProcessManager implements BpmnProcessManager {
                                 "Could not find JSON node " + BpmnJsonConverter.EDITOR_CHILD_SHAPES);
                     }
 
-                    BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(definitionNode);
+                    BpmnModel bpmnModel = new DropdownAwareJsonConverter().convertToBpmnModel(definitionNode);
                     deployment = FlowableDeployUtils.deployDefinition(
                             engine,
                             resourceName,

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableRuntimeUtils.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableRuntimeUtils.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableRuntimeUtils.java
index 12d8faa..cdf035d 100644
--- a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableRuntimeUtils.java
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableRuntimeUtils.java
@@ -22,6 +22,7 @@ import java.util.Base64;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.core.flowable.support.DomainProcessEngine;
@@ -33,7 +34,6 @@ import org.apache.syncope.core.workflow.api.WorkflowException;
 import org.flowable.common.engine.api.FlowableException;
 import org.flowable.engine.history.HistoricActivityInstance;
 import org.flowable.engine.impl.RuntimeServiceImpl;
-import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
 import org.flowable.engine.repository.ProcessDefinition;
 import org.flowable.engine.runtime.ProcessInstance;
 import org.flowable.task.api.Task;
@@ -97,8 +97,17 @@ public final class FlowableRuntimeUtils {
         return procInst == null ? null : procInst.getId();
     }
 
-    public static String getProcBusinessKey(final String processDefinitionId, final String userKey) {
-        return processDefinitionId + ":" + userKey;
+    public static String getProcBusinessKey(final String procDefId, final String userKey) {
+        return procDefId + ":" + userKey;
+    }
+
+    public static Pair<String, String> splitProcBusinessKey(final String procBusinessKey) {
+        String[] split = procBusinessKey.split(":");
+        if (split == null || split.length != 2) {
+            throw new WorkflowException(new IllegalArgumentException("Unexpected business key: " + procBusinessKey));
+        }
+
+        return Pair.of(split[0], split[1]);
     }
 
     public static ProcessDefinition getLatestProcDefByKey(final DomainProcessEngine engine, final String key) {
@@ -111,17 +120,17 @@ public final class FlowableRuntimeUtils {
     }
 
     public static Set<String> getPerformedTasks(
-            final DomainProcessEngine engine, final String procInstID, final User user) {
+            final DomainProcessEngine engine, final String procInstId, final User user) {
 
         return engine.getHistoryService().createHistoricActivityInstanceQuery().
-                executionId(procInstID).
+                executionId(procInstId).
                 list().stream().
                 map(HistoricActivityInstance::getActivityId).
                 collect(Collectors.toSet());
     }
 
-    public static void updateStatus(final DomainProcessEngine engine, final String procInstID, final User user) {
-        List<Task> tasks = createTaskQuery(engine, false).processInstanceId(procInstID).list();
+    public static void updateStatus(final DomainProcessEngine engine, final String procInstId, final User user) {
+        List<Task> tasks = createTaskQuery(engine, false).processInstanceId(procInstId).list();
         if (tasks.isEmpty() || tasks.size() > 1) {
             LOG.warn("While setting user status: unexpected task number ({})", tasks.size());
         } else {
@@ -129,13 +138,6 @@ public final class FlowableRuntimeUtils {
         }
     }
 
-    public static List<ProcessInstance> getProcessInstances(final DomainProcessEngine engine, final String userKey) {
-        return engine.getRuntimeService().createNativeProcessInstanceQuery().
-                sql("SELECT ID_,PROC_INST_ID_ FROM " + engine.getManagementService().getTableName(ExecutionEntity.class)
-                        + " WHERE BUSINESS_KEY_ LIKE '" + getProcBusinessKey("%", userKey) + "'"
-                        + " AND PARENT_ID_ IS NULL").list();
-    }
-
     public static TaskQuery createTaskQuery(final DomainProcessEngine engine, final boolean onlyFormTasks) {
         SyncopeTaskQueryImpl taskQuery = new SyncopeTaskQueryImpl(
                 ((RuntimeServiceImpl) engine.getRuntimeService()).getCommandExecutor());
@@ -145,10 +147,10 @@ public final class FlowableRuntimeUtils {
         return taskQuery;
     }
 
-    public static String getFormTask(final DomainProcessEngine engine, final String procInstID) {
+    public static String getFormTask(final DomainProcessEngine engine, final String procInstId) {
         String result = null;
 
-        List<Task> tasks = createTaskQuery(engine, true).processInstanceId(procInstID).list();
+        List<Task> tasks = createTaskQuery(engine, true).processInstanceId(procInstId).list();
         if (tasks.isEmpty() || tasks.size() > 1) {
             LOG.debug("While checking if form task: unexpected task number ({})", tasks.size());
         } else {
@@ -162,7 +164,7 @@ public final class FlowableRuntimeUtils {
      * Saves resources to be propagated and password for later - after form submission - propagation.
      *
      * @param engine Flowable engine
-     * @param procInstID process instance id
+     * @param procInstId process instance id
      * @param user user JPA entity
      * @param userTO user transfer object
      * @param password password
@@ -171,35 +173,35 @@ public final class FlowableRuntimeUtils {
      */
     public static void saveForFormSubmit(
             final DomainProcessEngine engine,
-            final String procInstID,
+            final String procInstId,
             final User user,
             final UserTO userTO,
             final String password,
             final Boolean enabled,
             final PropagationByResource propByRes) {
 
-        String formTaskId = getFormTask(engine, procInstID);
+        String formTaskId = getFormTask(engine, procInstId);
         if (formTaskId == null) {
             return;
         }
 
-        engine.getRuntimeService().setVariable(procInstID, FlowableRuntimeUtils.USER_TO, userTO);
+        engine.getRuntimeService().setVariable(procInstId, FlowableRuntimeUtils.USER_TO, userTO);
 
         if (password == null) {
             String encryptedPwd = engine.getRuntimeService().
-                    getVariable(procInstID, FlowableRuntimeUtils.ENCRYPTED_PWD, String.class);
+                    getVariable(procInstId, FlowableRuntimeUtils.ENCRYPTED_PWD, String.class);
             if (encryptedPwd != null) {
                 userTO.setPassword(decrypt(encryptedPwd));
             }
         } else {
             userTO.setPassword(password);
             engine.getRuntimeService().
-                    setVariable(procInstID, FlowableRuntimeUtils.ENCRYPTED_PWD, encrypt(password));
+                    setVariable(procInstId, FlowableRuntimeUtils.ENCRYPTED_PWD, encrypt(password));
         }
 
-        engine.getRuntimeService().setVariable(procInstID, FlowableRuntimeUtils.ENABLED, enabled);
+        engine.getRuntimeService().setVariable(procInstId, FlowableRuntimeUtils.ENABLED, enabled);
 
-        engine.getRuntimeService().setVariable(procInstID, FlowableRuntimeUtils.PROP_BY_RESOURCE, propByRes);
+        engine.getRuntimeService().setVariable(procInstId, FlowableRuntimeUtils.PROP_BY_RESOURCE, propByRes);
         if (propByRes != null) {
             propByRes.clear();
         }

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserRequestHandler.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserRequestHandler.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserRequestHandler.java
index 1c198a8..45a834b 100644
--- a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserRequestHandler.java
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableUserRequestHandler.java
@@ -31,13 +31,14 @@ import org.apache.commons.lang3.StringUtils;
 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.UserRequestTO;
+import org.apache.syncope.common.lib.to.UserRequest;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.to.UserRequestFormProperty;
 import org.apache.syncope.common.lib.to.UserRequestForm;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.common.lib.types.UserRequestFormPropertyType;
+import org.apache.syncope.core.flowable.api.DropdownValueProvider;
 import org.apache.syncope.core.flowable.api.WorkflowTaskManager;
 import org.apache.syncope.core.flowable.support.DomainProcessEngine;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
@@ -49,16 +50,21 @@ import org.apache.syncope.core.provisioning.api.PropagationByResource;
 import org.apache.syncope.core.provisioning.api.WorkflowResult;
 import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
 import org.apache.syncope.core.provisioning.api.event.AnyDeletedEvent;
+import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.apache.syncope.core.spring.BeanUtils;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.apache.syncope.core.workflow.api.WorkflowException;
 import org.flowable.common.engine.api.FlowableException;
+import org.flowable.common.engine.api.FlowableIllegalArgumentException;
 import org.flowable.engine.form.FormProperty;
 import org.flowable.engine.form.FormType;
 import org.flowable.engine.form.TaskFormData;
 import org.flowable.engine.history.HistoricActivityInstance;
+import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
 import org.flowable.engine.impl.persistence.entity.HistoricFormPropertyEntity;
+import org.flowable.engine.runtime.NativeProcessInstanceQuery;
 import org.flowable.engine.runtime.ProcessInstance;
+import org.flowable.engine.runtime.ProcessInstanceQuery;
 import org.flowable.task.api.Task;
 import org.flowable.task.api.TaskQuery;
 import org.flowable.task.api.history.HistoricTaskInstance;
@@ -93,6 +99,93 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
     @Autowired
     protected EntityFactory entityFactory;
 
+    protected NativeProcessInstanceQuery createProcessInstanceQuery(final String userKey) {
+        return engine.getRuntimeService().createNativeProcessInstanceQuery().
+                sql("SELECT DISTINCT ID_,BUSINESS_KEY_,ACT_ID_ FROM "
+                        + engine.getManagementService().getTableName(ExecutionEntity.class)
+                        + " WHERE BUSINESS_KEY_ LIKE '"
+                        + FlowableRuntimeUtils.getProcBusinessKey("%", userKey) + "'"
+                        + " AND BUSINESS_KEY_ NOT LIKE '"
+                        + FlowableRuntimeUtils.getProcBusinessKey(FlowableRuntimeUtils.WF_PROCESS_ID, "%") + "'"
+                        + " AND PARENT_ID_ IS NULL");
+    }
+
+    protected int countProcessInstances(final String userKey) {
+        return (int) engine.getRuntimeService().createNativeProcessInstanceQuery().
+                sql("SELECT COUNT(ID_) FROM "
+                        + engine.getManagementService().getTableName(ExecutionEntity.class)
+                        + " WHERE BUSINESS_KEY_ LIKE '"
+                        + FlowableRuntimeUtils.getProcBusinessKey("%", userKey) + "'"
+                        + " AND BUSINESS_KEY_ NOT LIKE '"
+                        + FlowableRuntimeUtils.getProcBusinessKey(FlowableRuntimeUtils.WF_PROCESS_ID, "%") + "'"
+                        + " AND PARENT_ID_ IS NULL").count();
+    }
+
+    protected UserRequest getUserRequest(final ProcessInstance procInst) {
+        Pair<String, String> split = FlowableRuntimeUtils.splitProcBusinessKey(procInst.getBusinessKey());
+
+        UserRequest userRequest = new UserRequest();
+        userRequest.setBpmnProcess(split.getLeft());
+        userRequest.setUser(split.getRight());
+        userRequest.setExecutionId(procInst.getId());
+        userRequest.setActivityId(procInst.getActivityId());
+        return userRequest;
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public Pair<Integer, List<UserRequest>> getUserRequests(
+            final String userKey,
+            final int page,
+            final int size,
+            final List<OrderByClause> orderByClauses) {
+
+        Integer count = null;
+        List<UserRequest> result = null;
+        if (userKey == null) {
+            ProcessInstanceQuery query = engine.getRuntimeService().createProcessInstanceQuery().active();
+            for (OrderByClause clause : orderByClauses) {
+                boolean sorted = true;
+                switch (clause.getField().trim()) {
+                    case "processDefinitionId":
+                        query.orderByProcessDefinitionId();
+                        break;
+
+                    case "processDefinitionKey":
+                        query.orderByProcessDefinitionKey();
+                        break;
+
+                    case "processInstanceId":
+                        query.orderByProcessInstanceId();
+                        break;
+
+                    default:
+                        LOG.warn("User request sort request by {}: unsupported, ignoring", clause.getField().trim());
+                        sorted = false;
+                }
+                if (sorted) {
+                    if (clause.getDirection() == OrderByClause.Direction.ASC) {
+                        query.asc();
+                    } else {
+                        query.desc();
+                    }
+                }
+
+                count = (int) query.count();
+                result = query.listPage(size * (page <= 0 ? 0 : page - 1), size).stream().
+                        map(procInst -> getUserRequest(procInst)).
+                        collect(Collectors.toList());
+            }
+        } else {
+            count = countProcessInstances(userKey);
+            result = createProcessInstanceQuery(userKey).listPage(size * (page <= 0 ? 0 : page - 1), size).stream().
+                    map(procInst -> getUserRequest(procInst)).
+                    collect(Collectors.toList());
+        }
+
+        return Pair.of(count, result);
+    }
+
     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
@@ -101,8 +194,8 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
     }
 
     @Override
-    public UserRequestTO start(final String bpmnProcess, final User user) {
-        Map<String, Object> variables = new HashMap<>(2);
+    public UserRequest start(final String bpmnProcess, final User user) {
+        Map<String, Object> variables = new HashMap<>();
         variables.put(FlowableRuntimeUtils.WF_EXECUTOR, AuthContextUtils.getUsername());
         variables.put(FlowableRuntimeUtils.USER, lazyLoad(user));
         variables.put(FlowableRuntimeUtils.USER_TO, dataBinder.getUserTO(user, true));
@@ -118,12 +211,8 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
                 procInst.getProcessInstanceId(),
                 FlowableRuntimeUtils.getProcBusinessKey(bpmnProcess, user.getKey()));
 
-        UserRequestTO userRequestTO = new UserRequestTO();
-        userRequestTO.setProcessInstanceId(procInst.getProcessInstanceId());
-        userRequestTO.setExecutionId(procInst.getId());
-        userRequestTO.setBpmnProcess(bpmnProcess);
-        userRequestTO.setUser(user.getKey());
-        return userRequestTO;
+        return getUserRequest(engine.getRuntimeService().createProcessInstanceQuery().
+                processInstanceId(procInst.getProcessInstanceId()).singleResult());
     }
 
     @Override
@@ -132,7 +221,11 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
         try {
             procInst = engine.getRuntimeService().
                     createProcessInstanceQuery().processInstanceId(executionId).singleResult();
+            if (procInst == null) {
+                throw new FlowableIllegalArgumentException("ProcessInstance with id " + executionId);
+            }
         } catch (FlowableException e) {
+            LOG.error("Could find execution ProcessInstance with id {}", executionId, e);
             throw new NotFoundException("User request execution with id " + executionId);
         }
 
@@ -163,7 +256,7 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
     public void cancelByUser(final AnyDeletedEvent event) {
         if (AuthContextUtils.getDomain().equals(event.getDomain()) && event.getAnyTypeKind() == AnyTypeKind.USER) {
             String username = event.getAnyName();
-            FlowableRuntimeUtils.getProcessInstances(engine, event.getAnyKey()).
+            createProcessInstanceQuery(event.getAnyKey()).list().
                     forEach(procInst -> {
                         engine.getRuntimeService().deleteProcessInstance(
                                 procInst.getId(), "Cascade Delete user " + username);
@@ -192,6 +285,10 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
                     result = UserRequestFormPropertyType.Boolean;
                     break;
 
+                case "dropdown":
+                    result = UserRequestFormPropertyType.Dropdown;
+                    break;
+
                 case "string":
                 default:
                     break;
@@ -201,19 +298,19 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
         return result;
     }
 
-    protected UserRequestForm getFormTO(final Task task) {
-        return getFormTO(task, engine.getFormService().getTaskFormData(task.getId()));
+    protected UserRequestForm getForm(final Task task) {
+        return FlowableUserRequestHandler.this.getForm(task, engine.getFormService().getTaskFormData(task.getId()));
     }
 
-    protected UserRequestForm getFormTO(final Task task, final TaskFormData fd) {
+    protected UserRequestForm getForm(final Task task, final TaskFormData fd) {
         UserRequestForm formTO =
-                getFormTO(task.getProcessInstanceId(), task.getId(), fd.getFormKey(), fd.getFormProperties());
+                getForm(task.getProcessInstanceId(), task.getId(), fd.getFormKey(), fd.getFormProperties());
         BeanUtils.copyProperties(task, formTO);
 
         return formTO;
     }
 
-    protected UserRequestForm getFormTO(final HistoricTaskInstance task) {
+    protected UserRequestForm getForm(final HistoricTaskInstance task) {
         List<HistoricFormPropertyEntity> props = engine.getHistoryService().
                 createHistoricDetailQuery().taskId(task.getId()).list().stream().
                 filter(HistoricFormPropertyEntity.class::isInstance).
@@ -244,16 +341,19 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
     }
 
     protected UserRequestForm getHistoricFormTO(
-            final String procInstID,
+            final String procInstId,
             final String taskId,
             final String formKey,
             final List<HistoricFormPropertyEntity> props) {
 
         UserRequestForm formTO = new UserRequestForm();
 
-        User user = userDAO.find(getUserKey(procInstID));
+        formTO.setBpmnProcess(engine.getRuntimeService().createProcessInstanceQuery().
+                processInstanceId(procInstId).singleResult().getProcessDefinitionKey());
+
+        User user = userDAO.find(getUserKey(procInstId));
         if (user == null) {
-            throw new NotFoundException("User for process instance id " + procInstID);
+            throw new NotFoundException("User for process instance id " + procInstId);
         }
         formTO.setUsername(user.getUsername());
 
@@ -261,9 +361,9 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
         formTO.setFormKey(formKey);
 
         formTO.setUserTO(engine.getRuntimeService().
-                getVariable(procInstID, FlowableRuntimeUtils.USER_TO, UserTO.class));
+                getVariable(procInstId, FlowableRuntimeUtils.USER_TO, UserTO.class));
         formTO.setUserPatch(engine.getRuntimeService().
-                getVariable(procInstID, FlowableRuntimeUtils.USER_PATCH, UserPatch.class));
+                getVariable(procInstId, FlowableRuntimeUtils.USER_PATCH, UserPatch.class));
 
         formTO.getProperties().addAll(props.stream().map(prop -> {
             UserRequestFormProperty propertyTO = new UserRequestFormProperty();
@@ -277,37 +377,58 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
     }
 
     @SuppressWarnings("unchecked")
-    protected UserRequestForm getFormTO(
-            final String procInstID,
+    protected UserRequestForm getForm(
+            final String procInstId,
             final String taskId,
             final String formKey,
             final List<FormProperty> props) {
 
         UserRequestForm formTO = new UserRequestForm();
 
-        User user = userDAO.find(getUserKey(procInstID));
+        formTO.setBpmnProcess(engine.getRuntimeService().createProcessInstanceQuery().
+                processInstanceId(procInstId).singleResult().getProcessDefinitionKey());
+
+        User user = userDAO.find(getUserKey(procInstId));
         if (user == null) {
-            throw new NotFoundException("User for process instance id " + procInstID);
+            throw new NotFoundException("User for process instance id " + procInstId);
         }
         formTO.setUsername(user.getUsername());
 
+        formTO.setExecutionId(procInstId);
         formTO.setTaskId(taskId);
         formTO.setFormKey(formKey);
 
         formTO.setUserTO(engine.getRuntimeService().
-                getVariable(procInstID, FlowableRuntimeUtils.USER_TO, UserTO.class));
+                getVariable(procInstId, FlowableRuntimeUtils.USER_TO, UserTO.class));
         formTO.setUserPatch(engine.getRuntimeService().
-                getVariable(procInstID, FlowableRuntimeUtils.USER_PATCH, UserPatch.class));
+                getVariable(procInstId, FlowableRuntimeUtils.USER_PATCH, UserPatch.class));
 
         formTO.getProperties().addAll(props.stream().map(fProp -> {
             UserRequestFormProperty propertyTO = new UserRequestFormProperty();
             BeanUtils.copyProperties(fProp, propertyTO, PROPERTY_IGNORE_PROPS);
             propertyTO.setType(fromFlowableFormType(fProp.getType()));
-            if (propertyTO.getType() == UserRequestFormPropertyType.Date) {
-                propertyTO.setDatePattern((String) fProp.getType().getInformation("datePattern"));
-            }
-            if (propertyTO.getType() == UserRequestFormPropertyType.Enum) {
-                propertyTO.getEnumValues().putAll((Map<String, String>) fProp.getType().getInformation("values"));
+            switch (propertyTO.getType()) {
+                case Date:
+                    propertyTO.setDatePattern((String) fProp.getType().getInformation("datePattern"));
+                    break;
+
+                case Enum:
+                    propertyTO.getEnumValues().putAll((Map<String, String>) fProp.getType().getInformation("values"));
+                    break;
+
+                case Dropdown:
+                    String valueProviderBean = (String) fProp.getType().getInformation(DropdownValueProvider.NAME);
+                    try {
+                        DropdownValueProvider valueProvider = ApplicationContextProvider.getApplicationContext().
+                                getBean(valueProviderBean, DropdownValueProvider.class);
+                        propertyTO.getDropdownValues().putAll(valueProvider.getValues());
+                    } catch (Exception e) {
+                        LOG.error("Could not find bean {} of type {} for form property {}",
+                                valueProviderBean, DropdownValueProvider.class.getName(), propertyTO.getId(), e);
+                    }
+                    break;
+
+                default:
             }
             return propertyTO;
         }).collect(Collectors.toList()));
@@ -318,26 +439,28 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
     @Transactional(readOnly = true)
     @Override
     public Pair<Integer, List<UserRequestForm>> getForms(
-            final int page, final int size, final List<OrderByClause> orderByClauses) {
+            final String userKey,
+            final int page,
+            final int size,
+            final List<OrderByClause> orderByClauses) {
 
-        Pair<Integer, List<UserRequestForm>> forms = null;
+        Pair<Integer, List<UserRequestForm>> forms;
 
-        TaskQuery formTaskQuery = FlowableRuntimeUtils.createTaskQuery(engine, true);
+        TaskQuery query = FlowableRuntimeUtils.createTaskQuery(engine, true);
+        if (userKey != null) {
+            query.processInstanceBusinessKeyLike(FlowableRuntimeUtils.getProcBusinessKey("%", userKey));
+        }
 
         String authUser = AuthContextUtils.getUsername();
         if (adminUser.equals(authUser)) {
-            forms = getForms(formTaskQuery, page, size, orderByClauses);
+            forms = getForms(query, page, size, orderByClauses);
         } else {
             User user = userDAO.findByUsername(authUser);
-            if (user == null) {
-                throw new NotFoundException("Syncope User " + authUser);
-            }
-
-            forms = getForms(formTaskQuery.taskCandidateOrAssigned(user.getUsername()), page, size, orderByClauses);
+            forms = getForms(query.taskCandidateOrAssigned(user.getUsername()), page, size, orderByClauses);
 
             List<String> candidateGroups = new ArrayList<>(userDAO.findAllGroupNames(user));
             if (!candidateGroups.isEmpty()) {
-                forms = getForms(formTaskQuery.taskCandidateGroupIn(candidateGroups), page, size, orderByClauses);
+                forms = getForms(query.taskCandidateGroupIn(candidateGroups), page, size, orderByClauses);
             }
         }
 
@@ -349,75 +472,56 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
     protected Pair<Integer, List<UserRequestForm>> getForms(
             final TaskQuery query, final int page, final int size, final List<OrderByClause> orderByClauses) {
 
-        TaskQuery sortedQuery = query;
         for (OrderByClause clause : orderByClauses) {
-            boolean ack = true;
+            boolean sorted = true;
             switch (clause.getField().trim()) {
+                case "bpmnProcess":
+                    query.orderByProcessDefinitionId();
+                    break;
+
+                case "executionId":
+                    query.orderByExecutionId();
+                    break;
+
                 case "taskId":
-                    sortedQuery = sortedQuery.orderByTaskId();
+                    query.orderByTaskId();
                     break;
 
                 case "createTime":
-                    sortedQuery = sortedQuery.orderByTaskCreateTime();
+                    query.orderByTaskCreateTime();
                     break;
 
                 case "dueDate":
-                    sortedQuery = sortedQuery.orderByTaskDueDate();
+                    query.orderByTaskDueDate();
                     break;
 
                 case "owner":
-                    sortedQuery = sortedQuery.orderByTaskOwner();
+                    query.orderByTaskOwner();
                     break;
 
                 default:
                     LOG.warn("Form sort request by {}: unsupported, ignoring", clause.getField().trim());
-                    ack = false;
+                    sorted = false;
             }
-            if (ack) {
-                sortedQuery = clause.getDirection() == OrderByClause.Direction.ASC
-                        ? sortedQuery.asc()
-                        : sortedQuery.desc();
+            if (sorted) {
+                if (clause.getDirection() == OrderByClause.Direction.ASC) {
+                    query.asc();
+                } else {
+                    query.desc();
+                }
             }
         }
 
-        List<UserRequestForm> result = sortedQuery.listPage(size * (page <= 0 ? 0 : page - 1), size).stream().
+        List<UserRequestForm> result = query.listPage(size * (page <= 0 ? 0 : page - 1), size).stream().
                 map(task -> task instanceof HistoricTaskInstance
-                ? getFormTO((HistoricTaskInstance) task) : getFormTO(task)).
+                ? FlowableUserRequestHandler.this.getForm((HistoricTaskInstance) task)
+                : FlowableUserRequestHandler.this.getForm(task)).
                 collect(Collectors.toList());
 
         return Pair.of((int) query.count(), result);
     }
 
-    @Override
-    public List<UserRequestForm> getForms(final String userKey) {
-        List<UserRequestForm> result = new ArrayList<>();
-        FlowableRuntimeUtils.getProcessInstances(engine, userKey).forEach(procInst -> {
-            Task task;
-            try {
-                task = FlowableRuntimeUtils.createTaskQuery(engine, true).
-                        processInstanceId(procInst.getProcessInstanceId()).singleResult();
-            } catch (FlowableException e) {
-                throw new WorkflowException("While reading form for process instance "
-                        + procInst.getProcessInstanceId(), e);
-            }
-
-            if (task != null) {
-                TaskFormData formData;
-                try {
-                    formData = engine.getFormService().getTaskFormData(task.getId());
-                } catch (FlowableException e) {
-                    throw new WorkflowException("Error while getting form data for task " + task.getId(), e);
-                }
-                if (formData != null && !formData.getFormProperties().isEmpty()) {
-                    result.add(getFormTO(task, formData));
-                }
-            }
-        });
-
-        return result;
-    }
-
-    protected Pair<Task, TaskFormData> checkTask(final String taskId, final String authUser) {
+    protected Pair<Task, TaskFormData> parseTask(final String taskId) {
         Task task;
         try {
             task = FlowableRuntimeUtils.createTaskQuery(engine, true).taskId(taskId).singleResult();
@@ -435,27 +539,20 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
             throw new NotFoundException("Form for Flowable Task " + taskId, e);
         }
 
-        if (!adminUser.equals(authUser)) {
-            User user = userDAO.findByUsername(authUser);
-            if (user == null) {
-                throw new NotFoundException("Syncope User " + authUser);
-            }
-        }
-
         return Pair.of(task, formData);
     }
 
     @Override
     public UserRequestForm claimForm(final String taskId) {
-        String authUser = AuthContextUtils.getUsername();
-        Pair<Task, TaskFormData> checked = checkTask(taskId, authUser);
+        Pair<Task, TaskFormData> parsed = parseTask(taskId);
 
+        String authUser = AuthContextUtils.getUsername();
         if (!adminUser.equals(authUser)) {
             List<Task> tasksForUser = FlowableRuntimeUtils.createTaskQuery(engine, true).
-                    taskId(taskId).taskCandidateUser(authUser).list();
+                    taskId(taskId).taskCandidateOrAssigned(authUser).list();
             if (tasksForUser.isEmpty()) {
                 throw new WorkflowException(
-                        new IllegalArgumentException(authUser + " is not candidate for task " + taskId));
+                        new IllegalArgumentException(authUser + " is not candidate nor assignee of task " + taskId));
             }
         }
 
@@ -467,7 +564,7 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
             throw new WorkflowException("While reading task " + taskId, e);
         }
 
-        return getFormTO(task, checked.getRight());
+        return FlowableUserRequestHandler.this.getForm(task, parsed.getRight());
     }
 
     private Map<String, String> getPropertiesForSubmit(final UserRequestForm form) {
@@ -482,36 +579,36 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
 
     @Override
     public WorkflowResult<UserPatch> submitForm(final UserRequestForm form) {
-        String authUser = AuthContextUtils.getUsername();
-        Pair<Task, TaskFormData> checked = checkTask(form.getTaskId(), authUser);
+        Pair<Task, TaskFormData> parsed = parseTask(form.getTaskId());
 
-        if (!checked.getLeft().getOwner().equals(authUser)) {
+        String authUser = AuthContextUtils.getUsername();
+        if (!parsed.getLeft().getOwner().equals(authUser)) {
             throw new WorkflowException(new IllegalArgumentException("Task " + form.getTaskId() + " assigned to "
-                    + checked.getLeft().getOwner() + " but submitted by " + authUser));
+                    + parsed.getLeft().getOwner() + " but submitted by " + authUser));
         }
 
-        String procInstID = checked.getLeft().getProcessInstanceId();
+        String procInstId = parsed.getLeft().getProcessInstanceId();
 
-        User user = userDAO.find(getUserKey(procInstID));
+        User user = userDAO.find(getUserKey(procInstId));
         if (user == null) {
-            throw new NotFoundException("User with key " + getUserKey(procInstID));
+            throw new NotFoundException("User with key " + getUserKey(procInstId));
         }
 
-        Set<String> preTasks = FlowableRuntimeUtils.getPerformedTasks(engine, procInstID, user);
+        Set<String> preTasks = FlowableRuntimeUtils.getPerformedTasks(engine, procInstId, user);
 
-        engine.getRuntimeService().setVariable(procInstID, FlowableRuntimeUtils.TASK, "submit");
-        engine.getRuntimeService().setVariable(procInstID, FlowableRuntimeUtils.FORM_SUBMITTER, authUser);
-        engine.getRuntimeService().setVariable(procInstID, FlowableRuntimeUtils.USER, lazyLoad(user));
+        engine.getRuntimeService().setVariable(procInstId, FlowableRuntimeUtils.TASK, "submit");
+        engine.getRuntimeService().setVariable(procInstId, FlowableRuntimeUtils.FORM_SUBMITTER, authUser);
+        engine.getRuntimeService().setVariable(procInstId, FlowableRuntimeUtils.USER, lazyLoad(user));
         try {
             engine.getFormService().submitTaskFormData(form.getTaskId(), getPropertiesForSubmit(form));
         } catch (FlowableException e) {
             FlowableRuntimeUtils.throwException(e, "While submitting form for task " + form.getTaskId());
         }
-        Set<String> postTasks = FlowableRuntimeUtils.getPerformedTasks(engine, procInstID, user);
+        Set<String> postTasks = FlowableRuntimeUtils.getPerformedTasks(engine, procInstId, user);
         postTasks.removeAll(preTasks);
         postTasks.add(form.getTaskId());
-        if (procInstID.equals(FlowableRuntimeUtils.getWFProcInstID(engine, user.getKey()))) {
-            FlowableRuntimeUtils.updateStatus(engine, procInstID, user);
+        if (procInstId.equals(FlowableRuntimeUtils.getWFProcInstID(engine, user.getKey()))) {
+            FlowableRuntimeUtils.updateStatus(engine, procInstId, user);
         }
 
         user = userDAO.save(user);
@@ -521,34 +618,34 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
         PropagationByResource propByRes = null;
 
         ProcessInstance afterSubmitPI = engine.getRuntimeService().
-                createProcessInstanceQuery().processInstanceId(procInstID).singleResult();
+                createProcessInstanceQuery().processInstanceId(procInstId).singleResult();
         if (afterSubmitPI != null) {
-            engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.TASK);
-            engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.FORM_SUBMITTER);
-            engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.USER);
-            engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.USER_TO);
+            engine.getRuntimeService().removeVariable(procInstId, FlowableRuntimeUtils.TASK);
+            engine.getRuntimeService().removeVariable(procInstId, FlowableRuntimeUtils.FORM_SUBMITTER);
+            engine.getRuntimeService().removeVariable(procInstId, FlowableRuntimeUtils.USER);
+            engine.getRuntimeService().removeVariable(procInstId, FlowableRuntimeUtils.USER_TO);
 
             // see if there is any propagation to be done
             propByRes = engine.getRuntimeService().
-                    getVariable(procInstID, FlowableRuntimeUtils.PROP_BY_RESOURCE, PropagationByResource.class);
-            engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.PROP_BY_RESOURCE);
+                    getVariable(procInstId, FlowableRuntimeUtils.PROP_BY_RESOURCE, PropagationByResource.class);
+            engine.getRuntimeService().removeVariable(procInstId, FlowableRuntimeUtils.PROP_BY_RESOURCE);
 
             // fetch - if available - the encrypted password
             String encryptedPwd = engine.getRuntimeService().
-                    getVariable(procInstID, FlowableRuntimeUtils.ENCRYPTED_PWD, String.class);
-            engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.ENCRYPTED_PWD);
+                    getVariable(procInstId, FlowableRuntimeUtils.ENCRYPTED_PWD, String.class);
+            engine.getRuntimeService().removeVariable(procInstId, FlowableRuntimeUtils.ENCRYPTED_PWD);
             if (StringUtils.isNotBlank(encryptedPwd)) {
                 clearPassword = FlowableRuntimeUtils.decrypt(encryptedPwd);
             }
 
             Boolean enabled = engine.getRuntimeService().
-                    getVariable(procInstID, FlowableRuntimeUtils.ENABLED, Boolean.class);
-            engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.ENABLED);
+                    getVariable(procInstId, FlowableRuntimeUtils.ENABLED, Boolean.class);
+            engine.getRuntimeService().removeVariable(procInstId, FlowableRuntimeUtils.ENABLED);
 
             // supports approval chains
             FlowableRuntimeUtils.saveForFormSubmit(
                     engine,
-                    procInstID,
+                    procInstId,
                     user,
                     dataBinder.getUserTO(user, true),
                     clearPassword,
@@ -556,8 +653,8 @@ public class FlowableUserRequestHandler implements UserRequestHandler {
                     propByRes);
 
             userPatch = engine.getRuntimeService().
-                    getVariable(procInstID, FlowableRuntimeUtils.USER_PATCH, UserPatch.class);
-            engine.getRuntimeService().removeVariable(procInstID, FlowableRuntimeUtils.USER_PATCH);
+                    getVariable(procInstId, FlowableRuntimeUtils.USER_PATCH, UserPatch.class);
+            engine.getRuntimeService().removeVariable(procInstId, FlowableRuntimeUtils.USER_PATCH);
         }
         if (userPatch == null) {
             userPatch = new UserPatch();

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/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
index 162b113..de2bbfe 100644
--- 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
@@ -18,13 +18,16 @@
  */
 package org.apache.syncope.core.flowable.support;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 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.form.AbstractFormType;
 import org.flowable.engine.impl.util.EngineServiceUtil;
 import org.flowable.idm.spring.SpringIdmEngineConfiguration;
 import org.flowable.spring.SpringExpressionManager;
@@ -81,6 +84,9 @@ public class DomainProcessEngineFactoryBean
                                 EngineConfigurationConstants.KEY_IDM_ENGINE_CONFIG,
                                 ctx.getBean(SpringIdmEngineConfiguration.class));
                     }
+                    List<AbstractFormType> customFormTypes = new ArrayList<>();
+                    customFormTypes.add(new DropdownFormType(null));
+                    conf.setCustomFormTypes(customFormTypes);
 
                     engines.put(domain, conf.buildProcessEngine());
                 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DropdownAwareJsonConverter.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DropdownAwareJsonConverter.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DropdownAwareJsonConverter.java
new file mode 100644
index 0000000..ad29e17
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DropdownAwareJsonConverter.java
@@ -0,0 +1,31 @@
+/*
+ * 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.bpmn.model.UserTask;
+import org.flowable.editor.constants.StencilConstants;
+import org.flowable.editor.language.json.converter.BpmnJsonConverter;
+
+public class DropdownAwareJsonConverter extends BpmnJsonConverter {
+
+    public DropdownAwareJsonConverter() {
+        convertersToBpmnMap.put(StencilConstants.STENCIL_TASK_USER, DropdownAwareUserTaskJsonConverter.class);
+        convertersToJsonMap.put(UserTask.class, DropdownAwareUserTaskJsonConverter.class);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DropdownAwareUserTaskJsonConverter.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DropdownAwareUserTaskJsonConverter.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DropdownAwareUserTaskJsonConverter.java
new file mode 100644
index 0000000..838201c
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DropdownAwareUserTaskJsonConverter.java
@@ -0,0 +1,98 @@
+/*
+ * 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 com.fasterxml.jackson.databind.JsonNode;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+import org.flowable.bpmn.model.BaseElement;
+import org.flowable.bpmn.model.FormProperty;
+import org.flowable.bpmn.model.FormValue;
+import org.flowable.bpmn.model.StartEvent;
+import org.flowable.bpmn.model.UserTask;
+import org.flowable.editor.language.json.converter.BpmnJsonConverterUtil;
+import org.flowable.editor.language.json.converter.UserTaskJsonConverter;
+
+public class DropdownAwareUserTaskJsonConverter extends UserTaskJsonConverter {
+
+    @Override
+    protected void convertJsonToFormProperties(final JsonNode objectNode, final BaseElement element) {
+        JsonNode formPropertiesNode = getProperty(PROPERTY_FORM_PROPERTIES, objectNode);
+        if (formPropertiesNode != null) {
+            formPropertiesNode = BpmnJsonConverterUtil.validateIfNodeIsTextual(formPropertiesNode);
+            JsonNode propertiesArray = formPropertiesNode.get("formProperties");
+            if (propertiesArray != null) {
+                for (JsonNode formNode : propertiesArray) {
+                    JsonNode formIdNode = formNode.get(PROPERTY_FORM_ID);
+                    if (formIdNode != null && StringUtils.isNotEmpty(formIdNode.asText())) {
+
+                        FormProperty formProperty = new FormProperty();
+                        formProperty.setId(formIdNode.asText());
+                        formProperty.setName(getValueAsString(PROPERTY_FORM_NAME, formNode));
+                        formProperty.setType(getValueAsString(PROPERTY_FORM_TYPE, formNode));
+                        formProperty.setExpression(getValueAsString(PROPERTY_FORM_EXPRESSION, formNode));
+                        formProperty.setVariable(getValueAsString(PROPERTY_FORM_VARIABLE, formNode));
+
+                        if ("date".equalsIgnoreCase(formProperty.getType())) {
+                            formProperty.setDatePattern(getValueAsString(PROPERTY_FORM_DATE_PATTERN, formNode));
+
+                        } else if ("enum".equalsIgnoreCase(formProperty.getType())
+                                || "dropdown".equalsIgnoreCase(formProperty.getType())) {
+
+                            JsonNode enumValuesNode = formNode.get(PROPERTY_FORM_ENUM_VALUES);
+                            if (enumValuesNode != null) {
+                                List<FormValue> formValueList = new ArrayList<>();
+                                for (JsonNode enumNode : enumValuesNode) {
+                                    if (enumNode.get(PROPERTY_FORM_ENUM_VALUES_ID) != null && !enumNode.get(
+                                            PROPERTY_FORM_ENUM_VALUES_ID).isNull() && enumNode.get(
+                                                    PROPERTY_FORM_ENUM_VALUES_NAME) != null
+                                            && !enumNode.get(PROPERTY_FORM_ENUM_VALUES_NAME).isNull()) {
+
+                                        FormValue formValue = new FormValue();
+                                        formValue.setId(enumNode.get(PROPERTY_FORM_ENUM_VALUES_ID).asText());
+                                        formValue.setName(enumNode.get(PROPERTY_FORM_ENUM_VALUES_NAME).asText());
+                                        formValueList.add(formValue);
+
+                                    } else if (enumNode.get("value") != null && !enumNode.get("value").isNull()) {
+                                        FormValue formValue = new FormValue();
+                                        formValue.setId(enumNode.get("value").asText());
+                                        formValue.setName(enumNode.get("value").asText());
+                                        formValueList.add(formValue);
+                                    }
+                                }
+                                formProperty.setFormValues(formValueList);
+                            }
+                        }
+
+                        formProperty.setRequired(getValueAsBoolean(PROPERTY_FORM_REQUIRED, formNode));
+                        formProperty.setReadable(getValueAsBoolean(PROPERTY_FORM_READABLE, formNode));
+                        formProperty.setWriteable(getValueAsBoolean(PROPERTY_FORM_WRITABLE, formNode));
+
+                        if (element instanceof StartEvent) {
+                            ((StartEvent) element).getFormProperties().add(formProperty);
+                        } else if (element instanceof UserTask) {
+                            ((UserTask) element).getFormProperties().add(formProperty);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DropdownFormType.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DropdownFormType.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DropdownFormType.java
new file mode 100644
index 0000000..6c612f6
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/DropdownFormType.java
@@ -0,0 +1,59 @@
+/*
+ * 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.engine.form.AbstractFormType;
+
+/**
+ * Extension to predefined Flowable form types relying on the provided
+ * {@link org.apache.syncope.core.flowable.api.DropdownValueProvider} bean to populate values.
+ */
+public class DropdownFormType extends AbstractFormType {
+
+    private static final long serialVersionUID = -3549337216346168946L;
+
+    protected final String dropdownValueProvider;
+
+    public DropdownFormType(final String dropdownValueProvider) {
+        this.dropdownValueProvider = dropdownValueProvider;
+    }
+
+    @Override
+    public String getName() {
+        return "dropdown";
+    }
+
+    @Override
+    public Object getInformation(final String key) {
+        if (key.equals("dropdownValueProvider")) {
+            return dropdownValueProvider;
+        }
+        return null;
+    }
+
+    @Override
+    public Object convertFormValueToModelValue(final String propertyValue) {
+        return propertyValue;
+    }
+
+    @Override
+    public String convertModelValueToFormValue(final Object modelValue) {
+        return modelValue == null ? null : modelValue.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeFormHandlerHelper.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeFormHandlerHelper.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeFormHandlerHelper.java
new file mode 100644
index 0000000..9f6e751
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeFormHandlerHelper.java
@@ -0,0 +1,56 @@
+/*
+ * 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.bpmn.model.FlowElement;
+import org.flowable.bpmn.model.UserTask;
+import org.flowable.bpmn.model.Process;
+import org.flowable.engine.impl.form.FormHandlerHelper;
+import org.flowable.engine.impl.form.TaskFormHandler;
+import org.flowable.engine.impl.persistence.entity.DeploymentEntity;
+import org.flowable.engine.impl.util.CommandContextUtil;
+import org.flowable.engine.impl.util.ProcessDefinitionUtil;
+import org.flowable.engine.repository.ProcessDefinition;
+
+/**
+ * Used to inject {@link SyncopeTaskFormHandler} rather than
+ * {@link org.flowable.engine.impl.form.DefaultTaskFormHandler}.
+ */
+public class SyncopeFormHandlerHelper extends FormHandlerHelper {
+
+    @Override
+    public TaskFormHandler getTaskFormHandlder(final String procDefId, final String taskId) {
+        Process process = ProcessDefinitionUtil.getProcess(procDefId);
+        FlowElement flowElement = process.getFlowElement(taskId, true);
+        if (flowElement instanceof UserTask) {
+            UserTask userTask = (UserTask) flowElement;
+
+            ProcessDefinition processDefinitionEntity = ProcessDefinitionUtil.getProcessDefinition(procDefId);
+            DeploymentEntity deploymentEntity = CommandContextUtil.getProcessEngineConfiguration().
+                    getDeploymentEntityManager().findById(processDefinitionEntity.getDeploymentId());
+
+            TaskFormHandler taskFormHandler = new SyncopeTaskFormHandler();
+            taskFormHandler.parseConfiguration(
+                    userTask.getFormProperties(), userTask.getFormKey(), deploymentEntity, processDefinitionEntity);
+            return taskFormHandler;
+        }
+
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeTaskFormHandler.java
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeTaskFormHandler.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeTaskFormHandler.java
new file mode 100644
index 0000000..69c5e50
--- /dev/null
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeTaskFormHandler.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.List;
+import java.util.Optional;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.core.flowable.api.DropdownValueProvider;
+import org.flowable.bpmn.model.FormProperty;
+import org.flowable.common.engine.api.delegate.Expression;
+import org.flowable.common.engine.impl.el.ExpressionManager;
+import org.flowable.engine.form.AbstractFormType;
+import org.flowable.engine.impl.form.DefaultTaskFormHandler;
+import org.flowable.engine.impl.form.FormPropertyHandler;
+import org.flowable.engine.impl.form.FormTypes;
+import org.flowable.engine.impl.persistence.entity.DeploymentEntity;
+import org.flowable.engine.impl.util.CommandContextUtil;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Extends {@link DefaultTaskFormHandler} with purpose of supporting more form types than Flowable's default.
+ */
+public class SyncopeTaskFormHandler extends DefaultTaskFormHandler {
+
+    private static final long serialVersionUID = -5271243544388455797L;
+
+    protected static final Logger LOG = LoggerFactory.getLogger(SyncopeTaskFormHandler.class);
+
+    protected Optional<AbstractFormType> parseFormPropertyType(
+            final FormProperty formProperty, final ExpressionManager expressionManager) {
+
+        AbstractFormType formType = null;
+
+        switch (formProperty.getType()) {
+            case "dropdown":
+                if (formProperty.getFormValues().isEmpty()
+                        || !DropdownValueProvider.NAME.equals(formProperty.getFormValues().get(0).getId())) {
+
+                    LOG.warn("A single value with id '" + DropdownValueProvider.NAME + "' was expected, ignoring");
+                } else {
+                    formType = new DropdownFormType(formProperty.getFormValues().get(0).getName());
+                }
+                break;
+
+            default:
+        }
+
+        return Optional.ofNullable(formType);
+    }
+
+    @Override
+    public void parseConfiguration(
+            final List<FormProperty> formProperties,
+            final String formKey,
+            final DeploymentEntity deployment,
+            final ProcessDefinition processDefinition) {
+
+        this.deploymentId = deployment.getId();
+
+        ExpressionManager expressionManager = CommandContextUtil.getProcessEngineConfiguration().getExpressionManager();
+
+        if (StringUtils.isNotEmpty(formKey)) {
+            this.formKey = expressionManager.createExpression(formKey);
+        }
+
+        FormTypes formTypes = CommandContextUtil.getProcessEngineConfiguration().getFormTypes();
+
+        formProperties.forEach(formProperty -> {
+            FormPropertyHandler formPropertyHandler = new FormPropertyHandler();
+            formPropertyHandler.setId(formProperty.getId());
+            formPropertyHandler.setName(formProperty.getName());
+
+            AbstractFormType type = parseFormPropertyType(formProperty, expressionManager).
+                    orElse(formTypes.parseFormPropertyType(formProperty));
+            formPropertyHandler.setType(type);
+            formPropertyHandler.setRequired(formProperty.isRequired());
+            formPropertyHandler.setReadable(formProperty.isReadable());
+            formPropertyHandler.setWritable(formProperty.isWriteable());
+            formPropertyHandler.setVariableName(formProperty.getVariable());
+
+            if (StringUtils.isNotEmpty(formProperty.getExpression())) {
+                Expression expression = expressionManager.createExpression(formProperty.getExpression());
+                formPropertyHandler.setVariableExpression(expression);
+            }
+
+            if (StringUtils.isNotEmpty(formProperty.getDefaultExpression())) {
+                Expression defaultExpression = expressionManager.createExpression(formProperty.getDefaultExpression());
+                formPropertyHandler.setDefaultExpression(defaultExpression);
+            }
+
+            formPropertyHandlers.add(formPropertyHandler);
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/flowable-bpmn/src/main/resources/workflowFlowableContext.xml
----------------------------------------------------------------------
diff --git a/ext/flowable/flowable-bpmn/src/main/resources/workflowFlowableContext.xml b/ext/flowable/flowable-bpmn/src/main/resources/workflowFlowableContext.xml
index 9e27f47..4f34237 100644
--- a/ext/flowable/flowable-bpmn/src/main/resources/workflowFlowableContext.xml
+++ b/ext/flowable/flowable-bpmn/src/main/resources/workflowFlowableContext.xml
@@ -38,6 +38,8 @@ under the License.
     <property name="idmEngineConfiguration" ref="syncopeIdmEngineConfiguration"/> 
   </bean>
 
+  <bean id="syncopeFormHandlerHelper" class="org.apache.syncope.core.flowable.support.SyncopeFormHandlerHelper"/>
+
   <bean class="org.apache.syncope.core.flowable.support.DomainProcessEngineConfiguration" scope="prototype">
     <property name="databaseSchemaUpdate" value="true"/>
 
@@ -52,6 +54,8 @@ under the License.
         <bean class="org.apache.syncope.core.flowable.support.SyncopeEntitiesVariableType"/>
       </list>
     </property>
+    
+    <property name="formHandlerHelper" ref="syncopeFormHandlerHelper"/>
   </bean>
 
   <bean id="bpmnProcessManager" class="org.apache.syncope.core.flowable.impl.FlowableBpmnProcessManager"/>

http://git-wip-us.apache.org/repos/asf/syncope/blob/fee1317d/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/BpmnProcessLogic.java
----------------------------------------------------------------------
diff --git a/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/BpmnProcessLogic.java b/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/BpmnProcessLogic.java
index 8d280ac..ad76605 100644
--- a/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/BpmnProcessLogic.java
+++ b/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/BpmnProcessLogic.java
@@ -21,7 +21,7 @@ package org.apache.syncope.core.logic;
 import java.io.OutputStream;
 import java.lang.reflect.Method;
 import java.util.List;
-import org.apache.syncope.common.lib.to.BpmnProcessTO;
+import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.syncope.common.lib.types.FlowableEntitlement;
 import org.apache.syncope.common.lib.types.BpmnProcessFormat;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -31,14 +31,14 @@ import org.springframework.transaction.annotation.Transactional;
 import org.apache.syncope.core.flowable.api.BpmnProcessManager;
 
 @Component
-public class BpmnProcessLogic extends AbstractTransactionalLogic<BpmnProcessTO> {
+public class BpmnProcessLogic extends AbstractTransactionalLogic<BpmnProcess> {
 
     @Autowired
     private BpmnProcessManager bpmnProcessManager;
 
     @PreAuthorize("hasRole('" + FlowableEntitlement.BPMN_PROCESS_LIST + "')")
     @Transactional(readOnly = true)
-    public List<BpmnProcessTO> list() {
+    public List<BpmnProcess> list() {
         return bpmnProcessManager.getProcesses();
     }
 
@@ -65,7 +65,7 @@ public class BpmnProcessLogic extends AbstractTransactionalLogic<BpmnProcessTO>
     }
 
     @Override
-    protected BpmnProcessTO resolveReference(final Method method, final Object... args)
+    protected BpmnProcess resolveReference(final Method method, final Object... args)
             throws UnresolvedReferenceException {
 
         throw new UnresolvedReferenceException();