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:06 UTC

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

[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());