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 2016/03/09 12:52:42 UTC

[06/17] syncope git commit: Further refactoring as per SYNCOPE-620

http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/syncpull/AbstractProvisioningJobDelegate.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/syncpull/AbstractProvisioningJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/syncpull/AbstractProvisioningJobDelegate.java
new file mode 100644
index 0000000..bd13365
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/syncpull/AbstractProvisioningJobDelegate.java
@@ -0,0 +1,434 @@
+/*
+ * 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.syncpull;
+
+import java.lang.reflect.ParameterizedType;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.annotation.Resource;
+import org.apache.syncope.common.lib.types.TraceLevel;
+import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
+import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.core.persistence.api.dao.PolicyDAO;
+import org.apache.syncope.core.persistence.api.entity.AnyType;
+import org.apache.syncope.core.persistence.api.entity.resource.Mapping;
+import org.apache.syncope.core.persistence.api.entity.resource.Provision;
+import org.apache.syncope.core.persistence.api.entity.task.ProvisioningTask;
+import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.core.provisioning.api.Connector;
+import org.apache.syncope.core.provisioning.api.ConnectorFactory;
+import org.apache.syncope.core.provisioning.api.syncpull.ProvisioningReport;
+import org.apache.syncope.core.provisioning.java.job.AbstractSchedTaskJobDelegate;
+import org.apache.syncope.core.provisioning.java.job.TaskJob;
+import org.quartz.JobExecutionException;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public abstract class AbstractProvisioningJobDelegate<T extends ProvisioningTask>
+        extends AbstractSchedTaskJobDelegate {
+
+    @Resource(name = "adminUser")
+    protected String adminUser;
+
+    /**
+     * ConnInstance loader.
+     */
+    @Autowired
+    protected ConnectorFactory connFactory;
+
+    @Autowired
+    protected AnyTypeDAO anyTypeDAO;
+
+    /**
+     * Resource DAO.
+     */
+    @Autowired
+    protected ExternalResourceDAO resourceDAO;
+
+    /**
+     * Policy DAO.
+     */
+    @Autowired
+    protected PolicyDAO policyDAO;
+
+    /**
+     * Create a textual report of the synchronization, based on the trace level.
+     *
+     * @param provResults Sync results
+     * @param syncTraceLevel Sync trace level
+     * @param dryRun dry run?
+     * @return report as string
+     */
+    protected String createReport(final Collection<ProvisioningReport> provResults, final TraceLevel syncTraceLevel,
+            final boolean dryRun) {
+
+        if (syncTraceLevel == TraceLevel.NONE) {
+            return null;
+        }
+
+        StringBuilder report = new StringBuilder();
+
+        if (dryRun) {
+            report.append("==>Dry run only, no modifications were made<==\n\n");
+        }
+
+        List<ProvisioningReport> uSuccCreate = new ArrayList<>();
+        List<ProvisioningReport> uFailCreate = new ArrayList<>();
+        List<ProvisioningReport> uSuccUpdate = new ArrayList<>();
+        List<ProvisioningReport> uFailUpdate = new ArrayList<>();
+        List<ProvisioningReport> uSuccDelete = new ArrayList<>();
+        List<ProvisioningReport> uFailDelete = new ArrayList<>();
+        List<ProvisioningReport> uSuccNone = new ArrayList<>();
+        List<ProvisioningReport> uIgnore = new ArrayList<>();
+        List<ProvisioningReport> gSuccCreate = new ArrayList<>();
+        List<ProvisioningReport> gFailCreate = new ArrayList<>();
+        List<ProvisioningReport> gSuccUpdate = new ArrayList<>();
+        List<ProvisioningReport> gFailUpdate = new ArrayList<>();
+        List<ProvisioningReport> gSuccDelete = new ArrayList<>();
+        List<ProvisioningReport> gFailDelete = new ArrayList<>();
+        List<ProvisioningReport> gSuccNone = new ArrayList<>();
+        List<ProvisioningReport> gIgnore = new ArrayList<>();
+        List<ProvisioningReport> aSuccCreate = new ArrayList<>();
+        List<ProvisioningReport> aFailCreate = new ArrayList<>();
+        List<ProvisioningReport> aSuccUpdate = new ArrayList<>();
+        List<ProvisioningReport> aFailUpdate = new ArrayList<>();
+        List<ProvisioningReport> aSuccDelete = new ArrayList<>();
+        List<ProvisioningReport> aFailDelete = new ArrayList<>();
+        List<ProvisioningReport> aSuccNone = new ArrayList<>();
+        List<ProvisioningReport> aIgnore = new ArrayList<>();
+
+        for (ProvisioningReport provResult : provResults) {
+            AnyType anyType = anyTypeDAO.find(provResult.getAnyType());
+
+            switch (provResult.getStatus()) {
+                case SUCCESS:
+                    switch (provResult.getOperation()) {
+                        case CREATE:
+                            switch (anyType.getKind()) {
+                                case USER:
+                                    uSuccCreate.add(provResult);
+                                    break;
+
+                                case GROUP:
+                                    gSuccCreate.add(provResult);
+                                    break;
+
+                                case ANY_OBJECT:
+                                default:
+                                    aSuccCreate.add(provResult);
+                            }
+                            break;
+
+                        case UPDATE:
+                            switch (anyType.getKind()) {
+                                case USER:
+                                    uSuccUpdate.add(provResult);
+                                    break;
+
+                                case GROUP:
+                                    gSuccUpdate.add(provResult);
+                                    break;
+
+                                case ANY_OBJECT:
+                                default:
+                                    aSuccUpdate.add(provResult);
+                            }
+                            break;
+
+                        case DELETE:
+                            switch (anyType.getKind()) {
+                                case USER:
+                                    uSuccDelete.add(provResult);
+                                    break;
+
+                                case GROUP:
+                                    gSuccDelete.add(provResult);
+                                    break;
+
+                                case ANY_OBJECT:
+                                default:
+                                    aSuccDelete.add(provResult);
+                            }
+                            break;
+
+                        case NONE:
+                            switch (anyType.getKind()) {
+                                case USER:
+                                    uSuccNone.add(provResult);
+                                    break;
+
+                                case GROUP:
+                                    gSuccNone.add(provResult);
+                                    break;
+
+                                case ANY_OBJECT:
+                                default:
+                                    aSuccNone.add(provResult);
+                            }
+                            break;
+
+                        default:
+                    }
+                    break;
+
+                case FAILURE:
+                    switch (provResult.getOperation()) {
+                        case CREATE:
+                            switch (anyType.getKind()) {
+                                case USER:
+                                    uFailCreate.add(provResult);
+                                    break;
+
+                                case GROUP:
+                                    gFailCreate.add(provResult);
+                                    break;
+
+                                case ANY_OBJECT:
+                                default:
+                                    aFailCreate.add(provResult);
+                            }
+                            break;
+
+                        case UPDATE:
+                            switch (anyType.getKind()) {
+                                case USER:
+                                    uFailUpdate.add(provResult);
+                                    break;
+
+                                case GROUP:
+                                    gFailUpdate.add(provResult);
+                                    break;
+
+                                case ANY_OBJECT:
+                                default:
+                                    aFailUpdate.add(provResult);
+                            }
+                            break;
+
+                        case DELETE:
+                            switch (anyType.getKind()) {
+                                case USER:
+                                    uFailDelete.add(provResult);
+                                    break;
+
+                                case GROUP:
+                                    gFailDelete.add(provResult);
+                                    break;
+
+                                case ANY_OBJECT:
+                                default:
+                                    aFailDelete.add(provResult);
+                            }
+                            break;
+
+                        default:
+                    }
+                    break;
+
+                case IGNORE:
+                    switch (anyType.getKind()) {
+                        case USER:
+                            uIgnore.add(provResult);
+                            break;
+
+                        case GROUP:
+                            gIgnore.add(provResult);
+                            break;
+
+                        case ANY_OBJECT:
+                        default:
+                            aIgnore.add(provResult);
+                    }
+                    break;
+
+                default:
+            }
+        }
+
+        // Summary, also to be included for FAILURE and ALL, so create it anyway.
+        report.append("Users ").
+                append("[created/failures]: ").append(uSuccCreate.size()).append('/').append(uFailCreate.size()).
+                append(' ').
+                append("[updated/failures]: ").append(uSuccUpdate.size()).append('/').append(uFailUpdate.size()).
+                append(' ').
+                append("[deleted/failures]: ").append(uSuccDelete.size()).append('/').append(uFailDelete.size()).
+                append(' ').
+                append("[no operation/ignored]: ").append(uSuccNone.size()).append('/').append(uIgnore.size()).
+                append('\n');
+        report.append("Groups ").
+                append("[created/failures]: ").append(gSuccCreate.size()).append('/').append(gFailCreate.size()).
+                append(' ').
+                append("[updated/failures]: ").append(gSuccUpdate.size()).append('/').append(gFailUpdate.size()).
+                append(' ').
+                append("[deleted/failures]: ").append(gSuccDelete.size()).append('/').append(gFailDelete.size()).
+                append(' ').
+                append("[no operation/ignored]: ").append(gSuccNone.size()).append('/').append(gIgnore.size()).
+                append('\n');
+        report.append("Any objects ").
+                append("[created/failures]: ").append(aSuccCreate.size()).append('/').append(aFailCreate.size()).
+                append(' ').
+                append("[updated/failures]: ").append(aSuccUpdate.size()).append('/').append(aFailUpdate.size()).
+                append(' ').
+                append("[deleted/failures]: ").append(aSuccDelete.size()).append('/').append(aFailDelete.size()).
+                append(' ').
+                append("[no operation/ignored]: ").append(aSuccNone.size()).append('/').append(aIgnore.size());
+
+        // Failures
+        if (syncTraceLevel == TraceLevel.FAILURES || syncTraceLevel == TraceLevel.ALL) {
+            if (!uFailCreate.isEmpty()) {
+                report.append("\n\nUsers failed to create: ");
+                report.append(ProvisioningReport.produceReport(uFailCreate, syncTraceLevel));
+            }
+            if (!uFailUpdate.isEmpty()) {
+                report.append("\nUsers failed to update: ");
+                report.append(ProvisioningReport.produceReport(uFailUpdate, syncTraceLevel));
+            }
+            if (!uFailDelete.isEmpty()) {
+                report.append("\nUsers failed to delete: ");
+                report.append(ProvisioningReport.produceReport(uFailDelete, syncTraceLevel));
+            }
+
+            if (!gFailCreate.isEmpty()) {
+                report.append("\n\nGroups failed to create: ");
+                report.append(ProvisioningReport.produceReport(gFailCreate, syncTraceLevel));
+            }
+            if (!gFailUpdate.isEmpty()) {
+                report.append("\nGroups failed to update: ");
+                report.append(ProvisioningReport.produceReport(gFailUpdate, syncTraceLevel));
+            }
+            if (!gFailDelete.isEmpty()) {
+                report.append("\nGroups failed to delete: ");
+                report.append(ProvisioningReport.produceReport(gFailDelete, syncTraceLevel));
+            }
+
+            if (!aFailCreate.isEmpty()) {
+                report.append("\nAny objects failed to create: ");
+                report.append(ProvisioningReport.produceReport(aFailCreate, syncTraceLevel));
+            }
+            if (!aFailUpdate.isEmpty()) {
+                report.append("\nAny objects failed to update: ");
+                report.append(ProvisioningReport.produceReport(aFailUpdate, syncTraceLevel));
+            }
+            if (!aFailDelete.isEmpty()) {
+                report.append("\nAny objects failed to delete: ");
+                report.append(ProvisioningReport.produceReport(aFailDelete, syncTraceLevel));
+            }
+        }
+
+        // Succeeded, only if on 'ALL' level
+        if (syncTraceLevel == TraceLevel.ALL) {
+            report.append("\n\nUsers created:\n").
+                    append(ProvisioningReport.produceReport(uSuccCreate, syncTraceLevel)).
+                    append("\nUsers updated:\n").
+                    append(ProvisioningReport.produceReport(uSuccUpdate, syncTraceLevel)).
+                    append("\nUsers deleted:\n").
+                    append(ProvisioningReport.produceReport(uSuccDelete, syncTraceLevel)).
+                    append("\nUsers no operation:\n").
+                    append(ProvisioningReport.produceReport(uSuccNone, syncTraceLevel)).
+                    append("\nUsers ignored:\n").
+                    append(ProvisioningReport.produceReport(uIgnore, syncTraceLevel));
+            report.append("\n\nGroups created:\n").
+                    append(ProvisioningReport.produceReport(gSuccCreate, syncTraceLevel)).
+                    append("\nGroups updated:\n").
+                    append(ProvisioningReport.produceReport(gSuccUpdate, syncTraceLevel)).
+                    append("\nGroups deleted:\n").
+                    append(ProvisioningReport.produceReport(gSuccDelete, syncTraceLevel)).
+                    append("\nGroups no operation:\n").
+                    append(ProvisioningReport.produceReport(gSuccNone, syncTraceLevel)).
+                    append("\nGroups ignored:\n").
+                    append(ProvisioningReport.produceReport(gSuccNone, syncTraceLevel));
+            report.append("\n\nAny objects created:\n").
+                    append(ProvisioningReport.produceReport(aSuccCreate, syncTraceLevel)).
+                    append("\nAny objects updated:\n").
+                    append(ProvisioningReport.produceReport(aSuccUpdate, syncTraceLevel)).
+                    append("\nAny objects deleted:\n").
+                    append(ProvisioningReport.produceReport(aSuccDelete, syncTraceLevel)).
+                    append("\nAny objects no operation:\n").
+                    append(ProvisioningReport.produceReport(aSuccNone, syncTraceLevel)).
+                    append("\nAny objects ignored:\n").
+                    append(ProvisioningReport.produceReport(aSuccNone, syncTraceLevel));
+        }
+
+        return report.toString();
+    }
+
+    @Override
+    protected String doExecute(final boolean dryRun) throws JobExecutionException {
+        try {
+            Class<T> clazz = getTaskClassReference();
+            if (!clazz.isAssignableFrom(task.getClass())) {
+                throw new JobExecutionException("Task " + task.getKey() + " isn't a ProvisioningTask");
+            }
+
+            T provisioningTask = clazz.cast(task);
+
+            Connector connector;
+            try {
+                connector = connFactory.getConnector(provisioningTask.getResource());
+            } catch (Exception e) {
+                String msg = String.format("Connector instance bean for resource %s and connInstance %s not found",
+                        provisioningTask.getResource(), provisioningTask.getResource().getConnector());
+                throw new JobExecutionException(msg, e);
+            }
+
+            boolean noMapping = true;
+            for (Provision provision : provisioningTask.getResource().getProvisions()) {
+                Mapping mapping = provision.getMapping();
+                if (mapping != null) {
+                    noMapping = false;
+                    if (mapping.getConnObjectKeyItem() == null) {
+                        throw new JobExecutionException(
+                                "Invalid ConnObjectKey mapping for provision " + provision);
+                    }
+                }
+            }
+            if (noMapping) {
+                return "No mapping configured for both users and groups: aborting...";
+            }
+
+            return doExecuteProvisioning(
+                    provisioningTask,
+                    connector,
+                    dryRun);
+        } catch (Throwable t) {
+            LOG.error("While executing provisioning job {}", getClass().getName(), t);
+            throw t;
+        }
+    }
+
+    protected abstract String doExecuteProvisioning(
+            final T task,
+            final Connector connector,
+            final boolean dryRun) throws JobExecutionException;
+
+    @Override
+    protected boolean hasToBeRegistered(final TaskExec execution) {
+        final ProvisioningTask provTask = (ProvisioningTask) task;
+
+        // True if either failed and failures have to be registered, or if ALL has to be registered.
+        return (TaskJob.Status.valueOf(execution.getStatus()) == TaskJob.Status.FAILURE
+                && provTask.getResource().getSyncTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal())
+                || provTask.getResource().getSyncTraceLevel().ordinal() >= TraceLevel.SUMMARY.ordinal();
+    }
+
+    @SuppressWarnings("unchecked")
+    private Class<T> getTaskClassReference() {
+        return (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/syncpull/AbstractPushResultHandler.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/syncpull/AbstractPushResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/syncpull/AbstractPushResultHandler.java
new file mode 100644
index 0000000..258e106
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/syncpull/AbstractPushResultHandler.java
@@ -0,0 +1,434 @@
+/*
+ * 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.syncpull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.apache.commons.collections4.IteratorUtils;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.apache.syncope.common.lib.patch.AnyPatch;
+import org.apache.syncope.common.lib.patch.StringPatchItem;
+import org.apache.syncope.common.lib.to.AnyTO;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditElements.Result;
+import org.apache.syncope.common.lib.types.MatchingRule;
+import org.apache.syncope.common.lib.types.PatchOperation;
+import org.apache.syncope.common.lib.types.PropagationByResource;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.common.lib.types.UnmatchingRule;
+import org.apache.syncope.core.persistence.api.entity.task.PushTask;
+import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.provisioning.api.syncpull.ProvisioningReport;
+import org.apache.syncope.core.provisioning.api.syncpull.PushActions;
+import org.apache.syncope.core.provisioning.java.MappingManagerImpl;
+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.anyobject.AnyObject;
+import org.apache.syncope.core.persistence.api.entity.group.Group;
+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.MappingManager;
+import org.apache.syncope.core.provisioning.api.TimeoutException;
+import org.apache.syncope.core.provisioning.api.syncpull.IgnoreProvisionException;
+import org.apache.syncope.core.provisioning.api.syncpull.SyncopePushResultHandler;
+import org.identityconnectors.framework.common.objects.ConnectorObject;
+import org.identityconnectors.framework.common.objects.ObjectClass;
+import org.identityconnectors.framework.common.objects.Uid;
+import org.quartz.JobExecutionException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHandler<PushTask, PushActions>
+        implements SyncopePushResultHandler {
+
+    @Autowired
+    protected MappingManager mappingManager;
+
+    protected abstract String getName(Any<?> any);
+
+    protected void deprovision(final Any<?> any) {
+        AnyTO before = getAnyTO(any.getKey());
+
+        List<String> noPropResources = new ArrayList<>(before.getResources());
+        noPropResources.remove(profile.getTask().getResource().getKey());
+
+        taskExecutor.execute(propagationManager.getDeleteTasks(
+                any.getType().getKind(),
+                any.getKey(),
+                null,
+                noPropResources));
+    }
+
+    protected void provision(final Any<?> any, final Boolean enabled) {
+        AnyTO before = getAnyTO(any.getKey());
+
+        List<String> noPropResources = new ArrayList<>(before.getResources());
+        noPropResources.remove(profile.getTask().getResource().getKey());
+
+        PropagationByResource propByRes = new PropagationByResource();
+        propByRes.add(ResourceOperation.CREATE, profile.getTask().getResource().getKey());
+
+        taskExecutor.execute(propagationManager.getCreateTasks(
+                any.getType().getKind(),
+                any.getKey(),
+                propByRes,
+                before.getVirAttrs(),
+                noPropResources));
+    }
+
+    @SuppressWarnings("unchecked")
+    protected void link(final Any<?> any, final Boolean unlink) {
+        AnyPatch patch = newPatch(any.getKey());
+        patch.getResources().add(new StringPatchItem.Builder().
+                operation(unlink ? PatchOperation.DELETE : PatchOperation.ADD_REPLACE).
+                value(profile.getTask().getResource().getKey()).build());
+
+        update(patch);
+    }
+
+    @SuppressWarnings("unchecked")
+    protected void unassign(final Any<?> any) {
+        AnyPatch patch = newPatch(any.getKey());
+        patch.getResources().add(new StringPatchItem.Builder().
+                operation(PatchOperation.DELETE).
+                value(profile.getTask().getResource().getKey()).build());
+
+        update(patch);
+
+        deprovision(any);
+    }
+
+    protected void assign(final Any<?> any, final Boolean enabled) {
+        AnyPatch patch = newPatch(any.getKey());
+        patch.getResources().add(new StringPatchItem.Builder().
+                operation(PatchOperation.ADD_REPLACE).
+                value(profile.getTask().getResource().getKey()).build());
+
+        update(patch);
+
+        provision(any, enabled);
+    }
+
+    protected ConnectorObject getRemoteObject(final String connObjectKey, final ObjectClass objectClass) {
+        ConnectorObject obj = null;
+        try {
+            Uid uid = new Uid(connObjectKey);
+
+            obj = profile.getConnector().getObject(objectClass,
+                    uid,
+                    MappingManagerImpl.buildOperationOptions(IteratorUtils.<MappingItem>emptyIterator()));
+        } catch (TimeoutException toe) {
+            LOG.debug("Request timeout", toe);
+            throw toe;
+        } catch (RuntimeException ignore) {
+            LOG.debug("While resolving {}", connObjectKey, ignore);
+        }
+
+        return obj;
+    }
+
+    @Transactional(propagation = Propagation.REQUIRES_NEW)
+    @Override
+    public boolean handle(final long anyKey) {
+        Any<?> any = null;
+        try {
+            any = getAny(anyKey);
+            doHandle(any);
+            return true;
+        } catch (IgnoreProvisionException e) {
+            ProvisioningReport result = new ProvisioningReport();
+            result.setOperation(ResourceOperation.NONE);
+            result.setAnyType(any == null ? null : any.getType().getKey());
+            result.setStatus(ProvisioningReport.Status.IGNORE);
+            result.setKey(anyKey);
+            profile.getResults().add(result);
+
+            LOG.warn("Ignoring during push", e);
+            return true;
+        } catch (JobExecutionException e) {
+            LOG.error("Push failed", e);
+            return false;
+        }
+    }
+
+    protected final void doHandle(final Any<?> any) throws JobExecutionException {
+        AnyUtils anyUtils = anyUtilsFactory.getInstance(any);
+
+        ProvisioningReport result = new ProvisioningReport();
+        profile.getResults().add(result);
+
+        result.setKey(any.getKey());
+        result.setAnyType(any.getType().getKey());
+        result.setName(getName(any));
+
+        Boolean enabled = any instanceof User && profile.getTask().isSyncStatus()
+                ? ((User) any).isSuspended() ? Boolean.FALSE : Boolean.TRUE
+                : null;
+
+        LOG.debug("Propagating {} with key {} towards {}",
+                anyUtils.getAnyTypeKind(), any.getKey(), profile.getTask().getResource());
+
+        Object output = null;
+        Result resultStatus = null;
+        String operation = null;
+
+        // Try to read remote object BEFORE any actual operation
+        Provision provision = profile.getTask().getResource().getProvision(any.getType());
+        String connObjecKey = mappingManager.getConnObjectKeyValue(any, provision);
+
+        ConnectorObject beforeObj = getRemoteObject(connObjecKey, provision.getObjectClass());
+
+        Boolean status = profile.getTask().isSyncStatus() ? enabled : null;
+
+        if (profile.isDryRun()) {
+            if (beforeObj == null) {
+                result.setOperation(getResourceOperation(profile.getTask().getUnmatchingRule()));
+            } else {
+                result.setOperation(getResourceOperation(profile.getTask().getMatchingRule()));
+            }
+            result.setStatus(ProvisioningReport.Status.SUCCESS);
+        } else {
+            try {
+                if (beforeObj == null) {
+                    operation = UnmatchingRule.toEventName(profile.getTask().getUnmatchingRule());
+                    result.setOperation(getResourceOperation(profile.getTask().getUnmatchingRule()));
+
+                    switch (profile.getTask().getUnmatchingRule()) {
+                        case ASSIGN:
+                            for (PushActions action : profile.getActions()) {
+                                action.beforeAssign(this.getProfile(), any);
+                            }
+
+                            if (!profile.getTask().isPerformCreate()) {
+                                LOG.debug("PushTask not configured for create");
+                            } else {
+                                assign(any, status);
+                            }
+
+                            break;
+
+                        case PROVISION:
+                            for (PushActions action : profile.getActions()) {
+                                action.beforeProvision(this.getProfile(), any);
+                            }
+
+                            if (!profile.getTask().isPerformCreate()) {
+                                LOG.debug("PushTask not configured for create");
+                            } else {
+                                provision(any, status);
+                            }
+
+                            break;
+
+                        case UNLINK:
+                            for (PushActions action : profile.getActions()) {
+                                action.beforeUnlink(this.getProfile(), any);
+                            }
+
+                            if (!profile.getTask().isPerformUpdate()) {
+                                LOG.debug("PushTask not configured for update");
+                            } else {
+                                link(any, true);
+                            }
+
+                            break;
+
+                        case IGNORE:
+                            LOG.debug("Ignored any: {}", any);
+                            break;
+                        default:
+                        // do nothing
+                    }
+                } else {
+                    operation = MatchingRule.toEventName(profile.getTask().getMatchingRule());
+                    result.setOperation(getResourceOperation(profile.getTask().getMatchingRule()));
+
+                    switch (profile.getTask().getMatchingRule()) {
+                        case UPDATE:
+                            for (PushActions action : profile.getActions()) {
+                                action.beforeUpdate(this.getProfile(), any);
+                            }
+                            if (!profile.getTask().isPerformUpdate()) {
+                                LOG.debug("PushTask not configured for update");
+                            } else {
+                                update(any, status);
+                            }
+
+                            break;
+
+                        case DEPROVISION:
+                            for (PushActions action : profile.getActions()) {
+                                action.beforeDeprovision(this.getProfile(), any);
+                            }
+
+                            if (!profile.getTask().isPerformDelete()) {
+                                LOG.debug("PushTask not configured for delete");
+                            } else {
+                                deprovision(any);
+                            }
+
+                            break;
+
+                        case UNASSIGN:
+                            for (PushActions action : profile.getActions()) {
+                                action.beforeUnassign(this.getProfile(), any);
+                            }
+
+                            if (!profile.getTask().isPerformDelete()) {
+                                LOG.debug("PushTask not configured for delete");
+                            } else {
+                                unassign(any);
+                            }
+
+                            break;
+
+                        case LINK:
+                            for (PushActions action : profile.getActions()) {
+                                action.beforeLink(this.getProfile(), any);
+                            }
+
+                            if (!profile.getTask().isPerformUpdate()) {
+                                LOG.debug("PushTask not configured for update");
+                            } else {
+                                link(any, false);
+                            }
+
+                            break;
+
+                        case UNLINK:
+                            for (PushActions action : profile.getActions()) {
+                                action.beforeUnlink(this.getProfile(), any);
+                            }
+
+                            if (!profile.getTask().isPerformUpdate()) {
+                                LOG.debug("PushTask not configured for update");
+                            } else {
+                                link(any, true);
+                            }
+
+                            break;
+
+                        case IGNORE:
+                            LOG.debug("Ignored any: {}", any);
+                            break;
+                        default:
+                        // do nothing
+                    }
+                }
+
+                for (PushActions action : profile.getActions()) {
+                    action.after(this.getProfile(), any, result);
+                }
+
+                result.setStatus(ProvisioningReport.Status.SUCCESS);
+                resultStatus = AuditElements.Result.SUCCESS;
+                output = getRemoteObject(connObjecKey, provision.getObjectClass());
+            } catch (IgnoreProvisionException e) {
+                throw e;
+            } catch (Exception e) {
+                result.setStatus(ProvisioningReport.Status.FAILURE);
+                result.setMessage(ExceptionUtils.getRootCauseMessage(e));
+                resultStatus = AuditElements.Result.FAILURE;
+                output = e;
+
+                LOG.warn("Error pushing {} towards {}", any, profile.getTask().getResource(), e);
+
+                for (PushActions action : profile.getActions()) {
+                    action.onError(this.getProfile(), any, result, e);
+                }
+
+                throw new JobExecutionException(e);
+            } finally {
+                notificationManager.createTasks(AuditElements.EventCategoryType.PUSH,
+                        any.getType().getKind().name().toLowerCase(),
+                        profile.getTask().getResource().getKey(),
+                        operation,
+                        resultStatus,
+                        beforeObj,
+                        output,
+                        any);
+                auditManager.audit(AuditElements.EventCategoryType.PUSH,
+                        any.getType().getKind().name().toLowerCase(),
+                        profile.getTask().getResource().getKey(),
+                        operation,
+                        resultStatus,
+                        connObjectUtils.getConnObjectTO(beforeObj),
+                        output instanceof ConnectorObject
+                                ? connObjectUtils.getConnObjectTO((ConnectorObject) output) : output,
+                        any);
+            }
+        }
+    }
+
+    private ResourceOperation getResourceOperation(final UnmatchingRule rule) {
+        switch (rule) {
+            case ASSIGN:
+            case PROVISION:
+                return ResourceOperation.CREATE;
+            default:
+                return ResourceOperation.NONE;
+        }
+    }
+
+    private ResourceOperation getResourceOperation(final MatchingRule rule) {
+        switch (rule) {
+            case UPDATE:
+                return ResourceOperation.UPDATE;
+            case DEPROVISION:
+            case UNASSIGN:
+                return ResourceOperation.DELETE;
+            default:
+                return ResourceOperation.NONE;
+        }
+    }
+
+    protected Any<?> update(final Any<?> any, final Boolean enabled) {
+        boolean changepwd;
+        Collection<String> resourceNames;
+        if (any instanceof User) {
+            changepwd = true;
+            resourceNames = userDAO.findAllResourceNames((User) any);
+        } else if (any instanceof AnyObject) {
+            changepwd = false;
+            resourceNames = anyObjectDAO.findAllResourceNames((AnyObject) any);
+        } else {
+            changepwd = false;
+            resourceNames = ((Group) any).getResourceNames();
+        }
+
+        List<String> noPropResources = new ArrayList<>(resourceNames);
+        noPropResources.remove(profile.getTask().getResource().getKey());
+
+        PropagationByResource propByRes = new PropagationByResource();
+        propByRes.add(ResourceOperation.CREATE, profile.getTask().getResource().getKey());
+
+        taskExecutor.execute(propagationManager.getUpdateTasks(
+                any.getType().getKind(),
+                any.getKey(),
+                changepwd,
+                null,
+                propByRes,
+                null,
+                noPropResources));
+
+        return getAny(any.getKey());
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/syncpull/AbstractSyncResultHandler.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/syncpull/AbstractSyncResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/syncpull/AbstractSyncResultHandler.java
new file mode 100644
index 0000000..42762f2
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/syncpull/AbstractSyncResultHandler.java
@@ -0,0 +1,797 @@
+/*
+ * 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.syncpull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.apache.syncope.common.lib.patch.AnyPatch;
+import org.apache.syncope.common.lib.patch.StringPatchItem;
+import org.apache.syncope.common.lib.to.AnyTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditElements.Result;
+import org.apache.syncope.common.lib.types.MatchingRule;
+import org.apache.syncope.common.lib.types.PatchOperation;
+import org.apache.syncope.common.lib.types.PropagationByResource;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.common.lib.types.UnmatchingRule;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.entity.task.SyncTask;
+import org.apache.syncope.core.provisioning.api.propagation.PropagationException;
+import org.apache.syncope.core.provisioning.api.syncpull.SyncActions;
+import org.apache.syncope.core.spring.security.DelegatedAdministrationException;
+import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
+import org.apache.syncope.core.persistence.api.entity.AnyUtils;
+import org.apache.syncope.core.persistence.api.entity.VirSchema;
+import org.apache.syncope.core.persistence.api.entity.resource.Provision;
+import org.apache.syncope.core.provisioning.api.ProvisioningManager;
+import org.apache.syncope.core.provisioning.api.cache.VirAttrCache;
+import org.apache.syncope.core.provisioning.api.cache.VirAttrCacheValue;
+import org.apache.syncope.core.provisioning.api.syncpull.IgnoreProvisionException;
+import org.apache.syncope.core.provisioning.api.syncpull.ProvisioningReport;
+import org.apache.syncope.core.provisioning.api.syncpull.SyncopeSyncResultHandler;
+import org.identityconnectors.framework.common.objects.Attribute;
+import org.identityconnectors.framework.common.objects.SyncDelta;
+import org.identityconnectors.framework.common.objects.SyncDeltaType;
+import org.quartz.JobExecutionException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional(rollbackFor = Throwable.class)
+public abstract class AbstractSyncResultHandler extends AbstractSyncopeResultHandler<SyncTask, SyncActions>
+        implements SyncopeSyncResultHandler {
+
+    @Autowired
+    protected SyncUtils syncUtilities;
+
+    @Autowired
+    protected VirSchemaDAO virSchemaDAO;
+
+    @Autowired
+    protected VirAttrCache virAttrCache;
+
+    protected abstract String getName(AnyTO anyTO);
+
+    protected abstract ProvisioningManager<?, ?> getProvisioningManager();
+
+    protected abstract AnyTO doCreate(AnyTO anyTO, SyncDelta delta, ProvisioningReport result);
+
+    protected AnyTO doLink(final AnyTO before, final boolean unlink) {
+        AnyPatch patch = newPatch(before.getKey());
+        patch.setKey(before.getKey());
+        patch.getResources().add(new StringPatchItem.Builder().
+                operation(unlink ? PatchOperation.DELETE : PatchOperation.ADD_REPLACE).
+                value(profile.getTask().getResource().getKey()).build());
+
+        return getAnyTO(update(patch).getResult());
+    }
+
+    protected abstract AnyTO doUpdate(AnyTO before, AnyPatch anyPatch, SyncDelta delta, ProvisioningReport result);
+
+    protected void doDeprovision(final AnyTypeKind kind, final Long key, final boolean unlink) {
+        PropagationByResource propByRes = new PropagationByResource();
+        propByRes.add(ResourceOperation.DELETE, profile.getTask().getResource().getKey());
+        taskExecutor.execute(propagationManager.getDeleteTasks(
+                kind,
+                key,
+                propByRes,
+                null));
+
+        if (unlink) {
+            AnyPatch anyObjectPatch = newPatch(key);
+            anyObjectPatch.getResources().add(new StringPatchItem.Builder().
+                    operation(PatchOperation.DELETE).
+                    value(profile.getTask().getResource().getKey()).build());
+        }
+    }
+
+    protected void doDelete(final AnyTypeKind kind, final Long key) {
+        PropagationByResource propByRes = new PropagationByResource();
+        propByRes.add(ResourceOperation.DELETE, profile.getTask().getResource().getKey());
+        try {
+            taskExecutor.execute(propagationManager.getDeleteTasks(
+                    kind,
+                    key,
+                    propByRes,
+                    null));
+        } catch (Exception e) {
+            // A propagation failure doesn't imply a synchronization failure.
+            // The propagation exception status will be reported into the propagation task execution.
+            LOG.error("Could not propagate anyObject " + key, e);
+        }
+
+        getProvisioningManager().delete(key, true);
+    }
+
+    @Override
+    public boolean handle(final SyncDelta delta) {
+        Provision provision = null;
+        try {
+            provision = profile.getTask().getResource().getProvision(delta.getObject().getObjectClass());
+            if (provision == null) {
+                throw new JobExecutionException("No provision found on " + profile.getTask().getResource() + " for "
+                        + delta.getObject().getObjectClass());
+            }
+
+            doHandle(delta, provision);
+            return true;
+        } catch (IgnoreProvisionException e) {
+            ProvisioningReport result = new ProvisioningReport();
+            result.setOperation(ResourceOperation.NONE);
+            result.setAnyType(provision == null
+                    ? getAnyUtils().getAnyTypeKind().name() : provision.getAnyType().getKey());
+            result.setStatus(ProvisioningReport.Status.IGNORE);
+            result.setKey(0L);
+            result.setName(delta.getObject().getName().getNameValue());
+            profile.getResults().add(result);
+
+            LOG.warn("Ignoring during synchronization", e);
+            return true;
+        } catch (JobExecutionException e) {
+            LOG.error("Synchronization failed", e);
+            return false;
+        }
+    }
+
+    protected List<ProvisioningReport> assign(
+            final SyncDelta delta, final Provision provision, final AnyUtils anyUtils)
+            throws JobExecutionException {
+
+        if (!profile.getTask().isPerformCreate()) {
+            LOG.debug("SyncTask not configured for create");
+            return Collections.<ProvisioningReport>emptyList();
+        }
+
+        AnyTO anyTO = connObjectUtils.getAnyTO(delta.getObject(), profile.getTask(), provision, anyUtils);
+
+        anyTO.getResources().add(profile.getTask().getResource().getKey());
+
+        ProvisioningReport result = new ProvisioningReport();
+        result.setOperation(ResourceOperation.CREATE);
+        result.setAnyType(provision.getAnyType().getKey());
+        result.setStatus(ProvisioningReport.Status.SUCCESS);
+        result.setName(getName(anyTO));
+
+        if (profile.isDryRun()) {
+            result.setKey(0L);
+        } else {
+            SyncDelta actionedDelta = delta;
+            for (SyncActions action : profile.getActions()) {
+                actionedDelta = action.beforeAssign(this.getProfile(), actionedDelta, anyTO);
+            }
+
+            create(anyTO, actionedDelta, UnmatchingRule.toEventName(UnmatchingRule.ASSIGN), result);
+        }
+
+        return Collections.singletonList(result);
+    }
+
+    protected List<ProvisioningReport> provision(
+            final SyncDelta delta, final Provision provision, final AnyUtils anyUtils)
+            throws JobExecutionException {
+
+        if (!profile.getTask().isPerformCreate()) {
+            LOG.debug("SyncTask not configured for create");
+            return Collections.<ProvisioningReport>emptyList();
+        }
+
+        AnyTO anyTO = connObjectUtils.getAnyTO(delta.getObject(), profile.getTask(), provision, anyUtils);
+
+        ProvisioningReport result = new ProvisioningReport();
+        result.setOperation(ResourceOperation.CREATE);
+        result.setAnyType(provision.getAnyType().getKey());
+        result.setStatus(ProvisioningReport.Status.SUCCESS);
+        result.setName(getName(anyTO));
+
+        if (profile.isDryRun()) {
+            result.setKey(0L);
+        } else {
+            SyncDelta actionedDelta = delta;
+            for (SyncActions action : profile.getActions()) {
+                actionedDelta = action.beforeProvision(this.getProfile(), actionedDelta, anyTO);
+            }
+
+            create(anyTO, actionedDelta, UnmatchingRule.toEventName(UnmatchingRule.PROVISION), result);
+        }
+
+        return Collections.singletonList(result);
+    }
+
+    private void create(
+            final AnyTO anyTO,
+            final SyncDelta delta,
+            final String operation,
+            final ProvisioningReport result)
+            throws JobExecutionException {
+
+        Object output;
+        Result resultStatus;
+
+        try {
+            AnyTO actual = doCreate(anyTO, delta, result);
+            result.setName(getName(actual));
+            output = actual;
+            resultStatus = Result.SUCCESS;
+
+            for (SyncActions action : profile.getActions()) {
+                action.after(this.getProfile(), delta, actual, result);
+            }
+        } catch (IgnoreProvisionException e) {
+            throw e;
+        } catch (PropagationException e) {
+            // A propagation failure doesn't imply a synchronization failure.
+            // The propagation exception status will be reported into the propagation task execution.
+            LOG.error("Could not propagate {} {}", anyTO.getType(), delta.getUid().getUidValue(), e);
+            output = e;
+            resultStatus = Result.FAILURE;
+
+            for (SyncActions action : profile.getActions()) {
+                action.onError(this.getProfile(), delta, result, e);
+            }
+        } catch (Exception e) {
+            result.setStatus(ProvisioningReport.Status.FAILURE);
+            result.setMessage(ExceptionUtils.getRootCauseMessage(e));
+            LOG.error("Could not create {} {} ", anyTO.getType(), delta.getUid().getUidValue(), e);
+            output = e;
+            resultStatus = Result.FAILURE;
+
+            for (SyncActions action : profile.getActions()) {
+                action.onError(this.getProfile(), delta, result, e);
+            }
+        }
+
+        audit(operation, resultStatus, null, output, delta);
+    }
+
+    protected List<ProvisioningReport> update(final SyncDelta delta, final List<Long> anys,
+            final Provision provision) throws JobExecutionException {
+
+        if (!profile.getTask().isPerformUpdate()) {
+            LOG.debug("SyncTask not configured for update");
+            return Collections.<ProvisioningReport>emptyList();
+        }
+
+        LOG.debug("About to update {}", anys);
+
+        List<ProvisioningReport> results = new ArrayList<>();
+
+        SyncDelta workingDelta = delta;
+        for (Long key : anys) {
+            LOG.debug("About to update {}", key);
+
+            ProvisioningReport result = new ProvisioningReport();
+            result.setOperation(ResourceOperation.UPDATE);
+            result.setAnyType(provision.getAnyType().getKey());
+            result.setStatus(ProvisioningReport.Status.SUCCESS);
+            result.setKey(key);
+
+            AnyTO before = getAnyTO(key);
+            if (before == null) {
+                result.setStatus(ProvisioningReport.Status.FAILURE);
+                result.setMessage(String.format("Any '%s(%d)' not found", provision.getAnyType().getKey(), key));
+            } else {
+                result.setName(getName(before));
+            }
+
+            Result resultStatus;
+            Object output;
+            if (!profile.isDryRun()) {
+                if (before == null) {
+                    resultStatus = Result.FAILURE;
+                    output = null;
+                } else {
+                    try {
+                        AnyPatch anyPatch = connObjectUtils.getAnyPatch(
+                                before.getKey(),
+                                workingDelta.getObject(),
+                                before,
+                                profile.getTask(),
+                                provision,
+                                getAnyUtils());
+
+                        for (SyncActions action : profile.getActions()) {
+                            workingDelta = action.beforeUpdate(this.getProfile(), workingDelta, before, anyPatch);
+                        }
+
+                        AnyTO updated = doUpdate(before, anyPatch, workingDelta, result);
+
+                        for (SyncActions action : profile.getActions()) {
+                            action.after(this.getProfile(), workingDelta, updated, result);
+                        }
+
+                        output = updated;
+                        resultStatus = Result.SUCCESS;
+                        result.setName(getName(updated));
+                        LOG.debug("{} {} successfully updated", provision.getAnyType().getKey(), key);
+                    } catch (IgnoreProvisionException e) {
+                        throw e;
+                    } catch (PropagationException e) {
+                        // A propagation failure doesn't imply a synchronization failure.
+                        // The propagation exception status will be reported into the propagation task execution.
+                        LOG.error("Could not propagate {} {}",
+                                provision.getAnyType().getKey(), workingDelta.getUid().getUidValue(), e);
+                        output = e;
+                        resultStatus = Result.FAILURE;
+
+                        for (SyncActions action : profile.getActions()) {
+                            action.onError(this.getProfile(), workingDelta, result, e);
+                        }
+                    } catch (Exception e) {
+                        result.setStatus(ProvisioningReport.Status.FAILURE);
+                        result.setMessage(ExceptionUtils.getRootCauseMessage(e));
+                        LOG.error("Could not update {} {}",
+                                provision.getAnyType().getKey(), workingDelta.getUid().getUidValue(), e);
+                        output = e;
+                        resultStatus = Result.FAILURE;
+
+                        for (SyncActions action : profile.getActions()) {
+                            action.onError(this.getProfile(), workingDelta, result, e);
+                        }
+                    }
+                }
+                audit(MatchingRule.toEventName(MatchingRule.UPDATE), resultStatus, before, output, workingDelta);
+            }
+            results.add(result);
+        }
+        return results;
+    }
+
+    protected List<ProvisioningReport> deprovision(
+            final SyncDelta delta,
+            final List<Long> anys,
+            final Provision provision,
+            final boolean unlink)
+            throws JobExecutionException {
+
+        if (!profile.getTask().isPerformUpdate()) {
+            LOG.debug("SyncTask not configured for update");
+            return Collections.<ProvisioningReport>emptyList();
+        }
+
+        LOG.debug("About to update {}", anys);
+
+        final List<ProvisioningReport> updResults = new ArrayList<>();
+
+        for (Long key : anys) {
+            LOG.debug("About to unassign resource {}", key);
+
+            Object output;
+            Result resultStatus;
+
+            ProvisioningReport result = new ProvisioningReport();
+            result.setOperation(ResourceOperation.DELETE);
+            result.setAnyType(provision.getAnyType().getKey());
+            result.setStatus(ProvisioningReport.Status.SUCCESS);
+            result.setKey(key);
+
+            AnyTO before = getAnyTO(key);
+
+            if (before == null) {
+                result.setStatus(ProvisioningReport.Status.FAILURE);
+                result.setMessage(String.format("Any '%s(%d)' not found", provision.getAnyType().getKey(), key));
+            }
+
+            if (!profile.isDryRun()) {
+                if (before == null) {
+                    resultStatus = Result.FAILURE;
+                    output = null;
+                } else {
+                    result.setName(getName(before));
+
+                    try {
+                        if (unlink) {
+                            for (SyncActions action : profile.getActions()) {
+                                action.beforeUnassign(this.getProfile(), delta, before);
+                            }
+                        } else {
+                            for (SyncActions action : profile.getActions()) {
+                                action.beforeDeprovision(this.getProfile(), delta, before);
+                            }
+                        }
+
+                        doDeprovision(provision.getAnyType().getKind(), key, unlink);
+                        output = getAnyTO(key);
+
+                        for (SyncActions action : profile.getActions()) {
+                            action.after(this.getProfile(), delta, AnyTO.class.cast(output), result);
+                        }
+
+                        resultStatus = Result.SUCCESS;
+                        LOG.debug("{} {} successfully updated", provision.getAnyType().getKey(), key);
+                    } catch (IgnoreProvisionException e) {
+                        throw e;
+                    } catch (PropagationException e) {
+                        // A propagation failure doesn't imply a synchronization failure.
+                        // The propagation exception status will be reported into the propagation task execution.
+                        LOG.error("Could not propagate {} {}",
+                                provision.getAnyType().getKey(), delta.getUid().getUidValue(), e);
+                        output = e;
+                        resultStatus = Result.FAILURE;
+
+                        for (SyncActions action : profile.getActions()) {
+                            action.onError(this.getProfile(), delta, result, e);
+                        }
+                    } catch (Exception e) {
+                        result.setStatus(ProvisioningReport.Status.FAILURE);
+                        result.setMessage(ExceptionUtils.getRootCauseMessage(e));
+                        LOG.error("Could not update {} {}",
+                                provision.getAnyType().getKey(), delta.getUid().getUidValue(), e);
+                        output = e;
+                        resultStatus = Result.FAILURE;
+
+                        for (SyncActions action : profile.getActions()) {
+                            action.onError(this.getProfile(), delta, result, e);
+                        }
+                    }
+                }
+                audit(unlink
+                        ? MatchingRule.toEventName(MatchingRule.UNASSIGN)
+                        : MatchingRule.toEventName(MatchingRule.DEPROVISION), resultStatus, before, output, delta);
+            }
+            updResults.add(result);
+        }
+
+        return updResults;
+    }
+
+    protected List<ProvisioningReport> link(
+            final SyncDelta delta,
+            final List<Long> anys,
+            final Provision provision,
+            final boolean unlink)
+            throws JobExecutionException {
+
+        if (!profile.getTask().isPerformUpdate()) {
+            LOG.debug("SyncTask not configured for update");
+            return Collections.<ProvisioningReport>emptyList();
+        }
+
+        LOG.debug("About to update {}", anys);
+
+        final List<ProvisioningReport> updResults = new ArrayList<>();
+
+        for (Long key : anys) {
+            LOG.debug("About to unassign resource {}", key);
+
+            Object output;
+            Result resultStatus;
+
+            ProvisioningReport result = new ProvisioningReport();
+            result.setOperation(ResourceOperation.NONE);
+            result.setAnyType(provision.getAnyType().getKey());
+            result.setStatus(ProvisioningReport.Status.SUCCESS);
+            result.setKey(key);
+
+            AnyTO before = getAnyTO(key);
+
+            if (before == null) {
+                result.setStatus(ProvisioningReport.Status.FAILURE);
+                result.setMessage(String.format("Any '%s(%d)' not found", provision.getAnyType().getKey(), key));
+            }
+
+            if (!profile.isDryRun()) {
+                if (before == null) {
+                    resultStatus = Result.FAILURE;
+                    output = null;
+                } else {
+                    result.setName(getName(before));
+
+                    try {
+                        if (unlink) {
+                            for (SyncActions action : profile.getActions()) {
+                                action.beforeUnlink(this.getProfile(), delta, before);
+                            }
+                        } else {
+                            for (SyncActions action : profile.getActions()) {
+                                action.beforeLink(this.getProfile(), delta, before);
+                            }
+                        }
+
+                        output = doLink(before, unlink);
+
+                        for (SyncActions action : profile.getActions()) {
+                            action.after(this.getProfile(), delta, AnyTO.class.cast(output), result);
+                        }
+
+                        resultStatus = Result.SUCCESS;
+                        LOG.debug("{} {} successfully updated", provision.getAnyType().getKey(), key);
+                    } catch (IgnoreProvisionException e) {
+                        throw e;
+                    } catch (PropagationException e) {
+                        // A propagation failure doesn't imply a synchronization failure.
+                        // The propagation exception status will be reported into the propagation task execution.
+                        LOG.error("Could not propagate {} {}",
+                                provision.getAnyType().getKey(), delta.getUid().getUidValue(), e);
+                        output = e;
+                        resultStatus = Result.FAILURE;
+
+                        for (SyncActions action : profile.getActions()) {
+                            action.onError(this.getProfile(), delta, result, e);
+                        }
+                    } catch (Exception e) {
+                        result.setStatus(ProvisioningReport.Status.FAILURE);
+                        result.setMessage(ExceptionUtils.getRootCauseMessage(e));
+                        LOG.error("Could not update {} {}",
+                                provision.getAnyType().getKey(), delta.getUid().getUidValue(), e);
+                        output = e;
+                        resultStatus = Result.FAILURE;
+
+                        for (SyncActions action : profile.getActions()) {
+                            action.onError(this.getProfile(), delta, result, e);
+                        }
+                    }
+                }
+                audit(unlink ? MatchingRule.toEventName(MatchingRule.UNLINK)
+                        : MatchingRule.toEventName(MatchingRule.LINK), resultStatus, before, output, delta);
+            }
+            updResults.add(result);
+        }
+
+        return updResults;
+    }
+
+    protected List<ProvisioningReport> delete(
+            final SyncDelta delta,
+            final List<Long> anys,
+            final Provision provision)
+            throws JobExecutionException {
+
+        if (!profile.getTask().isPerformDelete()) {
+            LOG.debug("SyncTask not configured for delete");
+            return Collections.<ProvisioningReport>emptyList();
+        }
+
+        LOG.debug("About to delete {}", anys);
+
+        List<ProvisioningReport> delResults = new ArrayList<>();
+
+        SyncDelta workingDelta = delta;
+        for (Long key : anys) {
+            Object output;
+            Result resultStatus = Result.FAILURE;
+
+            ProvisioningReport result = new ProvisioningReport();
+
+            try {
+                AnyTO before = getAnyTO(key);
+
+                result.setKey(key);
+                result.setName(getName(before));
+                result.setOperation(ResourceOperation.DELETE);
+                result.setAnyType(provision.getAnyType().getKey());
+                result.setStatus(ProvisioningReport.Status.SUCCESS);
+
+                if (!profile.isDryRun()) {
+                    for (SyncActions action : profile.getActions()) {
+                        workingDelta = action.beforeDelete(this.getProfile(), workingDelta, before);
+                    }
+
+                    try {
+                        doDelete(provision.getAnyType().getKind(), key);
+                        output = null;
+                        resultStatus = Result.SUCCESS;
+
+                        for (SyncActions action : profile.getActions()) {
+                            action.after(this.getProfile(), workingDelta, before, result);
+                        }
+                    } catch (IgnoreProvisionException e) {
+                        throw e;
+                    } catch (Exception e) {
+                        result.setStatus(ProvisioningReport.Status.FAILURE);
+                        result.setMessage(ExceptionUtils.getRootCauseMessage(e));
+                        LOG.error("Could not delete {} {}", provision.getAnyType().getKey(), key, e);
+                        output = e;
+
+                        for (SyncActions action : profile.getActions()) {
+                            action.onError(this.getProfile(), workingDelta, result, e);
+                        }
+                    }
+
+                    audit(ResourceOperation.DELETE.name().toLowerCase(), resultStatus, before, output, workingDelta);
+                }
+
+                delResults.add(result);
+            } catch (NotFoundException e) {
+                LOG.error("Could not find {} {}", provision.getAnyType().getKey(), key, e);
+            } catch (DelegatedAdministrationException e) {
+                LOG.error("Not allowed to read {} {}", provision.getAnyType().getKey(), key, e);
+            } catch (Exception e) {
+                LOG.error("Could not delete {} {}", provision.getAnyType().getKey(), key, e);
+            }
+        }
+
+        return delResults;
+    }
+
+    private List<ProvisioningReport> ignore(
+            final SyncDelta delta,
+            final Provision provision,
+            final boolean matching)
+            throws JobExecutionException {
+
+        LOG.debug("Any to ignore {}", delta.getObject().getUid().getUidValue());
+
+        final List<ProvisioningReport> ignoreResults = new ArrayList<>();
+        ProvisioningReport result = new ProvisioningReport();
+
+        result.setKey(null);
+        result.setName(delta.getObject().getUid().getUidValue());
+        result.setOperation(ResourceOperation.NONE);
+        result.setAnyType(provision.getAnyType().getKey());
+        result.setStatus(ProvisioningReport.Status.SUCCESS);
+        ignoreResults.add(result);
+
+        if (!profile.isDryRun()) {
+            audit(matching
+                    ? MatchingRule.toEventName(MatchingRule.IGNORE)
+                    : UnmatchingRule.toEventName(UnmatchingRule.IGNORE), Result.SUCCESS, null, null, delta);
+        }
+
+        return ignoreResults;
+    }
+
+    /**
+     * Look into SyncDelta and take necessary profile.getActions() (create / update / delete) on any object(s).
+     *
+     * @param delta returned by the underlying profile.getConnector()
+     * @param provision provisioning info
+     * @throws JobExecutionException in case of synchronization failure.
+     */
+    protected void doHandle(final SyncDelta delta, final Provision provision) throws JobExecutionException {
+        AnyUtils anyUtils = getAnyUtils();
+
+        LOG.debug("Process {} for {} as {}",
+                delta.getDeltaType(), delta.getUid().getUidValue(), delta.getObject().getObjectClass());
+
+        String uid = delta.getPreviousUid() == null
+                ? delta.getUid().getUidValue()
+                : delta.getPreviousUid().getUidValue();
+
+        try {
+            List<Long> anyKeys = syncUtilities.findExisting(uid, delta.getObject(), provision, anyUtils);
+            LOG.debug("Match(es) found for {} as {}: {}",
+                    delta.getUid().getUidValue(), delta.getObject().getObjectClass(), anyKeys);
+
+            if (anyKeys.size() > 1) {
+                switch (profile.getResAct()) {
+                    case IGNORE:
+                        throw new IllegalStateException("More than one match " + anyKeys);
+
+                    case FIRSTMATCH:
+                        anyKeys = anyKeys.subList(0, 1);
+                        break;
+
+                    case LASTMATCH:
+                        anyKeys = anyKeys.subList(anyKeys.size() - 1, anyKeys.size());
+                        break;
+
+                    default:
+                    // keep anyKeys unmodified
+                }
+            }
+
+            if (SyncDeltaType.CREATE_OR_UPDATE == delta.getDeltaType()) {
+                if (anyKeys.isEmpty()) {
+                    switch (profile.getTask().getUnmatchingRule()) {
+                        case ASSIGN:
+                            profile.getResults().addAll(assign(delta, provision, anyUtils));
+                            break;
+
+                        case PROVISION:
+                            profile.getResults().addAll(provision(delta, provision, anyUtils));
+                            break;
+
+                        case IGNORE:
+                            profile.getResults().addAll(ignore(delta, provision, false));
+                            break;
+
+                        default:
+                        // do nothing
+                    }
+                } else {
+                    // update VirAttrCache
+                    for (VirSchema virSchema : virSchemaDAO.findByProvision(provision)) {
+                        Attribute attr = delta.getObject().getAttributeByName(virSchema.getExtAttrName());
+                        for (Long anyKey : anyKeys) {
+                            if (attr == null) {
+                                virAttrCache.expire(
+                                        provision.getAnyType().getKey(),
+                                        anyKey,
+                                        virSchema.getKey());
+                            } else {
+                                VirAttrCacheValue cacheValue = new VirAttrCacheValue();
+                                cacheValue.setValues(attr.getValue());
+                                virAttrCache.put(
+                                        provision.getAnyType().getKey(),
+                                        anyKey,
+                                        virSchema.getKey(),
+                                        cacheValue);
+                            }
+                        }
+                    }
+
+                    switch (profile.getTask().getMatchingRule()) {
+                        case UPDATE:
+                            profile.getResults().addAll(update(delta, anyKeys, provision));
+                            break;
+
+                        case DEPROVISION:
+                            profile.getResults().addAll(deprovision(delta, anyKeys, provision, false));
+                            break;
+
+                        case UNASSIGN:
+                            profile.getResults().addAll(deprovision(delta, anyKeys, provision, true));
+                            break;
+
+                        case LINK:
+                            profile.getResults().addAll(link(delta, anyKeys, provision, false));
+                            break;
+
+                        case UNLINK:
+                            profile.getResults().addAll(link(delta, anyKeys, provision, true));
+                            break;
+
+                        case IGNORE:
+                            profile.getResults().addAll(ignore(delta, provision, true));
+                            break;
+
+                        default:
+                        // do nothing
+                    }
+                }
+            } else if (SyncDeltaType.DELETE == delta.getDeltaType()) {
+                if (anyKeys.isEmpty()) {
+                    LOG.debug("No match found for deletion");
+                } else {
+                    profile.getResults().addAll(delete(delta, anyKeys, provision));
+                }
+            }
+        } catch (IllegalStateException | IllegalArgumentException e) {
+            LOG.warn(e.getMessage());
+        }
+    }
+
+    private void audit(
+            final String event,
+            final Result result,
+            final Object before,
+            final Object output,
+            final Object... input) {
+
+        notificationManager.createTasks(AuditElements.EventCategoryType.SYNCHRONIZATION,
+                getAnyUtils().getAnyTypeKind().name().toLowerCase(),
+                profile.getTask().getResource().getKey(),
+                event,
+                result,
+                before,
+                output,
+                input);
+
+        auditManager.audit(AuditElements.EventCategoryType.SYNCHRONIZATION,
+                getAnyUtils().getAnyTypeKind().name().toLowerCase(),
+                profile.getTask().getResource().getKey(),
+                event,
+                result,
+                before,
+                output,
+                input);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/syncpull/AbstractSyncopeResultHandler.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/syncpull/AbstractSyncopeResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/syncpull/AbstractSyncopeResultHandler.java
new file mode 100644
index 0000000..bf2cd6e
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/syncpull/AbstractSyncopeResultHandler.java
@@ -0,0 +1,155 @@
+/*
+ * 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.syncpull;
+
+import org.apache.syncope.common.lib.patch.AnyPatch;
+import org.apache.syncope.common.lib.to.AnyTO;
+import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.entity.task.ProvisioningTask;
+import org.apache.syncope.core.provisioning.api.GroupProvisioningManager;
+import org.apache.syncope.core.provisioning.api.data.GroupDataBinder;
+import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
+import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
+import org.apache.syncope.core.provisioning.api.propagation.PropagationManager;
+import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
+import org.apache.syncope.core.provisioning.api.syncpull.ProvisioningProfile;
+import org.apache.syncope.core.provisioning.api.syncpull.SyncopeResultHandler;
+import org.apache.syncope.core.provisioning.java.utils.ConnObjectUtils;
+import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
+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.provisioning.api.AnyObjectProvisioningManager;
+import org.apache.syncope.core.provisioning.api.AuditManager;
+import org.apache.syncope.core.provisioning.api.WorkflowResult;
+import org.apache.syncope.core.provisioning.api.data.AnyObjectDataBinder;
+import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
+import org.apache.syncope.core.provisioning.api.syncpull.ProvisioningActions;
+import org.apache.syncope.core.workflow.api.AnyObjectWorkflowAdapter;
+import org.apache.syncope.core.workflow.api.GroupWorkflowAdapter;
+import org.apache.syncope.core.workflow.api.UserWorkflowAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public abstract class AbstractSyncopeResultHandler<T extends ProvisioningTask, A extends ProvisioningActions>
+        implements SyncopeResultHandler<T, A> {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(SyncopeResultHandler.class);
+
+    @Autowired
+    protected AnyObjectDAO anyObjectDAO;
+
+    @Autowired
+    protected UserDAO userDAO;
+
+    @Autowired
+    protected GroupDAO groupDAO;
+
+    /**
+     * ConnectorObject utils.
+     */
+    @Autowired
+    protected ConnObjectUtils connObjectUtils;
+
+    /**
+     * Notification Manager.
+     */
+    @Autowired
+    protected NotificationManager notificationManager;
+
+    /**
+     * Audit Manager.
+     */
+    @Autowired
+    protected AuditManager auditManager;
+
+    /**
+     * Propagation manager.
+     */
+    @Autowired
+    protected PropagationManager propagationManager;
+
+    /**
+     * Task executor.
+     */
+    @Autowired
+    protected PropagationTaskExecutor taskExecutor;
+
+    protected AnyObjectWorkflowAdapter awfAdapter;
+
+    /**
+     * User workflow adapter.
+     */
+    @Autowired
+    protected UserWorkflowAdapter uwfAdapter;
+
+    /**
+     * Group workflow adapter.
+     */
+    @Autowired
+    protected GroupWorkflowAdapter gwfAdapter;
+
+    @Autowired
+    protected AnyObjectDataBinder anyObjectDataBinder;
+
+    @Autowired
+    protected UserDataBinder userDataBinder;
+
+    @Autowired
+    protected GroupDataBinder groupDataBinder;
+
+    @Autowired
+    protected AnyObjectProvisioningManager anyObjectProvisioningManager;
+
+    @Autowired
+    protected UserProvisioningManager userProvisioningManager;
+
+    @Autowired
+    protected GroupProvisioningManager groupProvisioningManager;
+
+    @Autowired
+    protected AnyUtilsFactory anyUtilsFactory;
+
+    /**
+     * Sync profile.
+     */
+    protected ProvisioningProfile<T, A> profile;
+
+    protected abstract AnyUtils getAnyUtils();
+
+    protected abstract AnyTO getAnyTO(long key);
+
+    protected abstract Any<?> getAny(long key);
+
+    protected abstract AnyPatch newPatch(final long key);
+
+    protected abstract WorkflowResult<Long> update(AnyPatch patch);
+
+    @Override
+    public void setProfile(final ProvisioningProfile<T, A> profile) {
+        this.profile = profile;
+    }
+
+    @Override
+    public ProvisioningProfile<T, A> getProfile() {
+        return profile;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/syncpull/AnyObjectPushResultHandlerImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/syncpull/AnyObjectPushResultHandlerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/syncpull/AnyObjectPushResultHandlerImpl.java
new file mode 100644
index 0000000..6f83727
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/syncpull/AnyObjectPushResultHandlerImpl.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java.syncpull;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.patch.AnyObjectPatch;
+import org.apache.syncope.common.lib.patch.AnyPatch;
+import org.apache.syncope.common.lib.to.AnyTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.core.persistence.api.entity.Any;
+import org.apache.syncope.core.persistence.api.entity.AnyUtils;
+import org.apache.syncope.core.provisioning.api.WorkflowResult;
+import org.apache.syncope.core.provisioning.api.syncpull.AnyObjectPushResultHandler;
+
+public class AnyObjectPushResultHandlerImpl extends AbstractPushResultHandler implements AnyObjectPushResultHandler {
+
+    @Override
+    protected AnyUtils getAnyUtils() {
+        return anyUtilsFactory.getInstance(AnyTypeKind.ANY_OBJECT);
+    }
+
+    @Override
+    protected String getName(final Any<?> any) {
+        return StringUtils.EMPTY;
+    }
+
+    @Override
+    protected Any<?> getAny(final long key) {
+        try {
+            return anyObjectDAO.authFind(key);
+        } catch (Exception e) {
+            LOG.warn("Error retrieving anyObject {}", key, e);
+            return null;
+        }
+    }
+
+    @Override
+    protected AnyTO getAnyTO(final long key) {
+        return anyObjectDataBinder.getAnyObjectTO(key);
+    }
+
+    @Override
+    protected AnyPatch newPatch(final long key) {
+        AnyObjectPatch patch = new AnyObjectPatch();
+        patch.setKey(key);
+        return patch;
+    }
+
+    @Override
+    protected WorkflowResult<Long> update(final AnyPatch patch) {
+        return awfAdapter.update((AnyObjectPatch) patch);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/28569df5/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/syncpull/AnyObjectSyncResultHandlerImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/syncpull/AnyObjectSyncResultHandlerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/syncpull/AnyObjectSyncResultHandlerImpl.java
new file mode 100644
index 0000000..a8c6c1b
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/syncpull/AnyObjectSyncResultHandlerImpl.java
@@ -0,0 +1,112 @@
+/*
+ * 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.syncpull;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.patch.AnyObjectPatch;
+import org.apache.syncope.common.lib.patch.AnyPatch;
+import org.apache.syncope.common.lib.to.AnyTO;
+import org.apache.syncope.common.lib.to.PropagationStatus;
+import org.apache.syncope.common.lib.to.AnyObjectTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.core.persistence.api.entity.Any;
+import org.apache.syncope.core.persistence.api.entity.AnyUtils;
+import org.apache.syncope.core.provisioning.api.ProvisioningManager;
+import org.apache.syncope.core.provisioning.api.WorkflowResult;
+import org.apache.syncope.core.provisioning.api.syncpull.ProvisioningReport;
+import org.apache.syncope.core.provisioning.api.syncpull.AnyObjectSyncResultHandler;
+import org.identityconnectors.framework.common.objects.SyncDelta;
+
+public class AnyObjectSyncResultHandlerImpl extends AbstractSyncResultHandler implements AnyObjectSyncResultHandler {
+
+    @Override
+    protected AnyUtils getAnyUtils() {
+        return anyUtilsFactory.getInstance(AnyTypeKind.ANY_OBJECT);
+    }
+
+    @Override
+    protected String getName(final AnyTO anyTO) {
+        return StringUtils.EMPTY;
+    }
+
+    @Override
+    protected ProvisioningManager<?, ?> getProvisioningManager() {
+        return anyObjectProvisioningManager;
+    }
+
+    @Override
+    protected Any<?> getAny(final long key) {
+        try {
+            return anyObjectDAO.authFind(key);
+        } catch (Exception e) {
+            LOG.warn("Error retrieving anyObject {}", key, e);
+            return null;
+        }
+    }
+
+    @Override
+    protected AnyTO getAnyTO(final long key) {
+        return anyObjectDataBinder.getAnyObjectTO(key);
+    }
+
+    @Override
+    protected AnyPatch newPatch(final long key) {
+        AnyObjectPatch patch = new AnyObjectPatch();
+        patch.setKey(key);
+        return patch;
+    }
+
+    @Override
+    protected WorkflowResult<Long> update(final AnyPatch patch) {
+        return awfAdapter.update((AnyObjectPatch) patch);
+    }
+
+    @Override
+    protected AnyTO doCreate(final AnyTO anyTO, final SyncDelta delta, final ProvisioningReport result) {
+        AnyObjectTO anyObjectTO = AnyObjectTO.class.cast(anyTO);
+
+        Map.Entry<Long, List<PropagationStatus>> created = anyObjectProvisioningManager.create(
+                anyObjectTO, Collections.singleton(profile.getTask().getResource().getKey()), true);
+
+        result.setKey(created.getKey());
+        result.setName(getName(anyTO));
+
+        return getAnyTO(created.getKey());
+    }
+
+    @Override
+    protected AnyTO doUpdate(
+            final AnyTO before,
+            final AnyPatch anyPatch,
+            final SyncDelta delta,
+            final ProvisioningReport result) {
+
+        AnyObjectPatch anyObjectPatch = AnyObjectPatch.class.cast(anyPatch);
+
+        Map.Entry<Long, List<PropagationStatus>> updated =
+                anyObjectProvisioningManager.update(anyObjectPatch, true);
+
+        AnyObjectTO after = anyObjectDataBinder.getAnyObjectTO(updated.getKey());
+        result.setName(getName(after));
+        return after;
+    }
+}