http://git-wip-us.apache.org/repos/asf/syncope/blob/61a7fdd3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RealmDataBinderImpl.java
----------------------------------------------------------------------
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RealmDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RealmDataBinderImpl.java
index 42de46f..7771f06 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RealmDataBinderImpl.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RealmDataBinderImpl.java
@@ -65,7 +65,7 @@ public class RealmDataBinderImpl implements RealmDataBinder {
 
     private void setTemplates(final RealmTO realmTO, final Realm realm) {
         // validate JEXL expressions from templates and proceed if fine
-        templateUtils.check(realmTO.getTemplates(), 
ClientExceptionType.InvalidSyncTask);
+        templateUtils.check(realmTO.getTemplates(), 
ClientExceptionType.InvalidPullTask);
         for (Map.Entry<String, AnyTO> entry : 
realmTO.getTemplates().entrySet()) {
             AnyType type = anyTypeDAO.find(entry.getKey());
             if (type == null) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/61a7fdd3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
----------------------------------------------------------------------
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
index 97aa6f2..b4aa8b2 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
@@ -41,7 +41,6 @@ import 
org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.resource.Mapping;
 import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
-import org.apache.syncope.core.persistence.api.entity.policy.SyncPolicy;
 import org.apache.syncope.core.provisioning.java.jexl.JexlUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -54,6 +53,7 @@ import 
org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.identityconnectors.framework.common.objects.ObjectClass;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
+import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
 
 @Component
 public class ResourceDataBinderImpl implements ResourceDataBinder {
@@ -178,7 +178,7 @@ public class ResourceDataBinderImpl implements 
ResourceDataBinder {
         resource.setCreateTraceLevel(resourceTO.getCreateTraceLevel());
         resource.setUpdateTraceLevel(resourceTO.getUpdateTraceLevel());
         resource.setDeleteTraceLevel(resourceTO.getDeleteTraceLevel());
-        resource.setSyncTraceLevel(resourceTO.getSyncTraceLevel());
+        resource.setPullTraceLevel(resourceTO.getPullTraceLevel());
 
         resource.setPasswordPolicy(resourceTO.getPasswordPolicy() == null
                 ? null : (PasswordPolicy) 
policyDAO.find(resourceTO.getPasswordPolicy()));
@@ -186,8 +186,8 @@ public class ResourceDataBinderImpl implements 
ResourceDataBinder {
         resource.setAccountPolicy(resourceTO.getAccountPolicy() == null
                 ? null : (AccountPolicy) 
policyDAO.find(resourceTO.getAccountPolicy()));
 
-        resource.setSyncPolicy(resourceTO.getSyncPolicy() == null
-                ? null : (SyncPolicy) 
policyDAO.find(resourceTO.getSyncPolicy()));
+        resource.setPullPolicy(resourceTO.getPullPolicy() == null
+                ? null : (PullPolicy) 
policyDAO.find(resourceTO.getPullPolicy()));
 
         resource.setConfOverride(new HashSet<>(resourceTO.getConfOverride()));
 
@@ -340,7 +340,7 @@ public class ResourceDataBinderImpl implements 
ResourceDataBinder {
         resourceTO.setCreateTraceLevel(resource.getCreateTraceLevel());
         resourceTO.setUpdateTraceLevel(resource.getUpdateTraceLevel());
         resourceTO.setDeleteTraceLevel(resource.getDeleteTraceLevel());
-        resourceTO.setSyncTraceLevel(resource.getSyncTraceLevel());
+        resourceTO.setPullTraceLevel(resource.getPullTraceLevel());
 
         resourceTO.setPasswordPolicy(resource.getPasswordPolicy() == null
                 ? null : resource.getPasswordPolicy().getKey());
@@ -348,8 +348,8 @@ public class ResourceDataBinderImpl implements 
ResourceDataBinder {
         resourceTO.setAccountPolicy(resource.getAccountPolicy() == null
                 ? null : resource.getAccountPolicy().getKey());
 
-        resourceTO.setSyncPolicy(resource.getSyncPolicy() == null
-                ? null : resource.getSyncPolicy().getKey());
+        resourceTO.setPullPolicy(resource.getPullPolicy() == null
+                ? null : resource.getPullPolicy().getKey());
 
         resourceTO.getConfOverride().addAll(resource.getConfOverride());
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/61a7fdd3/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 e262521..7679ce0 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
@@ -30,7 +30,7 @@ import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.to.PropagationTaskTO;
 import org.apache.syncope.common.lib.to.PushTaskTO;
 import org.apache.syncope.common.lib.to.SchedTaskTO;
-import org.apache.syncope.common.lib.to.SyncTaskTO;
+import org.apache.syncope.common.lib.to.PullTaskTO;
 import org.apache.syncope.common.lib.to.ExecTO;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.JobType;
@@ -46,7 +46,6 @@ import 
org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
 import org.apache.syncope.core.persistence.api.entity.task.ProvisioningTask;
 import org.apache.syncope.core.persistence.api.entity.task.PushTask;
 import org.apache.syncope.core.persistence.api.entity.task.SchedTask;
-import org.apache.syncope.core.persistence.api.entity.task.SyncTask;
 import org.apache.syncope.core.persistence.api.entity.task.Task;
 import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
 import org.apache.syncope.core.persistence.api.entity.task.TaskUtils;
@@ -58,9 +57,10 @@ import 
org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import 
org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.AnyTemplate;
-import org.apache.syncope.core.persistence.api.entity.task.AnyTemplateSyncTask;
-import org.apache.syncope.core.provisioning.java.syncpull.PushJobDelegate;
-import org.apache.syncope.core.provisioning.java.syncpull.SyncJobDelegate;
+import org.apache.syncope.core.persistence.api.entity.task.AnyTemplatePullTask;
+import org.apache.syncope.core.persistence.api.entity.task.PullTask;
+import org.apache.syncope.core.provisioning.java.pushpull.PushJobDelegate;
+import org.apache.syncope.core.provisioning.java.pushpull.PullJobDelegate;
 import org.quartz.Scheduler;
 import org.quartz.SchedulerException;
 import org.quartz.Trigger;
@@ -142,46 +142,46 @@ public class TaskDataBinderImpl implements TaskDataBinder 
{
                     return 
pushTaskTO.getFilters().containsKey(anyFilter.getAnyType().getKey());
                 }
             });
-        } else if (task instanceof SyncTask && taskTO instanceof SyncTaskTO) {
-            SyncTask syncTask = (SyncTask) task;
-            final SyncTaskTO syncTaskTO = (SyncTaskTO) taskTO;
+        } else if (task instanceof PullTask && taskTO instanceof PullTaskTO) {
+            PullTask pullTask = (PullTask) task;
+            final PullTaskTO pullTaskTO = (PullTaskTO) taskTO;
 
-            syncTask.setSyncMode(syncTaskTO.getSyncMode());
-            
syncTask.setReconciliationFilterBuilderClassName(syncTaskTO.getReconciliationFilterBuilderClassName());
+            pullTask.setPullMode(pullTaskTO.getPullMode());
+            
pullTask.setReconciliationFilterBuilderClassName(pullTaskTO.getReconciliationFilterBuilderClassName());
 
-            
syncTask.setDestinationRealm(realmDAO.find(syncTaskTO.getDestinationRealm()));
+            
pullTask.setDestinationRealm(realmDAO.find(pullTaskTO.getDestinationRealm()));
 
-            syncTask.setJobDelegateClassName(SyncJobDelegate.class.getName());
+            pullTask.setJobDelegateClassName(PullJobDelegate.class.getName());
 
-            syncTask.setMatchingRule(syncTaskTO.getMatchingRule() == null
-                    ? MatchingRule.UPDATE : syncTaskTO.getMatchingRule());
-            syncTask.setUnmatchingRule(syncTaskTO.getUnmatchingRule() == null
-                    ? UnmatchingRule.PROVISION : 
syncTaskTO.getUnmatchingRule());
+            pullTask.setMatchingRule(pullTaskTO.getMatchingRule() == null
+                    ? MatchingRule.UPDATE : pullTaskTO.getMatchingRule());
+            pullTask.setUnmatchingRule(pullTaskTO.getUnmatchingRule() == null
+                    ? UnmatchingRule.PROVISION : 
pullTaskTO.getUnmatchingRule());
 
             // validate JEXL expressions from templates and proceed if fine
-            templateUtils.check(syncTaskTO.getTemplates(), 
ClientExceptionType.InvalidSyncTask);
-            for (Map.Entry<String, AnyTO> entry : 
syncTaskTO.getTemplates().entrySet()) {
+            templateUtils.check(pullTaskTO.getTemplates(), 
ClientExceptionType.InvalidPullTask);
+            for (Map.Entry<String, AnyTO> entry : 
pullTaskTO.getTemplates().entrySet()) {
                 AnyType type = anyTypeDAO.find(entry.getKey());
                 if (type == null) {
                     LOG.debug("Invalid AnyType {} specified, ignoring...", 
entry.getKey());
                 } else {
-                    AnyTemplateSyncTask anyTemplate = 
syncTask.getTemplate(type);
+                    AnyTemplatePullTask anyTemplate = 
pullTask.getTemplate(type);
                     if (anyTemplate == null) {
-                        anyTemplate = 
entityFactory.newEntity(AnyTemplateSyncTask.class);
+                        anyTemplate = 
entityFactory.newEntity(AnyTemplatePullTask.class);
                         anyTemplate.setAnyType(type);
-                        anyTemplate.setSyncTask(syncTask);
+                        anyTemplate.setPullTask(pullTask);
 
-                        syncTask.add(anyTemplate);
+                        pullTask.add(anyTemplate);
                     }
                     anyTemplate.set(entry.getValue());
                 }
             }
             // remove all templates not contained in the TO
-            CollectionUtils.filter(syncTask.getTemplates(), new 
Predicate<AnyTemplate>() {
+            CollectionUtils.filter(pullTask.getTemplates(), new 
Predicate<AnyTemplate>() {
 
                 @Override
                 public boolean evaluate(final AnyTemplate anyTemplate) {
-                    return 
syncTaskTO.getTemplates().containsKey(anyTemplate.getAnyType().getKey());
+                    return 
pullTaskTO.getTemplates().containsKey(anyTemplate.getAnyType().getKey());
                 }
             });
         }
@@ -190,7 +190,7 @@ public class TaskDataBinderImpl implements TaskDataBinder {
         task.setPerformCreate(taskTO.isPerformCreate());
         task.setPerformUpdate(taskTO.isPerformUpdate());
         task.setPerformDelete(taskTO.isPerformDelete());
-        task.setSyncStatus(taskTO.isSyncStatus());
+        task.setPullStatus(taskTO.isPullStatus());
         task.getActionsClassNames().clear();
         task.getActionsClassNames().addAll(taskTO.getActionsClassNames());
     }
@@ -334,21 +334,21 @@ public class TaskDataBinderImpl implements TaskDataBinder 
{
                 setExecTime((SchedTaskTO) taskTO, task);
                 break;
 
-            case SYNCHRONIZATION:
-                if (!(task instanceof SyncTask)) {
-                    throw new IllegalArgumentException("taskUtils is type Sync 
but task is not SyncTask: "
+            case PULL:
+                if (!(task instanceof PullTask)) {
+                    throw new IllegalArgumentException("taskUtils is type Pull 
but task is not PullTask: "
                             + task.getClass().getName());
                 }
                 setExecTime((SchedTaskTO) taskTO, task);
-                ((SyncTaskTO) taskTO).setDestinationRealm(((SyncTask) 
task).getDestinatioRealm().getFullPath());
-                ((SyncTaskTO) taskTO).setResource(((SyncTask) 
task).getResource().getKey());
-                ((SyncTaskTO) taskTO).setMatchingRule(((SyncTask) 
task).getMatchingRule() == null
-                        ? MatchingRule.UPDATE : ((SyncTask) 
task).getMatchingRule());
-                ((SyncTaskTO) taskTO).setUnmatchingRule(((SyncTask) 
task).getUnmatchingRule() == null
-                        ? UnmatchingRule.PROVISION : ((SyncTask) 
task).getUnmatchingRule());
-
-                for (AnyTemplate template : ((SyncTask) task).getTemplates()) {
-                    ((SyncTaskTO) 
taskTO).getTemplates().put(template.getAnyType().getKey(), template.get());
+                ((PullTaskTO) taskTO).setDestinationRealm(((PullTask) 
task).getDestinatioRealm().getFullPath());
+                ((PullTaskTO) taskTO).setResource(((PullTask) 
task).getResource().getKey());
+                ((PullTaskTO) taskTO).setMatchingRule(((PullTask) 
task).getMatchingRule() == null
+                        ? MatchingRule.UPDATE : ((PullTask) 
task).getMatchingRule());
+                ((PullTaskTO) taskTO).setUnmatchingRule(((PullTask) 
task).getUnmatchingRule() == null
+                        ? UnmatchingRule.PROVISION : ((PullTask) 
task).getUnmatchingRule());
+
+                for (AnyTemplate template : ((PullTask) task).getTemplates()) {
+                    ((PullTaskTO) 
taskTO).getTemplates().put(template.getAnyType().getKey(), template.get());
                 }
                 break;
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/61a7fdd3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPMembershipPropagationActions.java
----------------------------------------------------------------------
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPMembershipPropagationActions.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPMembershipPropagationActions.java
index 6aa076e..4c6455b 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPMembershipPropagationActions.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPMembershipPropagationActions.java
@@ -46,7 +46,7 @@ import 
org.springframework.transaction.annotation.Transactional;
  * Simple action for propagating group memberships to LDAP groups, when the 
same resource is configured for both users
  * and groups.
  *
- * @see 
org.apache.syncope.core.provisioning.java.sync.LDAPMembershipSyncActions
+ * @see 
org.apache.syncope.core.provisioning.java.pushpull.LDAPMembershipPullActions
  */
 public class LDAPMembershipPropagationActions extends 
DefaultPropagationActions {
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/61a7fdd3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractProvisioningJobDelegate.java
----------------------------------------------------------------------
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractProvisioningJobDelegate.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractProvisioningJobDelegate.java
new file mode 100644
index 0000000..d10d27f
--- /dev/null
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/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.pushpull;
+
+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.pushpull.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 provisionig operation, based on the 
trace level.
+     *
+     * @param provResults Provisioning results
+     * @param traceLevel Provisioning trace level
+     * @param dryRun dry run?
+     * @return report as string
+     */
+    protected String createReport(final Collection<ProvisioningReport> 
provResults, final TraceLevel traceLevel,
+            final boolean dryRun) {
+
+        if (traceLevel == 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 (traceLevel == TraceLevel.FAILURES || traceLevel == TraceLevel.ALL) 
{
+            if (!uFailCreate.isEmpty()) {
+                report.append("\n\nUsers failed to create: ");
+                report.append(ProvisioningReport.produceReport(uFailCreate, 
traceLevel));
+            }
+            if (!uFailUpdate.isEmpty()) {
+                report.append("\nUsers failed to update: ");
+                report.append(ProvisioningReport.produceReport(uFailUpdate, 
traceLevel));
+            }
+            if (!uFailDelete.isEmpty()) {
+                report.append("\nUsers failed to delete: ");
+                report.append(ProvisioningReport.produceReport(uFailDelete, 
traceLevel));
+            }
+
+            if (!gFailCreate.isEmpty()) {
+                report.append("\n\nGroups failed to create: ");
+                report.append(ProvisioningReport.produceReport(gFailCreate, 
traceLevel));
+            }
+            if (!gFailUpdate.isEmpty()) {
+                report.append("\nGroups failed to update: ");
+                report.append(ProvisioningReport.produceReport(gFailUpdate, 
traceLevel));
+            }
+            if (!gFailDelete.isEmpty()) {
+                report.append("\nGroups failed to delete: ");
+                report.append(ProvisioningReport.produceReport(gFailDelete, 
traceLevel));
+            }
+
+            if (!aFailCreate.isEmpty()) {
+                report.append("\nAny objects failed to create: ");
+                report.append(ProvisioningReport.produceReport(aFailCreate, 
traceLevel));
+            }
+            if (!aFailUpdate.isEmpty()) {
+                report.append("\nAny objects failed to update: ");
+                report.append(ProvisioningReport.produceReport(aFailUpdate, 
traceLevel));
+            }
+            if (!aFailDelete.isEmpty()) {
+                report.append("\nAny objects failed to delete: ");
+                report.append(ProvisioningReport.produceReport(aFailDelete, 
traceLevel));
+            }
+        }
+
+        // Succeeded, only if on 'ALL' level
+        if (traceLevel == TraceLevel.ALL) {
+            report.append("\n\nUsers created:\n").
+                    append(ProvisioningReport.produceReport(uSuccCreate, 
traceLevel)).
+                    append("\nUsers updated:\n").
+                    append(ProvisioningReport.produceReport(uSuccUpdate, 
traceLevel)).
+                    append("\nUsers deleted:\n").
+                    append(ProvisioningReport.produceReport(uSuccDelete, 
traceLevel)).
+                    append("\nUsers no operation:\n").
+                    append(ProvisioningReport.produceReport(uSuccNone, 
traceLevel)).
+                    append("\nUsers ignored:\n").
+                    append(ProvisioningReport.produceReport(uIgnore, 
traceLevel));
+            report.append("\n\nGroups created:\n").
+                    append(ProvisioningReport.produceReport(gSuccCreate, 
traceLevel)).
+                    append("\nGroups updated:\n").
+                    append(ProvisioningReport.produceReport(gSuccUpdate, 
traceLevel)).
+                    append("\nGroups deleted:\n").
+                    append(ProvisioningReport.produceReport(gSuccDelete, 
traceLevel)).
+                    append("\nGroups no operation:\n").
+                    append(ProvisioningReport.produceReport(gSuccNone, 
traceLevel)).
+                    append("\nGroups ignored:\n").
+                    append(ProvisioningReport.produceReport(gSuccNone, 
traceLevel));
+            report.append("\n\nAny objects created:\n").
+                    append(ProvisioningReport.produceReport(aSuccCreate, 
traceLevel)).
+                    append("\nAny objects updated:\n").
+                    append(ProvisioningReport.produceReport(aSuccUpdate, 
traceLevel)).
+                    append("\nAny objects deleted:\n").
+                    append(ProvisioningReport.produceReport(aSuccDelete, 
traceLevel)).
+                    append("\nAny objects no operation:\n").
+                    append(ProvisioningReport.produceReport(aSuccNone, 
traceLevel)).
+                    append("\nAny objects ignored:\n").
+                    append(ProvisioningReport.produceReport(aSuccNone, 
traceLevel));
+        }
+
+        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().getPullTraceLevel().ordinal() >= 
TraceLevel.FAILURES.ordinal())
+                || provTask.getResource().getPullTraceLevel().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/61a7fdd3/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
new file mode 100644
index 0000000..bde6ee7
--- /dev/null
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPullResultHandler.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.pushpull;
+
+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.provisioning.api.propagation.PropagationException;
+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.pushpull.IgnoreProvisionException;
+import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
+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;
+import org.apache.syncope.core.persistence.api.entity.task.PullTask;
+import 
org.apache.syncope.core.provisioning.api.pushpull.SyncopePullResultHandler;
+import org.apache.syncope.core.provisioning.api.pushpull.PullActions;
+
+@Transactional(rollbackFor = Throwable.class)
+public abstract class AbstractPullResultHandler extends 
AbstractSyncopeResultHandler<PullTask, PullActions>
+        implements SyncopePullResultHandler {
+
+    @Autowired
+    protected PullUtils pullUtils;
+
+    @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 pull 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 pull", e);
+            return true;
+        } catch (JobExecutionException e) {
+            LOG.error("Pull 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("PullTask 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 (PullActions 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("PullTask 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 (PullActions 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 (PullActions 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 pull 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 (PullActions 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 (PullActions 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("PullTask 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 (PullActions action : profile.getActions()) {
+                            workingDelta = 
action.beforeUpdate(this.getProfile(), workingDelta, before, anyPatch);
+                        }
+
+                        AnyTO updated = doUpdate(before, anyPatch, 
workingDelta, result);
+
+                        for (PullActions 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 pull 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 (PullActions 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 (PullActions 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("PullTask 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 (PullActions action : profile.getActions()) {
+                                action.beforeUnassign(this.getProfile(), 
delta, before);
+                            }
+                        } else {
+                            for (PullActions action : profile.getActions()) {
+                                action.beforeDeprovision(this.getProfile(), 
delta, before);
+                            }
+                        }
+
+                        doDeprovision(provision.getAnyType().getKind(), key, 
unlink);
+                        output = getAnyTO(key);
+
+                        for (PullActions 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 pull 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 (PullActions 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 (PullActions 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("PullTask 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 (PullActions action : profile.getActions()) {
+                                action.beforeUnlink(this.getProfile(), delta, 
before);
+                            }
+                        } else {
+                            for (PullActions action : profile.getActions()) {
+                                action.beforeLink(this.getProfile(), delta, 
before);
+                            }
+                        }
+
+                        output = doLink(before, unlink);
+
+                        for (PullActions 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 pull 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 (PullActions 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 (PullActions 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("PullTask 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 (PullActions action : profile.getActions()) {
+                        workingDelta = action.beforeDelete(this.getProfile(), 
workingDelta, before);
+                    }
+
+                    try {
+                        doDelete(provision.getAnyType().getKind(), key);
+                        output = null;
+                        resultStatus = Result.SUCCESS;
+
+                        for (PullActions 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 (PullActions 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 pull 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 = pullUtils.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.PULL,
+                getAnyUtils().getAnyTypeKind().name().toLowerCase(),
+                profile.getTask().getResource().getKey(),
+                event,
+                result,
+                before,
+                output,
+                input);
+
+        auditManager.audit(AuditElements.EventCategoryType.PULL,
+                getAnyUtils().getAnyTypeKind().name().toLowerCase(),
+                profile.getTask().getResource().getKey(),
+                event,
+                result,
+                before,
+                output,
+                input);
+    }
+}

Reply via email to