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 17:09:28 UTC

[syncope] branch master updated (ba66e38 -> 4f63cab)

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

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


    from ba66e38  Fixed label for validation message
     new 6f08020  Upgrading PDFBox
     new 34cf9b4  Upgrading Wicket SpringBoot
     new 88c7aa5  Upgrading checkstyle
     new 4f63cab  [SYNCOPE-1531] Core support

The 4 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:
 .../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 +++
 .../syncope/common/lib/to/AccessTokenTO.java       |   1 -
 .../syncope/common/lib/to}/ProvisioningReport.java |   2 +-
 .../syncope/common/rest/api/RESTHeaders.java       |   7 +-
 core/idm/logic/pom.xml                             |  65 +++++
 .../syncope/core/logic/ReconciliationLogic.java    | 219 ++++++++++++++++-
 .../apache/syncope/core/logic}/AbstractTest.java   |  14 +-
 .../syncope/core/logic}/DummyConfParamOps.java     |   2 +-
 .../apache/syncope/core/logic}/DummyDomainOps.java |   2 +-
 .../core/logic}/DummyImplementationLookup.java     |   4 +-
 .../apache/syncope/core/logic/DummyServiceOps.java |  34 ++-
 .../syncope/core/logic/IdMLogicTestContext.java}   |  19 +-
 .../core/logic/ReconciliationLogicTest.java        | 153 ++++++++++++
 .../syncope/core/logic}/TestInitializer.java       |   2 +-
 core/idm/logic/src/test/resources/logicTest.xml    |  65 +++++
 core/idm/logic/src/test/resources/test1.csv        |   3 +
 .../cxf/service/ReconciliationServiceImpl.java     |  43 ++++
 .../core/rest/cxf/service/ResourceServiceImpl.java |   3 +-
 .../apache/syncope/core/logic/AnyObjectLogic.java  |   9 +-
 .../org/apache/syncope/core/logic/GroupLogic.java  |  18 +-
 .../org/apache/syncope/core/logic/TaskLogic.java   |  10 +-
 .../org/apache/syncope/core/logic/UserLogic.java   |  15 +-
 .../jpa/dao/MyJPAJSONPlainSchemaDAO.java           |   2 +-
 .../jpa/dao/PGJPAJSONPlainSchemaDAO.java           |   2 +-
 .../core/persistence/jpa/dao/AbstractAnyDAO.java   |  18 +-
 .../persistence/jpa/dao/JPAPlainSchemaDAO.java     |   8 +-
 .../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     |  20 +-
 .../api}/jexl/SyncopeJexlFunctions.java            |   2 +-
 .../api/propagation/PropagationManager.java        |  19 ++
 .../api/propagation/PropagationTaskExecutor.java   |   4 +-
 .../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       | 208 ++++++++++++++++
 .../SyncopeStreamPullExecutor.java}                |  23 +-
 .../SyncopeStreamPushExecutor.java}                |  26 +-
 .../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 | 130 ++++++++--
 .../provisioning/java/ProvisioningContext.java     |   1 +
 .../java/data/AbstractAnyDataBinder.java           |  17 +-
 .../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 |   8 +-
 .../notification/DefaultNotificationManager.java   |   4 +-
 .../AbstractPropagationTaskExecutor.java           |  31 ++-
 .../LDAPMembershipPropagationActions.java          |  18 +-
 .../PriorityPropagationTaskExecutor.java           |  49 +---
 .../java/propagation/PropagationManagerImpl.java   |  75 +++---
 .../pushpull/AbstractProvisioningJobDelegate.java  | 114 +++++----
 .../java/pushpull/AbstractPullResultHandler.java   |  18 +-
 .../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     |   9 +-
 .../pushpull/DefaultUserPushResultHandler.java     |   2 +-
 .../provisioning/java/pushpull/InboundMatcher.java |  16 +-
 .../java/pushpull/LDAPMembershipPullActions.java   |   2 +-
 .../java/pushpull/LDAPPasswordPullActions.java     |   2 +-
 .../java/pushpull/OutboundMatcher.java             |   2 +-
 .../java/pushpull/PushJobDelegate.java             |   8 +-
 .../java/pushpull/SinglePullJobDelegate.java       |  10 +-
 .../java/pushpull/SinglePushJobDelegate.java       |   2 +-
 .../stream/StreamAnyObjectPushResultHandler.java   |  73 ++++++
 .../stream/StreamGroupPushResultHandler.java       |  73 ++++++
 .../pushpull/stream/StreamPullJobDelegate.java     | 264 +++++++++++++++++++++
 .../pushpull/stream/StreamPushJobDelegate.java     | 200 ++++++++++++++++
 .../stream/StreamUserPushResultHandler.java        |  73 ++++++
 .../core/provisioning/java/utils/MappingUtils.java |  85 -------
 .../provisioning/java/utils/TemplateUtils.java     |   2 +-
 .../core/provisioning/java/AbstractTest.java       |  12 +
 .../java/{ => data}/ResourceDataBinderTest.java    |   3 +-
 .../java/{ => jexl}/MailTemplateTest.java          |   6 +-
 .../provisioning/java/{ => jexl}/MappingTest.java  |  22 +-
 .../pushpull/stream/StreamPullJobDelegateTest.java | 117 +++++++++
 .../pushpull/stream/StreamPushJobDelegateTest.java | 119 ++++++++++
 .../camel/CamelUserProvisioningManager.java        |   2 +-
 .../syncope/core/logic/oidc/OIDCUserManager.java   |   2 +-
 .../java/data/OIDCProviderDataBinderImpl.java      |   4 +-
 .../syncope/core/logic/saml2/SAML2UserManager.java |   2 +-
 .../java/data/SAML2IdPDataBinderImpl.java          |   4 +-
 fit/core-reference/pom.xml                         |  94 ++++++++
 .../apache/syncope/fit/core/PullTaskITCase.java    |  15 +-
 .../syncope/fit/core/ReconciliationITCase.java     | 126 +++++++++-
 .../org/apache/syncope/fit/core/UserITCase.java    |   7 +-
 .../org/apache/syncope/fit/core/VirAttrITCase.java |  13 +-
 pom.xml                                            |  29 ++-
 108 files changed, 3270 insertions(+), 559 deletions(-)
 create mode 100644 common/idm/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AbstractCSVSpec.java
 create mode 100644 common/idm/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/CSVPullSpec.java
 create mode 100644 common/idm/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/CSVPushSpec.java
 rename {core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull => common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to}/ProvisioningReport.java (98%)
 copy core/{provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java => idm/logic/src/test/java/org/apache/syncope/core/logic}/AbstractTest.java (75%)
 copy core/{persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa => idm/logic/src/test/java/org/apache/syncope/core/logic}/DummyConfParamOps.java (96%)
 copy core/{provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java => idm/logic/src/test/java/org/apache/syncope/core/logic}/DummyDomainOps.java (97%)
 copy core/{spring/src/test/java/org/apache/syncope/core/spring/security => idm/logic/src/test/java/org/apache/syncope/core/logic}/DummyImplementationLookup.java (97%)
 copy sra/src/main/java/org/apache/syncope/sra/SyncopeSRAStartStop.java => core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyServiceOps.java (62%)
 copy core/{provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/ProvisioningTestContext.java => idm/logic/src/test/java/org/apache/syncope/core/logic/IdMLogicTestContext.java} (69%)
 create mode 100644 core/idm/logic/src/test/java/org/apache/syncope/core/logic/ReconciliationLogicTest.java
 copy core/{provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java => idm/logic/src/test/java/org/apache/syncope/core/logic}/TestInitializer.java (98%)
 create mode 100644 core/idm/logic/src/test/resources/logicTest.xml
 create mode 100644 core/idm/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 (94%)
 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/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/{SyncopeSinglePushExecutor.java => 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/04: 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 master
in repository https://gitbox.apache.org/repos/asf/syncope.git

commit 6f08020364b50e8ed6df3d7ccc75f7ad3d358828
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Thu Dec 26 07:44:20 2019 +0100

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

diff --git a/pom.xml b/pom.xml
index 0c84250..5db6ec1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1531,7 +1531,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] 03/04: Upgrading checkstyle

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

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

commit 88c7aa52edcbf5f1e572ce2b2b9543a465ecb98e
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Mon Dec 30 15:14:42 2019 +0100

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

diff --git a/pom.xml b/pom.xml
index 1cd3dd1..7903d41 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2077,7 +2077,7 @@ under the License.
             <dependency>
               <groupId>com.puppycrawl.tools</groupId>
               <artifactId>checkstyle</artifactId>
-              <version>8.27</version>
+              <version>8.28</version>
             </dependency>
           </dependencies>
           <configuration>


[syncope] 04/04: [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 master
in repository https://gitbox.apache.org/repos/asf/syncope.git

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

    [SYNCOPE-1531] Core support
---
 .../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 +++
 .../syncope/common/lib/to/AccessTokenTO.java       |   1 -
 .../syncope/common/lib/to}/ProvisioningReport.java |   2 +-
 .../syncope/common/rest/api/RESTHeaders.java       |   7 +-
 core/idm/logic/pom.xml                             |  65 +++++
 .../syncope/core/logic/ReconciliationLogic.java    | 219 ++++++++++++++++-
 .../apache/syncope/core/logic}/AbstractTest.java   |  14 +-
 .../syncope/core/logic/DummyConfParamOps.java}     |  27 ++-
 .../apache/syncope/core/logic/DummyDomainOps.java  |  59 +++++
 .../core/logic/DummyImplementationLookup.java      |  90 +++++++
 .../syncope/core/logic/DummyServiceOps.java}       |  29 ++-
 .../syncope/core/logic/IdMLogicTestContext.java    |  56 +++++
 .../core/logic/ReconciliationLogicTest.java        | 153 ++++++++++++
 .../apache/syncope/core/logic/TestInitializer.java |  70 ++++++
 core/idm/logic/src/test/resources/logicTest.xml    |  65 +++++
 core/idm/logic/src/test/resources/test1.csv        |   3 +
 .../cxf/service/ReconciliationServiceImpl.java     |  43 ++++
 .../core/rest/cxf/service/ResourceServiceImpl.java |   3 +-
 .../apache/syncope/core/logic/AnyObjectLogic.java  |   9 +-
 .../org/apache/syncope/core/logic/GroupLogic.java  |  18 +-
 .../org/apache/syncope/core/logic/TaskLogic.java   |  10 +-
 .../org/apache/syncope/core/logic/UserLogic.java   |  15 +-
 .../jpa/dao/MyJPAJSONPlainSchemaDAO.java           |   2 +-
 .../jpa/dao/PGJPAJSONPlainSchemaDAO.java           |   2 +-
 .../core/persistence/jpa/dao/AbstractAnyDAO.java   |  18 +-
 .../persistence/jpa/dao/JPAPlainSchemaDAO.java     |   8 +-
 .../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     |  20 +-
 .../api}/jexl/SyncopeJexlFunctions.java            |   2 +-
 .../api/propagation/PropagationManager.java        |  19 ++
 .../api/propagation/PropagationTaskExecutor.java   |   4 +-
 .../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       | 208 ++++++++++++++++
 .../SyncopeStreamPullExecutor.java}                |  23 +-
 .../SyncopeStreamPushExecutor.java}                |  26 +-
 .../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 | 130 ++++++++--
 .../provisioning/java/ProvisioningContext.java     |   1 +
 .../java/data/AbstractAnyDataBinder.java           |  17 +-
 .../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 |   8 +-
 .../notification/DefaultNotificationManager.java   |   4 +-
 .../AbstractPropagationTaskExecutor.java           |  31 ++-
 .../LDAPMembershipPropagationActions.java          |  18 +-
 .../PriorityPropagationTaskExecutor.java           |  49 +---
 .../java/propagation/PropagationManagerImpl.java   |  75 +++---
 .../pushpull/AbstractProvisioningJobDelegate.java  | 114 +++++----
 .../java/pushpull/AbstractPullResultHandler.java   |  18 +-
 .../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     |   9 +-
 .../pushpull/DefaultUserPushResultHandler.java     |   2 +-
 .../provisioning/java/pushpull/InboundMatcher.java |  16 +-
 .../java/pushpull/LDAPMembershipPullActions.java   |   2 +-
 .../java/pushpull/LDAPPasswordPullActions.java     |   2 +-
 .../java/pushpull/OutboundMatcher.java             |   2 +-
 .../java/pushpull/PushJobDelegate.java             |   8 +-
 .../java/pushpull/SinglePullJobDelegate.java       |  10 +-
 .../java/pushpull/SinglePushJobDelegate.java       |   2 +-
 .../stream/StreamAnyObjectPushResultHandler.java   |  73 ++++++
 .../stream/StreamGroupPushResultHandler.java       |  73 ++++++
 .../pushpull/stream/StreamPullJobDelegate.java     | 264 +++++++++++++++++++++
 .../pushpull/stream/StreamPushJobDelegate.java     | 200 ++++++++++++++++
 .../stream/StreamUserPushResultHandler.java        |  73 ++++++
 .../core/provisioning/java/utils/MappingUtils.java |  85 -------
 .../provisioning/java/utils/TemplateUtils.java     |   2 +-
 .../core/provisioning/java/AbstractTest.java       |  12 +
 .../java/{ => data}/ResourceDataBinderTest.java    |   3 +-
 .../java/{ => jexl}/MailTemplateTest.java          |   6 +-
 .../provisioning/java/{ => jexl}/MappingTest.java  |  22 +-
 .../pushpull/stream/StreamPullJobDelegateTest.java | 117 +++++++++
 .../pushpull/stream/StreamPushJobDelegateTest.java | 119 ++++++++++
 .../camel/CamelUserProvisioningManager.java        |   2 +-
 .../syncope/core/logic/oidc/OIDCUserManager.java   |   2 +-
 .../java/data/OIDCProviderDataBinderImpl.java      |   4 +-
 .../syncope/core/logic/saml2/SAML2UserManager.java |   2 +-
 .../java/data/SAML2IdPDataBinderImpl.java          |   4 +-
 fit/core-reference/pom.xml                         |  94 ++++++++
 .../apache/syncope/fit/core/PullTaskITCase.java    |  15 +-
 .../syncope/fit/core/ReconciliationITCase.java     | 126 +++++++++-
 .../org/apache/syncope/fit/core/UserITCase.java    |   7 +-
 .../org/apache/syncope/fit/core/VirAttrITCase.java |  13 +-
 pom.xml                                            |  23 +-
 108 files changed, 3535 insertions(+), 556 deletions(-)

diff --git a/common/idm/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AbstractCSVSpec.java b/common/idm/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AbstractCSVSpec.java
new file mode 100644
index 0000000..7844335
--- /dev/null
+++ b/common/idm/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/idm/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/CSVPullSpec.java b/common/idm/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/idm/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/idm/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/CSVPushSpec.java b/common/idm/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/CSVPushSpec.java
new file mode 100644
index 0000000..92b2196
--- /dev/null
+++ b/common/idm/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/idm/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ReconciliationService.java b/common/idm/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ReconciliationService.java
index cb44ef9..a2c8193 100644
--- a/common/idm/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ReconciliationService.java
+++ b/common/idm/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/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/AccessTokenTO.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/AccessTokenTO.java
index e3e00f6..501ecb9 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/AccessTokenTO.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/AccessTokenTO.java
@@ -72,5 +72,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/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/ProvisioningReport.java
similarity index 98%
rename from core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/ProvisioningReport.java
rename to common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/ProvisioningReport.java
index 3c0a6f0..216ab56 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/ProvisioningReport.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/ProvisioningReport.java
@@ -16,7 +16,7 @@
  * 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;
diff --git a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java
index 0429e29..fdabce0 100644
--- a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/RESTHeaders.java
+++ b/common/idrepo/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/core/idm/logic/pom.xml b/core/idm/logic/pom.xml
index c34b10c..baf271c 100644
--- a/core/idm/logic/pom.xml
+++ b/core/idm/logic/pom.xml
@@ -39,6 +39,11 @@ under the License.
 
   <dependencies>
     <dependency>
+      <groupId>com.fasterxml.jackson.dataformat</groupId>
+      <artifactId>jackson-dataformat-csv</artifactId>
+    </dependency>
+
+    <dependency>
       <groupId>org.apache.syncope.core.idrepo</groupId>
       <artifactId>syncope-core-idrepo-logic</artifactId>
       <version>${project.version}</version>
@@ -48,6 +53,45 @@ under the License.
       <artifactId>syncope-common-idm-rest-api</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>
@@ -57,6 +101,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/idm/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java b/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
index 2c9fd4b..388f82b 100644
--- a/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
+++ b/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
@@ -18,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;
@@ -35,9 +42,8 @@ import org.apache.syncope.common.lib.to.PushTaskTO;
 import org.apache.syncope.common.lib.to.ReconStatus;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
-import org.apache.syncope.common.lib.types.IdMEntitlement;
-import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.apache.syncope.common.lib.types.MatchType;
+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;
@@ -54,13 +60,30 @@ 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.lib.types.IdMEntitlement;
+import org.apache.syncope.common.lib.types.IdRepoEntitlement;
+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.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.provisioning.java.pushpull.InboundMatcher;
 import org.apache.syncope.core.provisioning.java.pushpull.OutboundMatcher;
 import org.apache.syncope.core.provisioning.java.utils.ConnObjectUtils;
 import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.identityconnectors.framework.common.objects.Attribute;
 import org.identityconnectors.framework.common.objects.ConnectorObject;
 import org.identityconnectors.framework.common.objects.Uid;
@@ -85,6 +108,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
@@ -105,6 +140,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) {
@@ -337,6 +378,178 @@ 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('" + IdRepoEntitlement.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 = IdRepoEntitlement.GROUP_SEARCH;
+                break;
+
+            case ANY_OBJECT:
+                entitlement = AnyEntitlement.SEARCH.getFor(anyType.getKey());
+                break;
+
+            case USER:
+            default:
+                entitlement = IdRepoEntitlement.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,
+                    AuthContextUtils.getUsername());
+        } catch (Exception e) {
+            LOG.error("Could not push to stream", e);
+            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Reconciliation);
+            sce.getElements().add(e.getMessage());
+            throw sce;
+        }
+    }
+
+    @PreAuthorize("hasRole('" + IdRepoEntitlement.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) {
+            LOG.error("Could not pull from stream", 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/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/AbstractTest.java b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/AbstractTest.java
similarity index 75%
copy from core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/AbstractTest.java
copy to core/idm/logic/src/test/java/org/apache/syncope/core/logic/AbstractTest.java
index b120f93..424f348 100644
--- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/AbstractTest.java
+++ b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/AbstractTest.java
@@ -16,15 +16,19 @@
  * 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.common.lib.types.EntitlementsHolder;
+import org.apache.syncope.common.lib.types.IdMEntitlement;
+import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 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;
 
-@SpringJUnitConfig(classes = { ProvisioningTestContext.class })
+@SpringJUnitConfig(classes = { IdMLogicTestContext.class })
 public abstract class AbstractTest {
 
     protected EntityManager entityManager() {
@@ -37,4 +41,10 @@ public abstract class AbstractTest {
 
         return entityManager;
     }
+
+    @BeforeAll
+    public static void init() {
+        EntitlementsHolder.getInstance().addAll(IdRepoEntitlement.values());
+        EntitlementsHolder.getInstance().addAll(IdMEntitlement.values());
+    }
 }
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/jexl/EmptyClassLoader.java b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyConfParamOps.java
similarity index 56%
copy from core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/jexl/EmptyClassLoader.java
copy to core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyConfParamOps.java
index 037113e..ca3a1ac 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/jexl/EmptyClassLoader.java
+++ b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyConfParamOps.java
@@ -16,21 +16,30 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.provisioning.java.jexl;
+package org.apache.syncope.core.logic;
 
-/**
- * A class loader that will throw {@link ClassNotFoundException} for every class name.
- */
-class EmptyClassLoader extends ClassLoader {
+import java.util.Map;
+import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
+import org.springframework.stereotype.Component;
+
+@Component
+public class DummyConfParamOps implements ConfParamOps {
 
     @Override
-    public Class<?> loadClass(final String name) throws ClassNotFoundException {
-        throw new ClassNotFoundException("This classloader won't attemp to load " + name);
+    public Map<String, Object> list(final String domain) {
+        return Map.of();
     }
 
     @Override
-    protected Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException {
-        throw new ClassNotFoundException("This classloader won't attemp to load " + name);
+    public <T> T get(final String domain, final String key, final T defaultValue, final Class<T> reference) {
+        return defaultValue;
     }
 
+    @Override
+    public <T> void set(final String domain, final String key, final T value) {
+    }
+
+    @Override
+    public void remove(final String domain, final String key) {
+    }
 }
diff --git a/core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyDomainOps.java b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyDomainOps.java
new file mode 100644
index 0000000..09757b0
--- /dev/null
+++ b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyDomainOps.java
@@ -0,0 +1,59 @@
+/*
+ * 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 java.util.List;
+import org.apache.syncope.common.keymaster.client.api.DomainOps;
+import org.apache.syncope.common.keymaster.client.api.model.Domain;
+import org.apache.syncope.common.lib.types.CipherAlgorithm;
+import org.springframework.stereotype.Component;
+
+@Component
+public class DummyDomainOps implements DomainOps {
+
+    @Override
+    public List<Domain> list() {
+        return List.of();
+    }
+
+    @Override
+    public Domain read(final String key) {
+        return new Domain.Builder(key).build();
+    }
+
+    @Override
+    public void create(final Domain domain) {
+        // nothing to do
+    }
+
+    @Override
+    public void changeAdminPassword(final String key, final String password, final CipherAlgorithm cipherAlgorithm) {
+        // nothing to do
+    }
+
+    @Override
+    public void adjustPoolSize(final String key, final int maxPoolSize, final int minIdle) {
+        // nothing to do
+    }
+
+    @Override
+    public void delete(final String key) {
+        // nothing to do
+    }
+}
diff --git a/core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyImplementationLookup.java b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyImplementationLookup.java
new file mode 100644
index 0000000..7cf8a15
--- /dev/null
+++ b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyImplementationLookup.java
@@ -0,0 +1,90 @@
+/*
+ * 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 java.util.Set;
+import org.apache.syncope.common.lib.policy.AccountRuleConf;
+import org.apache.syncope.common.lib.policy.PasswordRuleConf;
+import org.apache.syncope.common.lib.policy.PullCorrelationRuleConf;
+import org.apache.syncope.common.lib.policy.PushCorrelationRuleConf;
+import org.apache.syncope.common.lib.report.ReportletConf;
+import org.apache.syncope.core.persistence.api.ImplementationLookup;
+import org.apache.syncope.core.persistence.api.dao.AccountRule;
+import org.apache.syncope.core.persistence.api.dao.PasswordRule;
+import org.apache.syncope.core.persistence.api.dao.PullCorrelationRule;
+import org.apache.syncope.core.persistence.api.dao.PushCorrelationRule;
+import org.apache.syncope.core.persistence.api.dao.Reportlet;
+
+public class DummyImplementationLookup implements ImplementationLookup {
+
+    @Override
+    public int getOrder() {
+        return -1;
+    }
+
+    @Override
+    public Set<String> getClassNames(final String type) {
+        return Set.of();
+    }
+
+    @Override
+    public Set<Class<?>> getJWTSSOProviderClasses() {
+        return Set.of();
+    }
+
+    @Override
+    public Class<Reportlet> getReportletClass(
+            final Class<? extends ReportletConf> reportletConfClass) {
+
+        return null;
+    }
+
+    @Override
+    public Class<? extends AccountRule> getAccountRuleClass(
+            final Class<? extends AccountRuleConf> accountRuleConfClass) {
+
+        return null;
+    }
+
+    @Override
+    public Class<? extends PasswordRule> getPasswordRuleClass(
+            final Class<? extends PasswordRuleConf> passwordRuleConfClass) {
+
+        return null;
+    }
+
+    @Override
+    public Class<? extends PullCorrelationRule> getPullCorrelationRuleClass(
+            final Class<? extends PullCorrelationRuleConf> pullCorrelationRuleConfClass) {
+
+        return null;
+    }
+
+    @Override
+    public Class<? extends PushCorrelationRule> getPushCorrelationRuleClass(
+            final Class<? extends PushCorrelationRuleConf> pushCorrelationRuleConfClass) {
+
+        return null;
+    }
+
+    @Override
+    public Set<Class<?>> getAuditAppenderClasses() {
+        return Set.of();
+    }
+}
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/jexl/ClassFreeUberspect.java b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyServiceOps.java
similarity index 55%
copy from core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/jexl/ClassFreeUberspect.java
copy to core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyServiceOps.java
index aec38b8..ce88ef3 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/jexl/ClassFreeUberspect.java
+++ b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyServiceOps.java
@@ -16,26 +16,33 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.provisioning.java.jexl;
+package org.apache.syncope.core.logic;
 
-import org.apache.commons.jexl3.internal.introspection.Uberspect;
-import org.apache.commons.jexl3.introspection.JexlMethod;
-import org.apache.commons.jexl3.introspection.JexlPropertyGet;
+import java.util.List;
+import org.apache.syncope.common.keymaster.client.api.ServiceOps;
+import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
+import org.springframework.stereotype.Component;
 
-class ClassFreeUberspect extends Uberspect {
+@Component
+public class DummyServiceOps implements ServiceOps {
 
-    ClassFreeUberspect() {
-        super(null, null);
+    @Override
+    public void register(final NetworkService service) {
+        // do nothing
     }
 
     @Override
-    public JexlPropertyGet getPropertyGet(final Object obj, final Object identifier) {
-        return "class".equals(identifier) ? null : super.getPropertyGet(obj, identifier);
+    public void unregister(final NetworkService service) {
+        // do nothing
     }
 
     @Override
-    public JexlMethod getMethod(final Object obj, final String method, final Object... args) {
-        return "getClass".equals(method) ? null : super.getMethod(obj, method, args);
+    public List<NetworkService> list(final NetworkService.Type serviceType) {
+        return List.of();
     }
 
+    @Override
+    public NetworkService get(final NetworkService.Type serviceType) {
+        return null;
+    }
 }
diff --git a/core/idm/logic/src/test/java/org/apache/syncope/core/logic/IdMLogicTestContext.java b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/IdMLogicTestContext.java
new file mode 100644
index 0000000..2b4d0f9
--- /dev/null
+++ b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/IdMLogicTestContext.java
@@ -0,0 +1,56 @@
+/*
+ * 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 java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import org.apache.syncope.core.persistence.api.ImplementationLookup;
+import org.apache.syncope.core.persistence.jpa.PersistenceContext;
+import org.apache.syncope.core.provisioning.java.ProvisioningContext;
+import org.apache.syncope.core.spring.security.SecurityContext;
+import org.apache.syncope.core.workflow.java.WorkflowContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.Primary;
+import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
+
+@Import({ SecurityContext.class, PersistenceContext.class, ProvisioningContext.class, WorkflowContext.class })
+@ComponentScan("org.apache.syncope.core.logic")
+@Configuration
+public class IdMLogicTestContext {
+
+    @Bean
+    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() throws IOException {
+        PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
+        pspc.setIgnoreResourceNotFound(true);
+        pspc.setIgnoreUnresolvablePlaceholders(true);
+        return pspc;
+    }
+
+    @Primary
+    @Bean
+    public ImplementationLookup classPathScanImplementationLookup()
+            throws ClassNotFoundException, InstantiationException, IllegalAccessException,
+            NoSuchMethodException, IllegalArgumentException, InvocationTargetException {
+
+        return new DummyImplementationLookup();
+    }
+}
diff --git a/core/idm/logic/src/test/java/org/apache/syncope/core/logic/ReconciliationLogicTest.java b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/ReconciliationLogicTest.java
new file mode 100644
index 0000000..72d1a93
--- /dev/null
+++ b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/ReconciliationLogicTest.java
@@ -0,0 +1,153 @@
+/*
+ * 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.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.callAsAdmin(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.callAsAdmin(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.callAsAdmin(SyncopeConstants.MASTER_DOMAIN,
+                () -> userLogic.search(null, 1, 100, List.of(), 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.callAsAdmin(SyncopeConstants.MASTER_DOMAIN, () -> {
+            return reconciliationLogic.push(null, 1, 1, List.of(), 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/idm/logic/src/test/java/org/apache/syncope/core/logic/TestInitializer.java b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/TestInitializer.java
new file mode 100644
index 0000000..1a4a652
--- /dev/null
+++ b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/TestInitializer.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.logic;
+
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.core.persistence.api.DomainHolder;
+import org.apache.syncope.core.persistence.api.content.ContentLoader;
+import org.apache.syncope.core.persistence.jpa.StartupDomainLoader;
+import org.apache.syncope.core.spring.ApplicationContextProvider;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+
+@Component
+public class TestInitializer implements InitializingBean, ApplicationContextAware {
+
+    private ConfigurableApplicationContext ctx;
+
+    @Autowired
+    private StartupDomainLoader domainLoader;
+
+    @Autowired
+    private DomainHolder domainHolder;
+
+    @Autowired
+    private ContentLoader contentLoader;
+
+    @Override
+    public void setApplicationContext(final ApplicationContext ctx) throws BeansException {
+        this.ctx = (ConfigurableApplicationContext) ctx;
+    }
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        ApplicationContextProvider.setApplicationContext(ctx);
+        ApplicationContextProvider.setBeanFactory((DefaultListableBeanFactory) ctx.getBeanFactory());
+
+        if (!TransactionSynchronizationManager.isSynchronizationActive()) {
+            TransactionSynchronizationManager.initSynchronization();
+        }
+
+        domainLoader.load();
+
+        contentLoader.load(
+                SyncopeConstants.MASTER_DOMAIN,
+                domainHolder.getDomains().get(SyncopeConstants.MASTER_DOMAIN));
+    }
+}
diff --git a/core/idm/logic/src/test/resources/logicTest.xml b/core/idm/logic/src/test/resources/logicTest.xml
new file mode 100644
index 0000000..af40284
--- /dev/null
+++ b/core/idm/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/idm/logic/src/test/resources/test1.csv b/core/idm/logic/src/test/resources/test1.csv
new file mode 100644
index 0000000..0ea7355
--- /dev/null
+++ b/core/idm/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/idm/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ReconciliationServiceImpl.java b/core/idm/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ReconciliationServiceImpl.java
index 7b77c2f..6f3b1a1 100644
--- a/core/idm/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ReconciliationServiceImpl.java
+++ b/core/idm/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/core/idm/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ResourceServiceImpl.java b/core/idm/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ResourceServiceImpl.java
index beb377a..c572d98 100644
--- a/core/idm/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ResourceServiceImpl.java
+++ b/core/idm/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ResourceServiceImpl.java
@@ -19,7 +19,6 @@
 package org.apache.syncope.core.rest.cxf.service;
 
 import java.net.URI;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -104,7 +103,7 @@ public class ResourceServiceImpl extends AbstractServiceImpl implements Resource
             final String key, final String anyTypeKey, final ConnObjectTOQuery query) {
 
         Filter filter = null;
-        Set<String> moreAttrsToGet = Collections.emptySet();
+        Set<String> moreAttrsToGet = Set.of();
         if (StringUtils.isNotBlank(query.getFiql())) {
             try {
                 FilterVisitor visitor = new FilterVisitor();
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AnyObjectLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AnyObjectLogic.java
index 05efdb7..d0ac105 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AnyObjectLogic.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AnyObjectLogic.java
@@ -86,16 +86,17 @@ public class AnyObjectLogic extends AbstractAnyLogic<AnyObjectTO, AnyObjectCR, A
             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/idrepo/logic/src/main/java/org/apache/syncope/core/logic/GroupLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/GroupLogic.java
index f563347..daf795b 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/GroupLogic.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/GroupLogic.java
@@ -24,7 +24,6 @@ import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 import javax.annotation.Resource;
@@ -33,7 +32,6 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
 import org.apache.syncope.common.lib.SyncopeClientException;
-import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.request.GroupCR;
 import org.apache.syncope.common.lib.request.GroupUR;
 import org.apache.syncope.common.lib.request.StringPatchItem;
@@ -159,16 +157,18 @@ public class GroupLogic extends AbstractAnyLogic<GroupTO, GroupCR, GroupUR> {
             final String realm,
             final boolean details) {
 
-        int count = searchDAO.count(
-                RealmUtils.getEffective(SyncopeConstants.FULL_ADMIN_REALMS, realm),
-                Optional.ofNullable(searchCond).orElseGet(() -> groupDAO.getAllMatchingCond()), AnyTypeKind.GROUP);
+        Set<String> adminRealms = RealmUtils.getEffective(
+                AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.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),
-                Optional.ofNullable(searchCond).orElseGet(() -> groupDAO.getAllMatchingCond()),
-                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/idrepo/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
index 16b0122..41acb64 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java
@@ -43,11 +43,14 @@ import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.apache.syncope.common.lib.types.TaskType;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.batch.BatchResponseItem;
+import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.core.persistence.api.dao.NotificationDAO;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 import org.apache.syncope.core.persistence.api.dao.TaskDAO;
 import org.apache.syncope.core.persistence.api.dao.TaskExecDAO;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.entity.task.NotificationTask;
+import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
 import org.apache.syncope.core.persistence.api.entity.task.SchedTask;
 import org.apache.syncope.core.persistence.api.entity.task.Task;
 import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
@@ -56,8 +59,6 @@ import org.apache.syncope.core.persistence.api.entity.task.TaskUtilsFactory;
 import org.apache.syncope.core.provisioning.api.data.TaskDataBinder;
 import org.apache.syncope.core.provisioning.api.job.JobNamer;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
-import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
-import org.apache.syncope.core.persistence.api.dao.NotificationDAO;
 import org.apache.syncope.core.provisioning.api.notification.NotificationJobDelegate;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
 import org.apache.syncope.core.provisioning.api.utils.ExceptionUtils2;
@@ -232,18 +233,17 @@ public class TaskLogic extends AbstractExecutableLogic<TaskTO> {
 
         TaskUtils taskUtil = taskUtilsFactory.getInstance(task);
         String executor = AuthContextUtils.getUsername();
-        
+
         ExecTO result = null;
         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/idrepo/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
index 0651a98..270c438 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
@@ -23,7 +23,6 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.ArrayUtils;
@@ -111,14 +110,14 @@ public class UserLogic extends AbstractAnyLogic<UserTO, UserCR, UserUR> {
             final String realm,
             final boolean details) {
 
-        int count = searchDAO.count(RealmUtils.getEffective(
-                AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.USER_SEARCH), realm),
-            Optional.ofNullable(searchCond).orElseGet(() -> userDAO.getAllMatchingCond()), AnyTypeKind.USER);
+        Set<String> adminRealms = RealmUtils.getEffective(
+                AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.USER_SEARCH), realm);
 
-        List<User> matching = searchDAO.search(RealmUtils.getEffective(
-                AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.USER_SEARCH), realm),
-            Optional.ofNullable(searchCond).orElseGet(() -> userDAO.getAllMatchingCond()),
-                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/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 05d6a34..0be1f96 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
@@ -485,22 +485,18 @@ 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())).
-                    forEach(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 -> {
+        }).forEach(entry -> entry.getValue().forEach(typeClass -> {
             if (reference.equals(PlainSchema.class)) {
                 result.getForMemberships().get(entry.getKey()).
                         addAll((Collection<? extends S>) typeClass.getPlainSchemas());
@@ -543,7 +539,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 46f98ad..9309935 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,9 +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 020d4a5..e61568e 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
@@ -124,16 +124,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 103db94..f803af7 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 b397149..4ddccca 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,8 +26,8 @@ import org.apache.syncope.common.lib.request.StatusR;
 import org.apache.syncope.common.lib.request.UserCR;
 import org.apache.syncope.common.lib.request.UserUR;
 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.pushpull.ProvisioningReport;
 
 public interface UserProvisioningManager extends ProvisioningManager<UserTO, UserCR, UserUR> {
 
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 94%
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 83ee4cf..ba0e63f 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.Attr;
 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,17 +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) {
+
+        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 1dd92a8..9cc011f 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 523cc22..048f06b 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.request.UserUR;
 import org.apache.syncope.common.lib.Attr;
 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/PropagationTaskExecutor.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationTaskExecutor.java
index 6447fca..50455b6 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationTaskExecutor.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationTaskExecutor.java
@@ -70,6 +70,6 @@ public interface PropagationTaskExecutor {
      * @param executor the executor of this task
      * @return reporter to report propagation execution status
      */
-    PropagationReporter execute(Collection<PropagationTaskInfo> taskInfos, boolean nullPriorityAsync,
-                                String executor);
+    PropagationReporter execute(
+            Collection<PropagationTaskInfo> taskInfos, boolean nullPriorityAsync, String executor);
 }
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 ae97ff1..0070836 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
@@ -24,6 +24,7 @@ import org.apache.syncope.common.lib.request.AnyUR;
 import org.apache.syncope.common.lib.to.LinkedAccountTO;
 import org.apache.syncope.common.lib.to.RealmTO;
 import org.apache.syncope.common.lib.to.EntityTO;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.core.persistence.api.entity.resource.OrgUnit;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.identityconnectors.framework.common.objects.SyncDelta;
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 c0ca069..7614291 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..5ff51c8
--- /dev/null
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/stream/StreamConnector.java
@@ -0,0 +1,208 @@
+/*
+ * 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.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 Set.of();
+    }
+
+    @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 c0ca069..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,21 +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;
 
-@FunctionalInterface
-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..b70a2c4 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,23 @@
  * 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,
+            String executor)
+            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 a9610df..146e9d8 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>
@@ -142,6 +137,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 16faf61..ad89d8f 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 bb86889..49da4df 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
@@ -45,7 +45,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 0f4757f..27dbef4 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
@@ -23,13 +23,13 @@ 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.persistence.api.entity.Any;
 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.GroupableRelatable;
 import org.apache.syncope.core.persistence.api.entity.Membership;
 import org.apache.syncope.core.provisioning.api.DerAttrHandler;
+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/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 63fd67d..e84d043 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.Objects;
 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()) {
@@ -758,8 +844,8 @@ public class MappingManagerImpl implements MappingManager {
                     PlainAttrGetter.DEFAULT);
         }
 
-        return Optional.ofNullable(preparedAttr)
-                .map(attr -> MappingUtils.evaluateNAME(any, provision, attr.getKey()).getNameValue()).orElse(null);
+        return Optional.ofNullable(preparedAttr).
+                map(attr -> evaluateNAME(any, provision, attr.getKey()).getNameValue()).orElse(null);
     }
 
     @Transactional(readOnly = true)
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java
index 8b14cd9..92507b0 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java
@@ -29,6 +29,7 @@ import org.apache.syncope.core.provisioning.api.AnyObjectProvisioningManager;
 import org.apache.syncope.core.provisioning.api.AuditManager;
 import org.apache.syncope.core.provisioning.api.ConnIdBundleManager;
 import org.apache.syncope.core.provisioning.api.GroupProvisioningManager;
+import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
 import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
 import org.apache.syncope.core.provisioning.api.cache.VirAttrCache;
 import org.apache.syncope.core.provisioning.api.job.JobManager;
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 4f9649a..baa598f 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
@@ -73,8 +73,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;
@@ -226,7 +226,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());
                 }
@@ -256,7 +256,7 @@ abstract class AbstractAnyDataBinder {
         return reqValMissing;
     }
 
-    private static void checkMandatory(
+    private void checkMandatory(
             final PlainSchema schema,
             final PlainAttr<?> attr,
             final Any<?> any,
@@ -264,7 +264,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");
 
@@ -272,14 +272,13 @@ abstract class AbstractAnyDataBinder {
         }
     }
 
-    private static SyncopeClientException checkMandatory(final Any<?> any, final AnyUtils anyUtils) {
+    private SyncopeClientException checkMandatory(final Any<?> any, final AnyUtils anyUtils) {
         SyncopeClientException reqValMissing = SyncopeClientException.build(ClientExceptionType.RequiredValuesMissing);
 
         // Check if there is some mandatory schema defined for which no value has been provided
         AllowedSchemas<PlainSchema> allowedPlainSchemas = anyUtils.dao().findAllowedSchemas(any, PlainSchema.class);
-        allowedPlainSchemas.getForSelf()
-                .forEach(schema -> checkMandatory(schema, any.getPlainAttr(schema.getKey())
-                .orElse(null), any, reqValMissing));
+        allowedPlainSchemas.getForSelf().forEach(schema -> checkMandatory(
+                schema, any.getPlainAttr(schema.getKey()).orElse(null), any, reqValMissing));
         if (any instanceof GroupableRelatable) {
             allowedPlainSchemas.getForMemberships().forEach((group, schemas) -> {
                 GroupableRelatable<?, ?, ?, ?, ?> groupable = GroupableRelatable.class.cast(any);
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 e3d69e5..997b7d6 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 c06d18d..0a5a695 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 278f31b..96dad29 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
@@ -50,7 +50,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.ImplementationDAO;
 import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
@@ -67,7 +67,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 38350c4..e5d48b0 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 efc039e..1d50916 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
@@ -19,11 +19,9 @@
 package org.apache.syncope.core.provisioning.java.data;
 
 import java.util.Collection;
-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;
@@ -69,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;
@@ -726,14 +723,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) : Map.of(),
                 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 37a13e7..e1f9220 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
@@ -73,8 +73,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 aa7bde0..7895244 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
@@ -338,12 +338,21 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
     }
 
     @Override
-    public TaskExec execute(final PropagationTaskInfo taskInfo, final PropagationReporter reporter,
+    public TaskExec execute(
+            final PropagationTaskInfo taskInfo,
+            final PropagationReporter reporter,
             final String executor) {
+
         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());
@@ -360,9 +369,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();
 
@@ -382,12 +393,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
@@ -425,7 +434,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();
@@ -509,12 +518,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) {
@@ -523,7 +532,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
                     AuthContextUtils.getUsername(),
                     AuditElements.EventCategoryType.PROPAGATION,
                     anyTypeKind,
-                    resource,
+                    task.getResource().getKey(),
                     operation,
                     result,
                     beforeObj,
@@ -534,7 +543,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 45c5f63..854340d 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
@@ -119,11 +123,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);
@@ -136,13 +138,13 @@ public class LDAPMembershipPropagationActions implements PropagationActions {
         }
     }
 
-    private static String evaluateGroupConnObjectLink(final String connObjectLinkTemplate, final Group group) {
+    private String evaluateGroupConnObjectLink(final String connObjectLinkTemplate, final Group group) {
         LOG.debug("Evaluating connObjectLink for {}", group);
 
         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 0a60ba2..b6db281 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,7 +18,6 @@
  */
 package org.apache.syncope.core.provisioning.java.propagation;
 
-import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Comparator;
@@ -36,7 +35,6 @@ 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.Exec;
-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;
@@ -85,30 +83,25 @@ public class PriorityPropagationTaskExecutor extends AbstractPropagationTaskExec
             final boolean nullPriorityAsync,
             final String executorId) {
 
-        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);
             }
         });
 
-        prioritizedTasks.sort(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);
 
@@ -171,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 1c38712..9f7b979 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
@@ -18,6 +18,15 @@
  */
 package org.apache.syncope.core.provisioning.java.propagation;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
@@ -43,14 +52,15 @@ 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.PropagationByResource;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationManager;
-import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
 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.jexl.JexlUtils;
 import org.apache.syncope.core.provisioning.java.utils.ConnObjectUtils;
 import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
 import org.identityconnectors.framework.common.objects.Attribute;
@@ -61,15 +71,6 @@ import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.stream.Collectors;
 
 /**
  * Manage the data propagation to external resources.
@@ -85,30 +86,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;
 
@@ -116,6 +105,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) {
@@ -388,17 +380,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());
@@ -413,15 +406,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));
@@ -528,8 +522,9 @@ public class PropagationManagerImpl implements PropagationManager {
                 }
 
                 PropagationTaskInfo task = newTask(
+                        derAttrHandler,
                         any,
-                        resourceKey,
+                        resource,
                         operation,
                         provision,
                         deleteOnResource,
@@ -571,8 +566,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,
@@ -620,8 +616,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 15ec686..9884a63 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
@@ -19,9 +19,9 @@
 package org.apache.syncope.core.provisioning.java.pushpull;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Date;
 import java.util.List;
+import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.apache.syncope.common.lib.AnyOperations;
@@ -55,7 +55,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;
@@ -188,7 +188,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
         if (!profile.getTask().isPerformCreate()) {
             LOG.debug("PullTask not configured for create");
             end(provision.getAnyType().getKind(), UnmatchingRule.toEventName(rule), Result.SUCCESS, null, null, delta);
-            return Collections.<ProvisioningReport>emptyList();
+            return List.of();
         }
 
         AnyCR anyCR = connObjectUtils.getAnyCR(delta.getObject(), profile.getTask(), provision, true);
@@ -263,7 +263,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
             end(provision.getAnyType().getKind(), UnmatchingRule.toEventName(rule), resultStatus, null, output, delta);
         }
 
-        return Collections.singletonList(result);
+        return List.of(result);
     }
 
     protected void throwIgnoreProvisionException(final SyncDelta delta, final Exception exception)
@@ -293,7 +293,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
             LOG.debug("PullTask not configured for update");
             end(provision.getAnyType().getKind(),
                     MatchingRule.toEventName(MatchingRule.UPDATE), Result.SUCCESS, null, null, delta);
-            return Collections.<ProvisioningReport>emptyList();
+            return List.of();
         }
 
         LOG.debug("About to update {}", matches);
@@ -402,7 +402,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
             LOG.debug("PullTask not configured for update");
             end(provision.getAnyType().getKind(),
                     MatchingRule.toEventName(matchingRule), Result.SUCCESS, null, null, delta);
-            return Collections.<ProvisioningReport>emptyList();
+            return List.of();
         }
 
         LOG.debug("About to deprovision {}", matches);
@@ -519,7 +519,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
                             ? MatchingRule.toEventName(MatchingRule.UNLINK)
                             : MatchingRule.toEventName(MatchingRule.LINK),
                     Result.SUCCESS, null, null, delta);
-            return Collections.<ProvisioningReport>emptyList();
+            return List.of();
         }
 
         LOG.debug("About to update {}", matches);
@@ -619,7 +619,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
             LOG.debug("PullTask not configured for delete");
             end(provision.getAnyType().getKind(),
                     ResourceOperation.DELETE.name().toLowerCase(), Result.SUCCESS, null, null, delta);
-            return Collections.<ProvisioningReport>emptyList();
+            return List.of();
         }
 
         LOG.debug("About to delete {}", matches);
@@ -649,7 +649,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
                     try {
                         getProvisioningManager().delete(
                                 match.getAny().getKey(),
-                                Collections.singleton(profile.getTask().getResource().getKey()),
+                                Set.of(profile.getTask().getResource().getKey()),
                                 true);
                         output = null;
                         resultStatus = Result.SUCCESS;
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 f8a4e1b..50310f2 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 fd7add0..9afbbb2 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
@@ -35,7 +35,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 a87fff4..14943a0 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
@@ -37,7 +37,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 96c765a..63ba2c0 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
@@ -40,7 +40,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 f94a7d8..cd60747 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
@@ -44,7 +44,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 a79d7e2..4d3bf03 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 5e79274..facde19 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
@@ -18,7 +18,6 @@
  */
 package org.apache.syncope.core.provisioning.java.pushpull;
 
-import java.util.Collections;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
@@ -58,7 +57,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.apache.syncope.core.spring.security.AuthContextUtils;
 import org.identityconnectors.framework.common.objects.SyncDelta;
@@ -368,7 +367,7 @@ public class DefaultUserPullResultHandler extends AbstractPullResultHandler impl
                         req,
                         report,
                         null,
-                        Collections.singleton(profile.getTask().getResource().getKey()),
+                        Set.of(profile.getTask().getResource().getKey()),
                         true);
                 resultStatus = Result.SUCCESS;
 
@@ -493,7 +492,7 @@ public class DefaultUserPullResultHandler extends AbstractPullResultHandler impl
                         patch,
                         report,
                         null,
-                        Collections.singleton(profile.getTask().getResource().getKey()),
+                        Set.of(profile.getTask().getResource().getKey()),
                         true);
                 resultStatus = Result.SUCCESS;
 
@@ -587,7 +586,7 @@ public class DefaultUserPullResultHandler extends AbstractPullResultHandler impl
                             req,
                             report,
                             null,
-                            Collections.singleton(profile.getTask().getResource().getKey()),
+                            Set.of(profile.getTask().getResource().getKey()),
                             true);
                     resultStatus = Result.SUCCESS;
 
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 a34bc6b..8f04fef 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 a7ec5f3..3646a76 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
@@ -20,7 +20,6 @@ package org.apache.syncope.core.provisioning.java.pushpull;
 
 import java.text.ParseException;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
 import java.util.stream.Collectors;
@@ -58,7 +57,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;
@@ -212,13 +211,13 @@ public class InboundMatcher {
             List<Object> output = transformer.beforePull(
                     connObjectKeyItem,
                     null,
-                    Collections.<Object>singletonList(finalConnObjectKeyValue));
+                    List.of(finalConnObjectKeyValue));
             if (!CollectionUtils.isEmpty(output)) {
                 finalConnObjectKeyValue = output.get(0).toString();
             }
         }
 
-        List<PullMatch> noMatchResult = Collections.singletonList(PullCorrelationRule.NO_MATCH);
+        List<PullMatch> noMatchResult = List.of(PullCorrelationRule.NO_MATCH);
 
         IntAttrName intAttrName;
         try {
@@ -356,7 +355,7 @@ public class InboundMatcher {
             }
         }
 
-        List<PullMatch> result = Collections.emptyList();
+        List<PullMatch> result = List.of();
         try {
             if (rule.isPresent()) {
                 result = matchByCorrelationRule(syncDelta, provision, rule.get(), provision.getAnyType().getKind());
@@ -376,7 +375,7 @@ public class InboundMatcher {
                     }
                 }
                 if (connObjectKeyValue == null) {
-                    result = Collections.singletonList(PullCorrelationRule.NO_MATCH);
+                    result = List.of(PullCorrelationRule.NO_MATCH);
                 } else {
                     result = matchByConnObjectKeyValue(connObjectKeyItem.get(), connObjectKeyValue, provision);
                 }
@@ -412,14 +411,14 @@ public class InboundMatcher {
             }
         }
         if (connObjectKey == null) {
-            return Collections.emptyList();
+            return List.of();
         }
 
         for (ItemTransformer transformer : MappingUtils.getItemTransformers(connObjectKeyItem.get())) {
             List<Object> output = transformer.beforePull(
                     connObjectKeyItem.get(),
                     null,
-                    Collections.<Object>singletonList(connObjectKey));
+                    List.of(connObjectKey));
             if (!CollectionUtils.isEmpty(output)) {
                 connObjectKey = output.get(0).toString();
             }
@@ -459,4 +458,3 @@ public class InboundMatcher {
         return result;
     }
 }
-
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 7b09175..22550fc 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
@@ -30,7 +30,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 0c7df3f..cb4703c 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
@@ -35,7 +35,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/PushJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
index 2638b96..726323c 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
@@ -128,22 +128,22 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
         }
     }
 
-    protected static RealmPushResultHandler buildRealmHandler() {
+    protected RealmPushResultHandler buildRealmHandler() {
         return (RealmPushResultHandler) ApplicationContextProvider.getBeanFactory().
                 createBean(DefaultRealmPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
     }
 
-    protected static AnyObjectPushResultHandler buildAnyObjectHandler() {
+    protected AnyObjectPushResultHandler buildAnyObjectHandler() {
         return (AnyObjectPushResultHandler) ApplicationContextProvider.getBeanFactory().
                 createBean(DefaultAnyObjectPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
     }
 
-    protected static UserPushResultHandler buildUserHandler() {
+    protected UserPushResultHandler buildUserHandler() {
         return (UserPushResultHandler) ApplicationContextProvider.getBeanFactory().
                 createBean(DefaultUserPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
     }
 
-    protected static GroupPushResultHandler buildGroupHandler() {
+    protected GroupPushResultHandler buildGroupHandler() {
         return (GroupPushResultHandler) ApplicationContextProvider.getBeanFactory().
                 createBean(DefaultGroupPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
     }
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 02abde5..a31d08e 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
@@ -23,6 +23,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Stream;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.common.lib.to.PullTaskTO;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.ConflictResolutionAction;
@@ -42,14 +43,13 @@ 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.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;
@@ -160,7 +160,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])));
 
@@ -182,13 +182,13 @@ public class SinglePullJobDelegate extends PullJobDelegate implements SyncopeSin
         }
     }
 
-    static class AccountReconciliationFilterBuilder implements ReconFilterBuilder {
+    static 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 4889311..f0c4258 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
@@ -33,7 +33,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..17576cb
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamAnyObjectPushResultHandler.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+    private String executor;
+
+    public void setExecutor(final String executor) {
+        this.executor = executor;
+    }
+
+    @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, executor);
+        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..32e6710
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamGroupPushResultHandler.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+    private String executor;
+
+    public void setExecutor(final String executor) {
+        this.executor = executor;
+    }
+
+    @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, executor);
+        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..ff6553e
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java
@@ -0,0 +1,264 @@
+/*
+ * 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.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.common.lib.types.IdMImplementationType;
+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.apache.syncope.core.spring.security.SecureRandomUtils;
+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 impl = implementationDAO.find(pullCorrelationRule);
+            if (impl == null || !IdMImplementationType.PULL_ACTIONS.equals(impl.getType())) {
+                LOG.debug("Invalid " + Implementation.class.getSimpleName() + " {}, ignoring...", pullCorrelationRule);
+            } else {
+                pullCorrelationRuleEntity = entityFactory.newEntity(PullCorrelationRuleEntity.class);
+                pullCorrelationRuleEntity.setAnyType(anyType);
+                pullCorrelationRuleEntity.setImplementation(impl);
+            }
+        }
+
+        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.setKey("StreamPull_" + SecureRandomUtils.generateRandomUUID().toString());
+        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 || !IdMImplementationType.PULL_ACTIONS.equals(impl.getType())) {
+                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..1b775d4
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegate.java
@@ -0,0 +1,200 @@
+/*
+ * 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.IdMImplementationType;
+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;
+
+    private String executor;
+
+    @Override
+    protected AnyObjectPushResultHandler buildAnyObjectHandler() {
+        StreamAnyObjectPushResultHandler handler =
+                (StreamAnyObjectPushResultHandler) ApplicationContextProvider.getBeanFactory().createBean(
+                        StreamAnyObjectPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
+        handler.setExecutor(executor);
+        return handler;
+    }
+
+    @Override
+    protected UserPushResultHandler buildUserHandler() {
+        StreamUserPushResultHandler handler =
+                (StreamUserPushResultHandler) ApplicationContextProvider.getBeanFactory().
+                        createBean(StreamUserPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
+        handler.setExecutor(executor);
+        return handler;
+    }
+
+    @Override
+    protected GroupPushResultHandler buildGroupHandler() {
+        StreamGroupPushResultHandler handler = (StreamGroupPushResultHandler) ApplicationContextProvider.
+                getBeanFactory().
+                createBean(StreamGroupPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
+        handler.setExecutor(executor);
+        return handler;
+    }
+
+    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("StreamPush_" + 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,
+            final String executor) throws JobExecutionException {
+
+        LOG.debug("Executing stream push as {}", executor);
+        this.executor = executor;
+
+        List<PushActions> actions = new ArrayList<>();
+        pushTaskTO.getActions().forEach(key -> {
+            Implementation impl = implementationDAO.find(key);
+            if (impl == null || !IdMImplementationType.PUSH_ACTIONS.equals(impl.getType())) {
+                LOG.debug("Invalid " + Implementation.class.getSimpleName() + " {}, ignoring...", key);
+            } else {
+                try {
+                    actions.add(ImplementationManager.build(impl));
+                } 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..9b788a0
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamUserPushResultHandler.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+    private String executor;
+
+    public void setExecutor(final String executor) {
+        this.executor = executor;
+    }
+
+    @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, executor);
+        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 cdb8280..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,85 +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 = Optional.ofNullable(orgUnit).map(OrgUnit::getConnObjectLink).orElse(null);
-        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 8dcdd81..79a0491 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
@@ -34,12 +34,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/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 b120f93..3d9f9ba 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,13 @@
 package org.apache.syncope.core.provisioning.java;
 
 import javax.persistence.EntityManager;
+import org.apache.syncope.common.lib.types.AMEntitlement;
+import org.apache.syncope.common.lib.types.EntitlementsHolder;
+import org.apache.syncope.common.lib.types.IdMEntitlement;
+import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 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;
 
@@ -37,4 +42,11 @@ public abstract class AbstractTest {
 
         return entityManager;
     }
+
+    @BeforeAll
+    public static void init() {
+        EntitlementsHolder.getInstance().addAll(IdRepoEntitlement.values());
+        EntitlementsHolder.getInstance().addAll(IdMEntitlement.values());
+        EntitlementsHolder.getInstance().addAll(AMEntitlement.values());
+    }
 }
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 e2ab0da..bf71aa8 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 1efd0ea..60fb718 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;
@@ -34,8 +36,8 @@ import org.apache.commons.lang3.SerializationUtils;
 import org.apache.syncope.common.lib.Attr;
 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..dfa523c
--- /dev/null
+++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegateTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.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 = List.of(user).iterator();
+
+        @SuppressWarnings("unchecked")
+        MappingIterator<Map<String, String>> itor = mock(MappingIterator.class);
+        when(itor.hasNext()).thenAnswer(invocation -> backing.hasNext());
+        when(itor.next()).thenAnswer(invocation -> backing.next());
+
+        List<String> columns = user.keySet().stream().collect(Collectors.toList());
+
+        List<ProvisioningReport> results = AuthContextUtils.callAsAdmin(SyncopeConstants.MASTER_DOMAIN, () -> {
+            try {
+                return streamPullExecutor.pull(
+                        anyTypeDAO.findUser(),
+                        "username",
+                        columns,
+                        ConflictResolutionAction.IGNORE,
+                        null,
+                        new StreamConnector("username", null, itor, null),
+                        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..2fcab3c
--- /dev/null
+++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegateTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.callAsAdmin(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,
+                        "user");
+            } 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/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 ef0b317..4b7f46e 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,10 +31,10 @@ import org.apache.syncope.common.lib.request.StatusR;
 import org.apache.syncope.common.lib.request.UserCR;
 import org.apache.syncope.common.lib.request.UserUR;
 import org.apache.syncope.common.lib.to.PropagationStatus;
+import org.apache.syncope.common.lib.to.ProvisioningReport;
 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 32d3395..587dfa5 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
@@ -40,11 +40,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 7fd536e..a206cf9 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
@@ -41,8 +41,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;
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 1d33f34..1e7c335 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
@@ -42,11 +42,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 87b765c..992a8ce 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
@@ -42,8 +42,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 c327f43..a7b532e 100644
--- a/fit/core-reference/pom.xml
+++ b/fit/core-reference/pom.xml
@@ -427,6 +427,12 @@ under the License.
         </includes>
       </testResource>
       <testResource>
+        <directory>${basedir}/../../core/idm/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>
@@ -532,6 +538,17 @@ under the License.
       <build>
         <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>
@@ -644,6 +661,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>
@@ -769,6 +797,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>
@@ -882,6 +921,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>
@@ -1025,6 +1075,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>
@@ -1114,6 +1175,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>
@@ -1203,6 +1275,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>
@@ -1299,6 +1382,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 4405fcc..3a3e537 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
@@ -69,6 +69,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;
@@ -1036,15 +1037,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 1635f02..822bb5f 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 org.apache.syncope.common.lib.request.AnyObjectCR;
+import org.apache.syncope.common.lib.Attr;
+import java.util.List;
+import java.util.Map;
 import java.util.UUID;
+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.Attr;
+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;
@@ -168,4 +190,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 95a44dd..f0a2e61 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
@@ -31,11 +31,12 @@ import java.io.IOException;
 import java.security.AccessControlException;
 import java.util.ArrayList;
 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;
@@ -858,8 +859,8 @@ public class UserITCase extends AbstractITCase {
     private static 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 05b65c8..cf3dd58 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
@@ -223,13 +223,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);
 
@@ -380,14 +380,15 @@ public class VirAttrITCase extends AbstractITCase {
         // 4. update value on external resource
         // ----------------------------------------
         JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
-        String value = queryForObject(
-                jdbcTemplate, MAX_WAIT_SECONDS, "SELECT USERNAME FROM testpull WHERE ID=?", String.class, userTO.getKey());
+        String value = queryForObject(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());
+        value = queryForObject(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 7903d41..ed2b67a 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>
@@ -973,6 +974,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>
@@ -1785,10 +1791,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>
@@ -2446,7 +2464,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/3.0.0/</link>
             <link>http://www.javadoc.io/doc/org.apache.camel/camel-spring/3.0.0/</link>


[syncope] 02/04: Upgrading Wicket SpringBoot

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

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

commit 34cf9b47683822edea712cac16b054ef8e605be4
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Mon Dec 30 08:05:54 2019 +0100

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

diff --git a/pom.xml b/pom.xml
index 5db6ec1..1cd3dd1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -483,7 +483,7 @@ under the License.
     <wicket.version>8.6.1</wicket.version>
     <wicket-jqueryui.version>8.6.0</wicket-jqueryui.version>
     <wicket-bootstrap.version>2.0.11</wicket-bootstrap.version>
-    <wicket-spring-boot.version>2.1.7</wicket-spring-boot.version>
+    <wicket-spring-boot.version>2.1.8</wicket-spring-boot.version>
     
     <ianal-maven-plugin-version>1.0-alpha-1</ianal-maven-plugin-version>