syncope-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From andreapatrice...@apache.org
Subject [syncope] branch master updated: [SYnCOPE-1421] enabled enduser extensions, first draft of User Requests mgmt
Date Tue, 30 Apr 2019 15:37:18 GMT
This is an automated email from the ASF dual-hosted git repository.

andreapatricelli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/master by this push:
     new 44a906f  [SYnCOPE-1421] enabled enduser extensions, first draft of User Requests mgmt
44a906f is described below

commit 44a906fe8dcf2225c7f49c736987fe557beb26d4
Author: Andrea Patricelli <andreapatricelli@apache.org>
AuthorDate: Fri Apr 26 15:06:21 2019 +0200

    [SYnCOPE-1421] enabled enduser extensions, first draft of User Requests mgmt
---
 .../client/console/widgets/RemediationsWidget.java |   2 +-
 .../client/ui/commons}/annotations/ExtPage.java    |   2 +-
 .../client/ui/commons}/annotations/ExtWidget.java  |   2 +-
 .../init/ClassPathScanImplementationLookup.java    |   8 +-
 .../syncope/client/console/pages/BasePage.java     |   2 +-
 .../console/panels/DashboardExtensionsPanel.java   |   2 +-
 .../client/enduser/SyncopeEnduserSession.java      |  24 ++-
 .../client/enduser/SyncopeWebApplication.java      |  37 +++--
 .../init/ClassPathScanImplementationLookup.java    |  32 ++--
 .../syncope/client/enduser/navigation/Navbar.java  |  63 +++++++-
 .../client/enduser/pages/BaseEnduserWebPage.java   |  25 +++-
 .../Navbar.java => pages/BaseExtPage.java}         |  16 +-
 .../enduser/SyncopeWebApplication.properties       |   1 +
 .../syncope/client/enduser/navigation/Navbar.html  |  32 +---
 .../syncope/client/console/pages/CamelRoutes.java  |   2 +-
 .../client/console/widgets/CamelMetricsWidget.java |   2 +-
 .../syncope/client/console/pages/Flowable.java     |   2 +-
 .../console/widgets/UserRequestFormsWidget.java    |   2 +-
 ext/flowable/client-enduser/pom.xml                |   4 +-
 .../markup/html/form/BpmnProcessesAjaxPanel.java   |  32 ++--
 .../syncope/client/enduser/pages/Flowable.java     |  70 +++++++++
 .../client/enduser/resources/BpmnProcessList.java  |  84 -----------
 .../resources/UserRequestCancelResource.java       |  99 -------------
 .../resources/UserRequestFormClaimResource.java    |  88 -----------
 .../resources/UserRequestsFormsResource.java       | 162 ---------------------
 .../enduser/resources/UserRequestsResource.java    | 130 -----------------
 .../resources/UserRequestsStartResource.java       |  86 -----------
 .../client/enduser/rest/BpmnProcessRestClient.java |  65 +++++++++
 .../client/enduser/rest/UserRequestRestClient.java |  89 +++++++++++
 .../syncope/client/enduser/pages/Flowable.html     |  33 +++++
 .../client/enduser/pages/Flowable.properties       |  18 +++
 .../client/enduser/pages/UserRequestForms.html     |  32 ++++
 .../enduser/pages/UserRequestForms.properties      |  17 +++
 .../syncope/client/console/pages/OIDCClient.java   |   2 +-
 .../syncope/client/console/pages/SAML2SP.java      |   2 +-
 .../syncope/client/console/pages/SCIMConfPage.java |   2 +-
 fit/console-reference/pom.xml                      |   6 +
 fit/core-reference/pom.xml                         |   8 +-
 fit/enduser-reference/pom.xml                      |   5 +
 39 files changed, 548 insertions(+), 742 deletions(-)

diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/widgets/RemediationsWidget.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/widgets/RemediationsWidget.java
index f544f16..9e7a317 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/widgets/RemediationsWidget.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/widgets/RemediationsWidget.java
@@ -25,7 +25,7 @@ import java.util.Collections;
 import java.util.List;
 import org.apache.syncope.client.console.BookmarkablePageLinkBuilder;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
-import org.apache.syncope.client.console.annotations.ExtWidget;
+import org.apache.syncope.client.ui.commons.annotations.ExtWidget;
 import org.apache.syncope.client.console.pages.Remediations;
 import org.apache.syncope.client.console.rest.RemediationRestClient;
 import org.apache.syncope.client.console.wicket.ajax.IndicatorAjaxTimerBehavior;
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/annotations/ExtPage.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/annotations/ExtPage.java
similarity index 96%
rename from client/idrepo/console/src/main/java/org/apache/syncope/client/console/annotations/ExtPage.java
rename to client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/annotations/ExtPage.java
index f0ed504..4e1d146 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/annotations/ExtPage.java
+++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/annotations/ExtPage.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.console.annotations;
+package org.apache.syncope.client.ui.commons.annotations;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.ElementType;
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/annotations/ExtWidget.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/annotations/ExtWidget.java
similarity index 96%
rename from client/idrepo/console/src/main/java/org/apache/syncope/client/console/annotations/ExtWidget.java
rename to client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/annotations/ExtWidget.java
index 6741e85..edeec5d 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/annotations/ExtWidget.java
+++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/annotations/ExtWidget.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.console.annotations;
+package org.apache.syncope.client.ui.commons.annotations;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.ElementType;
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationLookup.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationLookup.java
index bb4fb60..09b69eb 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationLookup.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationLookup.java
@@ -32,8 +32,8 @@ import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.ObjectUtils;
 import org.apache.syncope.client.console.pages.BaseExtPage;
 import org.apache.syncope.client.ui.commons.annotations.BinaryPreview;
-import org.apache.syncope.client.console.annotations.ExtPage;
-import org.apache.syncope.client.console.annotations.ExtWidget;
+import org.apache.syncope.client.ui.commons.annotations.ExtPage;
+import org.apache.syncope.client.ui.commons.annotations.ExtWidget;
 import org.apache.syncope.client.console.annotations.IdMPage;
 import org.apache.syncope.client.console.annotations.Resource;
 import org.apache.syncope.client.console.commons.AnyDirectoryPanelAditionalActionLinksProvider;
@@ -195,9 +195,9 @@ public class ClassPathScanImplementationLookup {
             try {
                 Class<?> clazz = ClassUtils.resolveClassName(
                         bd.getBeanClassName(), ClassUtils.getDefaultClassLoader());
-                boolean isAbsractClazz = Modifier.isAbstract(clazz.getModifiers());
+                boolean isAbstractClazz = Modifier.isAbstract(clazz.getModifiers());
 
-                if (!isAbsractClazz) {
+                if (!isAbstractClazz) {
                     if (BaseExtPage.class.isAssignableFrom(clazz)) {
                         if (clazz.isAnnotationPresent(ExtPage.class)) {
                             extPages.add((Class<? extends BaseExtPage>) clazz);
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
index 0998167..654fe77 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
@@ -24,7 +24,7 @@ import java.util.List;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.console.BookmarkablePageLinkBuilder;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
-import org.apache.syncope.client.console.annotations.ExtPage;
+import org.apache.syncope.client.ui.commons.annotations.ExtPage;
 import org.apache.syncope.client.console.annotations.IdMPage;
 import org.apache.syncope.client.console.commons.Constants;
 import org.apache.syncope.client.ui.commons.HttpResourceStream;
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DashboardExtensionsPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DashboardExtensionsPanel.java
index 14ac72f..1d05327 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DashboardExtensionsPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DashboardExtensionsPanel.java
@@ -21,7 +21,7 @@ package org.apache.syncope.client.console.panels;
 import java.lang.reflect.Constructor;
 import java.util.ArrayList;
 import java.util.List;
-import org.apache.syncope.client.console.annotations.ExtWidget;
+import org.apache.syncope.client.ui.commons.annotations.ExtWidget;
 import org.apache.syncope.client.console.widgets.BaseExtWidget;
 import org.apache.wicket.AttributeModifier;
 import org.apache.wicket.PageReference;
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java
index 310cf92..678e472 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java
@@ -32,6 +32,7 @@ import org.apache.commons.lang3.time.FastDateFormat;
 import org.apache.cxf.jaxrs.client.WebClient;
 import org.apache.syncope.client.lib.AnonymousAuthenticationHandler;
 import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
 import org.apache.syncope.common.lib.info.PlatformInfo;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
@@ -55,6 +56,8 @@ public class SyncopeEnduserSession extends WebSession {
 
     private final SyncopeClient anonymousClient;
 
+    private final SyncopeClientFactoryBean clientFactory;
+
     private SyncopeClient client;
 
     private final PlatformInfo platformInfo;
@@ -62,7 +65,7 @@ public class SyncopeEnduserSession extends WebSession {
     private UserTO selfTO;
 
     private final Map<Class<?>, Object> services = Collections.synchronizedMap(new HashMap<>());
-    
+
     private final ThreadPoolTaskExecutor executor;
 
     public static SyncopeEnduserSession get() {
@@ -71,12 +74,14 @@ public class SyncopeEnduserSession extends WebSession {
 
     public SyncopeEnduserSession(final Request request) {
         super(request);
+
+        clientFactory = SyncopeWebApplication.get().newClientFactory();
         anonymousClient = SyncopeWebApplication.get().getClientFactory().
                 create(new AnonymousAuthenticationHandler(
                         SyncopeWebApplication.get().getAnonymousUser(),
                         SyncopeWebApplication.get().getAnonymousKey()));
         platformInfo = anonymousClient.getService(SyncopeService.class).platform();
-        
+
         executor = new ThreadPoolTaskExecutor();
         executor.setWaitForTasksToCompleteOnShutdown(false);
         executor.setCorePoolSize(SyncopeWebApplication.get().getCorePoolSize());
@@ -179,6 +184,21 @@ public class SyncopeEnduserSession extends WebSession {
         return serviceInstance;
     }
 
+    public <T> T getService(final MediaType mediaType, final Class<T> serviceClass) {
+        T service;
+
+        synchronized (clientFactory) {
+            SyncopeClientFactoryBean.ContentType preType = clientFactory.getContentType();
+
+            clientFactory.setContentType(SyncopeClientFactoryBean.ContentType.fromString(mediaType.toString()));
+            service = clientFactory.create(getJWT()).getService(serviceClass);
+
+            clientFactory.setContentType(preType);
+        }
+
+        return service;
+    }
+
     public PlatformInfo getPlatformInfo() {
         return platformInfo;
     }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeWebApplication.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeWebApplication.java
index 4b57081..9046692 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeWebApplication.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeWebApplication.java
@@ -95,6 +95,16 @@ public class SyncopeWebApplication extends WicketBootStandardWebApplication {
     @Autowired
     private ClassPathScanImplementationLookup lookup;
 
+    private String scheme;
+
+    private String host;
+
+    private String port;
+
+    private String rootPath;
+
+    private String useGZIPCompression;
+
     private String domain;
 
     private String adminUser;
@@ -130,6 +140,17 @@ public class SyncopeWebApplication extends WicketBootStandardWebApplication {
         // read enduser.properties
         Properties props = PropertyUtils.read(getClass(), ENDUSER_PROPERTIES, "enduser.directory");
 
+        scheme = props.getProperty("scheme");
+        Args.notNull(scheme, "<scheme>");
+        host = props.getProperty("host");
+        Args.notNull(host, "<host>");
+        port = props.getProperty("port");
+        Args.notNull(port, "<port>");
+        rootPath = props.getProperty("rootPath");
+        Args.notNull(rootPath, "<rootPath>");
+        useGZIPCompression = props.getProperty("useGZIPCompression");
+        Args.notNull(useGZIPCompression, "<useGZIPCompression>");
+
         domain = props.getProperty("domain", SyncopeConstants.MASTER_DOMAIN);
         adminUser = props.getProperty("adminUser");
         Args.notNull(adminUser, "<adminUser>");
@@ -144,16 +165,6 @@ public class SyncopeWebApplication extends WicketBootStandardWebApplication {
         xsrfEnabled = Boolean.parseBoolean(props.getProperty("xsrf"));
         Args.notNull(xsrfEnabled, "<xsrf>");
 
-        String scheme = props.getProperty("scheme");
-        Args.notNull(scheme, "<scheme>");
-        String host = props.getProperty("host");
-        Args.notNull(host, "<host>");
-        String port = props.getProperty("port");
-        Args.notNull(port, "<port>");
-        String rootPath = props.getProperty("rootPath");
-        Args.notNull(rootPath, "<rootPath>");
-        String useGZIPCompression = props.getProperty("useGZIPCompression");
-        Args.notNull(useGZIPCompression, "<useGZIPCompression>");
         maxUploadFileSizeMB = props.getProperty("maxUploadFileSizeMB") == null
                 ? null
                 : Integer.valueOf(props.getProperty("maxUploadFileSizeMB"));
@@ -327,6 +338,12 @@ public class SyncopeWebApplication extends WicketBootStandardWebApplication {
         return new SyncopeEnduserSession(request);
     }
 
+    public SyncopeClientFactoryBean newClientFactory() {
+        return new SyncopeClientFactoryBean().
+                setAddress(scheme + "://" + host + ":" + port + StringUtils.prependIfMissing(rootPath, "/")).
+                setUseCompression(BooleanUtils.toBoolean(useGZIPCompression));
+    }
+
     protected Class<? extends WebPage> getSignInPageClass() {
         return Login.class;
     }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/init/ClassPathScanImplementationLookup.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/init/ClassPathScanImplementationLookup.java
index 9cdd056..78b2c0f 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/init/ClassPathScanImplementationLookup.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/init/ClassPathScanImplementationLookup.java
@@ -24,7 +24,9 @@ import java.util.Collections;
 import java.util.List;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.syncope.client.enduser.annotations.Resource;
+import org.apache.syncope.client.enduser.pages.BaseExtPage;
 import org.apache.syncope.client.ui.commons.annotations.BinaryPreview;
+import org.apache.syncope.client.ui.commons.annotations.ExtPage;
 import org.apache.syncope.client.ui.commons.markup.html.form.preview.AbstractBinaryPreviewer;
 import org.apache.syncope.client.ui.commons.panels.SSOLoginFormPanel;
 import org.apache.wicket.request.resource.AbstractResource;
@@ -39,13 +41,15 @@ public class ClassPathScanImplementationLookup {
 
     private static final Logger LOG = LoggerFactory.getLogger(ClassPathScanImplementationLookup.class);
 
-    private static final String DEFAULT_BASE_PACKAGE = "org.apache.syncope.client.enduser";
+    private static final String DEFAULT_BASE_PACKAGE = "org.apache.syncope";
 
-    private List<Class<? extends SSOLoginFormPanel>> ssoLoginFormPanels = new ArrayList<>();
+    private final List<Class<? extends SSOLoginFormPanel>> ssoLoginFormPanels = new ArrayList<>();
 
     private List<Class<? extends AbstractResource>> resources = new ArrayList<>();
 
-    private List<Class<? extends AbstractBinaryPreviewer>> previewers = new ArrayList<>();
+    private final List<Class<? extends AbstractBinaryPreviewer>> previewers = new ArrayList<>();
+
+    private final List<Class<? extends BaseExtPage>> extPages = new ArrayList<>();
 
     /**
      * This method can be overridden by subclasses to customize classpath scan.
@@ -58,19 +62,23 @@ public class ClassPathScanImplementationLookup {
 
     @SuppressWarnings("unchecked")
     public void load() {
-        resources = new ArrayList<>();
-        previewers = new ArrayList<>();
-
         ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
         scanner.addIncludeFilter(new AssignableTypeFilter(AbstractResource.class));
+        scanner.addIncludeFilter(new AssignableTypeFilter(BaseExtPage.class));
 
         for (BeanDefinition bd : scanner.findCandidateComponents(getBasePackage())) {
             try {
                 Class<?> clazz = ClassUtils.resolveClassName(bd.getBeanClassName(), ClassUtils.getDefaultClassLoader());
-                boolean isAbsractClazz = Modifier.isAbstract(clazz.getModifiers());
-
-                if (!isAbsractClazz) {
-                    if (AbstractResource.class.isAssignableFrom(clazz)) {
+                boolean isAbstractClazz = Modifier.isAbstract(clazz.getModifiers());
+                if (!isAbstractClazz) {
+                    if (BaseExtPage.class.isAssignableFrom(clazz)) {
+                        if (clazz.isAnnotationPresent(ExtPage.class)) {
+                            extPages.add((Class<? extends BaseExtPage>) clazz);
+                        } else {
+                            LOG.error("Could not find annotation {} in {}, ignoring",
+                                    ExtPage.class.getName(), clazz.getName());
+                        }
+                    } else if (AbstractResource.class.isAssignableFrom(clazz)) {
                         if (clazz.isAnnotationPresent(Resource.class)) {
                             resources.add((Class<? extends AbstractResource>) clazz);
                         } else if (AbstractBinaryPreviewer.class.isAssignableFrom(clazz)) {
@@ -110,4 +118,8 @@ public class ClassPathScanImplementationLookup {
         return this.ssoLoginFormPanels;
     }
 
+    public List<Class<? extends BaseExtPage>> getExtPageClasses() {
+        return extPages;
+    }
+
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/navigation/Navbar.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/navigation/Navbar.java
index a0bd90a..fafa87f 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/navigation/Navbar.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/navigation/Navbar.java
@@ -18,15 +18,76 @@
  */
 package org.apache.syncope.client.enduser.navigation;
 
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.enduser.pages.BaseEnduserWebPage;
+import org.apache.syncope.client.enduser.pages.BaseExtPage;
+import org.apache.syncope.client.ui.commons.annotations.ExtPage;
+import org.apache.wicket.AttributeModifier;
+import org.apache.wicket.Component;
+import org.apache.wicket.Page;
+import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+import org.apache.wicket.behavior.Behavior;
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
 import org.apache.wicket.markup.html.panel.Panel;
 
 public class Navbar extends Panel {
 
     private static final long serialVersionUID = 1323251762654401168L;
 
-    public Navbar(final String id) {
+    private final ListView<Class<? extends BaseExtPage>> extPages;
+
+    public Navbar(final String id, final List<Class<? extends BaseExtPage>> extPageClasses) {
         super(id);
         setOutputMarkupId(true);
+
+        extPages = new ListView<Class<? extends BaseExtPage>>("extPages", extPageClasses) {
+
+            private static final long serialVersionUID = 4949588177564901031L;
+
+            @Override
+            protected void populateItem(final ListItem<Class<? extends BaseExtPage>> item) {
+                WebMarkupContainer containingLI = new WebMarkupContainer("extPageLI");
+                item.add(containingLI);
+                if (item.getModelObject().equals(BaseEnduserWebPage.class)) {
+                    containingLI.add(new Behavior() {
+
+                        private static final long serialVersionUID = 1469628524240283489L;
+
+                        @Override
+                        public void onComponentTag(final Component component, final ComponentTag tag) {
+                            tag.put("class", "active");
+                        }
+                    });
+                }
+
+                ExtPage ann = item.getModelObject().getAnnotation(ExtPage.class);
+
+                BookmarkablePageLink<Page> link = new BookmarkablePageLink<>("extPage", item.getModelObject());
+                link.add(new Label("extPageLabel", ann.label()));
+                if (StringUtils.isNotBlank(ann.listEntitlement())) {
+                    MetaDataRoleAuthorizationStrategy.authorize(link, WebPage.RENDER, ann.listEntitlement());
+                }
+                containingLI.add(link);
+
+                Label extPageIcon = new Label("extPageIcon");
+                extPageIcon.add(new AttributeModifier("class", "fa " + ann.icon()));
+                link.add(extPageIcon);
+            }
+        };
+        extPages.setOutputMarkupId(true);
+        extPages.setVisible(!extPageClasses.isEmpty());
+        add(extPages);
+    }
+
+    public ListView<Class<? extends BaseExtPage>> getExtPages() {
+        return extPages;
     }
 
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/BaseEnduserWebPage.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/BaseEnduserWebPage.java
index 6a1b0b6..bc7edc4 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/BaseEnduserWebPage.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/BaseEnduserWebPage.java
@@ -18,9 +18,14 @@
  */
 package org.apache.syncope.client.enduser.pages;
 
+import org.apache.syncope.client.enduser.init.ClassPathScanImplementationLookup;
 import org.apache.syncope.client.enduser.navigation.Navbar;
 import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
+import org.apache.wicket.Component;
+import org.apache.wicket.behavior.Behavior;
+import org.apache.wicket.markup.ComponentTag;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.spring.injection.annot.SpringBean;
 
 public class BaseEnduserWebPage extends BaseWebPage {
 
@@ -28,6 +33,9 @@ public class BaseEnduserWebPage extends BaseWebPage {
 
     protected final Navbar navbar;
 
+    @SpringBean
+    protected ClassPathScanImplementationLookup lookup;
+
     public BaseEnduserWebPage() {
         this(null);
 
@@ -37,8 +45,23 @@ public class BaseEnduserWebPage extends BaseWebPage {
     public BaseEnduserWebPage(final PageParameters parameters) {
         super(parameters);
 
-        navbar = new Navbar("navbar");
+        navbar = new Navbar("navbar", lookup.getExtPageClasses());
         body.add(navbar);
+
+        if (getPage() instanceof BaseExtPage) {
+            navbar.getExtPages().add(new Behavior() {
+
+                private static final long serialVersionUID = 1469628524240283489L;
+
+                @Override
+                public void onComponentTag(final Component component, final ComponentTag tag) {
+                    tag.put("class", "treeview-menu menu-open");
+                    tag.put("style", "display: block;");
+                }
+
+            });
+
+        }
     }
 
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/navigation/Navbar.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/BaseExtPage.java
similarity index 68%
copy from client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/navigation/Navbar.java
copy to client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/BaseExtPage.java
index a0bd90a..ace1d2f 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/navigation/Navbar.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/BaseExtPage.java
@@ -16,17 +16,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.enduser.navigation;
+package org.apache.syncope.client.enduser.pages;
 
-import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
 
-public class Navbar extends Panel {
+public abstract class BaseExtPage extends BaseEnduserWebPage {
 
-    private static final long serialVersionUID = 1323251762654401168L;
+    private static final long serialVersionUID = 4627828052717627159L;
 
-    public Navbar(final String id) {
-        super(id);
-        setOutputMarkupId(true);
+    public BaseExtPage() {
+        super();
     }
 
+    public BaseExtPage(final PageParameters parameters) {
+        super(parameters);
+    }
 }
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeWebApplication.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeWebApplication.properties
index eb79cb9..aaf3d62 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeWebApplication.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeWebApplication.properties
@@ -50,6 +50,7 @@ key=Key
 types=Types
 self-registration-link=Self Registration
 self-pwd-reset-link=Self Password Reset
+user-requests-link=User Requests
 realms=Realms
 roles=Roles
 policies=Policies
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/navigation/Navbar.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/navigation/Navbar.html
index 2ed32ca..cced635 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/navigation/Navbar.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/navigation/Navbar.html
@@ -45,7 +45,7 @@ under the License.
           </a>
         </div>
 
-        <div id="navbar" class="navbar-collapse collapse in" aria-expanded="true">
+        <div id="navbarDetails" class="navbar-collapse collapse in" aria-expanded="true">
           <ul class="nav navbar-nav">
             <li class="active">
               <a href="#"
@@ -55,31 +55,11 @@ under the License.
                 <span class="sr-only">(current)</span>
               </a>
             </li>
-
-            <li class="dropdown">
-              <a href="#" class="dropdown-toggle" 
-                 data-toggle="dropdown" 
-                 role="button" 
-                 aria-label="'User requests' menu element"
-                 tabindex="2"
-                 aria-haspopup="true" 
-                 aria-expanded="false">
-                User Requests
-                <span class="caret"></span>
-              </a>
-              <ul class="dropdown-menu">
-                <li>
-                  <a href="#" aria-label="'Start new requests' menu element" tabindex="3">
-                    Start new requests
-                  </a>
-                </li>
-                <li>
-                  <a href="#" aria-label="'View ongoing requests' menu element" tabindex="4">
-                    View ongoing requests
-                  </a>
-                </li>
-              </ul>
-            </li>
+            <ul wicket:id="extPages" class="nav navbar-nav">
+              <li wicket:id="extPageLI" class="active">
+                <a href="#" wicket:id="extPage"><i wicket:id="extPageIcon"></i><span wicket:id="extPageLabel"/></a>
+              </li>
+            </ul>
           </ul>
         </div>
       </div>
diff --git a/ext/camel/client-console/src/main/java/org/apache/syncope/client/console/pages/CamelRoutes.java b/ext/camel/client-console/src/main/java/org/apache/syncope/client/console/pages/CamelRoutes.java
index de928b5..3f6150d 100644
--- a/ext/camel/client-console/src/main/java/org/apache/syncope/client/console/pages/CamelRoutes.java
+++ b/ext/camel/client-console/src/main/java/org/apache/syncope/client/console/pages/CamelRoutes.java
@@ -27,7 +27,7 @@ import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.CamelEntitlement;
 import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
 import org.apache.wicket.markup.html.WebMarkupContainer;
-import org.apache.syncope.client.console.annotations.ExtPage;
+import org.apache.syncope.client.ui.commons.annotations.ExtPage;
 import org.apache.syncope.client.console.panels.CamelRoutesDirectoryPanel;
 import org.apache.wicket.extensions.markup.html.tabs.AbstractTab;
 import org.apache.wicket.extensions.markup.html.tabs.ITab;
diff --git a/ext/camel/client-console/src/main/java/org/apache/syncope/client/console/widgets/CamelMetricsWidget.java b/ext/camel/client-console/src/main/java/org/apache/syncope/client/console/widgets/CamelMetricsWidget.java
index 1ccc74d..e948bfc 100644
--- a/ext/camel/client-console/src/main/java/org/apache/syncope/client/console/widgets/CamelMetricsWidget.java
+++ b/ext/camel/client-console/src/main/java/org/apache/syncope/client/console/widgets/CamelMetricsWidget.java
@@ -21,7 +21,7 @@ package org.apache.syncope.client.console.widgets;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
-import org.apache.syncope.client.console.annotations.ExtWidget;
+import org.apache.syncope.client.ui.commons.annotations.ExtWidget;
 import org.apache.syncope.client.console.chartjs.Bar;
 import org.apache.syncope.client.console.chartjs.BarDataSet;
 import org.apache.syncope.client.console.chartjs.ChartJSPanel;
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 1eb40c6..7e972ab 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
@@ -19,7 +19,7 @@
 package org.apache.syncope.client.console.pages;
 
 import org.apache.syncope.client.console.BookmarkablePageLinkBuilder;
-import org.apache.syncope.client.console.annotations.ExtPage;
+import org.apache.syncope.client.ui.commons.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.BpmnProcess;
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/widgets/UserRequestFormsWidget.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/widgets/UserRequestFormsWidget.java
index b445dd9..d8d1319 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/widgets/UserRequestFormsWidget.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/widgets/UserRequestFormsWidget.java
@@ -25,7 +25,7 @@ import java.util.Collections;
 import java.util.List;
 import org.apache.syncope.client.console.BookmarkablePageLinkBuilder;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
-import org.apache.syncope.client.console.annotations.ExtWidget;
+import org.apache.syncope.client.ui.commons.annotations.ExtWidget;
 import org.apache.syncope.client.console.pages.UserRequests;
 import org.apache.syncope.client.console.rest.UserRequestRestClient;
 import org.apache.syncope.client.console.wicket.ajax.IndicatorAjaxTimerBehavior;
diff --git a/ext/flowable/client-enduser/pom.xml b/ext/flowable/client-enduser/pom.xml
index a1c252a..83a77cb 100644
--- a/ext/flowable/client-enduser/pom.xml
+++ b/ext/flowable/client-enduser/pom.xml
@@ -50,8 +50,8 @@ under the License.
     </dependency>
     
     <dependency>
-      <groupId>org.apache.syncope.client</groupId>
-      <artifactId>syncope-client-enduser</artifactId>
+      <groupId>org.apache.syncope.client.idrepo</groupId>
+      <artifactId>syncope-client-idrepo-enduser</artifactId>
       <version>${project.version}</version>
     </dependency>
  
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/BaseEnduserWebPage.java b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/markup/html/form/BpmnProcessesAjaxPanel.java
similarity index 52%
copy from client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/BaseEnduserWebPage.java
copy to ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/markup/html/form/BpmnProcessesAjaxPanel.java
index 6a1b0b6..7ad531d 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/BaseEnduserWebPage.java
+++ b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/markup/html/form/BpmnProcessesAjaxPanel.java
@@ -16,29 +16,27 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.enduser.pages;
+package org.apache.syncope.client.enduser.markup.html.form;
 
-import org.apache.syncope.client.enduser.navigation.Navbar;
-import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
-import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
+import org.apache.wicket.behavior.Behavior;
+import org.apache.wicket.model.IModel;
 
-public class BaseEnduserWebPage extends BaseWebPage {
+public class BpmnProcessesAjaxPanel extends AjaxTextFieldPanel {
 
-    private static final long serialVersionUID = 5760583420031293480L;
+    private static final long serialVersionUID = 9082907182704884397L;
 
-    protected final Navbar navbar;
-
-    public BaseEnduserWebPage() {
-        this(null);
-
-        body.add(navbar);
+    public BpmnProcessesAjaxPanel(
+            final String id, final String name, final IModel<String> model) {
+        this(id, name, model, null);
     }
 
-    public BaseEnduserWebPage(final PageParameters parameters) {
-        super(parameters);
-
-        navbar = new Navbar("navbar");
-        body.add(navbar);
+    public BpmnProcessesAjaxPanel(
+            final String id, final String name, final IModel<String> model, final Behavior behavior) {
+        super(id, name, model, false);
+        if (behavior != null) {
+            field.add(behavior);
+        }
     }
 
 }
diff --git a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/pages/Flowable.java b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/pages/Flowable.java
new file mode 100644
index 0000000..2a33195
--- /dev/null
+++ b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/pages/Flowable.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.client.enduser.pages;
+
+import java.util.stream.Collectors;
+import org.apache.syncope.client.enduser.markup.html.form.BpmnProcessesAjaxPanel;
+import org.apache.syncope.client.enduser.rest.BpmnProcessRestClient;
+import org.apache.syncope.client.enduser.rest.UserRequestRestClient;
+import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.ui.commons.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior;
+import org.apache.syncope.client.ui.commons.annotations.ExtPage;
+import org.apache.syncope.common.lib.types.FlowableEntitlement;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+
+@ExtPage(label = "User Requests", icon = "fa-briefcase",
+        listEntitlement = FlowableEntitlement.BPMN_PROCESS_GET, priority = 200)
+public class Flowable extends BaseExtPage {
+
+    private static final long serialVersionUID = -8781434495150074529L;
+
+    private final Model<String> bpmnProcessModel = new Model<>();
+
+    private final BpmnProcessRestClient restClient = new BpmnProcessRestClient();
+
+    private final UserRequestRestClient userRequestRestClient = new UserRequestRestClient();
+
+    public Flowable(final PageParameters parameters) {
+        super(parameters);
+
+        final WebMarkupContainer content = new WebMarkupContainer("content");
+        content.setOutputMarkupId(true);
+        // autocomplete select with bpmnProcesses
+        final BpmnProcessesAjaxPanel bpmnProcesses =
+                new BpmnProcessesAjaxPanel("bpmnProcesses", "bpmnProcesses", bpmnProcessModel,
+                        new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+                    private static final long serialVersionUID = -1107858522700306810L;
+
+                    @Override
+                    protected void onUpdate(final AjaxRequestTarget target) {
+                        userRequestRestClient.start(bpmnProcessModel.getObject(), null);
+                    }
+                });
+        bpmnProcesses.setChoices(restClient.getDefinitions().stream()
+                .filter(definition -> !definition.isUserWorkflow())
+                .map(definition -> definition.getKey()).collect(Collectors.toList()));
+        content.add(bpmnProcesses);
+
+        body.add(content);
+    }
+}
diff --git a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/BpmnProcessList.java b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/BpmnProcessList.java
deleted file mode 100644
index e629754..0000000
--- a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/BpmnProcessList.java
+++ /dev/null
@@ -1,84 +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.client.enduser.resources;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.Collections;
-import java.util.List;
-import java.util.stream.Collectors;
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import org.apache.syncope.client.enduser.SyncopeEnduserSession;
-import org.apache.syncope.client.enduser.annotations.Resource;
-import org.apache.syncope.common.lib.to.BpmnProcess;
-import org.apache.syncope.common.rest.api.service.BpmnProcessService;
-import org.apache.wicket.request.resource.AbstractResource;
-import org.apache.wicket.request.resource.IResource;
-
-@Resource(key = "bpmnProcessesList", path = "/api/flowable/bpmnProcesses/")
-public class BpmnProcessList extends BaseResource {
-
-    private static final long serialVersionUID = 7273151109078469253L;
-
-    @Override
-    protected ResourceResponse newResourceResponse(final IResource.Attributes attributes) {
-        LOG.debug("List available Flowable BPMN processes definitions [{}] useful to start User Requests");
-
-        ResourceResponse response = new AbstractResource.ResourceResponse();
-        response.setContentType(MediaType.APPLICATION_JSON);
-        try {
-            HttpServletRequest request = (HttpServletRequest) attributes.getRequest().getContainerRequest();
-            if (!xsrfCheck(request)) {
-                LOG.error("XSRF TOKEN does not match");
-                response.setError(Response.Status.BAD_REQUEST.getStatusCode(), "XSRF TOKEN does not match");
-                return response;
-            }
-
-            final List<BpmnProcess> bpmnProcesses = SyncopeEnduserSession.get().
-                    getService(BpmnProcessService.class).list();
-
-            response.setWriteCallback(new AbstractResource.WriteCallback() {
-
-                @Override
-                public void writeData(final IResource.Attributes attributes) throws IOException {
-                    // retain also not userWorkflow processes
-                    attributes.getResponse().write(MAPPER.writeValueAsString(bpmnProcesses == null
-                            ? Collections.<BpmnProcess>emptyList()
-                            : bpmnProcesses.stream().filter(bpmnProcess -> !bpmnProcess.isUserWorkflow()).collect(
-                                    Collectors.toList())));
-                }
-            });
-
-            response.setContentType(MediaType.APPLICATION_JSON);
-            response.setTextEncoding(StandardCharsets.UTF_8.name());
-            response.setStatusCode(Response.Status.OK.getStatusCode());
-        } catch (Exception e) {
-            LOG.error("Error retrieving BPMN processes", e);
-            response.setError(Response.Status.BAD_REQUEST.getStatusCode(), new StringBuilder()
-                    .append("ErrorMessage{{ ")
-                    .append(e.getMessage())
-                    .append(" }}")
-                    .toString());
-        }
-
-        return response;
-    }
-}
diff --git a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestCancelResource.java b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestCancelResource.java
deleted file mode 100644
index 3e1116b..0000000
--- a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestCancelResource.java
+++ /dev/null
@@ -1,99 +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.client.enduser.resources;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.HttpMethod;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import org.apache.syncope.client.enduser.SyncopeEnduserSession;
-import org.apache.syncope.client.enduser.annotations.Resource;
-import org.apache.syncope.common.rest.api.service.UserRequestService;
-import org.apache.wicket.request.IRequestParameters;
-import org.apache.wicket.request.mapper.parameter.PageParameters;
-import org.apache.wicket.request.resource.AbstractResource;
-import org.apache.wicket.request.resource.IResource;
-import org.apache.wicket.util.string.StringValue;
-
-@Resource(key = "userRequestCancel", path = "/api/flowable/userRequests/${executionId}")
-public class UserRequestCancelResource extends BaseResource {
-
-    private static final long serialVersionUID = 7273151109078469253L;
-
-    @Override
-    protected ResourceResponse newResourceResponse(final IResource.Attributes attributes) {
-
-        ResourceResponse response = new AbstractResource.ResourceResponse();
-        response.setContentType(MediaType.APPLICATION_JSON);
-        StringValue executionId;
-        try {
-            HttpServletRequest request = (HttpServletRequest) attributes.getRequest().getContainerRequest();
-            if (!xsrfCheck(request)) {
-                LOG.error("XSRF TOKEN does not match");
-                response.setError(Response.Status.BAD_REQUEST.getStatusCode(), "XSRF TOKEN does not match");
-                return response;
-            }
-
-            if (!HttpMethod.DELETE.equals(request.getMethod())) {
-                throw new UnsupportedOperationException("Unsupported operation, only DELETE allowed");
-            }
-
-            PageParameters parameters = attributes.getParameters();
-            executionId = parameters.get("executionId");
-            IRequestParameters requestParameters = attributes.getRequest().getQueryParameters();
-            StringValue reason = requestParameters.getParameterValue("reason");
-            LOG.debug("Cancel Flowable User Request with execution id [{}] for user [{}] with reason [{}]", executionId,
-                    SyncopeEnduserSession.get().getSelfTO().getUsername(), reason);
-            if (executionId.isEmpty()) {
-                throw new IllegalArgumentException("Empty executionId, please provide a value");
-            }
-
-            SyncopeEnduserSession.get().getService(UserRequestService.class).cancel(executionId.toString(),
-                    reason.toString());
-
-            final String outcomeMessage = String.format(
-                    "User Request with execution id [%s] successfully canceled for User [%s]", executionId.
-                            toString(), SyncopeEnduserSession.get().getSelfTO().getUsername());
-
-            response.setWriteCallback(new AbstractResource.WriteCallback() {
-
-                @Override
-                public void writeData(final IResource.Attributes attributes) throws IOException {
-                    attributes.getResponse().write(outcomeMessage);
-                }
-            });
-
-            response.setContentType(MediaType.APPLICATION_JSON);
-            response.setTextEncoding(StandardCharsets.UTF_8.name());
-            response.setStatusCode(Response.Status.OK.getStatusCode());
-        } catch (Exception e) {
-            LOG.error("Error cancelling User Request for [{}]", SyncopeEnduserSession.get().getSelfTO().getUsername(),
-                    e);
-            response.setError(Response.Status.BAD_REQUEST.getStatusCode(), new StringBuilder()
-                    .append("ErrorMessage{{ ")
-                    .append(e.getMessage())
-                    .append(" }}")
-                    .toString());
-        }
-
-        return response;
-    }
-}
diff --git a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestFormClaimResource.java b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestFormClaimResource.java
deleted file mode 100644
index f7b9420..0000000
--- a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestFormClaimResource.java
+++ /dev/null
@@ -1,88 +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.client.enduser.resources;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import org.apache.syncope.client.enduser.SyncopeEnduserSession;
-import org.apache.syncope.client.enduser.annotations.Resource;
-import org.apache.syncope.common.lib.to.UserRequestForm;
-import org.apache.syncope.common.rest.api.service.UserRequestService;
-import org.apache.wicket.request.mapper.parameter.PageParameters;
-import org.apache.wicket.request.resource.AbstractResource;
-import org.apache.wicket.request.resource.IResource;
-import org.apache.wicket.util.string.StringValue;
-
-@Resource(key = "userRequestCancelByUsername", path = "/api/flowable/userRequests/forms/${taskId}/claim")
-public class UserRequestFormClaimResource extends BaseResource {
-
-    private static final long serialVersionUID = 7273151109078469253L;
-
-    @Override
-    protected ResourceResponse newResourceResponse(final IResource.Attributes attributes) {
-
-        ResourceResponse response = new AbstractResource.ResourceResponse();
-        response.setContentType(MediaType.APPLICATION_JSON);
-        StringValue taskId;
-        try {
-            HttpServletRequest request = (HttpServletRequest) attributes.getRequest().getContainerRequest();
-            if (!xsrfCheck(request)) {
-                LOG.error("XSRF TOKEN does not match");
-                response.setError(Response.Status.BAD_REQUEST.getStatusCode(), "XSRF TOKEN does not match");
-                return response;
-            }
-
-            PageParameters parameters = attributes.getParameters();
-            taskId = parameters.get("taskId");
-            LOG.debug("Claim Flowable User Request Form with task id [{}] for user [{}] with reason [{}]", taskId,
-                    SyncopeEnduserSession.get().getSelfTO().getUsername());
-            if (taskId.isEmpty()) {
-                throw new IllegalArgumentException("Empty taskId, please provide a value");
-            }
-            UserRequestForm requestForm = SyncopeEnduserSession.get().getService(UserRequestService.class).claimForm(
-                    taskId.toString());
-
-            response.setWriteCallback(new AbstractResource.WriteCallback() {
-
-                @Override
-                public void writeData(final IResource.Attributes attributes) throws IOException {
-                    attributes.getResponse().write(MAPPER.writeValueAsString(requestForm));
-                }
-            });
-
-            response.setContentType(MediaType.APPLICATION_JSON);
-            response.setTextEncoding(StandardCharsets.UTF_8.name());
-            response.setStatusCode(Response.Status.OK.getStatusCode());
-        } catch (Exception e) {
-            LOG.
-                    error("Error claiming User Request Form for [{}]", SyncopeEnduserSession.get().getSelfTO().
-                            getUsername(), e);
-            response.setError(Response.Status.BAD_REQUEST.getStatusCode(), new StringBuilder()
-                    .append("ErrorMessage{{ ")
-                    .append(e.getMessage())
-                    .append(" }}")
-                    .toString());
-        }
-
-        return response;
-    }
-}
diff --git a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsFormsResource.java b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsFormsResource.java
deleted file mode 100644
index c51027b..0000000
--- a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsFormsResource.java
+++ /dev/null
@@ -1,162 +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.client.enduser.resources;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.text.ParseException;
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.HttpMethod;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.time.FastDateFormat;
-import org.apache.syncope.client.enduser.SyncopeEnduserSession;
-import org.apache.syncope.client.enduser.annotations.Resource;
-import org.apache.syncope.common.lib.to.PagedResult;
-import org.apache.syncope.common.lib.to.UserRequestForm;
-import org.apache.syncope.common.lib.types.UserRequestFormPropertyType;
-import org.apache.syncope.common.rest.api.beans.UserRequestFormQuery;
-import org.apache.syncope.common.rest.api.service.UserRequestService;
-import org.apache.wicket.request.IRequestParameters;
-import org.apache.wicket.request.resource.AbstractResource;
-import org.apache.wicket.request.resource.IResource;
-import org.apache.wicket.util.string.StringValue;
-
-@Resource(key = "userRequestsForms", path = "/api/flowable/userRequests/forms")
-public class UserRequestsFormsResource extends BaseResource {
-
-    private static final long serialVersionUID = 7273151109078469253L;
-
-    @Override
-    protected ResourceResponse newResourceResponse(final IResource.Attributes attributes) {
-
-        ResourceResponse response = new AbstractResource.ResourceResponse();
-        response.setContentType(MediaType.APPLICATION_JSON);
-        response.setTextEncoding(StandardCharsets.UTF_8.name());
-        StringValue username = StringValue.valueOf(SyncopeEnduserSession.get().getSelfTO().getUsername());
-        try {
-            HttpServletRequest request = (HttpServletRequest) attributes.getRequest().getContainerRequest();
-            if (!xsrfCheck(request)) {
-                LOG.error("XSRF TOKEN does not match");
-                response.setError(Response.Status.BAD_REQUEST.getStatusCode(), "XSRF TOKEN does not match");
-                return response;
-            }
-
-            switch (request.getMethod()) {
-                case HttpMethod.GET:
-                    IRequestParameters requestParameters = attributes.getRequest().getQueryParameters();
-                    StringValue page = requestParameters.getParameterValue("page");
-                    StringValue size = requestParameters.getParameterValue("size");
-                    LOG.debug("List available Flowable User Requests Forms by user [{}]", username);
-                    final PagedResult<UserRequestForm> userRequestForms = SyncopeEnduserSession.get().
-                            getService(UserRequestService.class).getForms(
-                            new UserRequestFormQuery.Builder()
-                                    .user(username.isEmpty()
-                                            ? SyncopeEnduserSession.get().getSelfTO().getUsername()
-                                            : username.toString())
-                                    .page(page.isEmpty()
-                                            ? 1
-                                            : Integer.parseInt(page.toString()))
-                                    .size(size.isEmpty()
-                                            ? 10
-                                            : Integer.parseInt(size.toString())).build());
-
-                    // Date -> millis conversion for Date properties of the form
-                    userRequestForms.getResult().stream().forEach(form
-                            -> form.getProperties().stream()
-                                    .filter(prop -> UserRequestFormPropertyType.Date == prop.getType()
-                                    && StringUtils.isNotBlank(prop.getValue()))
-                                    .forEach(prop -> {
-                                        try {
-                                            prop.setValue(String.valueOf(FastDateFormat.getInstance(prop.
-                                                    getDatePattern()).parse(prop.getValue()).getTime()));
-                                        } catch (ParseException e) {
-                                            LOG.error("Unable to parse date", e);
-                                        }
-                                    }));
-
-                    response.setWriteCallback(new AbstractResource.WriteCallback() {
-
-                        @Override
-                        public void writeData(final IResource.Attributes attributes) throws IOException {
-                            attributes.getResponse().write(MAPPER.writeValueAsString(userRequestForms));
-                        }
-                    });
-                    break;
-                case HttpMethod.POST:
-                    UserRequestForm requestForm = MAPPER.
-                            readValue(request.getReader().readLine(), UserRequestForm.class);
-                    if (requestForm == null) {
-                        throw new IllegalArgumentException("Empty userRequestForm, please provide a valid one");
-                    }
-
-                    UserRequestService userRequestService = SyncopeEnduserSession.get().getService(
-                            UserRequestService.class);
-                    // 1. claim form as logged user
-                    userRequestService.claimForm(requestForm.getTaskId());
-                    // millis -> Date conversion for Date properties of the form
-                    requestForm.getProperties().stream()
-                            .filter(prop -> UserRequestFormPropertyType.Date == prop.getType()
-                            && StringUtils.isNotBlank(prop.getValue()))
-                            .forEach(prop -> {
-                                try {
-                                    prop.setValue(FastDateFormat.getInstance(prop.getDatePattern()).format(Long.valueOf(
-                                            prop.getValue())));
-                                } catch (NumberFormatException e) {
-                                    LOG.error("Unable to format date", e);
-                                }
-                            });
-                    // 2. Submit form
-                    LOG.debug("Submit Flowable User Request Form for user [{}]", requestForm.getUsername());
-                    userRequestService.submitForm(requestForm);
-
-                    response.setStatusCode(Response.Status.NO_CONTENT.getStatusCode());
-                    response.setWriteCallback(new AbstractResource.WriteCallback() {
-
-                        @Override
-                        public void writeData(final IResource.Attributes attributes) throws IOException {
-                            // DO NOTHING
-                        }
-                    });
-                    break;
-                default:
-                    LOG.error("Method [{}] not supported", request.getMethod());
-                    response.setError(Response.Status.BAD_REQUEST.getStatusCode(), new StringBuilder()
-                            .append("ErrorMessage{{ ")
-                            .append("Method not supported")
-                            .append(" }}")
-                            .toString());
-                    break;
-            }
-            response.setContentType(MediaType.APPLICATION_JSON);
-            response.setTextEncoding(StandardCharsets.UTF_8.name());
-            response.setStatusCode(Response.Status.OK.getStatusCode());
-        } catch (Exception e) {
-            LOG.error("Error dealing with forms of user [{}]", username, e);
-            response.setError(Response.Status.BAD_REQUEST.getStatusCode(), new StringBuilder()
-                    .append("ErrorMessage{{ ")
-                    .append(e.getMessage())
-                    .append(" }}")
-                    .toString());
-        }
-
-        return response;
-    }
-}
diff --git a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsResource.java b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsResource.java
deleted file mode 100644
index 812e408..0000000
--- a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsResource.java
+++ /dev/null
@@ -1,130 +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.client.enduser.resources;
-
-import static org.apache.syncope.client.enduser.resources.BaseResource.LOG;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.HttpMethod;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import org.apache.syncope.client.enduser.SyncopeEnduserSession;
-import org.apache.syncope.client.enduser.annotations.Resource;
-import org.apache.syncope.common.lib.to.PagedResult;
-import org.apache.syncope.common.lib.to.UserRequest;
-import org.apache.syncope.common.rest.api.beans.UserRequestQuery;
-import org.apache.syncope.common.rest.api.service.UserRequestService;
-import org.apache.wicket.request.IRequestParameters;
-import org.apache.wicket.request.resource.AbstractResource;
-import org.apache.wicket.request.resource.IResource;
-import org.apache.wicket.util.string.StringValue;
-
-@Resource(key = "userRequests", path = "/api/flowable/userRequests")
-public class UserRequestsResource extends BaseResource {
-
-    private static final long serialVersionUID = 7273151109078469253L;
-
-    @Override
-    protected ResourceResponse newResourceResponse(final IResource.Attributes attributes) {
-
-        ResourceResponse response = new AbstractResource.ResourceResponse();
-        response.setContentType(MediaType.APPLICATION_JSON);
-        StringValue username = StringValue.valueOf(SyncopeEnduserSession.get().getSelfTO().getUsername());
-        try {
-            HttpServletRequest request = (HttpServletRequest) attributes.getRequest().getContainerRequest();
-            if (!xsrfCheck(request)) {
-                LOG.error("XSRF TOKEN does not match");
-                response.setError(Response.Status.BAD_REQUEST.getStatusCode(), "XSRF TOKEN does not match");
-                return response;
-            }
-
-            IRequestParameters requestParameters = attributes.getRequest().getQueryParameters();
-            switch (request.getMethod()) {
-                case HttpMethod.DELETE:
-                    StringValue executionId = requestParameters.getParameterValue("executionId");
-                    StringValue reason = requestParameters.getParameterValue("reason");
-                    LOG.debug("Cancel Flowable User Request with execution id [{}] for user [{}] with reason [{}]",
-                            executionId, SyncopeEnduserSession.get().getSelfTO().getUsername(), reason);
-                    if (executionId.isEmpty()) {
-                        throw new IllegalArgumentException("Empty executionId, please provide a value");
-                    }
-                    SyncopeEnduserSession.get().getService(UserRequestService.class).cancel(executionId.toString(),
-                            reason.toString());
-                    response.setStatusCode(Response.Status.NO_CONTENT.getStatusCode());
-                    response.setWriteCallback(new AbstractResource.WriteCallback() {
-
-                        @Override
-                        public void writeData(final IResource.Attributes attributes) throws IOException {
-                            // DO NOTHING
-                        }
-                    });
-                    break;
-
-                case HttpMethod.GET:
-                    StringValue page = requestParameters.getParameterValue("page");
-                    StringValue size = requestParameters.getParameterValue("size");
-                    LOG.debug("List available Flowable User Requests for user [{}]", username);
-                    final PagedResult<UserRequest> userRequests = SyncopeEnduserSession.get().
-                            getService(UserRequestService.class).list(
-                            new UserRequestQuery.Builder()
-                                    .user(username.isEmpty()
-                                            ? SyncopeEnduserSession.get().getSelfTO().getUsername()
-                                            : username.toString())
-                                    .page(page.isEmpty()
-                                            ? 1
-                                            : Integer.parseInt(
-                                                    page.toString()))
-                                    .size(size.isEmpty()
-                                            ? 10
-                                            : Integer.parseInt(
-                                                    size.toString())).build());
-                    response.setWriteCallback(new AbstractResource.WriteCallback() {
-
-                        @Override
-                        public void writeData(final IResource.Attributes attributes) throws IOException {
-                            attributes.getResponse().write(MAPPER.writeValueAsString(userRequests));
-                        }
-                    });
-                    break;
-                default:
-                    LOG.error("Method [{}] not supported", request.getMethod());
-                    response.setError(Response.Status.BAD_REQUEST.getStatusCode(), new StringBuilder()
-                            .append("ErrorMessage{{ ")
-                            .append("Method not supported")
-                            .append(" }}")
-                            .toString());
-                    break;
-            }
-            response.setContentType(MediaType.APPLICATION_JSON);
-            response.setTextEncoding(StandardCharsets.UTF_8.name());
-            response.setStatusCode(Response.Status.OK.getStatusCode());
-        } catch (Exception e) {
-            LOG.error("Error retrieving user requests for [{}]", username, e);
-            response.setError(Response.Status.BAD_REQUEST.getStatusCode(), new StringBuilder()
-                    .append("ErrorMessage{{ ")
-                    .append(e.getMessage())
-                    .append(" }}")
-                    .toString());
-        }
-
-        return response;
-    }
-}
diff --git a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsStartResource.java b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsStartResource.java
deleted file mode 100644
index 4c65768..0000000
--- a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsStartResource.java
+++ /dev/null
@@ -1,86 +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.client.enduser.resources;
-
-import static org.apache.syncope.client.enduser.resources.BaseResource.LOG;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import org.apache.syncope.client.enduser.SyncopeEnduserSession;
-import org.apache.syncope.client.enduser.annotations.Resource;
-import org.apache.syncope.common.rest.api.service.UserRequestService;
-import org.apache.wicket.request.mapper.parameter.PageParameters;
-import org.apache.wicket.request.resource.AbstractResource;
-import org.apache.wicket.request.resource.IResource;
-import org.apache.wicket.util.string.StringValue;
-
-@Resource(key = "userRequests", path = "/api/flowable/userRequests/start/${bpmnProcess}")
-public class UserRequestsStartResource extends BaseResource {
-
-    private static final long serialVersionUID = 7273151109078469253L;
-
-    @Override
-    protected ResourceResponse newResourceResponse(final IResource.Attributes attributes) {
-        ResourceResponse response = new AbstractResource.ResourceResponse();
-        response.setContentType(MediaType.APPLICATION_JSON);
-        StringValue bpmnProcess = null;
-        try {
-            HttpServletRequest request = (HttpServletRequest) attributes.getRequest().getContainerRequest();
-            if (!xsrfCheck(request)) {
-                LOG.error("XSRF TOKEN does not match");
-                response.setError(Response.Status.BAD_REQUEST.getStatusCode(), "XSRF TOKEN does not match");
-                return response;
-            }
-
-            PageParameters parameters = attributes.getParameters();
-            bpmnProcess = parameters.get("bpmnProcess");
-
-            LOG.debug("Start Flowable User Request from process [{}] for user [{}]",
-                    bpmnProcess, SyncopeEnduserSession.get().getSelfTO().getUsername());
-            if (bpmnProcess.isEmpty()) {
-                throw new IllegalArgumentException("Empty bpmnProcess, please provide a value");
-            }
-            SyncopeEnduserSession.get().getService(UserRequestService.class).start(bpmnProcess.toString(), null);
-            response.setStatusCode(Response.Status.NO_CONTENT.getStatusCode());
-            response.setWriteCallback(new AbstractResource.WriteCallback() {
-
-                @Override
-                public void writeData(final IResource.Attributes attributes) throws IOException {
-                    // DO NOTHING
-                }
-            });
-
-            response.setContentType(MediaType.APPLICATION_JSON);
-            response.setTextEncoding(StandardCharsets.UTF_8.name());
-            response.setStatusCode(Response.Status.OK.getStatusCode());
-        } catch (Exception e) {
-            LOG.error("Error starting user request from [{}]", bpmnProcess, e);
-            response.setError(Response.Status.BAD_REQUEST.getStatusCode(), new StringBuilder()
-                    .append("ErrorMessage{{ ")
-                    .append(e.getMessage())
-                    .append(" }}")
-                    .toString());
-        }
-
-        return response;
-    }
-}
diff --git a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/rest/BpmnProcessRestClient.java b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/rest/BpmnProcessRestClient.java
new file mode 100644
index 0000000..bb6462e
--- /dev/null
+++ b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/rest/BpmnProcessRestClient.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.client.enduser.rest;
+
+import java.io.InputStream;
+import java.util.List;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+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;
+
+public class BpmnProcessRestClient extends BaseRestClient {
+
+    private static final long serialVersionUID = 5049285686167071017L;
+
+    private BpmnProcessService getService(final MediaType mediaType) {
+        return SyncopeEnduserSession.get().getService(mediaType, BpmnProcessService.class);
+    }
+
+    public List<BpmnProcess> getDefinitions() {
+        return getService(BpmnProcessService.class).list();
+    }
+
+    public InputStream getDefinition(final MediaType mediaType, final String key) {
+        Response response = getService(mediaType).get(key);
+
+        return (InputStream) response.getEntity();
+    }
+
+    public byte[] getDiagram(final String key) {
+        BpmnProcessService service = getService(BpmnProcessService.class);
+        WebClient.client(service).accept(RESTHeaders.MEDIATYPE_IMAGE_PNG);
+        Response response = service.exportDiagram(key);
+
+        byte[] diagram;
+        try {
+            diagram = IOUtils.readBytesFromStream((InputStream) response.getEntity());
+        } catch (Exception e) {
+            LOG.error("Could not get workflow diagram", e);
+            diagram = new byte[0];
+        }
+        return diagram;
+    }
+
+}
diff --git a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/rest/UserRequestRestClient.java b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/rest/UserRequestRestClient.java
new file mode 100644
index 0000000..9a844ff
--- /dev/null
+++ b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/rest/UserRequestRestClient.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.client.enduser.rest;
+
+import java.util.List;
+import java.util.Optional;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.common.lib.to.PagedResult;
+import org.apache.syncope.common.lib.to.UserRequest;
+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.wicket.extensions.markup.html.repeater.util.SortParam;
+import org.apache.syncope.common.rest.api.service.UserRequestService;
+
+public class UserRequestRestClient extends BaseRestClient {
+
+    private static final long serialVersionUID = -4785231164900813921L;
+
+    public int countUserRequests() {
+        return getService(UserRequestService.class).
+                list(new UserRequestQuery.Builder().page(1).size(1).build()).
+                getTotalCount();
+    }
+
+    public List<UserRequest> getUserRequests(
+            final int page,
+            final int size,
+            final String username,
+            final SortParam<String> sort) {
+        return getService(UserRequestService.class).list(
+                new UserRequestQuery.Builder()
+                        .user(StringUtils.isBlank(username)
+                                ? SyncopeEnduserSession.get().getSelfTO().getUsername()
+                                : username)
+                        .page(page).size(size).build()).getResult();
+    }
+
+    public void cancelRequest(final String executionId, final String reason) {
+        getService(UserRequestService.class).cancel(executionId, reason);
+    }
+
+    public int countForms() {
+        return getService(UserRequestService.class).
+                getForms(new UserRequestFormQuery.Builder().page(1).size(1).build()).
+                getTotalCount();
+    }
+
+    public List<UserRequestForm> getForms(final int page, final int size, final SortParam<String> sort) {
+        return getService(UserRequestService.class).
+                getForms(new UserRequestFormQuery.Builder().page(page).size(size).orderBy(toOrderBy(sort)).build()).
+                getResult();
+    }
+
+    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 void submitForm(final UserRequestForm form) {
+        getService(UserRequestService.class).submitForm(form);
+    }
+
+    public void start(final String bpmnProcess, final String user) {
+        getService(UserRequestService.class).start(bpmnProcess, user);
+    }
+
+}
diff --git a/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable.html b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable.html
new file mode 100644
index 0000000..9349c18
--- /dev/null
+++ b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable.html
@@ -0,0 +1,33 @@
+<!--
+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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
+  <wicket:extend>
+    <section class="content-header">
+      <h1>&nbsp;</h1>
+      <ol class="breadcrumb">
+        <li class="active"><wicket:message key="extensions"/></li>
+        <li class="active">Flowable</li>
+      </ol>
+    </section>
+
+    <section class="content" wicket:id="content">
+      <span wicket:id="bpmnProcesses">[BPMN PROCESSES]</span>
+    </section>
+  </wicket:extend>
+</html>
\ No newline at end of file
diff --git a/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable.properties b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable.properties
new file mode 100644
index 0000000..463bfa8
--- /dev/null
+++ b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable.properties
@@ -0,0 +1,18 @@
+# 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.
+user.requests.accordion=${bpmnProcess}
+
diff --git a/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/UserRequestForms.html b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/UserRequestForms.html
new file mode 100644
index 0000000..a89d762
--- /dev/null
+++ b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/UserRequestForms.html
@@ -0,0 +1,32 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
+  <wicket:extend>
+    <section class="content-header">
+      <h1>&nbsp;</h1>
+      <ol class="breadcrumb">
+        <li class="active"><wicket:message key="extensions"/></li>
+        <li class="active">Flowable</li>
+      </ol>
+    </section>
+
+    <section class="content" wicket:id="content">
+    </section>
+  </wicket:extend>
+</html>
\ No newline at end of file
diff --git a/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/UserRequestForms.properties b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/UserRequestForms.properties
new file mode 100644
index 0000000..7e26701
--- /dev/null
+++ b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/UserRequestForms.properties
@@ -0,0 +1,17 @@
+# 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.
+xmlEditorTitle=Workflow XML Editor
diff --git a/ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/pages/OIDCClient.java b/ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/pages/OIDCClient.java
index 66bb812..d335c4b 100644
--- a/ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/pages/OIDCClient.java
+++ b/ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/pages/OIDCClient.java
@@ -22,7 +22,7 @@ import de.agilecoders.wicket.core.markup.html.bootstrap.tabs.AjaxBootstrapTabbed
 import java.util.ArrayList;
 import java.util.List;
 import org.apache.syncope.client.console.BookmarkablePageLinkBuilder;
-import org.apache.syncope.client.console.annotations.ExtPage;
+import org.apache.syncope.client.ui.commons.annotations.ExtPage;
 import org.apache.syncope.client.console.panels.OIDCProvidersDirectoryPanel;
 import org.apache.syncope.common.lib.types.OIDCClientEntitlement;
 import org.apache.wicket.extensions.markup.html.tabs.AbstractTab;
diff --git a/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/pages/SAML2SP.java b/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/pages/SAML2SP.java
index 71ef8e5..9f020dd 100644
--- a/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/pages/SAML2SP.java
+++ b/ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/pages/SAML2SP.java
@@ -22,7 +22,7 @@ import de.agilecoders.wicket.core.markup.html.bootstrap.tabs.AjaxBootstrapTabbed
 import java.util.ArrayList;
 import java.util.List;
 import org.apache.syncope.client.console.BookmarkablePageLinkBuilder;
-import org.apache.syncope.client.console.annotations.ExtPage;
+import org.apache.syncope.client.ui.commons.annotations.ExtPage;
 import org.apache.syncope.client.console.panels.SAML2IdPsDirectoryPanel;
 import org.apache.syncope.client.console.panels.SAML2SPPanel;
 import org.apache.syncope.common.lib.types.SAML2SPEntitlement;
diff --git a/ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/pages/SCIMConfPage.java b/ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/pages/SCIMConfPage.java
index 917b076..230b66b 100644
--- a/ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/pages/SCIMConfPage.java
+++ b/ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/pages/SCIMConfPage.java
@@ -23,7 +23,7 @@ import java.io.Serializable;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.console.BookmarkablePageLinkBuilder;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
-import org.apache.syncope.client.console.annotations.ExtPage;
+import org.apache.syncope.client.ui.commons.annotations.ExtPage;
 import org.apache.syncope.client.console.commons.Constants;
 import org.apache.syncope.client.console.panels.SCIMConfPanel;
 import org.apache.syncope.client.console.rest.SCIMConfRestClient;
diff --git a/fit/console-reference/pom.xml b/fit/console-reference/pom.xml
index 398cc60..0ec716a 100644
--- a/fit/console-reference/pom.xml
+++ b/fit/console-reference/pom.xml
@@ -62,6 +62,12 @@ under the License.
     </dependency>
 
     <dependency>
+      <groupId>org.apache.syncope.ext.flowable</groupId>
+      <artifactId>syncope-ext-flowable-client-enduser</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
       <groupId>org.apache.syncope.ext.camel</groupId>
       <artifactId>syncope-ext-camel-client-console</artifactId>
       <version>${project.version}</version>
diff --git a/fit/core-reference/pom.xml b/fit/core-reference/pom.xml
index 1b045cd..244f387 100644
--- a/fit/core-reference/pom.xml
+++ b/fit/core-reference/pom.xml
@@ -137,7 +137,13 @@ under the License.
       <artifactId>syncope-ext-flowable-client-console</artifactId>
       <version>${project.version}</version>
       <scope>test</scope>
-    </dependency>  
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.ext.flowable</groupId>
+      <artifactId>syncope-ext-flowable-client-enduser</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>org.apache.syncope.ext.camel</groupId>
       <artifactId>syncope-ext-camel-rest-cxf</artifactId>
diff --git a/fit/enduser-reference/pom.xml b/fit/enduser-reference/pom.xml
index e0e6e13..084c95b 100644
--- a/fit/enduser-reference/pom.xml
+++ b/fit/enduser-reference/pom.xml
@@ -54,6 +54,11 @@ under the License.
       <artifactId>syncope-common-keymaster-client-zookeeper</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.ext.flowable</groupId>
+      <artifactId>syncope-ext-flowable-client-enduser</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     
     <!-- TEST -->
     <dependency>


Mime
View raw message