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 2017/09/22 08:15:57 UTC

[6/6] syncope git commit: [SYNCOPE-1212] Clearing and refactoring for easier extensions

[SYNCOPE-1212] Clearing and refactoring for easier extensions


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

Branch: refs/heads/master
Commit: 9c289134b3452874d279e83ef300dc8c313d44f9
Parents: 9e24b8e
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Fri Sep 22 10:12:45 2017 +0200
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Fri Sep 22 10:15:38 2017 +0200

----------------------------------------------------------------------
 .../jpa/content/ContentLoaderHandler.java       |   2 +-
 .../entity/task/AbstractProvisioningTask.java   |  10 -
 .../api/pushpull/RealmPullResultHandler.java    |  23 +
 .../api/pushpull/RealmPushResultHandler.java    |  23 +
 .../java/data/TaskDataBinderImpl.java           |   8 +-
 .../provisioning/java/job/JobManagerImpl.java   |  21 +-
 .../pushpull/AbstractPullResultHandler.java     |  14 +-
 .../pushpull/AbstractPushResultHandler.java     |  10 +-
 .../AnyObjectPullResultHandlerImpl.java         | 112 ---
 .../AnyObjectPushResultHandlerImpl.java         |  70 --
 .../DefaultAnyObjectPullResultHandler.java      | 112 +++
 .../DefaultAnyObjectPushResultHandler.java      |  70 ++
 .../pushpull/DefaultGroupPullResultHandler.java | 138 ++++
 .../pushpull/DefaultGroupPushResultHandler.java |  70 ++
 .../pushpull/DefaultRealmPullResultHandler.java | 795 +++++++++++++++++++
 .../pushpull/DefaultRealmPushResultHandler.java | 450 +++++++++++
 .../pushpull/DefaultUserPullResultHandler.java  | 134 ++++
 .../pushpull/DefaultUserPushResultHandler.java  |  99 +++
 .../pushpull/GroupPullResultHandlerImpl.java    | 138 ----
 .../pushpull/GroupPushResultHandlerImpl.java    |  70 --
 .../java/pushpull/PullJobDelegate.java          |  87 +-
 .../java/pushpull/PushJobDelegate.java          |  81 +-
 .../pushpull/RealmPullResultHandlerImpl.java    | 795 -------------------
 .../pushpull/RealmPushResultHandlerImpl.java    | 450 -----------
 .../pushpull/UserPullResultHandlerImpl.java     | 134 ----
 .../pushpull/UserPushResultHandlerImpl.java     |  99 ---
 26 files changed, 2063 insertions(+), 1952 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/9c289134/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/content/ContentLoaderHandler.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/content/ContentLoaderHandler.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/content/ContentLoaderHandler.java
index 72f5967..713fee0 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/content/ContentLoaderHandler.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/content/ContentLoaderHandler.java
@@ -189,7 +189,7 @@ public class ContentLoaderHandler extends DefaultHandler {
         try {
             jdbcTemplate.update(query.toString(), getParameters(qName, atts));
         } catch (DataAccessException e) {
-            LOG.error("While trying to perform {}", query, e);
+            LOG.error("While trying to perform {} with params {}", query, getParameters(qName, atts), e);
             if (!continueOnError) {
                 throw e;
             }

http://git-wip-us.apache.org/repos/asf/syncope/blob/9c289134/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/task/AbstractProvisioningTask.java
----------------------------------------------------------------------
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/task/AbstractProvisioningTask.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/task/AbstractProvisioningTask.java
index 0441eb3..dc35f9d 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/task/AbstractProvisioningTask.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/task/AbstractProvisioningTask.java
@@ -80,16 +80,6 @@ public abstract class AbstractProvisioningTask extends JPASchedTask implements P
     protected MatchingRule matchingRule;
 
     @Override
-    public String getJobDelegateClassName() {
-        return null;
-    }
-
-    @Override
-    public void setJobDelegateClassName(final String jobDelegateClassName) {
-        // fixed, cannot be changed
-    }
-
-    @Override
     public ExternalResource getResource() {
         return resource;
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/9c289134/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/RealmPullResultHandler.java
----------------------------------------------------------------------
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/RealmPullResultHandler.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/RealmPullResultHandler.java
new file mode 100644
index 0000000..6f38692
--- /dev/null
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/RealmPullResultHandler.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+public interface RealmPullResultHandler extends SyncopePullResultHandler {
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/9c289134/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/RealmPushResultHandler.java
----------------------------------------------------------------------
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/RealmPushResultHandler.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/RealmPushResultHandler.java
new file mode 100644
index 0000000..e994213
--- /dev/null
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/pushpull/RealmPushResultHandler.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+public interface RealmPushResultHandler extends SyncopePushResultHandler {
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/9c289134/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/TaskDataBinderImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/TaskDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/TaskDataBinderImpl.java
index 646e950..2066cb2 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/TaskDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/TaskDataBinderImpl.java
@@ -110,7 +110,9 @@ public class TaskDataBinderImpl implements TaskDataBinder {
             PushTask pushTask = (PushTask) task;
             final PushTaskTO pushTaskTO = (PushTaskTO) taskTO;
 
-            pushTask.setJobDelegateClassName(PushJobDelegate.class.getName());
+            pushTask.setJobDelegateClassName(pushTaskTO.getJobDelegateClassName() == null
+                    ? PushJobDelegate.class.getName()
+                    : pushTaskTO.getJobDelegateClassName());
 
             pushTask.setSourceRealm(realmDAO.findByFullPath(pushTaskTO.getSourceRealm()));
 
@@ -148,7 +150,9 @@ public class TaskDataBinderImpl implements TaskDataBinder {
 
             pullTask.setDestinationRealm(realmDAO.findByFullPath(pullTaskTO.getDestinationRealm()));
 
-            pullTask.setJobDelegateClassName(PullJobDelegate.class.getName());
+            pullTask.setJobDelegateClassName(pullTaskTO.getJobDelegateClassName() == null
+                    ? PullJobDelegate.class.getName()
+                    : pullTaskTO.getJobDelegateClassName());
 
             pullTask.setMatchingRule(pullTaskTO.getMatchingRule() == null
                     ? MatchingRule.UPDATE : pullTaskTO.getMatchingRule());

http://git-wip-us.apache.org/repos/asf/syncope/blob/9c289134/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/JobManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/JobManagerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/JobManagerImpl.java
index f82c243..42daca5 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/JobManagerImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/JobManagerImpl.java
@@ -46,8 +46,6 @@ import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.apache.syncope.core.persistence.api.SyncopeLoader;
 import org.apache.syncope.core.persistence.api.DomainsHolder;
-import org.apache.syncope.core.provisioning.java.pushpull.PushJobDelegate;
-import org.apache.syncope.core.provisioning.java.pushpull.PullJobDelegate;
 import org.quartz.CronScheduleBuilder;
 import org.quartz.Job;
 import org.quartz.JobBuilder;
@@ -69,8 +67,11 @@ import org.identityconnectors.common.IOUtil;
 import org.quartz.impl.jdbcjobstore.Constants;
 import org.springframework.jdbc.datasource.DataSourceUtils;
 import org.apache.syncope.core.persistence.api.entity.task.PullTask;
+import org.apache.syncope.core.provisioning.api.job.SchedTaskJobDelegate;
 import org.apache.syncope.core.provisioning.java.job.notification.NotificationJob;
 import org.apache.syncope.core.provisioning.java.job.report.ReportJob;
+import org.apache.syncope.core.provisioning.java.pushpull.PullJobDelegate;
+import org.apache.syncope.core.provisioning.java.pushpull.PushJobDelegate;
 
 public class JobManagerImpl implements JobManager, SyncopeLoader {
 
@@ -214,11 +215,17 @@ public class JobManagerImpl implements JobManager, SyncopeLoader {
         TaskJob job = createSpringBean(TaskJob.class);
         job.setTaskKey(task.getKey());
 
-        String jobDelegateClassName = task instanceof PullTask
-                ? PullJobDelegate.class.getName()
-                : task instanceof PushTask
-                        ? PushJobDelegate.class.getName()
-                        : task.getJobDelegateClassName();
+        String jobDelegateClassName = task.getJobDelegateClassName() == null
+                ? task instanceof PullTask
+                        ? PullJobDelegate.class.getName()
+                        : task instanceof PushTask
+                                ? PushJobDelegate.class.getName()
+                                : null
+                : task.getJobDelegateClassName();
+        if (jobDelegateClassName == null) {
+            throw new IllegalArgumentException("Task " + task
+                    + " does not provide any " + SchedTaskJobDelegate.class.getSimpleName());
+        }
 
         Map<String, Object> jobMap = new HashMap<>();
         jobMap.put(JobManager.DOMAIN_KEY, AuthContextUtils.getDomain());

http://git-wip-us.apache.org/repos/asf/syncope/blob/9c289134/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 b6fe9ac..d545994 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
@@ -69,23 +69,23 @@ public abstract class AbstractPullResultHandler extends AbstractSyncopeResultHan
     protected PullUtils pullUtils;
 
     @Autowired
-    private NotificationManager notificationManager;
+    protected NotificationManager notificationManager;
 
     @Autowired
-    private AuditManager auditManager;
+    protected AuditManager auditManager;
 
     @Autowired
-    private ConnObjectUtils connObjectUtils;
+    protected ConnObjectUtils connObjectUtils;
 
     @Autowired
-    private VirSchemaDAO virSchemaDAO;
+    protected VirSchemaDAO virSchemaDAO;
 
     @Autowired
-    private VirAttrCache virAttrCache;
+    protected VirAttrCache virAttrCache;
 
-    private SyncopePullExecutor executor;
+    protected SyncopePullExecutor executor;
 
-    private Result latestResult;
+    protected Result latestResult;
 
     protected abstract String getName(AnyTO anyTO);
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/9c289134/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 395ec3e..cfbd325 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
@@ -96,7 +96,7 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
         }
     }
 
-    private void update(final Any<?> any, final ProvisioningReport result) {
+    protected void update(final Any<?> any, final ProvisioningReport result) {
         boolean changepwd;
         Collection<String> resourceKeys;
         if (any instanceof User) {
@@ -241,7 +241,7 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
         }
     }
 
-    private void doHandle(final Any<?> any) throws JobExecutionException {
+    protected void doHandle(final Any<?> any) throws JobExecutionException {
         AnyUtils anyUtils = anyUtilsFactory.getInstance(any);
 
         ProvisioningReport result = new ProvisioningReport();
@@ -473,7 +473,7 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
         }
     }
 
-    private ResourceOperation toResourceOperation(final UnmatchingRule rule) {
+    protected ResourceOperation toResourceOperation(final UnmatchingRule rule) {
         switch (rule) {
             case ASSIGN:
             case PROVISION:
@@ -483,7 +483,7 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
         }
     }
 
-    private ResourceOperation toResourceOperation(final MatchingRule rule) {
+    protected ResourceOperation toResourceOperation(final MatchingRule rule) {
         switch (rule) {
             case UPDATE:
                 return ResourceOperation.UPDATE;
@@ -495,7 +495,7 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
         }
     }
 
-    private ProvisioningReport.Status toProvisioningReportStatus(final PropagationTaskExecStatus status) {
+    protected ProvisioningReport.Status toProvisioningReportStatus(final PropagationTaskExecStatus status) {
         switch (status) {
             case FAILURE:
                 return ProvisioningReport.Status.FAILURE;

http://git-wip-us.apache.org/repos/asf/syncope/blob/9c289134/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AnyObjectPullResultHandlerImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AnyObjectPullResultHandlerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AnyObjectPullResultHandlerImpl.java
deleted file mode 100644
index 3e652ee..0000000
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AnyObjectPullResultHandlerImpl.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * 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.Collections;
-import java.util.List;
-import java.util.Map;
-import org.apache.commons.lang3.tuple.Pair;
-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.AnyObjectProvisioningManager;
-import org.apache.syncope.core.provisioning.api.ProvisioningManager;
-import org.apache.syncope.core.provisioning.api.WorkflowResult;
-import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
-import org.identityconnectors.framework.common.objects.SyncDelta;
-import org.apache.syncope.core.provisioning.api.pushpull.AnyObjectPullResultHandler;
-import org.springframework.beans.factory.annotation.Autowired;
-
-public class AnyObjectPullResultHandlerImpl extends AbstractPullResultHandler implements AnyObjectPullResultHandler {
-
-    @Autowired
-    private AnyObjectProvisioningManager anyObjectProvisioningManager;
-
-    @Override
-    protected AnyUtils getAnyUtils() {
-        return anyUtilsFactory.getInstance(AnyTypeKind.ANY_OBJECT);
-    }
-
-    @Override
-    protected String getName(final AnyTO anyTO) {
-        return AnyObjectTO.class.cast(anyTO).getName();
-    }
-
-    @Override
-    protected ProvisioningManager<?, ?> getProvisioningManager() {
-        return anyObjectProvisioningManager;
-    }
-
-    @Override
-    protected Any<?> getAny(final String key) {
-        try {
-            return anyObjectDAO.authFind(key);
-        } catch (Exception e) {
-            LOG.warn("Error retrieving anyObject {}", key, e);
-            return null;
-        }
-    }
-
-    @Override
-    protected AnyTO getAnyTO(final String key) {
-        return anyObjectDataBinder.getAnyObjectTO(key);
-    }
-
-    @Override
-    protected AnyPatch newPatch(final String key) {
-        AnyObjectPatch patch = new AnyObjectPatch();
-        patch.setKey(key);
-        return patch;
-    }
-
-    @Override
-    protected WorkflowResult<? extends AnyPatch> update(final AnyPatch patch) {
-        return awfAdapter.update((AnyObjectPatch) patch);
-    }
-
-    @Override
-    protected AnyTO doCreate(final AnyTO anyTO, final SyncDelta delta) {
-        AnyObjectTO anyObjectTO = AnyObjectTO.class.cast(anyTO);
-
-        Map.Entry<String, List<PropagationStatus>> created = anyObjectProvisioningManager.create(
-                anyObjectTO, Collections.singleton(profile.getTask().getResource().getKey()), true);
-
-        return getAnyTO(created.getKey());
-    }
-
-    @Override
-    protected AnyPatch doUpdate(
-            final AnyTO before,
-            final AnyPatch anyPatch,
-            final SyncDelta delta,
-            final ProvisioningReport result) {
-
-        AnyObjectPatch anyObjectPatch = AnyObjectPatch.class.cast(anyPatch);
-
-        Pair<AnyObjectPatch, List<PropagationStatus>> updated = anyObjectProvisioningManager.update(
-                anyObjectPatch, Collections.singleton(profile.getTask().getResource().getKey()), true);
-
-        return anyPatch;
-    }
-}

http://git-wip-us.apache.org/repos/asf/syncope/blob/9c289134/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AnyObjectPushResultHandlerImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AnyObjectPushResultHandlerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AnyObjectPushResultHandlerImpl.java
deleted file mode 100644
index 48f464a..0000000
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AnyObjectPushResultHandlerImpl.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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 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.pushpull.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 String key) {
-        try {
-            return anyObjectDAO.authFind(key);
-        } catch (Exception e) {
-            LOG.warn("Error retrieving anyObject {}", key, e);
-            return null;
-        }
-    }
-
-    @Override
-    protected AnyTO getAnyTO(final String key) {
-        return anyObjectDataBinder.getAnyObjectTO(key);
-    }
-
-    @Override
-    protected AnyPatch newPatch(final String key) {
-        AnyObjectPatch patch = new AnyObjectPatch();
-        patch.setKey(key);
-        return patch;
-    }
-
-    @Override
-    protected WorkflowResult<? extends AnyObjectPatch> update(final AnyPatch patch) {
-        return awfAdapter.update((AnyObjectPatch) patch);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/syncope/blob/9c289134/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultAnyObjectPullResultHandler.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultAnyObjectPullResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultAnyObjectPullResultHandler.java
new file mode 100644
index 0000000..8d71a2d
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultAnyObjectPullResultHandler.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.pushpull;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.common.lib.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.AnyObjectProvisioningManager;
+import org.apache.syncope.core.provisioning.api.ProvisioningManager;
+import org.apache.syncope.core.provisioning.api.WorkflowResult;
+import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
+import org.identityconnectors.framework.common.objects.SyncDelta;
+import org.apache.syncope.core.provisioning.api.pushpull.AnyObjectPullResultHandler;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public class DefaultAnyObjectPullResultHandler extends AbstractPullResultHandler implements AnyObjectPullResultHandler {
+
+    @Autowired
+    private AnyObjectProvisioningManager anyObjectProvisioningManager;
+
+    @Override
+    protected AnyUtils getAnyUtils() {
+        return anyUtilsFactory.getInstance(AnyTypeKind.ANY_OBJECT);
+    }
+
+    @Override
+    protected String getName(final AnyTO anyTO) {
+        return AnyObjectTO.class.cast(anyTO).getName();
+    }
+
+    @Override
+    protected ProvisioningManager<?, ?> getProvisioningManager() {
+        return anyObjectProvisioningManager;
+    }
+
+    @Override
+    protected Any<?> getAny(final String key) {
+        try {
+            return anyObjectDAO.authFind(key);
+        } catch (Exception e) {
+            LOG.warn("Error retrieving anyObject {}", key, e);
+            return null;
+        }
+    }
+
+    @Override
+    protected AnyTO getAnyTO(final String key) {
+        return anyObjectDataBinder.getAnyObjectTO(key);
+    }
+
+    @Override
+    protected AnyPatch newPatch(final String key) {
+        AnyObjectPatch patch = new AnyObjectPatch();
+        patch.setKey(key);
+        return patch;
+    }
+
+    @Override
+    protected WorkflowResult<? extends AnyPatch> update(final AnyPatch patch) {
+        return awfAdapter.update((AnyObjectPatch) patch);
+    }
+
+    @Override
+    protected AnyTO doCreate(final AnyTO anyTO, final SyncDelta delta) {
+        AnyObjectTO anyObjectTO = AnyObjectTO.class.cast(anyTO);
+
+        Map.Entry<String, List<PropagationStatus>> created = anyObjectProvisioningManager.create(
+                anyObjectTO, Collections.singleton(profile.getTask().getResource().getKey()), true);
+
+        return getAnyTO(created.getKey());
+    }
+
+    @Override
+    protected AnyPatch doUpdate(
+            final AnyTO before,
+            final AnyPatch anyPatch,
+            final SyncDelta delta,
+            final ProvisioningReport result) {
+
+        AnyObjectPatch anyObjectPatch = AnyObjectPatch.class.cast(anyPatch);
+
+        Pair<AnyObjectPatch, List<PropagationStatus>> updated = anyObjectProvisioningManager.update(
+                anyObjectPatch, Collections.singleton(profile.getTask().getResource().getKey()), true);
+
+        return anyPatch;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/9c289134/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultAnyObjectPushResultHandler.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultAnyObjectPushResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultAnyObjectPushResultHandler.java
new file mode 100644
index 0000000..3c401ae
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultAnyObjectPushResultHandler.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.pushpull;
+
+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.pushpull.AnyObjectPushResultHandler;
+
+public class DefaultAnyObjectPushResultHandler 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 String key) {
+        try {
+            return anyObjectDAO.authFind(key);
+        } catch (Exception e) {
+            LOG.warn("Error retrieving anyObject {}", key, e);
+            return null;
+        }
+    }
+
+    @Override
+    protected AnyTO getAnyTO(final String key) {
+        return anyObjectDataBinder.getAnyObjectTO(key);
+    }
+
+    @Override
+    protected AnyPatch newPatch(final String key) {
+        AnyObjectPatch patch = new AnyObjectPatch();
+        patch.setKey(key);
+        return patch;
+    }
+
+    @Override
+    protected WorkflowResult<? extends AnyObjectPatch> update(final AnyPatch patch) {
+        return awfAdapter.update((AnyObjectPatch) patch);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/9c289134/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultGroupPullResultHandler.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultGroupPullResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultGroupPullResultHandler.java
new file mode 100644
index 0000000..451659b
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultGroupPullResultHandler.java
@@ -0,0 +1,138 @@
+/*
+ * 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.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.common.lib.patch.AnyPatch;
+import org.apache.syncope.common.lib.patch.AttrPatch;
+import org.apache.syncope.common.lib.patch.GroupPatch;
+import org.apache.syncope.common.lib.to.AnyTO;
+import org.apache.syncope.common.lib.to.PropagationStatus;
+import org.apache.syncope.common.lib.to.GroupTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.PatchOperation;
+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.GroupProvisioningManager;
+import org.apache.syncope.core.provisioning.api.ProvisioningManager;
+import org.apache.syncope.core.provisioning.api.WorkflowResult;
+import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
+import org.identityconnectors.framework.common.objects.SyncDelta;
+import org.apache.syncope.core.provisioning.api.pushpull.GroupPullResultHandler;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public class DefaultGroupPullResultHandler extends AbstractPullResultHandler implements GroupPullResultHandler {
+
+    @Autowired
+    private GroupProvisioningManager groupProvisioningManager;
+
+    private final Map<String, String> groupOwnerMap = new HashMap<>();
+
+    @Override
+    public Map<String, String> getGroupOwnerMap() {
+        return this.groupOwnerMap;
+    }
+
+    @Override
+    protected AnyUtils getAnyUtils() {
+        return anyUtilsFactory.getInstance(AnyTypeKind.GROUP);
+    }
+
+    @Override
+    protected String getName(final AnyTO anyTO) {
+        return GroupTO.class.cast(anyTO).getName();
+    }
+
+    @Override
+    protected ProvisioningManager<?, ?> getProvisioningManager() {
+        return groupProvisioningManager;
+    }
+
+    @Override
+    protected Any<?> getAny(final String key) {
+        try {
+            return groupDAO.authFind(key);
+        } catch (Exception e) {
+            LOG.warn("Error retrieving group {}", key, e);
+            return null;
+        }
+    }
+
+    @Override
+    protected AnyTO getAnyTO(final String key) {
+        return groupDataBinder.getGroupTO(key);
+    }
+
+    @Override
+    protected AnyPatch newPatch(final String key) {
+        GroupPatch patch = new GroupPatch();
+        patch.setKey(key);
+        return patch;
+    }
+
+    @Override
+    protected WorkflowResult<? extends AnyPatch> update(final AnyPatch patch) {
+        return gwfAdapter.update((GroupPatch) patch);
+    }
+
+    @Override
+    protected AnyTO doCreate(final AnyTO anyTO, final SyncDelta delta) {
+        GroupTO groupTO = GroupTO.class.cast(anyTO);
+
+        Map.Entry<String, List<PropagationStatus>> created = groupProvisioningManager.create(
+                groupTO,
+                groupOwnerMap,
+                Collections.singleton(profile.getTask().getResource().getKey()),
+                true);
+
+        return getAnyTO(created.getKey());
+    }
+
+    @Override
+    protected AnyPatch doUpdate(
+            final AnyTO before,
+            final AnyPatch anyPatch,
+            final SyncDelta delta,
+            final ProvisioningReport result) {
+
+        GroupPatch groupPatch = GroupPatch.class.cast(anyPatch);
+
+        Pair<GroupPatch, List<PropagationStatus>> updated = groupProvisioningManager.update(
+                groupPatch, Collections.singleton(profile.getTask().getResource().getKey()), true);
+
+        String groupOwner = null;
+        for (AttrPatch attrPatch : groupPatch.getPlainAttrs()) {
+            if (attrPatch.getOperation() == PatchOperation.ADD_REPLACE && attrPatch.getAttrTO() != null
+                    && attrPatch.getAttrTO().getSchema().isEmpty() && !attrPatch.getAttrTO().getValues().isEmpty()) {
+
+                groupOwner = attrPatch.getAttrTO().getValues().get(0);
+            }
+        }
+        if (groupOwner != null) {
+            groupOwnerMap.put(updated.getLeft().getKey(), groupOwner);
+        }
+
+        return anyPatch;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/9c289134/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultGroupPushResultHandler.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultGroupPushResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultGroupPushResultHandler.java
new file mode 100644
index 0000000..993f747
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultGroupPushResultHandler.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.pushpull;
+
+import org.apache.syncope.common.lib.patch.GroupPatch;
+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.persistence.api.entity.group.Group;
+import org.apache.syncope.core.provisioning.api.WorkflowResult;
+import org.apache.syncope.core.provisioning.api.pushpull.GroupPushResultHandler;
+
+public class DefaultGroupPushResultHandler extends AbstractPushResultHandler implements GroupPushResultHandler {
+
+    @Override
+    protected AnyUtils getAnyUtils() {
+        return anyUtilsFactory.getInstance(AnyTypeKind.GROUP);
+    }
+
+    @Override
+    protected String getName(final Any<?> any) {
+        return Group.class.cast(any).getName();
+    }
+
+    @Override
+    protected Any<?> getAny(final String key) {
+        try {
+            return groupDAO.authFind(key);
+        } catch (Exception e) {
+            LOG.warn("Error retrieving group {}", key, e);
+            return null;
+        }
+    }
+
+    @Override
+    protected AnyTO getAnyTO(final String key) {
+        return groupDataBinder.getGroupTO(key);
+    }
+
+    @Override
+    protected AnyPatch newPatch(final String key) {
+        GroupPatch patch = new GroupPatch();
+        patch.setKey(key);
+        return patch;
+    }
+
+    @Override
+    protected WorkflowResult<? extends AnyPatch> update(final AnyPatch patch) {
+        return gwfAdapter.update((GroupPatch) patch);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/9c289134/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
new file mode 100644
index 0000000..69c0604
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
@@ -0,0 +1,795 @@
+/*
+ * 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.Collections;
+import java.util.List;
+import java.util.Set;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.RealmTO;
+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.ClientExceptionType;
+import org.apache.syncope.common.lib.types.MatchingRule;
+import org.apache.syncope.core.provisioning.api.PropagationByResource;
+import org.apache.syncope.common.lib.types.PullMode;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.common.lib.types.UnmatchingRule;
+import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
+import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
+import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.entity.Realm;
+import org.apache.syncope.core.persistence.api.entity.resource.OrgUnit;
+import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.core.persistence.api.entity.task.PullTask;
+import org.apache.syncope.core.provisioning.api.propagation.PropagationException;
+import org.apache.syncope.core.provisioning.api.pushpull.IgnoreProvisionException;
+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.RealmPullResultHandler;
+import org.apache.syncope.core.provisioning.api.pushpull.SyncopePullExecutor;
+import org.apache.syncope.core.provisioning.java.utils.ConnObjectUtils;
+import org.apache.syncope.core.spring.security.DelegatedAdministrationException;
+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 class DefaultRealmPullResultHandler
+        extends AbstractRealmResultHandler<PullTask, PullActions>
+        implements RealmPullResultHandler {
+
+    @Autowired
+    private PullUtils pullUtils;
+
+    @Autowired
+    private ConnObjectUtils connObjectUtils;
+
+    @Autowired
+    private AnySearchDAO searchDAO;
+
+    private SyncopePullExecutor executor;
+
+    private Result latestResult;
+
+    @Override
+    public void setPullExecutor(final SyncopePullExecutor executor) {
+        this.executor = executor;
+    }
+
+    @Override
+    public boolean handle(final SyncDelta delta) {
+        try {
+            OrgUnit orgUnit = profile.getTask().getResource().getOrgUnit();
+            if (orgUnit == null) {
+                throw new JobExecutionException("No orgUnit found on " + profile.getTask().getResource() + " for "
+                        + delta.getObject().getObjectClass());
+            }
+
+            doHandle(delta, orgUnit);
+
+            LOG.debug("Successfully handled {}", delta);
+
+            if (profile.getTask().getPullMode() != PullMode.INCREMENTAL) {
+                return true;
+            }
+
+            boolean shouldContinue;
+            synchronized (this) {
+                shouldContinue = latestResult == Result.SUCCESS;
+                this.latestResult = null;
+            }
+            if (shouldContinue) {
+                executor.setLatestSyncToken(delta.getObjectClass(), delta.getToken());
+            }
+            return shouldContinue;
+        } catch (IgnoreProvisionException e) {
+            ProvisioningReport ignoreResult = new ProvisioningReport();
+            ignoreResult.setOperation(ResourceOperation.NONE);
+            ignoreResult.setStatus(ProvisioningReport.Status.IGNORE);
+            ignoreResult.setAnyType(REALM_TYPE);
+            ignoreResult.setKey(null);
+            ignoreResult.setName(delta.getObject().getName().getNameValue());
+            profile.getResults().add(ignoreResult);
+
+            LOG.warn("Ignoring during pull", e);
+
+            executor.setLatestSyncToken(delta.getObjectClass(), delta.getToken());
+
+            return true;
+        } catch (JobExecutionException e) {
+            LOG.error("Pull failed", e);
+
+            return false;
+        }
+    }
+
+    private List<ProvisioningReport> assign(final SyncDelta delta, final OrgUnit orgUnit) throws JobExecutionException {
+        if (!profile.getTask().isPerformCreate()) {
+            LOG.debug("PullTask not configured for create");
+            finalize(UnmatchingRule.toEventName(UnmatchingRule.ASSIGN), Result.SUCCESS, null, null, delta);
+            return Collections.<ProvisioningReport>emptyList();
+        }
+
+        RealmTO realmTO = connObjectUtils.getRealmTO(delta.getObject(), profile.getTask(), orgUnit);
+        if (realmTO.getFullPath() == null) {
+            if (realmTO.getParent() == null) {
+                realmTO.setParent(profile.getTask().getDestinatioRealm().getFullPath());
+            }
+
+            realmTO.setFullPath(realmTO.getParent() + "/" + realmTO.getName());
+        }
+        realmTO.getResources().add(profile.getTask().getResource().getKey());
+
+        ProvisioningReport result = new ProvisioningReport();
+        result.setOperation(ResourceOperation.CREATE);
+        result.setAnyType(REALM_TYPE);
+        result.setStatus(ProvisioningReport.Status.SUCCESS);
+        result.setName(realmTO.getFullPath());
+
+        if (profile.isDryRun()) {
+            result.setKey(null);
+            finalize(UnmatchingRule.toEventName(UnmatchingRule.ASSIGN), Result.SUCCESS, null, null, delta);
+        } else {
+            SyncDelta actionedDelta = delta;
+            for (PullActions action : profile.getActions()) {
+                actionedDelta = action.beforeAssign(profile, actionedDelta, realmTO);
+            }
+
+            create(realmTO, actionedDelta, UnmatchingRule.toEventName(UnmatchingRule.ASSIGN), result);
+        }
+
+        return Collections.singletonList(result);
+    }
+
+    private List<ProvisioningReport> provision(final SyncDelta delta, final OrgUnit orgUnit)
+            throws JobExecutionException {
+
+        if (!profile.getTask().isPerformCreate()) {
+            LOG.debug("PullTask not configured for create");
+            finalize(UnmatchingRule.toEventName(UnmatchingRule.PROVISION), Result.SUCCESS, null, null, delta);
+            return Collections.<ProvisioningReport>emptyList();
+        }
+
+        RealmTO realmTO = connObjectUtils.getRealmTO(delta.getObject(), profile.getTask(), orgUnit);
+        if (realmTO.getFullPath() == null) {
+            if (realmTO.getParent() == null) {
+                realmTO.setParent(profile.getTask().getDestinatioRealm().getFullPath());
+            }
+
+            realmTO.setFullPath(realmTO.getParent() + "/" + realmTO.getName());
+        }
+
+        ProvisioningReport result = new ProvisioningReport();
+        result.setOperation(ResourceOperation.CREATE);
+        result.setAnyType(REALM_TYPE);
+        result.setStatus(ProvisioningReport.Status.SUCCESS);
+        result.setName(realmTO.getFullPath());
+
+        if (profile.isDryRun()) {
+            result.setKey(null);
+            finalize(UnmatchingRule.toEventName(UnmatchingRule.PROVISION), Result.SUCCESS, null, null, delta);
+        } else {
+            SyncDelta actionedDelta = delta;
+            for (PullActions action : profile.getActions()) {
+                actionedDelta = action.beforeProvision(profile, actionedDelta, realmTO);
+            }
+
+            create(realmTO, actionedDelta, UnmatchingRule.toEventName(UnmatchingRule.PROVISION), result);
+        }
+
+        return Collections.singletonList(result);
+    }
+
+    private void throwIgnoreProvisionException(final SyncDelta delta, final Exception exception)
+            throws JobExecutionException {
+
+        if (exception instanceof IgnoreProvisionException) {
+            throw IgnoreProvisionException.class.cast(exception);
+        }
+
+        IgnoreProvisionException ipe = null;
+        for (PullActions action : profile.getActions()) {
+            if (ipe == null) {
+                ipe = action.onError(profile, delta, exception);
+            }
+        }
+        if (ipe != null) {
+            throw ipe;
+        }
+    }
+
+    private void create(
+            final RealmTO realmTO,
+            final SyncDelta delta,
+            final String operation,
+            final ProvisioningReport result)
+            throws JobExecutionException {
+
+        Object output;
+        Result resultStatus;
+
+        try {
+            Realm realm = realmDAO.save(binder.create(profile.getTask().getDestinatioRealm().getFullPath(), realmTO));
+
+            PropagationByResource propByRes = new PropagationByResource();
+            for (String resource : realm.getResourceKeys()) {
+                propByRes.add(ResourceOperation.CREATE, resource);
+            }
+            List<PropagationTask> tasks = propagationManager.createTasks(realm, propByRes, null);
+            taskExecutor.execute(tasks, false);
+
+            RealmTO actual = binder.getRealmTO(realm, true);
+
+            result.setKey(actual.getKey());
+            result.setName(profile.getTask().getDestinatioRealm().getFullPath() + "/" + actual.getName());
+
+            output = actual;
+            resultStatus = Result.SUCCESS;
+
+            for (PullActions action : profile.getActions()) {
+                action.after(profile, delta, actual, result);
+            }
+
+            LOG.debug("Realm {} successfully created", actual.getKey());
+        } catch (PropagationException e) {
+            // A propagation failure doesn't imply a pull failure.
+            // The propagation exception status will be reported into the propagation task execution.
+            LOG.error("Could not propagate Realm {}", delta.getUid().getUidValue(), e);
+            output = e;
+            resultStatus = Result.FAILURE;
+        } catch (Exception e) {
+            throwIgnoreProvisionException(delta, e);
+
+            result.setStatus(ProvisioningReport.Status.FAILURE);
+            result.setMessage(ExceptionUtils.getRootCauseMessage(e));
+            LOG.error("Could not create Realm {} ", delta.getUid().getUidValue(), e);
+            output = e;
+            resultStatus = Result.FAILURE;
+        }
+
+        finalize(operation, resultStatus, null, output, delta);
+    }
+
+    private List<ProvisioningReport> update(final SyncDelta delta, final List<String> keys)
+            throws JobExecutionException {
+
+        if (!profile.getTask().isPerformUpdate()) {
+            LOG.debug("PullTask not configured for update");
+            finalize(MatchingRule.toEventName(MatchingRule.UPDATE), Result.SUCCESS, null, null, delta);
+            return Collections.<ProvisioningReport>emptyList();
+        }
+
+        LOG.debug("About to update {}", keys);
+
+        List<ProvisioningReport> results = new ArrayList<>();
+
+        SyncDelta workingDelta = delta;
+        for (String key : keys) {
+            LOG.debug("About to update {}", key);
+
+            ProvisioningReport result = new ProvisioningReport();
+            result.setOperation(ResourceOperation.UPDATE);
+            result.setAnyType(REALM_TYPE);
+            result.setStatus(ProvisioningReport.Status.SUCCESS);
+            result.setKey(key);
+
+            Realm realm = realmDAO.find(key);
+            RealmTO before = binder.getRealmTO(realm, true);
+            if (before == null) {
+                result.setStatus(ProvisioningReport.Status.FAILURE);
+                result.setMessage(String.format("Realm '%s' not found", key));
+            } else {
+                result.setName(before.getFullPath());
+            }
+
+            if (!profile.isDryRun()) {
+                Result resultStatus;
+                Object output;
+
+                if (before == null) {
+                    resultStatus = Result.FAILURE;
+                    output = null;
+                } else {
+                    try {
+                        for (PullActions action : profile.getActions()) {
+                            workingDelta = action.beforeUpdate(profile, workingDelta, before, null);
+                        }
+
+                        PropagationByResource propByRes = binder.update(realm, before);
+                        realm = realmDAO.save(realm);
+                        RealmTO updated = binder.getRealmTO(realm, true);
+
+                        List<PropagationTask> tasks = propagationManager.createTasks(realm, propByRes, null);
+                        taskExecutor.execute(tasks, false);
+
+                        for (PullActions action : profile.getActions()) {
+                            action.after(profile, workingDelta, updated, result);
+                        }
+
+                        output = updated;
+                        resultStatus = Result.SUCCESS;
+                        result.setName(updated.getFullPath());
+
+                        LOG.debug("{} successfully updated", updated);
+                    } catch (PropagationException e) {
+                        // A propagation failure doesn't imply a pull failure.
+                        // The propagation exception status will be reported into the propagation task execution.
+                        LOG.error("Could not propagate Realm {}", workingDelta.getUid().getUidValue(), e);
+                        output = e;
+                        resultStatus = Result.FAILURE;
+                    } catch (Exception e) {
+                        throwIgnoreProvisionException(workingDelta, e);
+
+                        result.setStatus(ProvisioningReport.Status.FAILURE);
+                        result.setMessage(ExceptionUtils.getRootCauseMessage(e));
+                        LOG.error("Could not update Realm {}", workingDelta.getUid().getUidValue(), e);
+                        output = e;
+                        resultStatus = Result.FAILURE;
+                    }
+                }
+                finalize(MatchingRule.toEventName(MatchingRule.UPDATE), resultStatus, before, output, workingDelta);
+            }
+            results.add(result);
+        }
+
+        return results;
+    }
+
+    private List<ProvisioningReport> deprovision(final SyncDelta delta, final List<String> keys, final boolean unlink)
+            throws JobExecutionException {
+
+        if (!profile.getTask().isPerformUpdate()) {
+            LOG.debug("PullTask not configured for update");
+            finalize(unlink
+                    ? MatchingRule.toEventName(MatchingRule.UNASSIGN)
+                    : MatchingRule.toEventName(MatchingRule.DEPROVISION), Result.SUCCESS, null, null, delta);
+            return Collections.<ProvisioningReport>emptyList();
+        }
+
+        LOG.debug("About to deprovision {}", keys);
+
+        final List<ProvisioningReport> results = new ArrayList<>();
+
+        SyncDelta workingDelta = delta;
+        for (String key : keys) {
+            LOG.debug("About to unassign resource {}", key);
+
+            ProvisioningReport result = new ProvisioningReport();
+            result.setOperation(ResourceOperation.DELETE);
+            result.setAnyType(REALM_TYPE);
+            result.setStatus(ProvisioningReport.Status.SUCCESS);
+            result.setKey(key);
+
+            Realm realm = realmDAO.find(key);
+            RealmTO before = binder.getRealmTO(realm, true);
+            if (before == null) {
+                result.setStatus(ProvisioningReport.Status.FAILURE);
+                result.setMessage(String.format("Realm '%s' not found", key));
+            } else {
+                result.setName(before.getFullPath());
+            }
+
+            if (!profile.isDryRun()) {
+                Object output;
+                Result resultStatus;
+
+                if (before == null) {
+                    resultStatus = Result.FAILURE;
+                    output = null;
+                } else {
+                    try {
+                        if (unlink) {
+                            for (PullActions action : profile.getActions()) {
+                                workingDelta = action.beforeUnassign(profile, workingDelta, before);
+                            }
+                        } else {
+                            for (PullActions action : profile.getActions()) {
+                                workingDelta = action.beforeDeprovision(profile, workingDelta, before);
+                            }
+                        }
+
+                        PropagationByResource propByRes = new PropagationByResource();
+                        propByRes.add(ResourceOperation.DELETE, profile.getTask().getResource().getKey());
+                        taskExecutor.execute(propagationManager.createTasks(realm, propByRes, null), false);
+
+                        if (unlink) {
+                            realm.getResources().remove(profile.getTask().getResource());
+                            output = binder.getRealmTO(realmDAO.save(realm), true);
+                        } else {
+                            output = binder.getRealmTO(realm, true);
+                        }
+
+                        for (PullActions action : profile.getActions()) {
+                            action.after(profile, workingDelta, RealmTO.class.cast(output), result);
+                        }
+
+                        resultStatus = Result.SUCCESS;
+
+                        LOG.debug("{} successfully updated", realm);
+                    } catch (PropagationException e) {
+                        // A propagation failure doesn't imply a pull failure.
+                        // The propagation exception status will be reported into the propagation task execution.
+                        LOG.error("Could not propagate Realm {}", workingDelta.getUid().getUidValue(), e);
+                        output = e;
+                        resultStatus = Result.FAILURE;
+                    } catch (Exception e) {
+                        throwIgnoreProvisionException(workingDelta, e);
+
+                        result.setStatus(ProvisioningReport.Status.FAILURE);
+                        result.setMessage(ExceptionUtils.getRootCauseMessage(e));
+                        LOG.error("Could not update Realm {}", delta.getUid().getUidValue(), e);
+                        output = e;
+                        resultStatus = Result.FAILURE;
+                    }
+                }
+                finalize(unlink
+                        ? MatchingRule.toEventName(MatchingRule.UNASSIGN)
+                        : MatchingRule.toEventName(MatchingRule.DEPROVISION), resultStatus, before, output, delta);
+            }
+            results.add(result);
+        }
+
+        return results;
+    }
+
+    private List<ProvisioningReport> link(final SyncDelta delta, final List<String> keys, final boolean unlink)
+            throws JobExecutionException {
+
+        if (!profile.getTask().isPerformUpdate()) {
+            LOG.debug("PullTask not configured for update");
+            finalize(unlink
+                    ? MatchingRule.toEventName(MatchingRule.UNLINK)
+                    : MatchingRule.toEventName(MatchingRule.LINK), Result.SUCCESS, null, null, delta);
+            return Collections.<ProvisioningReport>emptyList();
+        }
+
+        LOG.debug("About to link {}", keys);
+
+        final List<ProvisioningReport> results = new ArrayList<>();
+
+        SyncDelta workingDelta = delta;
+        for (String key : keys) {
+            LOG.debug("About to unassign resource {}", key);
+
+            ProvisioningReport result = new ProvisioningReport();
+            result.setOperation(ResourceOperation.NONE);
+            result.setAnyType(REALM_TYPE);
+            result.setStatus(ProvisioningReport.Status.SUCCESS);
+            result.setKey(key);
+
+            Realm realm = realmDAO.find(key);
+            RealmTO before = binder.getRealmTO(realm, true);
+            if (before == null) {
+                result.setStatus(ProvisioningReport.Status.FAILURE);
+                result.setMessage(String.format("Realm '%s' not found", key));
+            } else {
+                result.setName(before.getFullPath());
+            }
+
+            Object output;
+            Result resultStatus;
+            if (!profile.isDryRun()) {
+                if (before == null) {
+                    resultStatus = Result.FAILURE;
+                    output = null;
+                } else {
+                    try {
+                        if (unlink) {
+                            for (PullActions action : profile.getActions()) {
+                                workingDelta = action.beforeUnlink(profile, workingDelta, before);
+                            }
+                        } else {
+                            for (PullActions action : profile.getActions()) {
+                                workingDelta = action.beforeLink(profile, workingDelta, before);
+                            }
+                        }
+
+                        if (unlink) {
+                            realm.getResources().remove(profile.getTask().getResource());
+                        } else {
+                            realm.add(profile.getTask().getResource());
+                        }
+                        output = update(workingDelta, Collections.singletonList(key));
+
+                        for (PullActions action : profile.getActions()) {
+                            action.after(profile, workingDelta, RealmTO.class.cast(output), result);
+                        }
+
+                        resultStatus = Result.SUCCESS;
+
+                        LOG.debug("{} successfully updated", realm);
+                    } catch (PropagationException e) {
+                        // A propagation failure doesn't imply a pull failure.
+                        // The propagation exception status will be reported into the propagation task execution.
+                        LOG.error("Could not propagate Realm {}", workingDelta.getUid().getUidValue(), e);
+                        output = e;
+                        resultStatus = Result.FAILURE;
+                    } catch (Exception e) {
+                        throwIgnoreProvisionException(workingDelta, e);
+
+                        result.setStatus(ProvisioningReport.Status.FAILURE);
+                        result.setMessage(ExceptionUtils.getRootCauseMessage(e));
+                        LOG.error("Could not update Realm {}", workingDelta.getUid().getUidValue(), e);
+                        output = e;
+                        resultStatus = Result.FAILURE;
+                    }
+                }
+                finalize(unlink
+                        ? MatchingRule.toEventName(MatchingRule.UNLINK)
+                        : MatchingRule.toEventName(MatchingRule.LINK), resultStatus, before, output, workingDelta);
+            }
+            results.add(result);
+        }
+
+        return results;
+    }
+
+    private List<ProvisioningReport> delete(final SyncDelta delta, final List<String> keys)
+            throws JobExecutionException {
+
+        if (!profile.getTask().isPerformDelete()) {
+            LOG.debug("PullTask not configured for delete");
+            finalize(ResourceOperation.DELETE.name().toLowerCase(), Result.SUCCESS, null, null, delta);
+            return Collections.<ProvisioningReport>emptyList();
+        }
+
+        LOG.debug("About to delete {}", keys);
+
+        List<ProvisioningReport> results = new ArrayList<>();
+
+        SyncDelta workingDelta = delta;
+        for (String key : keys) {
+            Object output;
+            Result resultStatus = Result.FAILURE;
+
+            ProvisioningReport result = new ProvisioningReport();
+
+            try {
+                result.setKey(key);
+                result.setOperation(ResourceOperation.DELETE);
+                result.setAnyType(REALM_TYPE);
+                result.setStatus(ProvisioningReport.Status.SUCCESS);
+
+                Realm realm = realmDAO.find(key);
+                RealmTO before = binder.getRealmTO(realm, true);
+                if (before == null) {
+                    result.setStatus(ProvisioningReport.Status.FAILURE);
+                    result.setMessage(String.format("Realm '%s' not found", key));
+                } else {
+                    result.setName(before.getFullPath());
+                }
+
+                if (!profile.isDryRun()) {
+                    for (PullActions action : profile.getActions()) {
+                        workingDelta = action.beforeDelete(profile, workingDelta, before);
+                    }
+
+                    try {
+                        if (!realmDAO.findChildren(realm).isEmpty()) {
+                            throw SyncopeClientException.build(ClientExceptionType.HasChildren);
+                        }
+
+                        Set<String> adminRealms = Collections.singleton(realm.getFullPath());
+                        AnyCond keyCond = new AnyCond(AttributeCond.Type.ISNOTNULL);
+                        keyCond.setSchema("key");
+                        SearchCond allMatchingCond = SearchCond.getLeafCond(keyCond);
+                        int users = searchDAO.count(adminRealms, allMatchingCond, AnyTypeKind.USER);
+                        int groups = searchDAO.count(adminRealms, allMatchingCond, AnyTypeKind.GROUP);
+                        int anyObjects = searchDAO.count(adminRealms, allMatchingCond, AnyTypeKind.ANY_OBJECT);
+
+                        if (users + groups + anyObjects > 0) {
+                            SyncopeClientException containedAnys = SyncopeClientException.build(
+                                    ClientExceptionType.AssociatedAnys);
+                            containedAnys.getElements().add(users + " user(s)");
+                            containedAnys.getElements().add(groups + " group(s)");
+                            containedAnys.getElements().add(anyObjects + " anyObject(s)");
+                            throw containedAnys;
+                        }
+
+                        PropagationByResource propByRes = new PropagationByResource();
+                        for (String resource : realm.getResourceKeys()) {
+                            propByRes.add(ResourceOperation.DELETE, resource);
+                        }
+                        List<PropagationTask> tasks = propagationManager.createTasks(realm, propByRes, null);
+                        taskExecutor.execute(tasks, false);
+
+                        realmDAO.delete(realm);
+
+                        output = null;
+                        resultStatus = Result.SUCCESS;
+
+                        for (PullActions action : profile.getActions()) {
+                            action.after(profile, workingDelta, before, result);
+                        }
+                    } catch (Exception e) {
+                        throwIgnoreProvisionException(workingDelta, e);
+
+                        result.setStatus(ProvisioningReport.Status.FAILURE);
+                        result.setMessage(ExceptionUtils.getRootCauseMessage(e));
+                        LOG.error("Could not delete {}", realm, e);
+                        output = e;
+                    }
+
+                    finalize(ResourceOperation.DELETE.name().toLowerCase(), resultStatus, before, output, workingDelta);
+                }
+
+                results.add(result);
+            } catch (DelegatedAdministrationException e) {
+                LOG.error("Not allowed to read Realm {}", key, e);
+            } catch (Exception e) {
+                LOG.error("Could not delete Realm {}", key, e);
+            }
+        }
+
+        return results;
+    }
+
+    private ProvisioningReport ignore(
+            final SyncDelta delta,
+            final boolean matching)
+            throws JobExecutionException {
+
+        LOG.debug("Any to ignore {}", delta.getObject().getUid().getUidValue());
+
+        ProvisioningReport result = new ProvisioningReport();
+
+        result.setKey(null);
+        result.setName(delta.getObject().getUid().getUidValue());
+        result.setOperation(ResourceOperation.NONE);
+        result.setAnyType(REALM_TYPE);
+        result.setStatus(ProvisioningReport.Status.SUCCESS);
+
+        if (!profile.isDryRun()) {
+            finalize(matching
+                    ? MatchingRule.toEventName(MatchingRule.IGNORE)
+                    : UnmatchingRule.toEventName(UnmatchingRule.IGNORE), Result.SUCCESS, null, null, delta);
+        }
+
+        return result;
+    }
+
+    private void doHandle(final SyncDelta delta, final OrgUnit orgUnit) throws JobExecutionException {
+        LOG.debug("Process {} for {} as {}",
+                delta.getDeltaType(), delta.getUid().getUidValue(), delta.getObject().getObjectClass());
+
+        String uid = delta.getPreviousUid() == null
+                ? delta.getUid().getUidValue()
+                : delta.getPreviousUid().getUidValue();
+
+        List<String> keys = pullUtils.findExisting(uid, delta.getObject(), orgUnit);
+        LOG.debug("Match found for {} as {}: {}",
+                delta.getUid().getUidValue(), delta.getObject().getObjectClass(), keys);
+
+        if (keys.size() > 1) {
+            switch (profile.getResAct()) {
+                case IGNORE:
+                    throw new IllegalStateException("More than one match " + keys);
+
+                case FIRSTMATCH:
+                    keys = keys.subList(0, 1);
+                    break;
+
+                case LASTMATCH:
+                    keys = keys.subList(keys.size() - 1, keys.size());
+                    break;
+
+                default:
+                // keep keys unmodified
+                }
+        }
+
+        try {
+            if (SyncDeltaType.CREATE_OR_UPDATE == delta.getDeltaType()) {
+                if (keys.isEmpty()) {
+                    switch (profile.getTask().getUnmatchingRule()) {
+                        case ASSIGN:
+                            profile.getResults().addAll(assign(delta, orgUnit));
+                            break;
+
+                        case PROVISION:
+                            profile.getResults().addAll(provision(delta, orgUnit));
+                            break;
+
+                        case IGNORE:
+                            profile.getResults().add(ignore(delta, false));
+                            break;
+
+                        default:
+                        // do nothing
+                    }
+                } else {
+                    switch (profile.getTask().getMatchingRule()) {
+                        case UPDATE:
+                            profile.getResults().addAll(update(delta, keys));
+                            break;
+
+                        case DEPROVISION:
+                            profile.getResults().addAll(deprovision(delta, keys, false));
+                            break;
+
+                        case UNASSIGN:
+                            profile.getResults().addAll(deprovision(delta, keys, true));
+                            break;
+
+                        case LINK:
+                            profile.getResults().addAll(link(delta, keys, false));
+                            break;
+
+                        case UNLINK:
+                            profile.getResults().addAll(link(delta, keys, true));
+                            break;
+
+                        case IGNORE:
+                            profile.getResults().add(ignore(delta, true));
+                            break;
+
+                        default:
+                        // do nothing
+                    }
+                }
+            } else if (SyncDeltaType.DELETE == delta.getDeltaType()) {
+                if (keys.isEmpty()) {
+                    finalize(ResourceOperation.DELETE.name().toLowerCase(), Result.SUCCESS, null, null, delta);
+                    LOG.debug("No match found for deletion");
+                } else {
+                    profile.getResults().addAll(delete(delta, keys));
+                }
+            }
+        } catch (IllegalStateException | IllegalArgumentException e) {
+            LOG.warn(e.getMessage());
+        }
+    }
+
+    private void finalize(
+            final String event,
+            final Result result,
+            final Object before,
+            final Object output,
+            final SyncDelta delta) {
+
+        synchronized (this) {
+            this.latestResult = result;
+        }
+
+        notificationManager.createTasks(AuditElements.EventCategoryType.PULL,
+                REALM_TYPE.toLowerCase(),
+                profile.getTask().getResource().getKey(),
+                event,
+                result,
+                before,
+                output,
+                delta);
+
+        auditManager.audit(AuditElements.EventCategoryType.PULL,
+                REALM_TYPE.toLowerCase(),
+                profile.getTask().getResource().getKey(),
+                event,
+                result,
+                before,
+                output,
+                delta);
+    }
+}