You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2020/01/09 10:46:21 UTC

[syncope] 02/03: [SYNCOPE-1531] Improvements: Swagger docs + propagation actions for CSV export + result page in Admin Console for CSV import

This is an automated email from the ASF dual-hosted git repository.

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

commit 18d84af51dee57f4636ddca65dcdf33b4b7e29f5
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Wed Jan 8 17:17:38 2020 +0100

    [SYNCOPE-1531] Improvements: Swagger docs + propagation actions for CSV export + result page in Admin Console for CSV import
---
 ...AnyDirectoryPanelAdditionalActionsProvider.java |  42 +++++--
 .../client/console/panels/CSVConfPanel.java        |   7 +-
 .../console/rest/ReconciliationRestClient.java     |   8 +-
 .../console/wizards/CSVPullWizardBuilder.java      |  21 ++--
 .../console/wizards/CSVPushWizardBuilder.java      |  28 ++++-
 .../wizards/any/LinkedAccountPlainAttrsPanel.java  |   3 +-
 .../wizards/any/ProvisioningReportsPanel.java      |  95 +++++++++++++++
 .../wizards/CSVPullWizardBuilder$PullTask.html     |   2 +-
 .../CSVPullWizardBuilder$PullTask.properties       |   2 +-
 .../CSVPullWizardBuilder$PullTask_fr_CA.properties |   2 +-
 .../CSVPullWizardBuilder$PullTask_it.properties    |   2 +-
 .../CSVPullWizardBuilder$PullTask_ja.properties    |   2 +-
 .../CSVPullWizardBuilder$PullTask_pt_BR.properties |   2 +-
 .../CSVPullWizardBuilder$PullTask_ru.properties    |   2 +-
 .../wizards/CSVPushWizardBuilder$PushTask.html     |   3 +-
 .../CSVPushWizardBuilder$PushTask.properties       |   3 +-
 .../CSVPushWizardBuilder$PushTask_fr_CA.properties |   3 +-
 .../CSVPushWizardBuilder$PushTask_it.properties    |   3 +-
 .../CSVPushWizardBuilder$PushTask_ja.properties    |   3 +-
 .../CSVPushWizardBuilder$PushTask_pt_BR.properties |   3 +-
 .../CSVPushWizardBuilder$PushTask_ru.properties    |   3 +-
 .../ProvisioningReportsPanel.html}                 |  11 +-
 .../ProvisioningReportsPanel.properties}           |   8 +-
 .../ProvisioningReportsPanel_fr_CA.properties}     |   8 +-
 .../ProvisioningReportsPanel_it.properties}        |   8 +-
 .../ProvisioningReportsPanel_ja.properties}        |   8 +-
 .../ProvisioningReportsPanel_pt_BR.properties}     |   8 +-
 .../ProvisioningReportsPanel_ru.properties}        |   8 +-
 .../syncope/client/ui/commons/BaseLogin.java       |   3 +-
 .../console/audit/AuditHistoryDirectoryPanel.java  |   3 +-
 .../client/console/panels/AnyDirectoryPanel.java   |   3 +-
 .../client/console/panels/ConsoleLogPanel.java     |   6 +-
 .../markup/html/form/AjaxCharacterFieldPanel.java  |   3 +-
 .../wizards/any/AbstractAttrsWizardStep.java       |   5 +-
 .../syncope/common/lib/to/ProvisioningReport.java  |  15 ++-
 .../common/rest/api/beans/AbstractCSVSpec.java     |  70 ++++++++---
 .../syncope/common/rest/api/beans/CSVPushSpec.java |  16 +++
 .../syncope/common/rest/api/beans/ReconQuery.java  |   3 +-
 .../syncope/common/rest/api/beans/ExecQuery.java   |   1 -
 .../common/rest/api/beans/ExecuteQuery.java        |   1 -
 .../common/rest/api/service/JAXRSService.java      |   2 +
 .../syncope/core/logic/ReconciliationLogic.java    |  69 +++++------
 .../core/logic/ReconciliationLogicTest.java        |  12 +-
 .../api/jexl/SyncopeJexlFunctions.java             |   1 -
 .../pushpull/stream/SyncopeStreamPullExecutor.java |   3 +-
 .../pushpull/stream/SyncopeStreamPushExecutor.java |   4 +-
 .../provisioning/api/IntAttrNameParserTest.java    |   7 +-
 core/provisioning-java/pom.xml                     |   5 +
 .../java/pushpull/OutboundMatcher.java             |   5 +-
 .../java/pushpull/stream/CSVStreamConnector.java}  | 132 +++++++++++++++------
 .../pushpull/stream/StreamPullJobDelegate.java     |   4 +-
 .../pushpull/stream/StreamPushJobDelegate.java     |  30 +++--
 .../core/provisioning/java/utils/MappingUtils.java |   3 +-
 .../java/pushpull/LDAPPasswordPullActionsTest.java |  33 +++---
 .../pushpull/stream/StreamPullJobDelegateTest.java |  58 +++++----
 .../pushpull/stream/StreamPushJobDelegateTest.java |  36 +++---
 core/spring/pom.xml                                |   9 +-
 .../client/console/rest/BpmnProcessRestClient.java |   5 +-
 .../syncope/core/logic/saml2/SAML2UserManager.java |   3 +-
 .../org/apache/syncope/fit/console/LogsITCase.java |   5 +-
 .../syncope/fit/core/ReconciliationITCase.java     |  13 +-
 61 files changed, 567 insertions(+), 299 deletions(-)

diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMAnyDirectoryPanelAdditionalActionsProvider.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMAnyDirectoryPanelAdditionalActionsProvider.java
index c6abd9d..b091655 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMAnyDirectoryPanelAdditionalActionsProvider.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMAnyDirectoryPanelAdditionalActionsProvider.java
@@ -18,6 +18,8 @@
  */
 package org.apache.syncope.client.console.commons;
 
+import java.io.Serializable;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
 import org.apache.syncope.client.console.PreferenceManager;
@@ -29,8 +31,11 @@ import org.apache.syncope.client.console.wicket.ajax.form.AjaxDownloadBehavior;
 import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
 import org.apache.syncope.client.console.wizards.CSVPullWizardBuilder;
 import org.apache.syncope.client.console.wizards.CSVPushWizardBuilder;
+import org.apache.syncope.client.console.wizards.any.ProvisioningReportsPanel;
+import org.apache.syncope.client.console.wizards.any.ResultPage;
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.common.rest.api.beans.AnyQuery;
 import org.apache.syncope.common.rest.api.beans.CSVPullSpec;
 import org.apache.syncope.common.rest.api.beans.CSVPushSpec;
@@ -39,6 +44,7 @@ import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.markup.html.AjaxLink;
 import org.apache.wicket.event.IEvent;
 import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.panel.Panel;
 import org.apache.wicket.model.Model;
 import org.apache.wicket.model.StringResourceModel;
 
@@ -70,17 +76,39 @@ public class IdMAnyDirectoryPanelAdditionalActionsProvider implements AnyDirecto
                     modal.close(target);
                 } else if (event.getPayload() instanceof AjaxWizard.NewItemFinishEvent) {
                     AjaxWizard.NewItemFinishEvent<?> payload = (AjaxWizard.NewItemFinishEvent) event.getPayload();
-                    if (Constants.OPERATION_SUCCEEDED.equals(payload.getResult())) {
-                        AjaxRequestTarget target = payload.getTarget();
+                    AjaxRequestTarget target = payload.getTarget();
 
+                    SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED));
+                    ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
+
+                    target.add(container);
+
+                    if (payload.getResult() instanceof ArrayList) {
+                        modal.setContent(new ResultPage<Serializable>(
+                                null,
+                                payload.getResult()) {
+
+                            private static final long serialVersionUID = -2630573849050255233L;
+
+                            @Override
+                            protected void closeAction(final AjaxRequestTarget target) {
+                                modal.close(target);
+                            }
+
+                            @Override
+                            protected Panel customResultBody(
+                                    final String id, final Serializable item, final Serializable result) {
+
+                                @SuppressWarnings("unchecked")
+                                ArrayList<ProvisioningReport> reports = (ArrayList<ProvisioningReport>) result;
+                                return new ProvisioningReportsPanel(id, reports, pageRef);
+                            }
+                        });
+                        target.add(modal.getForm());
+                    } else if (Constants.OPERATION_SUCCEEDED.equals(payload.getResult())) {
                         if (csvDownloadBehavior.hasResponse()) {
                             csvDownloadBehavior.initiate(target);
                         }
-
-                        SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED));
-                        ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
-
-                        target.add(container);
                         modal.close(target);
                     }
                 }
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/CSVConfPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/CSVConfPanel.java
index a5e4f0c..4867673 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/CSVConfPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/CSVConfPanel.java
@@ -18,7 +18,6 @@
  */
 package org.apache.syncope.client.console.panels;
 
-import java.util.Arrays;
 import java.util.List;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -43,21 +42,21 @@ public class CSVConfPanel extends Panel {
 
         AjaxCharacterFieldPanel columnSeparator = new AjaxCharacterFieldPanel(
                 "columnSeparator", "columnSeparator", new PropertyModel<>(spec, "columnSeparator"));
-        columnSeparator.setChoices(Arrays.asList(',', ';', ' '));
+        columnSeparator.setChoices(List.of(',', ';', ' '));
         columnSeparator.setRequired(true);
         add(columnSeparator);
 
         AjaxTextFieldPanel arrayElementSeparator = new AjaxTextFieldPanel(
                 "arrayElementSeparator", "arrayElementSeparator",
                 new PropertyModel<>(spec, "arrayElementSeparator"));
-        arrayElementSeparator.setChoices(Arrays.asList(";"));
+        arrayElementSeparator.setChoices(List.of(";"));
         arrayElementSeparator.setRequired(true);
         arrayElementSeparator.addValidator(new StringValidator(1, 1));
         add(arrayElementSeparator);
 
         AjaxCharacterFieldPanel quoteChar = new AjaxCharacterFieldPanel(
                 "quoteChar", "quoteChar", new PropertyModel<>(spec, "quoteChar"));
-        quoteChar.setChoices(Arrays.asList('"'));
+        quoteChar.setChoices(List.of('"'));
         quoteChar.setRequired(true);
         add(quoteChar);
 
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/rest/ReconciliationRestClient.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/rest/ReconciliationRestClient.java
index 3855eb7..a341569 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/rest/ReconciliationRestClient.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/rest/ReconciliationRestClient.java
@@ -19,7 +19,8 @@
 package org.apache.syncope.client.console.rest;
 
 import java.io.InputStream;
-import java.util.List;
+import java.util.ArrayList;
+import java.util.stream.Collectors;
 import javax.ws.rs.core.Response;
 import org.apache.cxf.jaxrs.client.Client;
 import org.apache.cxf.jaxrs.client.WebClient;
@@ -63,12 +64,13 @@ public class ReconciliationRestClient extends BaseRestClient {
         return response;
     }
 
-    public static List<ProvisioningReport> pull(final CSVPullSpec spec, final InputStream csv) {
+    public static ArrayList<ProvisioningReport> pull(final CSVPullSpec spec, final InputStream csv) {
         ReconciliationService service = getService(ReconciliationService.class);
         Client client = WebClient.client(service);
         client.type(RESTHeaders.TEXT_CSV);
 
-        List<ProvisioningReport> result = service.pull(spec, csv);
+        ArrayList<ProvisioningReport> result = service.pull(spec, csv).stream().
+                collect(Collectors.toCollection(ArrayList::new));
 
         SyncopeConsoleSession.get().resetClient(ReconciliationService.class);
 
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder.java
index 1c3a2a2..99fb6b1 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder.java
@@ -21,9 +21,7 @@ package org.apache.syncope.client.console.wizards;
 import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.fileinput.BootstrapFileInputField;
 import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.fileinput.FileInputConfig;
 import java.io.ByteArrayInputStream;
-import java.io.Serializable;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -36,6 +34,7 @@ import org.apache.syncope.client.ui.commons.markup.html.form.AjaxCheckBoxPanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPalettePanel;
 import org.apache.syncope.common.lib.to.EntityTO;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.common.lib.types.ConflictResolutionAction;
 import org.apache.syncope.common.lib.types.IdMImplementationType;
 import org.apache.syncope.common.lib.types.MatchingRule;
@@ -102,9 +101,8 @@ public class CSVPullWizardBuilder extends BaseAjaxWizardBuilder<CSVPullSpec> {
     }
 
     @Override
-    protected Serializable onApplyInternal(final CSVPullSpec modelObject) {
-        ReconciliationRestClient.pull(modelObject, new ByteArrayInputStream(csv.getObject()));
-        return Constants.OPERATION_SUCCEEDED;
+    protected ArrayList<ProvisioningReport> onApplyInternal(final CSVPullSpec modelObject) {
+        return ReconciliationRestClient.pull(modelObject, new ByteArrayInputStream(csv.getObject()));
     }
 
     @Override
@@ -243,23 +241,24 @@ public class CSVPullWizardBuilder extends BaseAjaxWizardBuilder<CSVPullSpec> {
 
             AjaxDropDownChoicePanel<MatchingRule> matchingRule = new AjaxDropDownChoicePanel<>(
                     "matchingRule", "matchingRule", new PropertyModel<>(spec, "matchingRule"), false);
-            matchingRule.setChoices(Arrays.asList(MatchingRule.values()));
+            matchingRule.setChoices(List.of(MatchingRule.values()));
             add(matchingRule);
 
             AjaxDropDownChoicePanel<UnmatchingRule> unmatchingRule = new AjaxDropDownChoicePanel<>(
                     "unmatchingRule", "unmatchingRule", new PropertyModel<>(spec, "unmatchingRule"),
                     false);
-            unmatchingRule.setChoices(Arrays.asList(UnmatchingRule.values()));
+            unmatchingRule.setChoices(List.of(UnmatchingRule.values()));
             add(unmatchingRule);
 
-            AjaxPalettePanel<String> actions = new AjaxPalettePanel.Builder<String>().
-                    build("actions", new PropertyModel<>(spec, "actions"), new ListModel<>(pullActions.getObject()));
-            add(actions);
+            AjaxPalettePanel<String> provisioningActions =
+                    new AjaxPalettePanel.Builder<String>().build("provisioningActions",
+                            new PropertyModel<>(spec, "provisioningActions"), new ListModel<>(pullActions.getObject()));
+            add(provisioningActions);
 
             AjaxDropDownChoicePanel<ConflictResolutionAction> conflictResolutionAction = new AjaxDropDownChoicePanel<>(
                     "conflictResolutionAction", "conflictResolutionAction",
                     new PropertyModel<>(spec, "conflictResolutionAction"), false);
-            conflictResolutionAction.setChoices(Arrays.asList(ConflictResolutionAction.values()));
+            conflictResolutionAction.setChoices(List.of(ConflictResolutionAction.values()));
             conflictResolutionAction.setRequired(true);
             add(conflictResolutionAction);
 
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder.java
index 1f2911d..ee2af97 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder.java
@@ -19,7 +19,6 @@
 package org.apache.syncope.client.console.wizards;
 
 import java.io.Serializable;
-import java.util.Arrays;
 import java.util.List;
 import java.util.stream.Collectors;
 import org.apache.syncope.client.console.panels.CSVConfPanel;
@@ -98,6 +97,17 @@ public class CSVPushWizardBuilder extends BaseAjaxWizardBuilder<CSVPushSpec> {
 
         private final ImplementationRestClient implRestClient = new ImplementationRestClient();
 
+        private final IModel<List<String>> propActions = new LoadableDetachableModel<List<String>>() {
+
+            private static final long serialVersionUID = 4659376149825914247L;
+
+            @Override
+            protected List<String> load() {
+                return implRestClient.list(IdMImplementationType.PROPAGATION_ACTIONS).stream().
+                        map(EntityTO::getKey).sorted().collect(Collectors.toList());
+            }
+        };
+
         private final IModel<List<String>> pushActions = new LoadableDetachableModel<List<String>>() {
 
             private static final long serialVersionUID = 4659376149825914247L;
@@ -114,20 +124,26 @@ public class CSVPushWizardBuilder extends BaseAjaxWizardBuilder<CSVPushSpec> {
                     "ignorePaging", "ignorePaging", new PropertyModel<>(spec, "ignorePaging"), true);
             add(ignorePaging);
 
+            AjaxPalettePanel<String> propagationActions =
+                    new AjaxPalettePanel.Builder<String>().build("propagationActions",
+                            new PropertyModel<>(spec, "propagationActions"), new ListModel<>(propActions.getObject()));
+            add(propagationActions);
+
             AjaxDropDownChoicePanel<MatchingRule> matchingRule = new AjaxDropDownChoicePanel<>(
                     "matchingRule", "matchingRule", new PropertyModel<>(spec, "matchingRule"), false);
-            matchingRule.setChoices(Arrays.asList(MatchingRule.values()));
+            matchingRule.setChoices(List.of(MatchingRule.values()));
             add(matchingRule);
 
             AjaxDropDownChoicePanel<UnmatchingRule> unmatchingRule = new AjaxDropDownChoicePanel<>(
                     "unmatchingRule", "unmatchingRule", new PropertyModel<>(spec, "unmatchingRule"),
                     false);
-            unmatchingRule.setChoices(Arrays.asList(UnmatchingRule.values()));
+            unmatchingRule.setChoices(List.of(UnmatchingRule.values()));
             add(unmatchingRule);
 
-            AjaxPalettePanel<String> actions = new AjaxPalettePanel.Builder<String>().
-                    build("actions", new PropertyModel<>(spec, "actions"), new ListModel<>(pushActions.getObject()));
-            add(actions);
+            AjaxPalettePanel<String> provisioningActions =
+                    new AjaxPalettePanel.Builder<String>().build("provisioningActions",
+                            new PropertyModel<>(spec, "provisioningActions"), new ListModel<>(pushActions.getObject()));
+            add(provisioningActions);
         }
     }
 }
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountPlainAttrsPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountPlainAttrsPanel.java
index edfbed7..3346de8 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountPlainAttrsPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountPlainAttrsPanel.java
@@ -22,7 +22,6 @@ import org.apache.syncope.client.ui.commons.wizards.any.EntityWrapper;
 import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.checkbox.bootstraptoggle.BootstrapToggle;
 import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.checkbox.bootstraptoggle.BootstrapToggleConfig;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -80,7 +79,7 @@ public class LinkedAccountPlainAttrsPanel extends AbstractAttrsWizardStep<PlainS
         super(userTO,
                 AjaxWizard.Mode.EDIT,
                 new AnyTypeRestClient().read(userTO.getType()).getClasses(),
-                AnyLayoutUtils.fetch(Arrays.asList(userTO.getType())).getUser().getWhichPlainAttrs(),
+                AnyLayoutUtils.fetch(List.of(userTO.getType())).getUser().getWhichPlainAttrs(),
                 modelObject);
 
         this.linkedAccountTO = modelObject.getInnerObject();
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel.java
new file mode 100644
index 0000000..264bb3f
--- /dev/null
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel.java
@@ -0,0 +1,95 @@
+/*
+ * 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.wizards.any;
+
+import java.text.MessageFormat;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.syncope.client.console.panels.ListViewPanel;
+import org.apache.syncope.client.ui.commons.wicket.markup.html.bootstrap.tabs.Accordion;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.extensions.markup.html.tabs.AbstractTab;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.Model;
+
+public class ProvisioningReportsPanel extends Panel {
+
+    private static final long serialVersionUID = -1450755344104125918L;
+
+    public ProvisioningReportsPanel(
+            final String id, final List<ProvisioningReport> results, final PageReference pageRef) {
+        super(id);
+
+        List<ProvisioningReport> success = results.stream().
+                filter(result -> result.getStatus() == ProvisioningReport.Status.SUCCESS).
+                collect(Collectors.toList());
+        add(new Accordion("success", List.of(new AbstractTab(
+                new Model<>(MessageFormat.format(getString("success"), success.size()))) {
+
+            private static final long serialVersionUID = 1037272333056449378L;
+
+            @Override
+            public WebMarkupContainer getPanel(final String panelId) {
+                return new ListViewPanel.Builder<>(ProvisioningReport.class, pageRef).
+                        setItems(success).
+                        withChecks(ListViewPanel.CheckAvailability.NONE).
+                        includes("name", "message").
+                        build(panelId);
+            }
+        }), Model.of(-1)).setOutputMarkupId(true));
+
+        List<ProvisioningReport> failure = results.stream().
+                filter(result -> result.getStatus() == ProvisioningReport.Status.FAILURE).
+                collect(Collectors.toList());
+        add(new Accordion("failure", List.of(new AbstractTab(
+                new Model<>(MessageFormat.format(getString("failure"), failure.size()))) {
+
+            private static final long serialVersionUID = 1037272333056449378L;
+
+            @Override
+            public WebMarkupContainer getPanel(final String panelId) {
+                return new ListViewPanel.Builder<>(ProvisioningReport.class, pageRef).
+                        setItems(failure).
+                        withChecks(ListViewPanel.CheckAvailability.NONE).
+                        includes("name", "message").
+                        build(panelId);
+            }
+        }), Model.of(-1)).setOutputMarkupId(true));
+
+        List<ProvisioningReport> ignore = results.stream().
+                filter(result -> result.getStatus() == ProvisioningReport.Status.IGNORE).
+                collect(Collectors.toList());
+        add(new Accordion("ignore", List.of(new AbstractTab(
+                new Model<>(MessageFormat.format(getString("ignore"), ignore.size()))) {
+
+            private static final long serialVersionUID = 1037272333056449378L;
+
+            @Override
+            public WebMarkupContainer getPanel(final String panelId) {
+                return new ListViewPanel.Builder<>(ProvisioningReport.class, pageRef).
+                        setItems(ignore).
+                        withChecks(ListViewPanel.CheckAvailability.NONE).
+                        includes("name", "message").
+                        build(panelId);
+            }
+        }), Model.of(-1)).setOutputMarkupId(true));
+    }
+}
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask.html b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask.html
index 761b1f4..aab535e 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask.html
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask.html
@@ -22,7 +22,7 @@ under the License.
       <div wicket:id="remediation">[remediation]</div>
       <div wicket:id="matchingRule">[matchingRule]</div>
       <div wicket:id="unmatchingRule">[unmatchingRule]</div>
-      <div wicket:id="actions">[actions]</div>
+      <div wicket:id="provisioningActions">[provisioningActions]</div>
     </div>
     <div class="form-group">
       <div wicket:id="conflictResolutionAction">[conflictResolutionAction]</div>
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask.properties
index 5527723..eed190d 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask.properties
@@ -17,6 +17,6 @@
 remediation=Remediation
 matchingRule=Matching rule
 unmatchingRule=Unmatching rule
-actions=Actions
+provisioningActions=Pull Actions
 conflictResolutionAction=Conflict resolution action
 pullCorrelationRule=Correlation rule
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask_fr_CA.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask_fr_CA.properties
index c5e744a..fe255e2 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask_fr_CA.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask_fr_CA.properties
@@ -17,6 +17,6 @@
 remediation=Remediation
 matchingRule=R\u00e8gle de correspondance
 unmatchingRule=R\u00e8gle in\u00e9gal\u00e9e
-actions=Actions
+provisioningActions=Actions de pull
 conflictResolutionAction=Action de r\u00e9solution de conflits
 pullCorrelationRule=R\u00e8gle de corr\u00e9lation
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask_it.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask_it.properties
index 02ad161..fddd717 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask_it.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask_it.properties
@@ -17,6 +17,6 @@
 remediation=Bonifiche
 matchingRule=Regola di matrching
 unmatchingRule=Regola di unmatching
-actions=Azioni
+provisioningActions=Azioni di pull
 conflictResolutionAction=Azione di Risoluzione Conflitti
 pullCorrelationRule=Regola di Correlazione
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask_ja.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask_ja.properties
index 5527723..eed190d 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask_ja.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask_ja.properties
@@ -17,6 +17,6 @@
 remediation=Remediation
 matchingRule=Matching rule
 unmatchingRule=Unmatching rule
-actions=Actions
+provisioningActions=Pull Actions
 conflictResolutionAction=Conflict resolution action
 pullCorrelationRule=Correlation rule
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask_pt_BR.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask_pt_BR.properties
index 5527723..eed190d 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask_pt_BR.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask_pt_BR.properties
@@ -17,6 +17,6 @@
 remediation=Remediation
 matchingRule=Matching rule
 unmatchingRule=Unmatching rule
-actions=Actions
+provisioningActions=Pull Actions
 conflictResolutionAction=Conflict resolution action
 pullCorrelationRule=Correlation rule
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask_ru.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask_ru.properties
index 5527723..eed190d 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask_ru.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPullWizardBuilder$PullTask_ru.properties
@@ -17,6 +17,6 @@
 remediation=Remediation
 matchingRule=Matching rule
 unmatchingRule=Unmatching rule
-actions=Actions
+provisioningActions=Pull Actions
 conflictResolutionAction=Conflict resolution action
 pullCorrelationRule=Correlation rule
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.html b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.html
index 3773882..13dab03 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.html
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.html
@@ -20,11 +20,12 @@ under the License.
   <wicket:panel>
     <div class="form-group">
       <div wicket:id="ignorePaging">[ignorePaging]</div>
+      <div wicket:id="propagationActions">[propagationActions]</div>
     </div>
     <div class="form-group">
       <div wicket:id="matchingRule">[matchingRule]</div>
       <div wicket:id="unmatchingRule">[unmatchingRule]</div>
-      <div wicket:id="actions">[actions]</div>
+      <div wicket:id="provisioningActions">[provisioningActions]</div>
     </div>
   </wicket:panel>
 </html>
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.properties
index f269211..fa1b12b 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.properties
@@ -16,5 +16,6 @@
 # under the License.
 matchingRule=Matching rule
 unmatchingRule=Unmatching rule
-actions=Actions
+provisioningActions=Push Actions
 ignorePaging=Include all pages?
+propagationActions=Propagation Actions
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask_fr_CA.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask_fr_CA.properties
index 68e7e24..4ae6771 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask_fr_CA.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask_fr_CA.properties
@@ -16,5 +16,6 @@
 # under the License.
 matchingRule=R\u00e8gle de correspondance
 unmatchingRule=R\u00e8gle in\u00e9gal\u00e9e
-actions=Actions
+provisioningActions=Actions de push
 ignorePaging=Inclure toutes les pages?
+propagationActions=Actions de propagation
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask_it.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask_it.properties
index 6dfeb3d..c26816b 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask_it.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask_it.properties
@@ -16,5 +16,6 @@
 # under the License.
 matchingRule=Regola di matrching
 unmatchingRule=Regola di unmatching
-actions=Azioni
+provisioningActions=Azioni di push
 ignorePaging=Includere tutte le pagine?
+propagationActions=Azioni di propagazione
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask_ja.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask_ja.properties
index f269211..fa1b12b 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask_ja.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask_ja.properties
@@ -16,5 +16,6 @@
 # under the License.
 matchingRule=Matching rule
 unmatchingRule=Unmatching rule
-actions=Actions
+provisioningActions=Push Actions
 ignorePaging=Include all pages?
+propagationActions=Propagation Actions
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask_pt_BR.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask_pt_BR.properties
index f269211..fa1b12b 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask_pt_BR.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask_pt_BR.properties
@@ -16,5 +16,6 @@
 # under the License.
 matchingRule=Matching rule
 unmatchingRule=Unmatching rule
-actions=Actions
+provisioningActions=Push Actions
 ignorePaging=Include all pages?
+propagationActions=Propagation Actions
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask_ru.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask_ru.properties
index f269211..fa1b12b 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask_ru.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask_ru.properties
@@ -16,5 +16,6 @@
 # under the License.
 matchingRule=Matching rule
 unmatchingRule=Unmatching rule
-actions=Actions
+provisioningActions=Push Actions
 ignorePaging=Include all pages?
+propagationActions=Propagation Actions
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.html b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel.html
similarity index 74%
copy from client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.html
copy to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel.html
index 3773882..79d6373 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.html
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel.html
@@ -18,13 +18,8 @@ under the License.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
   <wicket:panel>
-    <div class="form-group">
-      <div wicket:id="ignorePaging">[ignorePaging]</div>
-    </div>
-    <div class="form-group">
-      <div wicket:id="matchingRule">[matchingRule]</div>
-      <div wicket:id="unmatchingRule">[unmatchingRule]</div>
-      <div wicket:id="actions">[actions]</div>
-    </div>
+    <div wicket:id="success"/>
+    <div wicket:id="failure"/>
+    <div wicket:id="ignore"/>
   </wicket:panel>
 </html>
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel.properties
similarity index 87%
copy from client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.properties
copy to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel.properties
index f269211..709310c 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel.properties
@@ -14,7 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-matchingRule=Matching rule
-unmatchingRule=Unmatching rule
-actions=Actions
-ignorePaging=Include all pages?
+success=Successfully imported: {0}
+failure=Failed to import: {0}
+ignore=Ignored during import: {0}
+message=Message
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel_fr_CA.properties
similarity index 84%
copy from client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.properties
copy to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel_fr_CA.properties
index f269211..b1800a6 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel_fr_CA.properties
@@ -14,7 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-matchingRule=Matching rule
-unmatchingRule=Unmatching rule
-actions=Actions
-ignorePaging=Include all pages?
+success=Import\u00e9 avec succ\u00e8s: {0}
+failure=\u00c9chec de l''importation: {0}
+ignore=Ignor\u00e9 lors de l''importationt: {0}
+message=Message
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel_it.properties
similarity index 85%
copy from client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.properties
copy to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel_it.properties
index f269211..2888724 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel_it.properties
@@ -14,7 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-matchingRule=Matching rule
-unmatchingRule=Unmatching rule
-actions=Actions
-ignorePaging=Include all pages?
+success=Importati con successo: {0}
+failure=Errori nell''importazione: {0}
+ignore=Ignorati dall''importazione: {0}
+message=Messaggio
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel_ja.properties
similarity index 87%
copy from client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.properties
copy to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel_ja.properties
index f269211..709310c 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel_ja.properties
@@ -14,7 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-matchingRule=Matching rule
-unmatchingRule=Unmatching rule
-actions=Actions
-ignorePaging=Include all pages?
+success=Successfully imported: {0}
+failure=Failed to import: {0}
+ignore=Ignored during import: {0}
+message=Message
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel_pt_BR.properties
similarity index 87%
copy from client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.properties
copy to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel_pt_BR.properties
index f269211..709310c 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel_pt_BR.properties
@@ -14,7 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-matchingRule=Matching rule
-unmatchingRule=Unmatching rule
-actions=Actions
-ignorePaging=Include all pages?
+success=Successfully imported: {0}
+failure=Failed to import: {0}
+ignore=Ignored during import: {0}
+message=Message
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel_ru.properties
similarity index 87%
copy from client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.properties
copy to client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel_ru.properties
index f269211..709310c 100644
--- a/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/CSVPushWizardBuilder$PushTask.properties
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/wizards/any/ProvisioningReportsPanel_ru.properties
@@ -14,7 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-matchingRule=Matching rule
-unmatchingRule=Unmatching rule
-actions=Actions
-ignorePaging=Include all pages?
+success=Successfully imported: {0}
+failure=Failed to import: {0}
+ignore=Ignored during import: {0}
+message=Message
diff --git a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/BaseLogin.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/BaseLogin.java
index 0627544..b177eff 100644
--- a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/BaseLogin.java
+++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/BaseLogin.java
@@ -22,7 +22,6 @@ import com.googlecode.wicket.kendo.ui.widget.notification.Notification;
 import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.select.BootstrapSelect;
 import java.security.AccessControlException;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
 import java.util.stream.Collectors;
@@ -260,7 +259,7 @@ public abstract class BaseLogin extends WebPage {
             });
 
             // set default language selection
-            List<Locale> filtered = Collections.emptyList();
+            List<Locale> filtered = List.of();
 
             String acceptLanguage = ((ServletWebRequest) RequestCycle.get().getRequest()).
                     getHeader(HttpHeaders.ACCEPT_LANGUAGE);
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryDirectoryPanel.java
index cf12f0d..790f0b5 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryDirectoryPanel.java
@@ -19,7 +19,6 @@
 package org.apache.syncope.client.console.audit;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
@@ -54,7 +53,7 @@ public abstract class AuditHistoryDirectoryPanel<T extends EntityTO> extends Dir
 
     private static final long serialVersionUID = -8248734710505211261L;
 
-    private static final List<String> EVENTS = Arrays.asList("create", "update");
+    private static final List<String> EVENTS = List.of("create", "update");
 
     private static final SortParam<String> REST_SORT = new SortParam<>("event_date", false);
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyDirectoryPanel.java
index da28bb2..c662fb7 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyDirectoryPanel.java
@@ -22,7 +22,6 @@ import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
 import java.io.Serializable;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Date;
 import java.util.List;
@@ -176,7 +175,7 @@ public abstract class AnyDirectoryPanel<A extends AnyTO, E extends AbstractAnyRe
 
             PreferenceManager.setList(
                     getRequest(), getResponse(), DisplayAttributesModalPanel.getPrefDetailView(type),
-                    Arrays.asList(getDefaultAttributeSelection()));
+                    List.of(getDefaultAttributeSelection()));
         }
 
         columns.addAll(prefcolumns);
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ConsoleLogPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ConsoleLogPanel.java
index 1d6f557..1c5c703 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ConsoleLogPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ConsoleLogPanel.java
@@ -34,8 +34,6 @@ public class ConsoleLogPanel extends AbstractLogsPanel<LoggerTO> {
 
     private static final long serialVersionUID = -9165749229623482717L;
 
-    private static final ConsoleLoggerController CONSOLE_LOGGER_CONTROLLER = new ConsoleLoggerController();
-
     public ConsoleLogPanel(final String id, final PageReference pageReference) {
         super(id, pageReference, ConsoleLoggerController.getLoggers());
     }
@@ -69,8 +67,8 @@ public class ConsoleLogPanel extends AbstractLogsPanel<LoggerTO> {
         }
 
         public static void setLogLevel(final String name, final LoggerLevel level) {
-            final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
-            final LoggerConfig logConf = SyncopeConstants.ROOT_LOGGER.equals(name)
+            LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
+            LoggerConfig logConf = SyncopeConstants.ROOT_LOGGER.equals(name)
                     ? ctx.getConfiguration().getLoggerConfig(LogManager.ROOT_LOGGER_NAME)
                     : ctx.getConfiguration().getLoggerConfig(name);
             logConf.setLevel(level.getLevel());
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxCharacterFieldPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxCharacterFieldPanel.java
index a880b95..8247a2a 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxCharacterFieldPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxCharacterFieldPanel.java
@@ -19,7 +19,6 @@
 package org.apache.syncope.client.console.wicket.markup.html.form;
 
 import de.agilecoders.wicket.core.markup.html.bootstrap.components.TooltipConfig;
-import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import org.apache.syncope.client.ui.commons.Constants;
@@ -42,7 +41,7 @@ public class AjaxCharacterFieldPanel extends FieldPanel<Character> implements Cl
 
     private Component questionMarkJexlHelp;
 
-    private List<Character> choices = Collections.emptyList();
+    private List<Character> choices = List.of();
 
     public AjaxCharacterFieldPanel(final String id, final String name, final IModel<Character> model) {
         this(id, name, model, true);
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrsWizardStep.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrsWizardStep.java
index 2b131f9..5cd5e69 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrsWizardStep.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrsWizardStep.java
@@ -20,7 +20,6 @@ package org.apache.syncope.client.console.wizards.any;
 
 import java.io.Serializable;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -104,7 +103,7 @@ public abstract class AbstractAttrsWizardStep<S extends SchemaTO> extends Wizard
 
         super();
         this.anyTypeClasses = anyTypeClasses;
-        this.attrs = new ListModel<>(Collections.emptyList());
+        this.attrs = new ListModel<>(List.of());
 
         this.setOutputMarkupId(true);
 
@@ -134,7 +133,7 @@ public abstract class AbstractAttrsWizardStep<S extends SchemaTO> extends Wizard
 
     protected void setSchemas(final List<String> anyTypeClasses, final Map<String, S> scs) {
         List<S> allSchemas = anyTypeClasses.isEmpty()
-                ? Collections.emptyList()
+                ? List.of()
                 : SchemaRestClient.getSchemas(getSchemaType(), null, anyTypeClasses.toArray(new String[] {}));
 
         scs.clear();
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/ProvisioningReport.java b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/to/ProvisioningReport.java
similarity index 90%
rename from common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/ProvisioningReport.java
rename to common/idm/lib/src/main/java/org/apache/syncope/common/lib/to/ProvisioningReport.java
index 216ab56..a23745a 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/ProvisioningReport.java
+++ b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/to/ProvisioningReport.java
@@ -18,14 +18,23 @@
  */
 package org.apache.syncope.common.lib.to;
 
+import java.io.Serializable;
 import java.util.Collection;
+import javax.xml.bind.annotation.XmlEnum;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.common.lib.types.TraceLevel;
 
-public class ProvisioningReport {
+@XmlRootElement(name = "provisioningReport")
+@XmlType
+public class ProvisioningReport implements Serializable {
 
+    private static final long serialVersionUID = 9201119472070963385L;
+
+    @XmlEnum
     public enum Status {
 
         SUCCESS,
@@ -135,9 +144,7 @@ public class ProvisioningReport {
      */
     public static String generate(final Collection<ProvisioningReport> results, final TraceLevel level) {
         StringBuilder sb = new StringBuilder();
-        results.forEach(result -> {
-            sb.append(result.getReportString(level)).append('\n');
-        });
+        results.forEach(result -> sb.append(result.getReportString(level)).append('\n'));
         return sb.toString();
     }
 
diff --git a/common/idm/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AbstractCSVSpec.java b/common/idm/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AbstractCSVSpec.java
index 81ba8f3..192ce44 100644
--- a/common/idm/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AbstractCSVSpec.java
+++ b/common/idm/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AbstractCSVSpec.java
@@ -18,6 +18,8 @@
  */
 package org.apache.syncope.common.rest.api.beans;
 
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Schema;
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
@@ -25,11 +27,26 @@ import javax.validation.constraints.NotNull;
 import javax.ws.rs.QueryParam;
 import org.apache.syncope.common.lib.types.MatchingRule;
 import org.apache.syncope.common.lib.types.UnmatchingRule;
+import org.apache.syncope.common.rest.api.service.JAXRSService;
 
 public abstract class AbstractCSVSpec implements Serializable {
 
     private static final long serialVersionUID = 2253975790270165334L;
 
+    private static final String PARAM_COLUMNSEPARATOR = "columnSeparator";
+
+    private static final String PARAM_ARRAYELEMENTSEPARATOR = "arrayElementSeparator";
+
+    private static final String PARAM_QUOTECHAR = "quoteChar";
+
+    private static final String PARAM_ESCAPECHAR = "escapeChar";
+
+    private static final String PARAM_LINESEPARATOR = "lineSeparator";
+
+    private static final String PARAM_NULLVALUE = "nullValue";
+
+    private static final String PARAM_ALLOWCOMMENTS = "allowComments";
+
     protected abstract static class Builder<T extends AbstractCSVSpec, B extends Builder<T, B>> {
 
         protected T instance;
@@ -98,8 +115,8 @@ public abstract class AbstractCSVSpec implements Serializable {
         }
 
         @SuppressWarnings("unchecked")
-        public B action(final String action) {
-            getInstance().getActions().add(action);
+        public B provisioningAction(final String provisioningActions) {
+            getInstance().getProvisioningActions().add(provisioningActions);
             return (B) this;
         }
 
@@ -128,77 +145,98 @@ public abstract class AbstractCSVSpec implements Serializable {
 
     protected MatchingRule matchingRule = MatchingRule.UPDATE;
 
-    protected List<String> actions = new ArrayList<>();
+    protected List<String> provisioningActions = new ArrayList<>();
 
+    @Parameter(name = JAXRSService.PARAM_ANYTYPEKEY, description = "any object type", schema =
+            @Schema(implementation = String.class))
     public String getAnyTypeKey() {
         return anyTypeKey;
     }
 
     @NotNull
-    @QueryParam("anyTypeKey")
+    @QueryParam(JAXRSService.PARAM_ANYTYPEKEY)
     public void setAnyTypeKey(final String anyTypeKey) {
         this.anyTypeKey = anyTypeKey;
     }
 
+    @Parameter(name = PARAM_COLUMNSEPARATOR, description = "separator for column values", schema =
+            @Schema(implementation = char.class, defaultValue = ","))
     public char getColumnSeparator() {
         return columnSeparator;
     }
 
-    @QueryParam("columnSeparator")
+    @QueryParam(PARAM_COLUMNSEPARATOR)
     public void setColumnSeparator(final char columnSeparator) {
         this.columnSeparator = columnSeparator;
     }
 
+    @Parameter(name = PARAM_ARRAYELEMENTSEPARATOR, description = "separator for array elements within a "
+            + "column", schema =
+            @Schema(implementation = String.class, defaultValue = ";"))
     public String getArrayElementSeparator() {
         return arrayElementSeparator;
     }
 
-    @QueryParam("arrayElementSeparator")
+    @QueryParam(PARAM_ARRAYELEMENTSEPARATOR)
     public void setArrayElementSeparator(final String arrayElementSeparator) {
         this.arrayElementSeparator = arrayElementSeparator;
     }
 
+    @Parameter(name = PARAM_QUOTECHAR, description = "character used for quoting values "
+            + "that contain quote characters or linefeeds", schema =
+            @Schema(implementation = char.class, defaultValue = "\""))
     public char getQuoteChar() {
         return quoteChar;
     }
 
-    @QueryParam("quoteChar")
+    @QueryParam(PARAM_QUOTECHAR)
     public void setQuoteChar(final char quoteChar) {
         this.quoteChar = quoteChar;
     }
 
+    @Parameter(name = PARAM_ESCAPECHAR, description = "if any, used to escape values; "
+            + "most commonly defined as backslash", schema =
+            @Schema(implementation = Character.class))
     public Character getEscapeChar() {
         return escapeChar;
     }
 
-    @QueryParam("escapeChar")
+    @QueryParam(PARAM_ESCAPECHAR)
     public void setEscapeChar(final Character escapeChar) {
         this.escapeChar = escapeChar;
     }
 
+    @Parameter(name = PARAM_LINESEPARATOR, description = "character used to separate data rows", schema =
+            @Schema(implementation = String.class, defaultValue = "\\\n"))
     public String getLineSeparator() {
         return lineSeparator;
     }
 
-    @QueryParam("lineSeparator")
+    @QueryParam(PARAM_LINESEPARATOR)
     public void setLineSeparator(final String lineSeparator) {
         this.lineSeparator = lineSeparator;
     }
 
+    @Parameter(name = PARAM_NULLVALUE, description = "when asked to write null, this string value will be used "
+            + "instead", schema =
+            @Schema(implementation = String.class, defaultValue = ""))
     public String getNullValue() {
         return nullValue;
     }
 
-    @QueryParam("nullValue")
+    @QueryParam(PARAM_NULLVALUE)
     public void setNullValue(final String nullValue) {
         this.nullValue = nullValue;
     }
 
+    @Parameter(name = PARAM_ALLOWCOMMENTS, description = "are hash comments, e.g. lines where the first non-whitespace "
+            + "character is '#' allowed? if so, they will be skipped without processing", schema =
+            @Schema(implementation = boolean.class, defaultValue = "false"))
     public boolean isAllowComments() {
         return allowComments;
     }
 
-    @QueryParam("allowComments")
+    @QueryParam(PARAM_ALLOWCOMMENTS)
     public void setAllowComments(final boolean allowComments) {
         this.allowComments = allowComments;
     }
@@ -221,12 +259,12 @@ public abstract class AbstractCSVSpec implements Serializable {
         this.matchingRule = matchingRule;
     }
 
-    public List<String> getActions() {
-        return actions;
+    public List<String> getProvisioningActions() {
+        return provisioningActions;
     }
 
-    @QueryParam("actions")
-    public void setActions(final List<String> actions) {
-        this.actions = actions;
+    @QueryParam("provisioningActions")
+    public void setProvisioningActions(final List<String> provisioningActions) {
+        this.provisioningActions = provisioningActions;
     }
 }
diff --git a/common/idm/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/CSVPushSpec.java b/common/idm/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/CSVPushSpec.java
index 7663010..0a68810 100644
--- a/common/idm/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/CSVPushSpec.java
+++ b/common/idm/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/CSVPushSpec.java
@@ -82,6 +82,11 @@ public class CSVPushSpec extends AbstractCSVSpec {
             getInstance().setIgnorePaging(ignorePagination);
             return this;
         }
+
+        public Builder propagationAction(final String propagationAction) {
+            getInstance().getPropagationActions().add(propagationAction);
+            return this;
+        }
     }
 
     private List<String> fields = new ArrayList<>();
@@ -94,6 +99,8 @@ public class CSVPushSpec extends AbstractCSVSpec {
 
     private boolean ignorePaging;
 
+    protected List<String> propagationActions = new ArrayList<>();
+
     public List<String> getFields() {
         return fields;
     }
@@ -138,4 +145,13 @@ public class CSVPushSpec extends AbstractCSVSpec {
     public void setIgnorePaging(final boolean ignorePaging) {
         this.ignorePaging = ignorePaging;
     }
+
+    public List<String> getPropagationActions() {
+        return propagationActions;
+    }
+
+    @QueryParam("propagationActions")
+    public void setPropagationActions(final List<String> propagationActions) {
+        this.propagationActions = propagationActions;
+    }
 }
diff --git a/common/idm/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ReconQuery.java b/common/idm/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ReconQuery.java
index ddd6799..c4b91d5 100644
--- a/common/idm/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ReconQuery.java
+++ b/common/idm/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ReconQuery.java
@@ -20,6 +20,7 @@ package org.apache.syncope.common.rest.api.beans;
 
 import javax.validation.constraints.NotNull;
 import javax.ws.rs.QueryParam;
+import org.apache.syncope.common.rest.api.service.JAXRSService;
 
 public class ReconQuery {
 
@@ -61,7 +62,7 @@ public class ReconQuery {
     }
 
     @NotNull
-    @QueryParam("anyTypeKey")
+    @QueryParam(JAXRSService.PARAM_ANYTYPEKEY)
     public void setAnyTypeKey(final String anyTypeKey) {
         this.anyTypeKey = anyTypeKey;
     }
diff --git a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ExecQuery.java b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ExecQuery.java
index 3b735a5..f4de253 100644
--- a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ExecQuery.java
+++ b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ExecQuery.java
@@ -49,5 +49,4 @@ public class ExecQuery extends AbstractQuery {
     public void setKey(final String key) {
         this.key = key;
     }
-
 }
diff --git a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ExecuteQuery.java b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ExecuteQuery.java
index 1947206..2a3ca7f 100644
--- a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ExecuteQuery.java
+++ b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/ExecuteQuery.java
@@ -96,5 +96,4 @@ public class ExecuteQuery implements Serializable {
     public void setDryRun(final Boolean dryRun) {
         this.dryRun = dryRun;
     }
-
 }
diff --git a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/JAXRSService.java b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/JAXRSService.java
index 455e208..abcc0e2 100644
--- a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/JAXRSService.java
+++ b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/JAXRSService.java
@@ -46,4 +46,6 @@ public interface JAXRSService {
 
     String PARAM_MAX = "max";
 
+    String PARAM_ANYTYPEKEY = "anyTypeKey";
+
 }
diff --git a/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java b/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
index 388f82b..ccb4380 100644
--- a/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
+++ b/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
@@ -18,16 +18,12 @@
  */
 package org.apache.syncope.core.logic;
 
-import com.fasterxml.jackson.databind.MappingIterator;
-import com.fasterxml.jackson.databind.SequenceWriter;
-import com.fasterxml.jackson.dataformat.csv.CsvMapper;
 import com.fasterxml.jackson.dataformat.csv.CsvSchema;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import org.apache.commons.lang3.StringUtils;
@@ -73,7 +69,7 @@ import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
-import org.apache.syncope.core.provisioning.api.pushpull.stream.StreamConnector;
+import org.apache.syncope.core.provisioning.java.pushpull.stream.CSVStreamConnector;
 import org.apache.syncope.core.provisioning.api.pushpull.SyncopeSinglePullExecutor;
 import org.apache.syncope.core.provisioning.api.pushpull.SyncopeSinglePushExecutor;
 import org.apache.syncope.core.provisioning.api.pushpull.stream.SyncopeStreamPullExecutor;
@@ -378,19 +374,18 @@ public class ReconciliationLogic extends AbstractTransactionalLogic<EntityTO> {
         }
     }
 
-    private CsvSchema csvSchema(final AbstractCSVSpec spec, final CsvSchema base) {
-        CsvSchema schema = base.
-                withColumnSeparator(spec.getColumnSeparator()).
-                withArrayElementSeparator(spec.getArrayElementSeparator()).
-                withQuoteChar(spec.getQuoteChar()).
-                withLineSeparator(spec.getLineSeparator()).
-                withNullValue(spec.getNullValue()).
-                withAllowComments(spec.isAllowComments());
+    private CsvSchema.Builder csvSchema(final AbstractCSVSpec spec) {
+        CsvSchema.Builder schemaBuilder = new CsvSchema.Builder().setUseHeader(true).
+                setColumnSeparator(spec.getColumnSeparator()).
+                setArrayElementSeparator(spec.getArrayElementSeparator()).
+                setQuoteChar(spec.getQuoteChar()).
+                setLineSeparator(spec.getLineSeparator()).
+                setNullValue(spec.getNullValue()).
+                setAllowComments(spec.isAllowComments());
         if (spec.getEscapeChar() != null) {
-            schema = schema.withEscapeChar(spec.getEscapeChar());
+            schemaBuilder.setEscapeChar(spec.getEscapeChar());
         }
-
-        return schema;
+        return schemaBuilder;
     }
 
     @PreAuthorize("hasRole('" + IdRepoEntitlement.TASK_EXECUTE + "')")
@@ -473,21 +468,24 @@ public class ReconciliationLogic extends AbstractTransactionalLogic<EntityTO> {
             }
         });
 
-        CsvSchema.Builder schemaBuilder = CsvSchema.builder().setUseHeader(true);
-        columns.forEach(schemaBuilder::addColumn);
-        CsvSchema schema = csvSchema(spec, schemaBuilder.build());
-
         PushTaskTO pushTask = new PushTaskTO();
         pushTask.setMatchingRule(spec.getMatchingRule());
         pushTask.setUnmatchingRule(spec.getUnmatchingRule());
-        pushTask.getActions().addAll(spec.getActions());
+        pushTask.getActions().addAll(spec.getProvisioningActions());
+
+        try (CSVStreamConnector connector = new CSVStreamConnector(
+                null,
+                spec.getArrayElementSeparator(),
+                csvSchema(spec),
+                null,
+                os)) {
 
-        try (SequenceWriter writer = new CsvMapper().writer(schema).forType(Map.class).writeValues(os)) {
             return streamPushExecutor.push(
                     anyType,
                     matching,
                     columns,
-                    new StreamConnector(null, spec.getArrayElementSeparator(), null, writer),
+                    connector,
+                    spec.getPropagationActions(),
                     pushTask,
                     AuthContextUtils.getUsername());
         } catch (Exception e) {
@@ -514,31 +512,26 @@ public class ReconciliationLogic extends AbstractTransactionalLogic<EntityTO> {
         pullTask.setRemediation(spec.isRemediation());
         pullTask.setMatchingRule(spec.getMatchingRule());
         pullTask.setUnmatchingRule(spec.getUnmatchingRule());
-        pullTask.getActions().addAll(spec.getActions());
+        pullTask.getActions().addAll(spec.getProvisioningActions());
 
-        CsvSchema schema = csvSchema(spec, CsvSchema.emptySchema().withHeader());
-        try {
-            MappingIterator<Map<String, String>> reader =
-                    new CsvMapper().readerFor(Map.class).with(schema).readValues(csv);
-
-            List<String> columns = new ArrayList<>();
-            ((CsvSchema) reader.getParserSchema()).forEach(column -> {
-                if (!spec.getIgnoreColumns().contains(column.getName())) {
-                    columns.add(column.getName());
-                }
-            });
+        try (CSVStreamConnector connector = new CSVStreamConnector(
+                spec.getKeyColumn(),
+                spec.getArrayElementSeparator(),
+                csvSchema(spec),
+                csv,
+                null)) {
 
+            List<String> columns = connector.getColumns(spec);
             if (!columns.contains(spec.getKeyColumn())) {
                 throw new NotFoundException("Key column '" + spec.getKeyColumn() + "'");
             }
 
-            return streamPullExecutor.pull(
-                    anyType,
+            return streamPullExecutor.pull(anyType,
                     spec.getKeyColumn(),
                     columns,
                     spec.getConflictResolutionAction(),
                     spec.getPullCorrelationRule(),
-                    new StreamConnector(spec.getKeyColumn(), spec.getArrayElementSeparator(), reader, null),
+                    connector,
                     pullTask);
         } catch (NotFoundException e) {
             throw e;
diff --git a/core/idm/logic/src/test/java/org/apache/syncope/core/logic/ReconciliationLogicTest.java b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/ReconciliationLogicTest.java
index 72d1a93..73064c6 100644
--- a/core/idm/logic/src/test/java/org/apache/syncope/core/logic/ReconciliationLogicTest.java
+++ b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/ReconciliationLogicTest.java
@@ -110,16 +110,8 @@ public class ReconciliationLogicTest extends AbstractTest {
         });
         assertEquals(search.getLeft(), results.size());
 
-        CsvSchema.Builder builder = CsvSchema.builder().setUseHeader(true);
-        builder.addColumn("username");
-        builder.addColumn("status");
-        builder.addColumn("firstname");
-        builder.addColumn("surname");
-        builder.addColumn("email");
-        builder.addColumn("loginDate");
-        CsvSchema schema = builder.build();
-
-        MappingIterator<Map<String, String>> reader = new CsvMapper().readerFor(Map.class).with(schema).readValues(in);
+        MappingIterator<Map<String, String>> reader =
+                new CsvMapper().readerFor(Map.class).with(CsvSchema.emptySchema().withHeader()).readValues(in);
 
         for (int i = 0; i < results.size() && reader.hasNext(); i++) {
             Map<String, String> row = reader.next();
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/jexl/SyncopeJexlFunctions.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/jexl/SyncopeJexlFunctions.java
index 9cc011f..26d695a 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/jexl/SyncopeJexlFunctions.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/jexl/SyncopeJexlFunctions.java
@@ -65,5 +65,4 @@ public class SyncopeJexlFunctions {
         Collections.reverse(headless);
         return prefix + attr + '=' + StringUtils.join(headless, ',' + attr + '=');
     }
-
 }
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/stream/SyncopeStreamPullExecutor.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/stream/SyncopeStreamPullExecutor.java
index 8d5c241..5215e63 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/stream/SyncopeStreamPullExecutor.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/stream/SyncopeStreamPullExecutor.java
@@ -23,6 +23,7 @@ import java.util.List;
 import org.apache.syncope.common.lib.to.PullTaskTO;
 import org.apache.syncope.common.lib.types.ConflictResolutionAction;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
+import org.apache.syncope.core.provisioning.api.Connector;
 import org.quartz.JobExecutionException;
 
 public interface SyncopeStreamPullExecutor {
@@ -33,7 +34,7 @@ public interface SyncopeStreamPullExecutor {
             List<String> columns,
             ConflictResolutionAction conflictResolutionAction,
             String pullCorrelationRule,
-            StreamConnector connector,
+            Connector connector,
             PullTaskTO pullTaskTO)
             throws JobExecutionException;
 }
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/stream/SyncopeStreamPushExecutor.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/stream/SyncopeStreamPushExecutor.java
index b70a2c4..c2b1e44 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/stream/SyncopeStreamPushExecutor.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/stream/SyncopeStreamPushExecutor.java
@@ -23,6 +23,7 @@ import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.common.lib.to.PushTaskTO;
 import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
+import org.apache.syncope.core.provisioning.api.Connector;
 import org.quartz.JobExecutionException;
 
 public interface SyncopeStreamPushExecutor {
@@ -31,7 +32,8 @@ public interface SyncopeStreamPushExecutor {
             AnyType anyType,
             List<? extends Any<?>> anys,
             List<String> columns,
-            StreamConnector connector,
+            Connector connector,
+            List<String> propagationActions,
             PushTaskTO pushTaskTO,
             String executor)
             throws JobExecutionException;
diff --git a/core/provisioning-api/src/test/java/org/apache/syncope/core/provisioning/api/IntAttrNameParserTest.java b/core/provisioning-api/src/test/java/org/apache/syncope/core/provisioning/api/IntAttrNameParserTest.java
index 2a2b178..1e14e94 100644
--- a/core/provisioning-api/src/test/java/org/apache/syncope/core/provisioning/api/IntAttrNameParserTest.java
+++ b/core/provisioning-api/src/test/java/org/apache/syncope/core/provisioning/api/IntAttrNameParserTest.java
@@ -29,7 +29,6 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import java.text.ParseException;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -62,9 +61,9 @@ public class IntAttrNameParserTest {
     private static final Map<AnyTypeKind, List<String>> FIELDS = new HashMap<>();
 
     static {
-        FIELDS.put(AnyTypeKind.USER, Arrays.asList("key", "username"));
-        FIELDS.put(AnyTypeKind.GROUP, Arrays.asList("key", "name", "userOwner"));
-        FIELDS.put(AnyTypeKind.ANY_OBJECT, Arrays.asList("key", "name"));
+        FIELDS.put(AnyTypeKind.USER, List.of("key", "username"));
+        FIELDS.put(AnyTypeKind.GROUP, List.of("key", "name", "userOwner"));
+        FIELDS.put(AnyTypeKind.ANY_OBJECT, List.of("key", "name"));
     }
 
     @Mock
diff --git a/core/provisioning-java/pom.xml b/core/provisioning-java/pom.xml
index 12fe7f5..a2c7067 100644
--- a/core/provisioning-java/pom.xml
+++ b/core/provisioning-java/pom.xml
@@ -59,6 +59,11 @@ under the License.
     </dependency>
     
     <dependency>
+      <groupId>com.fasterxml.jackson.dataformat</groupId>
+      <artifactId>jackson-dataformat-csv</artifactId>
+    </dependency>
+
+    <dependency>
       <groupId>org.codehaus.groovy</groupId>
       <artifactId>groovy</artifactId>
     </dependency>
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/OutboundMatcher.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/OutboundMatcher.java
index 41637c6..e7a96d9 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/OutboundMatcher.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/OutboundMatcher.java
@@ -19,7 +19,6 @@
 package org.apache.syncope.core.provisioning.java.pushpull;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
@@ -160,7 +159,7 @@ public class OutboundMatcher {
                         rule.get().getFilter(any, provision),
                         provision,
                         ArrayUtils.isEmpty(linkingItems)
-                        ? Optional.empty() : Optional.of(Arrays.asList(linkingItems)),
+                        ? Optional.empty() : Optional.of(List.of(linkingItems)),
                         Optional.empty()));
             } else {
                 Optional<? extends MappingItem> connObjectKeyItem = MappingUtils.getConnObjectKeyItem(provision);
@@ -173,7 +172,7 @@ public class OutboundMatcher {
                             connObjectKeyValue.get(),
                             provision,
                             ArrayUtils.isEmpty(linkingItems)
-                            ? Optional.empty() : Optional.of(Arrays.asList(linkingItems)),
+                            ? Optional.empty() : Optional.of(List.of(linkingItems)),
                             Optional.empty()).
                             ifPresent(result::add);
                 }
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/stream/StreamConnector.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/CSVStreamConnector.java
similarity index 60%
rename from core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/stream/StreamConnector.java
rename to core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/CSVStreamConnector.java
index 5ff51c8..6b025ae 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/stream/StreamConnector.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/CSVStreamConnector.java
@@ -16,19 +16,27 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.provisioning.api.pushpull.stream;
+package org.apache.syncope.core.provisioning.java.pushpull.stream;
 
 import com.fasterxml.jackson.databind.MappingIterator;
 import com.fasterxml.jackson.databind.SequenceWriter;
+import com.fasterxml.jackson.dataformat.csv.CsvMapper;
+import com.fasterxml.jackson.dataformat.csv.CsvSchema;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.rest.api.beans.CSVPullSpec;
 import org.apache.syncope.core.persistence.api.entity.ConnInstance;
 import org.apache.syncope.core.provisioning.api.Connector;
+import org.identityconnectors.framework.common.exceptions.ConnectorException;
 import org.identityconnectors.framework.common.objects.Attribute;
 import org.identityconnectors.framework.common.objects.AttributeBuilder;
 import org.identityconnectors.framework.common.objects.AttributeUtil;
@@ -43,28 +51,78 @@ import org.identityconnectors.framework.common.objects.SyncToken;
 import org.identityconnectors.framework.common.objects.Uid;
 import org.identityconnectors.framework.common.objects.filter.Filter;
 import org.identityconnectors.framework.spi.SearchResultsHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.util.CollectionUtils;
 
-public class StreamConnector implements Connector {
+public class CSVStreamConnector implements Connector, AutoCloseable {
+
+    private static final Logger LOG = LoggerFactory.getLogger(CSVStreamConnector.class);
 
     private final String keyColumn;
 
     private final String arrayElementsSeparator;
 
-    private final MappingIterator<Map<String, String>> reader;
+    private final CsvSchema.Builder schemaBuilder;
+
+    private final InputStream in;
+
+    private final OutputStream out;
+
+    private MappingIterator<Map<String, String>> reader;
 
-    private final SequenceWriter writer;
+    private SequenceWriter writer;
 
-    public StreamConnector(
+    public CSVStreamConnector(
             final String keyColumn,
             final String arrayElementsSeparator,
-            final MappingIterator<Map<String, String>> reader,
-            final SequenceWriter writer) {
+            final CsvSchema.Builder schemaBuilder,
+            final InputStream in,
+            final OutputStream out) {
 
         this.keyColumn = keyColumn;
         this.arrayElementsSeparator = arrayElementsSeparator;
-        this.reader = reader;
-        this.writer = writer;
+        this.schemaBuilder = schemaBuilder;
+        this.in = in;
+        this.out = out;
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (reader != null) {
+            reader.close();
+        }
+        if (writer != null) {
+            writer.close();
+        }
+    }
+
+    public MappingIterator<Map<String, String>> reader() throws IOException {
+        synchronized (this) {
+            if (reader == null) {
+                reader = new CsvMapper().readerFor(Map.class).with(schemaBuilder.build()).readValues(in);
+            }
+        }
+        return reader;
+    }
+
+    public List<String> getColumns(final CSVPullSpec spec) throws IOException {
+        List<String> columns = new ArrayList<>();
+        ((CsvSchema) reader().getParserSchema()).forEach(column -> {
+            if (!spec.getIgnoreColumns().contains(column.getName())) {
+                columns.add(column.getName());
+            }
+        });
+        return columns;
+    }
+
+    public SequenceWriter writer() throws IOException {
+        synchronized (this) {
+            if (writer == null) {
+                writer = new CsvMapper().writerFor(Map.class).with(schemaBuilder.build()).writeValues(out);
+            }
+        }
+        return writer;
     }
 
     @Override
@@ -84,29 +142,34 @@ public class StreamConnector implements Connector {
             final OperationOptions options,
             final AtomicReference<Boolean> propagationAttempted) {
 
-        if (writer != null) {
-            Map<String, String> row = new HashMap<>();
-            attrs.stream().filter(attr -> !AttributeUtil.isSpecial(attr)).forEach(attr -> {
-                if (CollectionUtils.isEmpty(attr.getValue()) || attr.getValue().get(0) == null) {
-                    row.put(attr.getName(), null);
-                } else if (attr.getValue().size() == 1) {
-                    row.put(attr.getName(), attr.getValue().get(0).toString());
-                } else if (arrayElementsSeparator == null) {
-                    row.put(attr.getName(), attr.getValue().toString());
-                } else {
-                    row.put(
-                            attr.getName(),
-                            attr.getValue().stream().map(Object::toString).
-                                    collect(Collectors.joining(arrayElementsSeparator)));
-                }
-            });
-            try {
-                writer.write(row);
-            } catch (IOException e) {
-                throw new IllegalStateException("Could not object " + row, e);
+        synchronized (schemaBuilder) {
+            if (schemaBuilder.size() == 0) {
+                attrs.stream().filter(attr -> !AttributeUtil.isSpecial(attr)).map(Attribute::getName).
+                        forEachOrdered(schemaBuilder::addColumn);
+            }
+        }
+
+        Map<String, String> row = new HashMap<>();
+        attrs.stream().filter(attr -> !AttributeUtil.isSpecial(attr)).forEach(attr -> {
+            if (CollectionUtils.isEmpty(attr.getValue()) || attr.getValue().get(0) == null) {
+                row.put(attr.getName(), null);
+            } else if (attr.getValue().size() == 1) {
+                row.put(attr.getName(), attr.getValue().get(0).toString());
+            } else if (arrayElementsSeparator == null) {
+                row.put(attr.getName(), attr.getValue().toString());
+            } else {
+                row.put(
+                        attr.getName(),
+                        attr.getValue().stream().map(Object::toString).
+                                collect(Collectors.joining(arrayElementsSeparator)));
             }
-            propagationAttempted.set(Boolean.TRUE);
+        });
+        try {
+            writer().write(row);
+        } catch (IOException e) {
+            throw new ConnectorException("Could not write object " + row, e);
         }
+        propagationAttempted.set(Boolean.TRUE);
         return null;
     }
 
@@ -165,9 +228,9 @@ public class StreamConnector implements Connector {
 
         SearchResult result = new SearchResult();
 
-        if (reader != null) {
-            while (reader.hasNext()) {
-                Map<String, String> row = reader.next();
+        try {
+            while (reader().hasNext()) {
+                Map<String, String> row = reader().next();
 
                 ConnectorObjectBuilder builder = new ConnectorObjectBuilder();
                 builder.setObjectClass(objectClass);
@@ -181,6 +244,9 @@ public class StreamConnector implements Connector {
 
                 handler.handle(builder.build());
             }
+        } catch (IOException e) {
+            LOG.error("Could not read CSV from provided stream", e);
+            throw new ConnectorException(e);
         }
 
         return result;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java
index ff6553e..2ce44a0 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java
@@ -47,8 +47,8 @@ import org.apache.syncope.core.provisioning.api.pushpull.GroupPullResultHandler;
 import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningProfile;
 import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.common.lib.types.IdMImplementationType;
+import org.apache.syncope.core.provisioning.api.Connector;
 import org.apache.syncope.core.provisioning.api.pushpull.PullActions;
-import org.apache.syncope.core.provisioning.api.pushpull.stream.StreamConnector;
 import org.apache.syncope.core.provisioning.api.pushpull.SyncopePullResultHandler;
 import org.apache.syncope.core.spring.ImplementationManager;
 import org.quartz.JobExecutionException;
@@ -168,7 +168,7 @@ public class StreamPullJobDelegate extends PullJobDelegate implements SyncopeStr
             final List<String> columns,
             final ConflictResolutionAction conflictResolutionAction,
             final String pullCorrelationRule,
-            final StreamConnector connector,
+            final Connector connector,
             final PullTaskTO pullTaskTO) throws JobExecutionException {
 
         LOG.debug("Executing stream pull");
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegate.java
index 1b775d4..cf18b9f 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegate.java
@@ -34,11 +34,11 @@ import org.apache.syncope.core.persistence.api.entity.resource.Mapping;
 import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.apache.syncope.core.persistence.api.entity.task.PushTask;
+import org.apache.syncope.core.provisioning.api.Connector;
 import org.apache.syncope.core.provisioning.api.pushpull.AnyObjectPushResultHandler;
 import org.apache.syncope.core.provisioning.api.pushpull.GroupPushResultHandler;
 import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningProfile;
 import org.apache.syncope.core.provisioning.api.pushpull.PushActions;
-import org.apache.syncope.core.provisioning.api.pushpull.stream.StreamConnector;
 import org.apache.syncope.core.provisioning.api.pushpull.SyncopePushResultHandler;
 import org.apache.syncope.core.provisioning.api.pushpull.stream.SyncopeStreamPushExecutor;
 import org.apache.syncope.core.provisioning.api.pushpull.UserPushResultHandler;
@@ -88,7 +88,9 @@ public class StreamPushJobDelegate extends PushJobDelegate implements SyncopeStr
     }
 
     private ExternalResource externalResource(
-            final AnyType anyType, final List<String> columns) throws JobExecutionException {
+            final AnyType anyType,
+            final List<String> columns,
+            final List<String> propagationActions) throws JobExecutionException {
 
         Provision provision = entityFactory.newEntity(Provision.class);
         provision.setAnyType(anyType);
@@ -118,6 +120,15 @@ public class StreamPushJobDelegate extends PushJobDelegate implements SyncopeStr
         resource.add(provision);
         provision.setResource(resource);
 
+        propagationActions.forEach(key -> {
+            Implementation impl = implementationDAO.find(key);
+            if (impl == null || !IdMImplementationType.PROPAGATION_ACTIONS.equals(impl.getType())) {
+                LOG.debug("Invalid " + Implementation.class.getSimpleName() + " {}, ignoring...", key);
+            } else {
+                resource.add(impl);
+            }
+        });
+
         return resource;
     }
 
@@ -126,21 +137,22 @@ public class StreamPushJobDelegate extends PushJobDelegate implements SyncopeStr
             final AnyType anyType,
             final List<? extends Any<?>> anys,
             final List<String> columns,
-            final StreamConnector connector,
+            final Connector connector,
+            final List<String> propagationActions,
             final PushTaskTO pushTaskTO,
             final String executor) throws JobExecutionException {
 
         LOG.debug("Executing stream push as {}", executor);
         this.executor = executor;
 
-        List<PushActions> actions = new ArrayList<>();
+        List<PushActions> pushActions = new ArrayList<>();
         pushTaskTO.getActions().forEach(key -> {
             Implementation impl = implementationDAO.find(key);
             if (impl == null || !IdMImplementationType.PUSH_ACTIONS.equals(impl.getType())) {
                 LOG.debug("Invalid " + Implementation.class.getSimpleName() + " {}, ignoring...", key);
             } else {
                 try {
-                    actions.add(ImplementationManager.build(impl));
+                    pushActions.add(ImplementationManager.build(impl));
                 } catch (Exception e) {
                     LOG.warn("While building {}", impl, e);
                 }
@@ -148,7 +160,7 @@ public class StreamPushJobDelegate extends PushJobDelegate implements SyncopeStr
         });
 
         try {
-            ExternalResource resource = externalResource(anyType, columns);
+            ExternalResource resource = externalResource(anyType, columns, propagationActions);
             Provision provision = resource.getProvisions().get(0);
 
             PushTask pushTask = entityFactory.newEntity(PushTask.class);
@@ -161,10 +173,10 @@ public class StreamPushJobDelegate extends PushJobDelegate implements SyncopeStr
             pushTask.setSyncStatus(false);
 
             profile = new ProvisioningProfile<>(connector, pushTask);
-            profile.getActions().addAll(actions);
+            profile.getActions().addAll(pushActions);
             profile.setConflictResolutionAction(ConflictResolutionAction.FIRSTMATCH);
 
-            for (PushActions action : actions) {
+            for (PushActions action : pushActions) {
                 action.beforeAll(profile);
             }
 
@@ -186,7 +198,7 @@ public class StreamPushJobDelegate extends PushJobDelegate implements SyncopeStr
 
             doHandle(anys, handler, provision.getResource());
 
-            for (PushActions action : actions) {
+            for (PushActions action : pushActions) {
                 action.afterAll(profile);
             }
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java
index 1f52585..a08c998 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java
@@ -19,7 +19,6 @@
 package org.apache.syncope.core.provisioning.java.utils;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
@@ -117,7 +116,7 @@ public final class MappingUtils {
         attrsToGet.add(Uid.NAME);
         attrsToGet.add(OperationalAttributes.ENABLE_NAME);
         if (!ArrayUtils.isEmpty(moreAttrsToGet)) {
-            attrsToGet.addAll(Arrays.asList(moreAttrsToGet));
+            attrsToGet.addAll(List.of(moreAttrsToGet));
         }
 
         items.filter(item -> item.getPurpose() != MappingPurpose.NONE).
diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPPasswordPullActionsTest.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPPasswordPullActionsTest.java
index de4a6bb..1b833eb 100644
--- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPPasswordPullActionsTest.java
+++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPPasswordPullActionsTest.java
@@ -25,9 +25,12 @@ import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.verify;
 
-import org.apache.syncope.common.lib.patch.AnyPatch;
-import org.apache.syncope.common.lib.patch.PasswordPatch;
-import org.apache.syncope.common.lib.patch.UserPatch;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.request.AnyCR;
+import org.apache.syncope.common.lib.request.AnyUR;
+import org.apache.syncope.common.lib.request.PasswordPatch;
+import org.apache.syncope.common.lib.request.UserCR;
+import org.apache.syncope.common.lib.request.UserUR;
 import org.apache.syncope.common.lib.to.EntityTO;
 import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.common.lib.to.UserTO;
@@ -53,9 +56,6 @@ public class LDAPPasswordPullActionsTest extends AbstractTest {
     private ProvisioningProfile<?, ?> profile;
 
     @Mock
-    private AnyPatch anyPatch;
-
-    @Mock
     private UserDAO userDAO;
 
     @Mock
@@ -64,6 +64,10 @@ public class LDAPPasswordPullActionsTest extends AbstractTest {
     @InjectMocks
     private LDAPPasswordPullActions ldapPasswordPullActions;
 
+    private AnyCR anyCR;
+
+    private AnyUR anyUR;
+
     private EntityTO entity;
 
     private String encodedPassword;
@@ -84,9 +88,10 @@ public class LDAPPasswordPullActionsTest extends AbstractTest {
     public void beforeProvision() throws JobExecutionException {
         String digest = "SHA256";
         String password = "t3stPassw0rd";
-        ReflectionTestUtils.setField(entity, "password", String.format("{%s}%s", digest, password));
+        anyCR = new UserCR.Builder(SyncopeConstants.ROOT_REALM, "username").
+                password(String.format("{%s}%s", digest, password)).build();
 
-        ldapPasswordPullActions.beforeProvision(profile, syncDelta, entity);
+        ldapPasswordPullActions.beforeProvision(profile, syncDelta, anyCR);
 
         assertEquals(CipherAlgorithm.valueOf(digest), ReflectionTestUtils.getField(ldapPasswordPullActions, "cipher"));
         assertEquals(password, ReflectionTestUtils.getField(ldapPasswordPullActions, "encodedPassword"));
@@ -94,14 +99,11 @@ public class LDAPPasswordPullActionsTest extends AbstractTest {
 
     @Test
     public void beforeUpdate() throws JobExecutionException {
-        anyPatch = new UserPatch();
-        PasswordPatch passwordPatch = new PasswordPatch();
-        String digest = "MD5";
-        String password = "an0therTestP4ss";
-        ReflectionTestUtils.setField(passwordPatch, "value", String.format("{%s}%s", digest, password));
-        ReflectionTestUtils.setField(anyPatch, "password", passwordPatch);
+        anyUR = new UserUR.Builder(null).
+                password(new PasswordPatch.Builder().value("{MD5}an0therTestP4ss").build()).
+                build();
 
-        ldapPasswordPullActions.beforeUpdate(profile, syncDelta, entity, anyPatch);
+        ldapPasswordPullActions.beforeUpdate(profile, syncDelta, entity, anyUR);
 
         assertNull(ReflectionTestUtils.getField(ldapPasswordPullActions, "encodedPassword"));
     }
@@ -126,5 +128,4 @@ public class LDAPPasswordPullActionsTest extends AbstractTest {
         assertNull(ReflectionTestUtils.getField(ldapPasswordPullActions, "encodedPassword"));
         assertNull(ReflectionTestUtils.getField(ldapPasswordPullActions, "cipher"));
     }
-
 }
diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegateTest.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegateTest.java
index dfa523c..bb57c94 100644
--- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegateTest.java
+++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegateTest.java
@@ -20,15 +20,11 @@ package org.apache.syncope.core.provisioning.java.pushpull.stream;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
 
-import com.fasterxml.jackson.databind.MappingIterator;
+import com.fasterxml.jackson.dataformat.csv.CsvSchema;
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
-import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.stream.Collectors;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.PullTaskTO;
@@ -41,7 +37,7 @@ import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.common.lib.to.ProvisioningReport;
-import org.apache.syncope.core.provisioning.api.pushpull.stream.StreamConnector;
+import org.apache.syncope.common.rest.api.beans.CSVPullSpec;
 import org.apache.syncope.core.provisioning.api.pushpull.stream.SyncopeStreamPullExecutor;
 import org.apache.syncope.core.provisioning.java.AbstractTest;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
@@ -64,39 +60,51 @@ public class StreamPullJobDelegateTest extends AbstractTest {
 
     @Test
     public void pull() throws JobExecutionException, IOException {
+        List<String> columns = List.of(
+                "username",
+                "email",
+                "surname",
+                "firstname",
+                "fullname",
+                "userId");
+
+        StringBuilder csv = new StringBuilder();
+        csv.append(columns.stream().collect(Collectors.joining(",")));
+        csv.append('\n');
+        csv.append("donizetti,");
+        csv.append("donizetti@apache.org,");
+        csv.append("Donizetti,");
+        csv.append("Gaetano,");
+        csv.append("Gaetano Donizetti,");
+        csv.append("donizetti@apache.org");
+        csv.append('\n');
+
         PullTaskTO pullTask = new PullTaskTO();
         pullTask.setDestinationRealm(SyncopeConstants.ROOT_REALM);
         pullTask.setRemediation(false);
         pullTask.setMatchingRule(MatchingRule.UPDATE);
         pullTask.setUnmatchingRule(UnmatchingRule.PROVISION);
 
-        Map<String, String> user = new HashMap<>();
-        user.put("username", "donizetti");
-        user.put("email", "donizetti@apache.org");
-        user.put("surname", "Donizetti");
-        user.put("firstname", "Gaetano");
-        user.put("fullname", "Gaetano Donizetti");
-        user.put("userId", "donizetti@apache.org");
-        Iterator<Map<String, String>> backing = List.of(user).iterator();
-
-        @SuppressWarnings("unchecked")
-        MappingIterator<Map<String, String>> itor = mock(MappingIterator.class);
-        when(itor.hasNext()).thenAnswer(invocation -> backing.hasNext());
-        when(itor.next()).thenAnswer(invocation -> backing.next());
+        List<ProvisioningReport> results = AuthContextUtils.callAsAdmin(SyncopeConstants.MASTER_DOMAIN, () -> {
+            try (CSVStreamConnector connector = new CSVStreamConnector(
+                    "username",
+                    ";",
+                    new CsvSchema.Builder().setUseHeader(true),
+                    new ByteArrayInputStream(csv.toString().getBytes()),
+                    null)) {
 
-        List<String> columns = user.keySet().stream().collect(Collectors.toList());
+                List<String> csvColumns = connector.getColumns(new CSVPullSpec());
+                assertEquals(columns, csvColumns);
 
-        List<ProvisioningReport> results = AuthContextUtils.callAsAdmin(SyncopeConstants.MASTER_DOMAIN, () -> {
-            try {
                 return streamPullExecutor.pull(
                         anyTypeDAO.findUser(),
                         "username",
                         columns,
                         ConflictResolutionAction.IGNORE,
                         null,
-                        new StreamConnector("username", null, itor, null),
+                        connector,
                         pullTask);
-            } catch (JobExecutionException e) {
+            } catch (Exception e) {
                 throw new RuntimeException(e);
             }
         });
diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegateTest.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegateTest.java
index 2fcab3c..7742760 100644
--- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegateTest.java
+++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegateTest.java
@@ -20,18 +20,17 @@ package org.apache.syncope.core.provisioning.java.pushpull.stream;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import com.fasterxml.jackson.databind.MappingIterator;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.SequenceWriter;
+import com.fasterxml.jackson.dataformat.csv.CsvMapper;
+import com.fasterxml.jackson.dataformat.csv.CsvSchema;
 import java.io.IOException;
 import java.io.PipedInputStream;
 import java.io.PipedOutputStream;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.common.lib.to.PushTaskTO;
@@ -39,7 +38,6 @@ import org.apache.syncope.common.lib.types.MatchingRule;
 import org.apache.syncope.common.lib.types.UnmatchingRule;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
-import org.apache.syncope.core.provisioning.api.pushpull.stream.StreamConnector;
 import org.apache.syncope.core.provisioning.api.pushpull.stream.SyncopeStreamPushExecutor;
 import org.apache.syncope.core.provisioning.java.AbstractTest;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
@@ -50,8 +48,6 @@ import org.springframework.transaction.annotation.Transactional;
 @Transactional("Master")
 public class StreamPushJobDelegateTest extends AbstractTest {
 
-    private static final ObjectMapper MAPPER = new ObjectMapper();
-
     @Autowired
     private SyncopeStreamPushExecutor streamPushExecutor;
 
@@ -71,14 +67,19 @@ public class StreamPushJobDelegateTest extends AbstractTest {
         pushTask.setUnmatchingRule(UnmatchingRule.PROVISION);
 
         List<ProvisioningReport> results = AuthContextUtils.callAsAdmin(SyncopeConstants.MASTER_DOMAIN, () -> {
-            try (SequenceWriter writer = MAPPER.writer().forType(Map.class).writeValues(os)) {
-                writer.init(true);
+            try (CSVStreamConnector connector = new CSVStreamConnector(
+                    null,
+                    ";",
+                    new CsvSchema.Builder().setUseHeader(true),
+                    null,
+                    os)) {
 
                 return streamPushExecutor.push(
                         anyTypeDAO.findUser(),
                         userDAO.findAll(1, 100),
-                        Arrays.asList("username", "firstname", "surname", "email", "status", "loginDate"),
-                        new StreamConnector(null, null, null, writer),
+                        List.of("username", "firstname", "surname", "email", "status", "loginDate"),
+                        connector,
+                        List.of(),
                         pushTask,
                         "user");
             } catch (Exception e) {
@@ -87,7 +88,8 @@ public class StreamPushJobDelegateTest extends AbstractTest {
         });
         assertEquals(userDAO.count(), results.size());
 
-        MappingIterator<Map<String, String>> reader = MAPPER.readerFor(Map.class).readValues(in);
+        MappingIterator<Map<String, String>> reader =
+                new CsvMapper().readerFor(Map.class).with(CsvSchema.emptySchema().withHeader()).readValues(in);
 
         for (int i = 0; i < results.size() && reader.hasNext(); i++) {
             Map<String, String> row = reader.next();
@@ -97,18 +99,18 @@ public class StreamPushJobDelegateTest extends AbstractTest {
 
             switch (row.get("username")) {
                 case "rossini":
-                    assertNull(row.get("email"));
-                    assertTrue(row.get("loginDate").contains(","));
+                    assertEquals(StringUtils.EMPTY, row.get("email"));
+                    assertTrue(row.get("loginDate").contains(";"));
                     break;
 
                 case "verdi":
                     assertEquals("verdi@syncope.org", row.get("email"));
-                    assertNull(row.get("loginDate"));
+                    assertEquals(StringUtils.EMPTY, row.get("loginDate"));
                     break;
 
                 case "bellini":
-                    assertNull(row.get("email"));
-                    assertFalse(row.get("loginDate").contains(","));
+                    assertEquals(StringUtils.EMPTY, row.get("email"));
+                    assertFalse(row.get("loginDate").contains(";"));
                     break;
 
                 default:
diff --git a/core/spring/pom.xml b/core/spring/pom.xml
index 71a03c9..b301382 100644
--- a/core/spring/pom.xml
+++ b/core/spring/pom.xml
@@ -101,8 +101,13 @@ under the License.
       <version>${project.version}</version>
     </dependency>        
     <dependency>
-      <groupId>org.apache.syncope.common.idrepo</groupId>
-      <artifactId>syncope-common-idrepo-rest-api</artifactId>
+      <groupId>org.apache.syncope.common.idm</groupId>
+      <artifactId>syncope-common-idm-rest-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.common.am</groupId>
+      <artifactId>syncope-common-am-rest-api</artifactId>
       <version>${project.version}</version>
     </dependency>
     <dependency>
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/rest/BpmnProcessRestClient.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/rest/BpmnProcessRestClient.java
index 12eb10c..4924163 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/rest/BpmnProcessRestClient.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/rest/BpmnProcessRestClient.java
@@ -19,7 +19,6 @@
 package org.apache.syncope.client.console.rest;
 
 import java.io.InputStream;
-import java.util.Collections;
 import java.util.List;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
@@ -44,8 +43,8 @@ public class BpmnProcessRestClient extends BaseRestClient {
         BpmnProcessService service = getService(BpmnProcessService.class);
 
         MetadataMap<String, String> headers = new MetadataMap<>();
-        headers.put(HttpHeaders.CONTENT_TYPE, Collections.singletonList(mediaType.toString()));
-        headers.put(HttpHeaders.ACCEPT, Collections.singletonList(mediaType.toString()));
+        headers.put(HttpHeaders.CONTENT_TYPE, List.of(mediaType.toString()));
+        headers.put(HttpHeaders.ACCEPT, List.of(mediaType.toString()));
         WebClient.client(service).headers(headers);
 
         return service;
diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2UserManager.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2UserManager.java
index 1e7c335..c3c0a3e 100644
--- a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2UserManager.java
+++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2UserManager.java
@@ -20,7 +20,6 @@ package org.apache.syncope.core.logic.saml2;
 
 import java.text.ParseException;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
 import java.util.stream.Collectors;
@@ -92,7 +91,7 @@ public class SAML2UserManager {
         SAML2IdP idp = idpDAO.find(idpKey);
         if (idp == null) {
             LOG.warn("Invalid IdP: {}", idpKey);
-            return Collections.emptyList();
+            return List.of();
         }
 
         return inboundMatcher.matchByConnObjectKeyValue(
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/LogsITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/LogsITCase.java
index acdf767..f039075 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/LogsITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/LogsITCase.java
@@ -73,8 +73,7 @@ public class LogsITCase extends AbstractConsoleITCase {
 
     @Test
     public void readConsoleLogs() {
-        TESTER.assertComponent("body:content:tabbedPanel:tabs-container:tabs:1:link",
-                AjaxFallbackLink.class);
+        TESTER.assertComponent("body:content:tabbedPanel:tabs-container:tabs:1:link", AjaxFallbackLink.class);
         TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:1:link");
         TESTER.assertComponent(CONTAINER_PATH, WebMarkupContainer.class);
 
@@ -91,6 +90,8 @@ public class LogsITCase extends AbstractConsoleITCase {
 
         TESTER.getRequest().addParameter(
                 result.getPageRelativePath() + ":fields:1:field:dropDownChoiceField", "6");
+        TESTER.assertComponent(
+                result.getPageRelativePath() + ":fields:1:field:dropDownChoiceField", DropDownChoice.class);
         TESTER.executeAjaxEvent(
                 result.getPageRelativePath() + ":fields:1:field:dropDownChoiceField", Constants.ON_CHANGE);
 
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReconciliationITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReconciliationITCase.java
index 822bb5f..5b2a20c 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReconciliationITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReconciliationITCase.java
@@ -251,17 +251,8 @@ public class ReconciliationITCase extends AbstractITCase {
         PagedResult<UserTO> users = userService.search(anyQuery);
         assertNotNull(users);
 
-        CsvSchema.Builder builder = CsvSchema.builder().setUseHeader(true);
-        builder.addColumn("username");
-        builder.addColumn("status");
-        builder.addColumn("firstname");
-        builder.addColumn("surname");
-        builder.addColumn("email");
-        builder.addColumn("loginDate");
-        CsvSchema schema = builder.build();
-
-        MappingIterator<Map<String, String>> reader = new CsvMapper().readerFor(Map.class).with(schema).
-                readValues((InputStream) response.getEntity());
+        MappingIterator<Map<String, String>> reader = new CsvMapper().readerFor(Map.class).
+                with(CsvSchema.emptySchema().withHeader()).readValues((InputStream) response.getEntity());
 
         int rows = 0;
         for (; reader.hasNext(); rows++) {