[SYNCOPE-1348] The old bulk operations are gone; time for some docs
authorFrancesco Chicchiriccò <ilgrosso@apache.org>
Thu, 9 Aug 2018 08:23:47 +0000 (10:23 +0200)
committerFrancesco Chicchiriccò <ilgrosso@apache.org>
Thu, 9 Aug 2018 08:24:30 +0000 (10:24 +0200)
138 files changed:
client/cli/src/main/java/org/apache/syncope/client/cli/SyncopeServices.java
client/cli/src/main/java/org/apache/syncope/client/cli/commands/user/UserDeleteAll.java
client/cli/src/main/java/org/apache/syncope/client/cli/commands/user/UserDeleteByAttribute.java
client/cli/src/main/java/org/apache/syncope/client/cli/commands/user/UserResultManager.java
client/cli/src/main/java/org/apache/syncope/client/cli/commands/user/UserSyncopeOperations.java
client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java
client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
client/console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/batch/BatchContent.java [new file with mode: 0644]
client/console/src/main/java/org/apache/syncope/client/console/batch/BatchModal.java [moved from client/console/src/main/java/org/apache/syncope/client/console/bulk/BulkActionModal.java with 82% similarity]
client/console/src/main/java/org/apache/syncope/client/console/bulk/BulkContent.java [deleted file]
client/console/src/main/java/org/apache/syncope/client/console/commons/Constants.java
client/console/src/main/java/org/apache/syncope/client/console/commons/status/StatusUtils.java
client/console/src/main/java/org/apache/syncope/client/console/notifications/MailTemplateDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/pages/Reports.java
client/console/src/main/java/org/apache/syncope/client/console/panels/AccessTokenDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/ActionDataTablePanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/AjaxDataTablePanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/AnyDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeClassesPanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypesPanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/ApplicationDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/ConnInstanceHistoryConfDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/DirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/DynRealmDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/GroupDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/ImplementationDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/PrivilegeDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/Realm.java
client/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypesPanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/RemediationDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/ResourceHistoryConfDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/RoleDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypePanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/SecurityQuestionsPanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/TypeExtensionDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/WorkflowDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/panels/search/AnySelectionDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyRuleDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/reports/ReportTemplateDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/reports/ReportletDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/rest/AbstractAnyRestClient.java
client/console/src/main/java/org/apache/syncope/client/console/rest/BaseRestClient.java
client/console/src/main/java/org/apache/syncope/client/console/rest/ExecutionRestClient.java
client/console/src/main/java/org/apache/syncope/client/console/rest/GroupRestClient.java
client/console/src/main/java/org/apache/syncope/client/console/rest/ReportRestClient.java
client/console/src/main/java/org/apache/syncope/client/console/rest/ResourceRestClient.java
client/console/src/main/java/org/apache/syncope/client/console/rest/TaskRestClient.java
client/console/src/main/java/org/apache/syncope/client/console/rest/UserRestClient.java
client/console/src/main/java/org/apache/syncope/client/console/status/AnyStatusDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/status/ResourceStatusDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/tasks/ExecutionsDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/tasks/NotificationTaskDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/tasks/PropagationTaskDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
client/console/src/main/java/org/apache/syncope/client/console/topology/Topology.java
client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/BatchResponseColumn.java [moved from client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/BulkActionResultColumn.java with 68% similarity]
client/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
client/console/src/main/java/org/apache/syncope/client/console/widgets/ReconDetailsModalPanel.java
client/console/src/main/java/org/apache/syncope/client/console/widgets/ReconciliationWidget.java
client/console/src/main/resources/META-INF/resources/css/batch.css [moved from client/console/src/main/resources/META-INF/resources/css/bulk.css with 97% similarity]
client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent.html [moved from client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkContent.html with 94% similarity]
client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent.properties [moved from client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkContent.properties with 95% similarity]
client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent_it.properties [moved from client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkContent_it.properties with 94% similarity]
client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent_ja.properties [moved from client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkContent_ja.properties with 94% similarity]
client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent_pt_BR.properties [moved from client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkContent_pt_BR.properties with 95% similarity]
client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchContent_ru.properties [moved from client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkContent_ru.properties with 80% similarity]
client/console/src/main/resources/org/apache/syncope/client/console/batch/BatchModal.html [moved from client/console/src/main/resources/org/apache/syncope/client/console/bulk/BulkActionModal.html with 100% similarity]
client/console/src/main/resources/org/apache/syncope/client/console/panels/AjaxDataTablePanel.html
client/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel.properties
client/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel_it.properties
client/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel_ja.properties
client/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel_pt_BR.properties
client/console/src/main/resources/org/apache/syncope/client/console/panels/DirectoryPanel_ru.properties
client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java
client/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java
client/lib/src/main/java/org/apache/syncope/client/lib/batch/BatchRequest.java
client/lib/src/main/java/org/apache/syncope/client/lib/batch/BatchResponse.java
common/lib/src/main/java/org/apache/syncope/common/lib/to/BulkAction.java [deleted file]
common/lib/src/main/java/org/apache/syncope/common/lib/to/BulkActionResult.java [deleted file]
common/lib/src/main/java/org/apache/syncope/common/lib/to/PropagationStatus.java
common/lib/src/main/java/org/apache/syncope/common/lib/types/ExecStatus.java [new file with mode: 0644]
common/lib/src/main/java/org/apache/syncope/common/lib/types/PropagationTaskExecStatus.java [deleted file]
common/lib/src/main/java/org/apache/syncope/common/lib/types/ProvisionAction.java [moved from common/lib/src/main/java/org/apache/syncope/common/lib/types/BulkMembersActionType.java with 96% similarity]
common/lib/src/main/java/org/apache/syncope/common/lib/types/ReportExecStatus.java
common/lib/src/main/java/org/apache/syncope/common/lib/types/TaskType.java
common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ExecDeleteQuery.java [moved from common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/BulkExecDeleteQuery.java with 95% similarity]
common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AnyService.java
common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ExecutableService.java
common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/GroupService.java
common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ReconciliationService.java
common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ResourceService.java
common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SyncopeService.java
common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/TaskService.java
core/logic/src/main/java/org/apache/syncope/core/logic/AbstractExecutableLogic.java
core/logic/src/main/java/org/apache/syncope/core/logic/GroupLogic.java
core/logic/src/main/java/org/apache/syncope/core/logic/ReportLogic.java
core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/PropagationTaskValidator.java
core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/TaskExecTest.java
core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/TaskTest.java
core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationReporter.java
core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/GroupMemberProvisionTaskJobDelegate.java
core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationReporter.java
core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java
core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java
core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPushResultHandler.java
core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java
core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractExecutableService.java
core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java
core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GroupServiceImpl.java
core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ResourceServiceImpl.java
core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/TaskServiceImpl.java
ext/camel/client-console/src/main/java/org/apache/syncope/client/console/panels/CamelRoutesDirectoryPanel.java
ext/oidcclient/client-console/src/main/java/org/apache/syncope/client/console/panels/OIDCProvidersDirectoryPanel.java
ext/saml2sp/client-console/src/main/java/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.java
fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
fit/core-reference/src/test/java/org/apache/syncope/fit/console/BatchesITCase.java [moved from fit/core-reference/src/test/java/org/apache/syncope/fit/console/BulkActionITCase.java with 94% similarity]
fit/core-reference/src/test/java/org/apache/syncope/fit/console/TopologyITCase.java
fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthenticationITCase.java
fit/core-reference/src/test/java/org/apache/syncope/fit/core/GroupITCase.java
fit/core-reference/src/test/java/org/apache/syncope/fit/core/MembershipITCase.java
fit/core-reference/src/test/java/org/apache/syncope/fit/core/MultitenancyITCase.java
fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java
fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
fit/core-reference/src/test/java/org/apache/syncope/fit/core/PushTaskITCase.java
fit/core-reference/src/test/java/org/apache/syncope/fit/core/RealmITCase.java
fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReportITCase.java
fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserIssuesITCase.java
fit/core-reference/src/test/java/org/apache/syncope/fit/core/VirAttrITCase.java

index b2606e4..29c6feb 100644 (file)
@@ -28,6 +28,7 @@ import org.apache.syncope.client.cli.commands.install.InstallConfigFileTemplate;
 import org.apache.syncope.client.cli.util.JasyptUtils;
 import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
+import org.apache.syncope.client.lib.batch.BatchRequest;
 import org.apache.syncope.common.rest.api.service.SyncopeService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -38,7 +39,7 @@ public final class SyncopeServices {
 
     private static String SYNCOPE_ADDRESS;
 
-    public static <T> T get(final Class<T> clazz) {
+    private static SyncopeClient client() {
         final Properties properties = new Properties();
         try (InputStream is = Files.newInputStream(Paths.get(InstallConfigFileTemplate.configurationFilePath()))) {
             properties.load(is);
@@ -49,13 +50,19 @@ public final class SyncopeServices {
         String syncopeAdminPassword = JasyptUtils.get().decrypt(properties.getProperty("syncope.admin.password"));
         SYNCOPE_ADDRESS = properties.getProperty("syncope.rest.services");
         String useGZIPCompression = properties.getProperty("useGZIPCompression");
-        SyncopeClient syncopeClient = new SyncopeClientFactoryBean().
+        return new SyncopeClientFactoryBean().
                 setAddress(SYNCOPE_ADDRESS).
                 setUseCompression(BooleanUtils.toBoolean(useGZIPCompression)).
                 create(properties.getProperty("syncope.admin.user"), syncopeAdminPassword);
+    }
 
+    public static <T> T get(final Class<T> clazz) {
         LOG.debug("Creating service for {}", clazz.getName());
-        return syncopeClient.getService(clazz);
+        return client().getService(clazz);
+    }
+
+    public static BatchRequest batch() {
+        return client().batch();
     }
 
     public static String getAddress() {
index 30d0953..e54812d 100644 (file)
  */
 package org.apache.syncope.client.cli.commands.user;
 
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Scanner;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.ws.rs.core.Response;
 import org.apache.syncope.client.cli.Input;
-import org.apache.syncope.common.lib.SyncopeClientException;
-import org.apache.syncope.common.lib.to.BulkActionResult;
+import org.apache.syncope.common.lib.to.ProvisioningResult;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.batch.BatchResponseItem;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -31,6 +39,8 @@ public class UserDeleteAll extends AbstractUserCommand {
 
     private static final Logger LOG = LoggerFactory.getLogger(UserDeleteAll.class);
 
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
     private static final String DELETE_ALL_HELP_MESSAGE = "user --delete-all {REALM}";
 
     private final Input input;
@@ -59,21 +69,31 @@ public class UserDeleteAll extends AbstractUserCommand {
                             userResultManager.notFoundError("Realm", realm);
                             return;
                         }
-                        final Map<String, BulkActionResult.Status> results = userSyncopeOperations.deleteAll(realm);
-                        final Map<String, String> users = new HashMap<>();
-                        int deletedUsers = 0;
-                        for (final Map.Entry<String, BulkActionResult.Status> entrySet : results.entrySet()) {
-                            final String userId = entrySet.getKey();
-                            final BulkActionResult.Status status = entrySet.getValue();
-                            if (!BulkActionResult.Status.SUCCESS.equals(status)) {
-                                users.put(userId, status.name());
+                        List<BatchResponseItem> results = userSyncopeOperations.deleteAll(realm);
+
+                        Map<String, String> failedUsers = new HashMap<>();
+                        AtomicReference<Integer> deletedUsers = new AtomicReference<>(0);
+
+                        results.forEach(item -> {
+                            if (item.getStatus() == Response.Status.OK.getStatusCode()) {
+                                deletedUsers.getAndSet(deletedUsers.get() + 1);
                             } else {
-                                deletedUsers++;
+                                try {
+                                    ProvisioningResult<UserTO> user = MAPPER.readValue(item.getContent(),
+                                            new TypeReference<ProvisioningResult<UserTO>>() {
+                                    });
+                                    failedUsers.put(
+                                            user.getEntity().getUsername(),
+                                            item.getHeaders().get(RESTHeaders.ERROR_CODE).toString());
+                                } catch (IOException ioe) {
+                                    LOG.error("Error reading {}", item.getContent(), ioe);
+                                }
                             }
-                        }
+                        });
+
                         userResultManager.genericMessage("Deleted users: " + deletedUsers);
-                        if (!users.isEmpty()) {
-                            userResultManager.printFailedUsers(users);
+                        if (!failedUsers.isEmpty()) {
+                            userResultManager.printFailedUsers(failedUsers);
                         }
                     } else {
                         userResultManager.genericError("Authentication error");
@@ -83,9 +103,9 @@ public class UserDeleteAll extends AbstractUserCommand {
                 } else {
                     userResultManager.genericError("Invalid parameter, please use [yes/no]");
                 }
-            } catch (final SyncopeClientException ex) {
-                LOG.error("Error deleting user", ex);
-                userResultManager.genericError(ex.getMessage());
+            } catch (Exception e) {
+                LOG.error("Error deleting user", e);
+                userResultManager.genericError(e.getMessage());
             }
         } else {
             userResultManager.commandOptionError(DELETE_ALL_HELP_MESSAGE);
index 3759656..f5e735e 100644 (file)
  */
 package org.apache.syncope.client.cli.commands.user;
 
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
-import javax.xml.ws.WebServiceException;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.ws.rs.core.Response;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.client.cli.Input;
-import org.apache.syncope.common.lib.SyncopeClientException;
-import org.apache.syncope.common.lib.to.BulkActionResult;
+import org.apache.syncope.common.lib.to.ProvisioningResult;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.batch.BatchResponseItem;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -32,6 +39,8 @@ public class UserDeleteByAttribute extends AbstractUserCommand {
 
     private static final Logger LOG = LoggerFactory.getLogger(UserDeleteByAttribute.class);
 
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
     private static final String SEARCH_HELP_MESSAGE = "user --delete-by-attribute {REALM} {ATTR-NAME}={ATTR-VALUE}";
 
     private final Input input;
@@ -49,34 +58,40 @@ public class UserDeleteByAttribute extends AbstractUserCommand {
                     userResultManager.notFoundError("Realm", realm);
                     return;
                 }
-                Map<String, BulkActionResult.Status> results = userSyncopeOperations.deleteByAttribute(
+                List<BatchResponseItem> results = userSyncopeOperations.deleteByAttribute(
                         realm, pairParameter.getKey(), pairParameter.getValue());
+
                 Map<String, String> failedUsers = new HashMap<>();
-                int deletedUsers = 0;
-                for (final Map.Entry<String, BulkActionResult.Status> entrySet : results.entrySet()) {
-                    String userId = entrySet.getKey();
-                    BulkActionResult.Status status = entrySet.getValue();
-                    if (BulkActionResult.Status.SUCCESS == status) {
-                        deletedUsers++;
+                AtomicReference<Integer> deletedUsers = new AtomicReference<>(0);
+
+                results.forEach(item -> {
+                    if (item.getStatus() == Response.Status.OK.getStatusCode()) {
+                        deletedUsers.getAndSet(deletedUsers.get() + 1);
                     } else {
-                        failedUsers.put(userId, status.name());
+                        try {
+                            ProvisioningResult<UserTO> user = MAPPER.readValue(item.getContent(),
+                                    new TypeReference<ProvisioningResult<UserTO>>() {
+                            });
+                            failedUsers.put(
+                                    user.getEntity().getUsername(),
+                                    item.getHeaders().get(RESTHeaders.ERROR_CODE).toString());
+                        } catch (IOException ioe) {
+                            LOG.error("Error reading {}", item.getContent(), ioe);
+                        }
                     }
-                }
+                });
+
                 userResultManager.genericMessage("Deleted users: " + deletedUsers);
                 if (!failedUsers.isEmpty()) {
                     userResultManager.printFailedUsers(failedUsers);
                 }
-            } catch (WebServiceException | SyncopeClientException ex) {
-                LOG.error("Error searching user", ex);
-                if (ex.getMessage().startsWith("NotFound")) {
+            } catch (Exception e) {
+                LOG.error("Error searching user", e);
+                if (e.getMessage().startsWith("NotFound")) {
                     userResultManager.notFoundError("User with " + pairParameter.getKey(), pairParameter.getValue());
                 } else {
-                    userResultManager.genericError(ex.getMessage());
+                    userResultManager.genericError(e.getMessage());
                 }
-            } catch (IllegalArgumentException ex) {
-                LOG.error("Error searching user", ex);
-                userResultManager.genericError(ex.getMessage());
-                userResultManager.genericError(SEARCH_HELP_MESSAGE);
             }
         } else {
             userResultManager.commandOptionError(SEARCH_HELP_MESSAGE);
index 450ff7e..9c23575 100644 (file)
@@ -32,9 +32,9 @@ public class UserResultManager extends CommonsResultManager {
 
     public void printUsers(final List<UserTO> userTOs) {
         System.out.println("");
-        for (final UserTO userTO : userTOs) {
+        userTOs.forEach(userTO -> {
             printUser(userTO);
-        }
+        });
     }
 
     private void printUser(final UserTO userTO) {
@@ -69,40 +69,40 @@ public class UserResultManager extends CommonsResultManager {
     }
 
     private void printResource(final Set<String> resources) {
-        for (final String resource : resources) {
+        resources.forEach(resource -> {
             System.out.println("       - " + resource);
-        }
+        });
     }
 
     private void printRole(final List<String> roles) {
-        for (final String role : roles) {
+        roles.forEach((role) -> {
             System.out.println("       - " + role);
-        }
+        });
     }
 
     private void printAttributes(final Set<AttrTO> derAttrTOs) {
-        for (final AttrTO attrTO : derAttrTOs) {
+        derAttrTOs.forEach(attrTO -> {
             final StringBuilder attributeSentence = new StringBuilder();
             attributeSentence.append("       ")
                     .append(attrTO.getSchema())
                     .append(": ")
                     .append(attrTO.getValues());
             System.out.println(attributeSentence);
-        }
+        });
     }
 
     private void printRelationships(final List<RelationshipTO> relationshipTOs) {
-        for (final RelationshipTO relationshipTO : relationshipTOs) {
+        relationshipTOs.forEach(relationshipTO -> {
             System.out.println("       type: " + relationshipTO.getType());
-        }
+        });
     }
 
     public void printFailedUsers(final Map<String, String> users) {
-        final Table.TableBuilder tableBuilder =
+        Table.TableBuilder tableBuilder =
                 new Table.TableBuilder("Users not deleted").header("user key").header("cause");
-        for (final Map.Entry<String, String> entrySet : users.entrySet()) {
-            tableBuilder.rowValues(Arrays.asList(entrySet.getKey(), entrySet.getValue()));
-        }
+        users.forEach((key, value) -> {
+            tableBuilder.rowValues(Arrays.asList(key, value));
+        });
         tableBuilder.build().print();
     }
 
index 42fb5a3..2c712ba 100644 (file)
  */
 package org.apache.syncope.client.cli.commands.user;
 
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
+import javax.ws.rs.core.Response;
 import org.apache.syncope.client.cli.SyncopeServices;
 import org.apache.syncope.client.lib.SyncopeClient;
-import org.apache.syncope.common.lib.to.BulkAction;
-import org.apache.syncope.common.lib.to.BulkActionResult;
-import org.apache.syncope.common.lib.to.EntityTO;
+import org.apache.syncope.client.lib.batch.BatchRequest;
 import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.rest.api.batch.BatchPayloadParser;
+import org.apache.syncope.common.rest.api.batch.BatchResponseItem;
 import org.apache.syncope.common.rest.api.beans.AnyQuery;
 import org.apache.syncope.common.rest.api.service.UserService;
 
@@ -91,34 +92,35 @@ public class UserSyncopeOperations {
         userService.delete(userKey);
     }
 
-    public Map<String, BulkActionResult.Status> deleteByAttribute(
-            final String realm, final String attributeName, final String attributeValue) {
+    public List<BatchResponseItem> deleteByAttribute(
+            final String realm, final String attributeName, final String attributeValue) throws IOException {
 
-        return bulkDelete(new AnyQuery.Builder().realm(realm).fiql(
+        return batchDelete(new AnyQuery.Builder().realm(realm).fiql(
                 SyncopeClient.getUserSearchConditionBuilder().is(attributeName).equalTo(attributeValue).query()).
                 build());
     }
 
-    public Map<String, BulkActionResult.Status> deleteAll(final String realm) {
-        return bulkDelete(new AnyQuery.Builder().realm(realm).details(false).build());
+    public List<BatchResponseItem> deleteAll(final String realm) throws IOException {
+        return batchDelete(new AnyQuery.Builder().realm(realm).details(false).build());
     }
 
-    private Map<String, BulkActionResult.Status> bulkDelete(final AnyQuery query) {
+    private List<BatchResponseItem> batchDelete(final AnyQuery query) throws IOException {
         query.setPage(0);
         query.setSize(0);
         int count = userService.search(query).getTotalCount();
 
-        BulkAction bulkAction = new BulkAction();
-        bulkAction.setType(BulkAction.Type.DELETE);
+        BatchRequest batchRequest = SyncopeServices.batch();
+        UserService batchUserService = batchRequest.getService(UserService.class);
 
         query.setSize(PAGE_SIZE);
         for (int page = 1; page <= (count / PAGE_SIZE) + 1; page++) {
             query.setPage(page);
 
-            bulkAction.getTargets().addAll(userService.search(query).getResult().stream().
-                    map(EntityTO::getKey).collect(Collectors.toList()));
+            userService.search(query).getResult().forEach(user -> batchUserService.delete(user.getKey()));
         }
 
-        return userService.bulk(bulkAction).readEntity(BulkActionResult.class).getResults();
+        Response response = batchRequest.commit().getResponse();
+        return BatchPayloadParser.parse(
+                (InputStream) response.getEntity(), response.getMediaType(), new BatchResponseItem());
     }
 }
index 288c130..4ff40d2 100644 (file)
@@ -300,7 +300,7 @@ public class SyncopeConsoleApplication extends AuthenticatedWebApplication {
 
     public SyncopeClientFactoryBean newClientFactory() {
         return new SyncopeClientFactoryBean().
-                setAddress(scheme + "://" + host + ":" + port + "/" + rootPath).
+                setAddress(scheme + "://" + host + ":" + port + StringUtils.prependIfMissing(rootPath, "/")).
                 setUseCompression(BooleanUtils.toBoolean(useGZIPCompression));
     }
 
index 568148c..bfde815 100644 (file)
@@ -27,6 +27,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.Callable;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Future;
 import javax.ws.rs.core.EntityTag;
 import javax.ws.rs.core.MediaType;
@@ -39,6 +40,7 @@ import org.apache.syncope.client.console.commons.Constants;
 import org.apache.syncope.client.lib.AnonymousAuthenticationHandler;
 import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
+import org.apache.syncope.client.lib.batch.BatchRequest;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.info.PlatformInfo;
 import org.apache.syncope.common.lib.info.SystemInfo;
@@ -50,6 +52,7 @@ import org.apache.wicket.authroles.authorization.strategies.role.Roles;
 import org.apache.wicket.request.Request;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.core.task.TaskRejectedException;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 
 public class SyncopeConsoleSession extends AuthenticatedWebSession {
@@ -113,11 +116,21 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession {
     }
 
     public void execute(final Runnable command) {
-        executor.execute(command);
+        try {
+            executor.execute(command);
+        } catch (TaskRejectedException e) {
+            LOG.error("Could not execute {}", command, e);
+        }
     }
 
     public <T> Future<T> execute(final Callable<T> command) {
-        return executor.submit(command);
+        try {
+            return executor.submit(command);
+        } catch (TaskRejectedException e) {
+            LOG.error("Could not execute {}", command, e);
+
+            return new CompletableFuture<>();
+        }
     }
 
     public PlatformInfo getPlatformInfo() {
@@ -288,6 +301,10 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession {
         return service;
     }
 
+    public BatchRequest batch() {
+        return client.batch();
+    }
+
     public <T> void resetClient(final Class<T> service) {
         T serviceInstance = getCachedService(service);
         WebClient.client(serviceInstance).reset();
index 252ccf6..24f0e59 100644 (file)
@@ -286,7 +286,7 @@ public class ApprovalDirectoryPanel
     }
 
     @Override
-    protected Collection<ActionLink.ActionType> getBulkActions() {
+    protected Collection<ActionLink.ActionType> getBatches() {
         return Collections.<ActionLink.ActionType>emptyList();
     }
 
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/batch/BatchContent.java b/client/console/src/main/java/org/apache/syncope/client/console/batch/BatchContent.java
new file mode 100644 (file)
index 0000000..37daa33
--- /dev/null
@@ -0,0 +1,379 @@
+/*
+ * 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.console.batch;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.commons.status.StatusBean;
+import org.apache.syncope.client.console.pages.BasePage;
+import org.apache.syncope.client.console.panels.MultilevelPanel;
+import org.apache.syncope.client.console.rest.AbstractAnyRestClient;
+import org.apache.syncope.client.console.rest.RestClient;
+import org.apache.syncope.client.console.rest.UserRestClient;
+import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BatchResponseColumn;
+import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
+import org.apache.syncope.client.lib.batch.BatchRequest;
+import org.apache.syncope.common.lib.patch.BooleanReplacePatchItem;
+import org.apache.syncope.common.lib.patch.StatusPatch;
+import org.apache.syncope.common.lib.patch.UserPatch;
+import org.apache.syncope.common.lib.to.AnyTO;
+import org.apache.syncope.common.lib.to.ExecTO;
+import org.apache.syncope.common.lib.to.GroupTO;
+import org.apache.syncope.common.lib.to.ReportTO;
+import org.apache.syncope.common.lib.to.TaskTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.ExecStatus;
+import org.apache.syncope.common.lib.types.ResourceAssociationAction;
+import org.apache.syncope.common.lib.types.ResourceDeassociationAction;
+import org.apache.syncope.common.lib.types.StandardEntitlement;
+import org.apache.syncope.common.lib.types.StatusPatchType;
+import org.apache.syncope.common.lib.types.TaskType;
+import org.apache.syncope.common.rest.api.beans.ExecuteQuery;
+import org.apache.syncope.common.rest.api.service.AnyObjectService;
+import org.apache.syncope.common.rest.api.service.AnyService;
+import org.apache.syncope.common.rest.api.service.GroupService;
+import org.apache.syncope.common.rest.api.service.ReportService;
+import org.apache.syncope.common.rest.api.service.TaskService;
+import org.apache.syncope.common.rest.api.service.UserService;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.extensions.ajax.markup.html.repeater.data.table.AjaxFallbackDefaultDataTable;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.CollectionUtils;
+
+public class BatchContent<T extends Serializable, S> extends MultilevelPanel.SecondLevel {
+
+    private static final long serialVersionUID = 4114026480146090963L;
+
+    protected static final Logger LOG = LoggerFactory.getLogger(BatchContent.class);
+
+    public BatchContent(
+            final BaseModal<?> modal,
+            final List<T> items,
+            final List<IColumn<T, S>> columns,
+            final Collection<ActionLink.ActionType> actions,
+            final RestClient batchExecutor,
+            final String keyFieldName) {
+
+        this(MultilevelPanel.SECOND_LEVEL_ID, modal, items, columns, actions, batchExecutor, keyFieldName);
+    }
+
+    public BatchContent(
+            final String id,
+            final BaseModal<?> modal,
+            final List<T> items,
+            final List<IColumn<T, S>> columns,
+            final Collection<ActionLink.ActionType> actions,
+            final RestClient batchExecutor,
+            final String keyFieldName) {
+
+        super(id);
+
+        WebMarkupContainer container = new WebMarkupContainer("container");
+        container.setOutputMarkupId(true);
+        add(container);
+
+        SortableDataProvider<T, S> dataProvider = new SortableDataProvider<T, S>() {
+
+            private static final long serialVersionUID = 5291903859908641954L;
+
+            @Override
+            public Iterator<? extends T> iterator(final long first, final long count) {
+                return items.iterator();
+            }
+
+            @Override
+            public long size() {
+                return items.size();
+            }
+
+            @Override
+            public IModel<T> model(final T object) {
+                return new CompoundPropertyModel<>(object);
+            }
+        };
+
+        container.add(new AjaxFallbackDefaultDataTable<>(
+                "selectedObjects",
+                columns,
+                dataProvider,
+                Integer.MAX_VALUE).setMarkupId("selectedObjects").setVisible(!CollectionUtils.isEmpty(items)));
+
+        ActionsPanel<Serializable> actionPanel = new ActionsPanel<>("actions", null);
+        container.add(actionPanel);
+
+        for (ActionLink.ActionType action : actions) {
+            actionPanel.add(new ActionLink<Serializable>() {
+
+                private static final long serialVersionUID = -3722207913631435501L;
+
+                @Override
+                protected boolean statusCondition(final Serializable modelObject) {
+                    return !CollectionUtils.isEmpty(items);
+                }
+
+                @Override
+                @SuppressWarnings("unchecked")
+                public void onClick(final AjaxRequestTarget target, final Serializable ignore) {
+                    if (CollectionUtils.isEmpty(items)) {
+                        throw new IllegalArgumentException("Invalid items");
+                    }
+
+                    Map<String, String> results;
+                    try {
+                        T singleItem = items.iterator().next();
+
+                        if (singleItem instanceof ExecTO) {
+                            results = new HashMap<>();
+                            items.forEach(item -> {
+                                ExecTO exec = ExecTO.class.cast(item);
+
+                                try {
+                                    batchExecutor.getClass().getMethod("deleteExecution",
+                                            String.class).invoke(batchExecutor, exec.getKey());
+                                    results.put(exec.getKey(), ExecStatus.SUCCESS.name());
+                                } catch (Exception e) {
+                                    LOG.error("Error deleting execution {}", exec.getKey(), e);
+                                    results.put(exec.getKey(), ExecStatus.FAILURE.name());
+                                }
+                            });
+                        } else if (singleItem instanceof StatusBean) {
+                            AbstractAnyRestClient<?> anyRestClient = AbstractAnyRestClient.class.cast(batchExecutor);
+
+                            // Group bean information by anyKey
+                            Map<String, List<StatusBean>> beans = new HashMap<>();
+                            items.stream().map(bean -> StatusBean.class.cast(bean)).
+                                    forEachOrdered(sb -> {
+                                        final List<StatusBean> sblist;
+                                        if (beans.containsKey(sb.getKey())) {
+                                            sblist = beans.get(sb.getKey());
+                                        } else {
+                                            sblist = new ArrayList<>();
+                                            beans.put(sb.getKey(), sblist);
+                                        }
+                                        sblist.add(sb);
+                                    });
+
+                            results = new HashMap<>();
+                            beans.forEach((key, value) -> {
+                                String etag = anyRestClient.read(key).getETagValue();
+
+                                switch (action) {
+                                    case DEPROVISION:
+                                        results.putAll(anyRestClient.deassociate(
+                                                ResourceDeassociationAction.DEPROVISION, etag, key, value));
+                                        break;
+
+                                    case UNASSIGN:
+                                        results.putAll(anyRestClient.deassociate(
+                                                ResourceDeassociationAction.UNASSIGN, etag, key, value));
+                                        break;
+
+                                    case UNLINK:
+                                        results.putAll(anyRestClient.deassociate(
+                                                ResourceDeassociationAction.UNLINK, etag, key, value));
+                                        break;
+
+                                    case ASSIGN:
+                                        results.putAll(anyRestClient.associate(
+                                                ResourceAssociationAction.ASSIGN, etag, key, value));
+                                        break;
+
+                                    case LINK:
+                                        results.putAll(anyRestClient.associate(
+                                                ResourceAssociationAction.LINK, etag, key, value));
+                                        break;
+
+                                    case PROVISION:
+                                        results.putAll(anyRestClient.associate(
+                                                ResourceAssociationAction.PROVISION, etag, key, value));
+                                        break;
+
+                                    case SUSPEND:
+                                        results.putAll(((UserRestClient) anyRestClient).suspend(etag, key, value));
+                                        break;
+
+                                    case REACTIVATE:
+                                        results.putAll(((UserRestClient) anyRestClient).reactivate(etag, key, value));
+                                        break;
+
+                                    default:
+                                }
+                            });
+                        } else {
+                            BatchRequest batch = SyncopeConsoleSession.get().batch();
+
+                            UserService batchUserService = batch.getService(UserService.class);
+                            GroupService batchGroupService = batch.getService(GroupService.class);
+                            AnyObjectService batchAnyObjectService = batch.getService(AnyObjectService.class);
+                            AnyService<?> batchAnyService = singleItem instanceof UserTO
+                                    ? batchUserService
+                                    : singleItem instanceof GroupTO
+                                            ? batchGroupService
+                                            : batchAnyObjectService;
+                            TaskService batchTaskService = batch.getService(TaskService.class);
+                            ReportService batchReportService = batch.getService(ReportService.class);
+
+                            switch (action) {
+                                case MUSTCHANGEPASSWORD:
+                                    items.forEach(item -> {
+                                        UserTO user = (UserTO) item;
+
+                                        UserPatch patch = new UserPatch();
+                                        patch.setKey(user.getKey());
+                                        patch.setMustChangePassword(new BooleanReplacePatchItem.Builder().
+                                                value(!user.isMustChangePassword()).build());
+
+                                        batchUserService.update(patch);
+                                    });
+                                    break;
+
+                                case SUSPEND:
+                                    items.forEach(item -> {
+                                        UserTO user = (UserTO) item;
+
+                                        StatusPatch patch = new StatusPatch.Builder().
+                                                key(user.getKey()).
+                                                type(StatusPatchType.SUSPEND).
+                                                onSyncope(true).
+                                                resources(user.getResources()).
+                                                build();
+
+                                        batchUserService.status(patch);
+                                    });
+                                    break;
+
+                                case REACTIVATE:
+                                    items.forEach(item -> {
+                                        UserTO user = (UserTO) item;
+
+                                        StatusPatch patch = new StatusPatch.Builder().
+                                                key(user.getKey()).
+                                                type(StatusPatchType.REACTIVATE).
+                                                onSyncope(true).
+                                                resources(user.getResources()).
+                                                build();
+
+                                        batchUserService.status(patch);
+                                    });
+                                    break;
+
+                                case DELETE:
+                                    items.forEach(item -> {
+                                        if (singleItem instanceof AnyTO) {
+                                            AnyTO any = (AnyTO) item;
+
+                                            batchAnyService.delete(any.getKey());
+                                        } else if (singleItem instanceof TaskTO) {
+                                            TaskTO task = (TaskTO) item;
+
+                                            batchTaskService.delete(
+                                                    TaskType.fromTOClass(task.getClass()),
+                                                    task.getKey());
+                                        } else if (singleItem instanceof ReportTO) {
+                                            ReportTO report = (ReportTO) item;
+
+                                            batchReportService.delete(report.getKey());
+                                        } else {
+                                            LOG.warn("Unsupported for DELETE: {}", singleItem.getClass().getName());
+                                        }
+                                    });
+
+                                    break;
+
+                                case DRYRUN:
+                                    items.forEach(item -> {
+                                        TaskTO task = (TaskTO) item;
+
+                                        batchTaskService.execute(
+                                                new ExecuteQuery.Builder().dryRun(true).key(task.getKey()).build());
+                                    });
+                                    break;
+
+                                case EXECUTE:
+                                    items.forEach(item -> {
+                                        if (singleItem instanceof TaskTO) {
+                                            TaskTO task = (TaskTO) item;
+
+                                            batchTaskService.execute(new ExecuteQuery.Builder().
+                                                    dryRun(false).key(task.getKey()).build());
+                                        } else if (singleItem instanceof ReportTO) {
+                                            ReportTO report = (ReportTO) item;
+
+                                            batchReportService.execute(new ExecuteQuery.Builder().
+                                                    key(report.getKey()).build());
+                                        }
+                                    });
+                                    break;
+
+                                default:
+                            }
+
+                            results = CastUtils.cast(Map.class.cast(
+                                    batchExecutor.getClass().getMethod("batch",
+                                            BatchRequest.class).invoke(batchExecutor, batch)));
+
+                            if (singleItem instanceof UserTO) {
+                                UserRestClient userRestClient = UserRestClient.class.cast(batchExecutor);
+                                for (int i = 0; i < items.size(); i++) {
+                                    items.set(i, (T) userRestClient.read(((UserTO) items.get(i)).getKey()));
+                                }
+                            }
+                        }
+
+                        List<IColumn<T, S>> newColumnList = new ArrayList<>(columns);
+                        newColumnList.add(newColumnList.size(), new BatchResponseColumn<>(results, keyFieldName));
+
+                        container.addOrReplace(new AjaxFallbackDefaultDataTable<>(
+                                "selectedObjects",
+                                newColumnList,
+                                dataProvider,
+                                Integer.MAX_VALUE).setVisible(!items.isEmpty()));
+
+                        actionPanel.setEnabled(false);
+                        actionPanel.setVisible(false);
+                        target.add(container);
+                        target.add(actionPanel);
+
+                        SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED));
+                    } catch (Exception e) {
+                        LOG.error("Batch failure", e);
+                        SyncopeConsoleSession.get().error("Operation " + action.getActionId() + " not supported");
+                    }
+                    ((BasePage) getPage()).getNotificationPanel().refresh(target);
+                }
+            }, action, StandardEntitlement.CONFIGURATION_LIST).hideLabel();
+        }
+    }
+}
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.console.bulk;
+package org.apache.syncope.client.console.batch;
 
 import java.io.Serializable;
 import java.util.Collection;
@@ -28,21 +28,21 @@ import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
 
-public class BulkActionModal<T extends Serializable, S> extends AbstractModalPanel<T> {
+public class BatchModal<T extends Serializable, S> extends AbstractModalPanel<T> {
 
     private static final long serialVersionUID = 4114026480146090962L;
 
-    public BulkActionModal(
+    public BatchModal(
             final BaseModal<T> modal,
             final PageReference pageRef,
-            final Collection<T> items,
+            final List<T> items,
             final List<IColumn<T, S>> columns,
             final Collection<ActionLink.ActionType> actions,
-            final RestClient bulkActionExecutor,
+            final RestClient batchExecutor,
             final String keyFieldName) {
 
         super(modal, pageRef);
-        add(new BulkContent<>("content", modal, items, columns, actions, bulkActionExecutor, keyFieldName).
+        add(new BatchContent<>("content", modal, items, columns, actions, batchExecutor, keyFieldName).
                 setRenderBodyOnly(true));
     }
 }
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/bulk/BulkContent.java b/client/console/src/main/java/org/apache/syncope/client/console/bulk/BulkContent.java
deleted file mode 100644 (file)
index 552b843..0000000
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.syncope.client.console.bulk;
-
-import java.io.Serializable;
-import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import org.apache.syncope.client.console.SyncopeConsoleSession;
-import org.apache.syncope.client.console.commons.Constants;
-import org.apache.syncope.client.console.commons.status.StatusBean;
-import org.apache.syncope.client.console.pages.BasePage;
-import org.apache.syncope.client.console.panels.MultilevelPanel;
-import org.apache.syncope.client.console.rest.AbstractAnyRestClient;
-import org.apache.syncope.client.console.rest.RestClient;
-import org.apache.syncope.client.console.rest.UserRestClient;
-import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BulkActionResultColumn;
-import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
-import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
-import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink.ActionType;
-import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
-import org.apache.syncope.common.lib.to.BulkAction;
-import org.apache.syncope.common.lib.to.BulkActionResult;
-import org.apache.syncope.common.lib.to.ExecTO;
-import org.apache.syncope.common.lib.types.StandardEntitlement;
-import org.apache.syncope.common.lib.to.BulkActionResult.Status;
-import org.apache.wicket.ajax.AjaxRequestTarget;
-import org.apache.wicket.extensions.ajax.markup.html.repeater.data.table.AjaxFallbackDefaultDataTable;
-import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
-import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
-import org.apache.wicket.markup.html.WebMarkupContainer;
-import org.apache.wicket.model.CompoundPropertyModel;
-import org.apache.wicket.model.IModel;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.BeanUtils;
-
-public class BulkContent<T extends Serializable, S> extends MultilevelPanel.SecondLevel {
-
-    private static final long serialVersionUID = 4114026480146090963L;
-
-    protected static final Logger LOG = LoggerFactory.getLogger(BulkContent.class);
-
-    public BulkContent(
-            final BaseModal<?> modal,
-            final Collection<T> items,
-            final List<IColumn<T, S>> columns,
-            final Collection<ActionLink.ActionType> actions,
-            final RestClient bulkActionExecutor,
-            final String keyFieldName) {
-
-        this(MultilevelPanel.SECOND_LEVEL_ID, modal, items, columns, actions, bulkActionExecutor, keyFieldName);
-    }
-
-    public BulkContent(
-            final String id,
-            final BaseModal<?> modal,
-            final Collection<T> items,
-            final List<IColumn<T, S>> columns,
-            final Collection<ActionLink.ActionType> actions,
-            final RestClient bulkActionExecutor,
-            final String keyFieldName) {
-
-        super(id);
-
-        final WebMarkupContainer container = new WebMarkupContainer("container");
-        container.setOutputMarkupId(true);
-        add(container);
-
-        final SortableDataProvider<T, S> dataProvider = new SortableDataProvider<T, S>() {
-
-            private static final long serialVersionUID = 5291903859908641954L;
-
-            @Override
-            public Iterator<? extends T> iterator(final long first, final long count) {
-                return items.iterator();
-            }
-
-            @Override
-            public long size() {
-                return items.size();
-            }
-
-            @Override
-            public IModel<T> model(final T object) {
-                return new CompoundPropertyModel<>(object);
-            }
-        };
-
-        container.add(new AjaxFallbackDefaultDataTable<>(
-                "selectedObjects",
-                columns,
-                dataProvider,
-                Integer.MAX_VALUE).setMarkupId("selectedObjects").setVisible(items != null && !items.isEmpty()));
-
-        final ActionsPanel<Serializable> actionPanel = new ActionsPanel<>("actions", null);
-        container.add(actionPanel);
-
-        for (ActionLink.ActionType action : actions) {
-            final ActionType actionToBeAddresed = action;
-
-            actionPanel.add(new ActionLink<Serializable>() {
-
-                private static final long serialVersionUID = -3722207913631435501L;
-
-                @Override
-                protected boolean statusCondition(final Serializable modelObject) {
-                    return items != null && !items.isEmpty();
-                }
-
-                @Override
-                public void onClick(final AjaxRequestTarget target, final Serializable ignore) {
-                    try {
-                        if (items == null || items.isEmpty()) {
-                            throw new IllegalArgumentException("Invalid items");
-                        }
-
-                        BulkActionResult res = null;
-                        try {
-                            T singleItem = items.iterator().next();
-                            if (singleItem instanceof StatusBean) {
-                                throw new IllegalArgumentException("Invalid items");
-                            }
-
-                            if (singleItem instanceof ExecTO) {
-                                res = new BulkActionResult();
-                                final Map<String, Status> results = res.getResults();
-                                items.forEach(item -> {
-                                    ExecTO exec = ExecTO.class.cast(item);
-                                    String key = exec.getKey();
-
-                                    try {
-                                        bulkActionExecutor.getClass().getMethod("deleteExecution",
-                                                String.class).invoke(bulkActionExecutor, exec.getKey());
-                                        results.put(String.valueOf(key), BulkActionResult.Status.SUCCESS);
-                                    } catch (Exception e) {
-                                        LOG.error("Error deleting execution {} of task {}", exec.getKey(), key, e);
-                                        results.put(String.valueOf(key), BulkActionResult.Status.FAILURE);
-                                    }
-                                });
-                            } else {
-                                final BulkAction bulkAction = new BulkAction();
-                                bulkAction.setType(BulkAction.Type.valueOf(actionToBeAddresed.name()));
-                                items.forEach(item -> {
-                                    try {
-                                        bulkAction.getTargets().add(getTargetId(item, keyFieldName).toString());
-                                    } catch (IllegalAccessException | InvocationTargetException e) {
-                                        LOG.error("Error retrieving item id {}", keyFieldName, e);
-                                    }
-                                });
-                                res = BulkActionResult.class.cast(
-                                        bulkActionExecutor.getClass().getMethod("bulkAction",
-                                                BulkAction.class).invoke(bulkActionExecutor, bulkAction));
-                            }
-
-                        } catch (IllegalArgumentException biae) {
-                            if (!(items.iterator().next() instanceof StatusBean)) {
-                                throw new IllegalArgumentException("Invalid items");
-                            }
-
-                            if (!(bulkActionExecutor instanceof AbstractAnyRestClient)) {
-                                throw new IllegalArgumentException("Invalid bulk action executor");
-                            }
-
-                            final AbstractAnyRestClient<?> anyRestClient = AbstractAnyRestClient.class.
-                                    cast(bulkActionExecutor);
-
-                            // Group bean information by anyKey
-                            final Map<String, List<StatusBean>> beans = new HashMap<>();
-                            items.stream().map(bean -> StatusBean.class.cast(bean)).
-                                    forEachOrdered(sb -> {
-                                        final List<StatusBean> sblist;
-                                        if (beans.containsKey(sb.getKey())) {
-                                            sblist = beans.get(sb.getKey());
-                                        } else {
-                                            sblist = new ArrayList<>();
-                                            beans.put(sb.getKey(), sblist);
-                                        }
-                                        sblist.add(sb);
-                                    });
-
-                            for (Map.Entry<String, List<StatusBean>> entry : beans.entrySet()) {
-                                final String etag = anyRestClient.read(entry.getKey()).getETagValue();
-                                switch (actionToBeAddresed) {
-                                    case DEPROVISION:
-                                        res = anyRestClient.deprovision(etag, entry.getKey(), entry.getValue());
-                                        break;
-                                    case UNASSIGN:
-                                        res = anyRestClient.unassign(etag, entry.getKey(), entry.getValue());
-                                        break;
-                                    case UNLINK:
-                                        res = anyRestClient.unlink(etag, entry.getKey(), entry.getValue());
-                                        break;
-                                    case ASSIGN:
-                                        res = anyRestClient.assign(etag, entry.getKey(), entry.getValue());
-                                        break;
-                                    case LINK:
-                                        res = anyRestClient.link(etag, entry.getKey(), entry.getValue());
-                                        break;
-                                    case PROVISION:
-                                        res = anyRestClient.provision(etag, entry.getKey(), entry.getValue());
-                                        break;
-                                    case REACTIVATE:
-                                        res = ((UserRestClient) anyRestClient).
-                                                reactivate(etag, entry.getKey(), entry.getValue());
-                                        break;
-                                    case SUSPEND:
-                                        res = ((UserRestClient) anyRestClient).
-                                                suspend(etag, entry.getKey(), entry.getValue());
-                                        break;
-                                    default:
-                                }
-                            }
-                        }
-
-                        if (modal != null) {
-                            modal.changeCloseButtonLabel(getString("close", null, "Close"), target);
-                        }
-
-                        final List<IColumn<T, S>> newColumnList = new ArrayList<>(columns);
-                        newColumnList.add(newColumnList.size(), new BulkActionResultColumn<>(res, keyFieldName));
-
-                        container.addOrReplace(new AjaxFallbackDefaultDataTable<>(
-                                "selectedObjects",
-                                newColumnList,
-                                dataProvider,
-                                Integer.MAX_VALUE).setVisible(!items.isEmpty()));
-
-                        actionPanel.setEnabled(false);
-                        actionPanel.setVisible(false);
-                        target.add(container);
-                        target.add(actionPanel);
-
-                        SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED));
-                    } catch (IllegalArgumentException | NoSuchMethodException | SecurityException
-                            | IllegalAccessException | InvocationTargetException e) {
-                        LOG.error("Bulk action failure", e);
-                        SyncopeConsoleSession.get().error(
-                                "Operation " + actionToBeAddresed.getActionId() + " not supported");
-                    }
-                    ((BasePage) getPage()).getNotificationPanel().refresh(target);
-                }
-            }, action, StandardEntitlement.CONFIGURATION_LIST).hideLabel();
-        }
-    }
-
-    private Object getTargetId(final Object target, final String idFieldName)
-            throws IllegalAccessException, InvocationTargetException {
-
-        return BeanUtils.getPropertyDescriptor(target.getClass(), idFieldName).
-                getReadMethod().invoke(target, new Object[0]);
-    }
-}
index 2fceb73..2bfd9ed 100644 (file)
@@ -29,6 +29,8 @@ public final class Constants {
 
     public static final String SYNCOPE = "syncope";
 
+    public static final String UNKNOWN = "UNKNOWN";
+
     public static final String ROLE_AUTHENTICATED = "AUTHENTICATED";
 
     public static final String MENU_COLLAPSE = "MENU_COLLAPSE";
index 85df442..e63ea0d 100644 (file)
@@ -30,7 +30,6 @@ import org.apache.syncope.client.console.commons.ConnIdSpecialName;
 import org.apache.syncope.client.console.commons.Constants;
 import org.apache.syncope.client.console.panels.LabelPanel;
 import org.apache.syncope.client.console.rest.ReconciliationRestClient;
-import org.apache.syncope.common.lib.patch.PasswordPatch;
 import org.apache.syncope.common.lib.patch.StatusPatch;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.to.AttrTO;
@@ -38,7 +37,7 @@ import org.apache.syncope.common.lib.to.ConnObjectTO;
 import org.apache.syncope.common.lib.to.RealmTO;
 import org.apache.syncope.common.lib.to.ReconStatus;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
-import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
+import org.apache.syncope.common.lib.types.ExecStatus;
 import org.apache.wicket.markup.ComponentTag;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.panel.Panel;
@@ -89,31 +88,27 @@ public final class StatusUtils implements Serializable {
     }
 
     public static StatusBean getStatusBean(
-            final RealmTO anyTO,
-            final String resourceName,
-            final ConnObjectTO objectTO) {
-
-        final StatusBean statusBean = new StatusBean(anyTO, resourceName);
+            final RealmTO realmTO,
+            final String resource,
+            final ConnObjectTO connObjectTO) {
 
-        if (objectTO != null) {
-            final Boolean enabled = isEnabled(objectTO);
+        StatusBean statusBean = new StatusBean(realmTO, resource);
 
-            final Status status = enabled == null
+        if (connObjectTO != null) {
+            Boolean enabled = isEnabled(connObjectTO);
+            statusBean.setStatus(enabled == null
                     ? Status.ACTIVE
                     : enabled
                             ? Status.ACTIVE
-                            : Status.SUSPENDED;
-
-            String connObjectLink = getConnObjectLink(objectTO);
+                            : Status.SUSPENDED);
 
-            statusBean.setStatus(status);
-            statusBean.setConnObjectLink(connObjectLink);
+            statusBean.setConnObjectLink(getConnObjectLink(connObjectTO));
         }
 
         return statusBean;
     }
 
-    private static Boolean isEnabled(final ConnObjectTO objectTO) {
+    public static Boolean isEnabled(final ConnObjectTO objectTO) {
         Optional<AttrTO> status = objectTO.getAttr(ConnIdSpecialName.ENABLE);
         return status.isPresent() && status.get().getValues() != null && !status.get().getValues().isEmpty()
                 ? Boolean.valueOf(status.get().getValues().get(0))
@@ -127,36 +122,18 @@ public final class StatusUtils implements Serializable {
                 : null;
     }
 
-    public static PasswordPatch buildPasswordPatch(final String password, final Collection<StatusBean> statuses) {
-        PasswordPatch.Builder builder = new PasswordPatch.Builder();
-        builder.value(password);
-
-        statuses.forEach((status) -> {
-            if (Constants.SYNCOPE.equalsIgnoreCase(status.getResource())) {
-                builder.onSyncope(true);
-            } else {
-                builder.resource(status.getResource());
-            }
-        });
-        return builder.build();
-    }
-
-    public static StatusPatch buildStatusPatch(final Collection<StatusBean> statuses) {
-        return buildStatusPatch(statuses, null);
-    }
-
-    public static StatusPatch buildStatusPatch(final Collection<StatusBean> statuses, final Boolean enable) {
+    public static StatusPatch.Builder statusPatch(final Collection<StatusBean> statuses) {
         StatusPatch.Builder builder = new StatusPatch.Builder();
         builder.onSyncope(false);
-        statuses.forEach((status) -> {
-            if ("syncope".equalsIgnoreCase(status.getResource())) {
+        statuses.forEach(status -> {
+            if (Constants.SYNCOPE.equalsIgnoreCase(status.getResource())) {
                 builder.onSyncope(true);
             } else {
                 builder.resource(status.getResource());
             }
         });
 
-        return builder.build();
+        return builder;
     }
 
     public static Panel getStatusImagePanel(final String componentId, final Status status) {
@@ -214,11 +191,11 @@ public final class StatusUtils implements Serializable {
         return getLabel(componentId, alt, title, clazz);
     }
 
-    public static Panel getStatusImagePanel(final String componentId, final PropagationTaskExecStatus status) {
+    public static Panel getStatusImagePanel(final String componentId, final ExecStatus status) {
         return new LabelPanel(componentId, getStatusImage("label", status));
     }
 
-    public static Label getStatusImage(final String componentId, final PropagationTaskExecStatus status) {
+    public static Label getStatusImage(final String componentId, final ExecStatus status) {
         final String alt, title, clazz;
 
         switch (status) {
index 95137d1..d5289cd 100644 (file)
@@ -186,7 +186,7 @@ public class MailTemplateDirectoryPanel
     }
 
     @Override
-    protected Collection<ActionLink.ActionType> getBulkActions() {
+    protected Collection<ActionLink.ActionType> getBatches() {
         return Collections.<ActionLink.ActionType>emptyList();
     }
 
index a27a650..a567e22 100644 (file)
@@ -161,7 +161,7 @@ public class NotificationDirectoryPanel
     }
 
     @Override
-    protected Collection<ActionLink.ActionType> getBulkActions() {
+    protected Collection<ActionLink.ActionType> getBatches() {
         return Collections.<ActionLink.ActionType>emptyList();
     }
 
index c01da4c..f51d647 100644 (file)
@@ -68,7 +68,7 @@ public class Reports extends BasePage {
                     private static final long serialVersionUID = -2195387360323687302L;
 
                     @Override
-                    protected void viewTask(final ReportTO reportTO, final AjaxRequestTarget target) {
+                    protected void viewReport(final ReportTO reportTO, final AjaxRequestTarget target) {
                         mlp.next(
                                 new StringResourceModel("report.view", this, new Model<>(reportTO)).getObject(),
                                 new ReportExecutionDetails(reportTO, getPageReference()),
index 29924fc..569f571 100644 (file)
@@ -138,7 +138,7 @@ public class AccessTokenDirectoryPanel
     }
 
     @Override
-    protected Collection<ActionLink.ActionType> getBulkActions() {
+    protected Collection<ActionLink.ActionType> getBatches() {
         return Collections.emptyList();
     }
 
index 7ea7baa..17717c5 100644 (file)
@@ -45,7 +45,7 @@ public class ActionDataTablePanel<T extends Serializable, S> extends DataTablePa
 
     private static final String CANCEL = "cancel";
 
-    private final Form<T> bulkActionForm;
+    private final Form<T> batchForm;
 
     private final ActionsPanel<Serializable> actionPanel;
 
@@ -57,8 +57,8 @@ public class ActionDataTablePanel<T extends Serializable, S> extends DataTablePa
 
         super(id);
 
-        bulkActionForm = new Form<>("groupForm");
-        add(bulkActionForm);
+        batchForm = new Form<>("groupForm");
+        add(batchForm);
 
         group = new ActionTableCheckGroup<T>("checkgroup", model) {
 
@@ -78,14 +78,14 @@ public class ActionDataTablePanel<T extends Serializable, S> extends DataTablePa
                 // triggers AJAX form submit
             }
         });
-        bulkActionForm.add(group);
+        batchForm.add(group);
 
         columns.add(0, new CheckGroupColumn<>(group));
         dataTable = new AjaxFallbackDataTable<>("dataTable", columns, dataProvider, rowsPerPage, this);
         group.add(dataTable);
 
         final WebMarkupContainer actionPanelContainer = new WebMarkupContainer("actionPanelContainer");
-        bulkActionForm.add(actionPanelContainer);
+        batchForm.add(actionPanelContainer);
 
         actionPanel = new ActionsPanel<>("actions", null);
         actionPanelContainer.add(actionPanel);
@@ -94,7 +94,7 @@ public class ActionDataTablePanel<T extends Serializable, S> extends DataTablePa
             actionPanelContainer.add(new AttributeModifier("style", "display: none"));
         }
 
-        bulkActionForm.add(new IndicatingAjaxButton(CANCEL, new ResourceModel(CANCEL)) {
+        batchForm.add(new IndicatingAjaxButton(CANCEL, new ResourceModel(CANCEL)) {
 
             private static final long serialVersionUID = -2341391430136818025L;
 
@@ -123,7 +123,7 @@ public class ActionDataTablePanel<T extends Serializable, S> extends DataTablePa
         };
 
         cancel.setDefaultFormProcessing(false);
-        bulkActionForm.addOrReplace(cancel);
+        batchForm.addOrReplace(cancel);
     }
 
     public Collection<T> getModelObject() {
index 885a3d5..0069c52 100644 (file)
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.client.console.panels;
 
+import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -26,8 +27,8 @@ import java.util.List;
 import org.apache.syncope.client.console.rest.BaseRestClient;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
 import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxFormChoiceComponentUpdatingBehavior;
-import org.apache.syncope.client.console.bulk.BulkActionModal;
-import org.apache.syncope.client.console.bulk.BulkContent;
+import org.apache.syncope.client.console.batch.BatchModal;
+import org.apache.syncope.client.console.batch.BatchContent;
 import org.apache.syncope.client.console.pages.BasePage;
 import org.apache.syncope.client.console.panels.DirectoryPanel.EventDataWrapper;
 import org.apache.syncope.client.console.rest.RestClient;
@@ -68,9 +69,9 @@ public final class AjaxDataTablePanel<T extends Serializable, S> extends DataTab
 
         private int rowsPerPage = 10;
 
-        private final Collection<ActionLink.ActionType> bulkActions = new ArrayList<>();
+        private final Collection<ActionLink.ActionType> batches = new ArrayList<>();
 
-        private RestClient bulkActionExecutor;
+        private RestClient batchExecutor;
 
         private String itemKeyField;
 
@@ -96,13 +97,13 @@ public final class AjaxDataTablePanel<T extends Serializable, S> extends DataTab
             return this;
         }
 
-        public Builder<T, S> addBulkAction(final ActionLink.ActionType actionType) {
-            bulkActions.add(actionType);
+        public Builder<T, S> addBatch(final ActionLink.ActionType actionType) {
+            batches.add(actionType);
             return this;
         }
 
-        public Builder<T, S> setBulkActionExecutor(final BaseRestClient bulkActionExecutor) {
-            this.bulkActionExecutor = bulkActionExecutor;
+        public Builder<T, S> setBatchExecutor(final BaseRestClient batchExecutor) {
+            this.batchExecutor = batchExecutor;
             return this;
         }
 
@@ -111,15 +112,16 @@ public final class AjaxDataTablePanel<T extends Serializable, S> extends DataTab
             return this;
         }
 
-        public Builder<T, S> setBulkActions(
-                final Collection<ActionLink.ActionType> bulkActions,
-                final RestClient bulkActionExecutor,
+        public Builder<T, S> setBatches(
+                final Collection<ActionLink.ActionType> batches,
+                final RestClient batchExecutor,
                 final String itemKeyField) {
-            this.bulkActions.clear();
-            if (bulkActions != null) {
-                this.bulkActions.addAll(bulkActions);
+
+            this.batches.clear();
+            if (batches != null) {
+                this.batches.addAll(batches);
             }
-            this.bulkActionExecutor = bulkActionExecutor;
+            this.batchExecutor = batchExecutor;
             this.itemKeyField = itemKeyField;
             return this;
         }
@@ -147,8 +149,8 @@ public final class AjaxDataTablePanel<T extends Serializable, S> extends DataTab
             return this;
         }
 
-        private boolean isBulkEnabled() {
-            return checkBoxEnabled && bulkActionExecutor != null && !bulkActions.isEmpty();
+        private boolean isBatchEnabled() {
+            return checkBoxEnabled && batchExecutor != null && !batches.isEmpty();
         }
 
         public void setMultiLevelPanel(final BaseModal<?> baseModal, final MultilevelPanel multiLevelPanel) {
@@ -168,16 +170,17 @@ public final class AjaxDataTablePanel<T extends Serializable, S> extends DataTab
     private AjaxDataTablePanel(final String id, final Builder<T, S> builder) {
         super(id);
 
-        final BaseModal<T> bulkModal = new BaseModal<>("bulkModal");
-        add(bulkModal);
+        BaseModal<T> batchModal = new BaseModal<>("batchModal");
+        batchModal.size(Modal.Size.Large);
+        add(batchModal);
 
-        bulkModal.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() {
+        batchModal.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() {
 
             private static final long serialVersionUID = 8804221891699487149L;
 
             @Override
             public void onClose(final AjaxRequestTarget target) {
-                bulkModal.show(false);
+                batchModal.show(false);
 
                 EventDataWrapper data = new EventDataWrapper();
                 data.setTarget(target);
@@ -191,11 +194,11 @@ public final class AjaxDataTablePanel<T extends Serializable, S> extends DataTab
             }
         });
 
-        Fragment fragment = new Fragment("tablePanel", "bulkAvailable", this);
+        Fragment fragment = new Fragment("tablePanel", "batchAvailable", this);
         add(fragment);
 
-        Form<T> bulkActionForm = new Form<>("groupForm");
-        fragment.add(bulkActionForm);
+        Form<T> batchForm = new Form<>("groupForm");
+        fragment.add(batchForm);
 
         group = new CheckGroup<>("checkgroup", model);
         group.add(new IndicatorAjaxFormChoiceComponentUpdatingBehavior() {
@@ -210,7 +213,7 @@ public final class AjaxDataTablePanel<T extends Serializable, S> extends DataTab
                 });
             }
         });
-        bulkActionForm.add(group);
+        batchForm.add(group);
 
         if (builder.checkBoxEnabled) {
             builder.columns.add(0, new CheckGroupColumn<>(group));
@@ -237,7 +240,7 @@ public final class AjaxDataTablePanel<T extends Serializable, S> extends DataTab
 
         group.add(dataTable);
 
-        fragment.add(new IndicatingAjaxButton("bulkActionLink", bulkActionForm) {
+        fragment.add(new IndicatingAjaxButton("batchLink", batchForm) {
 
             private static final long serialVersionUID = 382302811235019988L;
 
@@ -249,40 +252,39 @@ public final class AjaxDataTablePanel<T extends Serializable, S> extends DataTab
                 }
 
                 if (builder.multiLevelPanel == null) {
-                    bulkModal.header(new ResourceModel("bulk.action"));
-                    bulkModal.changeCloseButtonLabel(getString("cancel", null, "Cancel"), target);
+                    batchModal.header(new ResourceModel("batch"));
+                    batchModal.changeCloseButtonLabel(getString("cancel", null, "Cancel"), target);
 
-                    target.add(bulkModal.setContent(new BulkActionModal<>(
-                            bulkModal,
+                    target.add(batchModal.setContent(new BatchModal<>(
+                            batchModal,
                             builder.pageRef,
                             new ArrayList<>(group.getModelObject()),
                             builder.columns.size() == 1
                             ? builder.columns
                             // serialization problem with sublist only
                             : new ArrayList<>(builder.columns.subList(1, builder.columns.size())),
-                            builder.bulkActions,
-                            builder.bulkActionExecutor,
+                            builder.batches,
+                            builder.batchExecutor,
                             builder.itemKeyField)));
 
-                    bulkModal.show(true);
+                    batchModal.show(true);
                 } else {
-                    builder.multiLevelPanel.next(
-                            getString("bulk.action"),
-                            new BulkContent<>(
+                    builder.multiLevelPanel.next(getString("batch"),
+                            new BatchContent<>(
                                     builder.baseModal,
                                     new ArrayList<>(group.getModelObject()),
                                     builder.columns.size() == 1
                                     ? builder.columns
                                     // serialization problem with sublist only
                                     : new ArrayList<>(builder.columns.subList(1, builder.columns.size())),
-                                    builder.bulkActions,
-                                    builder.bulkActionExecutor,
+                                    builder.batches,
+                                    builder.batchExecutor,
                                     builder.itemKeyField),
                             target);
                 }
                 group.setModelObject(Collections.<T>emptyList());
                 target.add(group);
             }
-        }.setEnabled(builder.isBulkEnabled()).setVisible(builder.isBulkEnabled()));
+        }.setEnabled(builder.isBatchEnabled()).setVisible(builder.isBatchEnabled()));
     }
 }
index bea77ae..8fdc994 100644 (file)
@@ -221,12 +221,10 @@ public abstract class AnyDirectoryPanel<A extends AnyTO, E extends AbstractAnyRe
     }
 
     @Override
-    protected Collection<ActionLink.ActionType> getBulkActions() {
-        List<ActionLink.ActionType> bulkActions = new ArrayList<>();
-
-        bulkActions.add(ActionLink.ActionType.DELETE);
-
-        return bulkActions;
+    protected Collection<ActionLink.ActionType> getBatches() {
+        List<ActionLink.ActionType> batches = new ArrayList<>();
+        batches.add(ActionLink.ActionType.DELETE);
+        return batches;
     }
 
     public interface AnyDirectoryPanelBuilder extends Serializable {
index 8754d01..280e90b 100644 (file)
@@ -110,7 +110,7 @@ public class AnyTypeClassesPanel extends TypesDirectoryPanel<
     }
 
     @Override
-    protected Collection<ActionLink.ActionType> getBulkActions() {
+    protected Collection<ActionLink.ActionType> getBatches() {
         return Collections.<ActionLink.ActionType>emptyList();
     }
 
index 132a05a..97158e5 100644 (file)
@@ -109,7 +109,7 @@ public class AnyTypesPanel extends TypesDirectoryPanel<AnyTypeTO, AnyTypesPanel.
     }
 
     @Override
-    protected Collection<ActionLink.ActionType> getBulkActions() {
+    protected Collection<ActionLink.ActionType> getBatches() {
         return Collections.<ActionLink.ActionType>emptyList();
     }
 
index ba9e033..5fe68f0 100644 (file)
@@ -214,7 +214,7 @@ public class ApplicationDirectoryPanel extends
     }
 
     @Override
-    protected Collection<ActionLink.ActionType> getBulkActions() {
+    protected Collection<ActionLink.ActionType> getBatches() {
         return Collections.<ActionLink.ActionType>emptyList();
     }
 
index ab053bf..271d104 100644 (file)
@@ -163,7 +163,7 @@ public abstract class ConnInstanceHistoryConfDirectoryPanel extends DirectoryPan
     }
 
     @Override
-    protected Collection<ActionType> getBulkActions() {
+    protected Collection<ActionType> getBatches() {
         return Collections.<ActionLink.ActionType>emptyList();
     }
 
index 22c104e..c2ef5e5 100644 (file)
@@ -297,8 +297,7 @@ public abstract class DirectoryPanel<
 
         }.
                 setColumns(getColumns()).
-                setRowsPerPage(rows).
-                setBulkActions(getBulkActions(), restClient, itemKeyFieldName).
+                setRowsPerPage(rows).setBatches(getBatches(), restClient, itemKeyFieldName).
                 setContainer(container);
 
         if (!checkBoxEnabled) {
@@ -397,7 +396,7 @@ public abstract class DirectoryPanel<
         }
     }
 
-    protected abstract Collection<ActionLink.ActionType> getBulkActions();
+    protected abstract Collection<ActionLink.ActionType> getBatches();
 
     public abstract static class Builder<T extends Serializable, W extends Serializable, E extends RestClient>
             extends WizardMgtPanel.Builder<W> {
index ebbacd4..6cc36c6 100644 (file)
@@ -156,7 +156,7 @@ public class DynRealmDirectoryPanel extends
     }
 
     @Override
-    protected Collection<ActionLink.ActionType> getBulkActions() {
+    protected Collection<ActionLink.ActionType> getBatches() {
         return Collections.<ActionLink.ActionType>emptyList();
     }
 
index 59c4901..ac5663b 100644 (file)
@@ -52,7 +52,7 @@ import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.AnyEntitlement;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
-import org.apache.syncope.common.lib.types.BulkMembersActionType;
+import org.apache.syncope.common.lib.types.ProvisionAction;
 import org.apache.syncope.common.lib.types.StandardEntitlement;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
@@ -274,7 +274,7 @@ public class GroupDirectoryPanel extends AnyDirectoryPanel<GroupTO, GroupRestCli
             @Override
             public void onClick(final AjaxRequestTarget target, final GroupTO ignore) {
                 try {
-                    restClient.bulkMembersAction(model.getObject().getKey(), BulkMembersActionType.PROVISION);
+                    restClient.provisionMembers(model.getObject().getKey(), ProvisionAction.PROVISION);
                     SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED));
                     target.add(container);
                 } catch (SyncopeClientException e) {
@@ -288,15 +288,14 @@ public class GroupDirectoryPanel extends AnyDirectoryPanel<GroupTO, GroupRestCli
                 String.format("%s,%s", StandardEntitlement.TASK_CREATE, StandardEntitlement.TASK_EXECUTE)).
                 setRealm(realm);
 
-        panel.add(
-                new ActionLink<GroupTO>() {
+        panel.add(new ActionLink<GroupTO>() {
 
             private static final long serialVersionUID = -7978723352517770644L;
 
             @Override
             public void onClick(final AjaxRequestTarget target, final GroupTO ignore) {
                 try {
-                    restClient.bulkMembersAction(model.getObject().getKey(), BulkMembersActionType.DEPROVISION);
+                    restClient.provisionMembers(model.getObject().getKey(), ProvisionAction.DEPROVISION);
                     SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED));
                     target.add(container);
                 } catch (SyncopeClientException e) {
index 174526c..6d2e0be 100644 (file)
@@ -185,7 +185,7 @@ public class ImplementationDirectoryPanel extends DirectoryPanel<
     }
 
     @Override
-    protected Collection<ActionLink.ActionType> getBulkActions() {
+    protected Collection<ActionLink.ActionType> getBatches() {
         return Collections.<ActionLink.ActionType>emptyList();
     }
 
index dbf222c..e2ebb57 100644 (file)
@@ -133,7 +133,7 @@ public class ParametersDirectoryPanel
     }
 
     @Override
-    protected Collection<ActionLink.ActionType> getBulkActions() {
+    protected Collection<ActionLink.ActionType> getBatches() {
         return Collections.<ActionLink.ActionType>singletonList(ActionLink.ActionType.DELETE);
     }
 
index 2444812..47c972d 100644 (file)
@@ -127,7 +127,7 @@ public class PrivilegeDirectoryPanel extends DirectoryPanel<
     }
 
     @Override
-    protected Collection<ActionType> getBulkActions() {
+    protected Collection<ActionType> getBatches() {
         return Collections.<ActionType>emptyList();
     }
 
index 95331ee..303ec8e 100644 (file)
@@ -49,7 +49,7 @@ import org.apache.syncope.common.lib.to.EntityTO;
 import org.apache.syncope.common.lib.to.PropagationStatus;
 import org.apache.syncope.common.lib.to.ProvisioningResult;
 import org.apache.syncope.common.lib.to.RealmTO;
-import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
+import org.apache.syncope.common.lib.types.ExecStatus;
 import org.apache.syncope.common.lib.types.StandardEntitlement;
 import org.apache.wicket.Component;
 import org.apache.wicket.PageReference;
@@ -204,7 +204,7 @@ public abstract class Realm extends WizardMgtPanel<RealmTO> {
         add(mlp);
 
         final PropagationStatus syncope = new PropagationStatus();
-        syncope.setStatus(PropagationTaskExecStatus.SUCCESS);
+        syncope.setStatus(ExecStatus.SUCCESS);
         syncope.setResource(Constants.SYNCOPE);
 
         List<PropagationStatus> propagations = new ArrayList<>();
@@ -250,8 +250,8 @@ public abstract class Realm extends WizardMgtPanel<RealmTO> {
             @Override
             protected boolean statusCondition(final PropagationStatus bean) {
                 return !Constants.SYNCOPE.equals(bean.getResource())
-                        && (PropagationTaskExecStatus.CREATED == bean.getStatus()
-                        || PropagationTaskExecStatus.SUCCESS == bean.getStatus());
+                        && (ExecStatus.CREATED == bean.getStatus()
+                        || ExecStatus.SUCCESS == bean.getStatus());
             }
 
             @Override
index 939ac8c..2df032d 100644 (file)
@@ -112,7 +112,7 @@ public class RelationshipTypesPanel extends TypesDirectoryPanel<
     }
 
     @Override
-    protected Collection<ActionLink.ActionType> getBulkActions() {
+    protected Collection<ActionLink.ActionType> getBatches() {
         return Collections.<ActionLink.ActionType>emptyList();
     }
 
index 1e34454..4adb141 100644 (file)
@@ -302,7 +302,7 @@ public class RemediationDirectoryPanel
     }
 
     @Override
-    protected Collection<ActionLink.ActionType> getBulkActions() {
+    protected Collection<ActionLink.ActionType> getBatches() {
         return Collections.<ActionLink.ActionType>emptyList();
     }
 
index 09a1cf9..a9a0f06 100644 (file)
@@ -164,7 +164,7 @@ public abstract class ResourceHistoryConfDirectoryPanel extends DirectoryPanel<
     }
 
     @Override
-    protected Collection<ActionType> getBulkActions() {
+    protected Collection<ActionType> getBatches() {
         return Collections.<ActionLink.ActionType>emptyList();
     }
 
index ed17bc3..c58c095 100644 (file)
@@ -260,7 +260,7 @@ public class RoleDirectoryPanel extends DirectoryPanel<RoleTO, RoleWrapper, Role
     }
 
     @Override
-    protected Collection<ActionLink.ActionType> getBulkActions() {
+    protected Collection<ActionLink.ActionType> getBatches() {
         return Collections.<ActionLink.ActionType>singletonList(ActionLink.ActionType.DELETE);
     }
 
index 1ced1aa..e03aff7 100644 (file)
@@ -120,7 +120,7 @@ public class SchemaTypePanel extends TypesDirectoryPanel<SchemaTO, SchemaProvide
     }
 
     @Override
-    protected Collection<ActionLink.ActionType> getBulkActions() {
+    protected Collection<ActionLink.ActionType> getBatches() {
         return Collections.<ActionLink.ActionType>emptyList();
     }
 
index 7660153..2f05b43 100644 (file)
@@ -119,7 +119,7 @@ public class SecurityQuestionsPanel extends DirectoryPanel<
     }
 
     @Override
-    protected Collection<ActionLink.ActionType> getBulkActions() {
+    protected Collection<ActionLink.ActionType> getBatches() {
         return Collections.<ActionLink.ActionType>emptyList();
     }
 
index 511fbfc..43ab301 100644 (file)
@@ -163,7 +163,7 @@ public class TypeExtensionDirectoryPanel
     }
 
     @Override
-    protected Collection<ActionLink.ActionType> getBulkActions() {
+    protected Collection<ActionLink.ActionType> getBatches() {
         return Collections.emptyList();
     }
 
index 5452853..b0edf68 100644 (file)
@@ -89,15 +89,13 @@ public class UserDirectoryPanel extends AnyDirectoryPanel<UserTO, UserRestClient
     }
 
     @Override
-    protected Collection<ActionType> getBulkActions() {
-        List<ActionType> bulkActions = new ArrayList<>();
-
-        bulkActions.add(ActionType.MUSTCHANGEPASSWORD);
-        bulkActions.add(ActionType.DELETE);
-        bulkActions.add(ActionType.SUSPEND);
-        bulkActions.add(ActionType.REACTIVATE);
-
-        return bulkActions;
+    protected Collection<ActionType> getBatches() {
+        List<ActionType> batches = new ArrayList<>();
+        batches.add(ActionType.MUSTCHANGEPASSWORD);
+        batches.add(ActionType.DELETE);
+        batches.add(ActionType.SUSPEND);
+        batches.add(ActionType.REACTIVATE);
+        return batches;
     }
 
     @Override
index a830311..a640367 100644 (file)
@@ -280,7 +280,7 @@ public class WorkflowDirectoryPanel extends DirectoryPanel<
     }
 
     @Override
-    protected Collection<ActionLink.ActionType> getBulkActions() {
+    protected Collection<ActionLink.ActionType> getBatches() {
         return Collections.emptyList();
     }
 
index 3ce4143..3380469 100644 (file)
@@ -67,7 +67,7 @@ public abstract class AnySelectionDirectoryPanel<A extends AnyTO, E extends Abst
     }
 
     @Override
-    protected Collection<ActionType> getBulkActions() {
+    protected Collection<ActionType> getBatches() {
         return Collections.<ActionType>emptyList();
     }
 
index aae9114..5f09529 100644 (file)
@@ -194,7 +194,7 @@ public abstract class PolicyDirectoryPanel<T extends PolicyTO>
     }
 
     @Override
-    protected Collection<ActionType> getBulkActions() {
+    protected Collection<ActionType> getBatches() {
         return Collections.<ActionType>emptyList();
     }
 
index 2b89ee4..3a14f41 100644 (file)
@@ -205,10 +205,8 @@ public class PolicyRuleDirectoryPanel<T extends PolicyTO> extends DirectoryPanel
     }
 
     @Override
-    protected Collection<ActionType> getBulkActions() {
-        final List<ActionType> bulkActions = new ArrayList<>();
-        bulkActions.add(ActionType.DELETE);
-        return bulkActions;
+    protected Collection<ActionType> getBatches() {
+        return Collections.emptyList();
     }
 
     @Override
index 5109da3..537f5fd 100644 (file)
@@ -47,6 +47,7 @@ import org.apache.syncope.common.lib.types.StandardEntitlement;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.to.JobTO;
 import org.apache.syncope.common.lib.to.ReportTO;
+import org.apache.wicket.Component;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
@@ -58,6 +59,7 @@ import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColu
 import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
 import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
 import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.repeater.Item;
 import org.apache.wicket.model.CompoundPropertyModel;
 import org.apache.wicket.model.IModel;
@@ -112,7 +114,7 @@ public abstract class ReportDirectoryPanel
 
         columns.add(new DatePropertyColumn<>(
                 new StringResourceModel("nextExec", this), null, "nextExec"));
-        
+
         columns.add(new DatePropertyColumn<>(
                 new StringResourceModel("start", this), "start", "start"));
 
@@ -135,13 +137,18 @@ public abstract class ReportDirectoryPanel
                     final String componentId,
                     final IModel<ReportTO> rowModel) {
 
-                JobTO jobTO = restClient.getJob(rowModel.getObject().getKey());
-                JobActionPanel panel = new JobActionPanel(
-                        componentId, jobTO, false, ReportDirectoryPanel.this, pageRef);
-                MetaDataRoleAuthorizationStrategy.authorize(panel, WebPage.ENABLE,
-                        String.format("%s,%s",
-                                StandardEntitlement.TASK_EXECUTE,
-                                StandardEntitlement.TASK_UPDATE));
+                Component panel;
+                try {
+                    JobTO jobTO = restClient.getJob(rowModel.getObject().getKey());
+                    panel = new JobActionPanel(componentId, jobTO, false, ReportDirectoryPanel.this, pageRef);
+                    MetaDataRoleAuthorizationStrategy.authorize(panel, WebPage.ENABLE,
+                            String.format("%s,%s",
+                                    StandardEntitlement.REPORT_EXECUTE,
+                                    StandardEntitlement.REPORT_UPDATE));
+                } catch (Exception e) {
+                    LOG.error("Could not get job for report {}", rowModel.getObject().getKey(), e);
+                    panel = new Label(componentId, Model.of());
+                }
                 cellItem.add(panel);
             }
 
@@ -218,7 +225,7 @@ public abstract class ReportDirectoryPanel
 
             @Override
             public void onClick(final AjaxRequestTarget target, final ReportTO ignore) {
-                viewTask(model.getObject(), target);
+                viewReport(model.getObject(), target);
             }
         }, ActionLink.ActionType.VIEW, StandardEntitlement.REPORT_READ);
 
@@ -257,11 +264,11 @@ public abstract class ReportDirectoryPanel
     }
 
     @Override
-    protected Collection<ActionType> getBulkActions() {
-        final List<ActionType> bulkActions = new ArrayList<>();
-        bulkActions.add(ActionType.EXECUTE);
-        bulkActions.add(ActionType.DELETE);
-        return bulkActions;
+    protected Collection<ActionType> getBatches() {
+        List<ActionType> batches = new ArrayList<>();
+        batches.add(ActionType.EXECUTE);
+        batches.add(ActionType.DELETE);
+        return batches;
     }
 
     @Override
@@ -274,7 +281,7 @@ public abstract class ReportDirectoryPanel
         return Constants.PREF_REPORT_TASKS_PAGINATOR_ROWS;
     }
 
-    protected abstract void viewTask(final ReportTO reportTO, final AjaxRequestTarget target);
+    protected abstract void viewReport(ReportTO reportTO, AjaxRequestTarget target);
 
     protected class ReportDataProvider extends DirectoryDataProvider<ReportTO> {
 
index 04a37a2..4cd32b7 100644 (file)
@@ -205,7 +205,7 @@ public class ReportTemplateDirectoryPanel
     }
 
     @Override
-    protected Collection<ActionLink.ActionType> getBulkActions() {
+    protected Collection<ActionLink.ActionType> getBatches() {
         return Collections.<ActionLink.ActionType>emptyList();
 
     }
index 6ac03e2..7b7edd1 100644 (file)
@@ -191,10 +191,8 @@ public class ReportletDirectoryPanel extends DirectoryPanel<
     }
 
     @Override
-    protected Collection<ActionType> getBulkActions() {
-        final List<ActionType> bulkActions = new ArrayList<>();
-        bulkActions.add(ActionType.DELETE);
-        return bulkActions;
+    protected Collection<ActionType> getBatches() {
+        return Collections.emptyList();
     }
 
     @Override
index 639a4eb..0aa2a2f 100644 (file)
  */
 package org.apache.syncope.client.console.rest;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.cxf.jaxrs.client.Client;
+import org.apache.cxf.jaxrs.client.WebClient;
 import org.apache.syncope.client.console.commons.status.StatusBean;
 import org.apache.syncope.client.console.commons.status.StatusUtils;
+import org.apache.syncope.client.lib.batch.BatchRequest;
 import org.apache.syncope.common.lib.patch.AssociationPatch;
 import org.apache.syncope.common.lib.patch.DeassociationPatch;
 import org.apache.syncope.common.lib.patch.StatusPatch;
 import org.apache.syncope.common.lib.to.AnyTO;
-import org.apache.syncope.common.lib.to.BulkAction;
-import org.apache.syncope.common.lib.to.BulkActionResult;
 import org.apache.syncope.common.lib.to.ProvisioningResult;
 import org.apache.syncope.common.lib.types.ResourceAssociationAction;
 import org.apache.syncope.common.lib.types.ResourceDeassociationAction;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.batch.BatchPayloadParser;
+import org.apache.syncope.common.rest.api.batch.BatchRequestItem;
+import org.apache.syncope.common.rest.api.batch.BatchResponseItem;
 import org.apache.syncope.common.rest.api.service.AnyService;
 import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
 
@@ -59,112 +72,103 @@ public abstract class AbstractAnyRestClient<TO extends AnyTO> extends BaseRestCl
         return result;
     }
 
-    public BulkActionResult unlink(final String etag, final String key, final List<StatusBean> statuses) {
-        BulkActionResult result;
-        synchronized (this) {
-            AnyService<?> service = getService(etag, getAnyServiceClass());
-
-            DeassociationPatch deassociationPatch = new DeassociationPatch.Builder().key(key).
-                    action(ResourceDeassociationAction.UNLINK).
-                    resources(StatusUtils.buildStatusPatch(statuses).getResources()).build();
-
-            result = service.deassociate(deassociationPatch).readEntity(BulkActionResult.class);
-
-            resetClient(getAnyServiceClass());
-        }
-        return result;
+    private List<BatchResponseItem> parseBatchResponse(final Response response) throws IOException {
+        return BatchPayloadParser.parse(
+                (InputStream) response.getEntity(), response.getMediaType(), new BatchResponseItem());
     }
 
-    public BulkActionResult link(final String etag, final String key, final List<StatusBean> statuses) {
-        BulkActionResult result;
-        synchronized (this) {
-            AnyService<?> service = getService(etag, getAnyServiceClass());
-
-            StatusPatch statusPatch = StatusUtils.buildStatusPatch(statuses);
+    public Map<String, String> associate(
+            final ResourceAssociationAction action,
+            final String etag,
+            final String key,
+            final List<StatusBean> statuses) {
 
-            AssociationPatch associationPatch = new AssociationPatch.Builder().key(key).
-                    action(ResourceAssociationAction.LINK).
-                    onSyncope(statusPatch.isOnSyncope()).
-                    resources(statusPatch.getResources()).build();
-
-            result = service.associate(associationPatch).readEntity(BulkActionResult.class);
-
-            resetClient(getAnyServiceClass());
-        }
-        return result;
-    }
-
-    public BulkActionResult deprovision(final String etag, final String key, final List<StatusBean> statuses) {
-        BulkActionResult result;
+        Map<String, String> result = new LinkedHashMap<>();
         synchronized (this) {
             AnyService<?> service = getService(etag, getAnyServiceClass());
+            Client client = WebClient.client(service);
+            List<String> accept = client.getHeaders().get(HttpHeaders.ACCEPT);
+            if (!accept.contains(RESTHeaders.MULTIPART_MIXED)) {
+                client.accept(RESTHeaders.MULTIPART_MIXED);
+            }
 
-            DeassociationPatch deassociationPatch = new DeassociationPatch.Builder().key(key).
-                    action(ResourceDeassociationAction.DEPROVISION).
-                    resources(StatusUtils.buildStatusPatch(statuses).getResources()).build();
-
-            result = service.deassociate(deassociationPatch).readEntity(BulkActionResult.class);
-
-            resetClient(getAnyServiceClass());
-        }
-        return result;
-    }
-
-    public BulkActionResult provision(final String etag, final String key, final List<StatusBean> statuses) {
-        BulkActionResult result;
-        synchronized (this) {
-            AnyService<?> service = getService(etag, getAnyServiceClass());
-
-            StatusPatch statusPatch = StatusUtils.buildStatusPatch(statuses);
+            StatusPatch statusPatch = StatusUtils.statusPatch(statuses).build();
 
             AssociationPatch associationPatch = new AssociationPatch.Builder().key(key).
-                    action(ResourceAssociationAction.PROVISION).
+                    action(action).
                     onSyncope(statusPatch.isOnSyncope()).
                     resources(statusPatch.getResources()).build();
-
-            result = service.associate(associationPatch).readEntity(BulkActionResult.class);
+            try {
+                List<BatchResponseItem> items = parseBatchResponse(service.associate(associationPatch));
+                for (int i = 0; i < items.size(); i++) {
+                    result.put(
+                            associationPatch.getResources().get(i),
+                            getStatus(items.get(i).getStatus()));
+                }
+            } catch (IOException e) {
+                LOG.error("While processing Batch response", e);
+            }
 
             resetClient(getAnyServiceClass());
         }
         return result;
     }
 
-    public BulkActionResult unassign(final String etag, final String key, final List<StatusBean> statuses) {
-        BulkActionResult result;
+    public Map<String, String> deassociate(
+            final ResourceDeassociationAction action,
+            final String etag,
+            final String key,
+            final List<StatusBean> statuses) {
+
+        Map<String, String> result = new LinkedHashMap<>();
         synchronized (this) {
             AnyService<?> service = getService(etag, getAnyServiceClass());
+            Client client = WebClient.client(service);
+            List<String> accept = client.getHeaders().get(HttpHeaders.ACCEPT);
+            if (!accept.contains(RESTHeaders.MULTIPART_MIXED)) {
+                client.accept(RESTHeaders.MULTIPART_MIXED);
+            }
 
             DeassociationPatch deassociationPatch = new DeassociationPatch.Builder().key(key).
-                    action(ResourceDeassociationAction.UNASSIGN).
-                    resources(StatusUtils.buildStatusPatch(statuses).getResources()).build();
-
-            result = service.deassociate(deassociationPatch).readEntity(BulkActionResult.class);
+                    action(action).
+                    resources(StatusUtils.statusPatch(statuses).build().getResources()).build();
+            try {
+                List<BatchResponseItem> items = parseBatchResponse(service.deassociate(deassociationPatch));
+                for (int i = 0; i < items.size(); i++) {
+                    result.put(
+                            deassociationPatch.getResources().get(i),
+                            getStatus(items.get(i).getStatus()));
+                }
+            } catch (IOException e) {
+                LOG.error("While processing Batch response", e);
+            }
 
             resetClient(getAnyServiceClass());
         }
         return result;
     }
 
-    public BulkActionResult assign(final String etag, final String key, final List<StatusBean> statuses) {
-        BulkActionResult result;
-        synchronized (this) {
-            AnyService<?> service = getService(etag, getAnyServiceClass());
-
-            StatusPatch statusPatch = StatusUtils.buildStatusPatch(statuses);
-
-            AssociationPatch associationPatch = new AssociationPatch.Builder().key(key).
-                    action(ResourceAssociationAction.ASSIGN).
-                    onSyncope(statusPatch.isOnSyncope()).
-                    resources(statusPatch.getResources()).build();
-
-            result = service.associate(associationPatch).readEntity(BulkActionResult.class);
-
-            resetClient(getAnyServiceClass());
+    public Map<String, String> batch(final BatchRequest batchRequest) {
+        List<BatchRequestItem> batchRequestItems = new ArrayList<>(batchRequest.getItems());
+
+        Map<String, String> result = new LinkedHashMap<>();
+        try {
+            List<BatchResponseItem> batchResponseItems = batchRequest.commit().getItems();
+            for (int i = 0; i < batchResponseItems.size(); i++) {
+                String status = getStatus(batchResponseItems.get(i).getStatus());
+                if (batchRequestItems.get(i).getRequestURI().endsWith("/status")) {
+                    result.put(StringUtils.substringAfterLast(
+                            StringUtils.substringBefore(batchRequestItems.get(i).getRequestURI(), "/status"), "/"),
+                            status);
+                } else {
+                    result.put(StringUtils.substringAfterLast(
+                            batchRequestItems.get(i).getRequestURI(), "/"), status);
+                }
+            }
+        } catch (IOException e) {
+            LOG.error("While processing Batch response", e);
         }
-        return result;
-    }
 
-    public BulkActionResult bulkAction(final BulkAction action) {
-        return getService(getAnyServiceClass()).bulk(action).readEntity(BulkActionResult.class);
+        return result;
     }
 }
index 4a780a6..ecea2ae 100644 (file)
 package org.apache.syncope.client.console.rest;
 
 import java.net.URI;
-
 import javax.ws.rs.core.HttpHeaders;
-
 import org.apache.cxf.jaxrs.client.WebClient;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.commons.Constants;
 import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.common.lib.search.OrderByClauseBuilder;
+import org.apache.syncope.common.lib.types.ExecStatus;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.service.JAXRSService;
 import org.apache.syncope.common.rest.api.service.SyncopeService;
@@ -82,4 +82,11 @@ public abstract class BaseRestClient implements RestClient {
                 header(HttpHeaders.AUTHORIZATION, "Bearer " + SyncopeConsoleSession.get().getJWT()).
                 get(resultClass);
     }
+
+    protected static String getStatus(final int httpStatus) {
+        ExecStatus execStatus = ExecStatus.fromHttpStatus(httpStatus);
+        return execStatus == null
+                ? Constants.UNKNOWN
+                : execStatus.name();
+    }
 }
index 8cde692..d8c9ff9 100644 (file)
@@ -20,6 +20,8 @@ package org.apache.syncope.client.console.rest;
 
 import java.util.Date;
 import java.util.List;
+import java.util.Map;
+import org.apache.syncope.client.lib.batch.BatchRequest;
 import org.apache.syncope.common.lib.to.ExecTO;
 import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
 
@@ -34,4 +36,6 @@ public interface ExecutionRestClient extends RestClient {
     List<ExecTO> listExecutions(String taskKey, int page, int size, SortParam<String> sort);
 
     int countExecutions(String taskKey);
+
+    Map<String, String> batch(BatchRequest batchRequest);
 }
index e5d642b..4669d4d 100644 (file)
@@ -24,7 +24,7 @@ import javax.ws.rs.core.Response;
 import org.apache.syncope.common.lib.patch.GroupPatch;
 import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.to.ProvisioningResult;
-import org.apache.syncope.common.lib.types.BulkMembersActionType;
+import org.apache.syncope.common.lib.types.ProvisionAction;
 import org.apache.syncope.common.rest.api.beans.AnyQuery;
 import org.apache.syncope.common.rest.api.service.AnyService;
 import org.apache.syncope.common.rest.api.service.GroupService;
@@ -80,7 +80,7 @@ public class GroupRestClient extends AbstractAnyRestClient<GroupTO> {
                         orderBy(toOrderBy(sort)).details(false).build()).getResult();
     }
 
-    public void bulkMembersAction(final String key, final BulkMembersActionType actionType) {
-        getService(GroupService.class).bulkMembersAction(key, actionType);
+    public void provisionMembers(final String key, final ProvisionAction actionType) {
+        getService(GroupService.class).provisionMembers(key, actionType);
     }
 }
index e65ce7e..8b60941 100644 (file)
  */
 package org.apache.syncope.client.console.rest;
 
+import static org.apache.syncope.client.console.rest.BaseRestClient.getStatus;
+
+import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.LinkedHashMap;
 import java.util.List;
-import javax.ws.rs.NotSupportedException;
+import java.util.Map;
 import javax.ws.rs.core.Response;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.common.lib.to.BulkAction;
-import org.apache.syncope.common.lib.to.BulkActionResult;
+import org.apache.syncope.client.lib.batch.BatchRequest;
 import org.apache.syncope.common.lib.to.ExecTO;
 import org.apache.syncope.common.lib.to.JobTO;
 import org.apache.syncope.common.lib.to.ReportTO;
@@ -35,6 +39,8 @@ import org.apache.syncope.common.lib.to.ReportTemplateTO;
 import org.apache.syncope.common.lib.types.JobAction;
 import org.apache.syncope.common.lib.types.ReportExecExportFormat;
 import org.apache.syncope.common.lib.types.ReportTemplateFormat;
+import org.apache.syncope.common.rest.api.batch.BatchRequestItem;
+import org.apache.syncope.common.rest.api.batch.BatchResponseItem;
 import org.apache.syncope.common.rest.api.beans.ExecQuery;
 import org.apache.syncope.common.rest.api.beans.ExecuteQuery;
 import org.apache.syncope.common.rest.api.service.ReportService;
@@ -155,25 +161,29 @@ public class ReportRestClient extends BaseRestClient
                 key, format, IOUtils.toInputStream(content, StandardCharsets.UTF_8));
     }
 
-    public BulkActionResult bulkAction(final BulkAction action) {
-        BulkActionResult result = new BulkActionResult();
+    @Override
+    public Map<String, String> batch(final BatchRequest batchRequest) {
+        List<BatchRequestItem> batchRequestItems = new ArrayList<>(batchRequest.getItems());
 
-        switch (action.getType()) {
-            case DELETE:
-                for (String target : action.getTargets()) {
-                    delete(target);
-                    result.getResults().put(target, BulkActionResult.Status.SUCCESS);
-                }
-                break;
-            case EXECUTE:
-                for (String target : action.getTargets()) {
-                    startExecution(target, null);
-                    result.getResults().put(target, BulkActionResult.Status.SUCCESS);
+        Map<String, String> result = new LinkedHashMap<>();
+        try {
+            List<BatchResponseItem> batchResponseItems = batchRequest.commit().getItems();
+            for (int i = 0; i < batchResponseItems.size(); i++) {
+                String status = getStatus(batchResponseItems.get(i).getStatus());
+
+                if (batchRequestItems.get(i).getRequestURI().contains("/execute")) {
+                    result.put(StringUtils.substringAfterLast(
+                            StringUtils.substringBefore(batchRequestItems.get(i).getRequestURI(), "/execute"), "/"),
+                            status);
+                } else {
+                    result.put(StringUtils.substringAfterLast(
+                            batchRequestItems.get(i).getRequestURI(), "/"), status);
                 }
-                break;
-            default:
-                throw new NotSupportedException(action.getType().name());
+            }
+        } catch (IOException e) {
+            LOG.error("While processing Batch response", e);
         }
+
         return result;
     }
 }
index e1f8f78..c8e135b 100644 (file)
@@ -24,12 +24,9 @@ import java.util.List;
 import javax.ws.rs.core.Response;
 import org.apache.commons.lang3.ObjectUtils;
 import org.apache.commons.lang3.tuple.Pair;
-import org.apache.syncope.common.lib.patch.ResourceDeassociationPatch;
-import org.apache.syncope.common.lib.to.BulkActionResult;
 import org.apache.syncope.common.lib.to.ConnObjectTO;
 import org.apache.syncope.common.lib.to.PagedConnObjectTOResult;
 import org.apache.syncope.common.lib.to.ResourceTO;
-import org.apache.syncope.common.lib.types.ResourceDeassociationAction;
 import org.apache.syncope.common.rest.api.beans.ConnObjectTOListQuery;
 import org.apache.syncope.common.rest.api.service.ResourceService;
 import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
@@ -116,19 +113,6 @@ public class ResourceRestClient extends BaseRestClient {
         getService(ResourceService.class).delete(name);
     }
 
-    public BulkActionResult bulkAssociationAction(
-            final String resourceName, final String anyTypeName,
-            final ResourceDeassociationAction action, final List<String> anyKeys) {
-
-        ResourceDeassociationPatch patch = new ResourceDeassociationPatch();
-        patch.setKey(resourceName);
-        patch.setAnyTypeKey(anyTypeName);
-        patch.setAction(action);
-        patch.getAnyKyes().addAll(anyKeys);
-
-        return getService(ResourceService.class).bulkDeassociation(patch);
-    }
-
     public void setLatestSyncToken(final String key, final String anyType) {
         getService(ResourceService.class).setLatestSyncToken(key, anyType);
     }
index 516ef7c..2e6692e 100644 (file)
  */
 package org.apache.syncope.client.console.rest;
 
+import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.lib.batch.BatchRequest;
 import org.apache.syncope.common.lib.to.TaskTO;
-import org.apache.syncope.common.lib.to.BulkAction;
-import org.apache.syncope.common.lib.to.BulkActionResult;
 import org.apache.syncope.common.lib.to.NotificationTaskTO;
 import org.apache.syncope.common.lib.to.PropagationTaskTO;
 import org.apache.syncope.common.lib.to.PushTaskTO;
@@ -34,6 +38,8 @@ import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.JobAction;
 import org.apache.syncope.common.lib.types.TaskType;
+import org.apache.syncope.common.rest.api.batch.BatchRequestItem;
+import org.apache.syncope.common.rest.api.batch.BatchResponseItem;
 import org.apache.syncope.common.rest.api.beans.ExecuteQuery;
 import org.apache.syncope.common.rest.api.beans.ExecQuery;
 import org.apache.syncope.common.rest.api.beans.TaskQuery;
@@ -228,7 +234,29 @@ public class TaskRestClient extends BaseRestClient implements ExecutionRestClien
         getService(TaskService.class).update(type, taskTO);
     }
 
-    public BulkActionResult bulkAction(final BulkAction action) {
-        return getService(TaskService.class).bulk(action);
+    @Override
+    public Map<String, String> batch(final BatchRequest batchRequest) {
+        List<BatchRequestItem> batchRequestItems = new ArrayList<>(batchRequest.getItems());
+
+        Map<String, String> result = new LinkedHashMap<>();
+        try {
+            List<BatchResponseItem> batchResponseItems = batchRequest.commit().getItems();
+            for (int i = 0; i < batchResponseItems.size(); i++) {
+                String status = getStatus(batchResponseItems.get(i).getStatus());
+
+                if (batchRequestItems.get(i).getRequestURI().contains("/execute")) {
+                    result.put(StringUtils.substringAfterLast(
+                            StringUtils.substringBefore(batchRequestItems.get(i).getRequestURI(), "/execute"), "/"),
+                            status);
+                } else {
+                    result.put(StringUtils.substringAfterLast(
+                            batchRequestItems.get(i).getRequestURI(), "/"), status);
+                }
+            }
+        } catch (IOException e) {
+            LOG.error("While processing Batch response", e);
+        }
+
+        return result;
     }
 }
index 514d970..11eb0fa 100644 (file)
  */
 package org.apache.syncope.client.console.rest;
 
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import javax.ws.rs.core.GenericType;
 import javax.ws.rs.core.Response;
 import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.commons.status.Status;
 import org.apache.syncope.client.console.commons.status.StatusBean;
 import org.apache.syncope.client.console.commons.status.StatusUtils;
 import org.apache.syncope.common.lib.patch.BooleanReplacePatchItem;
 import org.apache.syncope.common.lib.patch.StatusPatch;
 import org.apache.syncope.common.lib.patch.UserPatch;
-import org.apache.syncope.common.lib.to.BulkActionResult;
-import org.apache.syncope.common.lib.to.PropagationStatus;
 import org.apache.syncope.common.lib.to.ProvisioningResult;
 import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.ExecStatus;
 import org.apache.syncope.common.lib.types.StatusPatchType;
 import org.apache.syncope.common.rest.api.beans.AnyQuery;
 import org.apache.syncope.common.rest.api.service.AnyService;
@@ -85,69 +86,70 @@ public class UserRestClient extends AbstractAnyRestClient<UserTO> {
     }
 
     public ProvisioningResult<UserTO> mustChangePassword(final String etag, final boolean value, final String key) {
-        final UserPatch userPatch = new UserPatch();
+        UserPatch userPatch = new UserPatch();
         userPatch.setKey(key);
         userPatch.setMustChangePassword(new BooleanReplacePatchItem.Builder().value(value).build());
         return update(etag, userPatch);
     }
 
-    public BulkActionResult suspend(final String etag, final String userKey, final List<StatusBean> statuses) {
-        StatusPatch statusPatch = StatusUtils.buildStatusPatch(statuses, false);
-        statusPatch.setKey(userKey);
-        statusPatch.setType(StatusPatchType.SUSPEND);
+    private Map<String, String> status(
+            final StatusPatchType type, final String etag, final String userKey, final List<StatusBean> statuses) {
 
-        BulkActionResult bulkActionResult;
+        StatusPatch statusPatch = StatusUtils.statusPatch(statuses).key(userKey).type(type).build();
+
+        Map<String, String> results;
         synchronized (this) {
-            bulkActionResult = new BulkActionResult();
-            Map<String, BulkActionResult.Status> results = bulkActionResult.getResults();
-            UserService service = getService(etag, UserService.class);
+            ProvisioningResult<UserTO> provisioningResult = getService(etag, UserService.class).status(statusPatch).
+                    readEntity(new GenericType<ProvisioningResult<UserTO>>() {
+                    });
 
-            ProvisioningResult<UserTO> provisioningResult = service.status(statusPatch).readEntity(
-                    new GenericType<ProvisioningResult<UserTO>>() {
+            statuses.forEach(statusBean -> statusBean.setStatus(Status.UNDEFINED));
+
+            results = new HashMap<>();
+            provisioningResult.getPropagationStatuses().forEach(propagationStatus -> {
+                results.put(propagationStatus.getResource(), propagationStatus.getStatus().name());
+
+                if (propagationStatus.getAfterObj() != null) {
+                    Boolean enabled = StatusUtils.isEnabled(propagationStatus.getAfterObj());
+                    if (enabled != null) {
+                        statuses.stream().
+                                filter(statusBean -> propagationStatus.getResource().equals(statusBean.getResource())).
+                                findFirst().
+                                ifPresent(statusBean -> statusBean.setStatus(
+                                enabled ? Status.ACTIVE : Status.SUSPENDED));
+                    }
+                }
             });
-
+            statuses.stream().
+                    filter(statusBean -> Constants.SYNCOPE.equals(statusBean.getResource())).
+                    findFirst().
+                    ifPresent(statusBean -> statusBean.setStatus(
+                    "suspended".equalsIgnoreCase(provisioningResult.getEntity().getStatus())
+                    ? Status.SUSPENDED : Status.ACTIVE));
             if (statusPatch.isOnSyncope()) {
                 results.put(Constants.SYNCOPE,
-                        "suspended".equalsIgnoreCase(provisioningResult.getEntity().getStatus())
-                        ? BulkActionResult.Status.SUCCESS
-                        : BulkActionResult.Status.FAILURE);
+                        ("suspended".equalsIgnoreCase(provisioningResult.getEntity().getStatus())
+                        && type == StatusPatchType.SUSPEND)
+                        || ("active".equalsIgnoreCase(provisioningResult.getEntity().getStatus())
+                        && type == StatusPatchType.REACTIVATE)
+                                ? ExecStatus.SUCCESS.name()
+                                : ExecStatus.FAILURE.name());
             }
 
-            for (PropagationStatus status : provisioningResult.getPropagationStatuses()) {
-                results.put(status.getResource(), BulkActionResult.Status.valueOf(status.getStatus().name()));
-            }
             resetClient(UserService.class);
         }
-        return bulkActionResult;
+        return results;
     }
 
-    public BulkActionResult reactivate(final String etag, final String userKey, final List<StatusBean> statuses) {
-        StatusPatch statusPatch = StatusUtils.buildStatusPatch(statuses, true);
-        statusPatch.setKey(userKey);
-        statusPatch.setType(StatusPatchType.REACTIVATE);
-
-        BulkActionResult bulkActionResult;
-        synchronized (this) {
-            bulkActionResult = new BulkActionResult();
-            Map<String, BulkActionResult.Status> results = bulkActionResult.getResults();
-            UserService service = getService(etag, UserService.class);
+    public Map<String, String> suspend(
+            final String etag, final String userKey, final List<StatusBean> statuses) {
 
-            ProvisioningResult<UserTO> provisioningResult = service.status(statusPatch).readEntity(
-                    new GenericType<ProvisioningResult<UserTO>>() {
-            });
+        return status(StatusPatchType.SUSPEND, etag, userKey, statuses);
+    }
 
-            if (statusPatch.isOnSyncope()) {
-                results.put(Constants.SYNCOPE,
-                        "active".equalsIgnoreCase(provisioningResult.getEntity().getStatus())
-                        ? BulkActionResult.Status.SUCCESS
-                        : BulkActionResult.Status.FAILURE);
-            }
+    public Map<String, String> reactivate(
+            final String etag, final String userKey, final List<StatusBean> statuses) {
 
-            for (PropagationStatus status : provisioningResult.getPropagationStatuses()) {
-                results.put(status.getResource(), BulkActionResult.Status.valueOf(status.getStatus().name()));
-            }
-            resetClient(UserService.class);
-        }
-        return bulkActionResult;
+        return status(StatusPatchType.REACTIVATE, etag, userKey, statuses);
     }
 }
index bd54f00..fe67ff6 100644 (file)
@@ -247,20 +247,20 @@ public class AnyStatusDirectoryPanel
     }
 
     @Override
-    protected Collection<ActionLink.ActionType> getBulkActions() {
-        List<ActionLink.ActionType> bulkActions = new ArrayList<>();
+    protected Collection<ActionLink.ActionType> getBatches() {
+        List<ActionLink.ActionType> batches = new ArrayList<>();
         if (statusOnly) {
-            bulkActions.add(ActionLink.ActionType.SUSPEND);
-            bulkActions.add(ActionLink.ActionType.REACTIVATE);
+            batches.add(ActionLink.ActionType.SUSPEND);
+            batches.add(ActionLink.ActionType.REACTIVATE);
         } else {
-            bulkActions.add(ActionLink.ActionType.UNLINK);
-            bulkActions.add(ActionLink.ActionType.LINK);
-            bulkActions.add(ActionLink.ActionType.DEPROVISION);
-            bulkActions.add(ActionLink.ActionType.PROVISION);
-            bulkActions.add(ActionLink.ActionType.ASSIGN);
-            bulkActions.add(ActionLink.ActionType.UNASSIGN);
+            batches.add(ActionLink.ActionType.UNLINK);
+            batches.add(ActionLink.ActionType.LINK);
+            batches.add(ActionLink.ActionType.DEPROVISION);
+            batches.add(ActionLink.ActionType.PROVISION);
+            batches.add(ActionLink.ActionType.ASSIGN);
+            batches.add(ActionLink.ActionType.UNASSIGN);
         }
-        return bulkActions;
+        return batches;
     }
 
     @Override
index 8b621bc..bba8d2f 100644 (file)
@@ -215,15 +215,15 @@ public class ResourceStatusDirectoryPanel
     }
 
     @Override
-    protected Collection<ActionLink.ActionType> getBulkActions() {
-        List<ActionLink.ActionType> bulkActions = new ArrayList<>();
-        bulkActions.add(ActionLink.ActionType.UNLINK);
-        bulkActions.add(ActionLink.ActionType.LINK);
-        bulkActions.add(ActionLink.ActionType.DEPROVISION);
-        bulkActions.add(ActionLink.ActionType.PROVISION);
-        bulkActions.add(ActionLink.ActionType.ASSIGN);
-        bulkActions.add(ActionLink.ActionType.UNASSIGN);
-        return bulkActions;
+    protected Collection<ActionLink.ActionType> getBatches() {
+        List<ActionLink.ActionType> batches = new ArrayList<>();
+        batches.add(ActionLink.ActionType.UNLINK);
+        batches.add(ActionLink.ActionType.LINK);
+        batches.add(ActionLink.ActionType.DEPROVISION);
+        batches.add(ActionLink.ActionType.PROVISION);
+        batches.add(ActionLink.ActionType.ASSIGN);
+        batches.add(ActionLink.ActionType.UNASSIGN);
+        return batches;
     }
 
     @Override
index 1494e34..267987f 100644 (file)
@@ -157,10 +157,10 @@ public abstract class ExecutionsDirectoryPanel
     }
 
     @Override
-    protected Collection<ActionLink.ActionType> getBulkActions() {
-        final List<ActionLink.ActionType> bulkActions = new ArrayList<>();
-        bulkActions.add(ActionLink.ActionType.DELETE);
-        return bulkActions;
+    protected Collection<ActionLink.ActionType> getBatches() {
+        List<ActionLink.ActionType> batches = new ArrayList<>();
+        batches.add(ActionLink.ActionType.DELETE);
+        return batches;
     }
 
     protected class ExecProvider extends DirectoryDataProvider<ExecTO> {
index 656b707..3a584ca 100644 (file)
@@ -176,11 +176,11 @@ public abstract class NotificationTaskDirectoryPanel
     }
 
     @Override
-    protected Collection<ActionType> getBulkActions() {
-        final List<ActionType> bulkActions = new ArrayList<>();
-        bulkActions.add(ActionType.DELETE);
-        bulkActions.add(ActionType.EXECUTE);
-        return bulkActions;
+    protected Collection<ActionType> getBatches() {
+        List<ActionType> batches = new ArrayList<>();
+        batches.add(ActionType.DELETE);
+        batches.add(ActionType.EXECUTE);
+        return batches;
     }
 
     @Override
index 5298b22..67be4c8 100644 (file)
@@ -183,11 +183,11 @@ public abstract class PropagationTaskDirectoryPanel
     }
 
     @Override
-    protected Collection<ActionType> getBulkActions() {
-        final List<ActionType> bulkActions = new ArrayList<>();
-        bulkActions.add(ActionType.DELETE);
-        bulkActions.add(ActionType.EXECUTE);
-        return bulkActions;
+    protected Collection<ActionType> getBatches() {
+        List<ActionType> batches = new ArrayList<>();
+        batches.add(ActionType.DELETE);
+        batches.add(ActionType.EXECUTE);
+        return batches;
     }
 
     @Override
index 0577433..2f44287 100644 (file)
@@ -34,6 +34,7 @@ import org.apache.syncope.common.lib.to.PullTaskTO;
 import org.apache.syncope.common.lib.to.PushTaskTO;
 import org.apache.syncope.common.lib.types.StandardEntitlement;
 import org.apache.syncope.common.lib.types.TaskType;
+import org.apache.wicket.Component;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
@@ -43,6 +44,7 @@ import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColu
 import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
 import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
 import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.repeater.Item;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.Model;
@@ -137,13 +139,18 @@ public abstract class ProvisioningTaskDirectoryPanel<T extends ProvisioningTaskT
                     final String componentId,
                     final IModel<T> rowModel) {
 
-                JobTO jobTO = restClient.getJob(rowModel.getObject().getKey());
-                JobActionPanel panel = new JobActionPanel(
-                        componentId, jobTO, false, ProvisioningTaskDirectoryPanel.this, pageRef);
-                MetaDataRoleAuthorizationStrategy.authorize(panel, WebPage.ENABLE,
-                        String.format("%s,%s",
-                                StandardEntitlement.TASK_EXECUTE,
-                                StandardEntitlement.TASK_UPDATE));
+                Component panel;
+                try {
+                    JobTO jobTO = restClient.getJob(rowModel.getObject().getKey());
+                    panel = new JobActionPanel(componentId, jobTO, false, ProvisioningTaskDirectoryPanel.this, pageRef);
+                    MetaDataRoleAuthorizationStrategy.authorize(panel, WebPage.ENABLE,
+                            String.format("%s,%s",
+                                    StandardEntitlement.TASK_EXECUTE,
+                                    StandardEntitlement.TASK_UPDATE));
+                } catch (Exception e) {
+                    LOG.error("Could not get job for task {}", rowModel.getObject().getKey(), e);
+                    panel = new Label(componentId, Model.of());
+                }
                 cellItem.add(panel);
             }
 
index 70170c2..0dc445d 100644 (file)
@@ -281,12 +281,12 @@ public abstract class SchedTaskDirectoryPanel<T extends SchedTaskTO>
     }
 
     @Override
-    protected Collection<ActionType> getBulkActions() {
-        final List<ActionType> bulkActions = new ArrayList<>();
-        bulkActions.add(ActionType.DELETE);
-        bulkActions.add(ActionType.EXECUTE);
-        bulkActions.add(ActionType.DRYRUN);
-        return bulkActions;
+    protected Collection<ActionType> getBatches() {
+        List<ActionType> batches = new ArrayList<>();
+        batches.add(ActionType.DELETE);
+        batches.add(ActionType.EXECUTE);
+        batches.add(ActionType.DRYRUN);
+        return batches;
     }
 
     @Override
index 070085e..b586966 100644 (file)
@@ -69,8 +69,6 @@ public class Topology extends BasePage {
 
     public static final String CONNECTOR_SERVER_LOCATION_PREFIX = "connid://";
 
-    public static final String ROOT_NAME = "Syncope";
-
     private final ResourceRestClient resourceRestClient = new ResourceRestClient();
 
     private final ConnectorRestClient connectorRestClient = new ConnectorRestClient();
@@ -200,7 +198,8 @@ public class Topology extends BasePage {
         // -----------------------------------------
         // Add Syncope (root topologynode)
         // -----------------------------------------
-        final TopologyNode syncopeTopologyNode = new TopologyNode(ROOT_NAME, ROOT_NAME, TopologyNode.Kind.SYNCOPE);
+        String rootName = StringUtils.capitalize(Constants.SYNCOPE);
+        final TopologyNode syncopeTopologyNode = new TopologyNode(rootName, rootName, TopologyNode.Kind.SYNCOPE);
         syncopeTopologyNode.setX(origX);
         syncopeTopologyNode.setY(origY);
 
@@ -208,7 +207,7 @@ public class Topology extends BasePage {
         syncopeTopologyNode.setHost(uri.getHost());
         syncopeTopologyNode.setPort(uri.getPort());
 
-        body.add(topologyNodePanel("syncope", syncopeTopologyNode));
+        body.add(topologyNodePanel(Constants.SYNCOPE, syncopeTopologyNode));
 
         final Map<Serializable, Map<Serializable, TopologyNode>> connections = new HashMap<>();
         final Map<Serializable, TopologyNode> syncopeConnections = new HashMap<>();
@@ -19,8 +19,8 @@
 package org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table;
 
 import java.lang.reflect.InvocationTargetException;
-import org.apache.syncope.common.lib.to.BulkActionResult;
-import org.apache.syncope.common.lib.to.BulkActionResult.Status;
+import java.util.Map;
+import org.apache.syncope.common.lib.types.ExecStatus;
 import org.apache.wicket.Component;
 import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
 import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
@@ -34,47 +34,41 @@ import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.BeansException;
 
-public class BulkActionResultColumn<T, S> extends AbstractColumn<T, S> {
+public class BatchResponseColumn<T, S> extends AbstractColumn<T, S> {
 
     private static final long serialVersionUID = 7955560320949560716L;
 
-    private static final Logger LOG = LoggerFactory.getLogger(BulkActionResultColumn.class);
+    private static final Logger LOG = LoggerFactory.getLogger(BatchResponseColumn.class);
 
-    private final BulkActionResult results;
+    private final Map<String, String> results;
 
     private final String keyFieldName;
 
-    public BulkActionResultColumn(final BulkActionResult results, final String keyFieldName) {
+    public BatchResponseColumn(final Map<String, String> results, final String keyFieldName) {
         super(new Model<String>());
         this.results = results;
         this.keyFieldName = keyFieldName;
     }
 
     @Override
-    public String getCssClass() {
-        return "bulkResultColumn";
-    }
-
-    @Override
     public Component getHeader(final String componentId) {
-        final Label label = new Label(componentId, new Model<>());
-        label.setDefaultModel(new StringResourceModel("bulk.action.result.header", label, new Model<>("Result")));
+        Label label = new Label(componentId, new Model<>());
+        label.setDefaultModel(new StringResourceModel("batch.result.header", label, new Model<>("Result")));
         return label;
     }
 
     @Override
     public void populateItem(final Item<ICellPopulator<T>> item, final String componentId, final IModel<T> rowModel) {
         try {
-            final Object id = BeanUtils.getPropertyDescriptor(rowModel.getObject().getClass(), keyFieldName).
+            Object key = BeanUtils.getPropertyDescriptor(rowModel.getObject().getClass(), keyFieldName).
                     getReadMethod().invoke(rowModel.getObject(), new Object[0]);
-            final Status status = results.getResults().containsKey(id.toString())
-                    ? results.getResults().get(id.toString())
-                    : Status.NOT_ATTEMPTED;
-
-            item.add(new Label(componentId, new StringResourceModel(status.name(), item, new Model<>(status.name()))));
+            String status = results.containsKey(key.toString())
+                    ? results.get(key.toString())
+                    : ExecStatus.NOT_ATTEMPTED.name();
 
+            item.add(new Label(componentId, new StringResourceModel(status, item, new Model<>(status))));
         } catch (BeansException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
-            LOG.error("Errore retrieving target id value", e);
+            LOG.error("Errore retrieving target key value", e);
         }
     }
 }
index efd27b5..5760b37 100644 (file)
@@ -332,7 +332,7 @@ public class JobWidget extends BaseWidget {
         }
 
         @Override
-        protected Collection<ActionLink.ActionType> getBulkActions() {
+        protected Collection<ActionLink.ActionType> getBatches() {
             return Collections.<ActionLink.ActionType>emptyList();
         }
 
@@ -614,7 +614,7 @@ public class JobWidget extends BaseWidget {
         }
 
         @Override
-        protected Collection<ActionLink.ActionType> getBulkActions() {
+        protected Collection<ActionLink.ActionType> getBatches() {
             return Collections.<ActionLink.ActionType>emptyList();
         }
 
index 7e969f3..7ae973d 100644 (file)
@@ -103,7 +103,7 @@ public class ReconDetailsModalPanel extends AbstractModalPanel<Any> {
         }
 
         @Override
-        protected Collection<ActionLink.ActionType> getBulkActions() {
+        protected Collection<ActionLink.ActionType> getBatches() {
             return Collections.<ActionLink.ActionType>emptyList();
         }
 
index 8723a7d..526e5ab 100644 (file)
@@ -348,7 +348,7 @@ public class ReconciliationWidget extends BaseWidget {
         }
 
         @Override
-        protected Collection<ActionLink.ActionType> getBulkActions() {
+        protected Collection<ActionLink.ActionType> getBatches() {
             return Collections.<ActionLink.ActionType>emptyList();
         }
 
  * specific language governing permissions and limitations
  * under the License.
  */
-div.bulkAction{
+div.batch{
   display:inline-table;
 }
 
-div.bulkActionCell{
+div.batchCell{
   display: table-cell;
   vertical-align: middle;
   text-align: center;
@@ -18,7 +18,7 @@ under the License.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
   <wicket:head>
-    <link rel="stylesheet" type="text/css" href="css/bulk.css" media="all"/>
+    <link rel="stylesheet" type="text/css" href="css/batch.css" media="all"/>
   </wicket:head>
   <wicket:panel>
     <div wicket:id="container" id="selectedObjects" class="table-responsive dataTable">
 # specific language governing permissions and limitations
 # under the License.
 #
-# SUCCESS=ВЫПОЛНЕНО
 SUCCESS=\u0412\u042b\u041f\u041e\u041b\u041d\u0415\u041d\u041e
-# FAILURE=ОШИБКА
 FAILURE=\u041e\u0428\u0418\u0411\u041a\u0410
-# CREATED=ОЖИДАНИЕ
 CREATED=\u041e\u0416\u0418\u0414\u0410\u041d\u0418\u0415
-# NOT_ATTEMPTED=ПОПЫТКА НЕ ВЫПОЛНЯЛАСЬ
 NOT_ATTEMPTED=\u041f\u041e\u041f\u042b\u0422\u041a\u0410 \u041d\u0415 \u0412\u042b\u041f\u041e\u041b\u041d\u042f\u041b\u0410\u0421\u042c
-# bulk.action.result.header=Результат
-bulk.action.result.header=\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442
+batch.result.header=\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442
+UNKNOWN=UNKNOWN
index fb204e2..b904a65 100644 (file)
@@ -19,24 +19,24 @@ under the License.
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
   <wicket:panel>
     <span wicket:id="tablePanel">[Table panel]</span>
-    <div wicket:id="bulkModal" />
+    <div wicket:id="batchModal" />
 
-    <wicket:fragment wicket:id="bulkAvailable">
+    <wicket:fragment wicket:id="batchAvailable">
       <form wicket:id="groupForm">
         <span wicket:id="checkgroup">
           <table class="ui-widget ui-widget-content table-hover" wicket:id="dataTable">[DataTable]</table>
         </span>
       </form>
-      <div class="bulkAction">
-        <div class="bulkActionCell">
-          <a href="#" wicket:id="bulkActionLink">
-            <i class="fa fa-gear" alt="bulk action icon"  title="Bulk action"></i>
+      <div class="batch">
+        <div class="batchCell">
+          <a href="#" wicket:id="batchLink">
+            <i class="fa fa-gear" alt="batch icon"  title="Batch"></i>
           </a>
         </div>
       </div>
     </wicket:fragment>
 
-    <wicket:fragment wicket:id="bulkNotAvailable">
+    <wicket:fragment wicket:id="batchNotAvailable">
       <table class="ui-widget ui-widget-content table-hover" wicket:id="dataTable">[DataTable]</table>
     </wicket:fragment>
 
index 5323044..ea31e87 100644 (file)
@@ -35,7 +35,7 @@ any.new=New ${anyTO.type}
 any.finish=Submit ${anyTO.type}
 any.cancel=Cancel ${anyTO.type}
 any.attr.display=Attributes to be displayed
-bulk.action=Bulk action
+batch=Batch
 any.propagation.tasks=Propagation tasks for ${type} ${name}
 any.notification.tasks=Notification tasks for ${type} ${name}
 notification.tasks=Tasks about notification ${key}
index 1af71ab..39d1dc5 100644 (file)
@@ -35,7 +35,7 @@ any.new=Nuovo ${anyTO.type}
 any.attr.display=Attributi da mostrare
 any.finish=Invia ${anyTO.type}
 any.cancel=Annulla ${anyTO.type}
-bulk.action=Operazioni di gruppo
+batch=Batch
 any.propagation.tasks=Task di propagazione per ${type} ${name}
 any.notification.tasks=Task di notifica per ${type} ${name}
 notification.tasks=Task relativi alla notifica ${key}
index a3f3697..69ac68d 100644 (file)
@@ -35,7 +35,7 @@ any.new=\u65b0\u3057\u3044 ${anyTO.type}
 any.finish=${anyTO.type} \u3092\u5b9f\u884c
 any.cancel=${anyTO.type} \u3092\u30ad\u30e3\u30f3\u30bb\u30eb
 any.attr.display=\u8868\u793a\u3059\u308b\u5c5e\u6027
-bulk.action=\u4e00\u62ec\u30a2\u30af\u30b7\u30e7\u30f3
+batch=Batch
 any.propagation.tasks=${type} ${name} \u306e\u4f1d\u64ad\u30bf\u30b9\u30af
 any.notification.tasks=${type} ${name} \u306e\u901a\u77e5\u30bf\u30b9\u30af
 notification.tasks=\u901a\u77e5 ${key} \u306b\u95a2\u3059\u308b\u30bf\u30b9\u30af
index 9082591..8f55e7b 100644 (file)
@@ -35,7 +35,7 @@ any.new=Novo ${anyTO.type}
 any.attr.display=Atributos a ser exibido
 any.finish=Apresentar ${anyTO.type}
 any.cancel=Cancelar ${anyTO.type}
-bulk.action=A\u00e7\u00e3o Composta
+batch=Batch
 any.propagation.tasks=Propagation tasks for ${type} ${name}
 any.notification.tasks=Notification tasks for ${type} ${name}
 notification.tasks=Tasks about notification ${key}
index 998d409..b88d40e 100644 (file)
@@ -36,7 +36,7 @@ any.new=\u0421\u043e\u0437\u0434\u0430\u0442\u044c ${anyTO.type}
 any.finish=\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c ${anyTO.type}
 any.cancel=\u041e\u0442\u043c\u0435\u043d\u0438\u0442\u044c ${anyTO.type}
 any.attr.display=\u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b
-bulk.action=\u041c\u0430\u0441\u0441\u043e\u0432\u043e\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435
+batch=Batch
 any.propagation.tasks=\u0417\u0430\u0434\u0430\u0447\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0434\u043b\u044f ${type} ${name}
 any.notification.tasks=\u0417\u0430\u0434\u0430\u0447\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439 \u0434\u043b\u044f ${type} ${name}
 notification.tasks=\u0417\u0430\u0434\u0430\u0447\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439 ${key}
index f1f6b3a..98e723f 100644 (file)
@@ -126,7 +126,7 @@ public class SyncopeEnduserApplication extends WebApplication implements Seriali
                 : Integer.valueOf(props.getProperty("maxUploadFileSizeMB"));
 
         clientFactory = new SyncopeClientFactoryBean().
-                setAddress(scheme + "://" + host + ":" + port + "/" + rootPath).
+                setAddress(scheme + "://" + host + ":" + port + StringUtils.prependIfMissing(rootPath, "/")).
                 setContentType(SyncopeClientFactoryBean.ContentType.JSON).
                 setUseCompression(BooleanUtils.toBoolean(useGZIPCompression));
 
index a614dd1..466b266 100644 (file)
@@ -48,6 +48,8 @@ import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.rest.api.Preference;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.service.AccessTokenService;
+import org.apache.syncope.common.rest.api.service.AnyService;
+import org.apache.syncope.common.rest.api.service.ExecutableService;
 import org.apache.syncope.common.rest.api.service.UserSelfService;
 
 /**
@@ -234,6 +236,9 @@ public class SyncopeClient {
 
             Client client = WebClient.client(serviceInstance);
             client.type(mediaType).accept(mediaType);
+            if (serviceInstance instanceof AnyService || serviceInstance instanceof ExecutableService) {
+                client.accept(RESTHeaders.MULTIPART_MIXED);
+            }
 
             ClientConfiguration config = WebClient.getConfig(client);
             config.getRequestContext().put(HEADER_SPLIT_PROPERTY, true);
@@ -360,6 +365,23 @@ public class SyncopeClient {
         return WebClient.client(service).getResponse().getEntityTag();
     }
 
+    /**
+     * Initiates a new Batch request.
+     *
+     * The typical operation flow is:
+     * <pre>
+     * BatchRequest batchRequest = syncopeClient.batch();
+     * batchRequest.getService(UserService.class).create(...);
+     * batchRequest.getService(UserService.class).update(...);
+     * batchRequest.getService(GroupService.class).update(...);
+     * batchRequest.getService(GroupService.class).delete(...);
+     * ...
+     * BatchResponse batchResponse = batchRequest().commit();
+     * List&lt;BatchResponseItem&gt; items = batchResponse.getItems()
+     * </pre>
+     *
+     * @return empty Batch request
+     */
     public BatchRequest batch() {
         return new BatchRequest(
                 mediaType,
index 580c95a..bc62a93 100644 (file)
@@ -31,6 +31,9 @@ import org.apache.syncope.common.rest.api.batch.BatchRequestItem;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+/**
+ * Encapsulates the Batch request management via CXF Proxy Client.
+ */
 public class BatchRequest {
 
     private static final Logger LOG = LoggerFactory.getLogger(BatchRequest.class);
@@ -78,10 +81,26 @@ public class BatchRequest {
         return bcfb.getBatchRequestItems();
     }
 
+    /**
+     * Sends the current request, with items accumulated by invoking methods on proxies obtained via
+     * {@link #getService(java.lang.Class)}, to the Batch service, and awaits for synchronous response.
+     * It also clears out the accumulated items, in case of reuse of this instance for subsequent requests.
+     * 
+     * @return batch response
+     */
     public BatchResponse commit() {
         return commit(false);
     }
 
+    /**
+     * Sends the current request, with items accumulated by invoking methods on proxies obtained via
+     * {@link #getService(java.lang.Class)}, to the Batch service, and awaits for a synchronous or asynchronous
+     * response, depending on the {@code async} parameter.
+     * It also clears out the accumulated items, in case of reuse of this instance for subsequent requests.
+     * 
+     * @param async whether asynchronous Batch process is requested, or not
+     * @return batch response
+     */
     public BatchResponse commit(final boolean async) {
         String boundary = "--batch_" + UUID.randomUUID().toString();
 
index 89cd0e7..80a32e8 100644 (file)
@@ -34,6 +34,9 @@ import org.apache.syncope.common.rest.api.batch.BatchResponseItem;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+/**
+ * Encapsulates the Batch response management via CXF Proxy Client.
+ */
 public class BatchResponse {
 
     private static final Logger LOG = LoggerFactory.getLogger(BatchResponse.class);
@@ -63,7 +66,7 @@ public class BatchResponse {
     }
 
     /**
-     * If asynchronous processing was requested, query the monitor URI.
+     * If asynchronous processing was requested, queries the monitor URI.
      *
      * @return the last Response received from the Batch service
      */
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/BulkAction.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/BulkAction.java
deleted file mode 100644 (file)
index c3edf7c..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.syncope.common.lib.to;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import java.util.ArrayList;
-import java.util.List;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlElementWrapper;
-import javax.xml.bind.annotation.XmlEnum;
-import javax.xml.bind.annotation.XmlRootElement;
-import javax.xml.bind.annotation.XmlType;
-import org.apache.syncope.common.lib.AbstractBaseBean;
-
-@XmlRootElement(name = "bulkAction")
-@XmlType
-public class BulkAction extends AbstractBaseBean {
-
-    private static final long serialVersionUID = 1395353278878758961L;
-
-    @XmlEnum
-    @XmlType(name = "bulkActionType")
-    public enum Type {
-
-        MUSTCHANGEPASSWORD,
-        DELETE,
-        REACTIVATE,
-        SUSPEND,
-        DRYRUN,
-        EXECUTE
-
-    }
-
-    private Type type;
-
-    private final List<String> targets = new ArrayList<>();
-
-    public Type getType() {
-        return type;
-    }
-
-    public void setType(final Type type) {
-        this.type = type;
-    }
-
-    @XmlElementWrapper(name = "targets")
-    @XmlElement(name = "target")
-    @JsonProperty("targets")
-    public List<String> getTargets() {
-        return targets;
-    }
-}
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/BulkActionResult.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/BulkActionResult.java
deleted file mode 100644 (file)
index 53b7cf6..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.syncope.common.lib.to;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import javax.xml.bind.annotation.XmlAccessType;
-import javax.xml.bind.annotation.XmlAccessorType;
-import javax.xml.bind.annotation.XmlEnum;
-import javax.xml.bind.annotation.XmlRootElement;
-import javax.xml.bind.annotation.XmlSeeAlso;
-import javax.xml.bind.annotation.XmlType;
-import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
-import org.apache.syncope.common.lib.AbstractBaseBean;
-import org.apache.syncope.common.lib.jaxb.XmlGenericMapAdapter;
-
-@XmlRootElement(name = "bulkActionResult")
-@XmlType
-@XmlAccessorType(XmlAccessType.FIELD)
-@XmlSeeAlso(BulkActionResult.Status.class)
-public class BulkActionResult extends AbstractBaseBean {
-
-    private static final long serialVersionUID = 2868894178821778133L;
-
-    @XmlEnum
-    @XmlType(name = "bulkActionStatus")
-    public enum Status {
-
-        // general bulk action result statuses
-        SUCCESS,
-        FAILURE,
-        // specific propagation task execution statuses
-        CREATED,
-        NOT_ATTEMPTED;
-
-    }
-
-    @XmlJavaTypeAdapter(XmlGenericMapAdapter.class)
-    private final Map<String, Status> results = new HashMap<>();
-
-    @JsonProperty
-    public Map<String, Status> getResults() {
-        return results;
-    }
-
-    @JsonIgnore
-    public List<String> getResultByStatus(final Status status) {
-        final List<String> result = new ArrayList<>();
-
-        results.entrySet().stream().
-                filter((entry) -> (entry.getValue() == status)).
-                forEachOrdered(entry -> result.add(entry.getKey()));
-
-        return Collections.unmodifiableList(result);
-    }
-}
index c2caa60..ee6f627 100644 (file)
@@ -21,7 +21,7 @@ package org.apache.syncope.common.lib.to;
 import org.apache.syncope.common.lib.AbstractBaseBean;
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlType;
-import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
+import org.apache.syncope.common.lib.types.ExecStatus;
 
 /**
  * Single propagation status.
@@ -50,7 +50,7 @@ public class PropagationStatus extends AbstractBaseBean {
     /**
      * Propagation task execution status.
      */
-    private PropagationTaskExecStatus status;
+    private ExecStatus status;
 
     /**
      * Propagation task execution failure message.
@@ -81,11 +81,11 @@ public class PropagationStatus extends AbstractBaseBean {
         this.resource = resource;
     }
 
-    public PropagationTaskExecStatus getStatus() {
+    public ExecStatus getStatus() {
         return status;
     }
 
-    public void setStatus(final PropagationTaskExecStatus status) {
+    public void setStatus(final ExecStatus status) {
         this.status = status;
     }
 
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/types/ExecStatus.java b/common/lib/src/main/java/org/apache/syncope/common/lib/types/ExecStatus.java
new file mode 100644 (file)
index 0000000..8b25c5e
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.lib.types;
+
+import javax.ws.rs.core.Response;
+import javax.xml.bind.annotation.XmlEnum;
+
+/**
+ * Status of some execution.
+ */
+@XmlEnum
+public enum ExecStatus {
+
+    CREATED(Response.Status.CREATED.getStatusCode()),
+    SUCCESS(Response.Status.OK.getStatusCode()),
+    FAILURE(Response.Status.BAD_REQUEST.getStatusCode()),
+    NOT_ATTEMPTED(Response.Status.PRECONDITION_REQUIRED.getStatusCode());
+
+    protected int httpStatus;
+
+    ExecStatus(final int httpStatus) {
+        this.httpStatus = httpStatus;
+    }
+
+    public int getHttpStatus() {
+        return httpStatus;
+    }
+
+    public static ExecStatus fromHttpStatus(final int httpStatus) {
+        ExecStatus status = null;
+        for (ExecStatus value : values()) {
+            if (httpStatus == value.getHttpStatus()) {
+                status = value;
+            }
+        }
+        if (status == null && httpStatus == Response.Status.NO_CONTENT.getStatusCode()) {
+            return ExecStatus.SUCCESS;
+        }
+        return status;
+    }
+}
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/types/PropagationTaskExecStatus.java b/common/lib/src/main/java/org/apache/syncope/common/lib/types/PropagationTaskExecStatus.java
deleted file mode 100644 (file)
index 6ae4a36..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.syncope.common.lib.types;
-
-import javax.xml.bind.annotation.XmlEnum;
-
-/**
- * Status of a propagation task execution.
- */
-@XmlEnum
-public enum PropagationTaskExecStatus {
-
-    CREATED,
-    SUCCESS,
-    FAILURE,
-    NOT_ATTEMPTED;
-
-}
index 2fe42ed..6421086 100644 (file)
@@ -28,7 +28,4 @@ public enum ReportExecStatus {
     SUCCESS,
     FAILURE;
 
-    public static ReportExecStatus fromString(final String value) {
-        return ReportExecStatus.valueOf(value.toUpperCase());
-    }
 }
index fcebb1c..0d444f2 100644 (file)
 package org.apache.syncope.common.lib.types;
 
 import javax.xml.bind.annotation.XmlEnum;
+import org.apache.syncope.common.lib.to.NotificationTaskTO;
+import org.apache.syncope.common.lib.to.PropagationTaskTO;
+import org.apache.syncope.common.lib.to.PullTaskTO;
+import org.apache.syncope.common.lib.to.PushTaskTO;
+import org.apache.syncope.common.lib.to.SchedTaskTO;
+import org.apache.syncope.common.lib.to.TaskTO;
 
 @XmlEnum
 public enum TaskType {
 
-    PROPAGATION,
-    NOTIFICATION,
-    SCHEDULED,
-    PULL,
-    PUSH;
+    PROPAGATION(PropagationTaskTO.class),
+    NOTIFICATION(NotificationTaskTO.class),
+    SCHEDULED(SchedTaskTO.class),
+    PULL(PullTaskTO.class),
+    PUSH(PushTaskTO.class);
 
+    private final Class<? extends TaskTO> toClass;
+
+    TaskType(final Class<? extends TaskTO> toClass) {
+        this.toClass = toClass;
+    }
+
+    public Class<? extends TaskTO> getToClass() {
+        return toClass;
+    }
+
+    public static TaskType fromTOClass(final Class<? extends TaskTO> clazz) {
+        return PushTaskTO.class.isAssignableFrom(clazz)
+                ? TaskType.PUSH
+                : PullTaskTO.class.isAssignableFrom(clazz)
+                ? TaskType.PULL
+                : NotificationTaskTO.class.isAssignableFrom(clazz)
+                ? TaskType.NOTIFICATION
+                : PropagationTaskTO.class.isAssignableFrom(clazz)
+                ? TaskType.PROPAGATION
+                : TaskType.SCHEDULED;
+    }
 }
@@ -24,13 +24,13 @@ import javax.ws.rs.PathParam;
 import javax.ws.rs.QueryParam;
 import org.apache.syncope.common.lib.AbstractBaseBean;
 
-public class BulkExecDeleteQuery extends AbstractBaseBean {
+public class ExecDeleteQuery extends AbstractBaseBean {
 
     private static final long serialVersionUID = 3846547401120638351L;
 
     public static class Builder {
 
-        private final BulkExecDeleteQuery instance = new BulkExecDeleteQuery();
+        private final ExecDeleteQuery instance = new ExecDeleteQuery();
 
         public Builder key(final String key) {
             instance.setKey(key);
@@ -57,7 +57,7 @@ public class BulkExecDeleteQuery extends AbstractBaseBean {
             return this;
         }
 
-        public BulkExecDeleteQuery build() {
+        public ExecDeleteQuery build() {
             return instance;
         }
     }
@@ -145,5 +145,4 @@ public class BulkExecDeleteQuery extends AbstractBaseBean {
             this.endedAfter = null;
         }
     }
-
 }
index 7bb78d8..eb3f9aa 100644 (file)
@@ -43,8 +43,6 @@ import org.apache.syncope.common.lib.patch.AssociationPatch;
 import org.apache.syncope.common.lib.patch.DeassociationPatch;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.to.AttrTO;
-import org.apache.syncope.common.lib.to.BulkAction;
-import org.apache.syncope.common.lib.to.BulkActionResult;
 import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.to.ProvisioningResult;
 import org.apache.syncope.common.lib.types.ResourceAssociationAction;
@@ -190,7 +188,7 @@ public interface AnyService<TO extends AnyTO> extends JAXRSService {
      * Executes resource-related operations on given entity.
      *
      * @param patch external resources to be used for propagation-related operations
-     * @return Response object featuring BulkActionResult as Entity
+     * @return batch results as Response entity
      */
     @Parameter(name = RESTHeaders.PREFER, in = ParameterIn.HEADER,
             description = "Allows client to specify a preference for the result to be returned from the server",
@@ -212,9 +210,7 @@ public interface AnyService<TO extends AnyTO> extends JAXRSService {
             @Schema(implementation = ResourceDeassociationAction.class))
     @ApiResponses({
         @ApiResponse(responseCode = "200",
-                description = "Bulk action result", content =
-                @Content(schema =
-                        @Schema(implementation = BulkActionResult.class))),
+                description = "Batch results available, returned as Response entity"),
         @ApiResponse(responseCode = "204",
                 description = "No content if 'Prefer: return-no-content' was specified", headers =
                 @Header(name = RESTHeaders.PREFERENCE_APPLIED, schema =
@@ -226,15 +222,15 @@ public interface AnyService<TO extends AnyTO> extends JAXRSService {
                 + " date of the entity") })
     @POST
     @Path("{key}/deassociate/{action}")
-    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
     @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    @Produces(RESTHeaders.MULTIPART_MIXED)
     Response deassociate(@NotNull DeassociationPatch patch);
 
     /**
      * Executes resource-related operations on given entity.
      *
      * @param patch external resources to be used for propagation-related operations
-     * @return Response object featuring BulkActionResult as Entity
+     * @return batch results as Response entity
      */
     @Parameter(name = RESTHeaders.PREFER, in = ParameterIn.HEADER,
             description = "Allows client to specify a preference for the result to be returned from the server",
@@ -256,9 +252,7 @@ public interface AnyService<TO extends AnyTO> extends JAXRSService {
             @Schema(implementation = ResourceAssociationAction.class))
     @ApiResponses({
         @ApiResponse(responseCode = "200",
-                description = "Bulk action result", content =
-                @Content(schema =
-                        @Schema(implementation = BulkActionResult.class))),
+                description = "Batch results available, returned as Response entity"),
         @ApiResponse(responseCode = "204",
                 description = "No content if 'Prefer: return-no-content' was specified", headers =
                 @Header(name = RESTHeaders.PREFERENCE_APPLIED, schema =
@@ -270,39 +264,7 @@ public interface AnyService<TO extends AnyTO> extends JAXRSService {
                 + " date of the entity") })
     @POST
     @Path("{key}/associate/{action}")
-    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
     @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    @Produces(RESTHeaders.MULTIPART_MIXED)
     Response associate(@NotNull AssociationPatch patch);
-
-    /**
-     * Executes the provided bulk action.
-     *
-     * @param bulkAction list of any object ids against which the bulk action will be performed.
-     * @return Response object featuring BulkActionResult as Entity
-     */
-    @Parameter(name = RESTHeaders.PREFER, in = ParameterIn.HEADER,
-            description = "Allows client to specify a preference for the result to be returned from the server",
-            allowEmptyValue = true, schema =
-            @Schema(defaultValue = "return-content", allowableValues = { "return-content", "return-no-content" }))
-    @Parameter(name = RESTHeaders.NULL_PRIORITY_ASYNC, in = ParameterIn.HEADER,
-            description = "If 'true', instructs the propagation process not to wait for completion when communicating"
-            + " with External Resources with no priority set",
-            allowEmptyValue = true, schema =
-            @Schema(type = "boolean", defaultValue = "false"))
-    @ApiResponses({
-        @ApiResponse(responseCode = "200",
-                description = "Bulk action result", content =
-                @Content(schema =
-                        @Schema(implementation = BulkActionResult.class))),
-        @ApiResponse(responseCode = "204",
-                description = "No content if 'Prefer: return-no-content' was specified", headers =
-                @Header(name = RESTHeaders.PREFERENCE_APPLIED, schema =
-                        @Schema(type = "string"),
-                        description = "Allows the server to inform the "
-                        + "client about the fact that a specified preference was applied")) })
-    @POST
-    @Path("bulk")
-    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
-    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
-    Response bulk(@NotNull BulkAction bulkAction);
 }
index 416bcdc..808aae8 100644 (file)
@@ -33,13 +33,13 @@ import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.MediaType;
-import org.apache.syncope.common.lib.to.BulkActionResult;
+import javax.ws.rs.core.Response;
 import org.apache.syncope.common.lib.to.ExecTO;
 import org.apache.syncope.common.lib.to.JobTO;
 import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.types.JobAction;
 import org.apache.syncope.common.rest.api.RESTHeaders;
-import org.apache.syncope.common.rest.api.beans.BulkExecDeleteQuery;
+import org.apache.syncope.common.rest.api.beans.ExecDeleteQuery;
 import org.apache.syncope.common.rest.api.beans.ExecQuery;
 import org.apache.syncope.common.rest.api.beans.ExecuteQuery;
 
@@ -83,12 +83,15 @@ public interface ExecutableService extends JAXRSService {
      * Deletes the executions belonging matching the given query.
      *
      * @param query query conditions
-     * @return bulk action result
+     * @return batch results as Response entity
      */
     @DELETE
+    @ApiResponses(
+            @ApiResponse(responseCode = "200",
+                    description = "Batch results available, returned as Response entity"))
     @Path("{key}/executions")
-    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
-    BulkActionResult deleteExecutions(@BeanParam BulkExecDeleteQuery query);
+    @Produces(RESTHeaders.MULTIPART_MIXED)
+    Response deleteExecutions(@BeanParam ExecDeleteQuery query);
 
     /**
      * Executes the executable matching the given query.
index 8b7d893..05c2ba7 100644 (file)
@@ -46,7 +46,7 @@ import org.apache.syncope.common.lib.to.ExecTO;
 import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.to.ProvisioningResult;
-import org.apache.syncope.common.lib.types.BulkMembersActionType;
+import org.apache.syncope.common.lib.types.ProvisionAction;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.beans.AnyQuery;
 
@@ -212,13 +212,13 @@ public interface GroupService extends AnyService<GroupTO> {
      * (De)provision all members of the given group from / onto all the resources associated to it.
      *
      * @param key group key
-     * @param actionType action type to perform on all group members
+     * @param action action type to perform on all group members
      * @return execution report for the task generated on purpose
      */
     @POST
-    @Path("{key}/members/{actionType}")
+    @Path("{key}/members/{action}")
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
-    ExecTO bulkMembersAction(
+    ExecTO provisionMembers(
             @NotNull @PathParam("key") String key,
-            @NotNull @PathParam("actionType") BulkMembersActionType actionType);
+            @NotNull @PathParam("action") ProvisionAction action);
 }
index 68e4179..11bffe0 100644 (file)
@@ -38,7 +38,7 @@ import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 
 /**
- * REST operations for tasks.
+ * REST operations for reconciliation.
  */
 @Tag(name = "Reconciliation")
 @SecurityRequirements({
index 6aaa3a8..27b7813 100644 (file)
@@ -41,12 +41,9 @@ import javax.ws.rs.Produces;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
-import org.apache.syncope.common.lib.patch.ResourceDeassociationPatch;
-import org.apache.syncope.common.lib.to.BulkActionResult;
 import org.apache.syncope.common.lib.to.ConnObjectTO;
 import org.apache.syncope.common.lib.to.PagedConnObjectTOResult;
 import org.apache.syncope.common.lib.to.ResourceTO;
-import org.apache.syncope.common.lib.types.ResourceDeassociationAction;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.beans.ConnObjectTOListQuery;
 
@@ -204,22 +201,4 @@ public interface ResourceService extends JAXRSService {
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
     @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
     void check(@NotNull ResourceTO resourceTO);
-
-    /**
-     * De-associate any objects from the given resource.
-     *
-     * @param patch any objects to be used for propagation-related operations
-     * @return Bulk action result
-     */
-    @Parameter(name = "key", description = "Resource's key", in = ParameterIn.PATH, schema =
-            @Schema(type = "string"))
-    @Parameter(name = "anyTypeKey", description = "AnyType's key", in = ParameterIn.PATH, schema =
-            @Schema(type = "string"))
-    @Parameter(name = "action", description = "Deassociation action", in = ParameterIn.PATH, schema =
-            @Schema(implementation = ResourceDeassociationAction.class))
-    @POST
-    @Path("{key}/bulkDeassociation/{anyTypeKey}/{action}")
-    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
-    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
-    BulkActionResult bulkDeassociation(@NotNull ResourceDeassociationPatch patch);
 }
index 26332e8..9e5ff71 100644 (file)
@@ -124,7 +124,7 @@ public interface SyncopeService extends JAXRSService {
     /**
      * Gets batch results, in case asynchronous was requested.
      *
-     * @return batch results returned as Response entity
+     * @return batch results as Response entity
      */
     @GET
     @ApiResponses({
index 70f5935..07a18d8 100644 (file)
@@ -43,8 +43,6 @@ import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import org.apache.syncope.common.lib.to.TaskTO;
-import org.apache.syncope.common.lib.to.BulkAction;
-import org.apache.syncope.common.lib.to.BulkActionResult;
 import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.to.SchedTaskTO;
 import org.apache.syncope.common.lib.types.TaskType;
@@ -140,16 +138,4 @@ public interface TaskService extends ExecutableService {
     @Path("{type}/{key}")
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
     void delete(@NotNull @PathParam("type") TaskType type, @NotNull @PathParam("key") String key);
-
-    /**
-     * Executes the provided bulk action.
-     *
-     * @param bulkAction list of task ids against which the bulk action will be performed.
-     * @return Bulk action result
-     */
-    @POST
-    @Path("bulk")
-    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
-    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
-    BulkActionResult bulk(@NotNull BulkAction bulkAction);
 }
index 6b34bc7..a25325d 100644 (file)
@@ -22,10 +22,10 @@ import java.util.Date;
 import java.util.List;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.AbstractBaseBean;
-import org.apache.syncope.common.lib.to.BulkActionResult;
 import org.apache.syncope.common.lib.to.ExecTO;
 import org.apache.syncope.common.lib.to.JobTO;
 import org.apache.syncope.common.lib.types.JobAction;
+import org.apache.syncope.common.rest.api.batch.BatchResponseItem;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 
 public abstract class AbstractExecutableLogic<T extends AbstractBaseBean> extends AbstractJobLogic<T> {
@@ -39,7 +39,7 @@ public abstract class AbstractExecutableLogic<T extends AbstractBaseBean> extend
 
     public abstract ExecTO deleteExecution(String executionKey);
 
-    public abstract BulkActionResult deleteExecutions(
+    public abstract List<BatchResponseItem> deleteExecutions(
             String key, Date startedBefore, Date startedAfter, Date endedBefore, Date endedAfter);
 
     public abstract JobTO getJob(String key);
index 8193745..5524fd7 100644 (file)
@@ -40,7 +40,7 @@ import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.to.PropagationStatus;
 import org.apache.syncope.common.lib.to.ProvisioningResult;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
-import org.apache.syncope.common.lib.types.BulkMembersActionType;
+import org.apache.syncope.common.lib.types.ProvisionAction;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.ImplementationEngine;
 import org.apache.syncope.common.lib.types.ImplementationType;
@@ -384,7 +384,7 @@ public class GroupLogic extends AbstractAnyLogic<GroupTO, GroupPatch> {
     @PreAuthorize("hasRole('" + StandardEntitlement.TASK_CREATE + "') "
             + "and hasRole('" + StandardEntitlement.TASK_EXECUTE + "')")
     @Transactional
-    public ExecTO bulkMembersAction(final String key, final BulkMembersActionType actionType) {
+    public ExecTO provisionMembers(final String key, final ProvisionAction action) {
         Group group = groupDAO.find(key);
         if (group == null) {
             throw new NotFoundException("Group " + key);
@@ -392,18 +392,19 @@ public class GroupLogic extends AbstractAnyLogic<GroupTO, GroupPatch> {
 
         Implementation jobDelegate = implementationDAO.find(ImplementationType.TASKJOB_DELEGATE).stream().
                 filter(impl -> GroupMemberProvisionTaskJobDelegate.class.getName().equals(impl.getBody())).
-                findFirst().orElse(null);
-        if (jobDelegate == null) {
-            jobDelegate = entityFactory.newEntity(Implementation.class);
-            jobDelegate.setKey(GroupMemberProvisionTaskJobDelegate.class.getSimpleName());
-            jobDelegate.setEngine(ImplementationEngine.JAVA);
-            jobDelegate.setType(ImplementationType.TASKJOB_DELEGATE);
-            jobDelegate.setBody(GroupMemberProvisionTaskJobDelegate.class.getName());
-            jobDelegate = implementationDAO.save(jobDelegate);
-        }
+                findFirst().orElseGet(() -> {
+                    Implementation caz = entityFactory.newEntity(Implementation.class);
+                    caz.setKey(GroupMemberProvisionTaskJobDelegate.class.getSimpleName());
+                    caz.setEngine(ImplementationEngine.JAVA);
+                    caz.setType(ImplementationType.TASKJOB_DELEGATE);
+                    caz.setBody(GroupMemberProvisionTaskJobDelegate.class.getName());
+                    caz = implementationDAO.save(caz);
+                    return caz;
+                });
 
         SchedTask task = entityFactory.newEntity(SchedTask.class);
-        task.setName("Bulk member provision for group " + group.getName());
+        task.setName((action == ProvisionAction.DEPROVISION ? "de" : "")
+                + "provision members of group " + group.getName());
         task.setActive(true);
         task.setJobDelegate(jobDelegate);
         task = taskDAO.save(task);
@@ -416,7 +417,7 @@ public class GroupLogic extends AbstractAnyLogic<GroupTO, GroupPatch> {
 
             jobDataMap.put(TaskJob.DRY_RUN_JOBDETAIL_KEY, false);
             jobDataMap.put(GroupMemberProvisionTaskJobDelegate.GROUP_KEY_JOBDETAIL_KEY, key);
-            jobDataMap.put(GroupMemberProvisionTaskJobDelegate.ACTION_TYPE_JOBDETAIL_KEY, actionType);
+            jobDataMap.put(GroupMemberProvisionTaskJobDelegate.ACTION_JOBDETAIL_KEY, action);
 
             scheduler.getScheduler().triggerJob(
                     JobNamer.getJobKey(task),
index 523b75a..95aa956 100644 (file)
@@ -22,12 +22,15 @@ import java.io.ByteArrayInputStream;
 import java.io.OutputStream;
 import java.lang.reflect.Method;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 import java.util.zip.ZipInputStream;
+import javax.ws.rs.core.Response;
 import javax.xml.transform.stream.StreamSource;
 import org.apache.cocoon.pipeline.NonCachingPipeline;
 import org.apache.cocoon.pipeline.Pipeline;
@@ -39,7 +42,6 @@ import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.commons.lang3.tuple.Triple;
 import org.apache.syncope.common.lib.SyncopeClientException;
-import org.apache.syncope.common.lib.to.BulkActionResult;
 import org.apache.syncope.common.lib.to.ExecTO;
 import org.apache.syncope.common.lib.to.JobTO;
 import org.apache.syncope.common.lib.to.ReportTO;
@@ -49,6 +51,8 @@ import org.apache.syncope.common.lib.types.JobType;
 import org.apache.syncope.common.lib.types.ReportExecExportFormat;
 import org.apache.syncope.common.lib.types.ReportExecStatus;
 import org.apache.syncope.common.lib.types.StandardEntitlement;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.batch.BatchResponseItem;
 import org.apache.syncope.core.logic.cocoon.FopSerializer;
 import org.apache.syncope.core.logic.cocoon.TextSerializer;
 import org.apache.syncope.core.logic.cocoon.XSLTTransformer;
@@ -62,6 +66,7 @@ import org.apache.syncope.core.persistence.api.entity.Report;
 import org.apache.syncope.core.persistence.api.entity.ReportExec;
 import org.apache.syncope.core.provisioning.api.data.ReportDataBinder;
 import org.apache.syncope.core.provisioning.api.job.JobNamer;
+import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2;
 import org.apache.xmlgraphics.util.MimeConstants;
 import org.quartz.JobKey;
 import org.quartz.SchedulerException;
@@ -332,7 +337,7 @@ public class ReportLogic extends AbstractExecutableLogic<ReportTO> {
 
     @PreAuthorize("hasRole('" + StandardEntitlement.REPORT_DELETE + "')")
     @Override
-    public BulkActionResult deleteExecutions(
+    public List<BatchResponseItem> deleteExecutions(
             final String key,
             final Date startedBefore, final Date startedAfter, final Date endedBefore, final Date endedAfter) {
 
@@ -341,19 +346,24 @@ public class ReportLogic extends AbstractExecutableLogic<ReportTO> {
             throw new NotFoundException("Report " + key);
         }
 
-        BulkActionResult result = new BulkActionResult();
+        List<BatchResponseItem> batchResponseItems = new ArrayList<>();
 
         reportExecDAO.findAll(report, startedBefore, startedAfter, endedBefore, endedAfter).forEach(exec -> {
+            BatchResponseItem item = new BatchResponseItem();
+            item.getHeaders().put(RESTHeaders.RESOURCE_KEY, Arrays.asList(exec.getKey()));
+            batchResponseItems.add(item);
+
             try {
                 reportExecDAO.delete(exec);
-                result.getResults().put(String.valueOf(exec.getKey()), BulkActionResult.Status.SUCCESS);
+                item.setStatus(Response.Status.OK.getStatusCode());
             } catch (Exception e) {
                 LOG.error("Error deleting execution {} of report {}", exec.getKey(), key, e);
-                result.getResults().put(String.valueOf(exec.getKey()), BulkActionResult.Status.FAILURE);
+                item.setStatus(Response.Status.BAD_REQUEST.getStatusCode());
+                item.setContent(ExceptionUtils2.getFullStackTrace(e));
             }
         });
 
-        return result;
+        return batchResponseItems;
     }
 
     @Override
index 526fb63..17f7376 100644 (file)
 package org.apache.syncope.core.logic;
 
 import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
+import javax.ws.rs.core.Response;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.commons.lang3.tuple.Triple;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.to.TaskTO;
-import org.apache.syncope.common.lib.to.BulkActionResult;
 import org.apache.syncope.common.lib.to.ExecTO;
 import org.apache.syncope.common.lib.to.JobTO;
 import org.apache.syncope.common.lib.to.PropagationTaskTO;
@@ -39,6 +41,8 @@ import org.apache.syncope.common.lib.types.JobAction;
 import org.apache.syncope.common.lib.types.JobType;
 import org.apache.syncope.common.lib.types.StandardEntitlement;
 import org.apache.syncope.common.lib.types.TaskType;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.batch.BatchResponseItem;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 import org.apache.syncope.core.persistence.api.dao.TaskDAO;
 import org.apache.syncope.core.persistence.api.dao.TaskExecDAO;
@@ -56,6 +60,7 @@ import org.apache.syncope.core.persistence.api.dao.ConfDAO;
 import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
 import org.apache.syncope.core.persistence.api.dao.NotificationDAO;
 import org.apache.syncope.core.provisioning.api.notification.NotificationJobDelegate;
+import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2;
 import org.apache.syncope.core.provisioning.java.job.TaskJob;
 import org.quartz.JobDataMap;
 import org.quartz.JobKey;
@@ -349,28 +354,36 @@ public class TaskLogic extends AbstractExecutableLogic<TaskTO> {
 
     @PreAuthorize("hasRole('" + StandardEntitlement.TASK_DELETE + "')")
     @Override
-    public BulkActionResult deleteExecutions(
+    public List<BatchResponseItem> deleteExecutions(
             final String key,
-            final Date startedBefore, final Date startedAfter, final Date endedBefore, final Date endedAfter) {
+            final Date startedBefore,
+            final Date startedAfter,
+            final Date endedBefore,
+            final Date endedAfter) {
 
         Task task = taskDAO.find(key);
         if (task == null) {
             throw new NotFoundException("Task " + key);
         }
 
-        BulkActionResult result = new BulkActionResult();
+        List<BatchResponseItem> batchResponseItems = new ArrayList<>();
 
         taskExecDAO.findAll(task, startedBefore, startedAfter, endedBefore, endedAfter).forEach(exec -> {
+            BatchResponseItem item = new BatchResponseItem();
+            item.getHeaders().put(RESTHeaders.RESOURCE_KEY, Arrays.asList(exec.getKey()));
+            batchResponseItems.add(item);
+
             try {
                 taskExecDAO.delete(exec);
-                result.getResults().put(String.valueOf(exec.getKey()), BulkActionResult.Status.SUCCESS);
+                item.setStatus(Response.Status.OK.getStatusCode());
             } catch (Exception e) {
                 LOG.error("Error deleting execution {} of task {}", exec.getKey(), key, e);
-                result.getResults().put(String.valueOf(exec.getKey()), BulkActionResult.Status.FAILURE);
+                item.setStatus(Response.Status.BAD_REQUEST.getStatusCode());
+                item.setContent(ExceptionUtils2.getFullStackTrace(e));
             }
         });
 
-        return result;
+        return batchResponseItems;
     }
 
     @Override
index 6c8a949..a50d105 100644 (file)
@@ -22,7 +22,7 @@ import java.util.List;
 
 import javax.validation.ConstraintValidatorContext;
 import org.apache.syncope.common.lib.types.EntityViolationType;
-import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
+import org.apache.syncope.common.lib.types.ExecStatus;
 import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
 import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
 
@@ -43,7 +43,7 @@ public class PropagationTaskValidator extends AbstractValidator<PropagationTaskC
                 List<? extends TaskExec> executions = task.getExecs();
                 for (TaskExec execution : executions) {
                     try {
-                        PropagationTaskExecStatus.valueOf(execution.getStatus());
+                        ExecStatus.valueOf(execution.getStatus());
                     } catch (IllegalArgumentException e) {
                         LOG.error("Invalid execution status '" + execution.getStatus() + "'", e);
                         isValid = false;
index 2bb8e83..af257f5 100644 (file)
@@ -24,7 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.List;
-import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
+import org.apache.syncope.common.lib.types.ExecStatus;
 import org.apache.syncope.core.persistence.api.dao.TaskDAO;
 import org.apache.syncope.core.persistence.api.dao.TaskExecDAO;
 import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
@@ -78,7 +78,7 @@ public class TaskExecTest extends AbstractTest {
         TaskExec exec = entityFactory.newEntity(TaskExec.class);
         exec.setStart(new Date());
         exec.setEnd(new Date());
-        exec.setStatus(PropagationTaskExecStatus.SUCCESS.name());
+        exec.setStatus(ExecStatus.SUCCESS.name());
         exec.setMessage(faultyMessage);
 
         task.add(exec);
index 0da3e39..d48cdf9 100644 (file)
@@ -36,7 +36,7 @@ import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.ImplementationEngine;
 import org.apache.syncope.common.lib.types.ImplementationType;
 import org.apache.syncope.common.lib.types.MatchingRule;
-import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
+import org.apache.syncope.common.lib.types.ExecStatus;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.common.lib.types.PullMode;
 import org.apache.syncope.common.lib.types.TaskType;
@@ -150,7 +150,7 @@ public class TaskTest extends AbstractTest {
 
         TaskExec execution = entityFactory.newEntity(TaskExec.class);
         execution.setTask(task);
-        execution.setStatus(PropagationTaskExecStatus.CREATED.name());
+        execution.setStatus(ExecStatus.CREATED.name());
         execution.setStart(new Date());
         task.add(execution);
 
index a27f02a..78ac2cc 100644 (file)
@@ -22,7 +22,7 @@ import java.util.Collection;
 import java.util.List;
 import org.apache.syncope.common.lib.to.PropagationStatus;
 import org.apache.syncope.common.lib.to.PropagationTaskTO;
-import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
+import org.apache.syncope.common.lib.types.ExecStatus;
 import org.identityconnectors.framework.common.objects.ConnectorObject;
 
 /**
@@ -50,7 +50,7 @@ public interface PropagationReporter {
      */
     void onSuccessOrNonPriorityResourceFailures(
             PropagationTaskTO propagationTask,
-            PropagationTaskExecStatus execStatus,
+            ExecStatus execStatus,
             String failureReason,
             ConnectorObject beforeObj,
             ConnectorObject afterObj);
index 10d48b9..2356cfb 100644 (file)
@@ -23,7 +23,7 @@ import java.util.List;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.to.PropagationStatus;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
-import org.apache.syncope.common.lib.types.BulkMembersActionType;
+import org.apache.syncope.common.lib.types.ProvisionAction;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.search.MembershipCond;
@@ -41,7 +41,7 @@ import org.springframework.transaction.annotation.Transactional;
 
 public class GroupMemberProvisionTaskJobDelegate extends AbstractSchedTaskJobDelegate {
 
-    public static final String ACTION_TYPE_JOBDETAIL_KEY = "actionType";
+    public static final String ACTION_JOBDETAIL_KEY = "action";
 
     public static final String GROUP_KEY_JOBDETAIL_KEY = "groupKey";
 
@@ -59,7 +59,7 @@ public class GroupMemberProvisionTaskJobDelegate extends AbstractSchedTaskJobDel
 
     private String groupKey;
 
-    private BulkMembersActionType actionType;
+    private ProvisionAction action;
 
     @Transactional
     @Override
@@ -67,7 +67,7 @@ public class GroupMemberProvisionTaskJobDelegate extends AbstractSchedTaskJobDel
             throws JobExecutionException {
 
         groupKey = context.getMergedJobDataMap().getString(GROUP_KEY_JOBDETAIL_KEY);
-        actionType = (BulkMembersActionType) context.getMergedJobDataMap().get(ACTION_TYPE_JOBDETAIL_KEY);
+        action = (ProvisionAction) context.getMergedJobDataMap().get(ACTION_JOBDETAIL_KEY);
 
         super.execute(taskKey, dryRun, context);
     }
@@ -77,7 +77,7 @@ public class GroupMemberProvisionTaskJobDelegate extends AbstractSchedTaskJobDel
         Group group = groupDAO.authFind(groupKey);
 
         StringBuilder result = new StringBuilder("Group ").append(group.getName()).append(" members ");
-        if (actionType == BulkMembersActionType.DEPROVISION) {
+        if (action == ProvisionAction.DEPROVISION) {
             result.append("de");
         }
         result.append("provision\n\n");
@@ -89,11 +89,11 @@ public class GroupMemberProvisionTaskJobDelegate extends AbstractSchedTaskJobDel
         List<User> users = searchDAO.search(SearchCond.getLeafCond(membershipCond), AnyTypeKind.USER);
         Collection<String> groupResourceKeys = groupDAO.findAllResourceKeys(groupKey);
         status.set("About to "
-                + (actionType == BulkMembersActionType.DEPROVISION ? "de" : "") + "provision "
+                + (action == ProvisionAction.DEPROVISION ? "de" : "") + "provision "
                 + users.size() + " users from " + groupResourceKeys);
 
         for (int i = 0; i < users.size() && !interrupt; i++) {
-            List<PropagationStatus> statuses = actionType == BulkMembersActionType.DEPROVISION
+            List<PropagationStatus> statuses = action == ProvisionAction.DEPROVISION
                     ? userProvisioningManager.deprovision(users.get(i).getKey(), groupResourceKeys, false)
                     : userProvisioningManager.provision(users.get(i).getKey(), true, null, groupResourceKeys, false);
             for (PropagationStatus propagationStatus : statuses) {
@@ -117,11 +117,11 @@ public class GroupMemberProvisionTaskJobDelegate extends AbstractSchedTaskJobDel
         membershipCond.setGroup(groupKey);
         List<AnyObject> anyObjects = searchDAO.search(SearchCond.getLeafCond(membershipCond), AnyTypeKind.ANY_OBJECT);
         status.set("About to "
-                + (actionType == BulkMembersActionType.DEPROVISION ? "de" : "") + "provision "
+                + (action == ProvisionAction.DEPROVISION ? "de" : "") + "provision "
                 + anyObjects.size() + " any objects from " + groupResourceKeys);
 
         for (int i = 0; i < anyObjects.size() && !interrupt; i++) {
-            List<PropagationStatus> statuses = actionType == BulkMembersActionType.DEPROVISION
+            List<PropagationStatus> statuses = action == ProvisionAction.DEPROVISION
                     ? anyObjectProvisioningManager.deprovision(anyObjects.get(i).getKey(), groupResourceKeys, false)
                     : anyObjectProvisioningManager.provision(anyObjects.get(i).getKey(), groupResourceKeys, false);
 
@@ -151,5 +151,4 @@ public class GroupMemberProvisionTaskJobDelegate extends AbstractSchedTaskJobDel
         // always record execution result
         return true;
     }
-
 }
index 4d7f00d..766f345 100644 (file)
@@ -36,7 +36,7 @@ import org.apache.syncope.common.lib.to.ExecTO;
 import org.apache.syncope.common.lib.to.PropagationTaskTO;
 import org.apache.syncope.common.lib.types.AuditElements;
 import org.apache.syncope.common.lib.types.AuditElements.Result;
-import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
+import org.apache.syncope.common.lib.types.ExecStatus;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.common.lib.types.TraceLevel;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
@@ -377,7 +377,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
         Date start = new Date();
 
         TaskExec execution = entityFactory.newEntity(TaskExec.class);
-        execution.setStatus(PropagationTaskExecStatus.CREATED.name());
+        execution.setStatus(ExecStatus.CREATED.name());
 
         String taskExecutionMessage = null;
         String failureReason = null;
@@ -423,8 +423,8 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
             }
 
             execution.setStatus(propagationAttempted.get()
-                    ? PropagationTaskExecStatus.SUCCESS.name()
-                    : PropagationTaskExecStatus.NOT_ATTEMPTED.name());
+                    ? ExecStatus.SUCCESS.name()
+                    : ExecStatus.NOT_ATTEMPTED.name());
 
             LOG.debug("Successfully propagated to {}", task.getResource());
             result = Result.SUCCESS;
@@ -449,7 +449,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
             }
 
             try {
-                execution.setStatus(PropagationTaskExecStatus.FAILURE.name());
+                execution.setStatus(ExecStatus.FAILURE.name());
             } catch (Exception wft) {
                 LOG.error("While executing KO action on {}", execution, wft);
             }
@@ -503,9 +503,8 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
             }
 
             if (reporter != null) {
-                reporter.onSuccessOrNonPriorityResourceFailures(
-                        taskTO,
-                        PropagationTaskExecStatus.valueOf(execution.getStatus()),
+                reporter.onSuccessOrNonPriorityResourceFailures(taskTO,
+                        ExecStatus.valueOf(execution.getStatus()),
                         failureReason,
                         beforeObj,
                         afterObj);
@@ -568,7 +567,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
     protected boolean hasToBeregistered(final PropagationTask task, final TaskExec execution) {
         boolean result;
 
-        boolean failed = PropagationTaskExecStatus.valueOf(execution.getStatus()) != PropagationTaskExecStatus.SUCCESS;
+        boolean failed = ExecStatus.valueOf(execution.getStatus()) != ExecStatus.SUCCESS;
 
         switch (task.getOperation()) {
 
index 2932147..a2df73c 100644 (file)
@@ -25,7 +25,7 @@ import java.util.List;
 import java.util.Optional;
 import org.apache.syncope.common.lib.to.PropagationStatus;
 import org.apache.syncope.common.lib.to.PropagationTaskTO;
-import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
+import org.apache.syncope.common.lib.types.ExecStatus;
 import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter;
 import org.apache.syncope.core.provisioning.java.utils.ConnObjectUtils;
@@ -48,7 +48,7 @@ public class DefaultPropagationReporter implements PropagationReporter {
     @Override
     public void onSuccessOrNonPriorityResourceFailures(
             final PropagationTaskTO taskTO,
-            final PropagationTaskExecStatus executionStatus,
+            final ExecStatus executionStatus,
             final String failureReason,
             final ConnectorObject beforeObj,
             final ConnectorObject afterObj) {
@@ -79,7 +79,7 @@ public class DefaultPropagationReporter implements PropagationReporter {
         if (propagationTask.isPresent()) {
             PropagationStatus status = new PropagationStatus();
             status.setResource(propagationTask.get().getResource());
-            status.setStatus(PropagationTaskExecStatus.FAILURE);
+            status.setStatus(ExecStatus.FAILURE);
             status.setFailureReason(
                     "Propagation error: " + failingResource + " priority resource failed to propagate.");
             add(status);
index be97bb5..adaa8f7 100644 (file)
@@ -35,7 +35,7 @@ import java.util.function.Function;
 import java.util.stream.Collectors;
 import javax.annotation.Resource;
 import org.apache.syncope.common.lib.to.PropagationTaskTO;
-import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
+import org.apache.syncope.common.lib.types.ExecStatus;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
@@ -99,15 +99,15 @@ public class PriorityPropagationTaskExecutor extends AbstractPropagationTaskExec
         // first process priority resources sequentially and fail as soon as any propagation failure is reported
         prioritizedTasks.forEach(task -> {
             TaskExec execution = null;
-            PropagationTaskExecStatus execStatus;
+            ExecStatus execStatus;
             try {
                 execution = newPropagationTaskCallable(task, reporter).call();
-                execStatus = PropagationTaskExecStatus.valueOf(execution.getStatus());
+                execStatus = ExecStatus.valueOf(execution.getStatus());
             } catch (Exception e) {
                 LOG.error("Unexpected exception", e);
-                execStatus = PropagationTaskExecStatus.FAILURE;
+                execStatus = ExecStatus.FAILURE;
             }
-            if (execStatus != PropagationTaskExecStatus.SUCCESS) {
+            if (execStatus != ExecStatus.SUCCESS) {
                 throw new PropagationException(task.getResource(), execution == null ? null : execution.getMessage());
             }
         });
@@ -128,8 +128,7 @@ public class PriorityPropagationTaskExecutor extends AbstractPropagationTaskExec
         if (!nullPriority.isEmpty()) {
             if (nullPriorityAsync) {
                 nullPriority.forEach((task, exec) -> {
-                    reporter.onSuccessOrNonPriorityResourceFailures(
-                            task, PropagationTaskExecStatus.CREATED, null, null, null);
+                    reporter.onSuccessOrNonPriorityResourceFailures(task, ExecStatus.CREATED, null, null, null);
                 });
             } else {
                 final Set<Future<TaskExec>> nullPriorityFutures = new HashSet<>(nullPriority.values());
index 06f6da9..4d42271 100644 (file)
@@ -31,7 +31,7 @@ import org.apache.syncope.common.lib.types.AuditElements;
 import org.apache.syncope.common.lib.types.AuditElements.Result;
 import org.apache.syncope.common.lib.types.MatchingRule;
 import org.apache.syncope.common.lib.types.PatchOperation;
-import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
+import org.apache.syncope.common.lib.types.ExecStatus;
 import org.apache.syncope.core.provisioning.api.PropagationByResource;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.common.lib.types.UnmatchingRule;
@@ -485,7 +485,7 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
         }
     }
 
-    protected ProvisioningReport.Status toProvisioningReportStatus(final PropagationTaskExecStatus status) {
+    protected ProvisioningReport.Status toProvisioningReportStatus(final ExecStatus status) {
         switch (status) {
             case FAILURE:
                 return ProvisioningReport.Status.FAILURE;
index 2a4a510..4973407 100644 (file)
@@ -29,7 +29,7 @@ import org.apache.syncope.common.lib.to.RealmTO;
 import org.apache.syncope.common.lib.types.AuditElements;
 import org.apache.syncope.common.lib.types.AuditElements.Result;
 import org.apache.syncope.common.lib.types.MatchingRule;
-import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
+import org.apache.syncope.common.lib.types.ExecStatus;
 import org.apache.syncope.core.provisioning.api.PropagationByResource;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.common.lib.types.UnmatchingRule;
@@ -444,7 +444,7 @@ public class DefaultRealmPushResultHandler
         }
     }
 
-    private ProvisioningReport.Status toProvisioningReportStatus(final PropagationTaskExecStatus status) {
+    private ProvisioningReport.Status toProvisioningReportStatus(final ExecStatus status) {
         switch (status) {
             case FAILURE:
                 return ProvisioningReport.Status.FAILURE;
index 9d028e4..59a451a 100644 (file)
  */
 package org.apache.syncope.core.rest.cxf.service;
 
+import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Collectors;
 import javax.ws.rs.BadRequestException;
 import javax.ws.rs.core.Response;
 import org.apache.commons.lang3.StringUtils;
@@ -30,29 +32,27 @@ import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.patch.AnyPatch;
 import org.apache.syncope.common.lib.patch.AssociationPatch;
 import org.apache.syncope.common.lib.patch.AttrPatch;
-import org.apache.syncope.common.lib.patch.BooleanReplacePatchItem;
 import org.apache.syncope.common.lib.patch.DeassociationPatch;
-import org.apache.syncope.common.lib.patch.StatusPatch;
-import org.apache.syncope.common.lib.patch.UserPatch;
 import org.apache.syncope.common.lib.search.SpecialAttr;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.to.AttrTO;
-import org.apache.syncope.common.lib.to.BulkAction;
-import org.apache.syncope.common.lib.to.BulkActionResult;
 import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.to.ProvisioningResult;
 import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.ResourceAssociationAction;
 import org.apache.syncope.common.lib.types.ResourceDeassociationAction;
 import org.apache.syncope.common.lib.types.SchemaType;
-import org.apache.syncope.common.lib.types.StatusPatchType;
+import org.apache.syncope.common.rest.api.Preference;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.batch.BatchPayloadGenerator;
+import org.apache.syncope.common.rest.api.batch.BatchResponseItem;
 import org.apache.syncope.common.rest.api.beans.AnyQuery;
 import org.apache.syncope.common.rest.api.service.AnyService;
 import org.apache.syncope.core.logic.AbstractAnyLogic;
-import org.apache.syncope.core.logic.UserLogic;
 import org.apache.syncope.core.persistence.api.dao.AnyDAO;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
 
 public abstract class AbstractAnyService<TO extends AnyTO, P extends AnyPatch>
         extends AbstractServiceImpl
@@ -230,28 +230,60 @@ public abstract class AbstractAnyService<TO extends AnyTO, P extends AnyPatch>
                 break;
 
             default:
-                updated = new ProvisioningResult<>();
-                updated.setEntity(getAnyLogic().read(patch.getKey()));
+                throw new BadRequestException("Missing action");
         }