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 2018/04/12 10:42:04 UTC

[1/6] syncope git commit: Reworking 'Set admin credentials'

Repository: syncope
Updated Branches:
  refs/heads/2_0_X 0189a50fd -> 73fab3dee
  refs/heads/master 321102374 -> e5860a76a


Reworking 'Set admin credentials'


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/b7aacd8d
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/b7aacd8d
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/b7aacd8d

Branch: refs/heads/2_0_X
Commit: b7aacd8d0538da9dde6c8dc940b423a1d52ac560
Parents: 0189a50
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Thu Apr 12 11:49:28 2018 +0200
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Thu Apr 12 11:49:28 2018 +0200

----------------------------------------------------------------------
 .../setadmincredentials.adoc                    | 23 +++++++++++++-------
 1 file changed, 15 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/b7aacd8d/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/setadmincredentials.adoc
----------------------------------------------------------------------
diff --git a/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/setadmincredentials.adoc b/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/setadmincredentials.adoc
index 4292a06..10e24f0 100644
--- a/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/setadmincredentials.adoc
+++ b/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/setadmincredentials.adoc
@@ -21,22 +21,28 @@
 [WARNING]
 The procedure below affects only the `Master` <<domains,domain>>; for other domains check <<domains-management,above>>.
 
-The default password for the `admin` user is `password`.
-
 The credentials are defined in the `security.properties` file; text encoding must be set to UTF-8:
 
-* `adminUser` - administrator username
-* `adminPassword` - SHA1 hash evaluation of cleartext password (represented as a sequence of 40 hexadecimal digits)
-* `adminPasswordAlgorithm` - algorithm to be used for hash evaluation (default value: SHA1)
+* `adminUser` - administrator username (default `admin`)
+* `adminPassword` - administrator password (default `password`)'s hashed value
+* `adminPasswordAlgorithm` - algorithm to be used for hash evaluation (default `SHA1`, others as
+`SHA256`, `SHA512`, `SMD5`, `SSHA1`, `SSHA256`, `SSHA512` and `BCRYPT` are supported)
 
-For GNU / Linux and Mac OS X, the SHA1 password can be obtained via the `sha1sum` command-line tool of
-http://www.gnu.org/software/coreutils/[GNU Core Utilities^]:
+[TIP]
+====
+The hashed password value can be obtained, depending on the actual algorithm, via various tools.
 
+As an example, for `SHA1` and GNU / Linux and Mac OS X, the `sha1sum` command-line tool of
+http://www.gnu.org/software/coreutils/[GNU Core Utilities^] can be used as follows:
 [source,bash]
 ....
 echo -n "new_password" | sha1sum
 ....
-For MS Windows, some options are available:
+
+Please beware that any shell special character must be properly escaped for the command above to produce the expected
+hashed value.
+
+Again about `SHA1`, for MS Windows some options are available:
 
 * http://support.microsoft.com/kb/841290[MS File Checksum Integrity Verifier^] +
 install, save your password to a file (e.g. `password.txt` without EOL) and issue at command line: +
@@ -47,3 +53,4 @@ fciv.exe -sha1 password.txt
 * http://gnuwin32.sourceforge.net/[GnuWin32^] port of GNU utilities for MS Windows
 * http://www.cygwin.com/[Cygwin^] Unix-like environment and command-line interface for Microsoft Windows (featuring
 http://www.gnu.org/software/coreutils/[GNU Core Utilities^])
+====


[2/6] syncope git commit: [SYNCOPE-1299] Core implementation

Posted by il...@apache.org.
http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
index 91eda38..3595886 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
@@ -84,6 +84,7 @@ import org.apache.syncope.common.rest.api.service.ResourceService;
 import org.apache.syncope.common.rest.api.service.GroupService;
 import org.apache.syncope.common.rest.api.service.MailTemplateService;
 import org.apache.syncope.common.rest.api.service.RealmService;
+import org.apache.syncope.common.rest.api.service.ReconciliationService;
 import org.apache.syncope.common.rest.api.service.RelationshipTypeService;
 import org.apache.syncope.common.rest.api.service.ReportTemplateService;
 import org.apache.syncope.common.rest.api.service.ResourceHistoryService;
@@ -227,6 +228,8 @@ public abstract class AbstractITCase {
 
     protected static TaskService taskService;
 
+    protected static ReconciliationService reconciliationService;
+
     protected static WorkflowService workflowService;
 
     protected static MailTemplateService mailTemplateService;
@@ -305,6 +308,7 @@ public abstract class AbstractITCase {
         reportTemplateService = adminClient.getService(ReportTemplateService.class);
         reportService = adminClient.getService(ReportService.class);
         taskService = adminClient.getService(TaskService.class);
+        reconciliationService = adminClient.getService(ReconciliationService.class);
         policyService = adminClient.getService(PolicyService.class);
         workflowService = adminClient.getService(WorkflowService.class);
         mailTemplateService = adminClient.getService(MailTemplateService.class);

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReconciliationITCase.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..d6cd241
--- /dev/null
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReconciliationITCase.java
@@ -0,0 +1,137 @@
+/*
+ * 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.fit.core;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Date;
+import javax.sql.DataSource;
+import org.apache.syncope.common.lib.to.AnyObjectTO;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.ReconciliationRequest;
+import org.apache.syncope.common.lib.to.ReconciliationStatus;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.ReconciliationAction;
+import org.apache.syncope.fit.AbstractITCase;
+import org.identityconnectors.framework.common.objects.OperationalAttributes;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = { "classpath:testJDBCEnv.xml" })
+public class ReconciliationITCase extends AbstractITCase {
+
+    @Autowired
+    private DataSource testDataSource;
+
+    @Test
+    public void push() {
+        // 1. create printer, with no resources
+        AnyObjectTO printer = AnyObjectITCase.getSampleTO("reconciliation");
+        printer.getResources().clear();
+        printer = createAnyObject(printer).getEntity();
+        assertNotNull(printer.getKey());
+
+        // 2. verify no printer with that name is on the external resource's db
+        JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
+        assertEquals(0, jdbcTemplate.queryForList(
+                "SELECT id FROM testPRINTER WHERE printername=?", printer.getName()).size());
+
+        // 3. verify reconciliation status
+        ReconciliationStatus status =
+                reconciliationService.status(AnyTypeKind.ANY_OBJECT, printer.getName(), "resource-db-scripted");
+        assertNotNull(status);
+        assertNotNull(status.getOnSyncope());
+        assertNull(status.getOnResource());
+
+        // 4. push
+        ReconciliationRequest request = new ReconciliationRequest();
+        request.setAction(ReconciliationAction.PUSH);
+        request.setAnyKey(printer.getKey());
+        request.setAnyTypeKind(AnyTypeKind.ANY_OBJECT);
+        request.setResourceKey("resource-db-scripted");
+        reconciliationService.reconcile(request);
+
+        // 5. verify that printer is now propagated
+        assertEquals(1, jdbcTemplate.queryForList(
+                "SELECT id FROM testPRINTER WHERE printername=?", printer.getName()).size());
+
+        // 6. verify resource was not assigned
+        printer = anyObjectService.read(printer.getKey());
+        assertTrue(printer.getResources().isEmpty());
+
+        // 7. verify reconciliation status
+        status = reconciliationService.status(AnyTypeKind.ANY_OBJECT, printer.getName(), "resource-db-scripted");
+        assertNotNull(status);
+        assertNotNull(status.getOnSyncope());
+        assertNotNull(status.getOnResource());
+
+        // __ENABLE__ management depends on the actual connector...
+        AttrTO enable = status.getOnSyncope().getAttr(OperationalAttributes.ENABLE_NAME);
+        if (enable != null) {
+            status.getOnSyncope().getAttrs().remove(enable);
+        }
+        assertEquals(status.getOnSyncope(), status.getOnResource());
+    }
+
+    @Test
+    public void pull() {
+        // 1. create printer, with no resources
+        AnyObjectTO printer = AnyObjectITCase.getSampleTO("reconciliation");
+        printer.getResources().clear();
+        printer = createAnyObject(printer).getEntity();
+        assertNotNull(printer.getKey());
+        assertNotEquals("Nowhere", printer.getPlainAttr("location").getValues().get(0));
+
+        // 2. create table into the external resource's db, with same name
+        JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
+        jdbcTemplate.update(
+                "INSERT INTO TESTPRINTER (id, printername, location, deleted, lastmodification) VALUES (?,?,?,?,?)",
+                printer.getKey(), printer.getName(), "Nowhere", false, new Date());
+
+        // 3. verify reconciliation status
+        ReconciliationStatus status =
+                reconciliationService.status(AnyTypeKind.ANY_OBJECT, printer.getName(), "resource-db-scripted");
+        assertNotNull(status);
+        assertNotNull(status.getOnSyncope());
+        assertNotNull(status.getOnResource());
+        assertNotEquals(status.getOnSyncope().getAttr("LOCATION"), status.getOnResource().getAttr("LOCATION"));
+
+        // 4. pull
+        ReconciliationRequest request = new ReconciliationRequest();
+        request.setAction(ReconciliationAction.PULL);
+        request.setAnyKey(printer.getKey());
+        request.setAnyTypeKind(AnyTypeKind.ANY_OBJECT);
+        request.setResourceKey("resource-db-scripted");
+        reconciliationService.reconcile(request);
+
+        // 5. verify reconciliation result (and resource is still not assigned)
+        printer = anyObjectService.read(printer.getKey());
+        assertEquals("Nowhere", printer.getPlainAttr("location").getValues().get(0));
+        assertTrue(printer.getResources().isEmpty());
+    }
+}


[3/6] syncope git commit: [SYNCOPE-1299] Core implementation

Posted by il...@apache.org.
[SYNCOPE-1299] Core implementation


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/73fab3de
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/73fab3de
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/73fab3de

Branch: refs/heads/2_0_X
Commit: 73fab3dee3f95029bc210a16f3c0bd57814e0920
Parents: b7aacd8
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Thu Apr 12 11:50:16 2018 +0200
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Thu Apr 12 11:50:16 2018 +0200

----------------------------------------------------------------------
 .../common/lib/to/ProvisioningTaskTO.java       |   9 +-
 .../apache/syncope/common/lib/to/RealmTO.java   |   6 +-
 .../common/lib/to/ReconciliationRequest.java    |  90 +++++++
 .../common/lib/to/ReconciliationStatus.java     |  56 +++++
 .../common/lib/types/ClientExceptionType.java   |   1 +
 .../common/lib/types/ReconciliationAction.java  |  28 +++
 .../rest/api/service/ReconciliationService.java |  72 ++++++
 .../syncope/core/logic/ReconciliationLogic.java | 238 +++++++++++++++++++
 .../core/persistence/api/entity/AnyUtils.java   |   7 +-
 .../persistence/jpa/dao/JPAAnyObjectDAO.java    |   4 +-
 .../persistence/jpa/entity/JPAAnyUtils.java     |  26 +-
 .../api/pushpull/SyncopeSinglePullExecutor.java |  36 +++
 .../api/pushpull/SyncopeSinglePushExecutor.java |  34 +++
 .../provisioning/java/MappingManagerImpl.java   |   2 +-
 .../pushpull/AbstractPullResultHandler.java     |   6 +-
 .../pushpull/AbstractPushResultHandler.java     |   2 +-
 .../java/pushpull/PullJobDelegate.java          |  49 ++--
 .../provisioning/java/pushpull/PullUtils.java   |  27 +--
 .../java/pushpull/PushJobDelegate.java          |  86 ++-----
 .../java/pushpull/SinglePullJobDelegate.java    | 167 +++++++++++++
 .../java/pushpull/SinglePushJobDelegate.java    | 110 +++++++++
 .../java/utils/ConnObjectUtils.java             |   4 +-
 .../rest/cxf/service/AbstractAnyService.java    |  26 +-
 .../rest/cxf/service/AbstractServiceImpl.java   |  15 ++
 .../rest/cxf/service/AnyObjectServiceImpl.java  |   2 +-
 .../core/rest/cxf/service/GroupServiceImpl.java |   2 +-
 .../cxf/service/ReconciliationServiceImpl.java  |  53 +++++
 .../core/rest/cxf/service/UserServiceImpl.java  |   2 +-
 .../org/apache/syncope/fit/AbstractITCase.java  |   4 +
 .../syncope/fit/core/ReconciliationITCase.java  | 137 +++++++++++
 30 files changed, 1136 insertions(+), 165 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/common/lib/src/main/java/org/apache/syncope/common/lib/to/ProvisioningTaskTO.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/ProvisioningTaskTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/ProvisioningTaskTO.java
index bd45068..f17122e 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/to/ProvisioningTaskTO.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/ProvisioningTaskTO.java
@@ -20,11 +20,10 @@ package org.apache.syncope.common.lib.to;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 import io.swagger.annotations.ApiModel;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.ArrayList;
+import java.util.List;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlElementWrapper;
-
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.XmlSeeAlso;
 import javax.xml.bind.annotation.XmlType;
@@ -53,7 +52,7 @@ public abstract class ProvisioningTaskTO extends SchedTaskTO {
 
     private MatchingRule matchingRule;
 
-    private final Set<String> actionsClassNames = new HashSet<>();
+    private final List<String> actionsClassNames = new ArrayList<>();
 
     @JsonProperty(required = true)
     @XmlElement(required = true)
@@ -100,7 +99,7 @@ public abstract class ProvisioningTaskTO extends SchedTaskTO {
     @XmlElementWrapper(name = "actionsClassNames")
     @XmlElement(name = "actionsClassName")
     @JsonProperty("actionsClassNames")
-    public Set<String> getActionsClassNames() {
+    public List<String> getActionsClassNames() {
         return actionsClassNames;
     }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/common/lib/src/main/java/org/apache/syncope/common/lib/to/RealmTO.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/RealmTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/RealmTO.java
index 3902cd3..ec52760 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/to/RealmTO.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/RealmTO.java
@@ -19,8 +19,10 @@
 package org.apache.syncope.common.lib.to;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import javax.ws.rs.PathParam;
@@ -50,7 +52,7 @@ public class RealmTO extends AbstractBaseBean implements EntityTO, TemplatableTO
 
     private String passwordPolicy;
 
-    private final Set<String> actionsClassNames = new HashSet<>();
+    private final List<String> actionsClassNames = new ArrayList<>();
 
     @XmlJavaTypeAdapter(XmlGenericMapAdapter.class)
     private final Map<String, AnyTO> templates = new HashMap<>();
@@ -111,7 +113,7 @@ public class RealmTO extends AbstractBaseBean implements EntityTO, TemplatableTO
     @XmlElementWrapper(name = "actionsClassNames")
     @XmlElement(name = "actionsClassName")
     @JsonProperty("actionsClassNames")
-    public Set<String> getActionsClassNames() {
+    public List<String> getActionsClassNames() {
         return actionsClassNames;
     }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/common/lib/src/main/java/org/apache/syncope/common/lib/to/ReconciliationRequest.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/ReconciliationRequest.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/ReconciliationRequest.java
new file mode 100644
index 0000000..d6cae93
--- /dev/null
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/ReconciliationRequest.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.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.ReconciliationAction;
+
+public class ReconciliationRequest extends AbstractBaseBean {
+
+    private static final long serialVersionUID = -2592156800185957182L;
+
+    private AnyTypeKind anyTypeKind;
+
+    private String anyKey;
+
+    private String resourceKey;
+
+    private ReconciliationAction action;
+
+    private final List<String> actionsClassNames = new ArrayList<>();
+
+    @JsonProperty(required = true)
+    @XmlElement(required = true)
+    public AnyTypeKind getAnyTypeKind() {
+        return anyTypeKind;
+    }
+
+    public void setAnyTypeKind(final AnyTypeKind anyTypeKind) {
+        this.anyTypeKind = anyTypeKind;
+    }
+
+    @JsonProperty(required = true)
+    @XmlElement(required = true)
+    public String getAnyKey() {
+        return anyKey;
+    }
+
+    public void setAnyKey(final String anyKey) {
+        this.anyKey = anyKey;
+    }
+
+    @JsonProperty(required = true)
+    @XmlElement(required = true)
+    public String getResourceKey() {
+        return resourceKey;
+    }
+
+    public void setResourceKey(final String resourceKey) {
+        this.resourceKey = resourceKey;
+    }
+
+    @JsonProperty(required = true)
+    @XmlElement(required = true)
+    public ReconciliationAction getAction() {
+        return action;
+    }
+
+    public void setAction(final ReconciliationAction action) {
+        this.action = action;
+    }
+
+    @XmlElementWrapper(name = "actionsClassNames")
+    @XmlElement(name = "actionsClassName")
+    @JsonProperty("actionsClassNames")
+    public List<String> getActionsClassNames() {
+        return actionsClassNames;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/common/lib/src/main/java/org/apache/syncope/common/lib/to/ReconciliationStatus.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/ReconciliationStatus.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/ReconciliationStatus.java
new file mode 100644
index 0000000..bd4f8b7
--- /dev/null
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/ReconciliationStatus.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.common.lib.to;
+
+import io.swagger.annotations.ApiModelProperty;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+/**
+ * Reconciliation status.
+ */
+@XmlRootElement(name = "reconciliationStatus")
+@XmlType
+public class ReconciliationStatus extends AbstractBaseBean {
+
+    private static final long serialVersionUID = -8516345256596521490L;
+
+    private ConnObjectTO onSyncope;
+
+    private ConnObjectTO onResource;
+
+    @ApiModelProperty(readOnly = true)
+    public ConnObjectTO getOnSyncope() {
+        return onSyncope;
+    }
+
+    public void setOnSyncope(final ConnObjectTO onSyncope) {
+        this.onSyncope = onSyncope;
+    }
+
+    @ApiModelProperty(readOnly = true)
+    public ConnObjectTO getOnResource() {
+        return onResource;
+    }
+
+    public void setOnResource(final ConnObjectTO onResource) {
+        this.onResource = onResource;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/common/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java b/common/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java
index c4077e5..f641a70 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java
@@ -72,6 +72,7 @@ public enum ClientExceptionType {
     InUseByNotifications(Response.Status.BAD_REQUEST),
     Scheduling(Response.Status.BAD_REQUEST),
     DelegatedAdministration(Response.Status.FORBIDDEN),
+    Reconciliation(Response.Status.BAD_REQUEST),
     Unknown(Response.Status.BAD_REQUEST),
     Workflow(Response.Status.BAD_REQUEST);
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/common/lib/src/main/java/org/apache/syncope/common/lib/types/ReconciliationAction.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/types/ReconciliationAction.java b/common/lib/src/main/java/org/apache/syncope/common/lib/types/ReconciliationAction.java
new file mode 100644
index 0000000..19b68c2
--- /dev/null
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/types/ReconciliationAction.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.lib.types;
+
+import javax.xml.bind.annotation.XmlEnum;
+
+@XmlEnum
+public enum ReconciliationAction {
+    PUSH,
+    PULL
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ReconciliationService.java
----------------------------------------------------------------------
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ReconciliationService.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ReconciliationService.java
new file mode 100644
index 0000000..ddbbd5d
--- /dev/null
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ReconciliationService.java
@@ -0,0 +1,72 @@
+/*
+ * 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.service;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.Authorization;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.to.ReconciliationRequest;
+import org.apache.syncope.common.lib.to.ReconciliationStatus;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+
+/**
+ * REST operations for tasks.
+ */
+@Api(tags = "Reconciliation", authorizations = {
+    @Authorization(value = "BasicAuthentication"),
+    @Authorization(value = "Bearer") })
+@Path("reconciliation")
+public interface ReconciliationService extends JAXRSService {
+
+    /**
+     * Gets current attributes on Syncope and on the given External Resource, related to given user, group or
+     * any object.
+     *
+     * @param anyTypeKind anyTypeKind
+     * @param anyKey user, group or any object: if value looks like a UUID then it is interpreted as key, otherwise as
+     * a (user)name
+     * @param resourceKey resource key
+     * @return reconciliation status
+     */
+    @GET
+    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    ReconciliationStatus status(
+            @NotNull @QueryParam("anyTypeKind") AnyTypeKind anyTypeKind,
+            @NotNull @QueryParam("anyKey") String anyKey,
+            @NotNull @QueryParam("resourceKey") String resourceKey);
+
+    /**
+     * Perform the required reconciliation action (PUSH or PULL) to the given user, group or any object and
+     * External Resource.
+     *
+     * @param request reconciliation request
+     */
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    void reconcile(@NotNull ReconciliationRequest request);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/core/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
new file mode 100644
index 0000000..7dee27b
--- /dev/null
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
@@ -0,0 +1,238 @@
+/*
+ * 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.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import org.apache.commons.collections4.IteratorUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.ConnObjectTO;
+import org.apache.syncope.common.lib.to.ReconciliationRequest;
+import org.apache.syncope.common.lib.to.ReconciliationStatus;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.StandardEntitlement;
+import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.dao.RealmDAO;
+import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
+import org.apache.syncope.core.persistence.api.entity.Any;
+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.VirSchema;
+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.persistence.api.entity.resource.Provision;
+import org.apache.syncope.core.provisioning.api.Connector;
+import org.apache.syncope.core.provisioning.api.ConnectorFactory;
+import org.apache.syncope.core.provisioning.api.MappingManager;
+import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
+import org.apache.syncope.core.provisioning.api.pushpull.SyncopeSinglePullExecutor;
+import org.apache.syncope.core.provisioning.api.pushpull.SyncopeSinglePushExecutor;
+import org.apache.syncope.core.provisioning.java.utils.ConnObjectUtils;
+import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
+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.Name;
+import org.identityconnectors.framework.common.objects.Uid;
+import org.quartz.JobExecutionException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ReconciliationLogic extends AbstractTransactionalLogic<AbstractBaseBean> {
+
+    @Autowired
+    private AnyUtilsFactory anyUtilsFactory;
+
+    @Autowired
+    private ExternalResourceDAO resourceDAO;
+
+    @Autowired
+    private VirSchemaDAO virSchemaDAO;
+
+    @Autowired
+    private RealmDAO realmDAO;
+
+    @Autowired
+    private MappingManager mappingManager;
+
+    @Autowired
+    private ConnectorFactory connFactory;
+
+    @Autowired
+    private SyncopeSinglePullExecutor singlePullExecutor;
+
+    @Autowired
+    private SyncopeSinglePushExecutor singlePushExecutor;
+
+    @SuppressWarnings("unchecked")
+    private Pair<Any<?>, Provision> init(final AnyTypeKind anyTypeKind, final String anyKey, final String resourceKey) {
+        AnyUtils anyUtils = anyUtilsFactory.getInstance(anyTypeKind);
+
+        Any<?> any = anyUtils.dao().authFind(anyKey);
+        if (any == null) {
+            throw new NotFoundException(anyTypeKind + " '" + anyKey + "'");
+        }
+
+        ExternalResource resource = resourceDAO.find(resourceKey);
+        if (resource == null) {
+            throw new NotFoundException("Resource '" + resourceKey + "'");
+        }
+        Provision provision = resource.getProvision(any.getType());
+        if (provision == null) {
+            throw new NotFoundException("Provision for " + any.getType() + " on Resource '" + resourceKey + "'");
+        }
+        if (provision.getMapping() == null) {
+            throw new NotFoundException("Mapping for " + any.getType() + " on Resource '" + resourceKey + "'");
+        }
+
+        return (Pair<Any<?>, Provision>) Pair.of(any, provision);
+    }
+
+    private ConnObjectTO getOnSyncope(final Any<?> any, final Provision provision, final String resourceKey) {
+        Pair<String, Set<Attribute>> attrs = mappingManager.prepareAttrs(any, null, false, true, provision);
+
+        MappingItem connObjectKey = provision.getMapping().getConnObjectKeyItem();
+        if (connObjectKey == null) {
+            throw new NotFoundException("No RemoteKey set for " + resourceKey);
+        }
+
+        ConnObjectTO connObjectTO = ConnObjectUtils.getConnObjectTO(attrs.getRight());
+        if (attrs.getLeft() != null) {
+            connObjectTO.getAttrs().add(new AttrTO.Builder().
+                    schema(connObjectKey.getExtAttrName()).value(attrs.getLeft()).build());
+            connObjectTO.getAttrs().add(new AttrTO.Builder().
+                    schema(Uid.NAME).value(attrs.getLeft()).build());
+        }
+
+        return connObjectTO;
+    }
+
+    private ConnObjectTO getOnResource(final Any<?> any, final Provision provision) {
+        // 1. build connObjectKeyItem
+        MappingItem connObjectKeyItem = MappingUtils.getConnObjectKeyItem(provision);
+        if (connObjectKeyItem == null) {
+            throw new NotFoundException("ConnObjectKey for " + any.getType()
+                    + " on resource '" + provision.getResource().getKey() + "'");
+        }
+        String connObjectKeyValue = mappingManager.getConnObjectKeyValue(any, provision);
+
+        // 2. determine attributes to query
+        Set<MappingItem> linkinMappingItems = new HashSet<>();
+        for (VirSchema virSchema : virSchemaDAO.findByProvision(provision)) {
+            linkinMappingItems.add(virSchema.asLinkingMappingItem());
+        }
+        Iterator<MappingItem> mapItems = IteratorUtils.chainedIterator(
+                provision.getMapping().getItems().iterator(),
+                linkinMappingItems.iterator());
+
+        // 3. read from the underlying connector
+        ConnObjectTO connObjectTO = null;
+
+        Connector connector = connFactory.getConnector(provision.getResource());
+        ConnectorObject connectorObject = connector.getObject(
+                provision.getObjectClass(),
+                AttributeBuilder.build(connObjectKeyItem.getExtAttrName(), connObjectKeyValue),
+                MappingUtils.buildOperationOptions(mapItems));
+        if (connectorObject != null) {
+            Set<Attribute> attributes = connectorObject.getAttributes();
+            if (AttributeUtil.find(Uid.NAME, attributes) == null) {
+                attributes.add(connectorObject.getUid());
+            }
+            if (AttributeUtil.find(Name.NAME, attributes) == null) {
+                attributes.add(connectorObject.getName());
+            }
+
+            connObjectTO = ConnObjectUtils.getConnObjectTO(attributes);
+        }
+
+        return connObjectTO;
+    }
+
+    @PreAuthorize("hasRole('" + StandardEntitlement.RESOURCE_GET_CONNOBJECT + "')")
+    public ReconciliationStatus status(final AnyTypeKind anyTypeKind, final String anyKey, final String resourceKey) {
+        Pair<Any<?>, Provision> init = init(anyTypeKind, anyKey, resourceKey);
+
+        ReconciliationStatus status = new ReconciliationStatus();
+        status.setOnSyncope(getOnSyncope(init.getLeft(), init.getRight(), resourceKey));
+        status.setOnResource(getOnResource(init.getLeft(), init.getRight()));
+
+        return status;
+    }
+
+    @PreAuthorize("hasRole('" + StandardEntitlement.TASK_EXECUTE + "')")
+    public void reconcile(final ReconciliationRequest request) {
+        Pair<Any<?>, Provision> init = init(request.getAnyTypeKind(), request.getAnyKey(), request.getResourceKey());
+
+        SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Reconciliation);
+        try {
+            List<ProvisioningReport> results = null;
+            switch (request.getAction()) {
+                case PUSH:
+                    results = singlePushExecutor.push(
+                            init.getRight(),
+                            connFactory.getConnector(init.getRight().getResource()),
+                            init.getLeft(),
+                            request.getActionsClassNames());
+                    break;
+
+                case PULL:
+                    results = singlePullExecutor.pull(
+                            init.getRight(),
+                            connFactory.getConnector(init.getRight().getResource()),
+                            init.getRight().getMapping().getConnObjectKeyItem().getExtAttrName(),
+                            mappingManager.getConnObjectKeyValue(init.getLeft(), init.getRight()),
+                            realmDAO.findByFullPath(init.getLeft().getRealm().getFullPath()),
+                            request.getActionsClassNames());
+                    break;
+
+                default:
+            }
+
+            if (results != null && !results.isEmpty()
+                    && results.get(0).getStatus() == ProvisioningReport.Status.FAILURE) {
+
+                sce.getElements().add(results.get(0).getMessage());
+            }
+        } catch (JobExecutionException e) {
+            sce.getElements().add(e.getMessage());
+        }
+
+        if (!sce.isEmpty()) {
+            throw sce;
+        }
+    }
+
+    @Override
+    protected AbstractBaseBean resolveReference(final Method method, final Object... os)
+            throws UnresolvedReferenceException {
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java
index 09079da..e59350e 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java
@@ -22,11 +22,12 @@ import java.util.Set;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.core.persistence.api.dao.AllowedSchemas;
+import org.apache.syncope.core.persistence.api.dao.AnyDAO;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 
 public interface AnyUtils {
 
-    AnyTypeKind getAnyTypeKind();
+    AnyTypeKind anyTypeKind();
 
     <T extends Any<?>> Class<T> anyClass();
 
@@ -45,9 +46,11 @@ public interface AnyUtils {
     <T extends PlainAttrValue> T newPlainAttrUniqueValue();
 
     <T extends PlainAttrValue> T clonePlainAttrValue(T src);
-    
+
     <T extends AnyTO> T newAnyTO();
 
+    <A extends Any<?>> AnyDAO<A> dao();
+
     Set<ExternalResource> getAllResources(Any<?> any);
 
     <S extends Schema> AllowedSchemas<S> getAllowedSchemas(Any<?> any, Class<S> reference);

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java
index cab4a22..b95dae5 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java
@@ -99,8 +99,8 @@ public class JPAAnyObjectDAO extends AbstractAnyDAO<AnyObject> implements AnyObj
 
     @Transactional(readOnly = true)
     @Override
-    public String findKey(final String username) {
-        return findKey(username, JPAAnyObject.TABLE);
+    public String findKey(final String name) {
+        return findKey(name, JPAAnyObject.TABLE);
     }
 
     @Transactional(readOnly = true)

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtils.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtils.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtils.java
index b687aa1..15fa17c 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtils.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtils.java
@@ -32,6 +32,7 @@ import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.core.persistence.api.dao.AllowedSchemas;
+import org.apache.syncope.core.persistence.api.dao.AnyDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
@@ -113,7 +114,7 @@ public class JPAAnyUtils implements AnyUtils {
     }
 
     @Override
-    public AnyTypeKind getAnyTypeKind() {
+    public AnyTypeKind anyTypeKind() {
         return anyTypeKind;
     }
 
@@ -337,6 +338,29 @@ public class JPAAnyUtils implements AnyUtils {
         return result;
     }
 
+    @Override
+    public <A extends Any<?>> AnyDAO<A> dao() {
+        AnyDAO<A> result = null;
+
+        switch (anyTypeKind) {
+            case USER:
+                result = (AnyDAO<A>) userDAO;
+                break;
+
+            case GROUP:
+                result = (AnyDAO<A>) groupDAO;
+                break;
+
+            case ANY_OBJECT:
+                result = (AnyDAO<A>) anyObjectDAO;
+                break;
+
+            default:
+        }
+
+        return result;
+    }
+
     @Transactional(readOnly = true)
     @Override
     public Set<ExternalResource> getAllResources(final Any<?> any) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePullExecutor.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..ba4d160
--- /dev/null
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePullExecutor.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+import java.util.List;
+import org.apache.syncope.core.persistence.api.entity.Realm;
+import org.apache.syncope.core.persistence.api.entity.resource.Provision;
+import org.apache.syncope.core.provisioning.api.Connector;
+import org.quartz.JobExecutionException;
+
+public interface SyncopeSinglePullExecutor {
+
+    List<ProvisioningReport> pull(
+            Provision provision,
+            Connector connector,
+            String connObjectKey,
+            String connObjectValue,
+            Realm realm,
+            List<String> actionsClassNames) throws JobExecutionException;
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePushExecutor.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..eb18f00
--- /dev/null
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePushExecutor.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+import java.util.List;
+import org.apache.syncope.core.persistence.api.entity.Any;
+import org.apache.syncope.core.persistence.api.entity.resource.Provision;
+import org.apache.syncope.core.provisioning.api.Connector;
+import org.quartz.JobExecutionException;
+
+public interface SyncopeSinglePushExecutor {
+
+    List<ProvisioningReport> push(
+            Provision provision,
+            Connector connector,
+            Any<?> any,
+            List<String> actionsClassNames) throws JobExecutionException;
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/MappingManagerImpl.java
----------------------------------------------------------------------
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 33b9535..ac63ad7 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
@@ -648,7 +648,7 @@ public class MappingManagerImpl implements MappingManager {
 
         IntAttrName intAttrName;
         try {
-            intAttrName = intAttrNameParser.parse(mapItem.getIntAttrName(), anyUtils.getAnyTypeKind());
+            intAttrName = intAttrNameParser.parse(mapItem.getIntAttrName(), anyUtils.anyTypeKind());
         } catch (ParseException e) {
             LOG.error("Invalid intAttrName '{}' specified, ignoring", mapItem.getIntAttrName(), e);
             return;

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
----------------------------------------------------------------------
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 25d63ee..da6f011 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
@@ -161,7 +161,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
             ProvisioningReport ignoreResult = new ProvisioningReport();
             ignoreResult.setOperation(ResourceOperation.NONE);
             ignoreResult.setAnyType(provision == null
-                    ? getAnyUtils().getAnyTypeKind().name() : provision.getAnyType().getKey());
+                    ? getAnyUtils().anyTypeKind().name() : provision.getAnyType().getKey());
             ignoreResult.setStatus(ProvisioningReport.Status.IGNORE);
             ignoreResult.setKey(null);
             ignoreResult.setName(delta.getObject().getName().getNameValue());
@@ -863,7 +863,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
         }
 
         notificationManager.createTasks(AuditElements.EventCategoryType.PULL,
-                getAnyUtils().getAnyTypeKind().name().toLowerCase(),
+                getAnyUtils().anyTypeKind().name().toLowerCase(),
                 profile.getTask().getResource().getKey(),
                 event,
                 result,
@@ -873,7 +873,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
                 furtherInput);
 
         auditManager.audit(AuditElements.EventCategoryType.PULL,
-                getAnyUtils().getAnyTypeKind().name().toLowerCase(),
+                getAnyUtils().anyTypeKind().name().toLowerCase(),
                 profile.getTask().getResource().getKey(),
                 event,
                 result,

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java
----------------------------------------------------------------------
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 7133e3a..fad3dfa 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
@@ -255,7 +255,7 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
                 : null;
 
         LOG.debug("Propagating {} with key {} towards {}",
-                anyUtils.getAnyTypeKind(), any.getKey(), profile.getTask().getResource());
+                anyUtils.anyTypeKind(), any.getKey(), profile.getTask().getResource());
 
         Object output = null;
         Result resultStatus = null;

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
index feffeba..9c9a040 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
@@ -80,14 +80,6 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
 
     protected ProvisioningProfile<PullTask, PullActions> profile;
 
-    protected RealmPullResultHandler rhandler;
-
-    protected AnyObjectPullResultHandler ahandler;
-
-    protected UserPullResultHandler uhandler;
-
-    protected GroupPullResultHandler ghandler;
-
     @Override
     public void setLatestSyncToken(final ObjectClass objectClass, final SyncToken latestSyncToken) {
         latestSyncTokens.put(objectClass, latestSyncToken);
@@ -168,30 +160,18 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
     }
 
     protected RealmPullResultHandler buildRealmHandler() {
-        RealmPullResultHandler handler = (RealmPullResultHandler) ApplicationContextProvider.getBeanFactory().
+        return (RealmPullResultHandler) ApplicationContextProvider.getBeanFactory().
                 createBean(DefaultRealmPullResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
-        handler.setProfile(profile);
-        handler.setPullExecutor(this);
-
-        return handler;
     }
 
     protected AnyObjectPullResultHandler buildAnyObjectHandler() {
-        AnyObjectPullResultHandler handler = (AnyObjectPullResultHandler) ApplicationContextProvider.getBeanFactory().
+        return (AnyObjectPullResultHandler) ApplicationContextProvider.getBeanFactory().
                 createBean(DefaultAnyObjectPullResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
-        handler.setProfile(profile);
-        handler.setPullExecutor(this);
-
-        return handler;
     }
 
     protected UserPullResultHandler buildUserHandler() {
-        UserPullResultHandler handler = (UserPullResultHandler) ApplicationContextProvider.getBeanFactory().
+        return (UserPullResultHandler) ApplicationContextProvider.getBeanFactory().
                 createBean(DefaultUserPullResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
-        handler.setProfile(profile);
-        handler.setPullExecutor(this);
-
-        return handler;
     }
 
     protected GroupPullResultHandler buildGroupHandler() {
@@ -247,7 +227,9 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
             OperationOptions options = MappingUtils.buildOperationOptions(
                     MappingUtils.getPullItems(orgUnit.getItems()).iterator());
 
-            rhandler = buildRealmHandler();
+            RealmPullResultHandler handler = buildRealmHandler();
+            handler.setProfile(profile);
+            handler.setPullExecutor(this);
 
             try {
                 switch (pullTask.getPullMode()) {
@@ -259,7 +241,7 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
                         connector.sync(
                                 orgUnit.getObjectClass(),
                                 orgUnit.getSyncToken(),
-                                rhandler,
+                                handler,
                                 options);
 
                         if (!dryRun) {
@@ -275,14 +257,14 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
                                                 AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
                         connector.filteredReconciliation(orgUnit.getObjectClass(),
                                 filterBuilder,
-                                rhandler,
+                                handler,
                                 options);
                         break;
 
                     case FULL_RECONCILIATION:
                     default:
                         connector.fullReconciliation(orgUnit.getObjectClass(),
-                                rhandler,
+                                handler,
                                 options);
                         break;
                 }
@@ -292,18 +274,15 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
         }
 
         // ...then provisions for any types
-        ahandler = buildAnyObjectHandler();
-        uhandler = buildUserHandler();
-        ghandler = buildGroupHandler();
-
+        SyncopePullResultHandler handler;
+        GroupPullResultHandler ghandler = buildGroupHandler();
         for (Provision provision : pullTask.getResource().getProvisions()) {
             if (provision.getMapping() != null) {
                 status.set("Pulling " + provision.getObjectClass().getObjectClassValue());
 
-                SyncopePullResultHandler handler;
                 switch (provision.getAnyType().getKind()) {
                     case USER:
-                        handler = uhandler;
+                        handler = buildUserHandler();
                         break;
 
                     case GROUP:
@@ -312,8 +291,10 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
 
                     case ANY_OBJECT:
                     default:
-                        handler = ahandler;
+                        handler = buildAnyObjectHandler();
                 }
+                handler.setProfile(profile);
+                handler.setPullExecutor(this);
 
                 try {
                     Set<MappingItem> linkinMappingItems = new HashSet<>();

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullUtils.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullUtils.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullUtils.java
index 46b6a22..28c31d1 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullUtils.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullUtils.java
@@ -28,11 +28,10 @@ import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.policy.PullPolicySpec;
 import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.ParsingValidationException;
-import org.apache.syncope.core.persistence.api.dao.AnyDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
-import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+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.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.entity.Any;
@@ -154,10 +153,10 @@ public class PullUtils {
             try {
                 List<String> anyKeys = match(connObj, provision, anyUtils);
                 if (anyKeys.isEmpty()) {
-                    LOG.debug("No matching {} found for {}, aborting", anyUtils.getAnyTypeKind(), connObj);
+                    LOG.debug("No matching {} found for {}, aborting", anyUtils.anyTypeKind(), connObj);
                 } else {
                     if (anyKeys.size() > 1) {
-                        LOG.warn("More than one {} found {} - taking first only", anyUtils.getAnyTypeKind(), anyKeys);
+                        LOG.warn("More than one {} found {} - taking first only", anyUtils.anyTypeKind(), anyKeys);
                     }
 
                     result = anyKeys.iterator().next();
@@ -170,14 +169,6 @@ public class PullUtils {
         return result;
     }
 
-    private AnyDAO<?> getAnyDAO(final AnyTypeKind anyTypeKind) {
-        return AnyTypeKind.USER == anyTypeKind
-                ? userDAO
-                : AnyTypeKind.ANY_OBJECT == anyTypeKind
-                        ? anyObjectDAO
-                        : groupDAO;
-    }
-
     private List<String> findByConnObjectKey(
             final ConnectorObject connObj, final Provision provision, final AnyUtils anyUtils) {
 
@@ -217,7 +208,7 @@ public class PullUtils {
         if (intAttrName.getField() != null) {
             switch (intAttrName.getField()) {
                 case "key":
-                    Any<?> any = getAnyDAO(provision.getAnyType().getKind()).find(connObjectKey);
+                    Any<?> any = anyUtils.dao().find(connObjectKey);
                     if (any != null) {
                         result.add(any.getKey());
                     }
@@ -260,17 +251,13 @@ public class PullUtils {
                         }
                     }
 
-                    List<? extends Any<?>> anys = getAnyDAO(provision.getAnyType().getKind()).
-                            findByPlainAttrValue(intAttrName.getSchemaName(), value);
-                    for (Any<?> any : anys) {
+                    for (Any<?> any : anyUtils.dao().findByPlainAttrValue(intAttrName.getSchemaName(), value)) {
                         result.add(any.getKey());
                     }
                     break;
 
                 case DERIVED:
-                    anys = getAnyDAO(provision.getAnyType().getKind()).
-                            findByDerAttrValue(intAttrName.getSchemaName(), connObjectKey);
-                    for (Any<?> any : anys) {
+                    for (Any<?> any : anyUtils.dao().findByDerAttrValue(intAttrName.getSchemaName(), connObjectKey)) {
                         result.add(any.getKey());
                     }
                     break;
@@ -331,7 +318,7 @@ public class PullUtils {
         try {
             return pullRule == null
                     ? findByConnObjectKey(connObj, provision, anyUtils)
-                    : findByCorrelationRule(connObj, pullRule, anyUtils.getAnyTypeKind());
+                    : findByCorrelationRule(connObj, pullRule, anyUtils.anyTypeKind());
         } catch (RuntimeException e) {
             LOG.error("Could not match {} with any existing {}", connObj, provision.getAnyType(), e);
             return Collections.<String>emptyList();

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
----------------------------------------------------------------------
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 ae5e669..4f1e52f 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
@@ -26,18 +26,15 @@ import java.util.Map;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.MutablePair;
 import org.apache.syncope.common.lib.SyncopeConstants;
-import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.core.persistence.api.search.SearchCondConverter;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.apache.syncope.core.persistence.api.dao.AnyDAO;
-import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
-import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
-import org.apache.syncope.core.persistence.api.dao.UserDAO;
 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.Any;
+import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
@@ -60,41 +57,21 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition;
 public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
 
     /**
-     * User DAO.
-     */
-    @Autowired
-    protected UserDAO userDAO;
-
-    /**
      * Search DAO.
      */
     @Autowired
     protected AnySearchDAO searchDAO;
 
-    /**
-     * Group DAO.
-     */
     @Autowired
-    protected GroupDAO groupDAO;
-
-    @Autowired
-    protected AnyObjectDAO anyObjectDAO;
+    protected RealmDAO realmDAO;
 
     @Autowired
-    protected RealmDAO realmDAO;
+    protected AnyUtilsFactory anyUtilsFactory;
 
     protected ProvisioningProfile<PushTask, PushActions> profile;
 
     protected final Map<String, MutablePair<Integer, String>> handled = new HashMap<>();
 
-    protected RealmPushResultHandler rhandler;
-
-    protected AnyObjectPushResultHandler ahandler;
-
-    protected UserPushResultHandler uhandler;
-
-    protected GroupPushResultHandler ghandler;
-
     protected void reportHandled(final String anyType, final String key) {
         MutablePair<Integer, String> pair = handled.get(anyType);
         if (pair == null) {
@@ -122,25 +99,6 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
         return status.get();
     }
 
-    protected AnyDAO<?> getAnyDAO(final AnyTypeKind anyTypeKind) {
-        AnyDAO<?> result;
-        switch (anyTypeKind) {
-            case USER:
-                result = userDAO;
-                break;
-
-            case GROUP:
-                result = groupDAO;
-                break;
-
-            case ANY_OBJECT:
-            default:
-                result = anyObjectDAO;
-        }
-
-        return result;
-    }
-
     protected void doHandle(
             final List<? extends Any<?>> anys,
             final SyncopePushResultHandler handler,
@@ -165,35 +123,23 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
     }
 
     protected RealmPushResultHandler buildRealmHandler() {
-        RealmPushResultHandler handler = (RealmPushResultHandler) ApplicationContextProvider.getBeanFactory().
+        return (RealmPushResultHandler) ApplicationContextProvider.getBeanFactory().
                 createBean(DefaultRealmPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
-        handler.setProfile(profile);
-
-        return handler;
     }
 
     protected AnyObjectPushResultHandler buildAnyObjectHandler() {
-        AnyObjectPushResultHandler handler = (AnyObjectPushResultHandler) ApplicationContextProvider.getBeanFactory().
+        return (AnyObjectPushResultHandler) ApplicationContextProvider.getBeanFactory().
                 createBean(DefaultAnyObjectPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
-        handler.setProfile(profile);
-
-        return handler;
     }
 
     protected UserPushResultHandler buildUserHandler() {
-        UserPushResultHandler handler = (UserPushResultHandler) ApplicationContextProvider.getBeanFactory().
+        return (UserPushResultHandler) ApplicationContextProvider.getBeanFactory().
                 createBean(DefaultUserPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
-        handler.setProfile(profile);
-
-        return handler;
     }
 
     protected GroupPushResultHandler buildGroupHandler() {
-        GroupPushResultHandler handler = (GroupPushResultHandler) ApplicationContextProvider.getBeanFactory().
+        return (GroupPushResultHandler) ApplicationContextProvider.getBeanFactory().
                 createBean(DefaultGroupPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
-        handler.setProfile(profile);
-
-        return handler;
     }
 
     @Override
@@ -234,13 +180,14 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
         if (pushTask.getResource().getOrgUnit() != null) {
             status.set("Pushing realms");
 
-            rhandler = buildRealmHandler();
+            RealmPushResultHandler handler = buildRealmHandler();
+            handler.setProfile(profile);
 
             for (Realm realm : realmDAO.findDescendants(profile.getTask().getSourceRealm())) {
                 // Never push the root realm
                 if (realm.getParent() != null) {
                     try {
-                        rhandler.handle(realm.getKey());
+                        handler.handle(realm.getKey());
                         reportHandled(SyncopeConstants.REALM_ANYTYPE, realm.getName());
                     } catch (Exception e) {
                         LOG.warn("Failure pushing '{}' on '{}'", realm, pushTask.getResource(), e);
@@ -251,30 +198,27 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
         }
 
         // ...then provisions for any types
-        ahandler = buildAnyObjectHandler();
-        uhandler = buildUserHandler();
-        ghandler = buildGroupHandler();
-
         for (Provision provision : pushTask.getResource().getProvisions()) {
             if (provision.getMapping() != null) {
                 status.set("Pushing " + provision.getAnyType().getKey());
 
-                AnyDAO<?> anyDAO = getAnyDAO(provision.getAnyType().getKind());
+                AnyDAO<?> anyDAO = anyUtilsFactory.getInstance(provision.getAnyType().getKind()).dao();
 
                 SyncopePushResultHandler handler;
                 switch (provision.getAnyType().getKind()) {
                     case USER:
-                        handler = uhandler;
+                        handler = buildUserHandler();
                         break;
 
                     case GROUP:
-                        handler = ghandler;
+                        handler = buildGroupHandler();
                         break;
 
                     case ANY_OBJECT:
                     default:
-                        handler = ahandler;
+                        handler = buildAnyObjectHandler();
                 }
+                handler.setProfile(profile);
 
                 String filter = pushTask.getFilter(provision.getAnyType()) == null
                         ? null

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..7df7f23
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java
@@ -0,0 +1,167 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import org.apache.commons.collections4.IteratorUtils;
+import org.apache.syncope.common.lib.types.ConflictResolutionAction;
+import org.apache.syncope.common.lib.types.MatchingRule;
+import org.apache.syncope.common.lib.types.PullMode;
+import org.apache.syncope.common.lib.types.UnmatchingRule;
+import org.apache.syncope.core.persistence.api.entity.Realm;
+import org.apache.syncope.core.persistence.api.entity.VirSchema;
+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.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.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.ApplicationContextProvider;
+import org.identityconnectors.framework.common.objects.AttributeBuilder;
+import org.identityconnectors.framework.common.objects.OperationOptions;
+import org.identityconnectors.framework.common.objects.filter.Filter;
+import org.identityconnectors.framework.common.objects.filter.FilterBuilder;
+import org.quartz.JobExecutionException;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SinglePullJobDelegate extends PullJobDelegate implements SyncopeSinglePullExecutor {
+
+    @Override
+    public List<ProvisioningReport> pull(
+            final Provision provision,
+            final Connector connector,
+            final String connObjectKey,
+            final String connObjectValue,
+            final Realm realm,
+            final List<String> actionsClassNames) throws JobExecutionException {
+
+        LOG.debug("Executing pull on {}", provision.getResource());
+
+        List<PullActions> actions = new ArrayList<>();
+        for (String className : actionsClassNames) {
+            try {
+                Class<?> actionsClass = Class.forName(className);
+                PullActions pullActions = (PullActions) ApplicationContextProvider.getBeanFactory().
+                        createBean(actionsClass, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, true);
+
+                actions.add(pullActions);
+            } catch (Exception e) {
+                LOG.warn("Class '{}' not found", className, e);
+            }
+        }
+
+        try {
+            Set<MappingItem> linkinMappingItems = new HashSet<>();
+            for (VirSchema virSchema : virSchemaDAO.findByProvision(provision)) {
+                linkinMappingItems.add(virSchema.asLinkingMappingItem());
+            }
+            Iterator<MappingItem> mapItems = IteratorUtils.chainedIterator(
+                    provision.getMapping().getItems().iterator(),
+                    linkinMappingItems.iterator());
+            OperationOptions options = MappingUtils.buildOperationOptions(mapItems);
+
+            PullTask pullTask = entityFactory.newEntity(PullTask.class);
+            pullTask.setResource(provision.getResource());
+            pullTask.setMatchingRule(MatchingRule.UPDATE);
+            pullTask.setUnmatchingRule(UnmatchingRule.PROVISION);
+            pullTask.setPullMode(PullMode.FILTERED_RECONCILIATION);
+            pullTask.setPerformCreate(true);
+            pullTask.setPerformUpdate(true);
+            pullTask.setDestinationRealm(realm);
+
+            profile = new ProvisioningProfile<>(connector, pullTask);
+            profile.setDryRun(false);
+            profile.setResAct(ConflictResolutionAction.FIRSTMATCH);
+            profile.getActions().addAll(actions);
+
+            for (PullActions action : actions) {
+                action.beforeAll(profile);
+            }
+
+            SyncopePullResultHandler handler;
+            GroupPullResultHandler ghandler = buildGroupHandler();
+            switch (provision.getAnyType().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
+            connector.filteredReconciliation(
+                    provision.getObjectClass(),
+                    new AccountReconciliationFilterBuilder(connObjectKey, connObjectValue),
+                    handler,
+                    options);
+
+            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 pulling from connector", e);
+        }
+    }
+
+    class AccountReconciliationFilterBuilder extends DefaultReconciliationFilterBuilder {
+
+        private final String key;
+
+        private final String value;
+
+        AccountReconciliationFilterBuilder(final String key, final String value) {
+            this.key = key;
+            this.value = value;
+        }
+
+        @Override
+        public Filter build() {
+            return FilterBuilder.equalTo(AttributeBuilder.build(key, value));
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePushJobDelegate.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..eceb4fb
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePushJobDelegate.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java.pushpull;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.apache.syncope.common.lib.types.MatchingRule;
+import org.apache.syncope.common.lib.types.UnmatchingRule;
+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.task.PushTask;
+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.core.provisioning.api.pushpull.PushActions;
+import org.apache.syncope.core.provisioning.api.pushpull.SyncopePushResultHandler;
+import org.apache.syncope.core.provisioning.api.pushpull.SyncopeSinglePushExecutor;
+import org.apache.syncope.core.spring.ApplicationContextProvider;
+import org.quartz.JobExecutionException;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SinglePushJobDelegate extends PushJobDelegate implements SyncopeSinglePushExecutor {
+
+    @Override
+    public List<ProvisioningReport> push(
+            final Provision provision,
+            final Connector connector,
+            final Any<?> any,
+            final List<String> actionsClassNames) throws JobExecutionException {
+
+        LOG.debug("Executing push on {}", provision.getResource());
+
+        List<PushActions> actions = new ArrayList<>();
+        for (String className : actionsClassNames) {
+            try {
+                Class<?> actionsClass = Class.forName(className);
+
+                PushActions pushActions = (PushActions) ApplicationContextProvider.getBeanFactory().
+                        createBean(actionsClass, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, true);
+                actions.add(pushActions);
+            } catch (Exception e) {
+                LOG.info("Class '{}' not found", className, e);
+            }
+        }
+
+        try {
+            PushTask pushTask = entityFactory.newEntity(PushTask.class);
+            pushTask.setResource(provision.getResource());
+            pushTask.setMatchingRule(MatchingRule.UPDATE);
+            pushTask.setUnmatchingRule(UnmatchingRule.PROVISION);
+            pushTask.setPerformCreate(true);
+            pushTask.setPerformUpdate(true);
+
+            profile = new ProvisioningProfile<>(connector, pushTask);
+            profile.getActions().addAll(actions);
+            profile.setResAct(null);
+
+            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(Arrays.asList(any), handler, pushTask.getResource());
+
+            for (PushActions action : actions) {
+                action.afterAll(profile);
+            }
+
+            return profile.getResults();
+        } catch (Exception e) {
+            throw e instanceof JobExecutionException
+                    ? (JobExecutionException) e
+                    : new JobExecutionException("While pushing to connector", e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
index ad6db20..a17b168 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
@@ -246,8 +246,8 @@ public class ConnObjectUtils {
         updated.setKey(key);
 
         T anyPatch = null;
-        if (null != anyUtils.getAnyTypeKind()) {
-            switch (anyUtils.getAnyTypeKind()) {
+        if (null != anyUtils.anyTypeKind()) {
+            switch (anyUtils.anyTypeKind()) {
                 case USER:
                     UserTO originalUser = (UserTO) original;
                     UserTO updatedUser = (UserTO) updated;

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java
----------------------------------------------------------------------
diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java
index e7be4f7..5102b71 100644
--- a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java
+++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java
@@ -64,18 +64,6 @@ public abstract class AbstractAnyService<TO extends AnyTO, P extends AnyPatch>
 
     protected abstract P newPatch(String key);
 
-    protected String getActualKey(final String key) {
-        String actualKey = key;
-        if (!SyncopeConstants.UUID_PATTERN.matcher(key).matches()) {
-            actualKey = getAnyDAO().findKey(key);
-            if (actualKey == null) {
-                throw new NotFoundException("User, Group or Any Object for " + key);
-            }
-        }
-
-        return actualKey;
-    }
-
     @Override
     public Set<AttrTO> read(final String key, final SchemaType schemaType) {
         TO any = read(key);
@@ -124,7 +112,7 @@ public abstract class AbstractAnyService<TO extends AnyTO, P extends AnyPatch>
 
     @Override
     public TO read(final String key) {
-        return getAnyLogic().read(getActualKey(key));
+        return getAnyLogic().read(getActualKey(getAnyDAO(), key));
     }
 
     @Override
@@ -161,7 +149,7 @@ public abstract class AbstractAnyService<TO extends AnyTO, P extends AnyPatch>
     }
 
     protected Response doUpdate(final P anyPatch) {
-        anyPatch.setKey(getActualKey(anyPatch.getKey()));
+        anyPatch.setKey(getActualKey(getAnyDAO(), anyPatch.getKey()));
         Date etagDate = findLastChange(anyPatch.getKey());
         checkETag(String.valueOf(etagDate.getTime()));
 
@@ -196,21 +184,23 @@ public abstract class AbstractAnyService<TO extends AnyTO, P extends AnyPatch>
 
     @Override
     public Response update(final String key, final SchemaType schemaType, final AttrTO attrTO) {
-        String actualKey = getActualKey(key);
+        String actualKey = getActualKey(getAnyDAO(), key);
         addUpdateOrReplaceAttr(actualKey, schemaType, attrTO, PatchOperation.ADD_REPLACE);
         return modificationResponse(read(actualKey, schemaType, attrTO.getSchema()));
     }
 
     @Override
     public void delete(final String key, final SchemaType schemaType, final String schema) {
-        String actualKey = getActualKey(key);
         addUpdateOrReplaceAttr(
-                actualKey, schemaType, new AttrTO.Builder().schema(schema).build(), PatchOperation.DELETE);
+                getActualKey(getAnyDAO(), key),
+                schemaType,
+                new AttrTO.Builder().schema(schema).build(),
+                PatchOperation.DELETE);
     }
 
     @Override
     public Response delete(final String key) {
-        String actualKey = getActualKey(key);
+        String actualKey = getActualKey(getAnyDAO(), key);
 
         Date etagDate = findLastChange(actualKey);
         checkETag(String.valueOf(etagDate.getTime()));

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java
----------------------------------------------------------------------
diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java
index a040960..caef5d8 100644
--- a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java
+++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java
@@ -37,12 +37,15 @@ import org.apache.cxf.jaxrs.ext.search.SearchCondition;
 import org.apache.cxf.jaxrs.ext.search.SearchContext;
 import org.apache.syncope.common.lib.AbstractBaseBean;
 import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.to.ProvisioningResult;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.rest.api.service.JAXRSService;
 import org.apache.syncope.common.rest.api.Preference;
 import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.core.persistence.api.dao.AnyDAO;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
@@ -64,6 +67,18 @@ abstract class AbstractServiceImpl implements JAXRSService {
     @Context
     protected SearchContext searchContext;
 
+    protected String getActualKey(final AnyDAO<?> dao, final String pretendingKey) {
+        String actualKey = pretendingKey;
+        if (!SyncopeConstants.UUID_PATTERN.matcher(pretendingKey).matches()) {
+            actualKey = dao.findKey(pretendingKey);
+            if (actualKey == null) {
+                throw new NotFoundException("User, Group or Any Object for " + pretendingKey);
+            }
+        }
+
+        return actualKey;
+    }
+
     protected boolean isNullPriorityAsync() {
         return BooleanUtils.toBoolean(messageContext.getHttpHeaders().getHeaderString(RESTHeaders.NULL_PRIORITY_ASYNC));
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceImpl.java
----------------------------------------------------------------------
diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceImpl.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceImpl.java
index 961a328..3a7d40b 100644
--- a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceImpl.java
+++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceImpl.java
@@ -71,7 +71,7 @@ public class AnyObjectServiceImpl extends AbstractAnyService<AnyObjectTO, AnyObj
 
     @Override
     public Response update(final AnyObjectTO anyObjectTO) {
-        anyObjectTO.setKey(getActualKey(anyObjectTO.getKey()));
+        anyObjectTO.setKey(getActualKey(getAnyDAO(), anyObjectTO.getKey()));
         AnyObjectTO before = logic.read(anyObjectTO.getKey());
 
         checkETag(before.getETagValue());

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GroupServiceImpl.java
----------------------------------------------------------------------
diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GroupServiceImpl.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GroupServiceImpl.java
index 4e23d22..bc621e9 100644
--- a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GroupServiceImpl.java
+++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GroupServiceImpl.java
@@ -68,7 +68,7 @@ public class GroupServiceImpl extends AbstractAnyService<GroupTO, GroupPatch> im
 
     @Override
     public Response update(final GroupTO groupTO) {
-        groupTO.setKey(getActualKey(groupTO.getKey()));
+        groupTO.setKey(getActualKey(getAnyDAO(), groupTO.getKey()));
         GroupTO before = logic.read(groupTO.getKey());
 
         checkETag(before.getETagValue());

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ReconciliationServiceImpl.java
----------------------------------------------------------------------
diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ReconciliationServiceImpl.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ReconciliationServiceImpl.java
new file mode 100644
index 0000000..80d89d5
--- /dev/null
+++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ReconciliationServiceImpl.java
@@ -0,0 +1,53 @@
+/*
+ * 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.rest.cxf.service;
+
+import org.apache.syncope.common.lib.to.ReconciliationRequest;
+import org.apache.syncope.common.lib.to.ReconciliationStatus;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.rest.api.service.ReconciliationService;
+import org.apache.syncope.core.logic.ReconciliationLogic;
+import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ReconciliationServiceImpl extends AbstractServiceImpl implements ReconciliationService {
+
+    @Autowired
+    private ReconciliationLogic logic;
+
+    @Autowired
+    private AnyUtilsFactory anyUtilsFactory;
+
+    @Override
+    public ReconciliationStatus status(final AnyTypeKind anyTypeKind, final String anyKey, final String resourceKey) {
+        return logic.status(
+                anyTypeKind,
+                getActualKey(anyUtilsFactory.getInstance(anyTypeKind).dao(), anyKey),
+                resourceKey);
+    }
+
+    @Override
+    public void reconcile(final ReconciliationRequest request) {
+        request.setAnyKey(
+                getActualKey(anyUtilsFactory.getInstance(request.getAnyTypeKind()).dao(), request.getAnyKey()));
+        logic.reconcile(request);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/73fab3de/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserServiceImpl.java
----------------------------------------------------------------------
diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserServiceImpl.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserServiceImpl.java
index 4af144b..99dc24d 100644
--- a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserServiceImpl.java
+++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserServiceImpl.java
@@ -67,7 +67,7 @@ public class UserServiceImpl extends AbstractAnyService<UserTO, UserPatch> imple
 
     @Override
     public Response update(final UserTO userTO) {
-        userTO.setKey(getActualKey(userTO.getKey()));
+        userTO.setKey(getActualKey(getAnyDAO(), userTO.getKey()));
         UserTO before = logic.read(userTO.getKey());
 
         checkETag(before.getETagValue());


[6/6] syncope git commit: [SYNCOPE-1299] Core implementation

Posted by il...@apache.org.
[SYNCOPE-1299] Core implementation


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/e5860a76
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/e5860a76
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/e5860a76

Branch: refs/heads/master
Commit: e5860a76a5584edf780df180eb9229ddf44de684
Parents: 3c4a351
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Thu Apr 12 11:50:16 2018 +0200
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Thu Apr 12 12:41:49 2018 +0200

----------------------------------------------------------------------
 .../common/lib/to/ProvisioningTaskTO.java       |   3 +-
 .../common/lib/to/ReconciliationRequest.java    | 100 ++++++++
 .../common/lib/to/ReconciliationStatus.java     |  56 +++++
 .../common/lib/types/ClientExceptionType.java   |   1 +
 .../common/lib/types/ReconciliationAction.java  |  28 +++
 .../rest/api/service/ReconciliationService.java |  78 +++++++
 .../syncope/core/logic/ReconciliationLogic.java | 232 +++++++++++++++++++
 .../core/persistence/api/entity/AnyUtils.java   |   7 +-
 .../persistence/jpa/dao/JPAAnyObjectDAO.java    |   4 +-
 .../persistence/jpa/entity/JPAAnyUtils.java     |  26 ++-
 .../api/pushpull/SyncopeSinglePullExecutor.java |  37 +++
 .../api/pushpull/SyncopeSinglePushExecutor.java |  34 +++
 .../provisioning/java/MappingManagerImpl.java   |   2 +-
 .../pushpull/AbstractPullResultHandler.java     |   6 +-
 .../pushpull/AbstractPushResultHandler.java     |   2 +-
 .../java/pushpull/PullJobDelegate.java          |  49 ++--
 .../provisioning/java/pushpull/PullUtils.java   |  23 +-
 .../java/pushpull/PushJobDelegate.java          |  86 ++-----
 .../java/pushpull/SinglePullJobDelegate.java    | 174 ++++++++++++++
 .../java/pushpull/SinglePushJobDelegate.java    | 117 ++++++++++
 .../java/utils/ConnObjectUtils.java             |   4 +-
 .../rest/cxf/service/AbstractAnyService.java    |  26 +--
 .../rest/cxf/service/AbstractServiceImpl.java   |  15 ++
 .../rest/cxf/service/AnyObjectServiceImpl.java  |   2 +-
 .../core/rest/cxf/service/GroupServiceImpl.java |   2 +-
 .../cxf/service/ReconciliationServiceImpl.java  |  53 +++++
 .../core/rest/cxf/service/UserServiceImpl.java  |   2 +-
 .../org/apache/syncope/fit/AbstractITCase.java  |   4 +
 .../syncope/fit/core/ReconciliationITCase.java  | 134 +++++++++++
 29 files changed, 1151 insertions(+), 156 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/common/lib/src/main/java/org/apache/syncope/common/lib/to/ProvisioningTaskTO.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/ProvisioningTaskTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/ProvisioningTaskTO.java
index 27d4f9b..cdef683 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/to/ProvisioningTaskTO.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/ProvisioningTaskTO.java
@@ -33,8 +33,7 @@ import org.apache.syncope.common.lib.types.UnmatchingRule;
 @XmlRootElement(name = "provisioningTask")
 @XmlType
 @XmlSeeAlso({ PushTaskTO.class, PullTaskTO.class })
-@Schema(
-        allOf = { SchedTaskTO.class },
+@Schema(allOf = { SchedTaskTO.class },
         subTypes = { PushTaskTO.class, PullTaskTO.class }, discriminatorProperty = "@class")
 public abstract class ProvisioningTaskTO extends SchedTaskTO {
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/common/lib/src/main/java/org/apache/syncope/common/lib/to/ReconciliationRequest.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/ReconciliationRequest.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/ReconciliationRequest.java
new file mode 100644
index 0000000..2ebf699
--- /dev/null
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/ReconciliationRequest.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.ReconciliationAction;
+
+public class ReconciliationRequest extends AbstractBaseBean {
+
+    private static final long serialVersionUID = -2592156800185957182L;
+
+    private AnyTypeKind anyTypeKind;
+
+    private String anyKey;
+
+    private String resourceKey;
+
+    private ReconciliationAction action;
+
+    private boolean remediation;
+
+    private final List<String> actions = new ArrayList<>();
+
+    @JsonProperty(required = true)
+    @XmlElement(required = true)
+    public AnyTypeKind getAnyTypeKind() {
+        return anyTypeKind;
+    }
+
+    public void setAnyTypeKind(final AnyTypeKind anyTypeKind) {
+        this.anyTypeKind = anyTypeKind;
+    }
+
+    @JsonProperty(required = true)
+    @XmlElement(required = true)
+    public String getAnyKey() {
+        return anyKey;
+    }
+
+    public void setAnyKey(final String anyKey) {
+        this.anyKey = anyKey;
+    }
+
+    @JsonProperty(required = true)
+    @XmlElement(required = true)
+    public String getResourceKey() {
+        return resourceKey;
+    }
+
+    public void setResourceKey(final String resourceKey) {
+        this.resourceKey = resourceKey;
+    }
+
+    @JsonProperty(required = true)
+    @XmlElement(required = true)
+    public ReconciliationAction getAction() {
+        return action;
+    }
+
+    public void setAction(final ReconciliationAction action) {
+        this.action = action;
+    }
+
+    public boolean isRemediation() {
+        return remediation;
+    }
+
+    public void setRemediation(final boolean remediation) {
+        this.remediation = remediation;
+    }
+
+    @XmlElementWrapper(name = "actions")
+    @XmlElement(name = "action")
+    @JsonProperty("actions")
+    public List<String> getActions() {
+        return actions;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/common/lib/src/main/java/org/apache/syncope/common/lib/to/ReconciliationStatus.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/ReconciliationStatus.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/ReconciliationStatus.java
new file mode 100644
index 0000000..2d7af98
--- /dev/null
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/ReconciliationStatus.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.common.lib.to;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+/**
+ * Reconciliation status.
+ */
+@XmlRootElement(name = "reconciliationStatus")
+@XmlType
+public class ReconciliationStatus extends AbstractBaseBean {
+
+    private static final long serialVersionUID = -8516345256596521490L;
+
+    private ConnObjectTO onSyncope;
+
+    private ConnObjectTO onResource;
+
+    @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+    public ConnObjectTO getOnSyncope() {
+        return onSyncope;
+    }
+
+    public void setOnSyncope(final ConnObjectTO onSyncope) {
+        this.onSyncope = onSyncope;
+    }
+
+    @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+    public ConnObjectTO getOnResource() {
+        return onResource;
+    }
+
+    public void setOnResource(final ConnObjectTO onResource) {
+        this.onResource = onResource;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/common/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java b/common/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java
index ada4a1e..8a8f744 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java
@@ -74,6 +74,7 @@ public enum ClientExceptionType {
     InUse(Response.Status.BAD_REQUEST),
     Scheduling(Response.Status.BAD_REQUEST),
     DelegatedAdministration(Response.Status.FORBIDDEN),
+    Reconciliation(Response.Status.BAD_REQUEST),
     Unknown(Response.Status.BAD_REQUEST),
     Workflow(Response.Status.BAD_REQUEST);
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/common/lib/src/main/java/org/apache/syncope/common/lib/types/ReconciliationAction.java
----------------------------------------------------------------------
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/types/ReconciliationAction.java b/common/lib/src/main/java/org/apache/syncope/common/lib/types/ReconciliationAction.java
new file mode 100644
index 0000000..19b68c2
--- /dev/null
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/types/ReconciliationAction.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.lib.types;
+
+import javax.xml.bind.annotation.XmlEnum;
+
+@XmlEnum
+public enum ReconciliationAction {
+    PUSH,
+    PULL
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ReconciliationService.java
----------------------------------------------------------------------
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ReconciliationService.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ReconciliationService.java
new file mode 100644
index 0000000..77eb840
--- /dev/null
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ReconciliationService.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.rest.api.service;
+
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+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 javax.validation.constraints.NotNull;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.to.ReconciliationRequest;
+import org.apache.syncope.common.lib.to.ReconciliationStatus;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+
+/**
+ * REST operations for tasks.
+ */
+@Tag(name = "Reconciliation")
+@SecurityRequirements({
+    @SecurityRequirement(name = "BasicAuthentication"),
+    @SecurityRequirement(name = "Bearer") })
+@Path("reconciliation")
+public interface ReconciliationService extends JAXRSService {
+
+    /**
+     * Gets current attributes on Syncope and on the given External Resource, related to given user, group or
+     * any object.
+     *
+     * @param anyTypeKind anyTypeKind
+     * @param anyKey user, group or any object: if value looks like a UUID then it is interpreted as key, otherwise as
+     * a (user)name
+     * @param resourceKey resource key
+     * @return reconciliation status
+     */
+    @GET
+    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    ReconciliationStatus status(
+            @NotNull @QueryParam("anyTypeKind") AnyTypeKind anyTypeKind,
+            @NotNull @QueryParam("anyKey") String anyKey,
+            @NotNull @QueryParam("resourceKey") String resourceKey);
+
+    /**
+     * Perform the required reconciliation action (PUSH or PULL) to the given user, group or any object and
+     * External Resource.
+     *
+     * @param request reconciliation request
+     */
+    @ApiResponses(
+            @ApiResponse(responseCode = "204", description = "Operation was successful"))
+    @POST
+    @Consumes({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, SyncopeConstants.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    void reconcile(@NotNull ReconciliationRequest request);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/core/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
new file mode 100644
index 0000000..a36c159
--- /dev/null
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
@@ -0,0 +1,232 @@
+/*
+ * 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.lang.reflect.Method;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.collections.IteratorChain;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.ConnObjectTO;
+import org.apache.syncope.common.lib.to.ReconciliationRequest;
+import org.apache.syncope.common.lib.to.ReconciliationStatus;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.StandardEntitlement;
+import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.dao.RealmDAO;
+import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
+import org.apache.syncope.core.persistence.api.entity.Any;
+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.resource.ExternalResource;
+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.provisioning.api.Connector;
+import org.apache.syncope.core.provisioning.api.ConnectorFactory;
+import org.apache.syncope.core.provisioning.api.MappingManager;
+import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
+import org.apache.syncope.core.provisioning.api.pushpull.SyncopeSinglePullExecutor;
+import org.apache.syncope.core.provisioning.api.pushpull.SyncopeSinglePushExecutor;
+import org.apache.syncope.core.provisioning.java.utils.ConnObjectUtils;
+import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
+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.Name;
+import org.identityconnectors.framework.common.objects.Uid;
+import org.quartz.JobExecutionException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ReconciliationLogic extends AbstractTransactionalLogic<AbstractBaseBean> {
+
+    @Autowired
+    private AnyUtilsFactory anyUtilsFactory;
+
+    @Autowired
+    private ExternalResourceDAO resourceDAO;
+
+    @Autowired
+    private VirSchemaDAO virSchemaDAO;
+
+    @Autowired
+    private RealmDAO realmDAO;
+
+    @Autowired
+    private MappingManager mappingManager;
+
+    @Autowired
+    private ConnectorFactory connFactory;
+
+    @Autowired
+    private SyncopeSinglePullExecutor singlePullExecutor;
+
+    @Autowired
+    private SyncopeSinglePushExecutor singlePushExecutor;
+
+    @SuppressWarnings("unchecked")
+    private Pair<Any<?>, Provision> init(final AnyTypeKind anyTypeKind, final String anyKey, final String resourceKey) {
+        AnyUtils anyUtils = anyUtilsFactory.getInstance(anyTypeKind);
+
+        Any<?> any = anyUtils.dao().authFind(anyKey);
+        if (any == null) {
+            throw new NotFoundException(anyTypeKind + " '" + anyKey + "'");
+        }
+
+        ExternalResource resource = resourceDAO.find(resourceKey);
+        if (resource == null) {
+            throw new NotFoundException("Resource '" + resourceKey + "'");
+        }
+        Provision provision = resource.getProvision(any.getType()).orElseThrow(()
+                -> new NotFoundException("Provision for " + any.getType() + " on Resource '" + resourceKey + "'"));
+        if (provision.getMapping() == null) {
+            throw new NotFoundException("Mapping for " + any.getType() + " on Resource '" + resourceKey + "'");
+        }
+
+        return (Pair<Any<?>, Provision>) Pair.of(any, provision);
+    }
+
+    private ConnObjectTO getOnSyncope(final Any<?> any, final Provision provision, final String resourceKey) {
+        Pair<String, Set<Attribute>> attrs = mappingManager.prepareAttrs(any, null, false, true, provision);
+
+        MappingItem connObjectKey = provision.getMapping().getConnObjectKeyItem().orElseThrow(()
+                -> new NotFoundException("No RemoteKey set for " + resourceKey));
+
+        ConnObjectTO connObjectTO = ConnObjectUtils.getConnObjectTO(attrs.getRight());
+        if (attrs.getLeft() != null) {
+            connObjectTO.getAttrs().add(new AttrTO.Builder().
+                    schema(connObjectKey.getExtAttrName()).value(attrs.getLeft()).build());
+            connObjectTO.getAttrs().add(new AttrTO.Builder().
+                    schema(Uid.NAME).value(attrs.getLeft()).build());
+        }
+
+        return connObjectTO;
+    }
+
+    private ConnObjectTO getOnResource(final Any<?> any, final Provision provision) {
+        // 1. build connObjectKeyItem
+        MappingItem connObjectKeyItem = MappingUtils.getConnObjectKeyItem(provision).orElseThrow(()
+                -> new NotFoundException("ConnObjectKey for " + any.getType()
+                        + " on resource '" + provision.getResource().getKey() + "'"));
+        String connObjectKeyValue = mappingManager.getConnObjectKeyValue(any, provision).orElseThrow(()
+                -> new NotFoundException("Value for ConnObjectKey for " + any.getType()
+                        + " on resource '" + provision.getResource().getKey() + "'"));
+
+        // 2. determine attributes to query
+        Set<MappingItem> linkinMappingItems = virSchemaDAO.findByProvision(provision).stream().
+                map(virSchema -> virSchema.asLinkingMappingItem()).collect(Collectors.toSet());
+        Iterator<MappingItem> mapItems = new IteratorChain<>(
+                provision.getMapping().getItems().iterator(),
+                linkinMappingItems.iterator());
+
+        // 3. read from the underlying connector
+        ConnObjectTO connObjectTO = null;
+
+        Connector connector = connFactory.getConnector(provision.getResource());
+        ConnectorObject connectorObject = connector.getObject(
+                provision.getObjectClass(),
+                AttributeBuilder.build(connObjectKeyItem.getExtAttrName(), connObjectKeyValue),
+                MappingUtils.buildOperationOptions(mapItems));
+        if (connectorObject != null) {
+            Set<Attribute> attributes = connectorObject.getAttributes();
+            if (AttributeUtil.find(Uid.NAME, attributes) == null) {
+                attributes.add(connectorObject.getUid());
+            }
+            if (AttributeUtil.find(Name.NAME, attributes) == null) {
+                attributes.add(connectorObject.getName());
+            }
+
+            connObjectTO = ConnObjectUtils.getConnObjectTO(attributes);
+        }
+
+        return connObjectTO;
+    }
+
+    @PreAuthorize("hasRole('" + StandardEntitlement.RESOURCE_GET_CONNOBJECT + "')")
+    public ReconciliationStatus status(final AnyTypeKind anyTypeKind, final String anyKey, final String resourceKey) {
+        Pair<Any<?>, Provision> init = init(anyTypeKind, anyKey, resourceKey);
+
+        ReconciliationStatus status = new ReconciliationStatus();
+        status.setOnSyncope(getOnSyncope(init.getLeft(), init.getRight(), resourceKey));
+        status.setOnResource(getOnResource(init.getLeft(), init.getRight()));
+
+        return status;
+    }
+
+    @PreAuthorize("hasRole('" + StandardEntitlement.TASK_EXECUTE + "')")
+    public void reconcile(final ReconciliationRequest request) {
+        Pair<Any<?>, Provision> init = init(request.getAnyTypeKind(), request.getAnyKey(), request.getResourceKey());
+
+        SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.Reconciliation);
+        try {
+            List<ProvisioningReport> results = null;
+            switch (request.getAction()) {
+                case PUSH:
+                    results = singlePushExecutor.push(
+                            init.getRight(),
+                            connFactory.getConnector(init.getRight().getResource()),
+                            init.getLeft(),
+                            request.getActions());
+                    break;
+
+                case PULL:
+                    results = singlePullExecutor.pull(
+                            init.getRight(),
+                            connFactory.getConnector(init.getRight().getResource()),
+                            init.getRight().getMapping().getConnObjectKeyItem().get().getExtAttrName(),
+                            mappingManager.getConnObjectKeyValue(init.getLeft(), init.getRight()).get(),
+                            realmDAO.findByFullPath(init.getLeft().getRealm().getFullPath()),
+                            request.isRemediation(),
+                            request.getActions());
+                    break;
+
+                default:
+            }
+
+            if (results != null && !results.isEmpty()
+                    && results.get(0).getStatus() == ProvisioningReport.Status.FAILURE) {
+
+                sce.getElements().add(results.get(0).getMessage());
+            }
+        } catch (JobExecutionException e) {
+            sce.getElements().add(e.getMessage());
+        }
+
+        if (!sce.isEmpty()) {
+            throw sce;
+        }
+    }
+
+    @Override
+    protected AbstractBaseBean resolveReference(final Method method, final Object... os)
+            throws UnresolvedReferenceException {
+
+        throw new UnresolvedReferenceException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java
----------------------------------------------------------------------
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java
index 09079da..e59350e 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java
@@ -22,11 +22,12 @@ import java.util.Set;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.core.persistence.api.dao.AllowedSchemas;
+import org.apache.syncope.core.persistence.api.dao.AnyDAO;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 
 public interface AnyUtils {
 
-    AnyTypeKind getAnyTypeKind();
+    AnyTypeKind anyTypeKind();
 
     <T extends Any<?>> Class<T> anyClass();
 
@@ -45,9 +46,11 @@ public interface AnyUtils {
     <T extends PlainAttrValue> T newPlainAttrUniqueValue();
 
     <T extends PlainAttrValue> T clonePlainAttrValue(T src);
-    
+
     <T extends AnyTO> T newAnyTO();
 
+    <A extends Any<?>> AnyDAO<A> dao();
+
     Set<ExternalResource> getAllResources(Any<?> any);
 
     <S extends Schema> AllowedSchemas<S> getAllowedSchemas(Any<?> any, Class<S> reference);

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java
index abba39c..64550a7 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyObjectDAO.java
@@ -92,8 +92,8 @@ public class JPAAnyObjectDAO extends AbstractAnyDAO<AnyObject> implements AnyObj
 
     @Transactional(readOnly = true)
     @Override
-    public String findKey(final String username) {
-        return findKey(username, JPAAnyObject.TABLE);
+    public String findKey(final String name) {
+        return findKey(name, JPAAnyObject.TABLE);
     }
 
     @Transactional(readOnly = true)

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtils.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtils.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtils.java
index b687aa1..15fa17c 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtils.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtils.java
@@ -32,6 +32,7 @@ import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.core.persistence.api.dao.AllowedSchemas;
+import org.apache.syncope.core.persistence.api.dao.AnyDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
@@ -113,7 +114,7 @@ public class JPAAnyUtils implements AnyUtils {
     }
 
     @Override
-    public AnyTypeKind getAnyTypeKind() {
+    public AnyTypeKind anyTypeKind() {
         return anyTypeKind;
     }
 
@@ -337,6 +338,29 @@ public class JPAAnyUtils implements AnyUtils {
         return result;
     }
 
+    @Override
+    public <A extends Any<?>> AnyDAO<A> dao() {
+        AnyDAO<A> result = null;
+
+        switch (anyTypeKind) {
+            case USER:
+                result = (AnyDAO<A>) userDAO;
+                break;
+
+            case GROUP:
+                result = (AnyDAO<A>) groupDAO;
+                break;
+
+            case ANY_OBJECT:
+                result = (AnyDAO<A>) anyObjectDAO;
+                break;
+
+            default:
+        }
+
+        return result;
+    }
+
     @Transactional(readOnly = true)
     @Override
     public Set<ExternalResource> getAllResources(final Any<?> any) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePullExecutor.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..bbf8430
--- /dev/null
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePullExecutor.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+import java.util.List;
+import org.apache.syncope.core.persistence.api.entity.Realm;
+import org.apache.syncope.core.persistence.api.entity.resource.Provision;
+import org.apache.syncope.core.provisioning.api.Connector;
+import org.quartz.JobExecutionException;
+
+public interface SyncopeSinglePullExecutor {
+
+    List<ProvisioningReport> pull(
+            Provision provision,
+            Connector connector,
+            String connObjectKey,
+            String connObjectValue,
+            Realm realm,
+            boolean remediation,
+            List<String> actions) throws JobExecutionException;
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePushExecutor.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..9068301
--- /dev/null
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/SyncopeSinglePushExecutor.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+import java.util.List;
+import org.apache.syncope.core.persistence.api.entity.Any;
+import org.apache.syncope.core.persistence.api.entity.resource.Provision;
+import org.apache.syncope.core.provisioning.api.Connector;
+import org.quartz.JobExecutionException;
+
+public interface SyncopeSinglePushExecutor {
+
+    List<ProvisioningReport> push(
+            Provision provision,
+            Connector connector,
+            Any<?> any,
+            List<String> actions) throws JobExecutionException;
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/MappingManagerImpl.java
----------------------------------------------------------------------
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 70a8647..83d8e76 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
@@ -674,7 +674,7 @@ public class MappingManagerImpl implements MappingManager {
 
         IntAttrName intAttrName;
         try {
-            intAttrName = intAttrNameParser.parse(mapItem.getIntAttrName(), anyUtils.getAnyTypeKind());
+            intAttrName = intAttrNameParser.parse(mapItem.getIntAttrName(), anyUtils.anyTypeKind());
         } catch (ParseException e) {
             LOG.error("Invalid intAttrName '{}' specified, ignoring", mapItem.getIntAttrName(), e);
             return;

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.java
----------------------------------------------------------------------
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 3abbd84..35e0356 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
@@ -171,7 +171,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
             ProvisioningReport ignoreResult = new ProvisioningReport();
             ignoreResult.setOperation(ResourceOperation.NONE);
             ignoreResult.setAnyType(provision == null
-                    ? getAnyUtils().getAnyTypeKind().name() : provision.getAnyType().getKey());
+                    ? getAnyUtils().anyTypeKind().name() : provision.getAnyType().getKey());
             ignoreResult.setStatus(ProvisioningReport.Status.IGNORE);
             ignoreResult.setKey(null);
             ignoreResult.setName(delta.getObject().getName().getNameValue());
@@ -914,7 +914,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
         }
 
         notificationManager.createTasks(AuditElements.EventCategoryType.PULL,
-                getAnyUtils().getAnyTypeKind().name().toLowerCase(),
+                getAnyUtils().anyTypeKind().name().toLowerCase(),
                 profile.getTask().getResource().getKey(),
                 event,
                 result,
@@ -924,7 +924,7 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
                 furtherInput);
 
         auditManager.audit(AuditElements.EventCategoryType.PULL,
-                getAnyUtils().getAnyTypeKind().name().toLowerCase(),
+                getAnyUtils().anyTypeKind().name().toLowerCase(),
                 profile.getTask().getResource().getKey(),
                 event,
                 result,

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java
----------------------------------------------------------------------
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 5f74388..63f397b 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
@@ -256,7 +256,7 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
                 : null;
 
         LOG.debug("Propagating {} with key {} towards {}",
-                anyUtils.getAnyTypeKind(), any.getKey(), profile.getTask().getResource());
+                anyUtils.anyTypeKind(), any.getKey(), profile.getTask().getResource());
 
         Object output = null;
         Result resultStatus = null;

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
index 9edfd6b..f8b73a2 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
@@ -80,14 +80,6 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
 
     protected ProvisioningProfile<PullTask, PullActions> profile;
 
-    protected RealmPullResultHandler rhandler;
-
-    protected AnyObjectPullResultHandler ahandler;
-
-    protected UserPullResultHandler uhandler;
-
-    protected GroupPullResultHandler ghandler;
-
     @Override
     public void setLatestSyncToken(final ObjectClass objectClass, final SyncToken latestSyncToken) {
         latestSyncTokens.put(objectClass, latestSyncToken);
@@ -168,30 +160,18 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
     }
 
     protected RealmPullResultHandler buildRealmHandler() {
-        RealmPullResultHandler handler = (RealmPullResultHandler) ApplicationContextProvider.getBeanFactory().
+        return (RealmPullResultHandler) ApplicationContextProvider.getBeanFactory().
                 createBean(DefaultRealmPullResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
-        handler.setProfile(profile);
-        handler.setPullExecutor(this);
-
-        return handler;
     }
 
     protected AnyObjectPullResultHandler buildAnyObjectHandler() {
-        AnyObjectPullResultHandler handler = (AnyObjectPullResultHandler) ApplicationContextProvider.getBeanFactory().
+        return (AnyObjectPullResultHandler) ApplicationContextProvider.getBeanFactory().
                 createBean(DefaultAnyObjectPullResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
-        handler.setProfile(profile);
-        handler.setPullExecutor(this);
-
-        return handler;
     }
 
     protected UserPullResultHandler buildUserHandler() {
-        UserPullResultHandler handler = (UserPullResultHandler) ApplicationContextProvider.getBeanFactory().
+        return (UserPullResultHandler) ApplicationContextProvider.getBeanFactory().
                 createBean(DefaultUserPullResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
-        handler.setProfile(profile);
-        handler.setPullExecutor(this);
-
-        return handler;
     }
 
     protected GroupPullResultHandler buildGroupHandler() {
@@ -245,7 +225,9 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
             OperationOptions options = MappingUtils.buildOperationOptions(
                     MappingUtils.getPullItems(orgUnit.getItems()).iterator());
 
-            rhandler = buildRealmHandler();
+            RealmPullResultHandler handler = buildRealmHandler();
+            handler.setProfile(profile);
+            handler.setPullExecutor(this);
 
             try {
                 switch (pullTask.getPullMode()) {
@@ -257,7 +239,7 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
                         connector.sync(
                                 orgUnit.getObjectClass(),
                                 orgUnit.getSyncToken(),
-                                rhandler,
+                                handler,
                                 options);
 
                         if (!dryRun) {
@@ -271,14 +253,14 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
                                 ImplementationManager.build(pullTask.getReconFilterBuilder());
                         connector.filteredReconciliation(orgUnit.getObjectClass(),
                                 filterBuilder,
-                                rhandler,
+                                handler,
                                 options);
                         break;
 
                     case FULL_RECONCILIATION:
                     default:
                         connector.fullReconciliation(orgUnit.getObjectClass(),
-                                rhandler,
+                                handler,
                                 options);
                         break;
                 }
@@ -288,18 +270,15 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
         }
 
         // ...then provisions for any types
-        ahandler = buildAnyObjectHandler();
-        uhandler = buildUserHandler();
-        ghandler = buildGroupHandler();
-
+        SyncopePullResultHandler handler;
+        GroupPullResultHandler ghandler = buildGroupHandler();
         for (Provision provision : pullTask.getResource().getProvisions()) {
             if (provision.getMapping() != null) {
                 status.set("Pulling " + provision.getObjectClass().getObjectClassValue());
 
-                SyncopePullResultHandler handler;
                 switch (provision.getAnyType().getKind()) {
                     case USER:
-                        handler = uhandler;
+                        handler = buildUserHandler();
                         break;
 
                     case GROUP:
@@ -308,8 +287,10 @@ public class PullJobDelegate extends AbstractProvisioningJobDelegate<PullTask> i
 
                     case ANY_OBJECT:
                     default:
-                        handler = ahandler;
+                        handler = buildAnyObjectHandler();
                 }
+                handler.setProfile(profile);
+                handler.setPullExecutor(this);
 
                 try {
                     Set<MappingItem> linkingMappingItems = virSchemaDAO.findByProvision(provision).stream().

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullUtils.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullUtils.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullUtils.java
index 77ca4e1..941e5e4 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullUtils.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullUtils.java
@@ -26,11 +26,10 @@ import java.util.Optional;
 import java.util.stream.Collectors;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.ParsingValidationException;
-import org.apache.syncope.core.persistence.api.dao.AnyDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
-import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+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.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.entity.Any;
@@ -148,10 +147,10 @@ public class PullUtils {
             try {
                 List<String> anyKeys = match(connObj, provision.get(), anyUtils);
                 if (anyKeys.isEmpty()) {
-                    LOG.debug("No matching {} found for {}, aborting", anyUtils.getAnyTypeKind(), connObj);
+                    LOG.debug("No matching {} found for {}, aborting", anyUtils.anyTypeKind(), connObj);
                 } else {
                     if (anyKeys.size() > 1) {
-                        LOG.warn("More than one {} found {} - taking first only", anyUtils.getAnyTypeKind(), anyKeys);
+                        LOG.warn("More than one {} found {} - taking first only", anyUtils.anyTypeKind(), anyKeys);
                     }
 
                     result = Optional.ofNullable(anyKeys.iterator().next());
@@ -164,14 +163,6 @@ public class PullUtils {
         return result;
     }
 
-    private AnyDAO<?> getAnyDAO(final AnyTypeKind anyTypeKind) {
-        return AnyTypeKind.USER == anyTypeKind
-                ? userDAO
-                : AnyTypeKind.ANY_OBJECT == anyTypeKind
-                        ? anyObjectDAO
-                        : groupDAO;
-    }
-
     private List<String> findByConnObjectKey(
             final ConnectorObject connObj, final Provision provision, final AnyUtils anyUtils) {
 
@@ -213,7 +204,7 @@ public class PullUtils {
         if (intAttrName.getField() != null) {
             switch (intAttrName.getField()) {
                 case "key":
-                    Any<?> any = getAnyDAO(provision.getAnyType().getKind()).find(connObjectKey);
+                    Any<?> any = anyUtils.dao().find(connObjectKey);
                     if (any != null) {
                         result.add(any.getKey());
                     }
@@ -256,13 +247,13 @@ public class PullUtils {
                         }
                     }
 
-                    result.addAll(getAnyDAO(provision.getAnyType().getKind()).
+                    result.addAll(anyUtils.dao().
                             findByPlainAttrValue(intAttrName.getSchemaName(), value).stream().
                             map(Entity::getKey).collect(Collectors.toList()));
                     break;
 
                 case DERIVED:
-                    result.addAll(getAnyDAO(provision.getAnyType().getKind()).
+                    result.addAll(anyUtils.dao().
                             findByDerAttrValue(intAttrName.getSchemaName(), connObjectKey).stream().
                             map(Entity::getKey).collect(Collectors.toList()));
                     break;
@@ -312,7 +303,7 @@ public class PullUtils {
 
         try {
             return rule.isPresent()
-                    ? findByCorrelationRule(connObj, provision, rule.get(), anyUtils.getAnyTypeKind())
+                    ? findByCorrelationRule(connObj, provision, rule.get(), anyUtils.anyTypeKind())
                     : findByConnObjectKey(connObj, provision, anyUtils);
         } catch (RuntimeException e) {
             LOG.error("Could not match {} with any existing {}", connObj, provision.getAnyType(), e);

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
----------------------------------------------------------------------
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 b813e71..2207f42 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
@@ -27,18 +27,15 @@ import java.util.Optional;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.MutablePair;
 import org.apache.syncope.common.lib.SyncopeConstants;
-import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.core.persistence.api.search.SearchCondConverter;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.apache.syncope.core.persistence.api.dao.AnyDAO;
-import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
-import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
-import org.apache.syncope.core.persistence.api.dao.UserDAO;
 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.Any;
+import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
@@ -63,41 +60,21 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition;
 public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
 
     /**
-     * User DAO.
-     */
-    @Autowired
-    protected UserDAO userDAO;
-
-    /**
      * Search DAO.
      */
     @Autowired
     protected AnySearchDAO searchDAO;
 
-    /**
-     * Group DAO.
-     */
     @Autowired
-    protected GroupDAO groupDAO;
-
-    @Autowired
-    protected AnyObjectDAO anyObjectDAO;
+    protected RealmDAO realmDAO;
 
     @Autowired
-    protected RealmDAO realmDAO;
+    protected AnyUtilsFactory anyUtilsFactory;
 
     protected ProvisioningProfile<PushTask, PushActions> profile;
 
     protected final Map<String, MutablePair<Integer, String>> handled = new HashMap<>();
 
-    protected RealmPushResultHandler rhandler;
-
-    protected AnyObjectPushResultHandler ahandler;
-
-    protected UserPushResultHandler uhandler;
-
-    protected GroupPushResultHandler ghandler;
-
     protected void reportHandled(final String anyType, final String key) {
         MutablePair<Integer, String> pair = handled.get(anyType);
         if (pair == null) {
@@ -125,25 +102,6 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
         return status.get();
     }
 
-    protected AnyDAO<?> getAnyDAO(final AnyTypeKind anyTypeKind) {
-        AnyDAO<?> result;
-        switch (anyTypeKind) {
-            case USER:
-                result = userDAO;
-                break;
-
-            case GROUP:
-                result = groupDAO;
-                break;
-
-            case ANY_OBJECT:
-            default:
-                result = anyObjectDAO;
-        }
-
-        return result;
-    }
-
     protected void doHandle(
             final List<? extends Any<?>> anys,
             final SyncopePushResultHandler handler,
@@ -168,35 +126,23 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
     }
 
     protected RealmPushResultHandler buildRealmHandler() {
-        RealmPushResultHandler handler = (RealmPushResultHandler) ApplicationContextProvider.getBeanFactory().
+        return (RealmPushResultHandler) ApplicationContextProvider.getBeanFactory().
                 createBean(DefaultRealmPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
-        handler.setProfile(profile);
-
-        return handler;
     }
 
     protected AnyObjectPushResultHandler buildAnyObjectHandler() {
-        AnyObjectPushResultHandler handler = (AnyObjectPushResultHandler) ApplicationContextProvider.getBeanFactory().
+        return (AnyObjectPushResultHandler) ApplicationContextProvider.getBeanFactory().
                 createBean(DefaultAnyObjectPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
-        handler.setProfile(profile);
-
-        return handler;
     }
 
     protected UserPushResultHandler buildUserHandler() {
-        UserPushResultHandler handler = (UserPushResultHandler) ApplicationContextProvider.getBeanFactory().
+        return (UserPushResultHandler) ApplicationContextProvider.getBeanFactory().
                 createBean(DefaultUserPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
-        handler.setProfile(profile);
-
-        return handler;
     }
 
     protected GroupPushResultHandler buildGroupHandler() {
-        GroupPushResultHandler handler = (GroupPushResultHandler) ApplicationContextProvider.getBeanFactory().
+        return (GroupPushResultHandler) ApplicationContextProvider.getBeanFactory().
                 createBean(DefaultGroupPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
-        handler.setProfile(profile);
-
-        return handler;
     }
 
     @Override
@@ -233,13 +179,14 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
         if (pushTask.getResource().getOrgUnit() != null) {
             status.set("Pushing realms");
 
-            rhandler = buildRealmHandler();
+            RealmPushResultHandler handler = buildRealmHandler();
+            handler.setProfile(profile);
 
             for (Realm realm : realmDAO.findDescendants(profile.getTask().getSourceRealm())) {
                 // Never push the root realm
                 if (realm.getParent() != null) {
                     try {
-                        rhandler.handle(realm.getKey());
+                        handler.handle(realm.getKey());
                         reportHandled(SyncopeConstants.REALM_ANYTYPE, realm.getName());
                     } catch (Exception e) {
                         LOG.warn("Failure pushing '{}' on '{}'", realm, pushTask.getResource(), e);
@@ -250,30 +197,27 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
         }
 
         // ...then provisions for any types
-        ahandler = buildAnyObjectHandler();
-        uhandler = buildUserHandler();
-        ghandler = buildGroupHandler();
-
         for (Provision provision : pushTask.getResource().getProvisions()) {
             if (provision.getMapping() != null) {
                 status.set("Pushing " + provision.getAnyType().getKey());
 
-                AnyDAO<?> anyDAO = getAnyDAO(provision.getAnyType().getKind());
+                AnyDAO<?> anyDAO = anyUtilsFactory.getInstance(provision.getAnyType().getKind()).dao();
 
                 SyncopePushResultHandler handler;
                 switch (provision.getAnyType().getKind()) {
                     case USER:
-                        handler = uhandler;
+                        handler = buildUserHandler();
                         break;
 
                     case GROUP:
-                        handler = ghandler;
+                        handler = buildGroupHandler();
                         break;
 
                     case ANY_OBJECT:
                     default:
-                        handler = ahandler;
+                        handler = buildAnyObjectHandler();
                 }
+                handler.setProfile(profile);
 
                 Optional<? extends PushTaskAnyFilter> anyFilter = pushTask.getFilter(provision.getAnyType());
                 String filter = anyFilter.isPresent()

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..f4f57b4
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java
@@ -0,0 +1,174 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.syncope.common.lib.collections.IteratorChain;
+import org.apache.syncope.common.lib.types.ConflictResolutionAction;
+import org.apache.syncope.common.lib.types.ImplementationType;
+import org.apache.syncope.common.lib.types.MatchingRule;
+import org.apache.syncope.common.lib.types.PullMode;
+import org.apache.syncope.common.lib.types.UnmatchingRule;
+import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
+import org.apache.syncope.core.persistence.api.entity.Realm;
+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.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.identityconnectors.framework.common.objects.AttributeBuilder;
+import org.identityconnectors.framework.common.objects.OperationOptions;
+import org.identityconnectors.framework.common.objects.filter.Filter;
+import org.identityconnectors.framework.common.objects.filter.FilterBuilder;
+import org.quartz.JobExecutionException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SinglePullJobDelegate extends PullJobDelegate implements SyncopeSinglePullExecutor {
+
+    @Autowired
+    private ImplementationDAO implementationDAO;
+
+    @Override
+    public List<ProvisioningReport> pull(
+            final Provision provision,
+            final Connector connector,
+            final String connObjectKey,
+            final String connObjectValue,
+            final Realm realm,
+            final boolean remediation,
+            final List<String> actionKeys) throws JobExecutionException {
+
+        LOG.debug("Executing pull on {}", provision.getResource());
+
+        List<PullActions> actions = new ArrayList<>();
+        actionKeys.forEach(key -> {
+            Implementation impl = implementationDAO.find(key);
+            if (impl == null || impl.getType() != ImplementationType.PULL_ACTIONS) {
+                LOG.debug("Invalid " + Implementation.class.getSimpleName() + " {}, ignoring...", key);
+            } else {
+                try {
+                    actions.add(ImplementationManager.build(impl));
+                } catch (Exception e) {
+                    LOG.warn("While building {}", impl, e);
+                }
+            }
+        });
+
+        try {
+            Set<MappingItem> linkinMappingItems = virSchemaDAO.findByProvision(provision).stream().
+                    map(virSchema -> virSchema.asLinkingMappingItem()).collect(Collectors.toSet());
+            Iterator<MappingItem> mapItems = new IteratorChain<>(
+                    provision.getMapping().getItems().iterator(),
+                    linkinMappingItems.iterator());
+            OperationOptions options = MappingUtils.buildOperationOptions(mapItems);
+
+            PullTask pullTask = entityFactory.newEntity(PullTask.class);
+            pullTask.setResource(provision.getResource());
+            pullTask.setMatchingRule(MatchingRule.UPDATE);
+            pullTask.setUnmatchingRule(UnmatchingRule.PROVISION);
+            pullTask.setPullMode(PullMode.FILTERED_RECONCILIATION);
+            pullTask.setPerformCreate(true);
+            pullTask.setPerformUpdate(true);
+            pullTask.setRemediation(remediation);
+            pullTask.setDestinationRealm(realm);
+
+            profile = new ProvisioningProfile<>(connector, pullTask);
+            profile.setDryRun(false);
+            profile.setResAct(ConflictResolutionAction.FIRSTMATCH);
+            profile.getActions().addAll(actions);
+
+            for (PullActions action : actions) {
+                action.beforeAll(profile);
+            }
+
+            SyncopePullResultHandler handler;
+            GroupPullResultHandler ghandler = buildGroupHandler();
+            switch (provision.getAnyType().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
+            connector.filteredReconciliation(
+                    provision.getObjectClass(),
+                    new AccountReconciliationFilterBuilder(connObjectKey, connObjectValue),
+                    handler,
+                    options);
+
+            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 pulling from connector", e);
+        }
+    }
+
+    class AccountReconciliationFilterBuilder implements ReconFilterBuilder {
+
+        private final String key;
+
+        private final String value;
+
+        AccountReconciliationFilterBuilder(final String key, final String value) {
+            this.key = key;
+            this.value = value;
+        }
+
+        @Override
+        public Filter build() {
+            return FilterBuilder.equalTo(AttributeBuilder.build(key, value));
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePushJobDelegate.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..76bdc16
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePushJobDelegate.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;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.apache.syncope.common.lib.types.ImplementationType;
+import org.apache.syncope.common.lib.types.MatchingRule;
+import org.apache.syncope.common.lib.types.UnmatchingRule;
+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.Implementation;
+import org.apache.syncope.core.persistence.api.entity.resource.Provision;
+import org.apache.syncope.core.persistence.api.entity.task.PushTask;
+import org.apache.syncope.core.provisioning.api.Connector;
+import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningProfile;
+import org.apache.syncope.core.provisioning.api.pushpull.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;
+import org.apache.syncope.core.spring.ImplementationManager;
+import org.quartz.JobExecutionException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SinglePushJobDelegate extends PushJobDelegate implements SyncopeSinglePushExecutor {
+
+    @Autowired
+    private ImplementationDAO implementationDAO;
+
+    @Override
+    public List<ProvisioningReport> push(
+            final Provision provision,
+            final Connector connector,
+            final Any<?> any,
+            final List<String> actionKeys) throws JobExecutionException {
+
+        LOG.debug("Executing push on {}", provision.getResource());
+
+        List<PushActions> actions = new ArrayList<>();
+        actionKeys.forEach(key -> {
+            Implementation impl = implementationDAO.find(key);
+            if (impl == null || impl.getType() != ImplementationType.PUSH_ACTIONS) {
+                LOG.debug("Invalid " + Implementation.class.getSimpleName() + " {}, ignoring...", key);
+            } else {
+                try {
+                    actions.add(ImplementationManager.build(impl));
+                } catch (Exception e) {
+                    LOG.warn("While building {}", impl, e);
+                }
+            }
+        });
+
+        try {
+            PushTask pushTask = entityFactory.newEntity(PushTask.class);
+            pushTask.setResource(provision.getResource());
+            pushTask.setMatchingRule(MatchingRule.UPDATE);
+            pushTask.setUnmatchingRule(UnmatchingRule.PROVISION);
+            pushTask.setPerformCreate(true);
+            pushTask.setPerformUpdate(true);
+
+            profile = new ProvisioningProfile<>(connector, pushTask);
+            profile.getActions().addAll(actions);
+            profile.setResAct(null);
+
+            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(Arrays.asList(any), handler, pushTask.getResource());
+
+            for (PushActions action : actions) {
+                action.afterAll(profile);
+            }
+
+            return profile.getResults();
+        } catch (Exception e) {
+            throw e instanceof JobExecutionException
+                    ? (JobExecutionException) e
+                    : new JobExecutionException("While pushing to connector", e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
index ad9cbed..63e44cb 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
@@ -242,8 +242,8 @@ public class ConnObjectUtils {
         updated.setKey(key);
 
         T anyPatch = null;
-        if (null != anyUtils.getAnyTypeKind()) {
-            switch (anyUtils.getAnyTypeKind()) {
+        if (null != anyUtils.anyTypeKind()) {
+            switch (anyUtils.anyTypeKind()) {
                 case USER:
                     UserTO originalUser = (UserTO) original;
                     UserTO updatedUser = (UserTO) updated;

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java
----------------------------------------------------------------------
diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java
index 92d2aad..9d028e4 100644
--- a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java
+++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java
@@ -64,18 +64,6 @@ public abstract class AbstractAnyService<TO extends AnyTO, P extends AnyPatch>
 
     protected abstract P newPatch(String key);
 
-    protected String getActualKey(final String key) {
-        String actualKey = key;
-        if (!SyncopeConstants.UUID_PATTERN.matcher(key).matches()) {
-            actualKey = getAnyDAO().findKey(key);
-            if (actualKey == null) {
-                throw new NotFoundException("User, Group or Any Object for " + key);
-            }
-        }
-
-        return actualKey;
-    }
-
     @Override
     public Set<AttrTO> read(final String key, final SchemaType schemaType) {
         TO any = read(key);
@@ -124,7 +112,7 @@ public abstract class AbstractAnyService<TO extends AnyTO, P extends AnyPatch>
 
     @Override
     public TO read(final String key) {
-        return getAnyLogic().read(getActualKey(key));
+        return getAnyLogic().read(getActualKey(getAnyDAO(), key));
     }
 
     @Override
@@ -161,7 +149,7 @@ public abstract class AbstractAnyService<TO extends AnyTO, P extends AnyPatch>
     }
 
     protected Response doUpdate(final P anyPatch) {
-        anyPatch.setKey(getActualKey(anyPatch.getKey()));
+        anyPatch.setKey(getActualKey(getAnyDAO(), anyPatch.getKey()));
         Date etagDate = findLastChange(anyPatch.getKey());
         checkETag(String.valueOf(etagDate.getTime()));
 
@@ -196,21 +184,23 @@ public abstract class AbstractAnyService<TO extends AnyTO, P extends AnyPatch>
 
     @Override
     public Response update(final String key, final SchemaType schemaType, final AttrTO attrTO) {
-        String actualKey = getActualKey(key);
+        String actualKey = getActualKey(getAnyDAO(), key);
         addUpdateOrReplaceAttr(actualKey, schemaType, attrTO, PatchOperation.ADD_REPLACE);
         return modificationResponse(read(actualKey, schemaType, attrTO.getSchema()));
     }
 
     @Override
     public void delete(final String key, final SchemaType schemaType, final String schema) {
-        String actualKey = getActualKey(key);
         addUpdateOrReplaceAttr(
-                actualKey, schemaType, new AttrTO.Builder().schema(schema).build(), PatchOperation.DELETE);
+                getActualKey(getAnyDAO(), key),
+                schemaType,
+                new AttrTO.Builder().schema(schema).build(),
+                PatchOperation.DELETE);
     }
 
     @Override
     public Response delete(final String key) {
-        String actualKey = getActualKey(key);
+        String actualKey = getActualKey(getAnyDAO(), key);
 
         Date etagDate = findLastChange(actualKey);
         checkETag(String.valueOf(etagDate.getTime()));

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java
----------------------------------------------------------------------
diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java
index a040960..caef5d8 100644
--- a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java
+++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java
@@ -37,12 +37,15 @@ import org.apache.cxf.jaxrs.ext.search.SearchCondition;
 import org.apache.cxf.jaxrs.ext.search.SearchContext;
 import org.apache.syncope.common.lib.AbstractBaseBean;
 import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.to.ProvisioningResult;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.rest.api.service.JAXRSService;
 import org.apache.syncope.common.rest.api.Preference;
 import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.core.persistence.api.dao.AnyDAO;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
@@ -64,6 +67,18 @@ abstract class AbstractServiceImpl implements JAXRSService {
     @Context
     protected SearchContext searchContext;
 
+    protected String getActualKey(final AnyDAO<?> dao, final String pretendingKey) {
+        String actualKey = pretendingKey;
+        if (!SyncopeConstants.UUID_PATTERN.matcher(pretendingKey).matches()) {
+            actualKey = dao.findKey(pretendingKey);
+            if (actualKey == null) {
+                throw new NotFoundException("User, Group or Any Object for " + pretendingKey);
+            }
+        }
+
+        return actualKey;
+    }
+
     protected boolean isNullPriorityAsync() {
         return BooleanUtils.toBoolean(messageContext.getHttpHeaders().getHeaderString(RESTHeaders.NULL_PRIORITY_ASYNC));
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceImpl.java
----------------------------------------------------------------------
diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceImpl.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceImpl.java
index 961a328..3a7d40b 100644
--- a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceImpl.java
+++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceImpl.java
@@ -71,7 +71,7 @@ public class AnyObjectServiceImpl extends AbstractAnyService<AnyObjectTO, AnyObj
 
     @Override
     public Response update(final AnyObjectTO anyObjectTO) {
-        anyObjectTO.setKey(getActualKey(anyObjectTO.getKey()));
+        anyObjectTO.setKey(getActualKey(getAnyDAO(), anyObjectTO.getKey()));
         AnyObjectTO before = logic.read(anyObjectTO.getKey());
 
         checkETag(before.getETagValue());

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GroupServiceImpl.java
----------------------------------------------------------------------
diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GroupServiceImpl.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GroupServiceImpl.java
index 4e23d22..bc621e9 100644
--- a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GroupServiceImpl.java
+++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GroupServiceImpl.java
@@ -68,7 +68,7 @@ public class GroupServiceImpl extends AbstractAnyService<GroupTO, GroupPatch> im
 
     @Override
     public Response update(final GroupTO groupTO) {
-        groupTO.setKey(getActualKey(groupTO.getKey()));
+        groupTO.setKey(getActualKey(getAnyDAO(), groupTO.getKey()));
         GroupTO before = logic.read(groupTO.getKey());
 
         checkETag(before.getETagValue());

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ReconciliationServiceImpl.java
----------------------------------------------------------------------
diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ReconciliationServiceImpl.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ReconciliationServiceImpl.java
new file mode 100644
index 0000000..80d89d5
--- /dev/null
+++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ReconciliationServiceImpl.java
@@ -0,0 +1,53 @@
+/*
+ * 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.rest.cxf.service;
+
+import org.apache.syncope.common.lib.to.ReconciliationRequest;
+import org.apache.syncope.common.lib.to.ReconciliationStatus;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.rest.api.service.ReconciliationService;
+import org.apache.syncope.core.logic.ReconciliationLogic;
+import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ReconciliationServiceImpl extends AbstractServiceImpl implements ReconciliationService {
+
+    @Autowired
+    private ReconciliationLogic logic;
+
+    @Autowired
+    private AnyUtilsFactory anyUtilsFactory;
+
+    @Override
+    public ReconciliationStatus status(final AnyTypeKind anyTypeKind, final String anyKey, final String resourceKey) {
+        return logic.status(
+                anyTypeKind,
+                getActualKey(anyUtilsFactory.getInstance(anyTypeKind).dao(), anyKey),
+                resourceKey);
+    }
+
+    @Override
+    public void reconcile(final ReconciliationRequest request) {
+        request.setAnyKey(
+                getActualKey(anyUtilsFactory.getInstance(request.getAnyTypeKind()).dao(), request.getAnyKey()));
+        logic.reconcile(request);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserServiceImpl.java
----------------------------------------------------------------------
diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserServiceImpl.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserServiceImpl.java
index 4af144b..99dc24d 100644
--- a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserServiceImpl.java
+++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserServiceImpl.java
@@ -67,7 +67,7 @@ public class UserServiceImpl extends AbstractAnyService<UserTO, UserPatch> imple
 
     @Override
     public Response update(final UserTO userTO) {
-        userTO.setKey(getActualKey(userTO.getKey()));
+        userTO.setKey(getActualKey(getAnyDAO(), userTO.getKey()));
         UserTO before = logic.read(userTO.getKey());
 
         checkETag(before.getETagValue());

http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
----------------------------------------------------------------------
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
index 3151971..759f5e4 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
@@ -85,6 +85,7 @@ import org.apache.syncope.common.rest.api.service.GroupService;
 import org.apache.syncope.common.rest.api.service.ImplementationService;
 import org.apache.syncope.common.rest.api.service.MailTemplateService;
 import org.apache.syncope.common.rest.api.service.RealmService;
+import org.apache.syncope.common.rest.api.service.ReconciliationService;
 import org.apache.syncope.common.rest.api.service.RelationshipTypeService;
 import org.apache.syncope.common.rest.api.service.RemediationService;
 import org.apache.syncope.common.rest.api.service.ReportTemplateService;
@@ -228,6 +229,8 @@ public abstract class AbstractITCase {
 
     protected static TaskService taskService;
 
+    protected static ReconciliationService reconciliationService;
+
     protected static WorkflowService workflowService;
 
     protected static MailTemplateService mailTemplateService;
@@ -307,6 +310,7 @@ public abstract class AbstractITCase {
         reportTemplateService = adminClient.getService(ReportTemplateService.class);
         reportService = adminClient.getService(ReportService.class);
         taskService = adminClient.getService(TaskService.class);
+        reconciliationService = adminClient.getService(ReconciliationService.class);
         policyService = adminClient.getService(PolicyService.class);
         workflowService = adminClient.getService(WorkflowService.class);
         mailTemplateService = adminClient.getService(MailTemplateService.class);


[4/6] syncope git commit: Reworking 'Set admin credentials'

Posted by il...@apache.org.
Reworking 'Set admin credentials'


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/3c4a3515
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/3c4a3515
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/3c4a3515

Branch: refs/heads/master
Commit: 3c4a351534e3221a2ce0f0a9660b442f8dbfdc74
Parents: 3211023
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Thu Apr 12 11:49:28 2018 +0200
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Thu Apr 12 11:50:35 2018 +0200

----------------------------------------------------------------------
 .../setadmincredentials.adoc                    | 23 +++++++++++++-------
 1 file changed, 15 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/3c4a3515/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/setadmincredentials.adoc
----------------------------------------------------------------------
diff --git a/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/setadmincredentials.adoc b/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/setadmincredentials.adoc
index 4292a06..10e24f0 100644
--- a/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/setadmincredentials.adoc
+++ b/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/setadmincredentials.adoc
@@ -21,22 +21,28 @@
 [WARNING]
 The procedure below affects only the `Master` <<domains,domain>>; for other domains check <<domains-management,above>>.
 
-The default password for the `admin` user is `password`.
-
 The credentials are defined in the `security.properties` file; text encoding must be set to UTF-8:
 
-* `adminUser` - administrator username
-* `adminPassword` - SHA1 hash evaluation of cleartext password (represented as a sequence of 40 hexadecimal digits)
-* `adminPasswordAlgorithm` - algorithm to be used for hash evaluation (default value: SHA1)
+* `adminUser` - administrator username (default `admin`)
+* `adminPassword` - administrator password (default `password`)'s hashed value
+* `adminPasswordAlgorithm` - algorithm to be used for hash evaluation (default `SHA1`, others as
+`SHA256`, `SHA512`, `SMD5`, `SSHA1`, `SSHA256`, `SSHA512` and `BCRYPT` are supported)
 
-For GNU / Linux and Mac OS X, the SHA1 password can be obtained via the `sha1sum` command-line tool of
-http://www.gnu.org/software/coreutils/[GNU Core Utilities^]:
+[TIP]
+====
+The hashed password value can be obtained, depending on the actual algorithm, via various tools.
 
+As an example, for `SHA1` and GNU / Linux and Mac OS X, the `sha1sum` command-line tool of
+http://www.gnu.org/software/coreutils/[GNU Core Utilities^] can be used as follows:
 [source,bash]
 ....
 echo -n "new_password" | sha1sum
 ....
-For MS Windows, some options are available:
+
+Please beware that any shell special character must be properly escaped for the command above to produce the expected
+hashed value.
+
+Again about `SHA1`, for MS Windows some options are available:
 
 * http://support.microsoft.com/kb/841290[MS File Checksum Integrity Verifier^] +
 install, save your password to a file (e.g. `password.txt` without EOL) and issue at command line: +
@@ -47,3 +53,4 @@ fciv.exe -sha1 password.txt
 * http://gnuwin32.sourceforge.net/[GnuWin32^] port of GNU utilities for MS Windows
 * http://www.cygwin.com/[Cygwin^] Unix-like environment and command-line interface for Microsoft Windows (featuring
 http://www.gnu.org/software/coreutils/[GNU Core Utilities^])
+====


[5/6] syncope git commit: [SYNCOPE-1299] Core implementation

Posted by il...@apache.org.
http://git-wip-us.apache.org/repos/asf/syncope/blob/e5860a76/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReconciliationITCase.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..172e69c
--- /dev/null
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReconciliationITCase.java
@@ -0,0 +1,134 @@
+/*
+ * 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.fit.core;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+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 java.util.Date;
+import javax.sql.DataSource;
+import org.apache.syncope.common.lib.to.AnyObjectTO;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.ReconciliationRequest;
+import org.apache.syncope.common.lib.to.ReconciliationStatus;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.ReconciliationAction;
+import org.apache.syncope.fit.AbstractITCase;
+import org.identityconnectors.framework.common.objects.OperationalAttributes;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+
+@SpringJUnitConfig(locations = { "classpath:testJDBCEnv.xml" })
+public class ReconciliationITCase extends AbstractITCase {
+
+    @Autowired
+    private DataSource testDataSource;
+
+    @Test
+    public void push() {
+        // 1. create printer, with no resources
+        AnyObjectTO printer = AnyObjectITCase.getSampleTO("reconciliation");
+        printer.getResources().clear();
+        printer = createAnyObject(printer).getEntity();
+        assertNotNull(printer.getKey());
+
+        // 2. verify no printer with that name is on the external resource's db
+        JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
+        assertEquals(0, jdbcTemplate.queryForList(
+                "SELECT id FROM testPRINTER WHERE printername=?", printer.getName()).size());
+
+        // 3. verify reconciliation status
+        ReconciliationStatus status =
+                reconciliationService.status(AnyTypeKind.ANY_OBJECT, printer.getName(), "resource-db-scripted");
+        assertNotNull(status);
+        assertNotNull(status.getOnSyncope());
+        assertNull(status.getOnResource());
+
+        // 4. push
+        ReconciliationRequest request = new ReconciliationRequest();
+        request.setAction(ReconciliationAction.PUSH);
+        request.setAnyKey(printer.getKey());
+        request.setAnyTypeKind(AnyTypeKind.ANY_OBJECT);
+        request.setResourceKey("resource-db-scripted");
+        reconciliationService.reconcile(request);
+
+        // 5. verify that printer is now propagated
+        assertEquals(1, jdbcTemplate.queryForList(
+                "SELECT id FROM testPRINTER WHERE printername=?", printer.getName()).size());
+
+        // 6. verify resource was not assigned
+        printer = anyObjectService.read(printer.getKey());
+        assertTrue(printer.getResources().isEmpty());
+
+        // 7. verify reconciliation status
+        status = reconciliationService.status(AnyTypeKind.ANY_OBJECT, printer.getName(), "resource-db-scripted");
+        assertNotNull(status);
+        assertNotNull(status.getOnSyncope());
+        assertNotNull(status.getOnResource());
+
+        // __ENABLE__ management depends on the actual connector...
+        AttrTO enable = status.getOnSyncope().getAttr(OperationalAttributes.ENABLE_NAME).orElse(null);
+        if (enable != null) {
+            status.getOnSyncope().getAttrs().remove(enable);
+        }
+        assertEquals(status.getOnSyncope(), status.getOnResource());
+    }
+
+    @Test
+    public void pull() {
+        // 1. create printer, with no resources
+        AnyObjectTO printer = AnyObjectITCase.getSampleTO("reconciliation");
+        printer.getResources().clear();
+        printer = createAnyObject(printer).getEntity();
+        assertNotNull(printer.getKey());
+        assertNotEquals("Nowhere", printer.getPlainAttr("location").get().getValues().get(0));
+
+        // 2. create table into the external resource's db, with same name
+        JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
+        jdbcTemplate.update(
+                "INSERT INTO TESTPRINTER (id, printername, location, deleted, lastmodification) VALUES (?,?,?,?,?)",
+                printer.getKey(), printer.getName(), "Nowhere", false, new Date());
+
+        // 3. verify reconciliation status
+        ReconciliationStatus status =
+                reconciliationService.status(AnyTypeKind.ANY_OBJECT, printer.getName(), "resource-db-scripted");
+        assertNotNull(status);
+        assertNotNull(status.getOnSyncope());
+        assertNotNull(status.getOnResource());
+        assertNotEquals(status.getOnSyncope().getAttr("LOCATION"), status.getOnResource().getAttr("LOCATION"));
+
+        // 4. pull
+        ReconciliationRequest request = new ReconciliationRequest();
+        request.setAction(ReconciliationAction.PULL);
+        request.setAnyKey(printer.getKey());
+        request.setAnyTypeKind(AnyTypeKind.ANY_OBJECT);
+        request.setResourceKey("resource-db-scripted");
+        reconciliationService.reconcile(request);
+
+        // 5. verify reconciliation result (and resource is still not assigned)
+        printer = anyObjectService.read(printer.getKey());
+        assertEquals("Nowhere", printer.getPlainAttr("location").get().getValues().get(0));
+        assertTrue(printer.getResources().isEmpty());
+    }
+}