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 2019/12/30 14:13:39 UTC

[syncope] branch 2_1_X updated (310ef5b -> b0dca11)

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

ilgrosso pushed a change to branch 2_1_X
in repository https://gitbox.apache.org/repos/asf/syncope.git.


    from 310ef5b  Upgrading CodeMirror
     new 336e4c1  Upgrading PDFBox
     new b0dca11  [SYNCOPE-1531] Core support

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../console/panels/AnyObjectDirectoryPanel.java    |   2 -
 .../client/console/panels/UserDirectoryPanel.java  |   2 -
 .../syncope/common/lib/to/AccessTokenTO.java       |   1 -
 .../syncope/common/lib/to}/ProvisioningReport.java |  83 ++++---
 .../syncope/common/rest/api/RESTHeaders.java       |   7 +-
 .../common/rest/api/beans/AbstractCSVSpec.java     | 229 ++++++++++++++++++
 .../syncope/common/rest/api/beans/CSVPullSpec.java | 136 +++++++++++
 .../syncope/common/rest/api/beans/CSVPushSpec.java | 139 +++++++++++
 .../rest/api/service/ReconciliationService.java    |  33 +++
 core/logic/pom.xml                                 |  67 +++++-
 .../apache/syncope/core/logic/AnyObjectLogic.java  |  13 +-
 .../org/apache/syncope/core/logic/GroupLogic.java  |  21 +-
 .../syncope/core/logic/ReconciliationLogic.java    | 212 ++++++++++++++++-
 .../org/apache/syncope/core/logic/TaskLogic.java   |   4 +-
 .../org/apache/syncope/core/logic/UserLogic.java   |  18 +-
 .../apache/syncope/core/logic}/AbstractTest.java   |   4 +-
 .../core/logic/ReconciliationLogicTest.java        | 154 ++++++++++++
 .../syncope/core/logic}/TestInitializer.java       |   3 +-
 .../src/test/resources/logicTest.xml}              |  14 +-
 core/logic/src/test/resources/test1.csv            |   3 +
 .../jpa/dao/MyJPAJSONPlainSchemaDAO.java           |   2 +-
 .../jpa/dao/PGJPAJSONPlainSchemaDAO.java           |   2 +-
 .../core/persistence/jpa/dao/AbstractAnyDAO.java   |  45 ++--
 .../persistence/jpa/dao/JPAPlainSchemaDAO.java     |  10 +-
 .../jpa/entity/resource/JPAExternalResource.java   |   8 +-
 core/provisioning-api/pom.xml                      |  29 ++-
 .../syncope/core/provisioning/api/Connector.java   |  55 ++++-
 .../core/provisioning/api}/IntAttrNameParser.java  |   3 +-
 .../provisioning/api/UserProvisioningManager.java  |   2 +-
 .../provisioning/api}/jexl/ClassFreeUberspect.java |   2 +-
 .../provisioning/api}/jexl/EmptyClassLoader.java   |   2 +-
 .../core/provisioning/api}/jexl/JexlUtils.java     |  24 +-
 .../api}/jexl/SyncopeJexlFunctions.java            |   2 +-
 .../api/propagation/PropagationManager.java        |  19 ++
 .../api/propagation/PropagationTaskInfo.java       |  65 ++++-
 .../api/pushpull/ProvisioningProfile.java          |   1 +
 .../provisioning/api/pushpull/PullActions.java     |   1 +
 .../provisioning/api/pushpull/PushActions.java     |   1 +
 .../api/pushpull/SyncopeSinglePullExecutor.java    |   1 +
 .../api/pushpull/SyncopeSinglePushExecutor.java    |   1 +
 .../api/pushpull/stream/StreamConnector.java       | 209 ++++++++++++++++
 .../SyncopeStreamPullExecutor.java}                |  22 +-
 .../stream/SyncopeStreamPushExecutor.java}         |  21 +-
 .../provisioning/api}/IntAttrNameParserTest.java   | 117 ++++++++-
 core/provisioning-java/pom.xml                     |  10 +-
 .../provisioning/java/ConnectorFacadeProxy.java    |  54 +----
 .../java/DefaultUserProvisioningManager.java       |   2 +-
 .../core/provisioning/java/DerAttrHandlerImpl.java |   2 +-
 .../core/provisioning/java/MappingManagerImpl.java | 128 ++++++++--
 .../java/data/AbstractAnyDataBinder.java           |   8 +-
 .../java/data/ConfigurationDataBinderImpl.java     |   2 +-
 .../java/data/JEXLItemTransformerImpl.java         |   9 +-
 .../java/data/NotificationDataBinderImpl.java      |   2 +-
 .../java/data/ResourceDataBinderImpl.java          |   4 +-
 .../java/data/SchemaDataBinderImpl.java            |   2 +-
 .../provisioning/java/data/UserDataBinderImpl.java |   7 +-
 .../notification/DefaultNotificationManager.java   |   4 +-
 .../AbstractPropagationTaskExecutor.java           |  26 +-
 .../LDAPMembershipPropagationActions.java          |  16 +-
 .../PriorityPropagationTaskExecutor.java           |  50 +---
 .../java/propagation/PropagationManagerImpl.java   |  57 ++---
 .../pushpull/AbstractProvisioningJobDelegate.java  | 114 +++++----
 .../java/pushpull/AbstractPullResultHandler.java   |   2 +-
 .../java/pushpull/AbstractPushResultHandler.java   |  22 +-
 .../java/pushpull/DBPasswordPullActions.java       |   2 +-
 .../DefaultAnyObjectPullResultHandler.java         |   2 +-
 .../pushpull/DefaultGroupPullResultHandler.java    |   2 +-
 .../pushpull/DefaultRealmPullResultHandler.java    |   2 +-
 .../pushpull/DefaultRealmPushResultHandler.java    |   2 +-
 .../pushpull/DefaultUserPullResultHandler.java     |   2 +-
 .../pushpull/DefaultUserPushResultHandler.java     |   2 +-
 .../provisioning/java/pushpull/InboundMatcher.java |   2 +-
 .../java/pushpull/LDAPMembershipPullActions.java   |   2 +-
 .../java/pushpull/LDAPPasswordPullActions.java     |   2 +-
 .../java/pushpull/OutboundMatcher.java             |   2 +-
 .../java/pushpull/SinglePullJobDelegate.java       |  10 +-
 .../java/pushpull/SinglePushJobDelegate.java       |   2 +-
 .../stream/StreamAnyObjectPushResultHandler.java   |  67 ++++++
 .../stream/StreamGroupPushResultHandler.java       |  67 ++++++
 .../pushpull/stream/StreamPullJobDelegate.java     | 262 +++++++++++++++++++++
 .../pushpull/stream/StreamPushJobDelegate.java     | 187 +++++++++++++++
 .../stream/StreamUserPushResultHandler.java        |  67 ++++++
 .../core/provisioning/java/utils/MappingUtils.java |  87 -------
 .../provisioning/java/utils/TemplateUtils.java     |   2 +-
 .../src/main/resources/provisioningContext.xml     |   2 +-
 .../core/provisioning/java/AbstractTest.java       |   8 +
 .../core/provisioning/java/TestInitializer.java    |   1 -
 .../java/{ => data}/ResourceDataBinderTest.java    |   3 +-
 .../java/{ => jexl}/MailTemplateTest.java          |   6 +-
 .../provisioning/java/{ => jexl}/MappingTest.java  |  22 +-
 .../pushpull/stream/StreamPullJobDelegateTest.java | 118 ++++++++++
 .../pushpull/stream/StreamPushJobDelegateTest.java | 118 ++++++++++
 .../cxf/service/ReconciliationServiceImpl.java     |  43 ++++
 .../camel/CamelUserProvisioningManager.java        |   2 +-
 .../syncope/core/logic/oidc/OIDCUserManager.java   |   2 +-
 .../java/data/OIDCProviderDataBinderImpl.java      |   6 +-
 .../syncope/core/logic/saml2/SAML2UserManager.java |   2 +-
 .../java/data/SAML2IdPDataBinderImpl.java          |   4 +-
 fit/core-reference/pom.xml                         |  96 +++++++-
 .../apache/syncope/fit/core/PullTaskITCase.java    |  16 +-
 .../syncope/fit/core/ReconciliationITCase.java     | 124 ++++++++++
 .../org/apache/syncope/fit/core/UserITCase.java    |   9 +-
 .../org/apache/syncope/fit/core/VirAttrITCase.java |  13 +-
 pom.xml                                            |  25 +-
 104 files changed, 3142 insertions(+), 561 deletions(-)
 rename {core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull => common/lib/src/main/java/org/apache/syncope/common/lib/to}/ProvisioningReport.java (62%)
 create mode 100644 common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AbstractCSVSpec.java
 create mode 100644 common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/CSVPullSpec.java
 create mode 100644 common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/CSVPushSpec.java
 copy core/{provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java => logic/src/test/java/org/apache/syncope/core/logic}/AbstractTest.java (95%)
 create mode 100644 core/logic/src/test/java/org/apache/syncope/core/logic/ReconciliationLogicTest.java
 copy core/{persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa => logic/src/test/java/org/apache/syncope/core/logic}/TestInitializer.java (96%)
 copy core/{provisioning-java/src/test/resources/provisioningTest.xml => logic/src/test/resources/logicTest.xml} (78%)
 create mode 100644 core/logic/src/test/resources/test1.csv
 rename core/{provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java => provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api}/IntAttrNameParser.java (98%)
 rename core/{provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java => provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api}/jexl/ClassFreeUberspect.java (96%)
 rename core/{provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java => provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api}/jexl/EmptyClassLoader.java (96%)
 rename core/{provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java => provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api}/jexl/JexlUtils.java (93%)
 rename core/{provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java => provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api}/jexl/SyncopeJexlFunctions.java (97%)
 create mode 100644 core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/stream/StreamConnector.java
 copy core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/{SyncopeSinglePullExecutor.java => stream/SyncopeStreamPullExecutor.java} (61%)
 copy core/{persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/policy/PullPolicy.java => provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/stream/SyncopeStreamPushExecutor.java} (60%)
 rename core/{provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java => provisioning-api/src/test/java/org/apache/syncope/core/provisioning/api}/IntAttrNameParserTest.java (75%)
 create mode 100644 core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamAnyObjectPushResultHandler.java
 create mode 100644 core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamGroupPushResultHandler.java
 create mode 100644 core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java
 create mode 100644 core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegate.java
 create mode 100644 core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamUserPushResultHandler.java
 rename core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/{ => data}/ResourceDataBinderTest.java (98%)
 rename core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/{ => jexl}/MailTemplateTest.java (96%)
 rename core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/{ => jexl}/MappingTest.java (80%)
 create mode 100644 core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegateTest.java
 create mode 100644 core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegateTest.java


[syncope] 01/02: Upgrading PDFBox

Posted by il...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 336e4c13e49580525b3eb02868c3a620ee10d8ef
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Thu Dec 26 07:44:05 2019 +0100

    Upgrading PDFBox
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 600331b..7f826c0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1428,7 +1428,7 @@ under the License.
       <dependency>
         <groupId>org.apache.pdfbox</groupId>
         <artifactId>pdfbox</artifactId>
-        <version>2.0.17</version>
+        <version>2.0.18</version>
       </dependency>
       
       <dependency>


[syncope] 02/02: [SYNCOPE-1531] Core support

Posted by il...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit b0dca1181b6160a920502c5778073e402ee33b00
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Mon Dec 30 15:13:20 2019 +0100

    [SYNCOPE-1531] Core support
---
 .../console/panels/AnyObjectDirectoryPanel.java    |   2 -
 .../client/console/panels/UserDirectoryPanel.java  |   2 -
 .../syncope/common/lib/to/AccessTokenTO.java       |   1 -
 .../syncope/common/lib/to}/ProvisioningReport.java |  83 ++++---
 .../syncope/common/rest/api/RESTHeaders.java       |   7 +-
 .../common/rest/api/beans/AbstractCSVSpec.java     | 229 ++++++++++++++++++
 .../syncope/common/rest/api/beans/CSVPullSpec.java | 136 +++++++++++
 .../syncope/common/rest/api/beans/CSVPushSpec.java | 139 +++++++++++
 .../rest/api/service/ReconciliationService.java    |  33 +++
 core/logic/pom.xml                                 |  67 +++++-
 .../apache/syncope/core/logic/AnyObjectLogic.java  |  13 +-
 .../org/apache/syncope/core/logic/GroupLogic.java  |  21 +-
 .../syncope/core/logic/ReconciliationLogic.java    | 212 ++++++++++++++++-
 .../org/apache/syncope/core/logic/TaskLogic.java   |   4 +-
 .../org/apache/syncope/core/logic/UserLogic.java   |  18 +-
 .../apache/syncope/core/logic}/AbstractTest.java   |   4 +-
 .../core/logic/ReconciliationLogicTest.java        | 154 ++++++++++++
 .../syncope/core/logic}/TestInitializer.java       |   3 +-
 core/logic/src/test/resources/logicTest.xml        |  65 +++++
 core/logic/src/test/resources/test1.csv            |   3 +
 .../jpa/dao/MyJPAJSONPlainSchemaDAO.java           |   2 +-
 .../jpa/dao/PGJPAJSONPlainSchemaDAO.java           |   2 +-
 .../core/persistence/jpa/dao/AbstractAnyDAO.java   |  45 ++--
 .../persistence/jpa/dao/JPAPlainSchemaDAO.java     |  10 +-
 .../jpa/entity/resource/JPAExternalResource.java   |   8 +-
 core/provisioning-api/pom.xml                      |  29 ++-
 .../syncope/core/provisioning/api/Connector.java   |  55 ++++-
 .../core/provisioning/api}/IntAttrNameParser.java  |   3 +-
 .../provisioning/api/UserProvisioningManager.java  |   2 +-
 .../provisioning/api}/jexl/ClassFreeUberspect.java |   2 +-
 .../provisioning/api}/jexl/EmptyClassLoader.java   |   2 +-
 .../core/provisioning/api}/jexl/JexlUtils.java     |  24 +-
 .../api}/jexl/SyncopeJexlFunctions.java            |   2 +-
 .../api/propagation/PropagationManager.java        |  19 ++
 .../api/propagation/PropagationTaskInfo.java       |  65 ++++-
 .../api/pushpull/ProvisioningProfile.java          |   1 +
 .../provisioning/api/pushpull/PullActions.java     |   1 +
 .../provisioning/api/pushpull/PushActions.java     |   1 +
 .../api/pushpull/SyncopeSinglePullExecutor.java    |   1 +
 .../api/pushpull/SyncopeSinglePushExecutor.java    |   1 +
 .../api/pushpull/stream/StreamConnector.java       | 209 ++++++++++++++++
 .../SyncopeStreamPullExecutor.java}                |  22 +-
 .../SyncopeStreamPushExecutor.java}                |  25 +-
 .../provisioning/api}/IntAttrNameParserTest.java   | 117 ++++++++-
 core/provisioning-java/pom.xml                     |  10 +-
 .../provisioning/java/ConnectorFacadeProxy.java    |  54 +----
 .../java/DefaultUserProvisioningManager.java       |   2 +-
 .../core/provisioning/java/DerAttrHandlerImpl.java |   2 +-
 .../core/provisioning/java/MappingManagerImpl.java | 128 ++++++++--
 .../java/data/AbstractAnyDataBinder.java           |   8 +-
 .../java/data/ConfigurationDataBinderImpl.java     |   2 +-
 .../java/data/JEXLItemTransformerImpl.java         |   9 +-
 .../java/data/NotificationDataBinderImpl.java      |   2 +-
 .../java/data/ResourceDataBinderImpl.java          |   4 +-
 .../java/data/SchemaDataBinderImpl.java            |   2 +-
 .../provisioning/java/data/UserDataBinderImpl.java |   7 +-
 .../notification/DefaultNotificationManager.java   |   4 +-
 .../AbstractPropagationTaskExecutor.java           |  26 +-
 .../LDAPMembershipPropagationActions.java          |  16 +-
 .../PriorityPropagationTaskExecutor.java           |  50 +---
 .../java/propagation/PropagationManagerImpl.java   |  57 ++---
 .../pushpull/AbstractProvisioningJobDelegate.java  | 114 +++++----
 .../java/pushpull/AbstractPullResultHandler.java   |   2 +-
 .../java/pushpull/AbstractPushResultHandler.java   |  22 +-
 .../java/pushpull/DBPasswordPullActions.java       |   2 +-
 .../DefaultAnyObjectPullResultHandler.java         |   2 +-
 .../pushpull/DefaultGroupPullResultHandler.java    |   2 +-
 .../pushpull/DefaultRealmPullResultHandler.java    |   2 +-
 .../pushpull/DefaultRealmPushResultHandler.java    |   2 +-
 .../pushpull/DefaultUserPullResultHandler.java     |   2 +-
 .../pushpull/DefaultUserPushResultHandler.java     |   2 +-
 .../provisioning/java/pushpull/InboundMatcher.java |   2 +-
 .../java/pushpull/LDAPMembershipPullActions.java   |   2 +-
 .../java/pushpull/LDAPPasswordPullActions.java     |   2 +-
 .../java/pushpull/OutboundMatcher.java             |   2 +-
 .../java/pushpull/SinglePullJobDelegate.java       |  10 +-
 .../java/pushpull/SinglePushJobDelegate.java       |   2 +-
 .../stream/StreamAnyObjectPushResultHandler.java   |  67 ++++++
 .../stream/StreamGroupPushResultHandler.java       |  67 ++++++
 .../pushpull/stream/StreamPullJobDelegate.java     | 262 +++++++++++++++++++++
 .../pushpull/stream/StreamPushJobDelegate.java     | 187 +++++++++++++++
 .../stream/StreamUserPushResultHandler.java        |  67 ++++++
 .../core/provisioning/java/utils/MappingUtils.java |  87 -------
 .../provisioning/java/utils/TemplateUtils.java     |   2 +-
 .../src/main/resources/provisioningContext.xml     |   2 +-
 .../core/provisioning/java/AbstractTest.java       |   8 +
 .../core/provisioning/java/TestInitializer.java    |   1 -
 .../java/{ => data}/ResourceDataBinderTest.java    |   3 +-
 .../java/{ => jexl}/MailTemplateTest.java          |   6 +-
 .../provisioning/java/{ => jexl}/MappingTest.java  |  22 +-
 .../pushpull/stream/StreamPullJobDelegateTest.java | 118 ++++++++++
 .../pushpull/stream/StreamPushJobDelegateTest.java | 118 ++++++++++
 .../cxf/service/ReconciliationServiceImpl.java     |  43 ++++
 .../camel/CamelUserProvisioningManager.java        |   2 +-
 .../syncope/core/logic/oidc/OIDCUserManager.java   |   2 +-
 .../java/data/OIDCProviderDataBinderImpl.java      |   6 +-
 .../syncope/core/logic/saml2/SAML2UserManager.java |   2 +-
 .../java/data/SAML2IdPDataBinderImpl.java          |   4 +-
 fit/core-reference/pom.xml                         |  96 +++++++-
 .../apache/syncope/fit/core/PullTaskITCase.java    |  16 +-
 .../syncope/fit/core/ReconciliationITCase.java     | 124 ++++++++++
 .../org/apache/syncope/fit/core/UserITCase.java    |   9 +-
 .../org/apache/syncope/fit/core/VirAttrITCase.java |  13 +-
 pom.xml                                            |  23 +-
 104 files changed, 3195 insertions(+), 561 deletions(-)

diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel.java
index 78128a6..40decfc 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel.java
@@ -48,7 +48,6 @@ import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.AuditElements;
 import org.apache.syncope.common.lib.types.StandardEntitlement;
 import org.apache.wicket.PageReference;
-import org.apache.wicket.WicketRuntimeException;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.event.Broadcast;
 import org.apache.wicket.model.CompoundPropertyModel;
@@ -209,7 +208,6 @@ public class AnyObjectDirectoryPanel extends AnyDirectoryPanel<AnyObjectTO, AnyO
                             LOG.error("While restoring any object {}", model.getObject().getKey(), e);
                             SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage())
                                     ? e.getClass().getName() : e.getMessage());
-                            throw new WicketRuntimeException(e);
                         }
                         ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
                     }
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java
index dff5a73..b849abc 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java
@@ -53,7 +53,6 @@ import org.apache.syncope.common.lib.types.AuditElements;
 import org.apache.syncope.common.lib.types.StandardEntitlement;
 import org.apache.syncope.common.rest.api.service.UserSelfService;
 import org.apache.wicket.PageReference;
-import org.apache.wicket.WicketRuntimeException;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.event.Broadcast;
 import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
@@ -388,7 +387,6 @@ public class UserDirectoryPanel extends AnyDirectoryPanel<UserTO, UserRestClient
                             LOG.error("While restoring user {}", model.getObject().getKey(), e);
                             SyncopeConsoleSession.get().error(
                                     StringUtils.isBlank(e.getMessage()) ? e.getClass().getName() : e.getMessage());
-                            throw new WicketRuntimeException(e);
                         }
                         ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
                     }
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/AccessTokenTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/AccessTokenTO.java
index c553e5d..f562880 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/to/AccessTokenTO.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/AccessTokenTO.java
@@ -74,5 +74,4 @@ public class AccessTokenTO extends BaseBean implements EntityTO {
     public void setOwner(final String owner) {
         this.owner = owner;
     }
-
 }
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/ProvisioningReport.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/ProvisioningReport.java
similarity index 62%
rename from core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/ProvisioningReport.java
rename to common/lib/src/main/java/org/apache/syncope/common/lib/to/ProvisioningReport.java
index 3c0a6f0..9feb396 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/ProvisioningReport.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/ProvisioningReport.java
@@ -16,16 +16,24 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.provisioning.api.pushpull;
+package org.apache.syncope.common.lib.to;
 
-import java.util.Collection;
-import org.apache.commons.lang3.StringUtils;
+import javax.xml.bind.annotation.XmlEnum;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.syncope.common.lib.BaseBean;
 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 extends BaseBean {
 
+    private static final long serialVersionUID = -5822836001006407497L;
+
+    @XmlEnum
     public enum Status {
 
         SUCCESS,
@@ -104,41 +112,40 @@ public class ProvisioningReport {
         this.uidValue = uidValue;
     }
 
-    /**
-     * Human readable report string, using the given trace level.
-     *
-     * @param level trace level
-     * @return String for certain levels, null for level NONE
-     */
-    public String getReportString(final TraceLevel level) {
-        if (level == TraceLevel.SUMMARY) {
-            // No per entry log in this case.
-            return null;
-        } else if (level == TraceLevel.FAILURES && status == Status.FAILURE) {
-            // only report failures
-            return String.format("Failed %s (key/name): %s/%s with message: %s", operation, key, name, message);
-        } else {
-            // All
-            return String.format("%s %s (key/name): %s/%s %s", operation, status, key, name,
-                    StringUtils.isBlank(message)
-                    ? ""
-                    : "with message: " + message);
-        }
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().
+                append(message).
+                append(status).
+                append(anyType).
+                append(operation).
+                append(key).
+                append(name).
+                append(uidValue).
+                build();
     }
 
-    /**
-     * Helper method to invoke logging per provisioning result, for the given trace level.
-     *
-     * @param results provisioning results
-     * @param level trace level
-     * @return report as string
-     */
-    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');
-        });
-        return sb.toString();
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final ProvisioningReport other = (ProvisioningReport) obj;
+        return new EqualsBuilder().
+                append(message, other.message).
+                append(status, other.status).
+                append(anyType, other.anyType).
+                append(operation, other.operation).
+                append(key, other.key).
+                append(name, other.name).
+                append(uidValue, other.uidValue).
+                build();
     }
 
     @Override
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java
index f993317..0393466 100644
--- a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java
@@ -68,6 +68,11 @@ public final class RESTHeaders {
     public static final MediaType APPLICATION_YAML_TYPE = new MediaType("application", "yaml");
 
     /**
+     * Mediatype for text/csv, not defined in {@link javax.ws.rs.core.MediaType}.
+     */
+    public static final String TEXT_CSV = "text/csv";
+
+    /**
      * Mediatype for multipart/mixed, not defined in {@link javax.ws.rs.core.MediaType}.
      */
     public static final String MULTIPART_MIXED = "multipart/mixed";
@@ -79,7 +84,7 @@ public final class RESTHeaders {
 
     /**
      * Builds Content-Type string for multipart/mixed and the given boundary.
-     * 
+     *
      * @param boundary multipart boundary value
      * @return multipart/mixed Content-Type string, with given boundary
      */
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AbstractCSVSpec.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AbstractCSVSpec.java
new file mode 100644
index 0000000..7844335
--- /dev/null
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AbstractCSVSpec.java
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.rest.api.beans;
+
+import java.util.ArrayList;
+import java.util.List;
+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;
+
+public abstract class AbstractCSVSpec {
+
+    protected abstract static class Builder<T extends AbstractCSVSpec, B extends Builder<T, B>> {
+
+        protected T instance;
+
+        protected abstract T newInstance();
+
+        protected T getInstance() {
+            if (instance == null) {
+                instance = newInstance();
+            }
+            return instance;
+        }
+
+        @SuppressWarnings("unchecked")
+        public B columnSeparator(final char columnSeparator) {
+            getInstance().setColumnSeparator(columnSeparator);
+            return (B) this;
+        }
+
+        @SuppressWarnings("unchecked")
+        public B arrayElementSeparator(final String arrayElementSeparator) {
+            getInstance().setArrayElementSeparator(arrayElementSeparator);
+            return (B) this;
+        }
+
+        @SuppressWarnings("unchecked")
+        public B quoteChar(final char quoteChar) {
+            getInstance().setQuoteChar(quoteChar);
+            return (B) this;
+        }
+
+        @SuppressWarnings("unchecked")
+        public B escapeChar(final char escapeChar) {
+            getInstance().setEscapeChar(escapeChar);
+            return (B) this;
+        }
+
+        @SuppressWarnings("unchecked")
+        public B lineSeparator(final String lineSeparatorChar) {
+            getInstance().setLineSeparator(lineSeparatorChar);
+            return (B) this;
+        }
+
+        @SuppressWarnings("unchecked")
+        public B nullValue(final String nullValue) {
+            getInstance().setNullValue(nullValue);
+            return (B) this;
+        }
+
+        @SuppressWarnings("unchecked")
+        public B allowComments(final boolean allowComments) {
+            getInstance().setAllowComments(allowComments);
+            return (B) this;
+        }
+
+        @SuppressWarnings("unchecked")
+        public B unmatchingRule(final UnmatchingRule unmatchingRule) {
+            getInstance().setUnmatchingRule(unmatchingRule);
+            return (B) this;
+        }
+
+        @SuppressWarnings("unchecked")
+        public B matchingRule(final MatchingRule matchingRule) {
+            getInstance().setMatchingRule(matchingRule);
+            return (B) this;
+        }
+
+        @SuppressWarnings("unchecked")
+        public B action(final String action) {
+            getInstance().getActions().add(action);
+            return (B) this;
+        }
+
+        public T build() {
+            return getInstance();
+        }
+    }
+
+    protected String anyTypeKey;
+
+    protected char columnSeparator = ',';
+
+    protected String arrayElementSeparator = ";";
+
+    protected char quoteChar = '"';
+
+    protected Character escapeChar;
+
+    protected String lineSeparator = "\n";
+
+    protected String nullValue = "";
+
+    protected boolean allowComments;
+
+    protected UnmatchingRule unmatchingRule = UnmatchingRule.PROVISION;
+
+    protected MatchingRule matchingRule = MatchingRule.UPDATE;
+
+    protected List<String> actions = new ArrayList<>();
+
+    public String getAnyTypeKey() {
+        return anyTypeKey;
+    }
+
+    @NotNull
+    @QueryParam("anyTypeKey")
+    public void setAnyTypeKey(final String anyTypeKey) {
+        this.anyTypeKey = anyTypeKey;
+    }
+
+    public char getColumnSeparator() {
+        return columnSeparator;
+    }
+
+    @QueryParam("columnSeparator")
+    public void setColumnSeparator(final char columnSeparator) {
+        this.columnSeparator = columnSeparator;
+    }
+
+    public String getArrayElementSeparator() {
+        return arrayElementSeparator;
+    }
+
+    @QueryParam("arrayElementSeparator")
+    public void setArrayElementSeparator(final String arrayElementSeparator) {
+        this.arrayElementSeparator = arrayElementSeparator;
+    }
+
+    public char getQuoteChar() {
+        return quoteChar;
+    }
+
+    @QueryParam("quoteChar")
+    public void setQuoteChar(final char quoteChar) {
+        this.quoteChar = quoteChar;
+    }
+
+    public Character getEscapeChar() {
+        return escapeChar;
+    }
+
+    @QueryParam("escapeChar")
+    public void setEscapeChar(final Character escapeChar) {
+        this.escapeChar = escapeChar;
+    }
+
+    public String getLineSeparator() {
+        return lineSeparator;
+    }
+
+    @QueryParam("lineSeparator")
+    public void setLineSeparator(final String lineSeparator) {
+        this.lineSeparator = lineSeparator;
+    }
+
+    public String getNullValue() {
+        return nullValue;
+    }
+
+    @QueryParam("nullValue")
+    public void setNullValue(final String nullValue) {
+        this.nullValue = nullValue;
+    }
+
+    public boolean isAllowComments() {
+        return allowComments;
+    }
+
+    @QueryParam("allowComments")
+    public void setAllowComments(final boolean allowComments) {
+        this.allowComments = allowComments;
+    }
+
+    public UnmatchingRule getUnmatchingRule() {
+        return unmatchingRule;
+    }
+
+    @QueryParam("unmatchingRule")
+    public void setUnmatchingRule(final UnmatchingRule unmatchingRule) {
+        this.unmatchingRule = unmatchingRule;
+    }
+
+    public MatchingRule getMatchingRule() {
+        return matchingRule;
+    }
+
+    @QueryParam("matchingRule")
+    public void setMatchingRule(final MatchingRule matchingRule) {
+        this.matchingRule = matchingRule;
+    }
+
+    public List<String> getActions() {
+        return actions;
+    }
+
+    @QueryParam("actions")
+    public void setActions(final List<String> actions) {
+        this.actions = actions;
+    }
+}
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/CSVPullSpec.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/CSVPullSpec.java
new file mode 100644
index 0000000..7bb8a2d
--- /dev/null
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/CSVPullSpec.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.rest.api.beans;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.QueryParam;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.types.ConflictResolutionAction;
+
+public class CSVPullSpec extends AbstractCSVSpec {
+
+    public static class Builder extends AbstractCSVSpec.Builder<CSVPullSpec, Builder> {
+
+        @Override
+        protected CSVPullSpec newInstance() {
+            return new CSVPullSpec();
+        }
+
+        public Builder(final String anyTypeKey, final String keyColumn) {
+            getInstance().setAnyTypeKey(anyTypeKey);
+            getInstance().setKeyColumn(keyColumn);
+        }
+
+        public Builder remediation(final boolean remediation) {
+            instance.setRemediation(remediation);
+            return this;
+        }
+
+        public Builder ignoreColumns(final String... ignoreColumns) {
+            instance.getIgnoreColumns().addAll(Stream.of(ignoreColumns).collect(Collectors.toList()));
+            return this;
+        }
+
+        public Builder destinationRealm(final String destinationRealm) {
+            instance.setDestinationRealm(destinationRealm);
+            return this;
+        }
+
+        public Builder conflictResolutionAction(final ConflictResolutionAction conflictResolutionAction) {
+            instance.setConflictResolutionAction(conflictResolutionAction);
+            return this;
+        }
+
+        public Builder pullCorrelationRule(final String pullCorrelationRule) {
+            instance.setPullCorrelationRule(pullCorrelationRule);
+            return this;
+        }
+    }
+
+    private String destinationRealm = SyncopeConstants.ROOT_REALM;
+
+    private String keyColumn;
+
+    private Set<String> ignoreColumns = new HashSet<>();
+
+    private boolean remediation;
+
+    private ConflictResolutionAction conflictResolutionAction = ConflictResolutionAction.IGNORE;
+
+    private String pullCorrelationRule;
+
+    public String getDestinationRealm() {
+        return destinationRealm;
+    }
+
+    @QueryParam("destinationRealm")
+    public void setDestinationRealm(final String destinationRealm) {
+        this.destinationRealm = destinationRealm;
+    }
+
+    public String getKeyColumn() {
+        return keyColumn;
+    }
+
+    @NotNull
+    @QueryParam("keyColumn")
+    public void setKeyColumn(final String keyColumn) {
+        this.keyColumn = keyColumn;
+    }
+
+    public Set<String> getIgnoreColumns() {
+        return ignoreColumns;
+    }
+
+    @QueryParam("ignoreColumns")
+    public void setIgnoreColumns(final Set<String> ignoreColumns) {
+        this.ignoreColumns = ignoreColumns;
+    }
+
+    public boolean isRemediation() {
+        return remediation;
+    }
+
+    @QueryParam("remediation")
+    public void setRemediation(final boolean remediation) {
+        this.remediation = remediation;
+    }
+
+    public ConflictResolutionAction getConflictResolutionAction() {
+        return conflictResolutionAction;
+    }
+
+    @QueryParam("conflictResolutionAction")
+    public void setConflictResolutionAction(final ConflictResolutionAction conflictResolutionAction) {
+        this.conflictResolutionAction = conflictResolutionAction;
+    }
+
+    public String getPullCorrelationRule() {
+        return pullCorrelationRule;
+    }
+
+    @QueryParam("pullCorrelationRule")
+    public void setPullCorrelationRule(final String pullCorrelationRule) {
+        this.pullCorrelationRule = pullCorrelationRule;
+    }
+}
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/CSVPushSpec.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/CSVPushSpec.java
new file mode 100644
index 0000000..92b2196
--- /dev/null
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/CSVPushSpec.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.rest.api.beans;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.ws.rs.QueryParam;
+
+public class CSVPushSpec extends AbstractCSVSpec {
+
+    public static class Builder extends AbstractCSVSpec.Builder<CSVPushSpec, Builder> {
+
+        @Override
+        protected CSVPushSpec newInstance() {
+            return new CSVPushSpec();
+        }
+
+        public Builder(final String anyTypeKey) {
+            getInstance().setAnyTypeKey(anyTypeKey);
+        }
+
+        public Builder field(final String field) {
+            getInstance().getFields().add(field);
+            return this;
+        }
+
+        public Builder fields(final Collection<String> fields) {
+            getInstance().getFields().addAll(fields);
+            return this;
+        }
+
+        public Builder plainAttr(final String plainAttr) {
+            getInstance().getPlainAttrs().add(plainAttr);
+            return this;
+        }
+
+        public Builder plainAttrs(final Collection<String> plainAttrs) {
+            getInstance().getPlainAttrs().addAll(plainAttrs);
+            return this;
+        }
+
+        public Builder derAttr(final String derAttr) {
+            getInstance().getDerAttrs().add(derAttr);
+            return this;
+        }
+
+        public Builder derAttrs(final Collection<String> derAttrs) {
+            getInstance().getDerAttrs().addAll(derAttrs);
+            return this;
+        }
+
+        public Builder virAttr(final String virAttr) {
+            getInstance().getVirAttrs().add(virAttr);
+            return this;
+        }
+
+        public Builder virAttrs(final Collection<String> virAttrs) {
+            getInstance().getVirAttrs().addAll(virAttrs);
+            return this;
+        }
+
+        public Builder ignorePagination(final boolean ignorePagination) {
+            getInstance().setIgnorePaging(ignorePagination);
+            return this;
+        }
+    }
+
+    private List<String> fields = new ArrayList<>();
+
+    private List<String> plainAttrs = new ArrayList<>();
+
+    private List<String> derAttrs = new ArrayList<>();
+
+    private List<String> virAttrs = new ArrayList<>();
+
+    private boolean ignorePaging;
+
+    public List<String> getFields() {
+        return fields;
+    }
+
+    @QueryParam("fields")
+    public void setFields(final List<String> fields) {
+        this.fields = fields;
+    }
+
+    public List<String> getPlainAttrs() {
+        return plainAttrs;
+    }
+
+    @QueryParam("plainAttrs")
+    public void setPlainAttrs(final List<String> plainAttrs) {
+        this.plainAttrs = plainAttrs;
+    }
+
+    public List<String> getDerAttrs() {
+        return derAttrs;
+    }
+
+    @QueryParam("derAttrs")
+    public void setDerAttrs(final List<String> derAttrs) {
+        this.derAttrs = derAttrs;
+    }
+
+    public List<String> getVirAttrs() {
+        return virAttrs;
+    }
+
+    @QueryParam("virAttrs")
+    public void setVirAttrs(final List<String> virAttrs) {
+        this.virAttrs = virAttrs;
+    }
+
+    public boolean isIgnorePaging() {
+        return ignorePaging;
+    }
+
+    @QueryParam("ignorePaging")
+    public void setIgnorePaging(final boolean ignorePaging) {
+        this.ignorePaging = ignorePaging;
+    }
+}
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ReconciliationService.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ReconciliationService.java
index cb44ef9..a2c8193 100644
--- a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ReconciliationService.java
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ReconciliationService.java
@@ -23,6 +23,8 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses;
 import io.swagger.v3.oas.annotations.security.SecurityRequirement;
 import io.swagger.v3.oas.annotations.security.SecurityRequirements;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import java.io.InputStream;
+import java.util.List;
 import javax.validation.constraints.NotNull;
 import javax.ws.rs.BeanParam;
 import javax.ws.rs.Consumes;
@@ -31,10 +33,15 @@ import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.common.lib.to.PullTaskTO;
 import org.apache.syncope.common.lib.to.PushTaskTO;
 import org.apache.syncope.common.lib.to.ReconStatus;
 import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.beans.AnyQuery;
+import org.apache.syncope.common.rest.api.beans.CSVPullSpec;
+import org.apache.syncope.common.rest.api.beans.CSVPushSpec;
 import org.apache.syncope.common.rest.api.beans.ReconQuery;
 
 /**
@@ -84,4 +91,30 @@ public interface ReconciliationService extends JAXRSService {
     @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
     void pull(@BeanParam ReconQuery query, @NotNull PullTaskTO pullTask);
+
+    /**
+     * Export a list of any objects matching the given query as CSV according to the provided specification.
+     *
+     * @param anyQuery query conditions
+     * @param spec CSV push specification
+     * @return CSV content matching the provided specification
+     */
+    @GET
+    @Path("csv/push")
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    @Produces({ RESTHeaders.TEXT_CSV })
+    Response push(@BeanParam AnyQuery anyQuery, @BeanParam CSVPushSpec spec);
+
+    /**
+     * Pulls the CSV input into Syncope according to the provided specification.
+     *
+     * @param spec CSV pull specification
+     * @param csv CSV input
+     * @return pull report
+     */
+    @POST
+    @Path("csv/pull")
+    @Consumes({ RESTHeaders.TEXT_CSV })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    List<ProvisioningReport> pull(@BeanParam CSVPullSpec spec, InputStream csv);
 }
diff --git a/core/logic/pom.xml b/core/logic/pom.xml
index 3903fbc..983c525 100644
--- a/core/logic/pom.xml
+++ b/core/logic/pom.xml
@@ -83,12 +83,56 @@ under the License.
       <groupId>org.apache.logging.log4j</groupId>
       <artifactId>log4j-core</artifactId>
     </dependency>
-      
+
+    <dependency>
+      <groupId>com.fasterxml.jackson.dataformat</groupId>
+      <artifactId>jackson-dataformat-csv</artifactId>
+    </dependency>
+
     <dependency>
       <groupId>org.apache.syncope.core</groupId>
       <artifactId>syncope-core-provisioning-java</artifactId>
       <version>${project.version}</version>
     </dependency>
+
+    <dependency>
+      <groupId>org.apache.syncope.core</groupId>
+      <artifactId>syncope-core-workflow-java</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.el</groupId>
+      <artifactId>javax.el-api</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.core</groupId>
+      <artifactId>syncope-core-persistence-jpa</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-simple</artifactId>
+      <version>${slf4j.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.h2database</groupId>
+      <artifactId>h2</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-test</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>
@@ -98,6 +142,27 @@ under the License.
         <filtering>true</filtering>
       </resource>
     </resources>
+    <testResources>
+      <testResource>
+        <directory>${basedir}/src/test/resources</directory>
+        <filtering>true</filtering>
+      </testResource>
+      <testResource>
+        <directory>${basedir}/../persistence-jpa/src/main/resources</directory>
+        <includes>
+          <include>persistence.properties</include>
+        </includes>
+        <filtering>true</filtering>
+      </testResource>
+      <testResource>
+        <directory>${basedir}/../persistence-jpa/src/test/resources</directory>
+        <filtering>true</filtering>
+      </testResource>
+      <testResource>
+        <directory>${basedir}/../provisioning-java/src/test/resources</directory>
+        <filtering>true</filtering>
+      </testResource>
+    </testResources>
         
     <plugins>
       <plugin>
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/AnyObjectLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/AnyObjectLogic.java
index 3acbc20..a4ddc44 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/AnyObjectLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/AnyObjectLogic.java
@@ -77,7 +77,9 @@ public class AnyObjectLogic extends AbstractAnyLogic<AnyObjectTO, AnyObjectPatch
     @Override
     public Pair<Integer, List<AnyObjectTO>> search(
             final SearchCond searchCond,
-            final int page, final int size, final List<OrderByClause> orderBy,
+            final int page,
+            final int size,
+            final List<OrderByClause> orderBy,
             final String realm,
             final boolean details) {
 
@@ -85,16 +87,17 @@ public class AnyObjectLogic extends AbstractAnyLogic<AnyObjectTO, AnyObjectPatch
             throw new UnsupportedOperationException("Need to specify " + AnyType.class.getSimpleName());
         }
 
-        Set<String> effectiveRealms = RealmUtils.getEffective(
+        Set<String> adminRealms = RealmUtils.getEffective(
                 AuthContextUtils.getAuthorizations().get(AnyEntitlement.SEARCH.getFor(searchCond.hasAnyTypeCond())),
                 realm);
 
-        int count = searchDAO.count(effectiveRealms, searchCond, AnyTypeKind.ANY_OBJECT);
+        int count = searchDAO.count(adminRealms, searchCond, AnyTypeKind.ANY_OBJECT);
 
         List<AnyObject> matching = searchDAO.search(
-                effectiveRealms, searchCond, page, size, orderBy, AnyTypeKind.ANY_OBJECT);
+                adminRealms, searchCond, page, size, orderBy, AnyTypeKind.ANY_OBJECT);
         List<AnyObjectTO> result = matching.stream().
-                map(anyObject -> binder.getAnyObjectTO(anyObject, details)).collect(Collectors.toList());
+                map(anyObject -> binder.getAnyObjectTO(anyObject, details)).
+                collect(Collectors.toList());
 
         return Pair.of(count, result);
     }
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/GroupLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/GroupLogic.java
index 5524fd7..cc0bf77 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/GroupLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/GroupLogic.java
@@ -32,7 +32,6 @@ import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.SyncopeClientException;
-import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.patch.GroupPatch;
 import org.apache.syncope.common.lib.patch.StringPatchItem;
 import org.apache.syncope.common.lib.to.ExecTO;
@@ -154,20 +153,24 @@ public class GroupLogic extends AbstractAnyLogic<GroupTO, GroupPatch> {
     @Override
     public Pair<Integer, List<GroupTO>> search(
             final SearchCond searchCond,
-            final int page, final int size, final List<OrderByClause> orderBy,
+            final int page,
+            final int size,
+            final List<OrderByClause> orderBy,
             final String realm,
             final boolean details) {
 
-        int count = searchDAO.count(
-                RealmUtils.getEffective(SyncopeConstants.FULL_ADMIN_REALMS, realm),
-                searchCond == null ? groupDAO.getAllMatchingCond() : searchCond, AnyTypeKind.GROUP);
+        Set<String> adminRealms = RealmUtils.getEffective(
+                AuthContextUtils.getAuthorizations().get(StandardEntitlement.GROUP_SEARCH), realm);
+
+        SearchCond effectiveCond = searchCond == null ? groupDAO.getAllMatchingCond() : searchCond;
+
+        int count = searchDAO.count(adminRealms, effectiveCond, AnyTypeKind.GROUP);
 
         List<Group> matching = searchDAO.search(
-                RealmUtils.getEffective(SyncopeConstants.FULL_ADMIN_REALMS, realm),
-                searchCond == null ? groupDAO.getAllMatchingCond() : searchCond,
-                page, size, orderBy, AnyTypeKind.GROUP);
+                adminRealms, effectiveCond, page, size, orderBy, AnyTypeKind.GROUP);
         List<GroupTO> result = matching.stream().
-                map(group -> binder.getGroupTO(group, details)).collect(Collectors.toList());
+                map(group -> binder.getGroupTO(group, details)).
+                collect(Collectors.toList());
 
         return Pair.of(count, result);
     }
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
index ebe7f35..c7a4dfe 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
@@ -18,9 +18,16 @@
  */
 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;
@@ -37,6 +44,7 @@ import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.MatchType;
 import org.apache.syncope.common.lib.types.StandardEntitlement;
+import org.apache.syncope.common.rest.api.beans.CSVPullSpec;
 import org.apache.syncope.common.rest.api.beans.ReconQuery;
 import org.apache.syncope.core.persistence.api.dao.AnyDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
@@ -53,7 +61,18 @@ import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
 import org.apache.syncope.core.provisioning.api.ConnectorFactory;
 import org.apache.syncope.core.provisioning.api.MappingManager;
 import org.apache.syncope.core.provisioning.api.VirAttrHandler;
-import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
+import org.apache.syncope.common.lib.types.AnyEntitlement;
+import org.apache.syncope.common.rest.api.beans.AbstractCSVSpec;
+import org.apache.syncope.common.rest.api.beans.CSVPushSpec;
+import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
+import org.apache.syncope.core.persistence.api.dao.DerSchemaDAO;
+import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
+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.api.pushpull.SyncopeSinglePullExecutor;
 import org.apache.syncope.core.provisioning.api.pushpull.SyncopeSinglePushExecutor;
 import org.apache.syncope.core.provisioning.java.pushpull.InboundMatcher;
@@ -67,6 +86,10 @@ import org.quartz.JobExecutionException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
+import org.apache.syncope.core.provisioning.api.pushpull.stream.SyncopeStreamPullExecutor;
+import org.apache.syncope.core.provisioning.api.pushpull.stream.SyncopeStreamPushExecutor;
+import org.apache.syncope.core.provisioning.api.utils.RealmUtils;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
 
 @Component
 public class ReconciliationLogic extends AbstractTransactionalLogic<EntityTO> {
@@ -84,6 +107,18 @@ public class ReconciliationLogic extends AbstractTransactionalLogic<EntityTO> {
     private RealmDAO realmDAO;
 
     @Autowired
+    private PlainSchemaDAO plainSchemaDAO;
+
+    @Autowired
+    private DerSchemaDAO derSchemaDAO;
+
+    @Autowired
+    private VirSchemaDAO virSchemaDAO;
+
+    @Autowired
+    private AnySearchDAO searchDAO;
+
+    @Autowired
     private VirAttrHandler virAttrHandler;
 
     @Autowired
@@ -104,6 +139,12 @@ public class ReconciliationLogic extends AbstractTransactionalLogic<EntityTO> {
     @Autowired
     private SyncopeSinglePushExecutor singlePushExecutor;
 
+    @Autowired
+    private SyncopeStreamPushExecutor streamPushExecutor;
+
+    @Autowired
+    private SyncopeStreamPullExecutor streamPullExecutor;
+
     private Provision getProvision(final String anyTypeKey, final String resourceKey) {
         AnyType anyType = anyTypeDAO.find(anyTypeKey);
         if (anyType == null) {
@@ -336,6 +377,175 @@ 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());
+        if (spec.getEscapeChar() != null) {
+            schema = schema.withEscapeChar(spec.getEscapeChar());
+        }
+
+        return schema;
+    }
+
+    @PreAuthorize("hasRole('" + StandardEntitlement.TASK_EXECUTE + "')")
+    public List<ProvisioningReport> push(
+            final SearchCond searchCond,
+            final int page,
+            final int size,
+            final List<OrderByClause> orderBy,
+            final String realm,
+            final CSVPushSpec spec,
+            final OutputStream os) {
+
+        AnyType anyType = anyTypeDAO.find(spec.getAnyTypeKey());
+        if (anyType == null) {
+            throw new NotFoundException("AnyType '" + spec.getAnyTypeKey() + "'");
+        }
+
+        AnyUtils anyUtils = anyUtilsFactory.getInstance(anyType.getKind());
+
+        String entitlement;
+        switch (anyType.getKind()) {
+            case GROUP:
+                entitlement = StandardEntitlement.GROUP_SEARCH;
+                break;
+
+            case ANY_OBJECT:
+                entitlement = AnyEntitlement.SEARCH.getFor(anyType.getKey());
+                break;
+
+            case USER:
+            default:
+                entitlement = StandardEntitlement.USER_SEARCH;
+        }
+
+        Set<String> adminRealms = RealmUtils.getEffective(AuthContextUtils.getAuthorizations().get(entitlement), realm);
+        SearchCond effectiveCond = searchCond == null ? anyUtils.dao().getAllMatchingCond() : searchCond;
+
+        List<Any<?>> matching;
+        if (spec.isIgnorePaging()) {
+            matching = new ArrayList<>();
+
+            int count = searchDAO.count(adminRealms, searchCond, anyType.getKind());
+            int pages = (count / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
+
+            for (int p = 1; p <= pages; p++) {
+                matching.addAll(searchDAO.search(adminRealms, effectiveCond,
+                        p, AnyDAO.DEFAULT_PAGE_SIZE, orderBy, anyType.getKind()));
+            }
+        } else {
+            matching = searchDAO.search(adminRealms, effectiveCond, page, size, orderBy, anyType.getKind());
+        }
+
+        List<String> columns = new ArrayList<>();
+        spec.getFields().forEach(item -> {
+            if (anyUtils.getField(item) == null) {
+                LOG.warn("Ignoring invalid field {}", item);
+            } else {
+                columns.add(item);
+            }
+        });
+        spec.getPlainAttrs().forEach(item -> {
+            if (plainSchemaDAO.find(item) == null) {
+                LOG.warn("Ignoring invalid plain schema {}", item);
+            } else {
+                columns.add(item);
+            }
+        });
+        spec.getDerAttrs().forEach(item -> {
+            if (derSchemaDAO.find(item) == null) {
+                LOG.warn("Ignoring invalid derived schema {}", item);
+            } else {
+                columns.add(item);
+            }
+        });
+        spec.getVirAttrs().forEach(item -> {
+            if (virSchemaDAO.find(item) == null) {
+                LOG.warn("Ignoring invalid virtual schema {}", item);
+            } else {
+                columns.add(item);
+            }
+        });
+
+        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());
+
+        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),
+                    pushTask);
+        } catch (Exception e) {
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Reconciliation);
+            sce.getElements().add(e.getMessage());
+            throw sce;
+        }
+    }
+
+    @PreAuthorize("hasRole('" + StandardEntitlement.TASK_EXECUTE + "')")
+    public List<ProvisioningReport> pull(final CSVPullSpec spec, final InputStream csv) {
+        AnyType anyType = anyTypeDAO.find(spec.getAnyTypeKey());
+        if (anyType == null) {
+            throw new NotFoundException("AnyType '" + spec.getAnyTypeKey() + "'");
+        }
+
+        if (realmDAO.findByFullPath(spec.getDestinationRealm()) == null) {
+            throw new NotFoundException("Realm " + spec.getDestinationRealm());
+        }
+
+        PullTaskTO pullTask = new PullTaskTO();
+        pullTask.setDestinationRealm(spec.getDestinationRealm());
+        pullTask.setRemediation(spec.isRemediation());
+        pullTask.setMatchingRule(spec.getMatchingRule());
+        pullTask.setUnmatchingRule(spec.getUnmatchingRule());
+        pullTask.getActions().addAll(spec.getActions());
+
+        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());
+                }
+            });
+
+            if (!columns.contains(spec.getKeyColumn())) {
+                throw new NotFoundException("Key column '" + spec.getKeyColumn() + "'");
+            }
+
+            return streamPullExecutor.pull(
+                    anyType,
+                    spec.getKeyColumn(),
+                    columns,
+                    spec.getConflictResolutionAction(),
+                    spec.getPullCorrelationRule(),
+                    new StreamConnector(spec.getKeyColumn(), spec.getArrayElementSeparator(), reader, null),
+                    pullTask);
+        } catch (NotFoundException e) {
+            throw e;
+        } catch (Exception e) {
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Reconciliation);
+            sce.getElements().add(e.getMessage());
+            throw sce;
+        }
+    }
+
     @Override
     protected EntityTO resolveReference(final Method method, final Object... os)
             throws UnresolvedReferenceException {
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
index 7d231f9..8726897 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
@@ -59,6 +59,7 @@ import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecu
 import org.apache.syncope.core.persistence.api.dao.ConfDAO;
 import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
 import org.apache.syncope.core.persistence.api.dao.NotificationDAO;
+import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
 import org.apache.syncope.core.provisioning.api.notification.NotificationJobDelegate;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
 import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2;
@@ -235,13 +236,12 @@ public class TaskLogic extends AbstractExecutableLogic<TaskTO> {
         switch (taskUtil.getType()) {
             case PROPAGATION:
                 PropagationTaskTO taskTO = binder.<PropagationTaskTO>getTaskTO(task, taskUtil, false);
-                PropagationTaskInfo taskInfo = new PropagationTaskInfo();
+                PropagationTaskInfo taskInfo = new PropagationTaskInfo(((PropagationTask) task).getResource());
                 taskInfo.setKey(taskTO.getKey());
                 taskInfo.setOperation(taskTO.getOperation());
                 taskInfo.setConnObjectKey(taskTO.getConnObjectKey());
                 taskInfo.setOldConnObjectKey(taskTO.getOldConnObjectKey());
                 taskInfo.setAttributes(taskTO.getAttributes());
-                taskInfo.setResource(taskTO.getResource());
                 taskInfo.setObjectClassName(taskTO.getObjectClassName());
                 taskInfo.setAnyTypeKind(taskTO.getAnyTypeKind());
                 taskInfo.setAnyType(taskTO.getAnyType());
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
index 56a01b4..40d376e 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
@@ -105,18 +105,20 @@ public class UserLogic extends AbstractAnyLogic<UserTO, UserPatch> {
     @Override
     public Pair<Integer, List<UserTO>> search(
             final SearchCond searchCond,
-            final int page, final int size, final List<OrderByClause> orderBy,
+            final int page,
+            final int size,
+            final List<OrderByClause> orderBy,
             final String realm,
             final boolean details) {
 
-        int count = searchDAO.count(RealmUtils.getEffective(
-                AuthContextUtils.getAuthorizations().get(StandardEntitlement.USER_SEARCH), realm),
-                searchCond == null ? userDAO.getAllMatchingCond() : searchCond, AnyTypeKind.USER);
+        Set<String> adminRealms = RealmUtils.getEffective(
+                AuthContextUtils.getAuthorizations().get(StandardEntitlement.USER_SEARCH), realm);
 
-        List<User> matching = searchDAO.search(RealmUtils.getEffective(
-                AuthContextUtils.getAuthorizations().get(StandardEntitlement.USER_SEARCH), realm),
-                searchCond == null ? userDAO.getAllMatchingCond() : searchCond,
-                page, size, orderBy, AnyTypeKind.USER);
+        SearchCond effectiveCond = searchCond == null ? userDAO.getAllMatchingCond() : searchCond;
+
+        int count = searchDAO.count(adminRealms, effectiveCond, AnyTypeKind.USER);
+
+        List<User> matching = searchDAO.search(adminRealms, effectiveCond, page, size, orderBy, AnyTypeKind.USER);
         List<UserTO> result = matching.stream().
                 map(user -> binder.returnUserTO(binder.getUserTO(user, details))).
                 collect(Collectors.toList());
diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/AbstractTest.java b/core/logic/src/test/java/org/apache/syncope/core/logic/AbstractTest.java
similarity index 95%
copy from core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/AbstractTest.java
copy to core/logic/src/test/java/org/apache/syncope/core/logic/AbstractTest.java
index ad660e7..90885bb 100644
--- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/AbstractTest.java
+++ b/core/logic/src/test/java/org/apache/syncope/core/logic/AbstractTest.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.provisioning.java;
+package org.apache.syncope.core.logic;
 
 import javax.persistence.EntityManager;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
@@ -28,7 +28,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
     "classpath:persistenceTest.xml",
     "classpath:provisioningContext.xml",
     "classpath:workflowContext.xml",
-    "classpath:provisioningTest.xml"
+    "classpath:logicTest.xml"
 })
 public abstract class AbstractTest {
 
diff --git a/core/logic/src/test/java/org/apache/syncope/core/logic/ReconciliationLogicTest.java b/core/logic/src/test/java/org/apache/syncope/core/logic/ReconciliationLogicTest.java
new file mode 100644
index 0000000..a1167e4
--- /dev/null
+++ b/core/logic/src/test/java/org/apache/syncope/core/logic/ReconciliationLogicTest.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.logic;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.fasterxml.jackson.databind.MappingIterator;
+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.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.common.rest.api.beans.CSVPullSpec;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
+import org.apache.syncope.common.rest.api.beans.CSVPushSpec;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional("Master")
+public class ReconciliationLogicTest extends AbstractTest {
+
+    @Autowired
+    private ReconciliationLogic reconciliationLogic;
+
+    @Autowired
+    private UserLogic userLogic;
+
+    @Test
+    public void pullFromCSV() {
+        CSVPullSpec spec = new CSVPullSpec.Builder(AnyTypeKind.USER.name(), "username").build();
+        InputStream csv = getClass().getResourceAsStream("/test1.csv");
+
+        List<ProvisioningReport> results = AuthContextUtils.execWithAuthContext(SyncopeConstants.MASTER_DOMAIN, () -> {
+            return reconciliationLogic.pull(spec, csv);
+        });
+        assertEquals(2, results.size());
+
+        assertEquals(AnyTypeKind.USER.name(), results.get(0).getAnyType());
+        assertNotNull(results.get(0).getKey());
+        assertEquals("donizetti", results.get(0).getName());
+        assertEquals("donizetti", results.get(0).getUidValue());
+        assertEquals(ResourceOperation.CREATE, results.get(0).getOperation());
+        assertEquals(ProvisioningReport.Status.SUCCESS, results.get(0).getStatus());
+
+        AuthContextUtils.execWithAuthContext(SyncopeConstants.MASTER_DOMAIN, () -> {
+            UserTO donizetti = userLogic.read(results.get(0).getKey());
+            assertNotNull(donizetti);
+            assertEquals("Gaetano", donizetti.getPlainAttr("firstname").get().getValues().get(0));
+            assertEquals(1, donizetti.getPlainAttr("loginDate").get().getValues().size());
+
+            UserTO cimarosa = userLogic.read(results.get(1).getKey());
+            assertNotNull(cimarosa);
+            assertEquals("Domenico Cimarosa", cimarosa.getPlainAttr("fullname").get().getValues().get(0));
+            assertEquals(2, cimarosa.getPlainAttr("loginDate").get().getValues().size());
+
+            return null;
+        });
+    }
+
+    @Test
+    public void pushToCSV() throws IOException {
+        Pair<Integer, List<UserTO>> search = AuthContextUtils.execWithAuthContext(SyncopeConstants.MASTER_DOMAIN,
+                () -> userLogic.search(null, 1, 100, Collections.emptyList(), SyncopeConstants.ROOT_REALM, false));
+        assertNotNull(search);
+
+        CSVPushSpec spec = new CSVPushSpec.Builder(AnyTypeKind.USER.name()).
+                ignorePagination(true).
+                field("username").
+                field("status").
+                plainAttr("firstname").
+                plainAttr("surname").
+                plainAttr("email").
+                plainAttr("loginDate").
+                build();
+
+        PipedInputStream in = new PipedInputStream();
+        PipedOutputStream os = new PipedOutputStream(in);
+
+        List<ProvisioningReport> results = AuthContextUtils.execWithAuthContext(SyncopeConstants.MASTER_DOMAIN, () -> {
+            return reconciliationLogic.push(null, 1, 1, Collections.emptyList(), SyncopeConstants.ROOT_REALM, spec, os);
+        });
+        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);
+
+        for (int i = 0; i < results.size() && reader.hasNext(); i++) {
+            Map<String, String> row = reader.next();
+
+            assertEquals(results.get(i).getName(), row.get("username"));
+            assertEquals(search.getRight().stream().filter(user -> row.get("username").equals(user.getUsername())).
+                    findFirst().get().getStatus(),
+                    row.get("status"));
+
+            switch (row.get("username")) {
+                case "rossini":
+                    assertEquals(spec.getNullValue(), row.get("email"));
+                    assertTrue(row.get("loginDate").contains(spec.getArrayElementSeparator()));
+                    break;
+
+                case "verdi":
+                    assertEquals("verdi@syncope.org", row.get("email"));
+                    assertEquals(spec.getNullValue(), row.get("loginDate"));
+                    break;
+
+                case "bellini":
+                    assertEquals(spec.getNullValue(), row.get("email"));
+                    assertFalse(row.get("loginDate").contains(spec.getArrayElementSeparator()));
+                    break;
+
+                default:
+                    break;
+            }
+        }
+    }
+}
diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/TestInitializer.java b/core/logic/src/test/java/org/apache/syncope/core/logic/TestInitializer.java
similarity index 96%
copy from core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/TestInitializer.java
copy to core/logic/src/test/java/org/apache/syncope/core/logic/TestInitializer.java
index 98392af..8ed44c2 100644
--- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/TestInitializer.java
+++ b/core/logic/src/test/java/org/apache/syncope/core/logic/TestInitializer.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.provisioning.java;
+package org.apache.syncope.core.logic;
 
 import org.apache.syncope.core.persistence.api.content.ContentLoader;
 import org.springframework.beans.factory.InitializingBean;
@@ -33,5 +33,4 @@ public class TestInitializer implements InitializingBean {
     public void afterPropertiesSet() throws Exception {
         contentLoader.load();
     }
-
 }
diff --git a/core/logic/src/test/resources/logicTest.xml b/core/logic/src/test/resources/logicTest.xml
new file mode 100644
index 0000000..af40284
--- /dev/null
+++ b/core/logic/src/test/resources/logicTest.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                           http://www.springframework.org/schema/beans/spring-beans.xsd">
+    
+  <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
+    <property name="locations">
+      <list>
+        <value>classpath:persistence.properties</value>
+        <value>classpath:domains/*.properties</value>
+        <value>classpath:security.properties</value>
+        <value>classpath:connid.properties</value>
+        <value>classpath:mail.properties</value>
+        <value>classpath:workflow.properties</value>
+        <value>classpath:provisioning.properties</value>
+        <value>classpath:logic.properties</value>
+      </list>
+    </property>
+    <property name="ignoreResourceNotFound" value="true"/>
+    <property name="ignoreUnresolvablePlaceholders" value="true"/>
+  </bean>
+
+  <bean id="jwtIssuer" class="java.lang.String">
+    <constructor-arg value="${jwtIssuer}"/>
+  </bean>
+  <bean id="jwsKey" class="java.lang.String">
+    <constructor-arg value="ZW7pRixehFuNUtnY5Se47IemgMryTzazPPJ9CGX5LTCmsOJpOgHAQEuPQeV9A28f"/>
+  </bean>
+  <bean id="accessTokenJwsSignatureVerifier"
+        class="org.apache.syncope.core.spring.security.jws.AccessTokenJwsSignatureVerifier">
+    <property name="jwsAlgorithm" value="${jwsAlgorithm}"/>
+    <property name="jwsKey" value="ZW7pRixehFuNUtnY5Se47IemgMryTzazPPJ9CGX5LTCmsOJpOgHAQEuPQeV9A28f"/>
+  </bean>
+  <bean id="accessTokenJwsSignatureProvider"
+        class="org.apache.syncope.core.spring.security.jws.AccessTokenJwsSignatureProvider">
+    <property name="jwsAlgorithm" value="${jwsAlgorithm}"/>
+    <property name="jwsKey" value="ZW7pRixehFuNUtnY5Se47IemgMryTzazPPJ9CGX5LTCmsOJpOgHAQEuPQeV9A28f"/>
+  </bean>
+  <bean id="credentialChecker" class="org.apache.syncope.core.spring.security.DefaultCredentialChecker">
+    <constructor-arg value="ZW7pRixehFuNUtnY5Se47IemgMryTzazPPJ9CGX5LTCmsOJpOgHAQEuPQeV9A28f" index="0"/>
+    <constructor-arg value="DE088591C00CC98B36F5ADAAF7DA2B004CF7F2FE7BBB45B766B6409876E2F3DB13C7905C6AA59464" index="1"/>
+    <constructor-arg value="anonymousKey" index="2"/>
+  </bean>
+  
+  <import resource="logicContext.xml"/>
+</beans>
diff --git a/core/logic/src/test/resources/test1.csv b/core/logic/src/test/resources/test1.csv
new file mode 100644
index 0000000..0ea7355
--- /dev/null
+++ b/core/logic/src/test/resources/test1.csv
@@ -0,0 +1,3 @@
+"username","email","surname","firstname","fullname","userId","loginDate"
+"donizetti","donizetti@apache.org","Donizetti","Gaetano","Gaetano Donizetti","donizetti@apache.org","2019-12-24"
+"cimarosa","cimarosa@apache.org","Cimarosa","Domenico","Domenico Cimarosa","cimarosa@apache.org","2018-11-21;2018-12-24"
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MyJPAJSONPlainSchemaDAO.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MyJPAJSONPlainSchemaDAO.java
index 07c9969..81bce44 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MyJPAJSONPlainSchemaDAO.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MyJPAJSONPlainSchemaDAO.java
@@ -30,6 +30,6 @@ public class MyJPAJSONPlainSchemaDAO extends AbstractJPAJSONPlainSchemaDAO {
                 "SELECT COUNT(id) FROM " + new SearchSupport(getAnyTypeKind(reference)).field().name
                 + " WHERE JSON_CONTAINS(plainAttrs, '[{\"schema\":\"" + schema.getKey() + "\"}]')");
 
-        return (long) query.getSingleResult() > 0;
+        return ((Number) query.getSingleResult()).intValue() > 0;
     }
 }
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONPlainSchemaDAO.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONPlainSchemaDAO.java
index 77019a7..c4d27d6 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONPlainSchemaDAO.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONPlainSchemaDAO.java
@@ -30,6 +30,6 @@ public class PGJPAJSONPlainSchemaDAO extends AbstractJPAJSONPlainSchemaDAO {
                 "SELECT COUNT(id) FROM " + new SearchSupport(getAnyTypeKind(reference)).field().name
                 + " WHERE plainAttrs @> '[{\"schema\":\"" + schema.getKey() + "\"}]'::jsonb");
 
-        return (long) query.getSingleResult() > 0;
+        return ((Number) query.getSingleResult()).intValue() > 0;
     }
 }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnyDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnyDAO.java
index 972d1f2..07a4f42 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnyDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnyDAO.java
@@ -486,38 +486,29 @@ public abstract class AbstractAnyDAO<A extends Any<?>> extends AbstractDAO<A> im
         // schemas given by type extensions
         Map<Group, List<? extends AnyTypeClass>> typeExtensionClasses = new HashMap<>();
         if (any instanceof User) {
-            ((User) any).getMemberships().forEach(memb -> {
-                memb.getRightEnd().getTypeExtensions().forEach(typeExtension -> {
-                    typeExtensionClasses.put(memb.getRightEnd(), typeExtension.getAuxClasses());
-                });
-            });
+            ((User) any).getMemberships().forEach(memb -> memb.getRightEnd().getTypeExtensions().
+                    forEach(typeExt -> typeExtensionClasses.put(memb.getRightEnd(), typeExt.getAuxClasses())));
         } else if (any instanceof AnyObject) {
-            ((AnyObject) any).getMemberships().forEach(memb -> {
-                memb.getRightEnd().getTypeExtensions().stream().
-                        filter(typeExtension -> any.getType().equals(typeExtension.getAnyType())).
-                        forEachOrdered((typeExtension) -> {
-                            typeExtensionClasses.put(memb.getRightEnd(), typeExtension.getAuxClasses());
-                        });
-            });
+            ((AnyObject) any).getMemberships().forEach(memb -> memb.getRightEnd().getTypeExtensions().stream().
+                    filter(typeExt -> any.getType().equals(typeExt.getAnyType())).
+                    forEach(typeExt -> typeExtensionClasses.put(memb.getRightEnd(), typeExt.getAuxClasses())));
         }
 
         typeExtensionClasses.entrySet().stream().map(entry -> {
             result.getForMemberships().put(entry.getKey(), new HashSet<>());
             return entry;
-        }).forEachOrdered((entry) -> {
-            entry.getValue().forEach(typeClass -> {
-                if (reference.equals(PlainSchema.class)) {
-                    result.getForMemberships().get(entry.getKey()).
-                            addAll((Collection<? extends S>) typeClass.getPlainSchemas());
-                } else if (reference.equals(DerSchema.class)) {
-                    result.getForMemberships().get(entry.getKey()).
-                            addAll((Collection<? extends S>) typeClass.getDerSchemas());
-                } else if (reference.equals(VirSchema.class)) {
-                    result.getForMemberships().get(entry.getKey()).
-                            addAll((Collection<? extends S>) typeClass.getVirSchemas());
-                }
-            });
-        });
+        }).forEach(entry -> entry.getValue().forEach(typeClass -> {
+            if (reference.equals(PlainSchema.class)) {
+                result.getForMemberships().get(entry.getKey()).
+                        addAll((Collection<? extends S>) typeClass.getPlainSchemas());
+            } else if (reference.equals(DerSchema.class)) {
+                result.getForMemberships().get(entry.getKey()).
+                        addAll((Collection<? extends S>) typeClass.getDerSchemas());
+            } else if (reference.equals(VirSchema.class)) {
+                result.getForMemberships().get(entry.getKey()).
+                        addAll((Collection<? extends S>) typeClass.getVirSchemas());
+            }
+        }));
 
         return result;
     }
@@ -549,7 +540,7 @@ public abstract class AbstractAnyDAO<A extends Any<?>> extends AbstractDAO<A> im
         query.getResultList().stream().map(resultKey -> resultKey instanceof Object[]
                 ? (String) ((Object[]) resultKey)[0]
                 : ((String) resultKey)).
-                forEachOrdered((actualKey) -> {
+                forEach((actualKey) -> {
                     DynRealm dynRealm = dynRealmDAO.find(actualKey.toString());
                     if (dynRealm == null) {
                         LOG.error("Could not find dynRealm with id {}, even though returned by the native query",
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAPlainSchemaDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAPlainSchemaDAO.java
index 066f938..619496d 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAPlainSchemaDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAPlainSchemaDAO.java
@@ -27,7 +27,6 @@ import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
 import org.apache.syncope.core.persistence.api.dao.PlainAttrDAO;
 import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
 import org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
-import org.apache.syncope.core.persistence.api.entity.AnyUtils;
 import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.PlainAttr;
@@ -118,7 +117,7 @@ public class JPAPlainSchemaDAO extends AbstractDAO<PlainSchema> implements Plain
                 + ".schema_id WHERE " + JPAPlainSchema.TABLE + ".id = ?1");
         query.setParameter(1, schema.getKey());
 
-        return (long) query.getSingleResult() > 0;
+        return ((Number) query.getSingleResult()).intValue() > 0;
     }
 
     @Override
@@ -128,11 +127,8 @@ public class JPAPlainSchemaDAO extends AbstractDAO<PlainSchema> implements Plain
 
     protected void deleteAttrs(final PlainSchema schema) {
         for (AnyTypeKind anyTypeKind : AnyTypeKind.values()) {
-            AnyUtils anyUtils = anyUtilsFactory.getInstance(anyTypeKind);
-
-            findAttrs(schema, anyUtils.plainAttrClass()).forEach(attr -> {
-                plainAttrDAO.delete(attr);
-            });
+            findAttrs(schema, anyUtilsFactory.getInstance(anyTypeKind).plainAttrClass()).
+                    forEach(attr -> plainAttrDAO.delete(attr));
         }
     }
 
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAExternalResource.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAExternalResource.java
index 5af5c09..5a0bf36 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAExternalResource.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAExternalResource.java
@@ -126,16 +126,16 @@ public class JPAExternalResource extends AbstractProvidedKeyEntity implements Ex
     @NotNull
     private TraceLevel provisioningTraceLevel = TraceLevel.FAILURES;
 
-    @ManyToOne(fetch = FetchType.LAZY)
+    @ManyToOne(fetch = FetchType.EAGER)
     private JPAPasswordPolicy passwordPolicy;
 
-    @ManyToOne(fetch = FetchType.LAZY)
+    @ManyToOne(fetch = FetchType.EAGER)
     private JPAAccountPolicy accountPolicy;
 
-    @ManyToOne(fetch = FetchType.LAZY)
+    @ManyToOne(fetch = FetchType.EAGER)
     private JPAPullPolicy pullPolicy;
 
-    @ManyToOne(fetch = FetchType.LAZY)
+    @ManyToOne(fetch = FetchType.EAGER)
     private JPAPushPolicy pushPolicy;
 
     /**
diff --git a/core/provisioning-api/pom.xml b/core/provisioning-api/pom.xml
index 5069f90..93d3e81 100644
--- a/core/provisioning-api/pom.xml
+++ b/core/provisioning-api/pom.xml
@@ -39,6 +39,11 @@ under the License.
 
   <dependencies>
     <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-jexl3</artifactId>
+    </dependency>
+
+    <dependency>
       <groupId>com.fasterxml.jackson.core</groupId>
       <artifactId>jackson-core</artifactId>
     </dependency>
@@ -50,12 +55,16 @@ under the License.
       <groupId>com.fasterxml.jackson.module</groupId>
       <artifactId>jackson-module-afterburner</artifactId>
     </dependency>
-    
+
     <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-context</artifactId>
     </dependency>
-    
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-tx</artifactId>
+    </dependency>
+
     <dependency>
       <groupId>org.quartz-scheduler</groupId>
       <artifactId>quartz</artifactId>
@@ -66,6 +75,22 @@ under the License.
       <artifactId>syncope-core-persistence-api</artifactId>
       <version>${project.version}</version>
     </dependency>
+
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-junit-jupiter</artifactId>
+      <scope>test</scope>
+    </dependency>    
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/Connector.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/Connector.java
index 09de13c..1c11784 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/Connector.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/Connector.java
@@ -21,6 +21,7 @@ package org.apache.syncope.core.provisioning.api;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.entity.ConnInstance;
 import org.apache.syncope.core.provisioning.api.pushpull.ReconFilterBuilder;
@@ -29,11 +30,15 @@ import org.identityconnectors.framework.common.objects.ConnectorObject;
 import org.identityconnectors.framework.common.objects.ObjectClass;
 import org.identityconnectors.framework.common.objects.ObjectClassInfo;
 import org.identityconnectors.framework.common.objects.OperationOptions;
+import org.identityconnectors.framework.common.objects.OperationOptionsBuilder;
 import org.identityconnectors.framework.common.objects.SyncResultsHandler;
 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.common.objects.SearchResult;
+import org.identityconnectors.framework.common.objects.SortKey;
+import org.identityconnectors.framework.common.objects.SyncDeltaBuilder;
+import org.identityconnectors.framework.common.objects.SyncDeltaType;
 import org.identityconnectors.framework.spi.SearchResultsHandler;
 
 /**
@@ -104,7 +109,9 @@ public interface Connector {
      * @param handler to be used to handle deltas.
      * @param options ConnId's OperationOptions.
      */
-    void fullReconciliation(ObjectClass objectClass, SyncResultsHandler handler, OperationOptions options);
+    default void fullReconciliation(ObjectClass objectClass, SyncResultsHandler handler, OperationOptions options) {
+        filteredReconciliation(objectClass, null, handler, options);
+    }
 
     /**
      * Fetches remote objects (for use during filtered reconciliation).
@@ -114,11 +121,36 @@ public interface Connector {
      * @param handler to be used to handle deltas.
      * @param options ConnId's OperationOptions.
      */
-    void filteredReconciliation(
+    default void filteredReconciliation(
             ObjectClass objectClass,
             ReconFilterBuilder filterBuilder,
             SyncResultsHandler handler,
-            OperationOptions options);
+            OperationOptions options) {
+
+        Filter filter = null;
+        OperationOptions actualOptions = options;
+        if (filterBuilder != null) {
+            filter = filterBuilder.build();
+            actualOptions = filterBuilder.build(actualOptions);
+        }
+
+        search(objectClass, filter, new SearchResultsHandler() {
+
+            @Override
+            public void handleResult(final SearchResult result) {
+                // nothing to do
+            }
+
+            @Override
+            public boolean handle(final ConnectorObject object) {
+                return handler.handle(new SyncDeltaBuilder().
+                        setObject(object).
+                        setDeltaType(SyncDeltaType.CREATE_OR_UPDATE).
+                        setToken(new SyncToken("")).
+                        build());
+            }
+        }, actualOptions);
+    }
 
     /**
      * Sync remote objects from a connector instance.
@@ -182,14 +214,27 @@ public interface Connector {
      * @param options ConnId's OperationOptions
      * @return search result
      */
-    SearchResult search(
+    default SearchResult search(
             ObjectClass objectClass,
             Filter filter,
             SearchResultsHandler handler,
             int pageSize,
             String pagedResultsCookie,
             List<OrderByClause> orderBy,
-            OperationOptions options);
+            OperationOptions options) {
+
+        OperationOptionsBuilder builder = new OperationOptionsBuilder().setPageSize(pageSize).setPagedResultsOffset(-1);
+        if (pagedResultsCookie != null) {
+            builder.setPagedResultsCookie(pagedResultsCookie);
+        }
+        builder.setSortKeys(orderBy.stream().
+                map(clause -> new SortKey(clause.getField(), clause.getDirection() == OrderByClause.Direction.ASC)).
+                collect(Collectors.toList()));
+
+        builder.setAttributesToGet(options.getAttributesToGet());
+
+        return search(objectClass, filter, handler, builder.build());
+    }
 
     /**
      * Builds metadata description of ConnId {@link ObjectClass}.
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/IntAttrNameParser.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/IntAttrNameParser.java
similarity index 98%
rename from core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/IntAttrNameParser.java
rename to core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/IntAttrNameParser.java
index ab1dc4d..fad50f4 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/IntAttrNameParser.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/IntAttrNameParser.java
@@ -16,10 +16,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.provisioning.java;
+package org.apache.syncope.core.provisioning.api;
 
 import java.text.ParseException;
-import org.apache.syncope.core.provisioning.api.IntAttrName;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import org.apache.commons.lang3.tuple.Pair;
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/UserProvisioningManager.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/UserProvisioningManager.java
index c9aa60c..9256a64 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/UserProvisioningManager.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/UserProvisioningManager.java
@@ -26,7 +26,7 @@ import org.apache.syncope.common.lib.patch.StatusPatch;
 import org.apache.syncope.common.lib.patch.UserPatch;
 import org.apache.syncope.common.lib.to.PropagationStatus;
 import org.apache.syncope.common.lib.to.UserTO;
-import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 
 public interface UserProvisioningManager extends ProvisioningManager<UserTO, UserPatch> {
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/jexl/ClassFreeUberspect.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/jexl/ClassFreeUberspect.java
similarity index 96%
rename from core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/jexl/ClassFreeUberspect.java
rename to core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/jexl/ClassFreeUberspect.java
index aec38b8..e218836 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/jexl/ClassFreeUberspect.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/jexl/ClassFreeUberspect.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.provisioning.java.jexl;
+package org.apache.syncope.core.provisioning.api.jexl;
 
 import org.apache.commons.jexl3.internal.introspection.Uberspect;
 import org.apache.commons.jexl3.introspection.JexlMethod;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/jexl/EmptyClassLoader.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/jexl/EmptyClassLoader.java
similarity index 96%
rename from core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/jexl/EmptyClassLoader.java
rename to core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/jexl/EmptyClassLoader.java
index 037113e..2457d7f 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/jexl/EmptyClassLoader.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/jexl/EmptyClassLoader.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.provisioning.java.jexl;
+package org.apache.syncope.core.provisioning.api.jexl;
 
 /**
  * A class loader that will throw {@link ClassNotFoundException} for every class name.
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/jexl/JexlUtils.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/jexl/JexlUtils.java
similarity index 93%
rename from core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/jexl/JexlUtils.java
rename to core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/jexl/JexlUtils.java
index d5cee96..6793b61 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/jexl/JexlUtils.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/jexl/JexlUtils.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.provisioning.java.jexl;
+package org.apache.syncope.core.provisioning.api.jexl;
 
 import java.beans.IntrospectionException;
 import java.beans.Introspector;
@@ -44,7 +44,6 @@ import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.to.AttrTO;
 import org.apache.syncope.common.lib.to.RealmTO;
-import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.apache.syncope.core.provisioning.api.utils.FormatUtils;
 import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.DerSchema;
@@ -227,19 +226,24 @@ public final class JexlUtils {
         });
     }
 
-    public static void addDerAttrsToContext(final Any<?> any, final JexlContext jexlContext) {
-        Map<DerSchema, String> derAttrs =
-                ApplicationContextProvider.getBeanFactory().getBean(DerAttrHandler.class).getValues(any);
+    public static void addDerAttrsToContext(
+            final Any<?> any,
+            final DerAttrHandler derAttrHandler,
+            final JexlContext jexlContext) {
 
-        derAttrs.forEach((schema, value) -> {
-            jexlContext.set(schema.getKey(), value);
-        });
+        Map<DerSchema, String> derAttrs = derAttrHandler.getValues(any);
+
+        derAttrs.forEach((schema, value) -> jexlContext.set(schema.getKey(), value));
     }
 
-    public static boolean evaluateMandatoryCondition(final String mandatoryCondition, final Any<?> any) {
+    public static boolean evaluateMandatoryCondition(
+            final String mandatoryCondition,
+            final Any<?> any,
+            final DerAttrHandler derAttrHandler) {
+
         JexlContext jexlContext = new MapContext();
         addPlainAttrsToContext(any.getPlainAttrs(), jexlContext);
-        addDerAttrsToContext(any, jexlContext);
+        addDerAttrsToContext(any, derAttrHandler, jexlContext);
 
         return Boolean.parseBoolean(evaluate(mandatoryCondition, jexlContext));
     }
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/jexl/SyncopeJexlFunctions.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/jexl/SyncopeJexlFunctions.java
similarity index 97%
rename from core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/jexl/SyncopeJexlFunctions.java
rename to core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/jexl/SyncopeJexlFunctions.java
index 5cf632a..9ec0b05 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/jexl/SyncopeJexlFunctions.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/jexl/SyncopeJexlFunctions.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.provisioning.java.jexl;
+package org.apache.syncope.core.provisioning.api.jexl;
 
 import java.util.Arrays;
 import java.util.Collections;
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationManager.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationManager.java
index 0378950..9c8d6cf 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationManager.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationManager.java
@@ -20,13 +20,22 @@ package org.apache.syncope.core.provisioning.api.propagation;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.Set;
+import java.util.stream.Stream;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.patch.UserPatch;
 import org.apache.syncope.common.lib.to.AttrTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.provisioning.api.PropagationByResource;
 import org.apache.syncope.core.persistence.api.entity.Realm;
+import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.resource.Item;
+import org.apache.syncope.core.persistence.api.entity.resource.Provision;
+import org.apache.syncope.core.provisioning.api.DerAttrHandler;
 import org.apache.syncope.core.provisioning.api.UserWorkflowResult;
+import org.identityconnectors.framework.common.objects.Attribute;
 
 public interface PropagationManager {
 
@@ -147,6 +156,16 @@ public interface PropagationManager {
             PropagationByResource<Pair<String, String>> propByLinkedAccount,
             Collection<String> noPropResourceKeys);
 
+    PropagationTaskInfo newTask(
+            DerAttrHandler derAttrHandler,
+            Any<?> any,
+            ExternalResource resource,
+            ResourceOperation operation,
+            Provision provision,
+            boolean deleteOnResource,
+            Stream<? extends Item> mappingItems,
+            Pair<String, Set<Attribute>> preparedAttrs);
+
     /**
      * Create the needed tasks for the realm for each resource associated, unless in {@code noPropResourceKeys}.
      *
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationTaskInfo.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationTaskInfo.java
index 322a163..c3b3e44 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationTaskInfo.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationTaskInfo.java
@@ -19,22 +19,57 @@
 package org.apache.syncope.core.provisioning.api.propagation;
 
 import java.util.Optional;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.apache.syncope.common.lib.to.PropagationTaskTO;
+import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
+import org.apache.syncope.core.provisioning.api.Connector;
 import org.identityconnectors.framework.common.objects.ConnectorObject;
 
 public class PropagationTaskInfo extends PropagationTaskTO {
 
     private static final long serialVersionUID = -2879861567335503099L;
 
+    private final ExternalResource externalResource;
+
+    private Connector connector;
+
     /**
      * Object on External Resource before propagation takes place.
      *
      * null: beforeObj was not attempted to read
-     * not null, but not present: beforeObj was attempted to read, but not found
+     * not null but not present: beforeObj was attempted to read, but not found
      * not null and present: beforeObj value is available
      */
     private Optional<ConnectorObject> beforeObj;
 
+    public PropagationTaskInfo(final ExternalResource externalResource) {
+        super();
+        this.externalResource = externalResource;
+    }
+
+    public Connector getConnector() {
+        return connector;
+    }
+
+    public void setConnector(final Connector connector) {
+        this.connector = connector;
+    }
+
+    public ExternalResource getExternalResource() {
+        return externalResource;
+    }
+
+    @Override
+    public String getResource() {
+        return externalResource.getKey();
+    }
+
+    @Override
+    public void setResource(final String resource) {
+        throw new IllegalArgumentException("Cannot set ExternalResource on " + getClass().getName());
+    }
+
     public Optional<ConnectorObject> getBeforeObj() {
         return beforeObj;
     }
@@ -42,4 +77,32 @@ public class PropagationTaskInfo extends PropagationTaskTO {
     public void setBeforeObj(final Optional<ConnectorObject> beforeObj) {
         this.beforeObj = beforeObj;
     }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().
+                appendSuper(super.hashCode()).
+                append(externalResource.getKey()).
+                append(beforeObj).
+                build();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final PropagationTaskInfo other = (PropagationTaskInfo) obj;
+        return new EqualsBuilder().
+                appendSuper(super.equals(obj)).
+                append(externalResource.getKey(), other.externalResource.getKey()).
+                append(beforeObj, other.beforeObj).
+                build();
+    }
 }
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/ProvisioningProfile.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/ProvisioningProfile.java
index 9aa3050..25f61b6 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/ProvisioningProfile.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/ProvisioningProfile.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.provisioning.api.pushpull;
 
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import java.util.ArrayList;
 import java.util.List;
 import org.apache.syncope.common.lib.types.ConflictResolutionAction;
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/PullActions.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/PullActions.java
index ad95fde..e5688e1 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/PullActions.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/PullActions.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.provisioning.api.pushpull;
 
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import java.util.Collections;
 import java.util.Set;
 import org.apache.syncope.common.lib.patch.AnyPatch;
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/PushActions.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/PushActions.java
index 1642b0e..642d591 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/PushActions.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/PushActions.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.provisioning.api.pushpull;
 
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.core.persistence.api.entity.Entity;
 import org.quartz.JobExecutionException;
 
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePullExecutor.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePullExecutor.java
index da1b994..ec9cd8b 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePullExecutor.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePullExecutor.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.provisioning.api.pushpull;
 
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import java.util.List;
 import org.apache.syncope.common.lib.to.PullTaskTO;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePushExecutor.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePushExecutor.java
index 8053a65..a9a7ce7 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePushExecutor.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePushExecutor.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.provisioning.api.pushpull;
 
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import java.util.List;
 import org.apache.syncope.common.lib.to.PushTaskTO;
 import org.apache.syncope.core.persistence.api.entity.Any;
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/stream/StreamConnector.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/stream/StreamConnector.java
new file mode 100644
index 0000000..0592db9
--- /dev/null
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/stream/StreamConnector.java
@@ -0,0 +1,209 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.api.pushpull.stream;
+
+import com.fasterxml.jackson.databind.MappingIterator;
+import com.fasterxml.jackson.databind.SequenceWriter;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+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.core.persistence.api.entity.ConnInstance;
+import org.apache.syncope.core.provisioning.api.Connector;
+import org.identityconnectors.framework.common.objects.Attribute;
+import org.identityconnectors.framework.common.objects.AttributeBuilder;
+import org.identityconnectors.framework.common.objects.AttributeUtil;
+import org.identityconnectors.framework.common.objects.ConnectorObject;
+import org.identityconnectors.framework.common.objects.ConnectorObjectBuilder;
+import org.identityconnectors.framework.common.objects.ObjectClass;
+import org.identityconnectors.framework.common.objects.ObjectClassInfo;
+import org.identityconnectors.framework.common.objects.OperationOptions;
+import org.identityconnectors.framework.common.objects.SearchResult;
+import org.identityconnectors.framework.common.objects.SyncResultsHandler;
+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.springframework.util.CollectionUtils;
+
+public class StreamConnector implements Connector {
+
+    private final String keyColumn;
+
+    private final String arrayElementsSeparator;
+
+    private final MappingIterator<Map<String, String>> reader;
+
+    private final SequenceWriter writer;
+
+    public StreamConnector(
+            final String keyColumn,
+            final String arrayElementsSeparator,
+            final MappingIterator<Map<String, String>> reader,
+            final SequenceWriter writer) {
+
+        this.keyColumn = keyColumn;
+        this.arrayElementsSeparator = arrayElementsSeparator;
+        this.reader = reader;
+        this.writer = writer;
+    }
+
+    @Override
+    public Uid authenticate(final String username, final String password, final OperationOptions options) {
+        return null;
+    }
+
+    @Override
+    public ConnInstance getConnInstance() {
+        return null;
+    }
+
+    @Override
+    public Uid create(
+            final ObjectClass objectClass,
+            final Set<Attribute> attrs,
+            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);
+            }
+            propagationAttempted.set(Boolean.TRUE);
+        }
+        return null;
+    }
+
+    @Override
+    public Uid update(
+            final ObjectClass objectClass,
+            final Uid uid,
+            final Set<Attribute> attrs,
+            final OperationOptions options,
+            final AtomicReference<Boolean> propagationAttempted) {
+
+        return null;
+    }
+
+    @Override
+    public void delete(
+            final ObjectClass objectClass,
+            final Uid uid,
+            final OperationOptions options,
+            final AtomicReference<Boolean> propagationAttempted) {
+
+        // nothing to do
+    }
+
+    @Override
+    public void sync(
+            final ObjectClass objectClass,
+            final SyncToken token,
+            final SyncResultsHandler handler,
+            final OperationOptions options) {
+
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public SyncToken getLatestSyncToken(final ObjectClass objectClass) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ConnectorObject getObject(
+            final ObjectClass objectClass,
+            final Attribute connObjectKey,
+            final boolean ignoreCaseMatch,
+            final OperationOptions options) {
+
+        return null;
+    }
+
+    @Override
+    public SearchResult search(
+            final ObjectClass objectClass,
+            final Filter filter,
+            final SearchResultsHandler handler,
+            final OperationOptions options) {
+
+        SearchResult result = new SearchResult();
+
+        if (reader != null) {
+            while (reader.hasNext()) {
+                Map<String, String> row = reader.next();
+
+                ConnectorObjectBuilder builder = new ConnectorObjectBuilder();
+                builder.setObjectClass(objectClass);
+                builder.setUid(row.get(keyColumn));
+                builder.setName(row.get(keyColumn));
+
+                row.forEach((key, value) -> builder.addAttribute(arrayElementsSeparator == null
+                        ? AttributeBuilder.build(key, value)
+                        : AttributeBuilder.build(key,
+                                (Object[]) StringUtils.splitByWholeSeparator(value, arrayElementsSeparator))));
+
+                handler.handle(builder.build());
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public Set<ObjectClassInfo> getObjectClassInfo() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public void validate() {
+        // nothing to do
+    }
+
+    @Override
+    public void test() {
+        // nothing to do
+    }
+
+    @Override
+    public void dispose() {
+        // nothing to do
+    }
+}
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePullExecutor.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/stream/SyncopeStreamPullExecutor.java
similarity index 61%
copy from core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePullExecutor.java
copy to core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/stream/SyncopeStreamPullExecutor.java
index da1b994..8d5c241 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePullExecutor.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/stream/SyncopeStreamPullExecutor.java
@@ -16,20 +16,24 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.provisioning.api.pushpull;
+package org.apache.syncope.core.provisioning.api.pushpull.stream;
 
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import java.util.List;
 import org.apache.syncope.common.lib.to.PullTaskTO;
-import org.apache.syncope.core.persistence.api.entity.resource.Provision;
-import org.apache.syncope.core.provisioning.api.Connector;
+import org.apache.syncope.common.lib.types.ConflictResolutionAction;
+import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.quartz.JobExecutionException;
 
-public interface SyncopeSinglePullExecutor {
+public interface SyncopeStreamPullExecutor {
 
     List<ProvisioningReport> pull(
-            Provision provision,
-            Connector connector,
-            String connObjectKey,
-            String connObjectValue,
-            PullTaskTO pullTaskTO) throws JobExecutionException;
+            AnyType anyType,
+            String keyColumn,
+            List<String> columns,
+            ConflictResolutionAction conflictResolutionAction,
+            String pullCorrelationRule,
+            StreamConnector connector,
+            PullTaskTO pullTaskTO)
+            throws JobExecutionException;
 }
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePushExecutor.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/stream/SyncopeStreamPushExecutor.java
similarity index 60%
copy from core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePushExecutor.java
copy to core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/stream/SyncopeStreamPushExecutor.java
index 8053a65..4ceae42 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePushExecutor.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/stream/SyncopeStreamPushExecutor.java
@@ -16,27 +16,22 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.provisioning.api.pushpull;
+package org.apache.syncope.core.provisioning.api.pushpull.stream;
 
 import java.util.List;
+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.resource.Provision;
-import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
-import org.apache.syncope.core.provisioning.api.Connector;
+import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.quartz.JobExecutionException;
 
-public interface SyncopeSinglePushExecutor {
+public interface SyncopeStreamPushExecutor {
 
     List<ProvisioningReport> push(
-            Provision provision,
-            Connector connector,
-            Any<?> any,
-            PushTaskTO pushTaskTO) throws JobExecutionException;
-
-    ProvisioningReport push(
-            Provision provision,
-            Connector connector,
-            LinkedAccount account,
-            PushTaskTO pushTaskTO) throws JobExecutionException;
+            AnyType anyType,
+            List<? extends Any<?>> anys,
+            List<String> columns,
+            StreamConnector connector,
+            PushTaskTO pushTaskTO)
+            throws JobExecutionException;
 }
diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/IntAttrNameParserTest.java b/core/provisioning-api/src/test/java/org/apache/syncope/core/provisioning/api/IntAttrNameParserTest.java
similarity index 75%
rename from core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/IntAttrNameParserTest.java
rename to core/provisioning-api/src/test/java/org/apache/syncope/core/provisioning/api/IntAttrNameParserTest.java
index b10a4b9..2a2b178 100644
--- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/IntAttrNameParserTest.java
+++ b/core/provisioning-api/src/test/java/org/apache/syncope/core/provisioning/api/IntAttrNameParserTest.java
@@ -16,31 +16,130 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.provisioning.java;
+package org.apache.syncope.core.provisioning.api;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+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;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.AttrSchemaType;
 import org.apache.syncope.common.lib.types.SchemaType;
+import org.apache.syncope.core.persistence.api.dao.DerSchemaDAO;
+import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
+import org.apache.syncope.core.persistence.api.entity.AnyUtils;
+import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
 import org.apache.syncope.core.persistence.api.entity.DerSchema;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.apache.syncope.core.persistence.api.entity.VirSchema;
-import org.apache.syncope.core.provisioning.api.IntAttrName;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.transaction.annotation.Transactional;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+import org.springframework.util.ReflectionUtils;
 
-@Transactional("Master")
-public class IntAttrNameParserTest extends AbstractTest {
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.WARN)
+public class IntAttrNameParserTest {
 
-    @Autowired
+    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"));
+    }
+
+    @Mock
+    private PlainSchemaDAO plainSchemaDAO;
+
+    @Mock
+    private DerSchemaDAO derSchemaDAO;
+
+    @Mock
+    private VirSchemaDAO virSchemaDAO;
+
+    @Mock
+    private AnyUtilsFactory anyUtilsFactory;
+
+    @Mock
+    private AnyUtils anyUtils;
+
+    @InjectMocks
     private IntAttrNameParser intAttrNameParser;
 
+    @BeforeEach
+    public void initMocks() throws NoSuchFieldException {
+        MockitoAnnotations.initMocks(this);
+
+        when(anyUtilsFactory.getInstance(any(AnyTypeKind.class))).thenAnswer(ic -> {
+            when(anyUtils.anyTypeKind()).thenReturn(ic.getArgument(0));
+            return anyUtils;
+        });
+        when(anyUtils.getField(anyString())).thenAnswer(ic -> {
+            String field = ic.getArgument(0);
+            return FIELDS.get(anyUtils.anyTypeKind()).contains(field)
+                    ? ReflectionUtils.findField(getClass(), "anyUtils")
+                    : null;
+        });
+        when(plainSchemaDAO.find(anyString())).thenAnswer(ic -> {
+            String schemaName = ic.getArgument(0);
+            switch (schemaName) {
+                case "email":
+                case "firstname":
+                case "location":
+                    PlainSchema schema = mock(PlainSchema.class);
+                    when(schema.getKey()).thenReturn(schemaName);
+                    when(schema.getType()).thenReturn(AttrSchemaType.String);
+                    return schema;
+
+                default:
+                    return null;
+            }
+        });
+        when(derSchemaDAO.find(anyString())).thenAnswer(ic -> {
+            String schemaName = ic.getArgument(0);
+            switch (schemaName) {
+                case "cn":
+                    DerSchema schema = mock(DerSchema.class);
+                    when(schema.getKey()).thenReturn(ic.getArgument(0));
+                    return schema;
+
+                default:
+                    return null;
+            }
+        });
+        when(virSchemaDAO.find(anyString())).thenAnswer(ic -> {
+            String schemaName = ic.getArgument(0);
+            switch (schemaName) {
+                case "rvirtualdata":
+                    VirSchema schema = mock(VirSchema.class);
+                    when(schema.getKey()).thenReturn(ic.getArgument(0));
+                    return schema;
+
+                default:
+                    return null;
+            }
+        });
+    }
+
     @Test
     public void ownFields() throws ParseException {
         IntAttrName intAttrName = intAttrNameParser.parse("key", AnyTypeKind.USER);
@@ -231,8 +330,8 @@ public class IntAttrNameParserTest extends AbstractTest {
 
     @Test
     public void relationship() throws ParseException {
-        IntAttrName intAttrName = intAttrNameParser.parse("relationships[inclusion][PRINTER].location",
-                AnyTypeKind.USER);
+        IntAttrName intAttrName = intAttrNameParser.parse(
+                "relationships[inclusion][PRINTER].location", AnyTypeKind.USER);
         assertNotNull(intAttrName);
         assertEquals(AnyTypeKind.ANY_OBJECT, intAttrName.getAnyTypeKind());
         assertNull(intAttrName.getField());
diff --git a/core/provisioning-java/pom.xml b/core/provisioning-java/pom.xml
index 3ab2723..6230bbe 100644
--- a/core/provisioning-java/pom.xml
+++ b/core/provisioning-java/pom.xml
@@ -45,11 +45,6 @@ under the License.
     </dependency>
 
     <dependency>
-      <groupId>org.apache.commons</groupId>
-      <artifactId>commons-jexl3</artifactId>
-    </dependency>
-    
-    <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-context-support</artifactId>
     </dependency>
@@ -138,6 +133,11 @@ under the License.
       <scope>test</scope>
     </dependency>
     <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
       <groupId>org.junit.jupiter</groupId>
       <artifactId>junit-jupiter</artifactId>
       <scope>test</scope>
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java
index a53174a..d2635e8 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java
@@ -24,7 +24,6 @@ import java.util.List;
 import java.util.Set;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
 import java.util.concurrent.atomic.AtomicReference;
 import org.apache.syncope.common.lib.types.ConnectorCapability;
 import org.apache.syncope.core.persistence.api.entity.ConnInstance;
@@ -33,7 +32,6 @@ import org.apache.syncope.core.provisioning.api.utils.ConnPoolConfUtils;
 import org.apache.syncope.core.provisioning.api.Connector;
 import org.apache.syncope.core.provisioning.api.TimeoutException;
 import org.apache.syncope.core.provisioning.api.pushpull.ReconFilterBuilder;
-import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.identityconnectors.common.security.GuardedByteArray;
 import org.identityconnectors.common.security.GuardedString;
@@ -49,9 +47,6 @@ import org.identityconnectors.framework.common.objects.ObjectClassInfo;
 import org.identityconnectors.framework.common.objects.OperationOptions;
 import org.identityconnectors.framework.common.objects.OperationOptionsBuilder;
 import org.identityconnectors.framework.common.objects.SearchResult;
-import org.identityconnectors.framework.common.objects.SortKey;
-import org.identityconnectors.framework.common.objects.SyncDeltaBuilder;
-import org.identityconnectors.framework.common.objects.SyncDeltaType;
 import org.identityconnectors.framework.common.objects.SyncResultsHandler;
 import org.identityconnectors.framework.common.objects.SyncToken;
 import org.identityconnectors.framework.common.objects.Uid;
@@ -310,7 +305,7 @@ public class ConnectorFacadeProxy implements Connector {
             final SyncResultsHandler handler,
             final OperationOptions options) {
 
-        filteredReconciliation(objectClass, null, handler, options);
+        Connector.super.fullReconciliation(objectClass, handler, options);
     }
 
     @Transactional
@@ -321,29 +316,7 @@ public class ConnectorFacadeProxy implements Connector {
             final SyncResultsHandler handler,
             final OperationOptions options) {
 
-        Filter filter = null;
-        OperationOptions actualOptions = options;
-        if (filterBuilder != null) {
-            filter = filterBuilder.build();
-            actualOptions = filterBuilder.build(actualOptions);
-        }
-
-        search(objectClass, filter, new SearchResultsHandler() {
-
-            @Override
-            public void handleResult(final SearchResult result) {
-                // nothing to do
-            }
-
-            @Override
-            public boolean handle(final ConnectorObject object) {
-                return handler.handle(new SyncDeltaBuilder().
-                        setObject(object).
-                        setDeltaType(SyncDeltaType.CREATE_OR_UPDATE).
-                        setToken(new SyncToken("")).
-                        build());
-            }
-        }, actualOptions);
+        Connector.super.filteredReconciliation(objectClass, filterBuilder, handler, options);
     }
 
     @Override
@@ -477,29 +450,6 @@ public class ConnectorFacadeProxy implements Connector {
     }
 
     @Override
-    public SearchResult search(
-            final ObjectClass objectClass,
-            final Filter filter,
-            final SearchResultsHandler handler,
-            final int pageSize,
-            final String pagedResultsCookie,
-            final List<OrderByClause> orderBy,
-            final OperationOptions options) {
-
-        OperationOptionsBuilder builder = new OperationOptionsBuilder().setPageSize(pageSize).setPagedResultsOffset(-1);
-        if (pagedResultsCookie != null) {
-            builder.setPagedResultsCookie(pagedResultsCookie);
-        }
-        builder.setSortKeys(orderBy.stream().map(clause
-                -> new SortKey(clause.getField(), clause.getDirection() == OrderByClause.Direction.ASC)).
-                collect(Collectors.toList()));
-
-        builder.setAttributesToGet(options.getAttributesToGet());
-
-        return search(objectClass, filter, handler, builder.build());
-    }
-
-    @Override
     public void dispose() {
         connector.dispose();
     }
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultUserProvisioningManager.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultUserProvisioningManager.java
index 46e4b9b..808f5b0 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultUserProvisioningManager.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultUserProvisioningManager.java
@@ -46,7 +46,7 @@ import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
 import org.apache.syncope.core.provisioning.api.VirAttrHandler;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
-import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.core.workflow.api.UserWorkflowAdapter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DerAttrHandlerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DerAttrHandlerImpl.java
index af4f57a..4499492 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DerAttrHandlerImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DerAttrHandlerImpl.java
@@ -24,7 +24,7 @@ import java.util.Map;
 import java.util.Set;
 import org.apache.commons.jexl3.JexlContext;
 import org.apache.commons.jexl3.MapContext;
-import org.apache.syncope.core.provisioning.java.jexl.JexlUtils;
+import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
 import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
 import org.apache.syncope.core.persistence.api.entity.DerSchema;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/MappingManagerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/MappingManagerImpl.java
index a656720..1bd37ce 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/MappingManagerImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/MappingManagerImpl.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.provisioning.java;
 
+import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Base64;
@@ -28,6 +29,8 @@ import java.util.List;
 import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
+import org.apache.commons.jexl3.JexlContext;
+import org.apache.commons.jexl3.MapContext;
 import org.apache.commons.lang3.BooleanUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.reflect.FieldUtils;
@@ -101,7 +104,9 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
 import org.apache.syncope.core.provisioning.api.data.ItemTransformer;
+import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
 import org.identityconnectors.framework.common.objects.Name;
+import org.identityconnectors.framework.common.objects.Uid;
 
 @Component
 public class MappingManagerImpl implements MappingManager {
@@ -182,6 +187,87 @@ public class MappingManagerImpl implements MappingManager {
         return connObjectKey;
     }
 
+    private static Name getName(final String evalConnObjectLink, final String connObjectKey) {
+        // If connObjectLink evaluates to an empty string, just use the provided connObjectKey as Name(),
+        // otherwise evaluated connObjectLink expression is taken as Name().
+        Name name;
+        if (StringUtils.isBlank(evalConnObjectLink)) {
+            // add connObjectKey as __NAME__ attribute ...
+            LOG.debug("Add connObjectKey [{}] as {}", connObjectKey, Name.NAME);
+            name = new Name(connObjectKey);
+        } else {
+            LOG.debug("Add connObjectLink [{}] as {}", evalConnObjectLink, Name.NAME);
+            name = new Name(evalConnObjectLink);
+
+            // connObjectKey not propagated: it will be used to set the value for __UID__ attribute
+            LOG.debug("connObjectKey will be used just as {} attribute", Uid.NAME);
+        }
+
+        return name;
+    }
+
+    /**
+     * Build __NAME__ for propagation.
+     * First look if there is a defined connObjectLink for the given resource (and in
+     * this case evaluate as JEXL); otherwise, take given connObjectKey.
+     *
+     * @param any given any object
+     * @param provision external resource
+     * @param connObjectKey connector object key
+     * @return the value to be propagated as __NAME__
+     */
+    private Name evaluateNAME(final Any<?> any, final Provision provision, final String connObjectKey) {
+        if (StringUtils.isBlank(connObjectKey)) {
+            // LOG error but avoid to throw exception: leave it to the external resource
+            LOG.warn("Missing ConnObjectKey value for {}: ", provision.getResource());
+        }
+
+        // Evaluate connObjectKey expression
+        String connObjectLink = provision == null || provision.getMapping() == null
+                ? null
+                : provision.getMapping().getConnObjectLink();
+        String evalConnObjectLink = null;
+        if (StringUtils.isNotBlank(connObjectLink)) {
+            JexlContext jexlContext = new MapContext();
+            JexlUtils.addFieldsToContext(any, jexlContext);
+            JexlUtils.addPlainAttrsToContext(any.getPlainAttrs(), jexlContext);
+            JexlUtils.addDerAttrsToContext(any, derAttrHandler, jexlContext);
+            evalConnObjectLink = JexlUtils.evaluate(connObjectLink, jexlContext);
+        }
+
+        return getName(evalConnObjectLink, connObjectKey);
+    }
+
+    /**
+     * Build __NAME__ for propagation.
+     * First look if there is a defined connObjectLink for the given resource (and in
+     * this case evaluate as JEXL); otherwise, take given connObjectKey.
+     *
+     * @param realm given any object
+     * @param orgUnit external resource
+     * @param connObjectKey connector object key
+     * @return the value to be propagated as __NAME__
+     */
+    private Name evaluateNAME(final Realm realm, final OrgUnit orgUnit, final String connObjectKey) {
+        if (StringUtils.isBlank(connObjectKey)) {
+            // LOG error but avoid to throw exception: leave it to the external resource
+            LOG.warn("Missing ConnObjectKey value for {}: ", orgUnit.getResource());
+        }
+
+        // Evaluate connObjectKey expression
+        String connObjectLink = orgUnit == null
+                ? null
+                : orgUnit.getConnObjectLink();
+        String evalConnObjectLink = null;
+        if (StringUtils.isNotBlank(connObjectLink)) {
+            JexlContext jexlContext = new MapContext();
+            JexlUtils.addFieldsToContext(realm, jexlContext);
+            evalConnObjectLink = JexlUtils.evaluate(connObjectLink, jexlContext);
+        }
+
+        return getName(evalConnObjectLink, connObjectKey);
+    }
+
     @Transactional(readOnly = true)
     @Override
     public Pair<String, Set<Attribute>> prepareAttrs(
@@ -225,7 +311,7 @@ public class MappingManagerImpl implements MappingManager {
                 attributes.remove(connObjectKeyAttr);
                 attributes.add(AttributeBuilder.build(connObjectKeyItem.getExtAttrName(), connObjectKeyValue[0]));
             }
-            Name name = MappingUtils.evaluateNAME(any, provision, connObjectKeyValue[0]);
+            Name name = evaluateNAME(any, provision, connObjectKeyValue[0]);
             attributes.add(name);
             if (connObjectKeyAttr == null
                     && connObjectKeyValue[0] != null && !connObjectKeyValue[0].equals(name.getNameValue())) {
@@ -300,7 +386,7 @@ public class MappingManagerImpl implements MappingManager {
                 attributes.remove(connObjectKeyExtAttr);
                 attributes.add(AttributeBuilder.build(connObjectKeyItem.getExtAttrName(), connObjectKey));
             }
-            Name name = MappingUtils.evaluateNAME(user, provision, connObjectKey);
+            Name name = evaluateNAME(user, provision, connObjectKey);
             attributes.add(name);
             if (!connObjectKey.equals(name.getNameValue()) && connObjectKeyExtAttr == null) {
                 attributes.add(AttributeBuilder.build(connObjectKeyItem.getExtAttrName(), connObjectKey));
@@ -378,7 +464,7 @@ public class MappingManagerImpl implements MappingManager {
                 attributes.remove(connObjectKeyAttr);
                 attributes.add(AttributeBuilder.build(connObjectKeyItem.get().getExtAttrName(), connObjectKeyValue[0]));
             }
-            attributes.add(MappingUtils.evaluateNAME(realm, orgUnit, connObjectKeyValue[0]));
+            attributes.add(evaluateNAME(realm, orgUnit, connObjectKeyValue[0]));
         }
 
         return Pair.of(connObjectKeyValue[0], attributes);
@@ -634,24 +720,24 @@ public class MappingManagerImpl implements MappingManager {
 
                     default:
                         try {
-                            Object fieldValue = FieldUtils.readField(ref, intAttrName.getField(), true);
-                            if (fieldValue instanceof Date) {
-                                // needed because ConnId does not natively supports the Date type
-                                attrValue.setStringValue(DateFormatUtils.ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT.
-                                        format((Date) fieldValue));
-                            } else if (Boolean.TYPE.isInstance(fieldValue)) {
-                                attrValue.setBooleanValue((Boolean) fieldValue);
-                            } else if (Double.TYPE.isInstance(fieldValue) || Float.TYPE.isInstance(fieldValue)) {
-                                attrValue.setDoubleValue((Double) fieldValue);
-                            } else if (Long.TYPE.isInstance(fieldValue) || Integer.TYPE.isInstance(fieldValue)) {
-                                attrValue.setLongValue((Long) fieldValue);
-                            } else {
-                                attrValue.setStringValue(fieldValue.toString());
-                            }
-                            values.add(attrValue);
-                        } catch (Exception e) {
-                            LOG.error("Could not read value of '{}' from {}", intAttrName.getField(), ref, e);
+                        Object fieldValue = FieldUtils.readField(ref, intAttrName.getField(), true);
+                        if (fieldValue instanceof Date) {
+                            // needed because ConnId does not natively supports the Date type
+                            attrValue.setStringValue(DateFormatUtils.ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT.
+                                    format((Date) fieldValue));
+                        } else if (Boolean.TYPE.isInstance(fieldValue)) {
+                            attrValue.setBooleanValue((Boolean) fieldValue);
+                        } else if (Double.TYPE.isInstance(fieldValue) || Float.TYPE.isInstance(fieldValue)) {
+                            attrValue.setDoubleValue((Double) fieldValue);
+                        } else if (Long.TYPE.isInstance(fieldValue) || Integer.TYPE.isInstance(fieldValue)) {
+                            attrValue.setLongValue((Long) fieldValue);
+                        } else {
+                            attrValue.setStringValue(fieldValue.toString());
                         }
+                        values.add(attrValue);
+                    } catch (Exception e) {
+                        LOG.error("Could not read value of '{}' from {}", intAttrName.getField(), ref, e);
+                    }
                 }
             } else if (intAttrName.getSchemaType() != null) {
                 switch (intAttrName.getSchemaType()) {
@@ -760,7 +846,7 @@ public class MappingManagerImpl implements MappingManager {
 
         return preparedAttr == null
                 ? null
-                : MappingUtils.evaluateNAME(any, provision, preparedAttr.getKey()).getNameValue();
+                : evaluateNAME(any, provision, preparedAttr.getKey()).getNameValue();
     }
 
     @Transactional(readOnly = true)
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
index 16c5888..5d63267 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
@@ -82,8 +82,8 @@ import org.apache.syncope.core.provisioning.api.MappingManager;
 import org.apache.syncope.core.provisioning.api.PlainAttrGetter;
 import org.apache.syncope.core.provisioning.api.PropagationByResource;
 import org.apache.syncope.core.provisioning.api.VirAttrHandler;
-import org.apache.syncope.core.provisioning.java.IntAttrNameParser;
-import org.apache.syncope.core.provisioning.java.jexl.JexlUtils;
+import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
+import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
 import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -225,7 +225,7 @@ abstract class AbstractAnyDataBinder {
                         AccountGetter.DEFAULT,
                         PlainAttrGetter.DEFAULT);
                 if (intValues.getRight().isEmpty()
-                        && JexlUtils.evaluateMandatoryCondition(mapItem.getMandatoryCondition(), any)) {
+                        && JexlUtils.evaluateMandatoryCondition(mapItem.getMandatoryCondition(), any, derAttrHandler)) {
 
                     missingAttrNames.add(mapItem.getIntAttrName());
                 }
@@ -263,7 +263,7 @@ abstract class AbstractAnyDataBinder {
 
         if (attr == null
                 && !schema.isReadonly()
-                && JexlUtils.evaluateMandatoryCondition(schema.getMandatoryCondition(), any)) {
+                && JexlUtils.evaluateMandatoryCondition(schema.getMandatoryCondition(), any, derAttrHandler)) {
 
             LOG.error("Mandatory schema " + schema.getKey() + " not provided with values");
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ConfigurationDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ConfigurationDataBinderImpl.java
index fbe700d..416cc9c 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ConfigurationDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ConfigurationDataBinderImpl.java
@@ -27,7 +27,7 @@ import org.apache.commons.jexl3.MapContext;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.to.AttrTO;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
-import org.apache.syncope.core.provisioning.java.jexl.JexlUtils;
+import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
 import org.apache.syncope.core.persistence.api.dao.ConfDAO;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/JEXLItemTransformerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/JEXLItemTransformerImpl.java
index 713cf25..1436960 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/JEXLItemTransformerImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/JEXLItemTransformerImpl.java
@@ -32,11 +32,16 @@ import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.Entity;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
 import org.apache.syncope.core.persistence.api.entity.resource.Item;
-import org.apache.syncope.core.provisioning.java.jexl.JexlUtils;
+import org.apache.syncope.core.provisioning.api.DerAttrHandler;
+import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
 import org.apache.syncope.core.provisioning.api.data.JEXLItemTransformer;
+import org.springframework.beans.factory.annotation.Autowired;
 
 public class JEXLItemTransformerImpl implements JEXLItemTransformer {
 
+    @Autowired
+    private DerAttrHandler derAttrHandler;
+
     private String propagationJEXL;
 
     private String pullJEXL;
@@ -67,7 +72,7 @@ public class JEXLItemTransformerImpl implements JEXLItemTransformer {
                         JexlUtils.addFieldsToContext(entity, jexlContext);
                         if (entity instanceof Any) {
                             JexlUtils.addPlainAttrsToContext(((Any<?>) entity).getPlainAttrs(), jexlContext);
-                            JexlUtils.addDerAttrsToContext(((Any<?>) entity), jexlContext);
+                            JexlUtils.addDerAttrsToContext(((Any<?>) entity), derAttrHandler, jexlContext);
                         }
                     }
                     jexlContext.set("value", originalValue);
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/NotificationDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/NotificationDataBinderImpl.java
index edbe242..648520a 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/NotificationDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/NotificationDataBinderImpl.java
@@ -36,7 +36,7 @@ import org.apache.syncope.core.persistence.api.entity.AnyAbout;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.MailTemplate;
-import org.apache.syncope.core.provisioning.java.IntAttrNameParser;
+import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
index b045ad7..f77dd6e 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
@@ -48,7 +48,7 @@ import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 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.policy.PasswordPolicy;
-import org.apache.syncope.core.provisioning.java.jexl.JexlUtils;
+import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
 import org.apache.syncope.core.persistence.api.dao.ConfDAO;
 import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
@@ -66,7 +66,7 @@ import org.apache.syncope.core.persistence.api.entity.resource.Item;
 import org.apache.syncope.core.persistence.api.entity.resource.OrgUnit;
 import org.apache.syncope.core.persistence.api.entity.resource.OrgUnitItem;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
-import org.apache.syncope.core.provisioning.java.IntAttrNameParser;
+import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
 import org.apache.syncope.core.provisioning.api.IntAttrName;
 import org.apache.syncope.core.provisioning.api.data.ResourceDataBinder;
 import org.identityconnectors.framework.common.objects.ObjectClass;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SchemaDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SchemaDataBinderImpl.java
index fa08a25..21f5818 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SchemaDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SchemaDataBinderImpl.java
@@ -33,7 +33,7 @@ import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
 import org.apache.syncope.core.persistence.api.entity.DerSchema;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.apache.syncope.core.persistence.api.entity.VirSchema;
-import org.apache.syncope.core.provisioning.java.jexl.JexlUtils;
+import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
 import org.apache.syncope.core.persistence.api.dao.DerSchemaDAO;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
index 2e22f9d..6d06c73 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
@@ -23,7 +23,6 @@ import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
@@ -68,7 +67,6 @@ import org.apache.syncope.core.persistence.api.entity.Privilege;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.RelationshipType;
 import org.apache.syncope.core.persistence.api.entity.Role;
-import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.user.LAPlainAttr;
@@ -739,14 +737,11 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat
             userTO.setSecurityQuestion(user.getSecurityQuestion().getKey());
         }
 
-        Map<VirSchema, List<String>> virAttrValues = details
-                ? virAttrHandler.getValues(user)
-                : Collections.<VirSchema, List<String>>emptyMap();
         fillTO(userTO, user.getRealm().getFullPath(),
                 user.getAuxClasses(),
                 user.getPlainAttrs(),
                 derAttrHandler.getValues(user),
-                virAttrValues,
+                details ? virAttrHandler.getValues(user) : Collections.emptyMap(),
                 userDAO.findAllResources(user),
                 details);
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java
index a6725a5..b82d00c 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java
@@ -75,8 +75,8 @@ import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
 import org.apache.syncope.core.provisioning.api.event.AfterHandlingEvent;
 import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
 import org.apache.syncope.core.provisioning.api.notification.RecipientsProvider;
-import org.apache.syncope.core.provisioning.java.IntAttrNameParser;
-import org.apache.syncope.core.provisioning.java.jexl.JexlUtils;
+import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
+import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
 import org.apache.syncope.core.spring.ImplementationManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
index 504d0e2..fd4e98a 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
@@ -347,8 +347,14 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
     public TaskExec execute(final PropagationTaskInfo taskInfo, final PropagationReporter reporter) {
         PropagationTask task;
         if (taskInfo.getKey() == null) {
+            // double-checks that provided External Resource is valid, for further actions
+            ExternalResource resource = resourceDAO.find(taskInfo.getResource());
+            if (resource == null) {
+                resource = taskInfo.getExternalResource();
+            }
+
             task = entityFactory.newEntity(PropagationTask.class);
-            task.setResource(resourceDAO.find(taskInfo.getResource()));
+            task.setResource(resource);
             task.setObjectClassName(taskInfo.getObjectClassName());
             task.setAnyTypeKind(taskInfo.getAnyTypeKind());
             task.setAnyType(taskInfo.getAnyType());
@@ -365,9 +371,11 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
         }
         task.setAttributes(attributes);
 
-        List<PropagationActions> actions = getPropagationActions(task.getResource());
+        Connector connector = taskInfo.getConnector() == null
+                ? connFactory.getConnector(task.getResource())
+                : taskInfo.getConnector();
 
-        String resource = task.getResource().getKey();
+        List<PropagationActions> actions = getPropagationActions(task.getResource());
 
         Date start = new Date();
 
@@ -386,12 +394,10 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
         Provision provision = null;
         OrgUnit orgUnit = null;
         Uid uid = null;
-        Connector connector = null;
         Result result;
         try {
             provision = task.getResource().getProvision(new ObjectClass(task.getObjectClassName())).orElse(null);
             orgUnit = task.getResource().getOrgUnit();
-            connector = connFactory.getConnector(task.getResource());
 
             if (taskInfo.getBeforeObj() == null) {
                 // Try to read remote object BEFORE any actual operation
@@ -429,7 +435,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
             result = Result.SUCCESS;
         } catch (Exception e) {
             result = Result.FAILURE;
-            LOG.error("Exception during provision on resource " + resource, e);
+            LOG.error("Exception during provision on resource " + task.getResource().getKey(), e);
 
             if (e instanceof ConnectorException && e.getCause() != null) {
                 taskExecutionMessage = e.getCause().getMessage();
@@ -515,12 +521,12 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
         String anyTypeKind = task.getAnyTypeKind() == null ? "realm" : task.getAnyTypeKind().name().toLowerCase();
         String operation = task.getOperation().name().toLowerCase();
         boolean notificationsAvailable = notificationManager.notificationsAvailable(
-                AuditElements.EventCategoryType.PROPAGATION, anyTypeKind, resource, operation);
+                AuditElements.EventCategoryType.PROPAGATION, anyTypeKind, task.getResource().getKey(), operation);
         boolean auditRequested = auditManager.auditRequested(
                 AuthContextUtils.getUsername(),
                 AuditElements.EventCategoryType.PROPAGATION,
                 anyTypeKind,
-                resource,
+                task.getResource().getKey(),
                 operation);
 
         if (notificationsAvailable || auditRequested) {
@@ -529,7 +535,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
                     AuthContextUtils.getUsername(),
                     AuditElements.EventCategoryType.PROPAGATION,
                     anyTypeKind,
-                    resource,
+                    task.getResource().getKey(),
                     operation,
                     result,
                     beforeObj,
@@ -540,7 +546,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
                     AuthContextUtils.getUsername(),
                     AuditElements.EventCategoryType.PROPAGATION,
                     anyTypeKind,
-                    resource,
+                    task.getResource().getKey(),
                     operation,
                     result,
                     beforeObj,
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPMembershipPropagationActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPMembershipPropagationActions.java
index d1817ae..5ff46c7 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPMembershipPropagationActions.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPMembershipPropagationActions.java
@@ -32,11 +32,12 @@ import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
 import org.apache.syncope.core.persistence.api.entity.user.User;
-import org.apache.syncope.core.provisioning.java.jexl.JexlUtils;
+import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
+import org.apache.syncope.core.provisioning.api.DerAttrHandler;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
 import org.identityconnectors.framework.common.objects.Attribute;
 import org.identityconnectors.framework.common.objects.AttributeBuilder;
@@ -58,6 +59,9 @@ public class LDAPMembershipPropagationActions implements PropagationActions {
     protected static final Logger LOG = LoggerFactory.getLogger(LDAPMembershipPropagationActions.class);
 
     @Autowired
+    protected DerAttrHandler derAttrHandler;
+
+    @Autowired
     protected AnyTypeDAO anyTypeDAO;
 
     @Autowired
@@ -121,11 +125,9 @@ public class LDAPMembershipPropagationActions implements PropagationActions {
 
                         Attribute beforeLdapGroups = beforeObj.getAttributeByName(getGroupMembershipAttrName());
                         LOG.debug("Memberships not managed by Syncope: {}", beforeLdapGroups);
-                        for (Object value : beforeLdapGroups.getValue()) {
-                            if (!connObjectLinks.contains(String.valueOf(value))) {
-                                groups.add(String.valueOf(value));
-                            }
-                        }
+                        beforeLdapGroups.getValue().stream().
+                                filter(value -> !connObjectLinks.contains(String.valueOf(value))).
+                                forEach(value -> groups.add(String.valueOf(value)));
                     }
                 }
                 LOG.debug("Add ldapGroups to attributes: {}" + groups);
@@ -144,7 +146,7 @@ public class LDAPMembershipPropagationActions implements PropagationActions {
         JexlContext jexlContext = new MapContext();
         JexlUtils.addFieldsToContext(group, jexlContext);
         JexlUtils.addPlainAttrsToContext(group.getPlainAttrs(), jexlContext);
-        JexlUtils.addDerAttrsToContext(group, jexlContext);
+        JexlUtils.addDerAttrsToContext(group, derAttrHandler, jexlContext);
 
         return JexlUtils.evaluate(connObjectLinkTemplate, jexlContext);
     }
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java
index b21dd21..a0d4b09 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java
@@ -18,10 +18,8 @@
  */
 package org.apache.syncope.core.provisioning.java.propagation;
 
-import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -35,7 +33,6 @@ import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import javax.annotation.Resource;
 import org.apache.syncope.common.lib.types.ExecStatus;
-import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationException;
@@ -83,30 +80,25 @@ public class PriorityPropagationTaskExecutor extends AbstractPropagationTaskExec
             final PropagationReporter reporter,
             final boolean nullPriorityAsync) {
 
-        Map<PropagationTaskInfo, ExternalResource> taskToResource = new HashMap<>(taskInfos.size());
         List<PropagationTaskInfo> prioritizedTasks = new ArrayList<>();
 
         int[] connRequestTimeout = { 60 };
 
-        taskInfos.forEach(task -> {
-            ExternalResource resource = resourceDAO.find(task.getResource());
-            taskToResource.put(task, resource);
+        taskInfos.stream().filter(task -> task.getExternalResource().getPropagationPriority() != null).forEach(task -> {
+            prioritizedTasks.add(task);
 
-            if (resource.getPropagationPriority() != null) {
-                prioritizedTasks.add(task);
+            if (task.getExternalResource().getConnector().getConnRequestTimeout() != null
+                    && connRequestTimeout[0] < task.getExternalResource().getConnector().getConnRequestTimeout()) {
 
-                if (resource.getConnector().getConnRequestTimeout() != null
-                        && connRequestTimeout[0] < resource.getConnector().getConnRequestTimeout()) {
-                    connRequestTimeout[0] = resource.getConnector().getConnRequestTimeout();
-                    LOG.debug("Upgrade request connection timeout to {}", connRequestTimeout);
-                }
+                connRequestTimeout[0] = task.getExternalResource().getConnector().getConnRequestTimeout();
+                LOG.debug("Upgrade request connection timeout to {}", connRequestTimeout);
             }
         });
 
-        Collections.sort(prioritizedTasks, new PriorityComparator(taskToResource));
+        prioritizedTasks.sort(Comparator.comparing(task -> task.getExternalResource().getPropagationPriority()));
         LOG.debug("Propagation tasks sorted by priority, for serial execution: {}", prioritizedTasks);
 
-        Collection<PropagationTaskInfo> concurrentTasks = taskInfos.stream().
+        Set<PropagationTaskInfo> concurrentTasks = taskInfos.stream().
                 filter(task -> !prioritizedTasks.contains(task)).collect(Collectors.toSet());
         LOG.debug("Propagation tasks for concurrent execution: {}", concurrentTasks);
 
@@ -172,30 +164,4 @@ public class PriorityPropagationTaskExecutor extends AbstractPropagationTaskExec
             }
         }
     }
-
-    /**
-     * Compare propagation tasks according to related ExternalResource's priority.
-     */
-    protected static class PriorityComparator implements Comparator<PropagationTaskInfo>, Serializable {
-
-        private static final long serialVersionUID = -1969355670784448878L;
-
-        private final Map<PropagationTaskInfo, ExternalResource> taskToResource;
-
-        public PriorityComparator(final Map<PropagationTaskInfo, ExternalResource> taskToResource) {
-            this.taskToResource = taskToResource;
-        }
-
-        @Override
-        public int compare(final PropagationTaskInfo task1, final PropagationTaskInfo task2) {
-            int prop1 = taskToResource.get(task1).getPropagationPriority();
-            int prop2 = taskToResource.get(task2).getPropagationPriority();
-
-            return prop1 > prop2
-                    ? 1
-                    : prop1 == prop2
-                            ? 0
-                            : -1;
-        }
-    }
 }
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PropagationManagerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PropagationManagerImpl.java
index 2bd038d..6b4a85e 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PropagationManagerImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PropagationManagerImpl.java
@@ -40,9 +40,7 @@ import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationManager;
-import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
 import org.apache.syncope.core.provisioning.java.utils.ConnObjectUtils;
-import org.apache.syncope.core.provisioning.java.jexl.JexlUtils;
 import org.apache.syncope.core.persistence.api.dao.AnyDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
@@ -56,8 +54,11 @@ import org.apache.syncope.core.persistence.api.entity.resource.OrgUnit;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
 import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.api.DerAttrHandler;
 import org.apache.syncope.core.provisioning.api.MappingManager;
 import org.apache.syncope.core.provisioning.api.UserWorkflowResult;
+import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
+import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
 import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
 import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
@@ -84,30 +85,18 @@ public class PropagationManagerImpl implements PropagationManager {
     @Autowired
     protected AnyObjectDAO anyObjectDAO;
 
-    /**
-     * User DAO.
-     */
     @Autowired
     protected UserDAO userDAO;
 
-    /**
-     * Group DAO.
-     */
     @Autowired
     protected GroupDAO groupDAO;
 
-    /**
-     * Resource DAO.
-     */
     @Autowired
     protected ExternalResourceDAO resourceDAO;
 
     @Autowired
     protected EntityFactory entityFactory;
 
-    /**
-     * ConnObjectUtils.
-     */
     @Autowired
     protected ConnObjectUtils connObjectUtils;
 
@@ -115,6 +104,9 @@ public class PropagationManagerImpl implements PropagationManager {
     protected MappingManager mappingManager;
 
     @Autowired
+    protected DerAttrHandler derAttrHandler;
+
+    @Autowired
     protected AnyUtilsFactory anyUtilsFactory;
 
     protected AnyDAO<? extends Any<?>> dao(final AnyTypeKind kind) {
@@ -387,17 +379,18 @@ public class PropagationManagerImpl implements PropagationManager {
         return createTasks(any, null, false, false, true, localPropByRes, propByLinkedAccount, null);
     }
 
-    protected PropagationTaskInfo newTask(
+    @Override
+    public PropagationTaskInfo newTask(
+            final DerAttrHandler derAttrHandler,
             final Any<?> any,
-            final String resource,
+            final ExternalResource resource,
             final ResourceOperation operation,
             final Provision provision,
             final boolean deleteOnResource,
             final Stream<? extends Item> mappingItems,
             final Pair<String, Set<Attribute>> preparedAttrs) {
 
-        PropagationTaskInfo task = new PropagationTaskInfo();
-        task.setResource(resource);
+        PropagationTaskInfo task = new PropagationTaskInfo(resource);
         task.setObjectClassName(provision.getObjectClass().getObjectClassValue());
         task.setAnyTypeKind(any.getType().getKind());
         task.setAnyType(any.getType().getKey());
@@ -412,15 +405,16 @@ public class PropagationManagerImpl implements PropagationManager {
         List<String> mandatoryMissing = new ArrayList<>();
         List<String> mandatoryNullOrEmpty = new ArrayList<>();
         mappingItems.filter(item -> (!item.isConnObjectKey()
-                && JexlUtils.evaluateMandatoryCondition(item.getMandatoryCondition(), any))).forEach(item -> {
-
-            Attribute attr = AttributeUtil.find(item.getExtAttrName(), preparedAttrs.getRight());
-            if (attr == null) {
-                mandatoryMissing.add(item.getExtAttrName());
-            } else if (CollectionUtils.isEmpty(attr.getValue())) {
-                mandatoryNullOrEmpty.add(item.getExtAttrName());
-            }
-        });
+                && JexlUtils.evaluateMandatoryCondition(item.getMandatoryCondition(), any, derAttrHandler))).
+                forEach(item -> {
+
+                    Attribute attr = AttributeUtil.find(item.getExtAttrName(), preparedAttrs.getRight());
+                    if (attr == null) {
+                        mandatoryMissing.add(item.getExtAttrName());
+                    } else if (CollectionUtils.isEmpty(attr.getValue())) {
+                        mandatoryNullOrEmpty.add(item.getExtAttrName());
+                    }
+                });
         if (!mandatoryMissing.isEmpty()) {
             preparedAttrs.getRight().add(AttributeBuilder.build(
                     PropagationTaskExecutor.MANDATORY_MISSING_ATTR_NAME, mandatoryMissing));
@@ -525,8 +519,9 @@ public class PropagationManagerImpl implements PropagationManager {
                 }
 
                 PropagationTaskInfo task = newTask(
+                        derAttrHandler,
                         any,
-                        resourceKey,
+                        resource,
                         operation,
                         provision,
                         deleteOnResource,
@@ -568,8 +563,9 @@ public class PropagationManagerImpl implements PropagationManager {
                             AnyTypeKind.USER.name(), account.getResource());
                 } else {
                     PropagationTaskInfo accountTask = newTask(
+                            derAttrHandler,
                             user,
-                            account.getResource().getKey(),
+                            account.getResource(),
                             operation,
                             provision,
                             deleteOnResource,
@@ -617,8 +613,7 @@ public class PropagationManagerImpl implements PropagationManager {
                 LOG.warn("Requesting propagation for {} but no ConnObjectLink provided for {}",
                         realm.getFullPath(), resource);
             } else {
-                PropagationTaskInfo task = new PropagationTaskInfo();
-                task.setResource(resource.getKey());
+                PropagationTaskInfo task = new PropagationTaskInfo(resource);
                 task.setObjectClassName(orgUnit.getObjectClass().getObjectClassValue());
                 task.setEntityKey(realm.getKey());
                 task.setOperation(operation);
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractProvisioningJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractProvisioningJobDelegate.java
index 5f06bfd..25a85c4 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractProvisioningJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractProvisioningJobDelegate.java
@@ -22,6 +22,7 @@ import java.lang.reflect.ParameterizedType;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Objects;
 import javax.annotation.Resource;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
@@ -36,7 +37,7 @@ import org.apache.syncope.core.persistence.api.entity.task.ProvisioningTask;
 import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
 import org.apache.syncope.core.provisioning.api.Connector;
 import org.apache.syncope.core.provisioning.api.ConnectorFactory;
-import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.core.provisioning.java.job.AbstractSchedTaskJobDelegate;
 import org.apache.syncope.core.provisioning.java.job.TaskJob;
 import org.quartz.JobExecutionException;
@@ -50,6 +51,37 @@ public abstract class AbstractProvisioningJobDelegate<T extends ProvisioningTask
 
     private static final String LINKED_ACCOUNT = "LINKED_ACCOUNT";
 
+    /**
+     * Helper method to invoke logging per provisioning result, for the given trace level.
+     *
+     * @param results provisioning results
+     * @param level trace level
+     * @return report as string
+     */
+    public static String generate(final Collection<ProvisioningReport> results, final TraceLevel level) {
+        StringBuilder sb = new StringBuilder();
+
+        results.stream().map(result -> {
+            if (level == TraceLevel.SUMMARY) {
+                // No per entry log in this case.
+                return null;
+            } else if (level == TraceLevel.FAILURES && result.getStatus() == ProvisioningReport.Status.FAILURE) {
+                // only report failures
+                return String.format("Failed %s (key/name): %s/%s with message: %s",
+                        result.getOperation(), result.getKey(), result.getName(), result.getMessage());
+            } else {
+                // All
+                return String.format("%s %s (key/name): %s/%s %s",
+                        result.getOperation(), result.getStatus(), result.getKey(), result.getName(),
+                        StringUtils.isBlank(result.getMessage())
+                        ? StringUtils.EMPTY
+                        : "with message: " + result.getMessage());
+            }
+        }).filter(Objects::nonNull).forEach(report -> sb.append(report).append('\n'));
+
+        return sb.toString();
+    }
+
     @Resource(name = "adminUser")
     protected String adminUser;
 
@@ -412,71 +444,71 @@ public abstract class AbstractProvisioningJobDelegate<T extends ProvisioningTask
             if (includeUser) {
                 if (!uFailCreate.isEmpty()) {
                     report.append("\n\nUsers failed to create: ");
-                    report.append(ProvisioningReport.generate(uFailCreate, traceLevel));
+                    report.append(generate(uFailCreate, traceLevel));
                 }
                 if (!uFailUpdate.isEmpty()) {
                     report.append("\nUsers failed to update: ");
-                    report.append(ProvisioningReport.generate(uFailUpdate, traceLevel));
+                    report.append(generate(uFailUpdate, traceLevel));
                 }
                 if (!uFailDelete.isEmpty()) {
                     report.append("\nUsers failed to delete: ");
-                    report.append(ProvisioningReport.generate(uFailDelete, traceLevel));
+                    report.append(generate(uFailDelete, traceLevel));
                 }
 
                 if (!laFailCreate.isEmpty()) {
                     report.append("\n\nAccounts failed to create: ");
-                    report.append(ProvisioningReport.generate(laFailCreate, traceLevel));
+                    report.append(generate(laFailCreate, traceLevel));
                 }
                 if (!laFailUpdate.isEmpty()) {
                     report.append("\nAccounts failed to update: ");
-                    report.append(ProvisioningReport.generate(laFailUpdate, traceLevel));
+                    report.append(generate(laFailUpdate, traceLevel));
                 }
                 if (!laFailDelete.isEmpty()) {
                     report.append("\nAccounts failed to delete: ");
-                    report.append(ProvisioningReport.generate(laFailDelete, traceLevel));
+                    report.append(generate(laFailDelete, traceLevel));
                 }
             }
 
             if (includeGroup) {
                 if (!gFailCreate.isEmpty()) {
                     report.append("\n\nGroups failed to create: ");
-                    report.append(ProvisioningReport.generate(gFailCreate, traceLevel));
+                    report.append(generate(gFailCreate, traceLevel));
                 }
                 if (!gFailUpdate.isEmpty()) {
                     report.append("\nGroups failed to update: ");
-                    report.append(ProvisioningReport.generate(gFailUpdate, traceLevel));
+                    report.append(generate(gFailUpdate, traceLevel));
                 }
                 if (!gFailDelete.isEmpty()) {
                     report.append("\nGroups failed to delete: ");
-                    report.append(ProvisioningReport.generate(gFailDelete, traceLevel));
+                    report.append(generate(gFailDelete, traceLevel));
                 }
             }
 
             if (includeAnyObject && !aFailCreate.isEmpty()) {
                 report.append("\nAny objects failed to create: ");
-                report.append(ProvisioningReport.generate(aFailCreate, traceLevel));
+                report.append(generate(aFailCreate, traceLevel));
             }
             if (includeAnyObject && !aFailUpdate.isEmpty()) {
                 report.append("\nAny objects failed to update: ");
-                report.append(ProvisioningReport.generate(aFailUpdate, traceLevel));
+                report.append(generate(aFailUpdate, traceLevel));
             }
             if (includeAnyObject && !aFailDelete.isEmpty()) {
                 report.append("\nAny objects failed to delete: ");
-                report.append(ProvisioningReport.generate(aFailDelete, traceLevel));
+                report.append(generate(aFailDelete, traceLevel));
             }
 
             if (includeRealm) {
                 if (!rFailCreate.isEmpty()) {
                     report.append("\nRealms failed to create: ");
-                    report.append(ProvisioningReport.generate(rFailCreate, traceLevel));
+                    report.append(generate(rFailCreate, traceLevel));
                 }
                 if (!rFailUpdate.isEmpty()) {
                     report.append("\nRealms failed to update: ");
-                    report.append(ProvisioningReport.generate(rFailUpdate, traceLevel));
+                    report.append(generate(rFailUpdate, traceLevel));
                 }
                 if (!rFailDelete.isEmpty()) {
                     report.append("\nRealms failed to delete: ");
-                    report.append(ProvisioningReport.generate(rFailDelete, traceLevel));
+                    report.append(generate(rFailDelete, traceLevel));
                 }
             }
         }
@@ -486,110 +518,110 @@ public abstract class AbstractProvisioningJobDelegate<T extends ProvisioningTask
             if (includeUser) {
                 if (!uSuccCreate.isEmpty()) {
                     report.append("\n\nUsers created:\n").
-                            append(ProvisioningReport.generate(uSuccCreate, traceLevel));
+                            append(generate(uSuccCreate, traceLevel));
                 }
                 if (!uSuccUpdate.isEmpty()) {
                     report.append("\nUsers updated:\n").
-                            append(ProvisioningReport.generate(uSuccUpdate, traceLevel));
+                            append(generate(uSuccUpdate, traceLevel));
                 }
                 if (!uSuccDelete.isEmpty()) {
                     report.append("\nUsers deleted:\n").
-                            append(ProvisioningReport.generate(uSuccDelete, traceLevel));
+                            append(generate(uSuccDelete, traceLevel));
                 }
                 if (!uSuccNone.isEmpty()) {
                     report.append("\nUsers no operation:\n").
-                            append(ProvisioningReport.generate(uSuccNone, traceLevel));
+                            append(generate(uSuccNone, traceLevel));
                 }
                 if (!uIgnore.isEmpty()) {
                     report.append("\nUsers ignored:\n").
-                            append(ProvisioningReport.generate(uIgnore, traceLevel));
+                            append(generate(uIgnore, traceLevel));
                 }
 
                 if (!laSuccCreate.isEmpty()) {
                     report.append("\n\nAccounts created:\n").
-                            append(ProvisioningReport.generate(laSuccCreate, traceLevel));
+                            append(generate(laSuccCreate, traceLevel));
                 }
                 if (!laSuccUpdate.isEmpty()) {
                     report.append("\nAccounts updated:\n").
-                            append(ProvisioningReport.generate(laSuccUpdate, traceLevel));
+                            append(generate(laSuccUpdate, traceLevel));
                 }
                 if (!laSuccDelete.isEmpty()) {
                     report.append("\nAccounts deleted:\n").
-                            append(ProvisioningReport.generate(laSuccDelete, traceLevel));
+                            append(generate(laSuccDelete, traceLevel));
                 }
                 if (!laSuccNone.isEmpty()) {
                     report.append("\nAccounts no operation:\n").
-                            append(ProvisioningReport.generate(laSuccNone, traceLevel));
+                            append(generate(laSuccNone, traceLevel));
                 }
                 if (!laIgnore.isEmpty()) {
                     report.append("\nAccounts ignored:\n").
-                            append(ProvisioningReport.generate(laIgnore, traceLevel));
+                            append(generate(laIgnore, traceLevel));
                 }
             }
             if (includeGroup) {
                 if (!gSuccCreate.isEmpty()) {
                     report.append("\n\nGroups created:\n").
-                            append(ProvisioningReport.generate(gSuccCreate, traceLevel));
+                            append(generate(gSuccCreate, traceLevel));
                 }
                 if (!gSuccUpdate.isEmpty()) {
                     report.append("\nGroups updated:\n").
-                            append(ProvisioningReport.generate(gSuccUpdate, traceLevel));
+                            append(generate(gSuccUpdate, traceLevel));
                 }
                 if (!gSuccDelete.isEmpty()) {
                     report.append("\nGroups deleted:\n").
-                            append(ProvisioningReport.generate(gSuccDelete, traceLevel));
+                            append(generate(gSuccDelete, traceLevel));
                 }
                 if (!gSuccNone.isEmpty()) {
                     report.append("\nGroups no operation:\n").
-                            append(ProvisioningReport.generate(gSuccNone, traceLevel));
+                            append(generate(gSuccNone, traceLevel));
                 }
                 if (!gIgnore.isEmpty()) {
                     report.append("\nGroups ignored:\n").
-                            append(ProvisioningReport.generate(gIgnore, traceLevel));
+                            append(generate(gIgnore, traceLevel));
                 }
             }
             if (includeAnyObject) {
                 if (!aSuccCreate.isEmpty()) {
                     report.append("\n\nAny objects created:\n").
-                            append(ProvisioningReport.generate(aSuccCreate, traceLevel));
+                            append(generate(aSuccCreate, traceLevel));
                 }
                 if (!aSuccUpdate.isEmpty()) {
                     report.append("\nAny objects updated:\n").
-                            append(ProvisioningReport.generate(aSuccUpdate, traceLevel));
+                            append(generate(aSuccUpdate, traceLevel));
                 }
                 if (!aSuccDelete.isEmpty()) {
                     report.append("\nAny objects deleted:\n").
-                            append(ProvisioningReport.generate(aSuccDelete, traceLevel));
+                            append(generate(aSuccDelete, traceLevel));
                 }
                 if (!aSuccNone.isEmpty()) {
                     report.append("\nAny objects no operation:\n").
-                            append(ProvisioningReport.generate(aSuccNone, traceLevel));
+                            append(generate(aSuccNone, traceLevel));
                 }
                 if (!aIgnore.isEmpty()) {
                     report.append("\nAny objects ignored:\n").
-                            append(ProvisioningReport.generate(aIgnore, traceLevel));
+                            append(generate(aIgnore, traceLevel));
                 }
             }
             if (includeRealm) {
                 if (!rSuccCreate.isEmpty()) {
                     report.append("\n\nRealms created:\n").
-                            append(ProvisioningReport.generate(rSuccCreate, traceLevel));
+                            append(generate(rSuccCreate, traceLevel));
                 }
                 if (!rSuccUpdate.isEmpty()) {
                     report.append("\nRealms updated:\n").
-                            append(ProvisioningReport.generate(rSuccUpdate, traceLevel));
+                            append(generate(rSuccUpdate, traceLevel));
                 }
                 if (!rSuccDelete.isEmpty()) {
                     report.append("\nRealms deleted:\n").
-                            append(ProvisioningReport.generate(rSuccDelete, traceLevel));
+                            append(generate(rSuccDelete, traceLevel));
                 }
                 if (!rSuccNone.isEmpty()) {
                     report.append("\nRealms no operation:\n").
-                            append(ProvisioningReport.generate(rSuccNone, traceLevel));
+                            append(generate(rSuccNone, traceLevel));
                 }
                 if (!rIgnore.isEmpty()) {
                     report.append("\nRealms ignored:\n").
-                            append(ProvisioningReport.generate(rIgnore, traceLevel));
+                            append(generate(rIgnore, traceLevel));
                 }
             }
         }
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
index fd0ae0e..300e554 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
@@ -54,7 +54,7 @@ import org.apache.syncope.core.provisioning.api.cache.VirAttrCache;
 import org.apache.syncope.core.provisioning.api.cache.VirAttrCacheValue;
 import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
 import org.apache.syncope.core.provisioning.api.pushpull.IgnoreProvisionException;
-import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.core.provisioning.api.pushpull.PullActions;
 import org.apache.syncope.core.persistence.api.dao.PullMatch;
 import org.apache.syncope.core.provisioning.api.pushpull.SyncopePullExecutor;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java
index 8e3635f..de3e6a6 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java
@@ -39,7 +39,7 @@ import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.common.lib.types.UnmatchingRule;
 import org.apache.syncope.core.persistence.api.entity.task.PushTask;
 import org.apache.syncope.core.persistence.api.entity.user.User;
-import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.core.provisioning.api.pushpull.PushActions;
 import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.Entity;
@@ -88,13 +88,6 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
 
     protected abstract String getName(Any<?> any);
 
-    protected void reportPropagation(final ProvisioningReport result, final PropagationReporter reporter) {
-        if (!reporter.getStatuses().isEmpty()) {
-            result.setStatus(toProvisioningReportStatus(reporter.getStatuses().get(0).getStatus()));
-            result.setMessage(reporter.getStatuses().get(0).getFailureReason());
-        }
-    }
-
     protected void update(
             final Any<?> any,
             final Boolean enable,
@@ -494,7 +487,14 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
         }
     }
 
-    protected ResourceOperation toResourceOperation(final UnmatchingRule rule) {
+    protected static void reportPropagation(final ProvisioningReport result, final PropagationReporter reporter) {
+        if (!reporter.getStatuses().isEmpty()) {
+            result.setStatus(toProvisioningReportStatus(reporter.getStatuses().get(0).getStatus()));
+            result.setMessage(reporter.getStatuses().get(0).getFailureReason());
+        }
+    }
+
+    protected static ResourceOperation toResourceOperation(final UnmatchingRule rule) {
         switch (rule) {
             case ASSIGN:
             case PROVISION:
@@ -504,7 +504,7 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
         }
     }
 
-    protected ResourceOperation toResourceOperation(final MatchingRule rule) {
+    protected static ResourceOperation toResourceOperation(final MatchingRule rule) {
         switch (rule) {
             case UPDATE:
                 return ResourceOperation.UPDATE;
@@ -516,7 +516,7 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
         }
     }
 
-    protected ProvisioningReport.Status toProvisioningReportStatus(final ExecStatus status) {
+    protected static ProvisioningReport.Status toProvisioningReportStatus(final ExecStatus status) {
         switch (status) {
             case FAILURE:
                 return ProvisioningReport.Status.FAILURE;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DBPasswordPullActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DBPasswordPullActions.java
index 5805543..7d76887 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DBPasswordPullActions.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DBPasswordPullActions.java
@@ -31,7 +31,7 @@ import org.apache.syncope.core.persistence.api.entity.ConnInstance;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.provisioning.api.Connector;
 import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningProfile;
-import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.core.provisioning.api.pushpull.PullActions;
 import org.identityconnectors.framework.common.objects.SyncDelta;
 import org.quartz.JobExecutionException;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultAnyObjectPullResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultAnyObjectPullResultHandler.java
index d04604d..8948cc4 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultAnyObjectPullResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultAnyObjectPullResultHandler.java
@@ -34,7 +34,7 @@ import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.provisioning.api.AnyObjectProvisioningManager;
 import org.apache.syncope.core.provisioning.api.ProvisioningManager;
 import org.apache.syncope.core.provisioning.api.WorkflowResult;
-import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.identityconnectors.framework.common.objects.SyncDelta;
 import org.apache.syncope.core.provisioning.api.pushpull.AnyObjectPullResultHandler;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultGroupPullResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultGroupPullResultHandler.java
index a163fdf..f5f3395 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultGroupPullResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultGroupPullResultHandler.java
@@ -37,7 +37,7 @@ import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.provisioning.api.GroupProvisioningManager;
 import org.apache.syncope.core.provisioning.api.ProvisioningManager;
 import org.apache.syncope.core.provisioning.api.WorkflowResult;
-import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.identityconnectors.framework.common.objects.SyncDelta;
 import org.apache.syncope.core.provisioning.api.pushpull.GroupPullResultHandler;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
index b15d6bf..461b04d 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
@@ -45,7 +45,7 @@ import org.apache.syncope.core.provisioning.api.PropagationByResource;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationException;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
 import org.apache.syncope.core.provisioning.api.pushpull.IgnoreProvisionException;
-import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.core.provisioning.api.pushpull.PullActions;
 import org.apache.syncope.core.provisioning.api.pushpull.RealmPullResultHandler;
 import org.apache.syncope.core.provisioning.api.pushpull.SyncopePullExecutor;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPushResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPushResultHandler.java
index 746b8ef..0afe207 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPushResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPushResultHandler.java
@@ -45,7 +45,7 @@ import org.apache.syncope.core.provisioning.api.event.AfterHandlingEvent;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
 import org.apache.syncope.core.provisioning.api.pushpull.IgnoreProvisionException;
-import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.core.provisioning.api.pushpull.PushActions;
 import org.apache.syncope.core.provisioning.api.pushpull.RealmPushResultHandler;
 import org.apache.syncope.core.provisioning.java.job.AfterHandlingJob;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultUserPullResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultUserPullResultHandler.java
index 4f29d4e..bb860f1 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultUserPullResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultUserPullResultHandler.java
@@ -56,7 +56,7 @@ import org.apache.syncope.core.provisioning.api.ProvisioningManager;
 import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
 import org.apache.syncope.core.provisioning.api.WorkflowResult;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationException;
-import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.core.provisioning.api.pushpull.PullActions;
 import org.identityconnectors.framework.common.objects.SyncDelta;
 import org.apache.syncope.core.provisioning.api.pushpull.UserPullResultHandler;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultUserPushResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultUserPushResultHandler.java
index ba668a4..78ad501 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultUserPushResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultUserPushResultHandler.java
@@ -45,7 +45,7 @@ import org.apache.syncope.core.provisioning.api.WorkflowResult;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
 import org.apache.syncope.core.provisioning.api.pushpull.IgnoreProvisionException;
-import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.core.provisioning.api.pushpull.PushActions;
 import org.apache.syncope.core.provisioning.api.pushpull.UserPushResultHandler;
 import org.apache.syncope.core.provisioning.java.propagation.DefaultPropagationReporter;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/InboundMatcher.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/InboundMatcher.java
index 78d947d..de1a654 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/InboundMatcher.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/InboundMatcher.java
@@ -58,7 +58,7 @@ import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.persistence.api.entity.resource.Item;
 import org.apache.syncope.core.provisioning.api.VirAttrHandler;
-import org.apache.syncope.core.provisioning.java.IntAttrNameParser;
+import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
 import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
 import org.apache.syncope.core.spring.ImplementationManager;
 import org.identityconnectors.framework.common.objects.Attribute;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java
index 3e00046..6a0ce62 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java
@@ -31,7 +31,7 @@ import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.provisioning.api.Connector;
 import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningProfile;
-import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
 import org.apache.syncope.core.persistence.api.dao.PullMatch;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPPasswordPullActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPPasswordPullActions.java
index 22c83ec..7a5f286 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPPasswordPullActions.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPPasswordPullActions.java
@@ -29,7 +29,7 @@ import org.apache.syncope.common.lib.types.CipherAlgorithm;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningProfile;
-import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.core.provisioning.api.pushpull.PullActions;
 import org.identityconnectors.framework.common.objects.SyncDelta;
 import org.quartz.JobExecutionException;
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 6e98595..41637c6 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
@@ -182,7 +182,7 @@ public class OutboundMatcher {
             LOG.error("Could not match {} with any existing {}", any, provision.getObjectClass(), e);
         }
 
-        if (result.size() == 1) {
+        if (any != null && result.size() == 1) {
             virAttrHandler.setValues(any, result.get(0));
         }
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java
index deebedf..16beb2d 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java
@@ -42,14 +42,14 @@ import org.apache.syncope.core.persistence.api.entity.task.PullTask;
 import org.apache.syncope.core.provisioning.api.Connector;
 import org.apache.syncope.core.provisioning.api.pushpull.GroupPullResultHandler;
 import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningProfile;
-import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.core.provisioning.api.pushpull.PullActions;
 import org.apache.syncope.core.provisioning.api.pushpull.ReconFilterBuilder;
 import org.apache.syncope.core.provisioning.api.pushpull.SyncopePullResultHandler;
 import org.apache.syncope.core.provisioning.api.pushpull.SyncopeSinglePullExecutor;
 import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
-import org.apache.syncope.core.spring.ImplementationManager;
 import org.apache.syncope.core.provisioning.java.utils.TemplateUtils;
+import org.apache.syncope.core.spring.ImplementationManager;
 import org.identityconnectors.framework.common.objects.AttributeBuilder;
 import org.identityconnectors.framework.common.objects.filter.Filter;
 import org.identityconnectors.framework.common.objects.filter.FilterBuilder;
@@ -163,7 +163,7 @@ public class SinglePullJobDelegate extends PullJobDelegate implements SyncopeSin
 
             connector.filteredReconciliation(
                     provision.getObjectClass(),
-                    new AccountReconciliationFilterBuilder(connObjectKey, connObjectValue),
+                    new SingleReconciliationFilterBuilder(connObjectKey, connObjectValue),
                     handler,
                     MappingUtils.buildOperationOptions(mapItems, moreAttrsToGet.toArray(new String[0])));
 
@@ -185,13 +185,13 @@ public class SinglePullJobDelegate extends PullJobDelegate implements SyncopeSin
         }
     }
 
-    class AccountReconciliationFilterBuilder implements ReconFilterBuilder {
+    class SingleReconciliationFilterBuilder implements ReconFilterBuilder {
 
         private final String key;
 
         private final String value;
 
-        AccountReconciliationFilterBuilder(final String key, final String value) {
+        SingleReconciliationFilterBuilder(final String key, final String value) {
             this.key = key;
             this.value = value;
         }
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePushJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePushJobDelegate.java
index 250c2ff..56555f1 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePushJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePushJobDelegate.java
@@ -34,7 +34,7 @@ import org.apache.syncope.core.persistence.api.entity.task.PushTask;
 import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
 import org.apache.syncope.core.provisioning.api.Connector;
 import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningProfile;
-import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.core.provisioning.api.pushpull.PushActions;
 import org.apache.syncope.core.provisioning.api.pushpull.SyncopePushResultHandler;
 import org.apache.syncope.core.provisioning.api.pushpull.SyncopeSinglePushExecutor;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamAnyObjectPushResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamAnyObjectPushResultHandler.java
new file mode 100644
index 0000000..38f5c56
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamAnyObjectPushResultHandler.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java.pushpull.stream;
+
+import java.util.Set;
+import java.util.stream.Stream;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.core.persistence.api.entity.Any;
+import org.apache.syncope.core.persistence.api.entity.resource.Item;
+import org.apache.syncope.core.persistence.api.entity.resource.Provision;
+import org.apache.syncope.core.provisioning.api.DerAttrHandler;
+import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter;
+import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
+import org.apache.syncope.core.provisioning.java.propagation.DefaultPropagationReporter;
+import org.apache.syncope.core.provisioning.java.pushpull.DefaultAnyObjectPushResultHandler;
+import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
+import org.identityconnectors.framework.common.objects.Attribute;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public class StreamAnyObjectPushResultHandler extends DefaultAnyObjectPushResultHandler {
+
+    @Autowired
+    private DerAttrHandler derAttrHandler;
+
+    @Override
+    protected void provision(final Any<?> any, final Boolean enabled, final ProvisioningReport result) {
+        Provision provision = profile.getTask().getResource().getProvisions().get(0);
+
+        Stream<? extends Item> items = MappingUtils.getPropagationItems(provision.getMapping().getItems().stream());
+
+        Pair<String, Set<Attribute>> preparedAttrs = mappingManager.prepareAttrs(any, null, false, enabled, provision);
+
+        PropagationTaskInfo propagationTask = propagationManager.newTask(
+                derAttrHandler,
+                any,
+                profile.getTask().getResource(),
+                ResourceOperation.CREATE,
+                provision,
+                false,
+                items,
+                preparedAttrs);
+        propagationTask.setConnector(profile.getConnector());
+        LOG.debug("PropagationTask created: {}", propagationTask);
+
+        PropagationReporter reporter = new DefaultPropagationReporter();
+        taskExecutor.execute(propagationTask, reporter);
+        reportPropagation(result, reporter);
+    }
+}
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamGroupPushResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamGroupPushResultHandler.java
new file mode 100644
index 0000000..ba5c17a
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamGroupPushResultHandler.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java.pushpull.stream;
+
+import java.util.Set;
+import java.util.stream.Stream;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.core.persistence.api.entity.Any;
+import org.apache.syncope.core.persistence.api.entity.resource.Item;
+import org.apache.syncope.core.persistence.api.entity.resource.Provision;
+import org.apache.syncope.core.provisioning.api.DerAttrHandler;
+import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter;
+import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
+import org.apache.syncope.core.provisioning.java.propagation.DefaultPropagationReporter;
+import org.apache.syncope.core.provisioning.java.pushpull.DefaultGroupPushResultHandler;
+import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
+import org.identityconnectors.framework.common.objects.Attribute;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public class StreamGroupPushResultHandler extends DefaultGroupPushResultHandler {
+
+    @Autowired
+    private DerAttrHandler derAttrHandler;
+
+    @Override
+    protected void provision(final Any<?> any, final Boolean enabled, final ProvisioningReport result) {
+        Provision provision = profile.getTask().getResource().getProvisions().get(0);
+
+        Stream<? extends Item> items = MappingUtils.getPropagationItems(provision.getMapping().getItems().stream());
+
+        Pair<String, Set<Attribute>> preparedAttrs = mappingManager.prepareAttrs(any, null, false, enabled, provision);
+
+        PropagationTaskInfo propagationTask = propagationManager.newTask(
+                derAttrHandler,
+                any,
+                profile.getTask().getResource(),
+                ResourceOperation.CREATE,
+                provision,
+                false,
+                items,
+                preparedAttrs);
+        propagationTask.setConnector(profile.getConnector());
+        LOG.debug("PropagationTask created: {}", propagationTask);
+
+        PropagationReporter reporter = new DefaultPropagationReporter();
+        taskExecutor.execute(propagationTask, reporter);
+        reportPropagation(result, reporter);
+    }
+}
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
new file mode 100644
index 0000000..2ef5254
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java
@@ -0,0 +1,262 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java.pushpull.stream;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Stream;
+import org.apache.syncope.common.lib.to.PullTaskTO;
+import org.apache.syncope.common.lib.types.ConflictResolutionAction;
+import org.apache.syncope.common.lib.types.ImplementationType;
+import org.apache.syncope.common.lib.types.MappingPurpose;
+import org.apache.syncope.common.lib.types.PullMode;
+import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
+import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.core.persistence.api.dao.RealmDAO;
+import org.apache.syncope.core.persistence.api.entity.AnyType;
+import org.apache.syncope.core.persistence.api.entity.AnyUtils;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
+import org.apache.syncope.core.persistence.api.entity.PlainSchema;
+import org.apache.syncope.core.persistence.api.entity.VirSchema;
+import org.apache.syncope.core.persistence.api.entity.policy.PullCorrelationRuleEntity;
+import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
+import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.resource.Item;
+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.PullTask;
+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.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;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.apache.syncope.core.provisioning.api.pushpull.stream.SyncopeStreamPullExecutor;
+import org.apache.syncope.core.provisioning.java.pushpull.PullJobDelegate;
+import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
+import org.identityconnectors.framework.common.objects.ObjectClass;
+
+@Component
+public class StreamPullJobDelegate extends PullJobDelegate implements SyncopeStreamPullExecutor {
+
+    @Autowired
+    private ImplementationDAO implementationDAO;
+
+    @Autowired
+    private RealmDAO realmDAO;
+
+    @Autowired
+    private PlainSchemaDAO plainSchemaDAO;
+
+    private PullPolicy pullPolicy(
+            final AnyType anyType,
+            final ConflictResolutionAction conflictResolutionAction,
+            final String pullCorrelationRule) {
+
+        PullCorrelationRuleEntity pullCorrelationRuleEntity = null;
+        if (pullCorrelationRule != null) {
+            Implementation implementation = implementationDAO.find(pullCorrelationRule);
+            if (implementation == null || implementation.getType() != ImplementationType.PULL_ACTIONS) {
+                LOG.debug("Invalid " + Implementation.class.getSimpleName() + " {}, ignoring...", pullCorrelationRule);
+            } else {
+                pullCorrelationRuleEntity = entityFactory.newEntity(PullCorrelationRuleEntity.class);
+                pullCorrelationRuleEntity.setAnyType(anyType);
+                pullCorrelationRuleEntity.setImplementation(implementation);
+            }
+        }
+
+        PullPolicy pullPolicy = entityFactory.newEntity(PullPolicy.class);
+        pullPolicy.setConflictResolutionAction(conflictResolutionAction);
+
+        if (pullCorrelationRuleEntity != null) {
+            pullPolicy.add(pullCorrelationRuleEntity);
+            pullCorrelationRuleEntity.setPullPolicy(pullPolicy);
+        }
+
+        return pullPolicy;
+    }
+
+    private Provision provision(
+            final AnyType anyType,
+            final String keyColumn,
+            final List<String> columns) throws JobExecutionException {
+
+        Provision provision = entityFactory.newEntity(Provision.class);
+        provision.setAnyType(anyType);
+        provision.setObjectClass(new ObjectClass(anyType.getKey()));
+
+        Mapping mapping = entityFactory.newEntity(Mapping.class);
+        provision.setMapping(mapping);
+        mapping.setProvision(provision);
+
+        AnyUtils anyUtils = anyUtilsFactory.getInstance(anyType.getKind());
+        if (anyUtils.getField(keyColumn) == null) {
+            PlainSchema keyColumnSchema = plainSchemaDAO.find(keyColumn);
+            if (keyColumnSchema == null) {
+                throw new JobExecutionException("Plain Schema for key column not found: " + keyColumn);
+            }
+        }
+
+        MappingItem connObjectKeyItem = entityFactory.newEntity(MappingItem.class);
+        connObjectKeyItem.setExtAttrName(keyColumn);
+        connObjectKeyItem.setIntAttrName(keyColumn);
+        connObjectKeyItem.setPurpose(MappingPurpose.PULL);
+        mapping.setConnObjectKeyItem(connObjectKeyItem);
+
+        columns.stream().
+                filter(column -> anyUtils.getField(column) != null
+                || plainSchemaDAO.find(column) != null || virSchemaDAO.find(column) != null).
+                map(column -> {
+                    MappingItem item = entityFactory.newEntity(MappingItem.class);
+                    item.setExtAttrName(column);
+                    item.setIntAttrName(column);
+                    item.setPurpose(MappingPurpose.PULL);
+                    mapping.add(item);
+                    return item;
+                }).forEach(mapping::add);
+
+        return provision;
+    }
+
+    private ExternalResource externalResource(
+            final AnyType anyType,
+            final String keyColumn,
+            final List<String> columns,
+            final ConflictResolutionAction conflictResolutionAction,
+            final String pullCorrelationRule) throws JobExecutionException {
+
+        Provision provision = provision(anyType, keyColumn, columns);
+
+        ExternalResource resource = entityFactory.newEntity(ExternalResource.class);
+        resource.add(provision);
+        provision.setResource(resource);
+
+        resource.setPullPolicy(pullPolicy(anyType, conflictResolutionAction, pullCorrelationRule));
+
+        return resource;
+    }
+
+    @Override
+    public List<ProvisioningReport> pull(
+            final AnyType anyType,
+            final String keyColumn,
+            final List<String> columns,
+            final ConflictResolutionAction conflictResolutionAction,
+            final String pullCorrelationRule,
+            final StreamConnector connector,
+            final PullTaskTO pullTaskTO) throws JobExecutionException {
+
+        LOG.debug("Executing stream pull");
+
+        List<PullActions> actions = new ArrayList<>();
+        pullTaskTO.getActions().forEach(key -> {
+            Implementation impl = implementationDAO.find(key);
+            if (impl == null || impl.getType() != ImplementationType.PULL_ACTIONS) {
+                LOG.debug("Invalid " + Implementation.class.getSimpleName() + " {}, ignoring...", key);
+            } else {
+                try {
+                    actions.add(ImplementationManager.build(impl));
+                } catch (Exception e) {
+                    LOG.warn("While building {}", impl, e);
+                }
+            }
+        });
+
+        try {
+            ExternalResource resource =
+                    externalResource(anyType, keyColumn, columns, conflictResolutionAction, pullCorrelationRule);
+            Provision provision = resource.getProvisions().get(0);
+
+            PullTask pullTask = entityFactory.newEntity(PullTask.class);
+            pullTask.setResource(resource);
+            pullTask.setMatchingRule(pullTaskTO.getMatchingRule());
+            pullTask.setUnmatchingRule(pullTaskTO.getUnmatchingRule());
+            pullTask.setPullMode(PullMode.FULL_RECONCILIATION);
+            pullTask.setPerformCreate(true);
+            pullTask.setPerformUpdate(true);
+            pullTask.setPerformDelete(false);
+            pullTask.setSyncStatus(false);
+            pullTask.setDestinationRealm(realmDAO.findByFullPath(pullTaskTO.getDestinationRealm()));
+            pullTask.setRemediation(pullTaskTO.isRemediation());
+
+            profile = new ProvisioningProfile<>(connector, pullTask);
+            profile.setDryRun(false);
+            profile.setConflictResolutionAction(ConflictResolutionAction.FIRSTMATCH);
+            profile.getActions().addAll(actions);
+
+            for (PullActions action : actions) {
+                action.beforeAll(profile);
+            }
+
+            SyncopePullResultHandler handler;
+            GroupPullResultHandler ghandler = buildGroupHandler();
+            switch (anyType.getKind()) {
+                case USER:
+                    handler = buildUserHandler();
+                    break;
+
+                case GROUP:
+                    handler = ghandler;
+                    break;
+
+                case ANY_OBJECT:
+                default:
+                    handler = buildAnyObjectHandler();
+            }
+            handler.setProfile(profile);
+            handler.setPullExecutor(this);
+
+            // execute filtered pull
+            Set<String> moreAttrsToGet = new HashSet<>();
+            actions.forEach(action -> moreAttrsToGet.addAll(action.moreAttrsToGet(profile, provision)));
+
+            Stream<? extends Item> mapItems = Stream.concat(
+                    MappingUtils.getPullItems(provision.getMapping().getItems().stream()),
+                    virSchemaDAO.findByProvision(provision).stream().map(VirSchema::asLinkingMappingItem));
+
+            connector.fullReconciliation(
+                    provision.getObjectClass(),
+                    handler,
+                    MappingUtils.buildOperationOptions(mapItems, moreAttrsToGet.toArray(new String[0])));
+
+            try {
+                setGroupOwners(ghandler);
+            } catch (Exception e) {
+                LOG.error("While setting group owners", e);
+            }
+
+            for (PullActions action : actions) {
+                action.afterAll(profile);
+            }
+
+            return profile.getResults();
+        } catch (Exception e) {
+            throw e instanceof JobExecutionException
+                    ? (JobExecutionException) e
+                    : new JobExecutionException("While stream pulling", e);
+        }
+    }
+}
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
new file mode 100644
index 0000000..dc129b6
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegate.java
@@ -0,0 +1,187 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java.pushpull.stream;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
+import org.apache.syncope.common.lib.to.PushTaskTO;
+import org.apache.syncope.common.lib.types.ConflictResolutionAction;
+import org.apache.syncope.common.lib.types.ImplementationType;
+import org.apache.syncope.common.lib.types.MappingPurpose;
+import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
+import org.apache.syncope.core.persistence.api.entity.Any;
+import org.apache.syncope.core.persistence.api.entity.AnyType;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
+import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
+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.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;
+import org.apache.syncope.core.provisioning.java.pushpull.PushJobDelegate;
+import org.apache.syncope.core.spring.ApplicationContextProvider;
+import org.apache.syncope.core.spring.ImplementationManager;
+import org.apache.syncope.core.spring.security.SecureRandomUtils;
+import org.identityconnectors.framework.common.objects.ObjectClass;
+import org.quartz.JobExecutionException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.stereotype.Component;
+
+@Component
+public class StreamPushJobDelegate extends PushJobDelegate implements SyncopeStreamPushExecutor {
+
+    @Autowired
+    private ImplementationDAO implementationDAO;
+
+    @Override
+    protected AnyObjectPushResultHandler buildAnyObjectHandler() {
+        return (AnyObjectPushResultHandler) ApplicationContextProvider.getBeanFactory().
+                createBean(StreamAnyObjectPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
+    }
+
+    @Override
+    protected UserPushResultHandler buildUserHandler() {
+        return (UserPushResultHandler) ApplicationContextProvider.getBeanFactory().
+                createBean(StreamUserPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
+    }
+
+    @Override
+    protected GroupPushResultHandler buildGroupHandler() {
+        return (GroupPushResultHandler) ApplicationContextProvider.getBeanFactory().
+                createBean(StreamGroupPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
+    }
+
+    private ExternalResource externalResource(
+            final AnyType anyType, final List<String> columns) throws JobExecutionException {
+
+        Provision provision = entityFactory.newEntity(Provision.class);
+        provision.setAnyType(anyType);
+        provision.setObjectClass(new ObjectClass(anyType.getKey()));
+
+        Mapping mapping = entityFactory.newEntity(Mapping.class);
+        provision.setMapping(mapping);
+        mapping.setProvision(provision);
+
+        MappingItem connObjectKeyItem = entityFactory.newEntity(MappingItem.class);
+        connObjectKeyItem.setExtAttrName("key");
+        connObjectKeyItem.setIntAttrName("key");
+        connObjectKeyItem.setPurpose(MappingPurpose.NONE);
+        mapping.setConnObjectKeyItem(connObjectKeyItem);
+
+        columns.stream().map(column -> {
+            MappingItem item = entityFactory.newEntity(MappingItem.class);
+            item.setExtAttrName(column);
+            item.setIntAttrName(column);
+            item.setPurpose(MappingPurpose.PROPAGATION);
+            mapping.add(item);
+            return item;
+        }).forEach(mapping::add);
+
+        ExternalResource resource = entityFactory.newEntity(ExternalResource.class);
+        resource.setKey("Stream_" + SecureRandomUtils.generateRandomUUID().toString());
+        resource.add(provision);
+        provision.setResource(resource);
+
+        return resource;
+    }
+
+    @Override
+    public List<ProvisioningReport> push(
+            final AnyType anyType,
+            final List<? extends Any<?>> anys,
+            final List<String> columns,
+            final StreamConnector connector,
+            final PushTaskTO pushTaskTO) throws JobExecutionException {
+
+        LOG.debug("Executing stream push");
+
+        List<PushActions> actions = new ArrayList<>();
+        pushTaskTO.getActions().forEach(key -> {
+            Implementation impl = implementationDAO.find(key);
+            if (impl == null || impl.getType() != ImplementationType.PUSH_ACTIONS) {
+                LOG.debug("Invalid " + Implementation.class.getSimpleName() + " {}, ignoring...", key);
+            } else {
+                try {
+                    actions.add(ImplementationManager.build(impl));
+                } catch (Exception e) {
+                    LOG.warn("While building {}", impl, e);
+                }
+            }
+        });
+
+        try {
+            ExternalResource resource = externalResource(anyType, columns);
+            Provision provision = resource.getProvisions().get(0);
+
+            PushTask pushTask = entityFactory.newEntity(PushTask.class);
+            pushTask.setResource(resource);
+            pushTask.setMatchingRule(pushTaskTO.getMatchingRule());
+            pushTask.setUnmatchingRule(pushTaskTO.getUnmatchingRule());
+            pushTask.setPerformCreate(true);
+            pushTask.setPerformUpdate(true);
+            pushTask.setPerformDelete(true);
+            pushTask.setSyncStatus(false);
+
+            profile = new ProvisioningProfile<>(connector, pushTask);
+            profile.getActions().addAll(actions);
+            profile.setConflictResolutionAction(ConflictResolutionAction.FIRSTMATCH);
+
+            for (PushActions action : actions) {
+                action.beforeAll(profile);
+            }
+
+            SyncopePushResultHandler handler;
+            switch (provision.getAnyType().getKind()) {
+                case USER:
+                    handler = buildUserHandler();
+                    break;
+
+                case GROUP:
+                    handler = buildGroupHandler();
+                    break;
+
+                case ANY_OBJECT:
+                default:
+                    handler = buildAnyObjectHandler();
+            }
+            handler.setProfile(profile);
+
+            doHandle(anys, handler, provision.getResource());
+
+            for (PushActions action : actions) {
+                action.afterAll(profile);
+            }
+
+            return profile.getResults();
+        } catch (Exception e) {
+            throw e instanceof JobExecutionException
+                    ? (JobExecutionException) e
+                    : new JobExecutionException("While stream pushing", e);
+        }
+    }
+}
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamUserPushResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamUserPushResultHandler.java
new file mode 100644
index 0000000..403750e
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamUserPushResultHandler.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java.pushpull.stream;
+
+import java.util.Set;
+import java.util.stream.Stream;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.core.persistence.api.entity.Any;
+import org.apache.syncope.core.persistence.api.entity.resource.Item;
+import org.apache.syncope.core.persistence.api.entity.resource.Provision;
+import org.apache.syncope.core.provisioning.api.DerAttrHandler;
+import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter;
+import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
+import org.apache.syncope.core.provisioning.java.propagation.DefaultPropagationReporter;
+import org.apache.syncope.core.provisioning.java.pushpull.DefaultUserPushResultHandler;
+import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
+import org.identityconnectors.framework.common.objects.Attribute;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public class StreamUserPushResultHandler extends DefaultUserPushResultHandler {
+
+    @Autowired
+    private DerAttrHandler derAttrHandler;
+
+    @Override
+    protected void provision(final Any<?> any, final Boolean enabled, final ProvisioningReport result) {
+        Provision provision = profile.getTask().getResource().getProvisions().get(0);
+
+        Stream<? extends Item> items = MappingUtils.getPropagationItems(provision.getMapping().getItems().stream());
+
+        Pair<String, Set<Attribute>> preparedAttrs = mappingManager.prepareAttrs(any, null, false, enabled, provision);
+
+        PropagationTaskInfo propagationTask = propagationManager.newTask(
+                derAttrHandler,
+                any,
+                profile.getTask().getResource(),
+                ResourceOperation.CREATE,
+                provision,
+                false,
+                items,
+                preparedAttrs);
+        propagationTask.setConnector(profile.getConnector());
+        LOG.debug("PropagationTask created: {}", propagationTask);
+
+        PropagationReporter reporter = new DefaultPropagationReporter();
+        taskExecutor.execute(propagationTask, reporter);
+        reportPropagation(result, reporter);
+    }
+}
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 45fc590..1f52585 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
@@ -25,20 +25,14 @@ import java.util.List;
 import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Stream;
-import org.apache.commons.jexl3.JexlContext;
-import org.apache.commons.jexl3.MapContext;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.types.MappingPurpose;
-import org.apache.syncope.core.persistence.api.entity.Any;
-import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.resource.Item;
 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.OrgUnit;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.apache.syncope.core.provisioning.java.data.JEXLItemTransformerImpl;
-import org.apache.syncope.core.provisioning.java.jexl.JexlUtils;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.identityconnectors.framework.common.objects.Name;
 import org.identityconnectors.framework.common.objects.OperationOptions;
@@ -77,87 +71,6 @@ public final class MappingUtils {
                 item -> item.getPurpose() == MappingPurpose.PULL || item.getPurpose() == MappingPurpose.BOTH);
     }
 
-    private static Name getName(final String evalConnObjectLink, final String connObjectKey) {
-        // If connObjectLink evaluates to an empty string, just use the provided connObjectKey as Name(),
-        // otherwise evaluated connObjectLink expression is taken as Name().
-        Name name;
-        if (StringUtils.isBlank(evalConnObjectLink)) {
-            // add connObjectKey as __NAME__ attribute ...
-            LOG.debug("Add connObjectKey [{}] as {}", connObjectKey, Name.NAME);
-            name = new Name(connObjectKey);
-        } else {
-            LOG.debug("Add connObjectLink [{}] as {}", evalConnObjectLink, Name.NAME);
-            name = new Name(evalConnObjectLink);
-
-            // connObjectKey not propagated: it will be used to set the value for __UID__ attribute
-            LOG.debug("connObjectKey will be used just as {} attribute", Uid.NAME);
-        }
-
-        return name;
-    }
-
-    /**
-     * Build __NAME__ for propagation.
-     * First look if there is a defined connObjectLink for the given resource (and in
-     * this case evaluate as JEXL); otherwise, take given connObjectKey.
-     *
-     * @param any given any object
-     * @param provision external resource
-     * @param connObjectKey connector object key
-     * @return the value to be propagated as __NAME__
-     */
-    public static Name evaluateNAME(final Any<?> any, final Provision provision, final String connObjectKey) {
-        if (StringUtils.isBlank(connObjectKey)) {
-            // LOG error but avoid to throw exception: leave it to the external resource
-            LOG.warn("Missing ConnObjectKey value for {}: ", provision.getResource());
-        }
-
-        // Evaluate connObjectKey expression
-        String connObjectLink = provision == null || provision.getMapping() == null
-                ? null
-                : provision.getMapping().getConnObjectLink();
-        String evalConnObjectLink = null;
-        if (StringUtils.isNotBlank(connObjectLink)) {
-            JexlContext jexlContext = new MapContext();
-            JexlUtils.addFieldsToContext(any, jexlContext);
-            JexlUtils.addPlainAttrsToContext(any.getPlainAttrs(), jexlContext);
-            JexlUtils.addDerAttrsToContext(any, jexlContext);
-            evalConnObjectLink = JexlUtils.evaluate(connObjectLink, jexlContext);
-        }
-
-        return getName(evalConnObjectLink, connObjectKey);
-    }
-
-    /**
-     * Build __NAME__ for propagation.
-     * First look if there is a defined connObjectLink for the given resource (and in
-     * this case evaluate as JEXL); otherwise, take given connObjectKey.
-     *
-     * @param realm given any object
-     * @param orgUnit external resource
-     * @param connObjectKey connector object key
-     * @return the value to be propagated as __NAME__
-     */
-    public static Name evaluateNAME(final Realm realm, final OrgUnit orgUnit, final String connObjectKey) {
-        if (StringUtils.isBlank(connObjectKey)) {
-            // LOG error but avoid to throw exception: leave it to the external resource
-            LOG.warn("Missing ConnObjectKey value for {}: ", orgUnit.getResource());
-        }
-
-        // Evaluate connObjectKey expression
-        String connObjectLink = orgUnit == null
-                ? null
-                : orgUnit.getConnObjectLink();
-        String evalConnObjectLink = null;
-        if (StringUtils.isNotBlank(connObjectLink)) {
-            JexlContext jexlContext = new MapContext();
-            JexlUtils.addFieldsToContext(realm, jexlContext);
-            evalConnObjectLink = JexlUtils.evaluate(connObjectLink, jexlContext);
-        }
-
-        return getName(evalConnObjectLink, connObjectKey);
-    }
-
     public static List<ItemTransformer> getItemTransformers(final Item item) {
         List<ItemTransformer> result = new ArrayList<>();
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/TemplateUtils.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/TemplateUtils.java
index 228952d..51d6e1f 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/TemplateUtils.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/TemplateUtils.java
@@ -31,12 +31,12 @@ import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.to.GroupableRelatableTO;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
-import org.apache.syncope.core.provisioning.java.jexl.JexlUtils;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.entity.AnyTemplate;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
diff --git a/core/provisioning-java/src/main/resources/provisioningContext.xml b/core/provisioning-java/src/main/resources/provisioningContext.xml
index 771948d..bcf9a35 100644
--- a/core/provisioning-java/src/main/resources/provisioningContext.xml
+++ b/core/provisioning-java/src/main/resources/provisioningContext.xml
@@ -118,5 +118,5 @@ under the License.
     <property name="stringLocations" value="${connid.locations}"/>
   </bean>
 
-  <bean class="org.apache.syncope.core.provisioning.java.IntAttrNameParser"/>
+  <bean class="org.apache.syncope.core.provisioning.api.IntAttrNameParser"/>
 </beans>
diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/AbstractTest.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/AbstractTest.java
index ad660e7..186910d 100644
--- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/AbstractTest.java
+++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/AbstractTest.java
@@ -19,8 +19,11 @@
 package org.apache.syncope.core.provisioning.java;
 
 import javax.persistence.EntityManager;
+import org.apache.syncope.common.lib.types.StandardEntitlement;
+import org.apache.syncope.core.provisioning.api.EntitlementsHolder;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.junit.jupiter.api.BeforeAll;
 import org.springframework.orm.jpa.EntityManagerFactoryUtils;
 import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
 
@@ -42,4 +45,9 @@ public abstract class AbstractTest {
 
         return entityManager;
     }
+
+    @BeforeAll
+    public static void init() {
+        EntitlementsHolder.getInstance().init(StandardEntitlement.values());
+    }
 }
diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/TestInitializer.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/TestInitializer.java
index 98392af..d55f176 100644
--- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/TestInitializer.java
+++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/TestInitializer.java
@@ -33,5 +33,4 @@ public class TestInitializer implements InitializingBean {
     public void afterPropertiesSet() throws Exception {
         contentLoader.load();
     }
-
 }
diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/ResourceDataBinderTest.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderTest.java
similarity index 98%
rename from core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/ResourceDataBinderTest.java
rename to core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderTest.java
index 4ebb037..4033eb5 100644
--- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/ResourceDataBinderTest.java
+++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderTest.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.provisioning.java;
+package org.apache.syncope.core.provisioning.java.data;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -40,6 +40,7 @@ import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
 import org.apache.syncope.core.provisioning.api.data.ResourceDataBinder;
+import org.apache.syncope.core.provisioning.java.AbstractTest;
 import org.apache.syncope.core.spring.security.SyncopeAuthenticationDetails;
 import org.apache.syncope.core.spring.security.SyncopeGrantedAuthority;
 import org.identityconnectors.framework.common.objects.ObjectClass;
diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/MailTemplateTest.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/jexl/MailTemplateTest.java
similarity index 96%
rename from core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/MailTemplateTest.java
rename to core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/jexl/MailTemplateTest.java
index ea1f551..46d56d5 100644
--- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/MailTemplateTest.java
+++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/jexl/MailTemplateTest.java
@@ -16,7 +16,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.provisioning.java;
+package org.apache.syncope.core.provisioning.java.jexl;
+
+import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
 
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -35,8 +37,8 @@ import org.apache.commons.lang3.SerializationUtils;
 import org.apache.syncope.common.lib.to.AttrTO;
 import org.apache.syncope.common.lib.to.MembershipTO;
 import org.apache.syncope.common.lib.to.UserTO;
-import org.apache.syncope.core.provisioning.java.jexl.JexlUtils;
 import org.apache.syncope.core.persistence.api.dao.MailTemplateDAO;
+import org.apache.syncope.core.provisioning.java.AbstractTest;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/MappingTest.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/jexl/MappingTest.java
similarity index 80%
rename from core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/MappingTest.java
rename to core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/jexl/MappingTest.java
index ea47539..862a866 100644
--- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/MappingTest.java
+++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/jexl/MappingTest.java
@@ -16,7 +16,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.provisioning.java;
+package org.apache.syncope.core.provisioning.java.jexl;
+
+import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -31,9 +33,7 @@ import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.apache.syncope.core.persistence.api.entity.user.User;
-import org.apache.syncope.core.provisioning.java.jexl.JexlUtils;
-import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
-import org.identityconnectors.framework.common.objects.Name;
+import org.apache.syncope.core.provisioning.java.AbstractTest;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
@@ -66,14 +66,16 @@ public class MappingTest extends AbstractTest {
         User user = userDAO.findByUsername("rossini");
         assertNotNull(user);
 
-        Name name = MappingUtils.evaluateNAME(user, provision, user.getUsername());
-        assertEquals("uid=rossini,ou=people,o=isp", name.getNameValue());
+        JexlContext jexlContext = new MapContext();
+        JexlUtils.addFieldsToContext(user, jexlContext);
+        JexlUtils.addPlainAttrsToContext(user.getPlainAttrs(), jexlContext);
 
-        provision.getMapping().setConnObjectLink(
-                "'uid=' + username + realm.replaceAll('/', ',o=') + ',ou=people,o=isp'");
+        assertEquals(
+                "uid=rossini,ou=people,o=isp",
+                JexlUtils.evaluate(provision.getMapping().getConnObjectLink(), jexlContext));
 
-        name = MappingUtils.evaluateNAME(user, provision, user.getUsername());
-        assertEquals("uid=rossini,o=even,ou=people,o=isp", name.getNameValue());
+        String connObjectLink = "'uid=' + username + realm.replaceAll('/', ',o=') + ',ou=people,o=isp'";
+        assertEquals("uid=rossini,o=even,ou=people,o=isp", JexlUtils.evaluate(connObjectLink, jexlContext));
     }
 
     @Test
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
new file mode 100644
index 0000000..db4e76e
--- /dev/null
+++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegateTest.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.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 java.io.IOException;
+import java.util.Collections;
+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;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.ConflictResolutionAction;
+import org.apache.syncope.common.lib.types.MatchingRule;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+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.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.core.provisioning.api.pushpull.stream.SyncopeStreamPullExecutor;
+import org.apache.syncope.core.provisioning.java.AbstractTest;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.junit.jupiter.api.Test;
+import org.quartz.JobExecutionException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional("Master")
+public class StreamPullJobDelegateTest extends AbstractTest {
+
+    @Autowired
+    private SyncopeStreamPullExecutor streamPullExecutor;
+
+    @Autowired
+    private AnyTypeDAO anyTypeDAO;
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Test
+    public void pull() throws JobExecutionException, IOException {
+        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 = Collections.singletonList(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<String> columns = user.keySet().stream().collect(Collectors.toList());
+
+        List<ProvisioningReport> results = AuthContextUtils.execWithAuthContext(SyncopeConstants.MASTER_DOMAIN, () -> {
+            try {
+                return streamPullExecutor.pull(
+                        anyTypeDAO.findUser(),
+                        "username",
+                        columns,
+                        ConflictResolutionAction.IGNORE,
+                        null,
+                        new StreamConnector("username", null, itor, null),
+                        pullTask);
+            } catch (JobExecutionException e) {
+                throw new RuntimeException(e);
+            }
+        });
+        assertEquals(1, results.size());
+
+        assertEquals(AnyTypeKind.USER.name(), results.get(0).getAnyType());
+        assertNotNull(results.get(0).getKey());
+        assertEquals("donizetti", results.get(0).getName());
+        assertEquals("donizetti", results.get(0).getUidValue());
+        assertEquals(ResourceOperation.CREATE, results.get(0).getOperation());
+        assertEquals(ProvisioningReport.Status.SUCCESS, results.get(0).getStatus());
+
+        User donizetti = userDAO.find(results.get(0).getKey());
+        assertNotNull(donizetti);
+        assertEquals("donizetti", donizetti.getUsername());
+        assertEquals("Gaetano", donizetti.getPlainAttr("firstname").get().getValuesAsStrings().get(0));
+    }
+}
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
new file mode 100644
index 0000000..a8931a7
--- /dev/null
+++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegateTest.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.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 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.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
+import org.apache.syncope.common.lib.to.PushTaskTO;
+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;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional("Master")
+public class StreamPushJobDelegateTest extends AbstractTest {
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    @Autowired
+    private SyncopeStreamPushExecutor streamPushExecutor;
+
+    @Autowired
+    private AnyTypeDAO anyTypeDAO;
+
+    @Autowired
+    private UserDAO userDAO;
+
+    @Test
+    public void push() throws IOException {
+        PipedInputStream in = new PipedInputStream();
+        PipedOutputStream os = new PipedOutputStream(in);
+
+        PushTaskTO pushTask = new PushTaskTO();
+        pushTask.setMatchingRule(MatchingRule.UPDATE);
+        pushTask.setUnmatchingRule(UnmatchingRule.PROVISION);
+
+        List<ProvisioningReport> results = AuthContextUtils.execWithAuthContext(SyncopeConstants.MASTER_DOMAIN, () -> {
+            try (SequenceWriter writer = MAPPER.writer().forType(Map.class).writeValues(os)) {
+                writer.init(true);
+
+                return streamPushExecutor.push(
+                        anyTypeDAO.findUser(),
+                        userDAO.findAll(1, 100),
+                        Arrays.asList("username", "firstname", "surname", "email", "status", "loginDate"),
+                        new StreamConnector(null, null, null, writer),
+                        pushTask);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        });
+        assertEquals(userDAO.count(), results.size());
+
+        MappingIterator<Map<String, String>> reader = MAPPER.readerFor(Map.class).readValues(in);
+
+        for (int i = 0; i < results.size() && reader.hasNext(); i++) {
+            Map<String, String> row = reader.next();
+
+            assertEquals(results.get(i).getName(), row.get("username"));
+            assertEquals(userDAO.findByUsername(row.get("username")).getStatus(), row.get("status"));
+
+            switch (row.get("username")) {
+                case "rossini":
+                    assertNull(row.get("email"));
+                    assertTrue(row.get("loginDate").contains(","));
+                    break;
+
+                case "verdi":
+                    assertEquals("verdi@syncope.org", row.get("email"));
+                    assertNull(row.get("loginDate"));
+                    break;
+
+                case "bellini":
+                    assertNull(row.get("email"));
+                    assertFalse(row.get("loginDate").contains(","));
+                    break;
+
+                default:
+                    break;
+            }
+        }
+    }
+}
diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ReconciliationServiceImpl.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ReconciliationServiceImpl.java
index 7b77c2f..6f3b1a1 100644
--- a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ReconciliationServiceImpl.java
+++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ReconciliationServiceImpl.java
@@ -18,13 +18,27 @@
  */
 package org.apache.syncope.core.rest.cxf.service;
 
+import java.io.InputStream;
+import java.util.List;
 import javax.validation.ValidationException;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.StreamingOutput;
+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.PullTaskTO;
 import org.apache.syncope.common.lib.to.PushTaskTO;
 import org.apache.syncope.common.lib.to.ReconStatus;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.beans.AnyQuery;
+import org.apache.syncope.common.rest.api.beans.CSVPullSpec;
+import org.apache.syncope.common.rest.api.beans.CSVPushSpec;
 import org.apache.syncope.common.rest.api.beans.ReconQuery;
 import org.apache.syncope.common.rest.api.service.ReconciliationService;
 import org.apache.syncope.core.logic.ReconciliationLogic;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -59,4 +73,33 @@ public class ReconciliationServiceImpl extends AbstractServiceImpl implements Re
         validate(reconQuery);
         logic.pull(reconQuery, pullTask);
     }
+
+    @Override
+    public Response push(final AnyQuery anyQuery, final CSVPushSpec spec) {
+        String realm = StringUtils.prependIfMissing(anyQuery.getRealm(), SyncopeConstants.ROOT_REALM);
+
+        SearchCond searchCond = StringUtils.isBlank(anyQuery.getFiql())
+                ? null
+                : getSearchCond(anyQuery.getFiql(), realm);
+
+        StreamingOutput sout = (os) -> logic.push(
+                searchCond,
+                anyQuery.getPage(),
+                anyQuery.getSize(),
+                getOrderByClauses(anyQuery.getOrderBy()),
+                realm,
+                spec,
+                os);
+
+        return Response.ok(sout).
+                type(RESTHeaders.TEXT_CSV).
+                header(HttpHeaders.CONTENT_DISPOSITION,
+                        "attachment; filename=" + AuthContextUtils.getDomain() + ".csv").
+                build();
+    }
+
+    @Override
+    public List<ProvisioningReport> pull(final CSVPullSpec spec, final InputStream csv) {
+        return logic.pull(spec, csv);
+    }
 }
diff --git a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/CamelUserProvisioningManager.java b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/CamelUserProvisioningManager.java
index 50bf5d9..9b9ac13 100644
--- a/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/CamelUserProvisioningManager.java
+++ b/ext/camel/provisioning-camel/src/main/java/org/apache/syncope/core/provisioning/camel/CamelUserProvisioningManager.java
@@ -31,11 +31,11 @@ import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.patch.StatusPatch;
 import org.apache.syncope.common.lib.patch.UserPatch;
 import org.apache.syncope.common.lib.to.PropagationStatus;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.core.provisioning.api.PropagationByResource;
 import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
 import org.apache.syncope.core.provisioning.api.UserWorkflowResult;
-import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.transaction.annotation.Propagation;
diff --git a/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCUserManager.java b/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCUserManager.java
index 4efc87a..f237947 100644
--- a/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCUserManager.java
+++ b/ext/oidcclient/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCUserManager.java
@@ -39,11 +39,11 @@ import org.apache.syncope.core.persistence.api.entity.OIDCProvider;
 import org.apache.syncope.core.persistence.api.entity.OIDCProviderItem;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.provisioning.api.IntAttrName;
+import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
 import org.apache.syncope.core.provisioning.api.OIDCProviderActions;
 import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
 import org.apache.syncope.core.provisioning.api.data.ItemTransformer;
 import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
-import org.apache.syncope.core.provisioning.java.IntAttrNameParser;
 import org.apache.syncope.core.provisioning.java.pushpull.InboundMatcher;
 import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
 import org.apache.syncope.core.provisioning.java.utils.TemplateUtils;
diff --git a/ext/oidcclient/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCProviderDataBinderImpl.java b/ext/oidcclient/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCProviderDataBinderImpl.java
index 45f19ab..dcfb1a5 100644
--- a/ext/oidcclient/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCProviderDataBinderImpl.java
+++ b/ext/oidcclient/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCProviderDataBinderImpl.java
@@ -39,8 +39,8 @@ import org.apache.syncope.core.persistence.api.entity.OIDCProviderItem;
 import org.apache.syncope.core.persistence.api.entity.OIDCUserTemplate;
 import org.apache.syncope.core.provisioning.api.IntAttrName;
 import org.apache.syncope.core.provisioning.api.data.OIDCProviderDataBinder;
-import org.apache.syncope.core.provisioning.java.IntAttrNameParser;
-import org.apache.syncope.core.provisioning.java.jexl.JexlUtils;
+import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
+import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -51,8 +51,6 @@ public class OIDCProviderDataBinderImpl implements OIDCProviderDataBinder {
 
     private static final Logger LOG = LoggerFactory.getLogger(OIDCProviderDataBinder.class);
 
-    private static final String[] ITEM_IGNORE_PROPERTIES = { "key", "purpose" };
-
     @Autowired
     private AnyTypeDAO anyTypeDAO;
 
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 6dc16fa..8e9dc1f 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
@@ -39,11 +39,11 @@ import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.entity.SAML2IdP;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.provisioning.api.IntAttrName;
+import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
 import org.apache.syncope.core.provisioning.api.SAML2IdPActions;
 import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
 import org.apache.syncope.core.provisioning.api.data.ItemTransformer;
 import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
-import org.apache.syncope.core.provisioning.java.IntAttrNameParser;
 import org.apache.syncope.core.provisioning.java.pushpull.InboundMatcher;
 import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
 import org.apache.syncope.core.provisioning.java.utils.TemplateUtils;
diff --git a/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPDataBinderImpl.java b/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPDataBinderImpl.java
index 7f2a482..0b7c261 100644
--- a/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPDataBinderImpl.java
+++ b/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPDataBinderImpl.java
@@ -40,8 +40,8 @@ import org.apache.syncope.core.persistence.api.entity.SAML2IdPItem;
 import org.apache.syncope.core.persistence.api.entity.SAML2UserTemplate;
 import org.apache.syncope.core.provisioning.api.IntAttrName;
 import org.apache.syncope.core.provisioning.api.data.SAML2IdPDataBinder;
-import org.apache.syncope.core.provisioning.java.IntAttrNameParser;
-import org.apache.syncope.core.provisioning.java.jexl.JexlUtils;
+import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
+import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/fit/core-reference/pom.xml b/fit/core-reference/pom.xml
index 0533c61..36f09fa 100644
--- a/fit/core-reference/pom.xml
+++ b/fit/core-reference/pom.xml
@@ -428,6 +428,12 @@ under the License.
         </includes>
       </testResource>
       <testResource>
+        <directory>${basedir}/../../core/logic/src/test/resources</directory>
+        <includes>
+          <include>test1.csv</include>
+        </includes>
+      </testResource>
+      <testResource>
         <directory>${basedir}/../../core/rest-cxf/src/main/resources</directory>
         <includes>
           <include>errorMessages.properties</include>
@@ -470,7 +476,18 @@ under the License.
               <webXml>${basedir}/src/main/resources/elasticsearch/web.xml</webXml>
             </configuration>
           </plugin>
-          
+
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-failsafe-plugin</artifactId>
+            <inherited>true</inherited>
+            <configuration>
+              <includes>
+                <include>**/org/apache/syncope/fit/core/*ITCase.java</include>
+              </includes>
+            </configuration>
+          </plugin>
+
           <plugin>
             <groupId>io.fabric8</groupId>
             <artifactId>docker-maven-plugin</artifactId>
@@ -600,6 +617,17 @@ under the License.
 
           <plugin>
             <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-failsafe-plugin</artifactId>
+            <inherited>true</inherited>
+            <configuration>
+              <includes>
+                <include>**/org/apache/syncope/fit/core/*ITCase.java</include>
+              </includes>
+            </configuration>
+          </plugin>
+
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
             <artifactId>maven-antrun-plugin</artifactId>
             <inherited>true</inherited>
             <executions>
@@ -737,6 +765,17 @@ under the License.
 
         <plugins>
           <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-failsafe-plugin</artifactId>
+            <inherited>true</inherited>
+            <configuration>
+              <includes>
+                <include>**/org/apache/syncope/fit/core/*ITCase.java</include>
+              </includes>
+            </configuration>
+          </plugin>
+
+          <plugin>
             <groupId>io.fabric8</groupId>
             <artifactId>docker-maven-plugin</artifactId>
             <configuration>
@@ -862,6 +901,17 @@ under the License.
 
           <plugin>
             <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-failsafe-plugin</artifactId>
+            <inherited>true</inherited>
+            <configuration>
+              <includes>
+                <include>**/org/apache/syncope/fit/core/*ITCase.java</include>
+              </includes>
+            </configuration>
+          </plugin>
+
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
             <artifactId>maven-antrun-plugin</artifactId>
             <inherited>true</inherited>
             <executions>
@@ -1005,6 +1055,17 @@ under the License.
 
         <plugins>
           <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-failsafe-plugin</artifactId>
+            <inherited>true</inherited>
+            <configuration>
+              <includes>
+                <include>**/org/apache/syncope/fit/core/*ITCase.java</include>
+              </includes>
+            </configuration>
+          </plugin>
+
+          <plugin>
             <groupId>io.fabric8</groupId>
             <artifactId>docker-maven-plugin</artifactId>
             <configuration>
@@ -1110,6 +1171,17 @@ under the License.
 
         <plugins>
           <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-failsafe-plugin</artifactId>
+            <inherited>true</inherited>
+            <configuration>
+              <includes>
+                <include>**/org/apache/syncope/fit/core/*ITCase.java</include>
+              </includes>
+            </configuration>
+          </plugin>
+
+          <plugin>
             <groupId>io.fabric8</groupId>
             <artifactId>docker-maven-plugin</artifactId>
             <configuration>
@@ -1214,6 +1286,17 @@ under the License.
 
         <plugins>
           <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-failsafe-plugin</artifactId>
+            <inherited>true</inherited>
+            <configuration>
+              <includes>
+                <include>**/org/apache/syncope/fit/core/*ITCase.java</include>
+              </includes>
+            </configuration>
+          </plugin>
+
+          <plugin>
             <groupId>io.fabric8</groupId>
             <artifactId>docker-maven-plugin</artifactId>
             <configuration>
@@ -1326,6 +1409,17 @@ under the License.
 
         <plugins>
           <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-failsafe-plugin</artifactId>
+            <inherited>true</inherited>
+            <configuration>
+              <includes>
+                <include>**/org/apache/syncope/fit/core/*ITCase.java</include>
+              </includes>
+            </configuration>
+          </plugin>
+
+          <plugin>
             <groupId>io.fabric8</groupId>
             <artifactId>docker-maven-plugin</artifactId>
             <configuration>
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
index 9d266a6..1f230a9 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
@@ -66,6 +66,7 @@ import org.apache.syncope.common.lib.to.ItemTO;
 import org.apache.syncope.common.lib.to.PullTaskTO;
 import org.apache.syncope.common.lib.to.ExecTO;
 import org.apache.syncope.common.lib.to.ImplementationTO;
+import org.apache.syncope.common.lib.to.PropagationTaskTO;
 import org.apache.syncope.common.lib.to.ProvisioningResult;
 import org.apache.syncope.common.lib.to.RemediationTO;
 import org.apache.syncope.common.lib.to.UserTO;
@@ -1035,7 +1036,6 @@ public class PullTaskITCase extends AbstractTaskITCase {
         template.setPassword("'password123'");
         template.getResources().add(RESOURCE_NAME_DBVIRATTR);
         template.getVirAttrs().add(attrTO("virtualdata", "'virtualvalue'"));
-
         task.getTemplates().put(AnyTypeKind.USER.name(), template);
 
         taskService.update(TaskType.PULL, task);
@@ -1043,15 +1043,21 @@ public class PullTaskITCase extends AbstractTaskITCase {
         // exec task: one user from CSV will match the user created above and template will be applied
         execProvisioningTask(taskService, TaskType.PULL, task.getKey(), MAX_WAIT_SECONDS, false);
 
-        // check that template was successfully applied...
-        userTO = userService.read(userTO.getKey());
-        assertEquals("virtualvalue", userTO.getVirAttr("virtualdata").get().getValues().get(0));
+        // check that template was successfully applied
+        // 1. propagation to db
+        PagedResult<PropagationTaskTO> tasks = taskService.search(new TaskQuery.Builder(TaskType.PROPAGATION).
+                anyTypeKind(AnyTypeKind.USER).entityKey(userTO.getKey()).resource(RESOURCE_NAME_DBVIRATTR).build());
+        assertFalse(tasks.getResult().isEmpty());
+        assertEquals(ExecStatus.SUCCESS.name(), tasks.getResult().get(0).getLatestExecStatus());
 
-        // ...and that propagation to db succeeded
         JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
         String value = queryForObject(jdbcTemplate,
                 MAX_WAIT_SECONDS, "SELECT USERNAME FROM testpull WHERE ID=?", String.class, userTO.getKey());
         assertEquals("virtualvalue", value);
+
+        // 2. virtual attribute
+        userTO = userService.read(userTO.getKey());
+        assertEquals("virtualvalue", userTO.getVirAttr("virtualdata").get().getValues().get(0));
     }
 
     @Test
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 145c25d..dc1887e 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
@@ -19,24 +19,46 @@
 package org.apache.syncope.fit.core;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 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.dataformat.csv.CsvMapper;
+import com.fasterxml.jackson.dataformat.csv.CsvSchema;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.Date;
+import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 import javax.sql.DataSource;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import org.apache.cxf.jaxrs.client.Client;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.AnyObjectTO;
 import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.PagedResult;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.common.lib.to.PullTaskTO;
 import org.apache.syncope.common.lib.to.PushTaskTO;
 import org.apache.syncope.common.lib.to.ReconStatus;
+import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.MatchType;
+import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.common.lib.types.UnmatchingRule;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.beans.AnyQuery;
+import org.apache.syncope.common.rest.api.beans.CSVPullSpec;
+import org.apache.syncope.common.rest.api.beans.CSVPushSpec;
 import org.apache.syncope.common.rest.api.beans.ReconQuery;
+import org.apache.syncope.common.rest.api.service.ReconciliationService;
 import org.apache.syncope.fit.AbstractITCase;
 import org.identityconnectors.framework.common.objects.OperationalAttributes;
 import org.identityconnectors.framework.common.objects.Uid;
@@ -174,4 +196,106 @@ public class ReconciliationITCase extends AbstractITCase {
         AnyObjectTO printer = anyObjectService.read(externalName);
         assertNotNull(printer);
     }
+
+    @Test
+    public void importCSV() {
+        ReconciliationService service = adminClient.getService(ReconciliationService.class);
+        Client client = WebClient.client(service);
+        client.type(RESTHeaders.TEXT_CSV);
+
+        CSVPullSpec spec = new CSVPullSpec.Builder(AnyTypeKind.USER.name(), "username").build();
+        InputStream csv = getClass().getResourceAsStream("/test1.csv");
+
+        List<ProvisioningReport> results = service.pull(spec, csv);
+        assertEquals(AnyTypeKind.USER.name(), results.get(0).getAnyType());
+        assertNotNull(results.get(0).getKey());
+        assertEquals("donizetti", results.get(0).getName());
+        assertEquals("donizetti", results.get(0).getUidValue());
+        assertEquals(ResourceOperation.CREATE, results.get(0).getOperation());
+        assertEquals(ProvisioningReport.Status.SUCCESS, results.get(0).getStatus());
+
+        UserTO donizetti = userService.read(results.get(0).getKey());
+        assertNotNull(donizetti);
+        assertEquals("Gaetano", donizetti.getPlainAttr("firstname").get().getValues().get(0));
+        assertEquals(1, donizetti.getPlainAttr("loginDate").get().getValues().size());
+
+        UserTO cimarosa = userService.read(results.get(1).getKey());
+        assertNotNull(cimarosa);
+        assertEquals("Domenico Cimarosa", cimarosa.getPlainAttr("fullname").get().getValues().get(0));
+        assertEquals(2, cimarosa.getPlainAttr("loginDate").get().getValues().size());
+    }
+
+    @Test
+    public void exportCSV() throws IOException {
+        ReconciliationService service = adminClient.getService(ReconciliationService.class);
+        Client client = WebClient.client(service);
+        client.accept(RESTHeaders.TEXT_CSV);
+
+        AnyQuery anyQuery = new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
+                fiql(SyncopeClient.getUserSearchConditionBuilder().is("username").equalTo("*ini").query()).
+                page(1).
+                size(1000).
+                orderBy("username ASC").
+                build();
+
+        CSVPushSpec spec = new CSVPushSpec.Builder(AnyTypeKind.USER.name()).
+                ignorePagination(true).
+                field("username").
+                field("status").
+                plainAttr("firstname").
+                plainAttr("surname").
+                plainAttr("email").
+                plainAttr("loginDate").
+                build();
+
+        Response response = service.push(anyQuery, spec);
+        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
+        assertEquals(
+                "attachment; filename=" + SyncopeConstants.MASTER_DOMAIN + ".csv",
+                response.getHeaderString(HttpHeaders.CONTENT_DISPOSITION));
+
+        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());
+
+        int rows = 0;
+        for (; reader.hasNext(); rows++) {
+            Map<String, String> row = reader.next();
+
+            assertEquals(users.getResult().get(rows).getUsername(), row.get("username"));
+            assertEquals(users.getResult().get(rows).getStatus(), row.get("status"));
+
+            switch (row.get("username")) {
+                case "rossini":
+                    assertEquals(spec.getNullValue(), row.get("email"));
+                    assertTrue(row.get("loginDate").contains(spec.getArrayElementSeparator()));
+                    break;
+
+                case "verdi":
+                    assertEquals("verdi@syncope.org", row.get("email"));
+                    assertEquals(spec.getNullValue(), row.get("loginDate"));
+                    break;
+
+                case "bellini":
+                    assertEquals(spec.getNullValue(), row.get("email"));
+                    assertFalse(row.get("loginDate").contains(spec.getArrayElementSeparator()));
+                    break;
+
+                default:
+                    break;
+            }
+        }
+        assertEquals(rows, users.getTotalCount());
+    }
 }
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
index 29ab274..2553993 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
@@ -32,11 +32,12 @@ import java.security.AccessControlException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 import javax.ws.rs.core.GenericType;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
@@ -872,10 +873,8 @@ public class UserITCase extends AbstractITCase {
     private void verifyAsyncResult(final List<PropagationStatus> statuses) {
         assertEquals(3, statuses.size());
 
-        Map<String, PropagationStatus> byResource = new HashMap<>(3);
-        statuses.forEach(status -> {
-            byResource.put(status.getResource(), status);
-        });
+        Map<String, PropagationStatus> byResource = statuses.stream().collect(
+                Collectors.toMap(PropagationStatus::getResource, Function.identity()));
         assertEquals(ExecStatus.SUCCESS, byResource.get(RESOURCE_NAME_LDAP).getStatus());
         assertTrue(byResource.get(RESOURCE_NAME_TESTDB).getStatus() == ExecStatus.CREATED
                 || byResource.get(RESOURCE_NAME_TESTDB).getStatus() == ExecStatus.SUCCESS);
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/VirAttrITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/VirAttrITCase.java
index 14e84b8..6c22e97 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/VirAttrITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/VirAttrITCase.java
@@ -75,8 +75,7 @@ public class VirAttrITCase extends AbstractITCase {
         UserTO userTO = UserITCase.getUniqueSampleTO("issue16@apache.org");
         userTO.getVirAttrs().add(attrTO("virtualdata", "virtualvalue"));
         userTO.getResources().add(RESOURCE_NAME_DBVIRATTR);
-        userTO.getMemberships().add(
-                new MembershipTO.Builder().group("f779c0d4-633b-4be5-8f57-32eb478a3ca5").build());
+        userTO.getMemberships().add(new MembershipTO.Builder().group("f779c0d4-633b-4be5-8f57-32eb478a3ca5").build());
 
         // 1. create user
         userTO = createUser(userTO).getEntity();
@@ -230,13 +229,13 @@ public class VirAttrITCase extends AbstractITCase {
 
         // 3. update virtual attribute directly
         JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
-        String value = queryForObject(jdbcTemplate, 
+        String value = queryForObject(jdbcTemplate,
                 MAX_WAIT_SECONDS, "SELECT USERNAME FROM testpull WHERE ID=?", String.class, actual.getKey());
         assertEquals("virattrcache", value);
 
         jdbcTemplate.update("UPDATE testpull set USERNAME='virattrcache2' WHERE ID=?", actual.getKey());
 
-        value = queryForObject(jdbcTemplate, 
+        value = queryForObject(jdbcTemplate,
                 MAX_WAIT_SECONDS, "SELECT USERNAME FROM testpull WHERE ID=?", String.class, actual.getKey());
         assertEquals("virattrcache2", value);
 
@@ -388,13 +387,15 @@ public class VirAttrITCase extends AbstractITCase {
         // ----------------------------------------
         JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
         String value = queryForObject(
-                jdbcTemplate, MAX_WAIT_SECONDS, "SELECT USERNAME FROM testpull WHERE ID=?", String.class, userTO.getKey());
+                jdbcTemplate, MAX_WAIT_SECONDS, "SELECT USERNAME FROM testpull WHERE ID=?", String.class, userTO.
+                        getKey());
         assertEquals("virattrcache", value);
 
         jdbcTemplate.update("UPDATE testpull set USERNAME='virattrcache2' WHERE ID=?", userTO.getKey());
 
         value = queryForObject(
-                jdbcTemplate, MAX_WAIT_SECONDS, "SELECT USERNAME FROM testpull WHERE ID=?", String.class, userTO.getKey());
+                jdbcTemplate, MAX_WAIT_SECONDS, "SELECT USERNAME FROM testpull WHERE ID=?", String.class, userTO.
+                        getKey());
         assertEquals("virattrcache2", value);
         // ----------------------------------------
 
diff --git a/pom.xml b/pom.xml
index 7f826c0..744fa92 100644
--- a/pom.xml
+++ b/pom.xml
@@ -452,6 +452,7 @@ under the License.
     <h2.version>1.4.200</h2.version>
 
     <junit.version>5.5.2</junit.version>
+    <mockito.version>3.2.4</mockito.version>
 
     <conf.directory>${project.build.directory}/test-classes</conf.directory>
     <bundles.directory>${project.build.directory}/bundles</bundles.directory>
@@ -916,6 +917,11 @@ under the License.
         </exclusions>
       </dependency>
       <dependency>
+        <groupId>com.fasterxml.jackson.dataformat</groupId>
+        <artifactId>jackson-dataformat-csv</artifactId>
+        <version>${jackson.version}</version>
+      </dependency>
+      <dependency>
         <groupId>com.fasterxml.jackson.datatype</groupId>
         <artifactId>jackson-datatype-joda</artifactId>
         <version>${jackson.version}</version>
@@ -1797,10 +1803,22 @@ under the License.
       <dependency>
         <groupId>org.mockito</groupId>
         <artifactId>mockito-core</artifactId>
-        <version>3.2.0</version>
+        <version>${mockito.version}</version>
         <scope>test</scope>
       </dependency>
       <dependency>
+        <groupId>org.mockito</groupId>
+        <artifactId>mockito-junit-jupiter</artifactId>
+        <version>${mockito.version}</version>
+        <scope>test</scope>
+        <exclusions>
+          <exclusion>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+          </exclusion>
+        </exclusions>
+      </dependency>
+      <dependency>
         <groupId>org.junit.jupiter</groupId>
         <artifactId>junit-jupiter</artifactId>
         <version>${junit.version}</version>
@@ -2466,7 +2484,8 @@ under the License.
             <link>http://fasterxml.github.io/jackson-databind/javadoc/2.10/</link>
             <link>http://fasterxml.github.io/jackson-annotations/javadoc/2.10/</link>
             <link>http://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.10/</link>
-            <link>http://fasterxml.github.io/jackson-dataformat-yaml/javadoc/2.9.pr1/</link>
+            <link>http://fasterxml.github.io/jackson-dataformats-text/javadoc/yaml/2.10/</link>
+            <link>http://fasterxml.github.io/jackson-dataformats-text/javadoc/csv/2.10/</link>
             <link>http://fasterxml.github.io/jackson-datatype-joda/javadoc/2.10/</link>
             <link>http://www.javadoc.io/doc/org.apache.camel/camel-core/2.24.2/</link>
             <link>http://www.javadoc.io/doc/org.apache.camel/camel-spring/2.24.2/</link>