You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by an...@apache.org on 2018/12/05 11:43:18 UTC
[syncope] branch 2_1_X updated: [SYNCOPE-1381] User requests and
user request forms management in enduser UI
This is an automated email from the ASF dual-hosted git repository.
andreapatricelli pushed a commit to branch 2_1_X
in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/2_1_X by this push:
new c666f56 [SYNCOPE-1381] User requests and user request forms management in enduser UI
c666f56 is described below
commit c666f56d9ea2a693f7c074ebe07bfaeb103a8ce2
Author: Andrea Patricelli <an...@apache.org>
AuthorDate: Wed Dec 5 12:42:09 2018 +0100
[SYNCOPE-1381] User requests and user request forms management in enduser UI
---
.../client/enduser/resources/BaseResource.java | 4 +-
.../app/css/accessibility/accessibilityFont.css | 5 +-
.../app/css/accessibility/accessibilityHC.css | 57 +++++-
.../resources/META-INF/resources/app/css/app.css | 10 ++
.../META-INF/resources/app/css/editUser.css | 14 +-
.../resources/META-INF/resources/app/index.html | 8 +
.../resources/META-INF/resources/app/js/app.js | 69 ++++++++
.../resources/app/js/controllers/UserController.js | 19 +-
.../resources/app/js/directives/bpmnProcesses.js | 39 +++++
.../resources/app/js/directives/formProperty.js | 83 +++++++++
.../resources/app/js/directives/modalContent.js | 31 ++++
.../resources/app/js/directives/modalWindow.js | 43 +++++
.../resources/app/js/directives/requestForms.js | 110 ++++++++++++
.../resources/app/js/directives/requests.js | 192 +++++++++++++++++++++
.../app/js/services/bpmnProcessService.js | 38 ++++
.../app/js/services/userRequestsService.js | 78 +++++++++
.../resources/app/languages/de/static.json | 9 +-
.../resources/app/languages/en/static.json | 7 +
.../resources/app/languages/it/static.json | 7 +
.../resources/app/languages/ja/static.json | 7 +
.../resources/app/views/bpmnProcesses.html | 31 ++++
.../META-INF/resources/app/views/formProperty.html | 92 ++++++++++
.../META-INF/resources/app/views/modalWindow.html | 30 ++++
.../META-INF/resources/app/views/requestForms.html | 61 +++++++
.../META-INF/resources/app/views/requests.html | 76 ++++++++
.../app/views/templates/editUserTemplate.html | 2 +-
.../resources/app/views/user-request-forms.html | 48 ++++++
.../resources/app/views/user-requests.html | 48 ++++++
.../enduser/src/main/resources/customTemplate.json | 6 +
.../syncope/client/console/pages/Flowable.java | 4 +-
.../common/lib/types/FlowableEntitlement.java | 2 -
.../core/flowable/impl/FlowableRuntimeUtils.java | 4 +-
.../syncope/core/logic/BpmnProcessLogic.java | 2 +-
ext/flowable/pom.xml | 1 +
.../syncope-ext-flowable-client-enduser/pom.xml | 80 +++++++++
.../client/enduser/resources/BpmnProcessList.java | 87 ++++++++++
.../resources/UserRequestCancelResource.java | 101 +++++++++++
.../resources/UserRequestFormClaimResource.java | 91 ++++++++++
.../resources/UserRequestsFormsResource.java | 165 ++++++++++++++++++
.../enduser/resources/UserRequestsResource.java | 130 ++++++++++++++
.../resources/UserRequestsStartResource.java | 86 +++++++++
fit/enduser-reference/pom.xml | 6 +
.../src/main/resources/customTemplate.json | 3 +
.../src/test/resources/customTemplate.json | 6 +
44 files changed, 1974 insertions(+), 18 deletions(-)
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/BaseResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/BaseResource.java
index 2540f5d..9e7ebb5 100644
--- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/BaseResource.java
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/BaseResource.java
@@ -18,6 +18,7 @@
*/
package org.apache.syncope.client.enduser.resources;
+import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
@@ -34,7 +35,8 @@ public abstract class BaseResource extends AbstractResource {
protected static final Logger LOG = LoggerFactory.getLogger(BaseResource.class);
- protected static final ObjectMapper MAPPER = new ObjectMapper();
+ protected static final ObjectMapper MAPPER = new ObjectMapper()
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
protected final boolean xsrfCheck(final HttpServletRequest request) {
final String requestXSRFHeader = request.getHeader(SyncopeEnduserConstants.XSRF_HEADER_NAME);
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/css/accessibility/accessibilityFont.css b/client/enduser/src/main/resources/META-INF/resources/app/css/accessibility/accessibilityFont.css
index fa4310c..0f4ef7f 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/css/accessibility/accessibilityFont.css
+++ b/client/enduser/src/main/resources/META-INF/resources/app/css/accessibility/accessibilityFont.css
@@ -34,7 +34,10 @@ body {
.suggestions,
.k-notification-wrap,
.btn-secondary,
- .card-body #attribute .fa {
+div[role="tablist"] .card-header a,
+div[role="tablist"] .card-collapse,
+ul.pagination,
+.card-body #attribute .fa {
font-size: 20px;
}
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/css/accessibility/accessibilityHC.css b/client/enduser/src/main/resources/META-INF/resources/app/css/accessibility/accessibilityHC.css
index 235daaf..0d9c90d 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/css/accessibility/accessibilityHC.css
+++ b/client/enduser/src/main/resources/META-INF/resources/app/css/accessibility/accessibilityHC.css
@@ -37,7 +37,8 @@ body {
#next,
#save,
#finish,
-#resetpassword {
+#resetpassword,
+.modal-content {
background: #464646;
color: #ffffff;
}
@@ -104,6 +105,59 @@ span.k-dropdown,
color: #ffffff;
}
+.modal-content {
+ border: solid 1px #ffffff;
+}
+
+.btn-warning {
+ background-color: #70420A;
+}
+
+.btn-warning:hover {
+ background-color: #F3AD57;
+ color: #000000;
+}
+
+.btn-primary {
+ background-color: #205179;
+}
+
+.btn-primary:hover {
+ background-color: #2F78B3;
+ color: #ffffff;
+}
+
+.pagination>.disabled>a, .pagination>.disabled>a:focus, .pagination>.disabled>a:hover, .pagination>.disabled>span,
+.pagination>.disabled>span:focus, .pagination>.disabled>span:hover {
+ color: #ffffff;
+ background-color: #343434;
+ border-color: #ddd;
+}
+
+.pagination>.active>a, .pagination>.active>a:focus, .pagination>.active>a:hover, .pagination>.active>span,
+.pagination>.active>span:focus, .pagination>.active>span:hover {
+ color: #ffffff;
+ background-color: #343434;
+ border-color: #ffffff;
+}
+
+.pagination>.active>a, .pagination>.active>span {
+ color: #343434;
+ background-color: #ffffff;
+ border-color: #ffffff;
+}
+
+.pagination>li>a, .pagination>li>span {
+ color: #ffffff;
+ background-color: #343434;
+ border-color: #ffffff;
+}
+
+.pagination>li>a:focus, .pagination>li>a:hover, .pagination>li>span:focus, .pagination>li>span:hover {
+ color: #343434;
+ background-color: #ffffff;
+ border-color: #ffffff;
+}
/* Login
============================================================================= */
@@ -141,7 +195,6 @@ span.k-dropdown,
#login-container .login-btn {
color: #ffffff;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#6a9647), color-stop(100%,#48543d));
-
background: #464646;
}
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/css/app.css b/client/enduser/src/main/resources/META-INF/resources/app/css/app.css
index 494fb66..301a19b 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/css/app.css
+++ b/client/enduser/src/main/resources/META-INF/resources/app/css/app.css
@@ -97,4 +97,14 @@ under the License.
background-color: #fff;
border-color: #ccc;
}
+
+.pagination-size {
+ margin: 20px 0;
+ width: 20%;
+}
+
+.pagination-size-sm {
+ margin: 20px 0;
+ width: 12%;
+}
/* Useless with Bootstrap > 3 --> */
\ No newline at end of file
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/css/editUser.css b/client/enduser/src/main/resources/META-INF/resources/app/css/editUser.css
index 536b41a..47d64d6 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/css/editUser.css
+++ b/client/enduser/src/main/resources/META-INF/resources/app/css/editUser.css
@@ -179,7 +179,7 @@ span.k-datetimepicker{
width: 170%;
}
-#date{
+#date, #date-property{
position: relative;
display: table;
border-collapse: separate;
@@ -309,6 +309,14 @@ div[role="tablist"] {
margin-bottom: 2px;
}
+.schema-type #date-property {
+ margin-bottom: 2px;
+}
+
+.property-label{
+ margin-top: 15px
+}
+
.multivalue input{
width: calc(100% - 70px);
margin-top: 10px
@@ -636,7 +644,7 @@ div[role="tablist"] {
width:100px;
}
- #date{
+ #date, #date-property{
position: relative;
display: table;
border-collapse: separate;
@@ -686,7 +694,7 @@ div[role="tablist"] {
margin-top: 0px
}
- #date{
+ #date, #date-property{
position: relative;
display: table;
border-collapse: separate;
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/index.html b/client/enduser/src/main/resources/META-INF/resources/app/index.html
index 8819fdd..7122b50 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/index.html
+++ b/client/enduser/src/main/resources/META-INF/resources/app/index.html
@@ -124,6 +124,8 @@ under the License.
<script src="js/services/oidcProviderService.js"></script>
<script src="js/services/saml2SPService.js"></script>
<script src="js/services/oidcClientService.js"></script>
+ <script src="js/services/bpmnProcessService.js"></script>
+ <script src="js/services/userRequestsService.js"></script>
<!--controllers-->
<script src="js/controllers/HomeController.js"></script>
<script src="js/controllers/LoginController.js"></script>
@@ -142,6 +144,9 @@ under the License.
<script src="js/directives/loader.js"></script>
<script src="js/directives/captcha.js"></script>
<script src="js/directives/resources.js"></script>
+ <script src="js/directives/requests.js"></script>
+ <script src="js/directives/requestForms.js"></script>
+ <script src="js/directives/formProperty.js"></script>
<script src="js/directives/groups.js"></script>
<script src="js/directives/auxClasses.js"></script>
<script src="js/directives/validate.js"></script>
@@ -149,6 +154,9 @@ under the License.
<script src="js/directives/validateDropdown.js"></script>
<script src="js/directives/fileInput.js"></script>
<script src="js/directives/dynamicTemplateItem.js"></script>
+ <script src="js/directives/modalWindow.js"></script>
+ <script src="js/directives/bpmnProcesses.js"></script>
+ <script src="js/directives/modalContent.js"></script>
<script src="js/directives/ngEnter.js"></script>
<!--validator-->
<script src="js/validator/validationRules.js"></script>
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/app.js b/client/enduser/src/main/resources/META-INF/resources/app/js/app.js
index 9e3128b..18f0d53 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/js/app.js
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/app.js
@@ -226,6 +226,26 @@ app.config(['$stateProvider', '$urlRouterProvider', '$httpProvider', '$translate
}]
}
})
+ .state('update.userRequests', {
+ url: '/user-requests',
+ templateUrl: 'views/user-requests.html',
+ resolve: {
+ 'authenticated': ['AuthService',
+ function (AuthService) {
+ return AuthService.islogged();
+ }]
+ }
+ })
+ .state('update.userRequestForms', {
+ url: '/user-request-forms',
+ templateUrl: 'views/user-request-forms.html',
+ resolve: {
+ 'authenticated': ['AuthService',
+ function (AuthService) {
+ return AuthService.islogged();
+ }]
+ }
+ })
.state('update.finish', {
url: '/finish',
templateUrl: 'views/user-form-finish.html',
@@ -430,6 +450,9 @@ app.controller('ApplicationController', ['$scope', '$rootScope', 'InfoService',
* Wizard steps from JSON
*/
$scope.wizard = response.wizard.steps;
+ $scope.creationWizard = $scope.clone($scope.wizard);
+ delete $scope.creationWizard['userRequests'];
+ delete $scope.creationWizard['userRequestForms'];
$scope.wizardFirstStep = response.wizard.firstStep;
callback($rootScope.dynTemplate);
@@ -549,6 +572,15 @@ app.controller('ApplicationController', ['$scope', '$rootScope', 'InfoService',
}
};
+ /*
+ * Date formatters
+ */
+
+ // from timestamp
+ $rootScope.formatDate = function (timestamp) {
+ return new Date(timestamp).toLocaleString();
+ };
+
/*
|--------------------------------------------------------------------------
| Notification mgmt
@@ -650,4 +682,41 @@ app.controller('ApplicationController', ['$scope', '$rootScope', 'InfoService',
$templateCache.removeAll();
};
};
+
+ $scope.clone = function clone(obj) {
+ var copy;
+
+ // Handle the 3 simple types, and null or undefined
+ if (null == obj || "object" != typeof obj)
+ return obj;
+
+ // Handle Date
+ if (obj instanceof Date) {
+ copy = new Date();
+ copy.setTime(obj.getTime());
+ return copy;
+ }
+
+ // Handle Array
+ if (obj instanceof Array) {
+ copy = [];
+ for (var i = 0, len = obj.length; i < len; i++) {
+ copy[i] = clone(obj[i]);
+ }
+ return copy;
+ }
+
+ // Handle Object
+ if (obj instanceof Object) {
+ copy = {};
+ for (var attr in obj) {
+ if (obj.hasOwnProperty(attr))
+ copy[attr] = clone(obj[attr]);
+ }
+ return copy;
+ }
+
+ throw new Error("Unable to copy obj! Its type isn't supported.");
+ };
+
}]);
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js
index f919b08..f81747b 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js
@@ -23,10 +23,11 @@
angular.module("self").controller("UserController", ['$scope', '$rootScope', '$location', "$state",
'UserSelfService', 'SchemaService', 'RealmService', 'ResourceService', 'SecurityQuestionService',
- 'GroupService', 'AnyService', 'UserUtil', 'GenericUtil', 'ValidationExecutor', '$translate', '$filter',
+ 'GroupService', 'AnyService', 'BpmnProcessService', 'UserRequestsService', 'UserUtil', 'GenericUtil',
+ 'ValidationExecutor', '$translate', '$filter',
function ($scope, $rootScope, $location, $state, UserSelfService, SchemaService, RealmService,
- ResourceService, SecurityQuestionService, GroupService, AnyService, UserUtil, GenericUtil, ValidationExecutor,
- $translate, $filter) {
+ ResourceService, SecurityQuestionService, GroupService, AnyService, BpmnProcessService, UserRequestsService,
+ UserUtil, GenericUtil, ValidationExecutor, $translate, $filter) {
$scope.user = {};
$scope.confirmPassword = {
@@ -37,6 +38,9 @@ angular.module("self").controller("UserController", ['$scope', '$rootScope', '$l
$scope.availableRealms = [];
$scope.availableSecurityQuestions = [];
+ $scope.bpmnProcesses = [];
+ $scope.userRequests = [];
+ $scope.userRequestsForms = [];
$scope.initialSecurityQuestion = '';
$scope.captchaInput = {
@@ -334,6 +338,15 @@ angular.module("self").controller("UserController", ['$scope', '$rootScope', '$l
initUserSchemas();
// initialize available resources
initResources();
+ // initialize user requests
+// if (!$scope.createMode && $scope.wizard.userRequests) {
+// console.debug("About to init user requests data");
+// initBpmnProcesses();
+// // this call will ever get the first 10 User Requests
+// initUserRequests();
+// // this call will ever get the first 10 User Requests Forms
+// initUserRequestsForms();
+// }
};
var readUser = function () {
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/bpmnProcesses.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/bpmnProcesses.js
new file mode 100644
index 0000000..58c8e71
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/bpmnProcesses.js
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+angular.module('self').directive('bpmnProcesses', [function () {
+ return {
+ restrict: 'E',
+ templateUrl: 'views/bpmnProcesses.html',
+ scope: {
+ },
+ controller: function ($scope) {
+ $scope.bpmnProcesses = $scope.$parent.$parent.resolve.bpmnProcesses;
+ $scope.selectedProcesses = $scope.$parent.$parent.resolve.selectedProcesses;
+ $scope.toggleSelection =
+ function (bpmnProcessKey) {
+ var index = $scope.selectedProcesses.indexOf(bpmnProcessKey);
+ if (index > -1) {
+ $scope.selectedProcesses.splice(index, 1);
+ } else {
+ $scope.selectedProcesses.push(bpmnProcessKey);
+ }
+ };
+ }};
+ }]);
+
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/formProperty.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/formProperty.js
new file mode 100644
index 0000000..39ad82a
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/formProperty.js
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+'use strict';
+
+angular.module('self')
+ .directive('formProperty', ['$rootScope', function ($rootScope) {
+ return {
+ restrict: 'E',
+ templateUrl: 'views/formProperty.html',
+ scope: {
+ property: "="
+ },
+ controller: function ($scope) {
+ $scope.initAttribute = function () {
+ switch ($scope.property.type) {
+ case "Long":
+ $scope.property.value = Number($scope.property.value) || undefined;
+ break;
+ case "Enum":
+ if ($scope.property.required !== "true") {
+ $scope.property.enumValues.empty = "";
+ }
+ $scope.enumKeys = Object.keys($scope.property.enumValues);
+ $scope.property.value = $scope.property.value || $scope.property.enumValues.empty;
+ break;
+ case "Dropdown":
+ if ($scope.property.required !== "true") {
+ $scope.property.dropdownValues.empty = "";
+ }
+ $scope.dropdownKeys = Object.keys($scope.property.dropdownValues);
+ $scope.property.value = $scope.property.value || $scope.property.dropdownValues.empty;
+ break;
+ case "Date":
+ $scope.getType = function (x) {
+ return typeof x;
+ };
+ $scope.isDate = function (x) {
+ return x instanceof Date;
+ };
+ $scope.languageid = $rootScope.languages.selectedLanguage.id;
+ $scope.isDateOnly = $scope.property.datePattern.indexOf("H") === -1
+ && $scope.property.datePattern.indexOf("h") === -1;
+ $scope.languageFormat = $scope.isDateOnly
+ ? $rootScope.languages.selectedLanguage.format.replace(" HH:mm", "")
+ : $rootScope.languages.selectedLanguage.format;
+ $scope.languageCulture = $rootScope.languages.selectedLanguage.culture;
+ // read date in milliseconds
+ $scope.selectedDate = $scope.property.value === null
+ ? undefined
+ : new Date($scope.property.value * 1);
+ $scope.bindDateToModel = function (selectedDate, extendedDate) {
+ if (selectedDate) {
+ // save date in milliseconds
+ $scope.property.value = new Date(extendedDate).getTime();
+ }
+ };
+ break;
+ case "Boolean":
+ $scope.property.value = $scope.property.value === "true" ? "true" : "false";
+ break;
+
+ }
+ };
+
+ }
+ };
+ }]);
\ No newline at end of file
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/modalContent.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/modalContent.js
new file mode 100644
index 0000000..f0aed20
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/modalContent.js
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+angular.module('self').directive('modalContent', ['$compile', function ($compile) {
+ return {
+ restrict: 'E',
+ scope: {
+ modalHtml: "="
+ },
+ replace: true,
+ link: function ($scope, element, attrs) {
+ var template = $compile($scope.modalHtml)($scope);
+ element.replaceWith(template);
+ }};
+ }]);
+
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/modalWindow.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/modalWindow.js
new file mode 100644
index 0000000..3ad18e9
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/modalWindow.js
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+angular.module('self').directive('modalWindow', [function () {
+ return {
+ restrict: 'E',
+ templateUrl: 'views/modalWindow.html',
+ scope: {
+ resolve: '<',
+ close: '&',
+ dismiss: '&'
+ },
+ controller: function ($scope) {
+ $scope.init = function () {
+ // Please provide an init logic, by default do nothing
+ };
+
+ $scope.ok = function () {
+ $scope.close({$value: $scope.resolve.result});
+ };
+
+ $scope.cancel = function () {
+ $scope.dismiss({$value: 'cancel'});
+ };
+
+ }};
+ }]);
+
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/requestForms.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/requestForms.js
new file mode 100644
index 0000000..4512257
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/requestForms.js
@@ -0,0 +1,110 @@
+/*
+ * 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.
+ */
+'use strict';
+
+angular.module('self')
+ .directive('requestForms', ['UserRequestsService',
+ function (UserRequestsService) {
+ return {
+ restrict: 'E',
+ templateUrl: 'views/requestForms.html',
+ scope: {
+ user: "="
+ },
+ controller: function ($scope) {
+ // Initialization
+ $scope.query = {
+ user: $scope.user.username,
+ page: 1,
+ size: 10
+ };
+
+ var calculatePages = function () {
+ $scope.totalPages = Math.ceil($scope.forms.totalCount / $scope.query.size);
+ $scope.pages = _.range(1, $scope.totalPages + 1);
+ };
+
+ // <Pagination>
+ $scope.reloadPage = function (page, size, successMsg) {
+ // update query pagination parameters
+ $scope.query.page = page;
+ $scope.query.size = size;
+ // recalculate pages
+ calculatePages();
+ if (page < 1 || page > $scope.totalPages) {
+ return;
+ }
+ // get current page of items
+
+ $scope.getUserRequestForms($scope.query, function (forms) {
+ $scope.forms = forms;
+ $scope.$parent.showSuccess(successMsg, $scope.$parent.notification);
+ });
+ };
+ // </Pagination>
+
+ var init = function () {
+ $scope.getUserRequestForms({
+ user: $scope.user.username,
+ page: 1,
+ size: 10
+ }, function (requestsForms) {
+ $scope.forms = requestsForms;
+ calculatePages();
+ $scope.availableSizes = [{id: 1, value: 10}, {id: 2, value: 25}, {id: 3, value: 50}];
+ $scope.selectedSize = $scope.availableSizes[0];
+ });
+ };
+
+ $scope.getUserRequestForms = function (query, callback) {
+ UserRequestsService.getUserRequestForms(query).then(function (response) {
+ callback(response);
+ }, function (response) {
+ var errorMessage;
+ // parse error response
+ if (response !== undefined) {
+ errorMessage = response.split("ErrorMessage{{")[1];
+ errorMessage = errorMessage.split("}}")[0];
+ }
+ console.error("Error retrieving User Request Forms: ", errorMessage);
+ });
+ };
+
+ init();
+
+ $scope.submit = function (form) {
+ UserRequestsService.submitForm(form).then(function (response) {
+ console.debug("Form successfully submitted");
+ $scope.$parent.showSuccess("Form successfully submitted", $scope.$parent.notification);
+ $scope.reloadPage($scope.query.page, $scope.query.size, "Form successfully submitted");
+ }, function (response) {
+ var errorMessage;
+ // parse error response
+ if (response !== undefined) {
+ errorMessage = response.split("ErrorMessage{{")[1];
+ errorMessage = errorMessage.split("}}")[0];
+ }
+ console.error("Error retrieving User Request Forms: ", errorMessage);
+ $scope.$parent.showError("Error: " + (errorMessage || response), $scope.$parent.notification);
+ });
+
+ };
+ }
+ };
+ }]);
\ No newline at end of file
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/requests.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/requests.js
new file mode 100644
index 0000000..64d15a5
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/requests.js
@@ -0,0 +1,192 @@
+/*
+ * 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.
+ */
+'use strict';
+
+angular.module('self')
+ .directive('requests', ['UserRequestsService', 'BpmnProcessService', "$uibModal", "$document", '$filter',
+ '$rootScope',
+ function (UserRequestsService, BpmnProcessService, $uibModal, $document, $filter, $rootScope) {
+ return {
+ restrict: 'E',
+ templateUrl: 'views/requests.html',
+ scope: {
+ user: "="
+ },
+ controller: function ($scope) {
+ // Initialization
+ $scope.query = {
+ user: $scope.user.username,
+ page: 1,
+ size: 10
+ };
+
+ var calculatePages = function () {
+ $scope.totalPages = Math.ceil($scope.requests.totalCount / $scope.query.size);
+ $scope.pages = _.range(1, $scope.totalPages + 1);
+ };
+
+ // <Pagination>
+ $scope.reloadPage = function (page, size, successMsg) {
+ // update query pagination parameters
+ $scope.query.page = page;
+ $scope.query.size = size;
+ // recalculate pages
+ calculatePages();
+ if (page < 1 || page > $scope.totalPages) {
+ return;
+ }
+ // get current page of items
+
+ $scope.getUserRequests($scope.query, function (requests) {
+ $scope.requests = requests;
+ if (successMsg) {
+ $scope.$parent.showSuccess(successMsg, $scope.$parent.notification);
+ }
+ });
+ };
+ // </Pagination>
+
+ $scope.getUserRequests = function (query, callback) {
+ UserRequestsService.getUserRequests(query).then(function (response) {
+ callback(response);
+ }, function (response) {
+ var errorMessage;
+ // parse error response
+ console.log("ERROR ", response);
+ if (response !== undefined) {
+ errorMessage = response.split("ErrorMessage{{")[1];
+ errorMessage = errorMessage.split("}}")[0];
+ }
+ console.error("Error retrieving User Requests: ", errorMessage);
+ $scope.$parent.showError("Error: " + (errorMessage || response), $scope.$parent.notification);
+ });
+ };
+
+ var init = function () {
+ $scope.requests = [];
+ $scope.getUserRequests($scope.query, function (requests) {
+ $scope.requests = requests;
+ calculatePages();
+ $scope.availableSizes = [{id: 1, value: 10}, {id: 2, value: 25}, {id: 3, value: 50}];
+ $scope.selectedSize = $scope.availableSizes[0];
+ // date formatting
+ $scope.formatDate = $rootScope.formatDate;
+ });
+
+ };
+
+ var initBpmnProcesses = function () {
+ $scope.bpmnProcesses = [];
+ BpmnProcessService.getBpmnProcesses().then(function (response) {
+ $scope.bpmnProcesses = response;
+ }, function (response) {
+ // parse error response and log
+ if (response !== undefined) {
+ var errorMessages = response.toString().split("ErrorMessage{{");
+ if (errorMessages.length > 1) {
+ console.error("Error retrieving BPMN Processes: ", response.toString()
+ .split("ErrorMessage{{")[1].split("}}")[0]);
+ } else {
+ console.error("Error retrieving BPMN Processes: ", errorMessages);
+ }
+ }
+ });
+ };
+
+ init();
+ initBpmnProcesses();
+
+ $scope.cancel = function (request, reason) {
+ console.log("Cancel request ", request.executionId, reason);
+ UserRequestsService.cancel(request.executionId, reason).then(function (response) {
+ var index = $scope.requests.result.indexOf(request);
+ if (index > -1) {
+ $scope.requests.result.splice(index, 1);
+ $scope.requests.totalCount--;
+ $scope.reloadPage($scope.query.page, $scope.query.size,
+ "Process " + request.executionId + " successfully canceled");
+ }
+ }, function (response) {
+ var errorMessage;
+ // parse error response
+ if (response !== undefined) {
+ errorMessage = response.split("ErrorMessage{{")[1];
+ errorMessage = errorMessage.split("}}")[0];
+ }
+ console.error("Error canceling User Request: ", request.executionId, errorMessage);
+ });
+ };
+
+ $scope.openComponentModal = function (size, parentSelector) {
+ $scope.selectedProcesses = [];
+ var parentElem = parentSelector ?
+ angular.element($document[0].querySelector(parentSelector)) : undefined;
+ var modalInstance = $uibModal.open({
+ animation: true,
+ ariaLabelledBy: 'modal-title',
+ ariaDescribedBy: 'modal-body',
+ component: 'modalWindow',
+ appendTo: parentElem,
+ size: size,
+ windowClass: 'in',
+ backdropClass: 'in',
+ resolve: {
+ bpmnProcesses: function () {
+ return $scope.bpmnProcesses;
+ },
+ selectedProcesses: function () {
+ return $scope.selectedProcesses;
+ },
+ modalHtml: function () {
+ return '<bpmn-processes></bpmn-processes>';
+ },
+ title: function () {
+ return $filter('translate')(["SELECT_PROCESS"]).SELECT_PROCESS;
+ }
+ }
+ });
+
+ modalInstance.result.then(function () {
+ for (var i = 0; i < $scope.selectedProcesses.length; i++) {
+ startRequest(i);
+ }
+ }, function () {
+ });
+ };
+
+ var startRequest = function (i) {
+ var currentProc = $scope.selectedProcesses[i];
+ UserRequestsService.start(currentProc).then(function (response) {
+ console.log("Process " + currentProc + " successfully started");
+ $scope.reloadPage($scope.query.page, $scope.query.size,
+ "Process " + currentProc + " successfully started");
+ }, function (response) {
+ var errorMessage;
+ // parse error response
+ if (response !== undefined) {
+ errorMessage = response.split("ErrorMessage{{")[1];
+ errorMessage = errorMessage.split("}}")[0];
+ }
+ $scope.$parent.showError("Error: " + (errorMessage || response), $scope.$parent.notification);
+ console.error("Error starting User Request: ", errorMessage);
+ });
+ };
+ }
+ };
+ }]);
\ No newline at end of file
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/services/bpmnProcessService.js b/client/enduser/src/main/resources/META-INF/resources/app/js/services/bpmnProcessService.js
new file mode 100644
index 0000000..73c8d7a
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/services/bpmnProcessService.js
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+'use strict';
+
+angular.module('self')
+ .factory('BpmnProcessService', ['$resource', '$q', '$http',
+ function ($resource, $q, $http) {
+
+ var bpmnService = {};
+
+ bpmnService.getBpmnProcesses = function () {
+ return $http.get("../api/flowable/bpmnProcesses/")
+ .then(function (response) {
+ return response.data;
+ }, function (response) {
+ return $q.reject(response.data || response.statusText);
+ });
+ };
+
+ return bpmnService;
+ }]);
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/services/userRequestsService.js b/client/enduser/src/main/resources/META-INF/resources/app/js/services/userRequestsService.js
new file mode 100644
index 0000000..b887784
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/services/userRequestsService.js
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+'use strict';
+
+angular.module('self')
+ .factory('UserRequestsService', ['$q', '$http', function ($q, $http) {
+
+ var userRequestsService = {};
+
+ userRequestsService.getUserRequests = function (query) {
+ return $http.get("../api/flowable/userRequests?user=" + query.user
+ + (query.page ? "&page=" + query.page + (query.size ? "&size=" + query.size : "") : "")
+ + (query.orderBy ? "&orderBy=" + query.orderby : ""))
+ .then(function (response) {
+ return response.data;
+ }, function (response) {
+ return $q.reject(response.data || response.statusText);
+ });
+ };
+
+ userRequestsService.getUserRequestForms = function (query) {
+ return $http.get("../api/flowable/userRequests/forms?user=" + query.user
+ + (query.page ? "&page=" + query.page + (query.size ? "&size=" + query.size : "") : "")
+ + (query.orderBy ? "&orderBy=" + query.orderby : ""))
+ .then(function (response) {
+ return response.data;
+ }, function (response) {
+ return $q.reject(response.data || response.statusText);
+ });
+ };
+
+ userRequestsService.submitForm = function (form) {
+ return $http.post("../api/flowable/userRequests/forms", form)
+ .then(function (response) {
+ return response.data;
+ }, function (response) {
+ return $q.reject(response.data || response.statusText);
+ });
+ };
+
+ userRequestsService.cancel = function (executionId, reason) {
+ return $http.delete("../api/flowable/userRequests?executionId=" + executionId
+ + (reason ? "&reason=" + reason : ""))
+ .then(function (response) {
+ return response.data;
+ }, function (response) {
+ return $q.reject(response.data || response.statusText);
+ });
+ };
+
+ userRequestsService.start = function (bpmnProcess) {
+ return $http.post("../api/flowable/userRequests/start/" + bpmnProcess)
+ .then(function (response) {
+ return response.data;
+ }, function (response) {
+ return $q.reject(response.data || response.statusText);
+ });
+ };
+
+ return userRequestsService;
+ }]);
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/languages/de/static.json b/client/enduser/src/main/resources/META-INF/resources/app/languages/de/static.json
index 9b0c4c2..b685f6b 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/languages/de/static.json
+++ b/client/enduser/src/main/resources/META-INF/resources/app/languages/de/static.json
@@ -54,6 +54,13 @@
"PASSWORD_UPDATED": "Passwort erfolgreich reset",
"GOBACKHOME": "Hier klicken um zur Homepage zurück zu gelangen",
"CONFIRM_REMOVE": "Löschen bestätigen",
- "own": "Besitzen"
+ "SELECT_PROCESS": "Wählen Sie einen oder mehrere Prozesse aus, um die Anfragen zu starten",
+ "PAGE_SIZE": "Seitengröße",
+ "EXECUTION_ID": "Ausführungs-ID",
+ "START_TIME": "Startzeit",
+ "START": "Start",
+ "own": "Besitzen",
+ "userRequests": "Anfragen",
+ "userRequestForms": "Formen"
}
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/languages/en/static.json b/client/enduser/src/main/resources/META-INF/resources/app/languages/en/static.json
index e580e26..31574e4 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/languages/en/static.json
+++ b/client/enduser/src/main/resources/META-INF/resources/app/languages/en/static.json
@@ -45,6 +45,8 @@
"derivedSchemas": "DerivedSchemas",
"virtualSchemas": "VirtualSchemas",
"resources": "Resources",
+ "userRequests": "Requests",
+ "userRequestForms": "Forms",
"finish": "Finish",
"RESOURCES_PLACEHOLDER": "Click to select resources...",
"REALM": "Realm",
@@ -54,6 +56,11 @@
"PASSWORD_UPDATED": "Password successfully reset",
"GOBACKHOME": "Click on this link to go back to the home page",
"CONFIRM_REMOVE": "This will remove the current value. Continue?",
+ "SELECT_PROCESS": "Select one (or many) processes to start request(s)",
+ "PAGE_SIZE": "Page size",
+ "EXECUTION_ID": "Execution id",
+ "START_TIME": "Start time",
+ "START": "Start",
"own": "Own"
}
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/languages/it/static.json b/client/enduser/src/main/resources/META-INF/resources/app/languages/it/static.json
index 55b16f5..3194dfc 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/languages/it/static.json
+++ b/client/enduser/src/main/resources/META-INF/resources/app/languages/it/static.json
@@ -45,6 +45,8 @@
"derivedSchemas": "DerivedSchemas",
"virtualSchemas": "VirtualSchemas",
"resources": "Risorse",
+ "userRequests": "Richieste",
+ "userRequestForms": "Form",
"finish": "Fine",
"RESOURCES_PLACEHOLDER": "Clicca per selezionare risorse...",
"NEWUSER": "Nuovo utente",
@@ -54,5 +56,10 @@
"PASSWORD_UPDATED": "Password resettata con successo",
"GOBACKHOME": "Clicca su questo link per tornare alla home page",
"CONFIRM_REMOVE": "Questa azione rimuoverà il valore corrente. Continuare?",
+ "SELECT_PROCESS": "Selezionare uno (o più) processi per far partire una richiesta(e)",
+ "PAGE_SIZE": "Elementi per pagina",
+ "EXECUTION_ID": "Id Esecuzione",
+ "START_TIME": "Effettuata il",
+ "START": "Start",
"own": "Propri"
}
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/languages/ja/static.json b/client/enduser/src/main/resources/META-INF/resources/app/languages/ja/static.json
index 046b29d..51cd728 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/languages/ja/static.json
+++ b/client/enduser/src/main/resources/META-INF/resources/app/languages/ja/static.json
@@ -54,6 +54,13 @@
"PASSWORD_UPDATED": "正常にパスワードをリセットしました",
"GOBACKHOME": "このリンクをクリックするとホームページに戻ります",
"CONFIRM_REMOVE": "これにより現在の値を削除します。続行しますか?",
+ "SELECT_PROCESS": "プロセスを1つ(または複数)選択して、要求を開始します。",
+ "PAGE_SIZE": "ページサイズ",
+ "EXECUTION_ID": "実行ID",
+ "START_TIME": "始まる時間",
+ "START": "開始",
+ "userRequests": "リクエスト",
+ "userRequestForms": "フォーム",
"own": "自分"
}
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/bpmnProcesses.html b/client/enduser/src/main/resources/META-INF/resources/app/views/bpmnProcesses.html
new file mode 100644
index 0000000..b2e5be8
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/bpmnProcesses.html
@@ -0,0 +1,31 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<ul>
+ <li style="list-style-type: none;" ng-repeat="bpmnProcess in bpmnProcesses">
+ <label>
+ <input
+ type="checkbox"
+ name="selected[]"
+ value="{{bpmnProcess.key}}"
+ ng-checked="selectedProcesses.indexOf(bpmnProcess.key) > -1"
+ ng-click="toggleSelection(bpmnProcess.key)"
+ > {{bpmnProcess.key}}
+ </label>
+ </li>
+</ul>
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/formProperty.html b/client/enduser/src/main/resources/META-INF/resources/app/views/formProperty.html
new file mode 100644
index 0000000..4827a3a
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/formProperty.html
@@ -0,0 +1,92 @@
+<!--
+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.
+-->
+<div ng-switch="property.type" class="schema-type">
+ <input ng-switch-when="String" class="form-control" type="text"
+ ng-model="property.value"
+ ng-required="{{property.required}}" validate="true"
+ ng-disabled="!property.writable"
+ name="{{property.name}}"/>
+
+ <div ng-switch-when="Boolean">
+ <input type="checkbox"
+ ng-true-value="'true'"
+ ng-false-value="'false'"
+ ng-model="property.value"
+ ng-required="{{property.required}}" validate="true"
+ ng-disabled="!property.writable"
+ ng-init="initAttribute()"
+ name="{{property.name}}"/>
+ </div>
+
+ <input ng-switch-when="Long" class="form-control"
+ type="number"
+ ng-model="property.value"
+ ng-required="{{property.required}}" validate="true"
+ ng-disabled="!property.writable"
+ ng-init="initAttribute()"
+ name="{{property.name}}"/>
+
+ <div ng-switch-when="Date" id="date-property">
+ <input type="text" class="dateTimePicker"
+ kendo-date-time-picker
+ ng-show="!isDateOnly"
+ ng-model="extendedDate"
+ ng-required="{{property.required}}" close-text="Close"
+ ng-init="initAttribute()"
+ ng-change="bindDateToModel(selectedDate, extendedDate)"
+ ng-disabled="!property.writable"
+ k-ng-model="selectedDate"
+ data-k-format="languageFormat"
+ />
+ <input type="text" class="datePicker"
+ kendo-date-picker
+ ng-show="isDateOnly"
+ ng-model="extendedDate"
+ ng-required="{{property.required}}" close-text="Close"
+ ng-init="initAttribute()"
+ ng-change="bindDateToModel(selectedDate, extendedDate)"
+ ng-disabled="!property.writable"
+ k-ng-model="selectedDate"
+ data-k-format="languageFormat"
+ />
+ </div>
+
+ <div ng-switch-when="Enum"
+ ng-init="initAttribute()">
+ <select class="form-control custom-select"
+ ng-model="property.value"
+ ng-required="{{property.required}}"
+ ng-disabled="!property.writable">
+ <option ng-repeat="key in enumKeys" value="{{key}}">
+ {{"empty" === key ? "" : property.enumValues[key]}}
+ </option>
+ </select>
+ </div>
+ <div ng-switch-when="Dropdown"
+ ng-init="initAttribute()">
+ <select class="form-control custom-select"
+ ng-model="property.value"
+ ng-required="{{property.required}}"
+ ng-disabled="!property.writable">
+ <option ng-repeat="key in dropdownKeys" value="{{key}}">
+ {{"empty" === key ? "" : property.dropdownValues[key]}}
+ </option>
+ </select>
+ </div>
+</div>
\ No newline at end of file
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/modalWindow.html b/client/enduser/src/main/resources/META-INF/resources/app/views/modalWindow.html
new file mode 100644
index 0000000..9f15200
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/modalWindow.html
@@ -0,0 +1,30 @@
+<!--
+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.
+-->
+<div ng-init="init()">
+ <div class="modal-header">
+ <h3 class="modal-title" id="modal-title">{{resolve.title}}</h3>
+ </div>
+ <div class="modal-body" id="modal-body">
+ <modal-content id="modal-content" modal-html="resolve.modalHtml"></modal-content>
+ </div>
+ <div class="modal-footer">
+ <button class="btn btn-primary" type="button" ng-click="ok()">{{ 'START' | translate}}</button>
+ <button class="btn btn-warning" type="button" ng-click="cancel()">{{'CANCEL'| translate}}</button>
+ </div>
+</div>
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/requestForms.html b/client/enduser/src/main/resources/META-INF/resources/app/views/requestForms.html
new file mode 100644
index 0000000..aa44b2c
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/requestForms.html
@@ -0,0 +1,61 @@
+<!--
+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.
+-->
+<div ng-repeat="form in forms.result">
+ <uib-accordion ng-if="forms.result.length">
+ <div uib-accordion-group heading="{{form.bpmnProcess| translate}}" class="breadcrumb-header panel panel-default">
+ <div id="attribute" class="form-group" ng-repeat="property in form.properties">
+ <label class="property-label" for="property.name">{{property.name}}
+ <span ng-if="property.required">*</span>
+ </label>
+ <form-property property="property"></form-property>
+ <validation-message name="{{property.name}}"/>
+ </div>
+ <div style="text-align: right">
+ <button class="btn btn-secondary btn-default" type="button" ng-click="submit(form)">{{ 'SUBMIT' | translate}}</button>
+ </div>
+ </div>
+ </uib-accordion>
+</div>
+<div class="row">
+ <div class="col-md-9" style="text-align: center">
+ <!-- pager -->
+ <ul ng-if="totalPages > 1" class="pagination">
+ <li ng-class="{disabled:query.page === 1}">
+ <a ng-click="reloadPage(1, query.size)">First</a>
+ </li>
+ <li ng-class="{disabled:query.page === 1}">
+ <a ng-click="reloadPage(query.page - 1, query.size)">Previous</a>
+ </li>
+ <li ng-repeat="page in pages" ng-class="{active:query.page === page}">
+ <a ng-click="reloadPage(page, query.size)">{{page}}</a>
+ </li>
+ <li ng-class="{disabled:query.page === totalPages}">
+ <a ng-click="reloadPage(query.page + 1, query.size)">Next</a>
+ </li>
+ <li ng-class="{disabled:query.page === totalPages}">
+ <a ng-click="reloadPage(totalPages, query.size)">Last</a>
+ </li>
+ </ul>
+ </div>
+ <div ng-if="totalPages > 1" class="form-group col-md-3 pagination-size-sm" style="text-align: left">
+ <select class="form-control" name="sizeSelect" id="sizeSelect"
+ ng-options="option.value for option in availableSizes track by option.id"
+ ng-model="selectedSize" ng-change="reloadPage(query.page, selectedSize.value)"></select>
+ </div>
+</div>
\ No newline at end of file
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/requests.html b/client/enduser/src/main/resources/META-INF/resources/app/views/requests.html
new file mode 100644
index 0000000..68c1da3
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/requests.html
@@ -0,0 +1,76 @@
+<!--
+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.
+-->
+<div ng-repeat="request in requests.result">
+ <uib-accordion ng-if="requests.result.length">
+ <div uib-accordion-group heading="{{request.bpmnProcess| translate}}" class="breadcrumb-header panel panel-default">
+ <table class="table">
+ <thead>
+ <tr>
+ <th>{{'EXECUTION_ID'| translate}}</th>
+ <th>{{'START_TIME'| translate}}</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <tr>
+ <td>{{request.executionId}}</td>
+ <td>{{formatDate(request.startTime)}}</td>
+ </tr>
+ </tbody>
+ </table>
+ <div style="text-align: right">
+ <a id="cancelRequest" class="btn btn-secondary btn-default" ng-click="cancel(request)">
+ <i class="fa fa-trash" aria-hidden="true"></i>
+ </a>
+ </div>
+ </div>
+ </uib-accordion>
+</div>
+<div class="row">
+ <div class="col-md-9" style="text-align: center">
+ <!-- pager -->
+ <ul ng-if="totalPages > 1" class="pagination">
+ <li ng-class="{disabled:query.page === 1}">
+ <a ng-click="reloadPage(1, query.size)">First</a>
+ </li>
+ <li ng-class="{disabled:query.page === 1}">
+ <a ng-click="reloadPage(query.page - 1, query.size)">Previous</a>
+ </li>
+ <li ng-repeat="page in pages" ng-class="{active:query.page === page}">
+ <a ng-click="reloadPage(page, query.size)">{{page}}</a>
+ </li>
+ <li ng-class="{disabled:query.page === totalPages}">
+ <a ng-click="reloadPage(query.page + 1, query.size)">Next</a>
+ </li>
+ <li ng-class="{disabled:query.page === totalPages}">
+ <a ng-click="reloadPage(totalPages, query.size)">Last</a>
+ </li>
+ </ul>
+ </div>
+ <div ng-if="totalPages > 1" class="form-group col-md-3 pagination-size" style="text-align: left">
+ <select class="form-control" name="sizeSelect" id="sizeSelect"
+ ng-options="option.value for option in availableSizes track by option.id"
+ ng-model="selectedSize" ng-change="reloadPage(query.page, selectedSize.value)"></select>
+ </div>
+</div>
+<div class="row" style="text-align: right">
+ <button class="btn btn-default btn-sm" type="button" ng-click="openComponentModal()">
+ <i class="fa fa-plus" title="Start requests"></i>
+ </button>
+</div>
\ No newline at end of file
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/templates/editUserTemplate.html b/client/enduser/src/main/resources/META-INF/resources/app/views/templates/editUserTemplate.html
index 396bfe7..5d16434 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/views/templates/editUserTemplate.html
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/templates/editUserTemplate.html
@@ -40,7 +40,7 @@ under the License.
<i class="fa fa-power-off" style="color:red"></i>
</a>
<!-- add class breadcrumb-disabled-link to buttons to prevent click -->
- <a ng-repeat="(key, value) in wizard" ui-sref-active="active" ui-sref=".{{key}}"
+ <a ng-repeat="(key, value) in (createMode ? creationWizard : wizard)" ui-sref-active="active" ui-sref=".{{key}}"
class="btn btn-secondary btn-default breadcrumb-btn-elem"
ng-class="createMode && !endReached ? 'disable-link' : ''">{{key| translate}}</a>
</div>
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/user-request-forms.html b/client/enduser/src/main/resources/META-INF/resources/app/views/user-request-forms.html
new file mode 100644
index 0000000..4971143
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/user-request-forms.html
@@ -0,0 +1,48 @@
+<!--
+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.
+-->
+<div>
+ <request-forms user="user"></request-forms>
+</div>
+
+<div id="attribute" class="form-group row justify-content-between p-0">
+ <div class="col-xs-3">
+ <a id="cancel" class="btn btn-danger float-left nav-button" tabindex="0"
+ ng-enter="logout()" ng-click="logout()">
+ {{'CANCEL'| translate}}
+ </a>
+ </div>
+ <div class="col-xs-9">
+ <div id="navButtons" class="float-left"
+ ng-class="(!createMode || (createMode && endReached)) ? 'col-xs-10' : 'col-xs-12'">
+ <navigation-buttons-partial ng-show="createMode" base="create" current="userRequestForms" wizard="{{wizard}}">
+ </navigation-buttons-partial>
+ <navigation-buttons-partial ng-show="!createMode" base="update" current="userRequestForms" wizard="{{wizard}}">
+ </navigation-buttons-partial>
+ </div>
+ <div class="float-right p-0"
+ ng-class="(!createMode || (createMode && endReached)) ? 'col-xs-2' : ''"
+ ng-show="!createMode || (createMode && endReached)">
+ <button id="finish" type="button" tabindex="0"
+ class="btn btn-secondary btn-default float-right nav-button"
+ ng-enter="finish()" ng-click="finish()">
+ {{'FINISH'| translate}}
+ </button>
+ </div>
+ </div>
+</div>
\ No newline at end of file
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/user-requests.html b/client/enduser/src/main/resources/META-INF/resources/app/views/user-requests.html
new file mode 100644
index 0000000..62c3e01
--- /dev/null
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/user-requests.html
@@ -0,0 +1,48 @@
+<!--
+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.
+-->
+<div id="attribute" class="form-group row upper-select ng-scope">
+ <requests user="user"></requests>
+</div>
+
+<div id="attribute" class="form-group row justify-content-between p-0">
+ <div class="col-xs-3">
+ <a id="cancel" class="btn btn-danger float-left nav-button" tabindex="0"
+ ng-enter="logout()" ng-click="logout()">
+ {{'CANCEL'| translate}}
+ </a>
+ </div>
+ <div class="col-xs-9">
+ <div id="navButtons" class="float-left"
+ ng-class="(!createMode || (createMode && endReached)) ? 'col-xs-10' : 'col-xs-12'">
+ <navigation-buttons-partial ng-show="createMode" base="create" current="userRequests" wizard="{{wizard}}">
+ </navigation-buttons-partial>
+ <navigation-buttons-partial ng-show="!createMode" base="update" current="userRequests" wizard="{{wizard}}">
+ </navigation-buttons-partial>
+ </div>
+ <div class="float-right p-0"
+ ng-class="(!createMode || (createMode && endReached)) ? 'col-xs-2' : ''"
+ ng-show="!createMode || (createMode && endReached)">
+ <button id="finish" type="button" tabindex="0"
+ class="btn btn-secondary btn-default float-right nav-button"
+ ng-enter="finish()" ng-click="finish()">
+ {{'FINISH'| translate}}
+ </button>
+ </div>
+ </div>
+</div>
\ No newline at end of file
diff --git a/client/enduser/src/main/resources/customTemplate.json b/client/enduser/src/main/resources/customTemplate.json
index e55ed4c..12c7f3b 100644
--- a/client/enduser/src/main/resources/customTemplate.json
+++ b/client/enduser/src/main/resources/customTemplate.json
@@ -74,6 +74,12 @@
"resources": {
"url": "/resources"
},
+ "userRequests": {
+ "url": "/user-requests"
+ },
+ "userRequestForms": {
+ "url": "/user-request-forms"
+ },
"finish": {
"url": "/finish"
}
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 4784913..1eb40c6 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
@@ -29,7 +29,7 @@ import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.request.mapper.parameter.PageParameters;
@ExtPage(label = "Flowable", icon = "fa-briefcase",
- listEntitlement = FlowableEntitlement.BPMN_PROCESS_LIST, priority = 200)
+ listEntitlement = FlowableEntitlement.BPMN_PROCESS_GET, priority = 200)
public class Flowable extends BaseExtPage {
private static final long serialVersionUID = -8781434495150074529L;
@@ -49,7 +49,7 @@ public class Flowable extends BaseExtPage {
}.disableCheckBoxes().build("bpmnProcessesPanel");
bpmnProcessesPanel.setOutputMarkupPlaceholderTag(true);
- MetaDataRoleAuthorizationStrategy.authorize(bpmnProcessesPanel, ENABLE, FlowableEntitlement.BPMN_PROCESS_LIST);
+ MetaDataRoleAuthorizationStrategy.authorize(bpmnProcessesPanel, ENABLE, FlowableEntitlement.BPMN_PROCESS_GET);
content.add(bpmnProcessesPanel);
}
diff --git a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/FlowableEntitlement.java b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/FlowableEntitlement.java
index 0c41c99..40943de 100644
--- a/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/FlowableEntitlement.java
+++ b/ext/flowable/common-lib/src/main/java/org/apache/syncope/common/lib/types/FlowableEntitlement.java
@@ -26,8 +26,6 @@ import java.util.TreeSet;
public final class FlowableEntitlement {
- public static final String BPMN_PROCESS_LIST = "BPMN_PROCESS_LIST";
-
public static final String BPMN_PROCESS_GET = "BPMN_PROCESS_GET";
public static final String BPMN_PROCESS_SET = "BPMN_PROCESS_SET";
diff --git a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableRuntimeUtils.java b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableRuntimeUtils.java
index 9ff621c..e7bfa00 100644
--- a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableRuntimeUtils.java
+++ b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/impl/FlowableRuntimeUtils.java
@@ -209,7 +209,9 @@ public final class FlowableRuntimeUtils {
}
public static void throwException(final FlowableException e, final String defaultMessage) {
- if (e.getCause() instanceof SyncopeClientException) {
+ if (e.getCause() == null) {
+ throw new WorkflowException(defaultMessage, e);
+ } else if (e.getCause() instanceof SyncopeClientException) {
throw (SyncopeClientException) e.getCause();
} else if (e.getCause() instanceof ParsingValidationException) {
throw (ParsingValidationException) e.getCause();
diff --git a/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/BpmnProcessLogic.java b/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/BpmnProcessLogic.java
index ad76605..c28516b 100644
--- a/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/BpmnProcessLogic.java
+++ b/ext/flowable/logic/src/main/java/org/apache/syncope/core/logic/BpmnProcessLogic.java
@@ -36,7 +36,7 @@ public class BpmnProcessLogic extends AbstractTransactionalLogic<BpmnProcess> {
@Autowired
private BpmnProcessManager bpmnProcessManager;
- @PreAuthorize("hasRole('" + FlowableEntitlement.BPMN_PROCESS_LIST + "')")
+ @PreAuthorize("isAuthenticated()")
@Transactional(readOnly = true)
public List<BpmnProcess> list() {
return bpmnProcessManager.getProcesses();
diff --git a/ext/flowable/pom.xml b/ext/flowable/pom.xml
index dfe532d..15591fd 100644
--- a/ext/flowable/pom.xml
+++ b/ext/flowable/pom.xml
@@ -44,6 +44,7 @@ under the License.
<module>rest-cxf</module>
<module>flowable-bpmn</module>
<module>client-console</module>
+ <module>syncope-ext-flowable-client-enduser</module>
</modules>
</project>
diff --git a/ext/flowable/syncope-ext-flowable-client-enduser/pom.xml b/ext/flowable/syncope-ext-flowable-client-enduser/pom.xml
new file mode 100644
index 0000000..09d5844
--- /dev/null
+++ b/ext/flowable/syncope-ext-flowable-client-enduser/pom.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.syncope.ext</groupId>
+ <artifactId>syncope-ext-flowable</artifactId>
+ <version>2.1.3-SNAPSHOT</version>
+ </parent>
+
+ <name>Apache Syncope Ext: Flowable Client Enduser</name>
+ <description>Apache Syncope Ext: Flowable Client Enduser</description>
+ <groupId>org.apache.syncope.ext.flowable</groupId>
+ <artifactId>syncope-ext-flowable-client-enduser</artifactId>
+ <packaging>jar</packaging>
+
+ <properties>
+ <rootpom.basedir>${basedir}/../../..</rootpom.basedir>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.syncope.ext.flowable</groupId>
+ <artifactId>syncope-ext-flowable-common-lib</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.syncope.ext.flowable</groupId>
+ <artifactId>syncope-ext-flowable-rest-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.syncope.client</groupId>
+ <artifactId>syncope-client-enduser</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ </plugin>
+ </plugins>
+ <resources>
+ <resource>
+ <directory>src/main/resources</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ </build>
+
+</project>
\ No newline at end of file
diff --git a/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/BpmnProcessList.java b/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/BpmnProcessList.java
new file mode 100644
index 0000000..4b8d80b
--- /dev/null
+++ b/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/BpmnProcessList.java
@@ -0,0 +1,87 @@
+/*
+ * 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 static org.apache.syncope.client.enduser.resources.BaseResource.MAPPER;
+
+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/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestCancelResource.java b/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestCancelResource.java
new file mode 100644
index 0000000..4012b69
--- /dev/null
+++ b/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestCancelResource.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.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.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/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestFormClaimResource.java b/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestFormClaimResource.java
new file mode 100644
index 0000000..a342c7f
--- /dev/null
+++ b/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestFormClaimResource.java
@@ -0,0 +1,91 @@
+/*
+ * 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 static org.apache.syncope.client.enduser.resources.BaseResource.MAPPER;
+
+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/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsFormsResource.java b/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsFormsResource.java
new file mode 100644
index 0000000..f303070
--- /dev/null
+++ b/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsFormsResource.java
@@ -0,0 +1,165 @@
+/*
+ * 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 static org.apache.syncope.client.enduser.resources.BaseResource.MAPPER;
+
+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/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsResource.java b/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsResource.java
new file mode 100644
index 0000000..812e408
--- /dev/null
+++ b/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsResource.java
@@ -0,0 +1,130 @@
+/*
+ * 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/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsStartResource.java b/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsStartResource.java
new file mode 100644
index 0000000..4c65768
--- /dev/null
+++ b/ext/flowable/syncope-ext-flowable-client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserRequestsStartResource.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.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/fit/enduser-reference/pom.xml b/fit/enduser-reference/pom.xml
index 47de321..de369e5 100644
--- a/fit/enduser-reference/pom.xml
+++ b/fit/enduser-reference/pom.xml
@@ -73,6 +73,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.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
diff --git a/fit/enduser-reference/src/main/resources/customTemplate.json b/fit/enduser-reference/src/main/resources/customTemplate.json
index e55ed4c..7223610 100644
--- a/fit/enduser-reference/src/main/resources/customTemplate.json
+++ b/fit/enduser-reference/src/main/resources/customTemplate.json
@@ -74,6 +74,9 @@
"resources": {
"url": "/resources"
},
+ "userRequests": {
+ "url": "/user-requests"
+ },
"finish": {
"url": "/finish"
}
diff --git a/fit/enduser-reference/src/test/resources/customTemplate.json b/fit/enduser-reference/src/test/resources/customTemplate.json
index e55ed4c..12c7f3b 100644
--- a/fit/enduser-reference/src/test/resources/customTemplate.json
+++ b/fit/enduser-reference/src/test/resources/customTemplate.json
@@ -74,6 +74,12 @@
"resources": {
"url": "/resources"
},
+ "userRequests": {
+ "url": "/user-requests"
+ },
+ "userRequestForms": {
+ "url": "/user-request-forms"
+ },
"finish": {
"url": "/finish"
}