You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by md...@apache.org on 2015/03/18 09:49:12 UTC

[1/5] syncope git commit: [SYNCOPE-648] Add matching/unmatching events to notification and audit

Repository: syncope
Updated Branches:
  refs/heads/master 28aef6c78 -> b606a4b87


[SYNCOPE-648] Add matching/unmatching events to notification and audit


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

Branch: refs/heads/master
Commit: 89d5ad43cf26454092dbd04446a0d9a6f5192f4a
Parents: b630219
Author: Marco Di Sabatino Di Diodoro <md...@apache.org>
Authored: Tue Mar 17 16:09:35 2015 +0100
Committer: Marco Di Sabatino Di Diodoro <md...@apache.org>
Committed: Tue Mar 17 16:09:35 2015 +0100

----------------------------------------------------------------------
 .../syncope/common/types/MatchingRule.java      |  7 ++-
 .../syncope/common/types/UnmatchingRule.java    |  7 ++-
 .../core/rest/controller/LoggerController.java  | 18 +++++++-
 .../impl/AbstractSubjectPushResultHandler.java  | 12 +++--
 .../impl/AbstractSubjectSyncResultHandler.java  | 47 +++++++++++++++++---
 .../syncope/core/sync/impl/AbstractSyncJob.java | 33 +++++++++++---
 .../syncope/core/rest/TaskTestITCase.java       | 44 ++++++++++++++++++
 7 files changed, 150 insertions(+), 18 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/89d5ad43/common/src/main/java/org/apache/syncope/common/types/MatchingRule.java
----------------------------------------------------------------------
diff --git a/common/src/main/java/org/apache/syncope/common/types/MatchingRule.java b/common/src/main/java/org/apache/syncope/common/types/MatchingRule.java
index 51933fc..ff36dec 100644
--- a/common/src/main/java/org/apache/syncope/common/types/MatchingRule.java
+++ b/common/src/main/java/org/apache/syncope/common/types/MatchingRule.java
@@ -49,6 +49,11 @@ public enum MatchingRule {
     /**
      * Just link resource without performing any (de-)provisioning operation.
      */
-    LINK
+    LINK;
+
+    public static String toEventName(final MatchingRule rule) {
+        return new StringBuilder(MatchingRule.class.getSimpleName()).
+                append("_").append(rule.name()).toString().toLowerCase();
+    }
 
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/89d5ad43/common/src/main/java/org/apache/syncope/common/types/UnmatchingRule.java
----------------------------------------------------------------------
diff --git a/common/src/main/java/org/apache/syncope/common/types/UnmatchingRule.java b/common/src/main/java/org/apache/syncope/common/types/UnmatchingRule.java
index d57aacf..224cf63 100644
--- a/common/src/main/java/org/apache/syncope/common/types/UnmatchingRule.java
+++ b/common/src/main/java/org/apache/syncope/common/types/UnmatchingRule.java
@@ -42,6 +42,11 @@ public enum UnmatchingRule {
      * Just unlink resource without performing any (de-)provisioning operation.
      * In case of sync task UNLINK and IGNORE will coincide.
      */
-    UNLINK
+    UNLINK;
+
+    public static String toEventName(final UnmatchingRule rule) {
+        return new StringBuilder(UnmatchingRule.class.getSimpleName()).
+                append("_").append(rule.name()).toString().toLowerCase();
+    }
 
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/89d5ad43/core/src/main/java/org/apache/syncope/core/rest/controller/LoggerController.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/syncope/core/rest/controller/LoggerController.java b/core/src/main/java/org/apache/syncope/core/rest/controller/LoggerController.java
index a4e53b8..f38d0b2 100644
--- a/core/src/main/java/org/apache/syncope/core/rest/controller/LoggerController.java
+++ b/core/src/main/java/org/apache/syncope/core/rest/controller/LoggerController.java
@@ -41,6 +41,8 @@ import org.apache.syncope.common.types.LoggerType;
 import org.apache.syncope.common.types.ResourceOperation;
 import org.apache.syncope.common.util.BeanUtils;
 import org.apache.syncope.common.SyncopeClientException;
+import org.apache.syncope.common.types.MatchingRule;
+import org.apache.syncope.common.types.UnmatchingRule;
 import org.apache.syncope.core.persistence.beans.ExternalResource;
 import org.apache.syncope.core.persistence.beans.SchedTask;
 import org.apache.syncope.core.persistence.beans.SyncTask;
@@ -217,7 +219,7 @@ public class LoggerController extends AbstractTransactionalController<LoggerTO>
             final String packageSearchPath =
                     ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                     + ClassUtils.convertClassNameToResourcePath(
-                    SystemPropertyUtils.resolvePlaceholders(this.getClass().getPackage().getName()))
+                            SystemPropertyUtils.resolvePlaceholders(this.getClass().getPackage().getName()))
                     + "/" + "**/*.class";
 
             final Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
@@ -239,7 +241,7 @@ public class LoggerController extends AbstractTransactionalController<LoggerTO>
                     }
                 }
             }
-            
+
             //SYNCOPE-608
             final EventCategoryTO authenticationControllerEvents = new EventCategoryTO();
             authenticationControllerEvents.setCategory("AuthenticationController");
@@ -270,6 +272,18 @@ public class LoggerController extends AbstractTransactionalController<LoggerTO>
                         pushEventCategoryTO.getEvents().add(resourceOperation.name().toLowerCase());
                     }
 
+                    for (UnmatchingRule unmatching : UnmatchingRule.values()) {
+                        String event = UnmatchingRule.toEventName(unmatching);
+                        syncEventCategoryTO.getEvents().add(event);
+                        pushEventCategoryTO.getEvents().add(event);
+                    }
+
+                    for (MatchingRule matching : MatchingRule.values()) {
+                        String event = MatchingRule.toEventName(matching);
+                        syncEventCategoryTO.getEvents().add(event);
+                        pushEventCategoryTO.getEvents().add(event);
+                    }
+
                     events.add(propEventCategoryTO);
                     events.add(syncEventCategoryTO);
                     events.add(pushEventCategoryTO);

http://git-wip-us.apache.org/repos/asf/syncope/blob/89d5ad43/core/src/main/java/org/apache/syncope/core/sync/impl/AbstractSubjectPushResultHandler.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/syncope/core/sync/impl/AbstractSubjectPushResultHandler.java b/core/src/main/java/org/apache/syncope/core/sync/impl/AbstractSubjectPushResultHandler.java
index f8cb658..e8954e9 100644
--- a/core/src/main/java/org/apache/syncope/core/sync/impl/AbstractSubjectPushResultHandler.java
+++ b/core/src/main/java/org/apache/syncope/core/sync/impl/AbstractSubjectPushResultHandler.java
@@ -132,7 +132,7 @@ public abstract class AbstractSubjectPushResultHandler extends AbstractSyncopeRe
         } else {
             try {
                 if (beforeObj == null) {
-                    operation = profile.getSyncTask().getUnmatchingRule().name().toLowerCase();
+                    operation = UnmatchingRule.toEventName(profile.getSyncTask().getUnmatchingRule());
                     result.setOperation(getResourceOperation(profile.getSyncTask().getUnmatchingRule()));
 
                     switch (profile.getSyncTask().getUnmatchingRule()) {
@@ -172,12 +172,15 @@ public abstract class AbstractSubjectPushResultHandler extends AbstractSyncopeRe
                             }
 
                             break;
+                        case IGNORE:
+                            LOG.debug("Ignored subjectId: {}", subjectId);
+                            break;
                         default:
                         // do nothing
                     }
 
                 } else {
-                    operation = profile.getSyncTask().getMatchingRule().name().toLowerCase();
+                    operation = MatchingRule.toEventName(profile.getSyncTask().getMatchingRule());           
                     result.setOperation(getResourceOperation(profile.getSyncTask().getMatchingRule()));
 
                     switch (profile.getSyncTask().getMatchingRule()) {
@@ -240,6 +243,9 @@ public abstract class AbstractSubjectPushResultHandler extends AbstractSyncopeRe
                             }
 
                             break;
+                        case IGNORE:
+                            LOG.debug("Ignored subjectId: {}", subjectId);
+                            break;
                         default:
                         // do nothing
                     }
@@ -263,7 +269,7 @@ public abstract class AbstractSubjectPushResultHandler extends AbstractSyncopeRe
             } finally {
                 notificationManager.createTasks(
                         AuditElements.EventCategoryType.PUSH,
-                        AttributableType.USER.name().toLowerCase(),
+                        attrUtil.getType().name().toLowerCase(),
                         profile.getSyncTask().getResource().getName(),
                         operation,
                         resultStatus,

http://git-wip-us.apache.org/repos/asf/syncope/blob/89d5ad43/core/src/main/java/org/apache/syncope/core/sync/impl/AbstractSubjectSyncResultHandler.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/syncope/core/sync/impl/AbstractSubjectSyncResultHandler.java b/core/src/main/java/org/apache/syncope/core/sync/impl/AbstractSubjectSyncResultHandler.java
index ff9bc3d..9a51ebe 100644
--- a/core/src/main/java/org/apache/syncope/core/sync/impl/AbstractSubjectSyncResultHandler.java
+++ b/core/src/main/java/org/apache/syncope/core/sync/impl/AbstractSubjectSyncResultHandler.java
@@ -31,7 +31,9 @@ import org.apache.syncope.common.mod.AbstractSubjectMod;
 import org.apache.syncope.common.to.AbstractSubjectTO;
 import org.apache.syncope.common.types.AuditElements;
 import org.apache.syncope.common.types.AuditElements.Result;
+import org.apache.syncope.common.types.MatchingRule;
 import org.apache.syncope.common.types.ResourceOperation;
+import org.apache.syncope.common.types.UnmatchingRule;
 import org.apache.syncope.core.persistence.beans.SyncTask;
 import org.apache.syncope.core.persistence.dao.NotFoundException;
 import org.apache.syncope.core.persistence.dao.UserDAO;
@@ -126,7 +128,7 @@ public abstract class AbstractSubjectSyncResultHandler extends AbstractSyncopeRe
                 _delta = action.beforeAssign(this.getProfile(), _delta, transformed);
             }
 
-            create(transformed, _delta, attrUtil, "assign", result);
+            create(transformed, _delta, attrUtil, UnmatchingRule.toEventName(UnmatchingRule.ASSIGN), result);
         }
 
         return Collections.singletonList(result);
@@ -161,7 +163,7 @@ public abstract class AbstractSubjectSyncResultHandler extends AbstractSyncopeRe
                 _delta = action.beforeProvision(this.getProfile(), _delta, transformed);
             }
 
-            create(transformed, _delta, attrUtil, "provision", result);
+            create(transformed, _delta, attrUtil, UnmatchingRule.toEventName(UnmatchingRule.PROVISION), result);
         }
 
         return Collections.<SyncResult>singletonList(result);
@@ -278,7 +280,7 @@ public abstract class AbstractSubjectSyncResultHandler extends AbstractSyncopeRe
                         resultStatus = Result.FAILURE;
                     }
                 }
-                audit("update", resultStatus, before, output, delta);
+                audit(MatchingRule.toEventName(MatchingRule.UPDATE), resultStatus, before, output, delta);
             }
             updResults.add(result);
         }
@@ -361,7 +363,9 @@ public abstract class AbstractSubjectSyncResultHandler extends AbstractSyncopeRe
                         resultStatus = Result.FAILURE;
                     }
                 }
-                audit(unlink ? "unassign" : "deprovision", resultStatus, before, output, delta);
+                audit(unlink
+                        ? MatchingRule.toEventName(MatchingRule.UNASSIGN)
+                        : MatchingRule.toEventName(MatchingRule.DEPROVISION), resultStatus, before, output, delta);
             }
             updResults.add(result);
         }
@@ -444,7 +448,8 @@ public abstract class AbstractSubjectSyncResultHandler extends AbstractSyncopeRe
                         resultStatus = Result.FAILURE;
                     }
                 }
-                audit(unlink ? "unlink" : "link", resultStatus, before, output, delta);
+                audit(unlink ? MatchingRule.toEventName(MatchingRule.UNLINK)
+                        : MatchingRule.toEventName(MatchingRule.LINK), resultStatus, before, output, delta);
             }
             updResults.add(result);
         }
@@ -500,7 +505,7 @@ public abstract class AbstractSubjectSyncResultHandler extends AbstractSyncopeRe
                         action.after(this.getProfile(), delta, before, result);
                     }
 
-                    audit("delete", resultStatus, before, output, delta);
+                    audit(ResourceOperation.DELETE.name().toLowerCase(), resultStatus, before, output, delta);
                 }
 
                 delResults.add(result);
@@ -517,6 +522,30 @@ public abstract class AbstractSubjectSyncResultHandler extends AbstractSyncopeRe
         return delResults;
     }
 
+    private List<SyncResult> ignore(SyncDelta delta, final AttributableUtil attrUtil, final boolean matching)
+            throws JobExecutionException {
+
+        LOG.debug("Subject to ignore {}", delta.getObject().getUid().getUidValue());
+
+        final List<SyncResult> ignoreResults = new ArrayList<SyncResult>();
+        final SyncResult result = new SyncResult();
+
+        result.setId(null);
+        result.setName(delta.getObject().getUid().getUidValue());
+        result.setOperation(ResourceOperation.NONE);
+        result.setSubjectType(attrUtil.getType());
+        result.setStatus(SyncResult.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 user(s)/role(s).
      *
@@ -566,6 +595,9 @@ public abstract class AbstractSubjectSyncResultHandler extends AbstractSyncopeRe
                         case PROVISION:
                             profile.getResults().addAll(create(delta, attrUtil));
                             break;
+                        case IGNORE:
+                            profile.getResults().addAll(ignore(delta, attrUtil, false));
+                            break;
                         default:
                         // do nothing
                     }
@@ -586,6 +618,9 @@ public abstract class AbstractSubjectSyncResultHandler extends AbstractSyncopeRe
                         case UNLINK:
                             profile.getResults().addAll(link(delta, subjectIds, attrUtil, true));
                             break;
+                        case IGNORE:
+                            profile.getResults().addAll(ignore(delta, attrUtil, true));
+                            break;
                         default:
                         // do nothing
                     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/89d5ad43/core/src/main/java/org/apache/syncope/core/sync/impl/AbstractSyncJob.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/syncope/core/sync/impl/AbstractSyncJob.java b/core/src/main/java/org/apache/syncope/core/sync/impl/AbstractSyncJob.java
index cabeb4f..0124082 100644
--- a/core/src/main/java/org/apache/syncope/core/sync/impl/AbstractSyncJob.java
+++ b/core/src/main/java/org/apache/syncope/core/sync/impl/AbstractSyncJob.java
@@ -116,12 +116,14 @@ public abstract class AbstractSyncJob<T extends AbstractSyncTask, A extends Abst
         List<SyncResult> uFailUpdate = new ArrayList<SyncResult>();
         List<SyncResult> uSuccDelete = new ArrayList<SyncResult>();
         List<SyncResult> uFailDelete = new ArrayList<SyncResult>();
+        List<SyncResult> uSuccNone = new ArrayList<SyncResult>();
         List<SyncResult> rSuccCreate = new ArrayList<SyncResult>();
         List<SyncResult> rFailCreate = new ArrayList<SyncResult>();
         List<SyncResult> rSuccUpdate = new ArrayList<SyncResult>();
         List<SyncResult> rFailUpdate = new ArrayList<SyncResult>();
         List<SyncResult> rSuccDelete = new ArrayList<SyncResult>();
         List<SyncResult> rFailDelete = new ArrayList<SyncResult>();
+        List<SyncResult> rSuccNone = new ArrayList<SyncResult>();
 
         for (SyncResult syncResult : syncResults) {
             switch (syncResult.getStatus()) {
@@ -169,6 +171,20 @@ public abstract class AbstractSyncJob<T extends AbstractSyncTask, A extends Abst
                             }
                             break;
 
+                        case NONE:
+                            switch (syncResult.getSubjectType()) {
+                                case USER:
+                                    uSuccNone.add(syncResult);
+                                    break;
+
+                                case ROLE:
+                                    rSuccNone.add(syncResult);
+                                    break;
+
+                                default:
+                            }
+                            break;
+
                         default:
                     }
                     break;
@@ -232,13 +248,16 @@ public abstract class AbstractSyncJob<T extends AbstractSyncTask, A extends Abst
                 append("[updated/failures]: ").append(uSuccUpdate.size()).append('/').append(uFailUpdate.size()).
                 append(' ').
                 append("[deleted/failures]: ").append(uSuccDelete.size()).append('/').append(uFailDelete.size()).
-                append('\n');
+                append(' ').
+                append("[ignored]: ").append(uSuccNone.size()).append('\n');
         report.append("Roles ").
                 append("[created/failures]: ").append(rSuccCreate.size()).append('/').append(rFailCreate.size()).
                 append(' ').
                 append("[updated/failures]: ").append(rSuccUpdate.size()).append('/').append(rFailUpdate.size()).
                 append(' ').
-                append("[deleted/failures]: ").append(rSuccDelete.size()).append('/').append(rFailDelete.size());
+                append("[deleted/failures]: ").append(rSuccDelete.size()).append('/').append(rFailDelete.size()).
+                append(' ').
+                append("[ignored]: ").append(rSuccNone.size());
 
         // Failures
         if (syncTraceLevel == TraceLevel.FAILURES || syncTraceLevel == TraceLevel.ALL) {
@@ -276,13 +295,17 @@ public abstract class AbstractSyncJob<T extends AbstractSyncTask, A extends Abst
                     .append("\nUsers updated:\n")
                     .append(SyncResult.produceReport(uSuccUpdate, syncTraceLevel))
                     .append("\nUsers deleted:\n")
-                    .append(SyncResult.produceReport(uSuccDelete, syncTraceLevel));
+                    .append(SyncResult.produceReport(uSuccDelete, syncTraceLevel))
+                    .append("\nUsers ignored:\n")
+                    .append(SyncResult.produceReport(uSuccNone, syncTraceLevel));
             report.append("\n\nRoles created:\n")
                     .append(SyncResult.produceReport(rSuccCreate, syncTraceLevel))
                     .append("\nRoles updated:\n")
                     .append(SyncResult.produceReport(rSuccUpdate, syncTraceLevel))
                     .append("\nRoles deleted:\n")
-                    .append(SyncResult.produceReport(rSuccDelete, syncTraceLevel));
+                    .append(SyncResult.produceReport(rSuccDelete, syncTraceLevel))
+                    .append("\nRoles ignored:\n")
+                    .append(SyncResult.produceReport(rSuccNone, syncTraceLevel));
         }
 
         return report.toString();
@@ -316,7 +339,7 @@ public abstract class AbstractSyncJob<T extends AbstractSyncTask, A extends Abst
             } catch (Exception e) {
                 final String msg = String.
                         format("Connector instance bean for resource %s and connInstance %s not found",
-                        syncTask.getResource(), syncTask.getResource().getConnector());
+                                syncTask.getResource(), syncTask.getResource().getConnector());
 
                 throw new JobExecutionException(msg, e);
             }

http://git-wip-us.apache.org/repos/asf/syncope/blob/89d5ad43/core/src/test/java/org/apache/syncope/core/rest/TaskTestITCase.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/syncope/core/rest/TaskTestITCase.java b/core/src/test/java/org/apache/syncope/core/rest/TaskTestITCase.java
index 468b8f9..0cc137a 100644
--- a/core/src/test/java/org/apache/syncope/core/rest/TaskTestITCase.java
+++ b/core/src/test/java/org/apache/syncope/core/rest/TaskTestITCase.java
@@ -1385,4 +1385,48 @@ public class TaskTestITCase extends AbstractTest {
             }
         }
     }
+
+    @Test
+    public void issueSYNCOPE648() {
+        //1. Create Push Task
+        final PushTaskTO task = new PushTaskTO();
+        task.setName("Test create Push");
+        task.setResource(RESOURCE_NAME_LDAP);
+        task.setUserFilter(
+                SyncopeClient.getUserSearchConditionBuilder().is("username").equalTo("_NO_ONE_").query());
+        task.setRoleFilter(
+                SyncopeClient.getRoleSearchConditionBuilder().is("name").equalTo("citizen").query());
+        task.setMatchingRule(MatchingRule.IGNORE);
+        task.setUnmatchingRule(UnmatchingRule.IGNORE);
+
+        final Response response = taskService.create(task);
+        final PushTaskTO actual = getObject(response.getLocation(), TaskService.class, PushTaskTO.class);
+        assertNotNull(actual);
+
+        // 2. Create notification
+        NotificationTO notification = new NotificationTO();
+        notification.setTraceLevel(TraceLevel.FAILURES);
+        notification.getEvents().add("[PushTask]:[role]:[resource-ldap]:[matchingrule_ignore]:[SUCCESS]");
+        notification.getEvents().add("[PushTask]:[role]:[resource-ldap]:[unmatchingrule_ignore]:[SUCCESS]");
+
+        notification.getStaticRecipients().add("issueyncope648@syncope.apache.org");
+        notification.setSelfAsRecipient(false);
+        notification.setRecipientAttrName("email");
+        notification.setRecipientAttrType(IntMappingType.UserSchema);
+
+        notification.setSender("syncope648@syncope.apache.org");
+        String subject = "Test notification";
+        notification.setSubject(subject);
+        notification.setTemplate("optin");
+        notification.setActive(true);
+
+        Response responseNotification = notificationService.create(notification);
+        notification = getObject(responseNotification.getLocation(), NotificationService.class, NotificationTO.class);
+        assertNotNull(notification);
+
+        execSyncTask(actual.getId(), 50, false);
+        
+        NotificationTaskTO taskTO = findNotificationTaskBySender("syncope648@syncope.apache.org");
+        assertNotNull(taskTO);
+    }
 }


[5/5] syncope git commit: [SYNCOPE-648]Merge from 1_2_X

Posted by md...@apache.org.
[SYNCOPE-648]Merge from 1_2_X


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

Branch: refs/heads/master
Commit: b606a4b87fa00140d24dfcecac3681734955acfb
Parents: 53721b8 12570a0
Author: Marco Di Sabatino Di Diodoro <md...@apache.org>
Authored: Wed Mar 18 09:48:51 2015 +0100
Committer: Marco Di Sabatino Di Diodoro <md...@apache.org>
Committed: Wed Mar 18 09:48:51 2015 +0100

----------------------------------------------------------------------

----------------------------------------------------------------------



[4/5] syncope git commit: [SYNCOPE-648]Add getAttributableUtil in User/Role PushResultHandler

Posted by md...@apache.org.
[SYNCOPE-648]Add getAttributableUtil in User/Role PushResultHandler


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

Branch: refs/heads/master
Commit: 12570a042846044c41fea54c8c6d27daf280de0d
Parents: 89d5ad4
Author: Marco Di Sabatino Di Diodoro <md...@apache.org>
Authored: Wed Mar 18 09:36:03 2015 +0100
Committer: Marco Di Sabatino Di Diodoro <md...@apache.org>
Committed: Wed Mar 18 09:36:03 2015 +0100

----------------------------------------------------------------------
 .../core/sync/impl/AbstractSubjectPushResultHandler.java    | 9 +++++----
 .../syncope/core/sync/impl/RolePushResultHandler.java       | 7 +++++++
 .../syncope/core/sync/impl/UserPushResultHandler.java       | 7 +++++++
 3 files changed, 19 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/12570a04/core/src/main/java/org/apache/syncope/core/sync/impl/AbstractSubjectPushResultHandler.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/syncope/core/sync/impl/AbstractSubjectPushResultHandler.java b/core/src/main/java/org/apache/syncope/core/sync/impl/AbstractSubjectPushResultHandler.java
index e8954e9..9ea22fd 100644
--- a/core/src/main/java/org/apache/syncope/core/sync/impl/AbstractSubjectPushResultHandler.java
+++ b/core/src/main/java/org/apache/syncope/core/sync/impl/AbstractSubjectPushResultHandler.java
@@ -52,6 +52,8 @@ import org.springframework.transaction.annotation.Transactional;
 
 public abstract class AbstractSubjectPushResultHandler extends AbstractSyncopeResultHandler<PushTask, PushActions> {
 
+    protected abstract AttributableUtil getAttributableUtil();
+
     protected abstract String getName(final AbstractSubject subject);
 
     protected abstract AbstractMapping getMapping();
@@ -114,7 +116,6 @@ public abstract class AbstractSubjectPushResultHandler extends AbstractSyncopeRe
         String operation = null;
 
         // Try to read remote object (user / group) BEFORE any actual operation
-
         final String accountId = MappingUtil.getAccountIdValue(
                 subject, profile.getSyncTask().getResource(), getMapping().getAccountIdItem());
 
@@ -180,7 +181,7 @@ public abstract class AbstractSubjectPushResultHandler extends AbstractSyncopeRe
                     }
 
                 } else {
-                    operation = MatchingRule.toEventName(profile.getSyncTask().getMatchingRule());           
+                    operation = MatchingRule.toEventName(profile.getSyncTask().getMatchingRule());
                     result.setOperation(getResourceOperation(profile.getSyncTask().getMatchingRule()));
 
                     switch (profile.getSyncTask().getMatchingRule()) {
@@ -269,7 +270,7 @@ public abstract class AbstractSubjectPushResultHandler extends AbstractSyncopeRe
             } finally {
                 notificationManager.createTasks(
                         AuditElements.EventCategoryType.PUSH,
-                        attrUtil.getType().name().toLowerCase(),
+                        getAttributableUtil().getType().name().toLowerCase(),
                         profile.getSyncTask().getResource().getName(),
                         operation,
                         resultStatus,
@@ -278,7 +279,7 @@ public abstract class AbstractSubjectPushResultHandler extends AbstractSyncopeRe
                         subject);
                 auditManager.audit(
                         AuditElements.EventCategoryType.PUSH,
-                        AttributableType.USER.name().toLowerCase(),
+                        getAttributableUtil().getType().name().toLowerCase(),
                         profile.getSyncTask().getResource().getName(),
                         operation,
                         resultStatus,

http://git-wip-us.apache.org/repos/asf/syncope/blob/12570a04/core/src/main/java/org/apache/syncope/core/sync/impl/RolePushResultHandler.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/syncope/core/sync/impl/RolePushResultHandler.java b/core/src/main/java/org/apache/syncope/core/sync/impl/RolePushResultHandler.java
index c29b49e..18f7008 100644
--- a/core/src/main/java/org/apache/syncope/core/sync/impl/RolePushResultHandler.java
+++ b/core/src/main/java/org/apache/syncope/core/sync/impl/RolePushResultHandler.java
@@ -24,6 +24,7 @@ import java.util.List;
 import org.apache.syncope.common.mod.RoleMod;
 import org.apache.syncope.common.to.AbstractSubjectTO;
 import org.apache.syncope.common.to.RoleTO;
+import org.apache.syncope.common.types.AttributableType;
 import org.apache.syncope.common.types.ResourceOperation;
 import org.apache.syncope.core.persistence.beans.AbstractMapping;
 import org.apache.syncope.core.persistence.beans.AbstractMappingItem;
@@ -31,6 +32,7 @@ import org.apache.syncope.core.persistence.beans.AbstractSubject;
 import org.apache.syncope.core.persistence.beans.role.SyncopeRole;
 import org.apache.syncope.core.propagation.PropagationByResource;
 import org.apache.syncope.core.propagation.TimeoutException;
+import org.apache.syncope.core.util.AttributableUtil;
 import org.identityconnectors.framework.common.objects.ConnectorObject;
 import org.identityconnectors.framework.common.objects.ObjectClass;
 import org.identityconnectors.framework.common.objects.Uid;
@@ -38,6 +40,11 @@ import org.identityconnectors.framework.common.objects.Uid;
 public class RolePushResultHandler extends AbstractSubjectPushResultHandler {
 
     @Override
+    protected AttributableUtil getAttributableUtil() {
+        return AttributableUtil.getInstance(AttributableType.ROLE);
+    }
+
+    @Override
     protected AbstractSubject deprovision(final AbstractSubject sbj) {
         final RoleTO before = roleDataBinder.getRoleTO(SyncopeRole.class.cast(sbj));
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/12570a04/core/src/main/java/org/apache/syncope/core/sync/impl/UserPushResultHandler.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/syncope/core/sync/impl/UserPushResultHandler.java b/core/src/main/java/org/apache/syncope/core/sync/impl/UserPushResultHandler.java
index 4637692..a608c6a 100644
--- a/core/src/main/java/org/apache/syncope/core/sync/impl/UserPushResultHandler.java
+++ b/core/src/main/java/org/apache/syncope/core/sync/impl/UserPushResultHandler.java
@@ -24,6 +24,7 @@ import java.util.List;
 import org.apache.syncope.common.mod.UserMod;
 import org.apache.syncope.common.to.AbstractSubjectTO;
 import org.apache.syncope.common.to.UserTO;
+import org.apache.syncope.common.types.AttributableType;
 import org.apache.syncope.common.types.ResourceOperation;
 import org.apache.syncope.core.persistence.beans.AbstractMapping;
 import org.apache.syncope.core.persistence.beans.AbstractMappingItem;
@@ -31,6 +32,7 @@ import org.apache.syncope.core.persistence.beans.AbstractSubject;
 import org.apache.syncope.core.persistence.beans.user.SyncopeUser;
 import org.apache.syncope.core.propagation.PropagationByResource;
 import org.apache.syncope.core.propagation.TimeoutException;
+import org.apache.syncope.core.util.AttributableUtil;
 import org.identityconnectors.framework.common.objects.ConnectorObject;
 import org.identityconnectors.framework.common.objects.ObjectClass;
 import org.identityconnectors.framework.common.objects.Uid;
@@ -38,6 +40,11 @@ import org.identityconnectors.framework.common.objects.Uid;
 public class UserPushResultHandler extends AbstractSubjectPushResultHandler {
 
     @Override
+    protected AttributableUtil getAttributableUtil() {
+        return AttributableUtil.getInstance(AttributableType.USER);
+    }
+
+    @Override
     protected AbstractSubject deprovision(final AbstractSubject sbj) {
         final UserTO before = userDataBinder.getUserTO(sbj.getId());
 


[2/5] syncope git commit: [SYNCOPE-648]Merge from 1_2_X

Posted by md...@apache.org.
http://git-wip-us.apache.org/repos/asf/syncope/blob/53721b82/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/RolePushResultHandlerImpl.java
----------------------------------------------------------------------
diff --cc core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/RolePushResultHandlerImpl.java
index d51b26f,0000000..c160556
mode 100644,000000..100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/RolePushResultHandlerImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/RolePushResultHandlerImpl.java
@@@ -1,155 -1,0 +1,162 @@@
 +/*
 + * 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.sync;
 +
 +import java.util.ArrayList;
 +import java.util.Collections;
 +import java.util.List;
 +import org.apache.syncope.common.lib.mod.RoleMod;
 +import org.apache.syncope.common.lib.to.AbstractSubjectTO;
 +import org.apache.syncope.common.lib.to.RoleTO;
++import org.apache.syncope.common.lib.types.AttributableType;
 +import org.apache.syncope.common.lib.types.PropagationByResource;
 +import org.apache.syncope.common.lib.types.ResourceOperation;
++import org.apache.syncope.core.persistence.api.entity.AttributableUtil;
 +import org.apache.syncope.core.persistence.api.entity.Mapping;
 +import org.apache.syncope.core.persistence.api.entity.MappingItem;
 +import org.apache.syncope.core.persistence.api.entity.Subject;
 +import org.apache.syncope.core.persistence.api.entity.role.Role;
 +import org.apache.syncope.core.provisioning.api.TimeoutException;
 +import org.apache.syncope.core.provisioning.api.sync.RolePushResultHandler;
 +import org.identityconnectors.framework.common.objects.ConnectorObject;
 +import org.identityconnectors.framework.common.objects.ObjectClass;
 +import org.identityconnectors.framework.common.objects.Uid;
 +
 +public class RolePushResultHandlerImpl extends AbstractPushResultHandler implements RolePushResultHandler {
 +
 +    @Override
++    protected AttributableUtil getAttributableUtil() {
++        return attrUtilFactory.getInstance(AttributableType.ROLE);
++    }
++
++    @Override
 +    protected Subject<?, ?, ?> deprovision(final Subject<?, ?, ?> sbj) {
 +        final RoleTO before = roleTransfer.getRoleTO(Role.class.cast(sbj));
 +
 +        final List<String> noPropResources = new ArrayList<>(before.getResources());
 +        noPropResources.remove(profile.getTask().getResource().getKey());
 +
 +        taskExecutor.execute(propagationManager.getRoleDeleteTaskIds(before.getKey(), noPropResources));
 +
 +        return roleDAO.authFetch(before.getKey());
 +    }
 +
 +    @Override
 +    protected Subject<?, ?, ?> provision(final Subject<?, ?, ?> sbj, final Boolean enabled) {
 +        final RoleTO before = roleTransfer.getRoleTO(Role.class.cast(sbj));
 +
 +        final List<String> noPropResources = new ArrayList<>(before.getResources());
 +        noPropResources.remove(profile.getTask().getResource().getKey());
 +
 +        final PropagationByResource propByRes = new PropagationByResource();
 +        propByRes.add(ResourceOperation.CREATE, profile.getTask().getResource().getKey());
 +
 +        taskExecutor.execute(propagationManager.getRoleCreateTaskIds(
 +                before.getKey(),
 +                Collections.unmodifiableCollection(before.getVirAttrs()),
 +                propByRes,
 +                noPropResources));
 +
 +        return roleDAO.authFetch(before.getKey());
 +    }
 +
 +    @Override
 +    protected Subject<?, ?, ?> link(final Subject<?, ?, ?> sbj, final Boolean unlink) {
 +        final RoleMod roleMod = new RoleMod();
 +        roleMod.setKey(sbj.getKey());
 +
 +        if (unlink) {
 +            roleMod.getResourcesToRemove().add(profile.getTask().getResource().getKey());
 +        } else {
 +            roleMod.getResourcesToAdd().add(profile.getTask().getResource().getKey());
 +        }
 +
 +        rwfAdapter.update(roleMod);
 +
 +        return roleDAO.authFetch(sbj.getKey());
 +    }
 +
 +    @Override
 +    protected Subject<?, ?, ?> unassign(final Subject<?, ?, ?> sbj) {
 +        final RoleMod roleMod = new RoleMod();
 +        roleMod.setKey(sbj.getKey());
 +        roleMod.getResourcesToRemove().add(profile.getTask().getResource().getKey());
 +        rwfAdapter.update(roleMod);
 +        return deprovision(sbj);
 +    }
 +
 +    @Override
 +    protected Subject<?, ?, ?> assign(final Subject<?, ?, ?> sbj, final Boolean enabled) {
 +        final RoleMod roleMod = new RoleMod();
 +        roleMod.setKey(sbj.getKey());
 +        roleMod.getResourcesToAdd().add(profile.getTask().getResource().getKey());
 +        rwfAdapter.update(roleMod);
 +        return provision(sbj, enabled);
 +    }
 +
 +    @Override
 +    protected String getName(final Subject<?, ?, ?> subject) {
 +        return Role.class.cast(subject).getName();
 +    }
 +
 +    @Override
 +    protected AbstractSubjectTO getSubjectTO(final long key) {
 +        try {
 +            return roleTransfer.getRoleTO(key);
 +        } catch (Exception e) {
 +            LOG.warn("Error retrieving user {}", key, e);
 +            return null;
 +        }
 +    }
 +
 +    @Override
 +    protected Subject<?, ?, ?> getSubject(final long key) {
 +        try {
 +            return roleDAO.authFetch(key);
 +        } catch (Exception e) {
 +            LOG.warn("Error retrieving role {}", key, e);
 +            return null;
 +        }
 +    }
 +
 +    @Override
 +    protected ConnectorObject getRemoteObject(final String accountId) {
 +        ConnectorObject obj = null;
 +
 +        try {
 +            final Uid uid = new Uid(accountId);
 +
 +            obj = profile.getConnector().getObject(
 +                    ObjectClass.GROUP,
 +                    uid,
 +                    profile.getConnector().getOperationOptions(Collections.<MappingItem>emptySet()));
 +        } catch (TimeoutException toe) {
 +            LOG.debug("Request timeout", toe);
 +            throw toe;
 +        } catch (RuntimeException ignore) {
 +            LOG.debug("While resolving {}", accountId, ignore);
 +        }
 +        return obj;
 +    }
 +
 +    @Override
 +    protected Mapping<?> getMapping() {
 +        return profile.getTask().getResource().getRmapping();
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/53721b82/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/UserPushResultHandlerImpl.java
----------------------------------------------------------------------
diff --cc core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/UserPushResultHandlerImpl.java
index 0c1ad8b,0000000..61637a4
mode 100644,000000..100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/UserPushResultHandlerImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/UserPushResultHandlerImpl.java
@@@ -1,160 -1,0 +1,167 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one
 + * or more contributor license agreements.  See the NOTICE file
 + * distributed with this work for additional information
 + * regarding copyright ownership.  The ASF licenses this file
 + * to you under the Apache License, Version 2.0 (the
 + * "License"); you may not use this file except in compliance
 + * with the License.  You may obtain a copy of the License at
 + *
 + *   http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing,
 + * software distributed under the License is distributed on an
 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + * KIND, either express or implied.  See the License for the
 + * specific language governing permissions and limitations
 + * under the License.
 + */
 +package org.apache.syncope.core.provisioning.java.sync;
 +
 +import java.util.ArrayList;
 +import java.util.Collections;
 +import java.util.List;
 +import org.apache.syncope.common.lib.mod.UserMod;
 +import org.apache.syncope.common.lib.to.AbstractSubjectTO;
 +import org.apache.syncope.common.lib.to.UserTO;
++import org.apache.syncope.common.lib.types.AttributableType;
 +import org.apache.syncope.common.lib.types.PropagationByResource;
 +import org.apache.syncope.common.lib.types.ResourceOperation;
++import org.apache.syncope.core.persistence.api.entity.AttributableUtil;
 +import org.apache.syncope.core.persistence.api.entity.Mapping;
 +import org.apache.syncope.core.persistence.api.entity.MappingItem;
 +import org.apache.syncope.core.persistence.api.entity.Subject;
 +import org.apache.syncope.core.persistence.api.entity.user.User;
 +import org.apache.syncope.core.provisioning.api.TimeoutException;
 +import org.apache.syncope.core.provisioning.api.sync.UserPushResultHandler;
 +import org.identityconnectors.framework.common.objects.ConnectorObject;
 +import org.identityconnectors.framework.common.objects.ObjectClass;
 +import org.identityconnectors.framework.common.objects.Uid;
 +
 +public class UserPushResultHandlerImpl extends AbstractPushResultHandler implements UserPushResultHandler {
 +
 +    @Override
++    protected AttributableUtil getAttributableUtil() {
++        return attrUtilFactory.getInstance(AttributableType.USER);
++    }
++
++    @Override
 +    protected Subject<?, ?, ?> deprovision(final Subject<?, ?, ?> sbj) {
 +        final UserTO before = userTransfer.getUserTO(sbj.getKey());
 +
 +        final List<String> noPropResources = new ArrayList<>(before.getResources());
 +        noPropResources.remove(profile.getTask().getResource().getKey());
 +
 +        taskExecutor.execute(propagationManager.getUserDeleteTaskIds(before.getKey(),
 +                Collections.singleton(profile.getTask().getResource().getKey()), noPropResources));
 +
 +        return userDAO.authFetch(before.getKey());
 +    }
 +
 +    @Override
 +    protected Subject<?, ?, ?> provision(final Subject<?, ?, ?> sbj, final Boolean enabled) {
 +        final UserTO before = userTransfer.getUserTO(sbj.getKey());
 +
 +        final List<String> noPropResources = new ArrayList<>(before.getResources());
 +        noPropResources.remove(profile.getTask().getResource().getKey());
 +
 +        final PropagationByResource propByRes = new PropagationByResource();
 +        propByRes.add(ResourceOperation.CREATE, profile.getTask().getResource().getKey());
 +
 +        taskExecutor.execute(propagationManager.getUserCreateTaskIds(
 +                before.getKey(),
 +                enabled,
 +                propByRes,
 +                null,
 +                Collections.unmodifiableCollection(before.getVirAttrs()),
 +                Collections.unmodifiableCollection(before.getMemberships()),
 +                noPropResources));
 +
 +        return userDAO.authFetch(before.getKey());
 +    }
 +
 +    @Override
 +    protected Subject<?, ?, ?> link(final Subject<?, ?, ?> sbj, final Boolean unlink) {
 +        final UserMod userMod = new UserMod();
 +        userMod.setKey(sbj.getKey());
 +
 +        if (unlink) {
 +            userMod.getResourcesToRemove().add(profile.getTask().getResource().getKey());
 +        } else {
 +            userMod.getResourcesToAdd().add(profile.getTask().getResource().getKey());
 +        }
 +
 +        uwfAdapter.update(userMod);
 +
 +        return userDAO.authFetch(userMod.getKey());
 +    }
 +
 +    @Override
 +    protected Subject<?, ?, ?> unassign(final Subject<?, ?, ?> sbj) {
 +        final UserMod userMod = new UserMod();
 +        userMod.setKey(sbj.getKey());
 +        userMod.getResourcesToRemove().add(profile.getTask().getResource().getKey());
 +        uwfAdapter.update(userMod);
 +        return deprovision(sbj);
 +    }
 +
 +    @Override
 +    protected Subject<?, ?, ?> assign(final Subject<?, ?, ?> sbj, final Boolean enabled) {
 +        final UserMod userMod = new UserMod();
 +        userMod.setKey(sbj.getKey());
 +        userMod.getResourcesToAdd().add(profile.getTask().getResource().getKey());
 +        uwfAdapter.update(userMod);
 +        return provision(sbj, enabled);
 +    }
 +
 +    @Override
 +    protected String getName(final Subject<?, ?, ?> subject) {
 +        return User.class.cast(subject).getUsername();
 +    }
 +
 +    @Override
 +    protected AbstractSubjectTO getSubjectTO(final long key) {
 +        try {
 +            return userTransfer.getUserTO(key);
 +        } catch (Exception e) {
 +            LOG.warn("Error retrieving user {}", key, e);
 +            return null;
 +        }
 +    }
 +
 +    @Override
 +    protected Subject<?, ?, ?> getSubject(final long key) {
 +        try {
 +            return userDAO.authFetch(key);
 +        } catch (Exception e) {
 +            LOG.warn("Error retrieving user {}", key, e);
 +            return null;
 +        }
 +    }
 +
 +    @Override
 +    protected ConnectorObject getRemoteObject(final String accountId) {
 +        ConnectorObject obj = null;
 +
 +        try {
 +            final Uid uid = new Uid(accountId);
 +
 +            obj = profile.getConnector().getObject(
 +                    ObjectClass.ACCOUNT,
 +                    uid,
 +                    profile.getConnector().getOperationOptions(Collections.<MappingItem>emptySet()));
 +
 +        } catch (TimeoutException toe) {
 +            LOG.debug("Request timeout", toe);
 +            throw toe;
 +        } catch (RuntimeException ignore) {
 +            LOG.debug("While resolving {}", accountId, ignore);
 +        }
 +        return obj;
 +    }
 +
 +    @Override
 +    protected Mapping<?> getMapping() {
 +        return profile.getTask().getResource().getUmapping();
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/53721b82/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/AbstractTaskITCase.java
----------------------------------------------------------------------
diff --cc fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/AbstractTaskITCase.java
index 6ba1dd8,0000000..669d088
mode 100644,000000..100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/AbstractTaskITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/AbstractTaskITCase.java
@@@ -1,137 -1,0 +1,155 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one
 + * or more contributor license agreements.  See the NOTICE file
 + * distributed with this work for additional information
 + * regarding copyright ownership.  The ASF licenses this file
 + * to you under the Apache License, Version 2.0 (the
 + * "License"); you may not use this file except in compliance
 + * with the License.  You may obtain a copy of the License at
 + *
 + *   http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing,
 + * software distributed under the License is distributed on an
 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + * KIND, either express or implied.  See the License for the
 + * specific language governing permissions and limitations
 + * under the License.
 + */
 +package org.apache.syncope.fit.core.reference;
 +
++import static org.apache.syncope.fit.core.reference.AbstractITCase.taskService;
 +import static org.junit.Assert.assertEquals;
++import static org.junit.Assert.assertFalse;
 +import static org.junit.Assert.assertNotNull;
 +import static org.junit.Assert.fail;
 +
 +import java.util.ArrayList;
 +import java.util.HashMap;
 +import java.util.List;
 +import java.util.Map;
 +import java.util.Set;
 +import java.util.concurrent.Callable;
 +import java.util.concurrent.ExecutorService;
 +import java.util.concurrent.Executors;
 +import java.util.concurrent.Future;
 +import java.util.concurrent.TimeUnit;
 +import org.apache.syncope.common.lib.to.AbstractTaskTO;
++import org.apache.syncope.common.lib.to.NotificationTaskTO;
++import org.apache.syncope.common.lib.to.PagedResult;
 +import org.apache.syncope.common.lib.to.TaskExecTO;
 +import org.apache.syncope.common.lib.to.UserTO;
++import org.apache.syncope.common.lib.types.TaskType;
 +
 +public abstract class AbstractTaskITCase extends AbstractITCase {
 +
 +    protected static final Long SYNC_TASK_ID = 4L;
 +
 +    protected static final Long SCHED_TASK_ID = 5L;
 +
 +    protected static class ThreadExec implements Callable<TaskExecTO> {
 +
 +        private final AbstractTaskITCase test;
 +
 +        private final Long taskKey;
 +
 +        private final int maxWaitSeconds;
 +
 +        private final boolean dryRun;
 +
 +        public ThreadExec(AbstractTaskITCase test, Long taskKey, int maxWaitSeconds, boolean dryRun) {
 +            this.test = test;
 +            this.taskKey = taskKey;
 +            this.maxWaitSeconds = maxWaitSeconds;
 +            this.dryRun = dryRun;
 +        }
 +
 +        @Override
 +        public TaskExecTO call() throws Exception {
 +            return test.execSyncTask(taskKey, maxWaitSeconds, dryRun);
 +        }
 +    }
 +
 +    /**
 +     * Remove initial and synchronized users to make test re-runnable.
 +     */
 +    protected void removeTestUsers() {
 +        for (int i = 0; i < 10; i++) {
 +            String cUserName = "test" + i;
 +            try {
 +                UserTO cUserTO = readUser(cUserName);
 +                userService.delete(cUserTO.getKey());
 +            } catch (Exception e) {
 +                // Ignore
 +            }
 +        }
 +    }
 +
 +    protected TaskExecTO execSyncTask(final Long taskKey, final int maxWaitSeconds, final boolean dryRun) {
 +        AbstractTaskTO taskTO = taskService.read(taskKey);
 +        assertNotNull(taskTO);
 +        assertNotNull(taskTO.getExecutions());
 +
 +        int preSyncSize = taskTO.getExecutions().size();
 +        TaskExecTO execution = taskService.execute(taskTO.getKey(), dryRun);
 +        assertEquals("JOB_FIRED", execution.getStatus());
 +
 +        int i = 0;
 +        int maxit = maxWaitSeconds;
 +
 +        // wait for sync completion (executions incremented)
 +        do {
 +            try {
 +                Thread.sleep(1000);
 +            } catch (InterruptedException e) {
 +            }
 +
 +            taskTO = taskService.read(taskTO.getKey());
 +
 +            assertNotNull(taskTO);
 +            assertNotNull(taskTO.getExecutions());
 +
 +            i++;
 +        } while (preSyncSize == taskTO.getExecutions().size() && i < maxit);
 +        if (i == maxit) {
 +            fail("Timeout when executing task " + taskKey);
 +        }
 +        return taskTO.getExecutions().get(taskTO.getExecutions().size() - 1);
 +    }
 +
 +    protected Map<Long, TaskExecTO> execSyncTasks(
 +            final Set<Long> taskKeys, final int maxWaitSeconds, final boolean dryRun) throws Exception {
 +
 +        final ExecutorService service = Executors.newFixedThreadPool(taskKeys.size());
 +        final List<Future<TaskExecTO>> futures = new ArrayList<>();
 +
 +        for (Long key : taskKeys) {
 +            futures.add(service.submit(new ThreadExec(this, key, maxWaitSeconds, dryRun)));
 +        }
 +
 +        final Map<Long, TaskExecTO> res = new HashMap<>();
 +
 +        for (Future<TaskExecTO> future : futures) {
 +            TaskExecTO taskExecTO = future.get(100, TimeUnit.SECONDS);
 +            res.put(taskExecTO.getTask(), taskExecTO);
 +        }
 +
 +        service.shutdownNow();
 +
 +        return res;
 +    }
 +
++    protected NotificationTaskTO findNotificationTaskBySender(final String sender) {
++        PagedResult<NotificationTaskTO> tasks = taskService.list(TaskType.NOTIFICATION);
++        assertNotNull(tasks);
++        assertFalse(tasks.getResult().isEmpty());
++        NotificationTaskTO taskTO = null;
++        for (NotificationTaskTO task : tasks.getResult()) {
++            if (sender.equals(task.getSender())) {
++                taskTO = task;
++            }
++        }
++        return taskTO;
++    }
++
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/53721b82/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/NotificationTaskITCase.java
----------------------------------------------------------------------
diff --cc fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/NotificationTaskITCase.java
index 87a49fb,0000000..ee75de9
mode 100644,000000..100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/NotificationTaskITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/NotificationTaskITCase.java
@@@ -1,155 -1,0 +1,140 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one
 + * or more contributor license agreements.  See the NOTICE file
 + * distributed with this work for additional information
 + * regarding copyright ownership.  The ASF licenses this file
 + * to you under the Apache License, Version 2.0 (the
 + * "License"); you may not use this file except in compliance
 + * with the License.  You may obtain a copy of the License at
 + *
 + *   http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing,
 + * software distributed under the License is distributed on an
 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + * KIND, either express or implied.  See the License for the
 + * specific language governing permissions and limitations
 + * under the License.
 + */
 +package org.apache.syncope.fit.core.reference;
 +
 +import static org.junit.Assert.assertEquals;
 +import static org.junit.Assert.assertFalse;
 +import static org.junit.Assert.assertNotNull;
 +import static org.junit.Assert.assertTrue;
 +
 +import javax.ws.rs.core.Response;
 +import org.apache.syncope.client.lib.SyncopeClient;
 +import org.apache.syncope.common.lib.to.MembershipTO;
 +import org.apache.syncope.common.lib.to.NotificationTO;
 +import org.apache.syncope.common.lib.to.NotificationTaskTO;
- import org.apache.syncope.common.lib.to.PagedResult;
 +import org.apache.syncope.common.lib.to.TaskExecTO;
 +import org.apache.syncope.common.lib.to.UserTO;
 +import org.apache.syncope.common.lib.types.IntMappingType;
- import org.apache.syncope.common.lib.types.TaskType;
 +import org.apache.syncope.common.lib.types.TraceLevel;
 +import org.apache.syncope.common.rest.api.service.NotificationService;
 +import org.junit.FixMethodOrder;
 +import org.junit.Test;
 +import org.junit.runners.MethodSorters;
 +
 +@FixMethodOrder(MethodSorters.JVM)
 +public class NotificationTaskITCase extends AbstractTaskITCase {
 +
 +    @Test
 +    public void issueSYNCOPE81() {
 +        String sender = "syncope81@syncope.apache.org";
 +        createNotificationTask(sender);
 +        NotificationTaskTO taskTO = findNotificationTaskBySender(sender);
 +        assertNotNull(taskTO);
 +
 +        assertTrue(taskTO.getExecutions().isEmpty());
 +
 +        // generate an execution in order to verify the deletion of a notification task with one or more executions
 +        TaskExecTO execution = taskService.execute(taskTO.getKey(), false);
 +        assertEquals("NOT_SENT", execution.getStatus());
 +
 +        int i = 0;
 +        int maxit = 50;
 +        int executions = 0;
 +
 +        // wait for task exec completion (executions incremented)
 +        do {
 +            try {
 +                Thread.sleep(1000);
 +            } catch (InterruptedException e) {
 +            }
 +
 +            taskTO = taskService.read(taskTO.getKey());
 +
 +            assertNotNull(taskTO);
 +            assertNotNull(taskTO.getExecutions());
 +
 +            i++;
 +        } while (executions == taskTO.getExecutions().size() && i < maxit);
 +
 +        assertFalse(taskTO.getExecutions().isEmpty());
 +
 +        taskService.delete(taskTO.getKey());
 +    }
 +
 +    @Test
 +    public void issueSYNCOPE86() {
 +        // 1. create notification task
 +        String sender = "syncope86@syncope.apache.org";
 +        createNotificationTask(sender);
 +
 +        // 2. get NotificationTaskTO for user just created
 +        NotificationTaskTO taskTO = findNotificationTaskBySender(sender);
 +        assertNotNull(taskTO);
 +        assertTrue(taskTO.getExecutions().isEmpty());
 +
 +        try {
 +            // 3. execute the generated NotificationTask
 +            TaskExecTO execution = taskService.execute(taskTO.getKey(), false);
 +            assertNotNull(execution);
 +
 +            // 4. verify
 +            taskTO = taskService.read(taskTO.getKey());
 +            assertNotNull(taskTO);
 +            assertEquals(1, taskTO.getExecutions().size());
 +        } finally {
 +            // Remove execution to make test re-runnable
 +            taskService.deleteExecution(taskTO.getExecutions().get(0).getKey());
 +        }
 +    }
 +
-     private NotificationTaskTO findNotificationTaskBySender(final String sender) {
-         PagedResult<NotificationTaskTO> tasks = taskService.list(TaskType.NOTIFICATION);
-         assertNotNull(tasks);
-         assertFalse(tasks.getResult().isEmpty());
-         NotificationTaskTO taskTO = null;
-         for (NotificationTaskTO task : tasks.getResult()) {
-             if (sender.equals(task.getSender())) {
-                 taskTO = task;
-             }
-         }
-         return taskTO;
-     }
- 
 +    private void createNotificationTask(final String sender) {
 +        // 1. Create notification
 +        NotificationTO notification = new NotificationTO();
 +        notification.setTraceLevel(TraceLevel.FAILURES);
 +        notification.getEvents().add("[REST]:[UserLogic]:[]:[create]:[SUCCESS]");
 +
 +        notification.setUserAbout(SyncopeClient.getUserSearchConditionBuilder().hasRoles(7L).query());
 +
 +        notification.setRecipients(SyncopeClient.getUserSearchConditionBuilder().hasRoles(8L).query());
 +        notification.setSelfAsRecipient(true);
 +
 +        notification.setRecipientAttrName("email");
 +        notification.setRecipientAttrType(IntMappingType.UserPlainSchema);
 +
 +        notification.setSender(sender);
 +        String subject = "Test notification";
 +        notification.setSubject(subject);
 +        notification.setTemplate("optin");
 +        notification.setActive(true);
 +
 +        Response response = notificationService.create(notification);
 +        notification = getObject(response.getLocation(), NotificationService.class, NotificationTO.class);
 +        assertNotNull(notification);
 +
 +        // 2. create user
 +        UserTO userTO = UserITCase.getUniqueSampleTO("syncope@syncope.apache.org");
 +        MembershipTO membershipTO = new MembershipTO();
 +        membershipTO.setRoleId(7);
 +        userTO.getMemberships().add(membershipTO);
 +
 +        userTO = createUser(userTO);
 +        assertNotNull(userTO);
 +    }
 +
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/53721b82/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/PushTaskITCase.java
----------------------------------------------------------------------
diff --cc fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/PushTaskITCase.java
index d7d354f,0000000..3427a1d
mode 100644,000000..100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/PushTaskITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/reference/PushTaskITCase.java
@@@ -1,353 -1,0 +1,401 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one
 + * or more contributor license agreements.  See the NOTICE file
 + * distributed with this work for additional information
 + * regarding copyright ownership.  The ASF licenses this file
 + * to you under the Apache License, Version 2.0 (the
 + * "License"); you may not use this file except in compliance
 + * with the License.  You may obtain a copy of the License at
 + *
 + *   http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing,
 + * software distributed under the License is distributed on an
 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + * KIND, either express or implied.  See the License for the
 + * specific language governing permissions and limitations
 + * under the License.
 + */
 +package org.apache.syncope.fit.core.reference;
 +
 +import static org.junit.Assert.assertEquals;
 +import static org.junit.Assert.assertFalse;
 +import static org.junit.Assert.assertNotNull;
 +import static org.junit.Assert.assertNull;
 +import static org.junit.Assert.assertTrue;
 +import static org.junit.Assert.fail;
 +
 +import java.util.HashSet;
 +import java.util.List;
 +import java.util.Set;
 +import javax.ws.rs.core.Response;
 +import org.apache.syncope.client.lib.SyncopeClient;
 +import org.apache.syncope.common.lib.to.AbstractTaskTO;
 +import org.apache.syncope.common.lib.to.MappingItemTO;
 +import org.apache.syncope.common.lib.to.MappingTO;
++import org.apache.syncope.common.lib.to.NotificationTO;
++import org.apache.syncope.common.lib.to.NotificationTaskTO;
 +import org.apache.syncope.common.lib.to.PagedResult;
 +import org.apache.syncope.common.lib.to.PlainSchemaTO;
 +import org.apache.syncope.common.lib.to.PushTaskTO;
 +import org.apache.syncope.common.lib.to.ResourceTO;
 +import org.apache.syncope.common.lib.to.RoleTO;
 +import org.apache.syncope.common.lib.to.TaskExecTO;
 +import org.apache.syncope.common.lib.types.AttrSchemaType;
 +import org.apache.syncope.common.lib.types.AttributableType;
 +import org.apache.syncope.common.lib.types.IntMappingType;
 +import org.apache.syncope.common.lib.types.MappingPurpose;
 +import org.apache.syncope.common.lib.types.MatchingRule;
 +import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
 +import org.apache.syncope.common.lib.types.SchemaType;
 +import org.apache.syncope.common.lib.types.SubjectType;
 +import org.apache.syncope.common.lib.types.TaskType;
++import org.apache.syncope.common.lib.types.TraceLevel;
 +import org.apache.syncope.common.lib.types.UnmatchingRule;
++import org.apache.syncope.common.rest.api.service.NotificationService;
 +import org.apache.syncope.common.rest.api.service.ResourceService;
 +import org.apache.syncope.common.rest.api.service.TaskService;
 +import org.junit.FixMethodOrder;
 +import org.junit.Test;
 +import org.junit.runners.MethodSorters;
 +import org.springframework.jdbc.core.JdbcTemplate;
 +
 +@FixMethodOrder(MethodSorters.JVM)
 +public class PushTaskITCase extends AbstractTaskITCase {
 +
 +    @Test
 +    public void getPushActionsClasses() {
 +        List<String> actions = syncopeService.info().getPushActions();
 +        assertNotNull(actions);
 +    }
 +
 +    @Test
 +    public void read() {
 +        PushTaskTO pushTaskTO = taskService.<PushTaskTO>read(17L);
 +        assertEquals(UnmatchingRule.ASSIGN, pushTaskTO.getUnmatchingRule());
 +        assertEquals(MatchingRule.UPDATE, pushTaskTO.getMatchingRule());
 +    }
 +
 +    @Test
 +    public void list() {
 +        final PagedResult<PushTaskTO> tasks = taskService.list(TaskType.PUSH);
 +        assertFalse(tasks.getResult().isEmpty());
 +        for (AbstractTaskTO task : tasks.getResult()) {
 +            if (!(task instanceof PushTaskTO)) {
 +                fail();
 +            }
 +        }
 +    }
 +
 +    @Test
 +    public void createPushTask() {
 +        PushTaskTO task = new PushTaskTO();
 +        task.setName("Test create Push");
 +        task.setResource(RESOURCE_NAME_WS2);
 +        task.setUserFilter(
 +                SyncopeClient.getUserSearchConditionBuilder().hasNotResources(RESOURCE_NAME_TESTDB2).query());
 +        task.setRoleFilter(
 +                SyncopeClient.getRoleSearchConditionBuilder().isNotNull("cool").query());
 +        task.setMatchingRule(MatchingRule.LINK);
 +
 +        final Response response = taskService.create(task);
 +        final PushTaskTO actual = getObject(response.getLocation(), TaskService.class, PushTaskTO.class);
 +        assertNotNull(actual);
 +
 +        task = taskService.read(actual.getKey());
 +        assertNotNull(task);
 +        assertEquals(task.getKey(), actual.getKey());
 +        assertEquals(task.getJobClassName(), actual.getJobClassName());
 +        assertEquals(task.getUserFilter(), actual.getUserFilter());
 +        assertEquals(task.getRoleFilter(), actual.getRoleFilter());
 +        assertEquals(UnmatchingRule.ASSIGN, actual.getUnmatchingRule());
 +        assertEquals(MatchingRule.LINK, actual.getMatchingRule());
 +    }
 +
 +    @Test
 +    public void pushMatchingUnmatchingRoles() {
 +        assertFalse(roleService.read(3L).getResources().contains(RESOURCE_NAME_LDAP));
 +
 +        execSyncTask(23L, 50, false);
 +
 +        assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_LDAP, SubjectType.ROLE, 3L));
 +        assertTrue(roleService.read(3L).getResources().contains(RESOURCE_NAME_LDAP));
 +
 +        execSyncTask(23L, 50, false);
 +
 +        assertNotNull(resourceService.getConnectorObject(RESOURCE_NAME_LDAP, SubjectType.ROLE, 3L));
 +        assertFalse(roleService.read(3L).getResources().contains(RESOURCE_NAME_LDAP));
 +    }
 +
 +    @Test
 +    public void pushUnmatchingUsers() throws Exception {
 +        assertFalse(userService.read(2L).getResources().contains(RESOURCE_NAME_TESTDB2));
 +        assertFalse(userService.read(3L).getResources().contains(RESOURCE_NAME_TESTDB2));
 +        assertFalse(userService.read(4L).getResources().contains(RESOURCE_NAME_TESTDB2));
 +        assertTrue(userService.read(5L).getResources().contains(RESOURCE_NAME_TESTDB2));
 +
 +        final JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
 +        assertEquals(0, jdbcTemplate.queryForList("SELECT ID FROM test2 WHERE ID='puccini'").size());
 +
 +        // ------------------------------------------
 +        // Unmatching --> Assign --> dryRuyn
 +        // ------------------------------------------
 +        execSyncTask(13L, 50, true);
 +        assertEquals(0, jdbcTemplate.queryForList("SELECT ID FROM test2 WHERE ID='vivaldi'").size());
 +        assertFalse(userService.read(3L).getResources().contains(RESOURCE_NAME_TESTDB2));
 +        // ------------------------------------------
 +
 +        final Set<Long> pushTaskIds = new HashSet<>();
 +        pushTaskIds.add(13L);
 +        pushTaskIds.add(14L);
 +        pushTaskIds.add(15L);
 +        pushTaskIds.add(16L);
 +        execSyncTasks(pushTaskIds, 50, false);
 +
 +        // ------------------------------------------
 +        // Unatching --> Ignore
 +        // ------------------------------------------
 +        assertEquals(1, jdbcTemplate.queryForList("SELECT ID FROM test2 WHERE ID='verdi'").size());
 +        assertFalse(userService.read(2L).getResources().contains(RESOURCE_NAME_TESTDB2));
 +        // ------------------------------------------
 +
 +        // ------------------------------------------
 +        // Unmatching --> Assign
 +        // ------------------------------------------
 +        assertEquals(1, jdbcTemplate.queryForList("SELECT ID FROM test2 WHERE ID='vivaldi'").size());
 +        assertTrue(userService.read(3L).getResources().contains(RESOURCE_NAME_TESTDB2));
 +        jdbcTemplate.execute("DELETE FROM test2 WHERE ID='vivaldi'");
 +        // ------------------------------------------
 +
 +        // ------------------------------------------
 +        // Unmatching --> Provision
 +        // ------------------------------------------
 +        assertEquals(1, jdbcTemplate.queryForList("SELECT ID FROM test2 WHERE ID='bellini'").size());
 +        assertFalse(userService.read(4L).getResources().contains(RESOURCE_NAME_TESTDB2));
 +        jdbcTemplate.execute("DELETE FROM test2 WHERE ID='bellini'");
 +        // ------------------------------------------
 +
 +        // ------------------------------------------
 +        // Unmatching --> Unlink
 +        // ------------------------------------------
 +        assertEquals(0, jdbcTemplate.queryForList("SELECT ID FROM test2 WHERE ID='puccini'").size());
 +        assertFalse(userService.read(5L).getResources().contains(RESOURCE_NAME_TESTDB2));
 +        // ------------------------------------------
 +    }
 +
 +    @Test
 +    public void pushMatchingUser() throws Exception {
 +        assertTrue(userService.read(1L).getResources().contains(RESOURCE_NAME_TESTDB2));
 +        assertFalse(userService.read(2L).getResources().contains(RESOURCE_NAME_TESTDB2));
 +
 +        final JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
 +        assertEquals(1, jdbcTemplate.queryForList("SELECT ID FROM test2 WHERE ID='verdi'").size());
 +        assertEquals(1, jdbcTemplate.queryForList("SELECT ID FROM test2 WHERE ID='rossini'").size());
 +
 +        // ------------------------------------------
 +        // Matching --> Deprovision --> dryRuyn
 +        // ------------------------------------------
 +        execSyncTask(19L, 50, true);
 +        assertTrue(userService.read(1L).getResources().contains(RESOURCE_NAME_TESTDB2));
 +        assertEquals(1, jdbcTemplate.queryForList("SELECT ID FROM test2 WHERE ID='rossini'").size());
 +        // ------------------------------------------
 +
 +        final Set<Long> pushTaskIds = new HashSet<>();
 +        pushTaskIds.add(18L);
 +        pushTaskIds.add(19L);
 +        pushTaskIds.add(16L);
 +
 +        execSyncTasks(pushTaskIds, 50, false);
 +
 +        // ------------------------------------------
 +        // Matching --> Deprovision && Ignore
 +        // ------------------------------------------
 +        assertFalse(userService.read(2L).getResources().contains(RESOURCE_NAME_TESTDB2));
 +        // DELETE Capability not available ....
 +        assertEquals(1, jdbcTemplate.queryForList("SELECT ID FROM test2 WHERE ID='verdi'").size());
 +        // ------------------------------------------
 +
 +        // ------------------------------------------
 +        // Matching --> Unassign
 +        // ------------------------------------------
 +        assertFalse(userService.read(1L).getResources().contains(RESOURCE_NAME_TESTDB2));
 +        // DELETE Capability not available ....
 +        assertEquals(1, jdbcTemplate.queryForList("SELECT ID FROM test2 WHERE ID='rossini'").size());
 +        // ------------------------------------------
 +
 +        // ------------------------------------------
 +        // Matching --> Link
 +        // ------------------------------------------
 +        execSyncTask(20L, 50, false);
 +        assertTrue(userService.read(2L).getResources().contains(RESOURCE_NAME_TESTDB2));
 +        assertEquals(1, jdbcTemplate.queryForList("SELECT ID FROM test2 WHERE ID='verdi'").size());
 +        // ------------------------------------------
 +
 +        pushTaskIds.clear();
 +        pushTaskIds.add(21L);
 +        pushTaskIds.add(22L);
 +
 +        execSyncTasks(pushTaskIds, 50, false);
 +
 +        // ------------------------------------------
 +        // Matching --> Unlink && Update
 +        // ------------------------------------------
 +        assertFalse(userService.read(2L).getResources().contains(RESOURCE_NAME_TESTDB2));
 +        assertEquals(1, jdbcTemplate.queryForList("SELECT ID FROM test2 WHERE ID='verdi'").size());
 +        // ------------------------------------------
 +    }
 +
 +    @Test
 +    public void issueSYNCOPE598() {
 +        // create a new role schema
 +        final PlainSchemaTO schemaTO = new PlainSchemaTO();
 +        schemaTO.setKey("LDAPGroupName" + getUUIDString());
 +        schemaTO.setType(AttrSchemaType.String);
 +        schemaTO.setMandatoryCondition("true");
 +
 +        final PlainSchemaTO newPlainSchemaTO = createSchema(AttributableType.ROLE, SchemaType.PLAIN, schemaTO);
 +        assertEquals(schemaTO, newPlainSchemaTO);
 +
 +        // create a new sample role
 +        RoleTO roleTO = new RoleTO();
 +        roleTO.setName("all" + getUUIDString());
 +        roleTO.setParent(8L);
 +
 +        roleTO.getRPlainAttrTemplates().add(newPlainSchemaTO.getKey());
 +        roleTO.getPlainAttrs().add(attrTO(newPlainSchemaTO.getKey(), "all"));
 +
 +        roleTO = createRole(roleTO);
 +        assertNotNull(roleTO);
 +
 +        String resourceName = "resource-ldap-roleonly";
 +        ResourceTO newResourceTO = null;
 +
 +        try {
 +            // Create resource ad-hoc
 +            ResourceTO resourceTO = new ResourceTO();
 +            resourceTO.setKey(resourceName);
 +            resourceTO.setConnectorId(105L);
 +
 +            final MappingTO umapping = new MappingTO();
 +            MappingItemTO item = new MappingItemTO();
 +            item.setIntMappingType(IntMappingType.Username);
 +            item.setExtAttrName("cn");
 +            item.setAccountid(true);
 +            item.setPurpose(MappingPurpose.PROPAGATION);
 +            item.setMandatoryCondition("true");
 +            umapping.setAccountIdItem(item);
 +
 +            item = new MappingItemTO();
 +            item.setIntMappingType(IntMappingType.UserPlainSchema);
 +            item.setExtAttrName("surname");
 +            item.setIntAttrName("sn");
 +            item.setPurpose(MappingPurpose.BOTH);
 +            umapping.addItem(item);
 +
 +            item = new MappingItemTO();
 +            item.setIntMappingType(IntMappingType.UserPlainSchema);
 +            item.setExtAttrName("email");
 +            item.setIntAttrName("mail");
 +            item.setPurpose(MappingPurpose.BOTH);
 +            umapping.addItem(item);
 +
 +            item = new MappingItemTO();
 +            item.setIntMappingType(IntMappingType.Password);
 +            item.setPassword(true);
 +            item.setPurpose(MappingPurpose.BOTH);
 +            item.setMandatoryCondition("true");
 +            umapping.addItem(item);
 +
 +            umapping.setAccountLink("'cn=' + username + ',ou=people,o=isp'");
 +
 +            final MappingTO rmapping = new MappingTO();
 +
 +            item = new MappingItemTO();
 +            item.setIntMappingType(IntMappingType.RolePlainSchema);
 +            item.setExtAttrName("cn");
 +            item.setIntAttrName(newPlainSchemaTO.getKey());
 +            item.setAccountid(true);
 +            item.setPurpose(MappingPurpose.BOTH);
 +            rmapping.setAccountIdItem(item);
 +
 +            rmapping.setAccountLink("'cn=' + " + newPlainSchemaTO.getKey() + " + ',ou=groups,o=isp'");
 +
 +            resourceTO.setRmapping(rmapping);
 +
 +            Response response = resourceService.create(resourceTO);
 +            newResourceTO = getObject(response.getLocation(), ResourceService.class, ResourceTO.class);
 +
 +            assertNotNull(newResourceTO);
 +            assertNull(newResourceTO.getUmapping());
 +            assertNotNull(newResourceTO.getRmapping());
 +
 +            // create push task ad-hoc
 +            final PushTaskTO task = new PushTaskTO();
 +            task.setName("issueSYNCOPE598");
 +            task.setResource(resourceName);
 +            task.setPerformCreate(true);
 +            task.setPerformDelete(true);
 +            task.setPerformUpdate(true);
 +            task.setUnmatchingRule(UnmatchingRule.ASSIGN);
 +            task.setMatchingRule(MatchingRule.UPDATE);
 +
 +            response = taskService.create(task);
 +            final PushTaskTO push = getObject(response.getLocation(), TaskService.class, PushTaskTO.class);
 +
 +            assertNotNull(push);
 +
 +            // execute the new task
 +            final TaskExecTO pushExec = execSyncTask(push.getKey(), 50, false);
 +            assertTrue(PropagationTaskExecStatus.valueOf(pushExec.getStatus()).isSuccessful());
 +        } finally {
 +            roleService.delete(roleTO.getKey());
 +            if (newResourceTO != null) {
 +                resourceService.delete(resourceName);
 +            }
 +        }
 +    }
++
++    @Test
++    public void issueSYNCOPE648() {
++        //1. Create Push Task
++        final PushTaskTO task = new PushTaskTO();
++        task.setName("Test create Push");
++        task.setResource(RESOURCE_NAME_LDAP);
++        task.setUserFilter(
++                SyncopeClient.getUserSearchConditionBuilder().is("username").equalTo("_NO_ONE_").query());
++        task.setRoleFilter(
++                SyncopeClient.getRoleSearchConditionBuilder().is("name").equalTo("citizen").query());
++        task.setMatchingRule(MatchingRule.IGNORE);
++        task.setUnmatchingRule(UnmatchingRule.IGNORE);
++
++        final Response response = taskService.create(task);
++        final PushTaskTO actual = getObject(response.getLocation(), TaskService.class, PushTaskTO.class);
++        assertNotNull(actual);
++
++        // 2. Create notification
++        NotificationTO notification = new NotificationTO();
++        notification.setTraceLevel(TraceLevel.FAILURES);
++        notification.getEvents().add("[PushTask]:[role]:[resource-ldap]:[matchingrule_ignore]:[SUCCESS]");
++        notification.getEvents().add("[PushTask]:[role]:[resource-ldap]:[unmatchingrule_ignore]:[SUCCESS]");
++
++        notification.getStaticRecipients().add("issueyncope648@syncope.apache.org");
++        notification.setSelfAsRecipient(false);
++        notification.setRecipientAttrName("email");
++        notification.setRecipientAttrType(IntMappingType.UserPlainSchema);
++
++        notification.setSender("syncope648@syncope.apache.org");
++        String subject = "Test notification";
++        notification.setSubject(subject);
++        notification.setTemplate("optin");
++        notification.setActive(true);
++
++        Response responseNotification = notificationService.create(notification);
++        notification = getObject(responseNotification.getLocation(), NotificationService.class, NotificationTO.class);
++        assertNotNull(notification);
++
++        execSyncTask(actual.getKey(), 50, false);
++
++        NotificationTaskTO taskTO = findNotificationTaskBySender("syncope648@syncope.apache.org");
++        assertNotNull(taskTO);
++    }
 +}


[3/5] syncope git commit: [SYNCOPE-648]Merge from 1_2_X

Posted by md...@apache.org.
[SYNCOPE-648]Merge from 1_2_X


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

Branch: refs/heads/master
Commit: 53721b8242f506260d91c70af456491ab85ab53f
Parents: 28aef6c 89d5ad4
Author: Marco Di Sabatino Di Diodoro <md...@apache.org>
Authored: Tue Mar 17 17:52:15 2015 +0100
Committer: Marco Di Sabatino Di Diodoro <md...@apache.org>
Committed: Tue Mar 17 17:52:15 2015 +0100

----------------------------------------------------------------------
 .../syncope/common/lib/types/MatchingRule.java  |  6 ++-
 .../common/lib/types/UnmatchingRule.java        |  6 ++-
 .../apache/syncope/core/logic/LoggerLogic.java  | 14 ++++++
 .../java/sync/AbstractProvisioningJob.java      | 31 +++++++++++--
 .../java/sync/AbstractPushResultHandler.java    | 17 +++++--
 .../java/sync/AbstractSyncResultHandler.java    | 49 +++++++++++++++++---
 .../java/sync/RolePushResultHandlerImpl.java    |  7 +++
 .../java/sync/UserPushResultHandlerImpl.java    |  7 +++
 .../fit/core/reference/AbstractTaskITCase.java  | 18 +++++++
 .../core/reference/NotificationTaskITCase.java  | 15 ------
 .../fit/core/reference/PushTaskITCase.java      | 48 +++++++++++++++++++
 11 files changed, 185 insertions(+), 33 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/53721b82/common/lib/src/main/java/org/apache/syncope/common/lib/types/MatchingRule.java
----------------------------------------------------------------------
diff --cc common/lib/src/main/java/org/apache/syncope/common/lib/types/MatchingRule.java
index 39764d9,0000000..4b3ad53
mode 100644,000000..100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/types/MatchingRule.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/types/MatchingRule.java
@@@ -1,54 -1,0 +1,58 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one
 + * or more contributor license agreements.  See the NOTICE file
 + * distributed with this work for additional information
 + * regarding copyright ownership.  The ASF licenses this file
 + * to you under the Apache License, Version 2.0 (the
 + * "License"); you may not use this file except in compliance
 + * with the License.  You may obtain a copy of the License at
 + *
 + *   http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing,
 + * software distributed under the License is distributed on an
 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + * KIND, either express or implied.  See the License for the
 + * specific language governing permissions and limitations
 + * under the License.
 + */
 +package org.apache.syncope.common.lib.types;
 +
 +import javax.xml.bind.annotation.XmlEnum;
 +
 +/**
 + * Sync/Push task matching rule.
 + */
 +@XmlEnum
 +public enum MatchingRule {
 +
 +    /**
 +     * Do not perform any action.
 +     */
 +    IGNORE,
 +    /**
 +     * Update matching entity.
 +     */
 +    UPDATE,
 +    /**
 +     * Delete resource entity.
 +     */
 +    DEPROVISION,
 +    /**
 +     * Unlink resource and delete resource entity.
 +     */
 +    UNASSIGN,
 +    /**
 +     * Just unlink resource without performing any (de-)provisioning operation.
 +     */
 +    UNLINK,
 +    /**
 +     * Just link resource without performing any (de-)provisioning operation.
 +     */
-     LINK
++    LINK;
 +
++    public static String toEventName(final MatchingRule rule) {
++        return new StringBuilder(MatchingRule.class.getSimpleName()).
++                append("_").append(rule.name()).toString().toLowerCase();
++    }
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/53721b82/common/lib/src/main/java/org/apache/syncope/common/lib/types/UnmatchingRule.java
----------------------------------------------------------------------
diff --cc common/lib/src/main/java/org/apache/syncope/common/lib/types/UnmatchingRule.java
index f579128,0000000..ef3b732
mode 100644,000000..100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/types/UnmatchingRule.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/types/UnmatchingRule.java
@@@ -1,47 -1,0 +1,51 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one
 + * or more contributor license agreements.  See the NOTICE file
 + * distributed with this work for additional information
 + * regarding copyright ownership.  The ASF licenses this file
 + * to you under the Apache License, Version 2.0 (the
 + * "License"); you may not use this file except in compliance
 + * with the License.  You may obtain a copy of the License at
 + *
 + *   http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing,
 + * software distributed under the License is distributed on an
 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + * KIND, either express or implied.  See the License for the
 + * specific language governing permissions and limitations
 + * under the License.
 + */
 +package org.apache.syncope.common.lib.types;
 +
 +import javax.xml.bind.annotation.XmlEnum;
 +
 +/**
 + * Sync/Push task un-matching rule.
 + */
 +@XmlEnum
 +public enum UnmatchingRule {
 +
 +    /**
 +     * Do not perform any action.
 +     */
 +    IGNORE,
 +    /**
 +     * Link the resource and create entity.
 +     */
 +    ASSIGN,
 +    /**
 +     * Create entity without linking the resource.
 +     */
 +    PROVISION,
 +    /**
 +     * Just unlink resource without performing any (de-)provisioning operation.
 +     * In case of sync task UNLINK and IGNORE will coincide.
 +     */
-     UNLINK
++    UNLINK;
 +
++    public static String toEventName(final UnmatchingRule rule) {
++        return new StringBuilder(UnmatchingRule.class.getSimpleName()).
++                append("_").append(rule.name()).toString().toLowerCase();
++    }
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/53721b82/core/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java
----------------------------------------------------------------------
diff --cc core/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java
index f1efdea,0000000..7a0f10e
mode 100644,000000..100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/LoggerLogic.java
@@@ -1,307 -1,0 +1,321 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one
 + * or more contributor license agreements.  See the NOTICE file
 + * distributed with this work for additional information
 + * regarding copyright ownership.  The ASF licenses this file
 + * to you under the Apache License, Version 2.0 (the
 + * "License"); you may not use this file except in compliance
 + * with the License.  You may obtain a copy of the License at
 + *
 + *   http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing,
 + * software distributed under the License is distributed on an
 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + * KIND, either express or implied.  See the License for the
 + * specific language governing permissions and limitations
 + * under the License.
 + */
 +package org.apache.syncope.core.logic;
 +
 +import java.lang.reflect.Method;
 +import java.lang.reflect.Modifier;
 +import java.util.ArrayList;
 +import java.util.HashSet;
 +import java.util.List;
 +import java.util.Set;
 +import org.apache.logging.log4j.Level;
 +import org.apache.logging.log4j.LogManager;
 +import org.apache.logging.log4j.core.LoggerContext;
 +import org.apache.logging.log4j.core.config.LoggerConfig;
 +import org.apache.syncope.common.lib.SyncopeClientException;
 +import org.apache.syncope.common.lib.SyncopeConstants;
 +import org.apache.syncope.common.lib.to.EventCategoryTO;
 +import org.apache.syncope.common.lib.to.LoggerTO;
 +import org.apache.syncope.common.lib.types.AttributableType;
 +import org.apache.syncope.common.lib.types.AuditElements.EventCategoryType;
 +import org.apache.syncope.common.lib.types.AuditLoggerName;
 +import org.apache.syncope.common.lib.types.ClientExceptionType;
 +import org.apache.syncope.common.lib.types.LoggerLevel;
 +import org.apache.syncope.common.lib.types.LoggerType;
++import org.apache.syncope.common.lib.types.MatchingRule;
 +import org.apache.syncope.common.lib.types.ResourceOperation;
 +import org.apache.syncope.common.lib.types.TaskType;
++import org.apache.syncope.common.lib.types.UnmatchingRule;
 +import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
 +import org.apache.syncope.core.persistence.api.dao.LoggerDAO;
 +import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 +import org.apache.syncope.core.persistence.api.dao.TaskDAO;
 +import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 +import org.apache.syncope.core.persistence.api.entity.ExternalResource;
 +import org.apache.syncope.core.persistence.api.entity.Logger;
 +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.misc.spring.BeanUtils;
 +import org.springframework.beans.factory.annotation.Autowired;
 +import org.springframework.core.io.Resource;
 +import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
 +import org.springframework.core.io.support.ResourcePatternResolver;
 +import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
 +import org.springframework.core.type.classreading.MetadataReader;
 +import org.springframework.core.type.classreading.MetadataReaderFactory;
 +import org.springframework.security.access.prepost.PreAuthorize;
 +import org.springframework.stereotype.Component;
 +import org.springframework.transaction.annotation.Transactional;
 +import org.springframework.util.ClassUtils;
 +import org.springframework.util.SystemPropertyUtils;
 +
 +@Component
 +public class LoggerLogic extends AbstractTransactionalLogic<LoggerTO> {
 +
 +    @Autowired
 +    private LoggerDAO loggerDAO;
 +
 +    @Autowired
 +    private ExternalResourceDAO resourceDAO;
 +
 +    @Autowired
 +    private TaskDAO taskDAO;
 +
 +    @Autowired
 +    private EntityFactory entityFactory;
 +
 +    private List<LoggerTO> list(final LoggerType type) {
 +        List<LoggerTO> result = new ArrayList<>();
 +        for (Logger logger : loggerDAO.findAll(type)) {
 +            LoggerTO loggerTO = new LoggerTO();
 +            BeanUtils.copyProperties(logger, loggerTO);
 +            result.add(loggerTO);
 +        }
 +
 +        return result;
 +    }
 +
 +    @PreAuthorize("hasRole('LOG_LIST')")
 +    @Transactional(readOnly = true)
 +    public List<LoggerTO> listLogs() {
 +        return list(LoggerType.LOG);
 +    }
 +
 +    @PreAuthorize("hasRole('AUDIT_LIST')")
 +    @Transactional(readOnly = true)
 +    public List<AuditLoggerName> listAudits() {
 +        List<AuditLoggerName> result = new ArrayList<>();
 +
 +        for (LoggerTO logger : list(LoggerType.AUDIT)) {
 +            try {
 +                result.add(AuditLoggerName.fromLoggerName(logger.getKey()));
 +            } catch (Exception e) {
 +                LOG.warn("Unexpected audit logger name: {}", logger.getKey(), e);
 +            }
 +        }
 +
 +        return result;
 +    }
 +
 +    private void throwInvalidLogger(final LoggerType type) {
 +        SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidLogger);
 +        sce.getElements().add("Expected " + type.name());
 +
 +        throw sce;
 +    }
 +
 +    private LoggerTO setLevel(final String name, final Level level, final LoggerType expectedType) {
 +        Logger syncopeLogger = loggerDAO.find(name);
 +        if (syncopeLogger == null) {
 +            LOG.debug("Logger {} not found: creating new...", name);
 +
 +            syncopeLogger = entityFactory.newEntity(Logger.class);
 +            syncopeLogger.setKey(name);
 +            syncopeLogger.setType(name.startsWith(LoggerType.AUDIT.getPrefix())
 +                    ? LoggerType.AUDIT
 +                    : LoggerType.LOG);
 +        }
 +
 +        if (expectedType != syncopeLogger.getType()) {
 +            throwInvalidLogger(expectedType);
 +        }
 +
 +        syncopeLogger.setLevel(LoggerLevel.fromLevel(level));
 +        syncopeLogger = loggerDAO.save(syncopeLogger);
 +
 +        LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
 +        LoggerConfig logConf = SyncopeConstants.ROOT_LOGGER.equals(name)
 +                ? ctx.getConfiguration().getLoggerConfig(LogManager.ROOT_LOGGER_NAME)
 +                : ctx.getConfiguration().getLoggerConfig(name);
 +        logConf.setLevel(level);
 +        ctx.updateLoggers();
 +
 +        LoggerTO result = new LoggerTO();
 +        BeanUtils.copyProperties(syncopeLogger, result);
 +
 +        return result;
 +    }
 +
 +    @PreAuthorize("hasRole('LOG_SET_LEVEL')")
 +    public LoggerTO setLogLevel(final String name, final Level level) {
 +        return setLevel(name, level, LoggerType.LOG);
 +    }
 +
 +    @PreAuthorize("hasRole('AUDIT_ENABLE')")
 +    public void enableAudit(final AuditLoggerName auditLoggerName) {
 +        try {
 +            setLevel(auditLoggerName.toLoggerName(), Level.DEBUG, LoggerType.AUDIT);
 +        } catch (IllegalArgumentException e) {
 +            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidLogger);
 +            sce.getElements().add(e.getMessage());
 +            throw sce;
 +        }
 +    }
 +
 +    private LoggerTO delete(final String name, final LoggerType expectedType) throws NotFoundException {
 +        Logger syncopeLogger = loggerDAO.find(name);
 +        if (syncopeLogger == null) {
 +            throw new NotFoundException("Logger " + name);
 +        } else if (expectedType != syncopeLogger.getType()) {
 +            throwInvalidLogger(expectedType);
 +        }
 +
 +        LoggerTO loggerToDelete = new LoggerTO();
 +        BeanUtils.copyProperties(syncopeLogger, loggerToDelete);
 +
 +        // remove SyncopeLogger from local storage, so that LoggerLoader won't load this next time
 +        loggerDAO.delete(syncopeLogger);
 +
 +        // set log level to OFF in order to disable configured logger until next reboot
 +        LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
 +        org.apache.logging.log4j.core.Logger logger = SyncopeConstants.ROOT_LOGGER.equals(name)
 +                ? ctx.getLogger(LogManager.ROOT_LOGGER_NAME) : ctx.getLogger(name);
 +        logger.setLevel(Level.OFF);
 +        ctx.updateLoggers();
 +
 +        return loggerToDelete;
 +    }
 +
 +    @PreAuthorize("hasRole('LOG_DELETE')")
 +    public LoggerTO deleteLog(final String name) throws NotFoundException {
 +        return delete(name, LoggerType.LOG);
 +    }
 +
 +    @PreAuthorize("hasRole('AUDIT_DISABLE')")
 +    public void disableAudit(final AuditLoggerName auditLoggerName) {
 +        try {
 +            delete(auditLoggerName.toLoggerName(), LoggerType.AUDIT);
 +        } catch (NotFoundException e) {
 +            LOG.debug("Ignoring disable of non existing logger {}", auditLoggerName.toLoggerName());
 +        } catch (IllegalArgumentException e) {
 +            SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidLogger);
 +            sce.getElements().add(e.getMessage());
 +            throw sce;
 +        }
 +    }
 +
 +    @PreAuthorize("hasRole('AUDIT_LIST') or hasRole('NOTIFICATION_LIST')")
 +    public List<EventCategoryTO> listAuditEvents() {
 +        // use set to avoi duplications or null elements
 +        final Set<EventCategoryTO> events = new HashSet<EventCategoryTO>();
 +
 +        try {
 +            final ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
 +            final MetadataReaderFactory metadataReaderFactory =
 +                    new CachingMetadataReaderFactory(resourcePatternResolver);
 +
 +            final String packageSearchPath =
 +                    ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
 +                    + ClassUtils.convertClassNameToResourcePath(
 +                            SystemPropertyUtils.resolvePlaceholders(this.getClass().getPackage().getName()))
 +                    + "/" + "**/*.class";
 +
 +            final Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
 +            for (Resource resource : resources) {
 +                if (resource.isReadable()) {
 +                    final MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
 +                    final Class<?> clazz = Class.forName(metadataReader.getClassMetadata().getClassName());
 +
 +                    if (clazz.isAnnotationPresent(Component.class)
 +                            && AbstractLogic.class.isAssignableFrom(clazz)) {
 +                        final EventCategoryTO eventCategoryTO = new EventCategoryTO();
 +                        eventCategoryTO.setCategory(clazz.getSimpleName());
 +                        for (Method method : clazz.getDeclaredMethods()) {
 +                            if (Modifier.isPublic(method.getModifiers())) {
 +                                eventCategoryTO.getEvents().add(method.getName());
 +                            }
 +                        }
 +                        events.add(eventCategoryTO);
 +                    }
 +                }
 +            }
 +
 +            //SYNCOPE-608
 +            final EventCategoryTO authenticationControllerEvents = new EventCategoryTO();
 +            authenticationControllerEvents.setCategory("AuthenticationController");
 +            authenticationControllerEvents.getEvents().add("login");
 +            events.add(authenticationControllerEvents);
 +
 +            events.add(new EventCategoryTO(EventCategoryType.PROPAGATION));
 +            events.add(new EventCategoryTO(EventCategoryType.SYNCHRONIZATION));
 +            events.add(new EventCategoryTO(EventCategoryType.PUSH));
 +
 +            for (AttributableType attributableType : AttributableType.values()) {
 +                for (ExternalResource resource : resourceDAO.findAll()) {
 +                    final EventCategoryTO propEventCategoryTO = new EventCategoryTO(EventCategoryType.PROPAGATION);
 +                    final EventCategoryTO syncEventCategoryTO = new EventCategoryTO(EventCategoryType.SYNCHRONIZATION);
 +                    final EventCategoryTO pushEventCategoryTO = new EventCategoryTO(EventCategoryType.PUSH);
 +
 +                    propEventCategoryTO.setCategory(attributableType.name().toLowerCase());
 +                    propEventCategoryTO.setSubcategory(resource.getKey());
 +
 +                    syncEventCategoryTO.setCategory(attributableType.name().toLowerCase());
 +                    pushEventCategoryTO.setCategory(attributableType.name().toLowerCase());
 +                    syncEventCategoryTO.setSubcategory(resource.getKey());
 +                    pushEventCategoryTO.setSubcategory(resource.getKey());
 +
 +                    for (ResourceOperation resourceOperation : ResourceOperation.values()) {
 +                        propEventCategoryTO.getEvents().add(resourceOperation.name().toLowerCase());
 +                        syncEventCategoryTO.getEvents().add(resourceOperation.name().toLowerCase());
 +                        pushEventCategoryTO.getEvents().add(resourceOperation.name().toLowerCase());
 +                    }
 +
++                    for (UnmatchingRule unmatching : UnmatchingRule.values()) {
++                        String event = UnmatchingRule.toEventName(unmatching);
++                        syncEventCategoryTO.getEvents().add(event);
++                        pushEventCategoryTO.getEvents().add(event);
++                    }
++
++                    for (MatchingRule matching : MatchingRule.values()) {
++                        String event = MatchingRule.toEventName(matching);
++                        syncEventCategoryTO.getEvents().add(event);
++                        pushEventCategoryTO.getEvents().add(event);
++                    }
++
 +                    events.add(propEventCategoryTO);
 +                    events.add(syncEventCategoryTO);
 +                    events.add(pushEventCategoryTO);
 +                }
 +            }
 +
 +            for (SchedTask task : taskDAO.<SchedTask>findAll(TaskType.SCHEDULED)) {
 +                final EventCategoryTO eventCategoryTO = new EventCategoryTO(EventCategoryType.TASK);
 +                eventCategoryTO.setCategory(Class.forName(task.getJobClassName()).getSimpleName());
 +                events.add(eventCategoryTO);
 +            }
 +
 +            for (SyncTask task : taskDAO.<SyncTask>findAll(TaskType.SYNCHRONIZATION)) {
 +                final EventCategoryTO eventCategoryTO = new EventCategoryTO(EventCategoryType.TASK);
 +                eventCategoryTO.setCategory(Class.forName(task.getJobClassName()).getSimpleName());
 +                events.add(eventCategoryTO);
 +            }
 +        } catch (Exception e) {
 +            LOG.error("Failure retrieving audit/notification events", e);
 +        }
 +
 +        return new ArrayList<>(events);
 +    }
 +
 +    @Override
 +    protected LoggerTO resolveReference(final Method method, final Object... args)
 +            throws UnresolvedReferenceException {
 +
 +        throw new UnresolvedReferenceException();
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/53721b82/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractProvisioningJob.java
----------------------------------------------------------------------
diff --cc core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractProvisioningJob.java
index ea03a12,0000000..59c665d
mode 100644,000000..100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractProvisioningJob.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractProvisioningJob.java
@@@ -1,375 -1,0 +1,398 @@@
 +/*
 + * 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.sync;
 +
 +import java.lang.reflect.ParameterizedType;
 +import java.util.ArrayList;
 +import java.util.Collection;
 +import java.util.List;
 +import org.apache.syncope.common.lib.types.TraceLevel;
 +import org.apache.syncope.core.persistence.api.dao.EntitlementDAO;
 +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.Entitlement;
 +import org.apache.syncope.core.persistence.api.entity.role.RMapping;
 +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.SyncTask;
 +import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
 +import org.apache.syncope.core.persistence.api.entity.user.UMapping;
 +import org.apache.syncope.core.provisioning.api.Connector;
 +import org.apache.syncope.core.provisioning.api.ConnectorFactory;
 +import org.apache.syncope.core.provisioning.api.sync.ProvisioningActions;
 +import org.apache.syncope.core.provisioning.api.sync.ProvisioningResult;
 +import org.apache.syncope.core.provisioning.java.job.AbstractTaskJob;
 +import org.quartz.JobExecutionException;
 +import org.springframework.beans.factory.annotation.Autowired;
 +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 +import org.springframework.security.core.GrantedAuthority;
 +import org.springframework.security.core.authority.SimpleGrantedAuthority;
 +import org.springframework.security.core.context.SecurityContextHolder;
 +import org.springframework.security.core.userdetails.User;
 +import org.springframework.security.core.userdetails.UserDetails;
 +
 +/**
 + * Job for executing synchronization tasks.
 + *
 + * @see AbstractTaskJob
 + * @see SyncTask
 + * @see PushTask
 + */
 +public abstract class AbstractProvisioningJob<T extends ProvisioningTask, A extends ProvisioningActions>
 +        extends AbstractTaskJob {
 +
 +    /**
 +     * ConnInstance loader.
 +     */
 +    @Autowired
 +    protected ConnectorFactory connFactory;
 +
 +    /**
 +     * Resource DAO.
 +     */
 +    @Autowired
 +    protected ExternalResourceDAO resourceDAO;
 +
 +    /**
 +     * Entitlement DAO.
 +     */
 +    @Autowired
 +    protected EntitlementDAO entitlementDAO;
 +
 +    /**
 +     * Policy DAO.
 +     */
 +    @Autowired
 +    protected PolicyDAO policyDAO;
 +
 +    /**
 +     * SyncJob actions.
 +     */
 +    protected List<A> actions;
 +
 +    public void setActions(final List<A> actions) {
 +        this.actions = actions;
 +    }
 +
 +    /**
 +     * Create a textual report of the synchronization, based on the trace level.
 +     *
 +     * @param provResults Sync results
 +     * @param syncTraceLevel Sync trace level
 +     * @param dryRun dry run?
 +     * @return report as string
 +     */
 +    protected String createReport(final Collection<ProvisioningResult> provResults, final TraceLevel syncTraceLevel,
 +            final boolean dryRun) {
 +
 +        if (syncTraceLevel == TraceLevel.NONE) {
 +            return null;
 +        }
 +
 +        StringBuilder report = new StringBuilder();
 +
 +        if (dryRun) {
 +            report.append("==>Dry run only, no modifications were made<==\n\n");
 +        }
 +
 +        List<ProvisioningResult> uSuccCreate = new ArrayList<>();
 +        List<ProvisioningResult> uFailCreate = new ArrayList<>();
 +        List<ProvisioningResult> uSuccUpdate = new ArrayList<>();
 +        List<ProvisioningResult> uFailUpdate = new ArrayList<>();
 +        List<ProvisioningResult> uSuccDelete = new ArrayList<>();
 +        List<ProvisioningResult> uFailDelete = new ArrayList<>();
++        List<ProvisioningResult> uSuccNone = new ArrayList<>();
 +        List<ProvisioningResult> rSuccCreate = new ArrayList<>();
 +        List<ProvisioningResult> rFailCreate = new ArrayList<>();
 +        List<ProvisioningResult> rSuccUpdate = new ArrayList<>();
 +        List<ProvisioningResult> rFailUpdate = new ArrayList<>();
 +        List<ProvisioningResult> rSuccDelete = new ArrayList<>();
 +        List<ProvisioningResult> rFailDelete = new ArrayList<>();
++        List<ProvisioningResult> rSuccNone = new ArrayList<>();
 +
 +        for (ProvisioningResult provResult : provResults) {
 +            switch (provResult.getStatus()) {
 +                case SUCCESS:
 +                    switch (provResult.getOperation()) {
 +                        case CREATE:
 +                            switch (provResult.getSubjectType()) {
 +                                case USER:
 +                                    uSuccCreate.add(provResult);
 +                                    break;
 +
 +                                case ROLE:
 +                                    rSuccCreate.add(provResult);
 +                                    break;
 +
 +                                default:
 +                            }
 +                            break;
 +
 +                        case UPDATE:
 +                            switch (provResult.getSubjectType()) {
 +                                case USER:
 +                                    uSuccUpdate.add(provResult);
 +                                    break;
 +
 +                                case ROLE:
 +                                    rSuccUpdate.add(provResult);
 +                                    break;
 +
 +                                default:
 +                            }
 +                            break;
 +
 +                        case DELETE:
 +                            switch (provResult.getSubjectType()) {
 +                                case USER:
 +                                    uSuccDelete.add(provResult);
 +                                    break;
 +
 +                                case ROLE:
 +                                    rSuccDelete.add(provResult);
 +                                    break;
 +
 +                                default:
 +                            }
 +                            break;
 +
++                        case NONE:
++                            switch (provResult.getSubjectType()) {
++                                case USER:
++                                    uSuccNone.add(provResult);
++                                    break;
++
++                                case ROLE:
++                                    rSuccNone.add(provResult);
++                                    break;
++
++                                default:
++                            }
++                            break;
++
 +                        default:
 +                    }
 +                    break;
 +
 +                case FAILURE:
 +                    switch (provResult.getOperation()) {
 +                        case CREATE:
 +                            switch (provResult.getSubjectType()) {
 +                                case USER:
 +                                    uFailCreate.add(provResult);
 +                                    break;
 +
 +                                case ROLE:
 +                                    rFailCreate.add(provResult);
 +                                    break;
 +
 +                                default:
 +                            }
 +                            break;
 +
 +                        case UPDATE:
 +                            switch (provResult.getSubjectType()) {
 +                                case USER:
 +                                    uFailUpdate.add(provResult);
 +                                    break;
 +
 +                                case ROLE:
 +                                    rFailUpdate.add(provResult);
 +                                    break;
 +
 +                                default:
 +                            }
 +                            break;
 +
 +                        case DELETE:
 +                            switch (provResult.getSubjectType()) {
 +                                case USER:
 +                                    uFailDelete.add(provResult);
 +                                    break;
 +
 +                                case ROLE:
 +                                    rFailDelete.add(provResult);
 +                                    break;
 +
 +                                default:
 +                            }
 +                            break;
 +
 +                        default:
 +                    }
 +                    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('\n');
++                append(' ').
++                append("[ignored]: ").append(uSuccNone.size()).append('\n');
 +        report.append("Roles ").
 +                append("[created/failures]: ").append(rSuccCreate.size()).append('/').append(rFailCreate.size()).
 +                append(' ').
 +                append("[updated/failures]: ").append(rSuccUpdate.size()).append('/').append(rFailUpdate.size()).
 +                append(' ').
-                 append("[deleted/failures]: ").append(rSuccDelete.size()).append('/').append(rFailDelete.size());
++                append("[deleted/failures]: ").append(rSuccDelete.size()).append('/').append(rFailDelete.size()).
++                append(' ').
++                append("[ignored]: ").append(rSuccNone.size());
 +
 +        // Failures
 +        if (syncTraceLevel == TraceLevel.FAILURES || syncTraceLevel == TraceLevel.ALL) {
 +            if (!uFailCreate.isEmpty()) {
 +                report.append("\n\nUsers failed to create: ");
 +                report.append(ProvisioningResult.produceReport(uFailCreate, syncTraceLevel));
 +            }
 +            if (!uFailUpdate.isEmpty()) {
 +                report.append("\nUsers failed to update: ");
 +                report.append(ProvisioningResult.produceReport(uFailUpdate, syncTraceLevel));
 +            }
 +            if (!uFailDelete.isEmpty()) {
 +                report.append("\nUsers failed to delete: ");
 +                report.append(ProvisioningResult.produceReport(uFailDelete, syncTraceLevel));
 +            }
 +
 +            if (!rFailCreate.isEmpty()) {
 +                report.append("\n\nRoles failed to create: ");
 +                report.append(ProvisioningResult.produceReport(rFailCreate, syncTraceLevel));
 +            }
 +            if (!rFailUpdate.isEmpty()) {
 +                report.append("\nRoles failed to update: ");
 +                report.append(ProvisioningResult.produceReport(rFailUpdate, syncTraceLevel));
 +            }
 +            if (!rFailDelete.isEmpty()) {
 +                report.append("\nRoles failed to delete: ");
 +                report.append(ProvisioningResult.produceReport(rFailDelete, syncTraceLevel));
 +            }
 +        }
 +
 +        // Succeeded, only if on 'ALL' level
 +        if (syncTraceLevel == TraceLevel.ALL) {
 +            report.append("\n\nUsers created:\n")
 +                    .append(ProvisioningResult.produceReport(uSuccCreate, syncTraceLevel))
 +                    .append("\nUsers updated:\n")
 +                    .append(ProvisioningResult.produceReport(uSuccUpdate, syncTraceLevel))
 +                    .append("\nUsers deleted:\n")
-                     .append(ProvisioningResult.produceReport(uSuccDelete, syncTraceLevel));
++                    .append(ProvisioningResult.produceReport(uSuccDelete, syncTraceLevel))
++                    .append("\nUsers ignored:\n")
++                    .append(ProvisioningResult.produceReport(uSuccNone, syncTraceLevel));
 +            report.append("\n\nRoles created:\n")
 +                    .append(ProvisioningResult.produceReport(rSuccCreate, syncTraceLevel))
 +                    .append("\nRoles updated:\n")
 +                    .append(ProvisioningResult.produceReport(rSuccUpdate, syncTraceLevel))
 +                    .append("\nRoles deleted:\n")
-                     .append(ProvisioningResult.produceReport(rSuccDelete, syncTraceLevel));
++                    .append(ProvisioningResult.produceReport(rSuccDelete, syncTraceLevel))
++                    .append("\nRoles ignored:\n")
++                    .append(ProvisioningResult.produceReport(rSuccNone, syncTraceLevel));
 +        }
 +
 +        return report.toString();
 +    }
 +
 +    @Override
 +    protected String doExecute(final boolean dryRun) throws JobExecutionException {
 +        // PRE: grant all authorities (i.e. setup the SecurityContextHolder)
 +        final List<GrantedAuthority> authorities = new ArrayList<>();
 +
 +        for (Entitlement entitlement : entitlementDAO.findAll()) {
 +            authorities.add(new SimpleGrantedAuthority(entitlement.getKey()));
 +        }
 +
 +        final UserDetails userDetails = new User("admin", "FAKE_PASSWORD", true, true, true, true, authorities);
 +
 +        SecurityContextHolder.getContext().setAuthentication(
 +                new UsernamePasswordAuthenticationToken(userDetails, "FAKE_PASSWORD", authorities));
 +
 +        try {
 +            final Class<T> clazz = getTaskClassReference();
 +            if (!clazz.isAssignableFrom(task.getClass())) {
 +                throw new JobExecutionException("Task " + taskId + " isn't a SyncTask");
 +            }
 +
 +            final T syncTask = clazz.cast(this.task);
 +
 +            final Connector connector;
 +            try {
 +                connector = connFactory.getConnector(syncTask.getResource());
 +            } catch (Exception e) {
 +                final String msg = String.
 +                        format("Connector instance bean for resource %s and connInstance %s not found",
 +                                syncTask.getResource(), syncTask.getResource().getConnector());
 +
 +                throw new JobExecutionException(msg, e);
 +            }
 +
 +            final UMapping uMapping = syncTask.getResource().getUmapping();
 +            if (uMapping != null && uMapping.getAccountIdItem() == null) {
 +                throw new JobExecutionException(
 +                        "Invalid user account id mapping for resource " + syncTask.getResource());
 +            }
 +            final RMapping rMapping = syncTask.getResource().getRmapping();
 +            if (rMapping != null && rMapping.getAccountIdItem() == null) {
 +                throw new JobExecutionException(
 +                        "Invalid role account id mapping for resource " + syncTask.getResource());
 +            }
 +            if (uMapping == null && rMapping == null) {
 +                return "No mapping configured for both users and roles: aborting...";
 +            }
 +
 +            return executeWithSecurityContext(
 +                    syncTask,
 +                    connector,
 +                    uMapping,
 +                    rMapping,
 +                    dryRun);
 +        } catch (Throwable t) {
 +            LOG.error("While executing provisioning job {}", getClass().getName(), t);
 +            throw t;
 +        } finally {
 +            // POST: clean up the SecurityContextHolder
 +            SecurityContextHolder.clearContext();
 +        }
 +    }
 +
 +    protected abstract String executeWithSecurityContext(
 +            final T task,
 +            final Connector connector,
 +            final UMapping uMapping,
 +            final RMapping rMapping,
 +            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 (Status.valueOf(execution.getStatus()) == Status.FAILURE
 +                && provTask.getResource().getSyncTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal())
 +                || provTask.getResource().getSyncTraceLevel().ordinal() >= TraceLevel.SUMMARY.ordinal();
 +    }
 +
 +    @SuppressWarnings("unchecked")
 +    private Class<T> getTaskClassReference() {
 +        return (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/53721b82/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractPushResultHandler.java
----------------------------------------------------------------------
diff --cc core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractPushResultHandler.java
index 5161c74,0000000..42dfedd
mode 100644,000000..100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractPushResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractPushResultHandler.java
@@@ -1,374 -1,0 +1,381 @@@
 +/*
 + * 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.sync;
 +
 +import java.util.ArrayList;
 +import java.util.HashSet;
 +import java.util.List;
 +import java.util.Set;
 +import org.apache.commons.lang3.exception.ExceptionUtils;
 +import org.apache.syncope.common.lib.mod.AttrMod;
 +import org.apache.syncope.common.lib.mod.MembershipMod;
 +import org.apache.syncope.common.lib.to.AbstractSubjectTO;
- import org.apache.syncope.common.lib.types.AttributableType;
 +import org.apache.syncope.common.lib.types.AuditElements;
 +import org.apache.syncope.common.lib.types.AuditElements.Result;
 +import org.apache.syncope.common.lib.types.IntMappingType;
 +import org.apache.syncope.common.lib.types.MatchingRule;
 +import org.apache.syncope.common.lib.types.PropagationByResource;
 +import org.apache.syncope.common.lib.types.ResourceOperation;
 +import org.apache.syncope.common.lib.types.UnmatchingRule;
 +import org.apache.syncope.core.persistence.api.entity.AttributableUtil;
 +import org.apache.syncope.core.persistence.api.entity.Mapping;
 +import org.apache.syncope.core.persistence.api.entity.MappingItem;
 +import org.apache.syncope.core.persistence.api.entity.Subject;
 +import org.apache.syncope.core.persistence.api.entity.VirAttr;
 +import org.apache.syncope.core.persistence.api.entity.membership.Membership;
 +import org.apache.syncope.core.persistence.api.entity.task.PushTask;
 +import org.apache.syncope.core.persistence.api.entity.user.User;
 +import org.apache.syncope.core.provisioning.api.sync.ProvisioningResult;
 +import org.apache.syncope.core.provisioning.api.sync.PushActions;
 +import org.apache.syncope.core.misc.MappingUtil;
 +import org.apache.syncope.core.provisioning.api.sync.SyncopePushResultHandler;
 +import org.identityconnectors.framework.common.objects.ConnectorObject;
 +import org.quartz.JobExecutionException;
 +import org.springframework.transaction.annotation.Transactional;
 +
 +public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHandler<PushTask, PushActions>
 +        implements SyncopePushResultHandler {
 +
++    protected abstract AttributableUtil getAttributableUtil();
++
 +    protected abstract String getName(final Subject<?, ?, ?> subject);
 +
 +    protected abstract Mapping<?> getMapping();
 +
 +    protected abstract AbstractSubjectTO getSubjectTO(final long key);
 +
 +    protected abstract Subject<?, ?, ?> getSubject(final long key);
 +
 +    protected abstract Subject<?, ?, ?> deprovision(final Subject<?, ?, ?> sbj);
 +
 +    protected abstract Subject<?, ?, ?> provision(final Subject<?, ?, ?> sbj, final Boolean enabled);
 +
 +    protected abstract Subject<?, ?, ?> link(final Subject<?, ?, ?> sbj, final Boolean unlink);
 +
 +    protected abstract Subject<?, ?, ?> unassign(final Subject<?, ?, ?> sbj);
 +
 +    protected abstract Subject<?, ?, ?> assign(final Subject<?, ?, ?> sbj, Boolean enabled);
 +
 +    protected abstract ConnectorObject getRemoteObject(final String accountId);
 +
 +    @Transactional
 +    @Override
 +    public boolean handle(final long subjectId) {
 +        try {
 +            doHandle(subjectId);
 +            return true;
 +        } catch (JobExecutionException e) {
 +            LOG.error("Synchronization failed", e);
 +            return false;
 +        }
 +    }
 +
 +    protected final void doHandle(final long subjectId)
 +            throws JobExecutionException {
 +
 +        final Subject<?, ?, ?> subject = getSubject(subjectId);
 +
 +        final AttributableUtil attrUtil = attrUtilFactory.getInstance(subject);
 +
 +        final ProvisioningResult result = new ProvisioningResult();
 +        profile.getResults().add(result);
 +
 +        result.setId(subject.getKey());
 +        result.setSubjectType(attrUtil.getType());
 +        result.setName(getName(subject));
 +
 +        final Boolean enabled = subject instanceof User && profile.getTask().isSyncStatus()
 +                ? ((User) subject).isSuspended() ? Boolean.FALSE : Boolean.TRUE
 +                : null;
 +
 +        LOG.debug("Propagating {} with key {} towards {}",
 +                attrUtil.getType(), subject.getKey(), profile.getTask().getResource());
 +
 +        Object output = null;
 +        Result resultStatus = null;
 +        ConnectorObject beforeObj = null;
 +        String operation = null;
 +
 +        // Try to read remote object (user / group) BEFORE any actual operation
 +        final String accountId = MappingUtil.getAccountIdValue(
 +                subject, profile.getTask().getResource(), getMapping().getAccountIdItem());
 +
 +        beforeObj = getRemoteObject(accountId);
 +
 +        Boolean status = profile.getTask().isSyncStatus() ? enabled : null;
 +
 +        if (profile.isDryRun()) {
 +            if (beforeObj == null) {
 +                result.setOperation(getResourceOperation(profile.getTask().getUnmatchingRule()));
 +            } else {
 +                result.setOperation(getResourceOperation(profile.getTask().getMatchingRule()));
 +            }
 +            result.setStatus(ProvisioningResult.Status.SUCCESS);
 +        } else {
 +            try {
 +                if (beforeObj == null) {
-                     operation = profile.getTask().getUnmatchingRule().name().toLowerCase();
++                    operation = UnmatchingRule.toEventName(profile.getTask().getUnmatchingRule());
 +                    result.setOperation(getResourceOperation(profile.getTask().getUnmatchingRule()));
 +
 +                    switch (profile.getTask().getUnmatchingRule()) {
 +                        case ASSIGN:
 +                            for (PushActions action : profile.getActions()) {
 +                                action.beforeAssign(this.getProfile(), subject);
 +                            }
 +
 +                            if (!profile.getTask().isPerformCreate()) {
 +                                LOG.debug("PushTask not configured for create");
 +                            } else {
 +                                assign(subject, status);
 +                            }
 +
 +                            break;
 +                        case PROVISION:
 +                            for (PushActions action : profile.getActions()) {
 +                                action.beforeProvision(this.getProfile(), subject);
 +                            }
 +
 +                            if (!profile.getTask().isPerformCreate()) {
 +                                LOG.debug("PushTask not configured for create");
 +                            } else {
 +                                provision(subject, status);
 +                            }
 +
 +                            break;
 +                        case UNLINK:
 +                            for (PushActions action : profile.getActions()) {
 +                                action.beforeUnlink(this.getProfile(), subject);
 +                            }
 +
 +                            if (!profile.getTask().isPerformUpdate()) {
 +                                LOG.debug("PushTask not configured for update");
 +                            } else {
 +                                link(subject, true);
 +                            }
 +
 +                            break;
++                        case IGNORE:
++                            LOG.debug("Ignored subjectId: {}", subjectId);
++                            break;
 +                        default:
 +                        // do nothing
 +                    }
 +
 +                } else {
-                     operation = profile.getTask().getMatchingRule().name().toLowerCase();
++                    operation = MatchingRule.toEventName(profile.getTask().getMatchingRule());
 +                    result.setOperation(getResourceOperation(profile.getTask().getMatchingRule()));
 +
 +                    switch (profile.getTask().getMatchingRule()) {
 +                        case UPDATE:
 +                            for (PushActions action : profile.getActions()) {
 +                                action.beforeUpdate(this.getProfile(), subject);
 +                            }
 +                            if (!profile.getTask().isPerformUpdate()) {
 +                                LOG.debug("PushTask not configured for update");
 +                            } else {
 +                                update(subject, status);
 +                            }
 +
 +                            break;
 +                        case DEPROVISION:
 +                            for (PushActions action : profile.getActions()) {
 +                                action.beforeDeprovision(this.getProfile(), subject);
 +                            }
 +
 +                            if (!profile.getTask().isPerformDelete()) {
 +                                LOG.debug("PushTask not configured for delete");
 +                            } else {
 +                                deprovision(subject);
 +                            }
 +
 +                            break;
 +                        case UNASSIGN:
 +                            for (PushActions action : profile.getActions()) {
 +                                action.beforeUnassign(this.getProfile(), subject);
 +                            }
 +
 +                            if (!profile.getTask().isPerformDelete()) {
 +                                LOG.debug("PushTask not configured for delete");
 +                            } else {
 +                                unassign(subject);
 +                            }
 +
 +                            break;
 +                        case LINK:
 +                            for (PushActions action : profile.getActions()) {
 +                                action.beforeLink(this.getProfile(), subject);
 +                            }
 +
 +                            if (!profile.getTask().isPerformUpdate()) {
 +                                LOG.debug("PushTask not configured for update");
 +                            } else {
 +                                link(subject, false);
 +                            }
 +
 +                            break;
 +                        case UNLINK:
 +                            for (PushActions action : profile.getActions()) {
 +                                action.beforeUnlink(this.getProfile(), subject);
 +                            }
 +
 +                            if (!profile.getTask().isPerformUpdate()) {
 +                                LOG.debug("PushTask not configured for update");
 +                            } else {
 +                                link(subject, true);
 +                            }
 +
 +                            break;
++                        case IGNORE:
++                            LOG.debug("Ignored subjectId: {}", subjectId);
++                            break;
 +                        default:
 +                        // do nothing
 +                    }
 +                }
 +
 +                for (PushActions action : profile.getActions()) {
 +                    action.after(this.getProfile(), subject, result);
 +                }
 +
 +                result.setStatus(ProvisioningResult.Status.SUCCESS);
 +                resultStatus = AuditElements.Result.SUCCESS;
 +                output = getRemoteObject(accountId);
 +            } catch (Exception e) {
 +                result.setStatus(ProvisioningResult.Status.FAILURE);
 +                result.setMessage(ExceptionUtils.getRootCauseMessage(e));
 +                resultStatus = AuditElements.Result.FAILURE;
 +                output = e;
 +
 +                LOG.warn("Error pushing {} towards {}", subject, profile.getTask().getResource(), e);
 +                throw new JobExecutionException(e);
 +            } finally {
 +                notificationManager.createTasks(
 +                        AuditElements.EventCategoryType.PUSH,
-                         AttributableType.USER.name().toLowerCase(),
++                        getAttributableUtil().getType().name().toLowerCase(),
 +                        profile.getTask().getResource().getKey(),
 +                        operation,
 +                        resultStatus,
 +                        beforeObj,
 +                        output,
 +                        subject);
 +                auditManager.audit(
 +                        AuditElements.EventCategoryType.PUSH,
-                         AttributableType.USER.name().toLowerCase(),
++                        getAttributableUtil().getType().name().toLowerCase(),
 +                        profile.getTask().getResource().getKey(),
 +                        operation,
 +                        resultStatus,
 +                        beforeObj,
 +                        output,
 +                        subject);
 +            }
 +        }
 +    }
 +
 +    private ResourceOperation getResourceOperation(final UnmatchingRule rule) {
 +        switch (rule) {
 +            case ASSIGN:
 +            case PROVISION:
 +                return ResourceOperation.CREATE;
 +            default:
 +                return ResourceOperation.NONE;
 +        }
 +    }
 +
 +    private ResourceOperation getResourceOperation(final MatchingRule rule) {
 +        switch (rule) {
 +            case UPDATE:
 +                return ResourceOperation.UPDATE;
 +            case DEPROVISION:
 +            case UNASSIGN:
 +                return ResourceOperation.DELETE;
 +            default:
 +                return ResourceOperation.NONE;
 +        }
 +    }
 +
 +    protected Subject<?, ?, ?> update(final Subject<?, ?, ?> sbj, final Boolean enabled) {
 +
 +        final Set<MembershipMod> membsToAdd = new HashSet<>();
 +        final Set<String> vattrToBeRemoved = new HashSet<>();
 +        final Set<String> membVattrToBeRemoved = new HashSet<>();
 +        final Set<AttrMod> vattrToBeUpdated = new HashSet<>();
 +
 +        // Search for all mapped vattrs
 +        final Mapping<?> umapping = getMapping();
 +        for (MappingItem mappingItem : umapping.getItems()) {
 +            if (mappingItem.getIntMappingType() == IntMappingType.UserVirtualSchema) {
 +                vattrToBeRemoved.add(mappingItem.getIntAttrName());
 +            } else if (mappingItem.getIntMappingType() == IntMappingType.MembershipVirtualSchema) {
 +                membVattrToBeRemoved.add(mappingItem.getIntAttrName());
 +            }
 +        }
 +
 +        // Search for all user's vattrs and:
 +        // 1. add mapped vattrs not owned by the user to the set of vattrs to be removed
 +        // 2. add all vattrs owned by the user to the set of vattrs to be update
 +        for (VirAttr vattr : sbj.getVirAttrs()) {
 +            vattrToBeRemoved.remove(vattr.getSchema().getKey());
 +            final AttrMod mod = new AttrMod();
 +            mod.setSchema(vattr.getSchema().getKey());
 +            mod.getValuesToBeAdded().addAll(vattr.getValues());
 +            vattrToBeUpdated.add(mod);
 +        }
 +
 +        final boolean changepwd;
 +
 +        if (sbj instanceof User) {
 +            changepwd = true;
 +
 +            // Search for memberships
 +            for (Membership membership : User.class.cast(sbj).getMemberships()) {
 +                final MembershipMod membershipMod = new MembershipMod();
 +                membershipMod.setKey(membership.getKey());
 +                membershipMod.setRole(membership.getRole().getKey());
 +
 +                for (VirAttr vattr : membership.getVirAttrs()) {
 +                    membVattrToBeRemoved.remove(vattr.getSchema().getKey());
 +                    final AttrMod mod = new AttrMod();
 +                    mod.setSchema(vattr.getSchema().getKey());
 +                    mod.getValuesToBeAdded().addAll(vattr.getValues());
 +                    membershipMod.getVirAttrsToUpdate().add(mod);
 +                }
 +
 +                membsToAdd.add(membershipMod);
 +            }
 +
 +            if (!membsToAdd.isEmpty()) {
 +                membsToAdd.iterator().next().getVirAttrsToRemove().addAll(membVattrToBeRemoved);
 +            }
 +        } else {
 +            changepwd = false;
 +        }
 +
 +        final List<String> noPropResources = new ArrayList<>(sbj.getResourceNames());
 +        noPropResources.remove(profile.getTask().getResource().getKey());
 +
 +        final PropagationByResource propByRes = new PropagationByResource();
 +        propByRes.add(ResourceOperation.CREATE, profile.getTask().getResource().getKey());
 +
 +        taskExecutor.execute(propagationManager.getUpdateTaskIds(
 +                sbj, null, changepwd, enabled, vattrToBeRemoved, vattrToBeUpdated, propByRes, noPropResources,
 +                membsToAdd));
 +
 +        return userDAO.authFetch(sbj.getKey());
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/53721b82/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractSyncResultHandler.java
----------------------------------------------------------------------
diff --cc core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractSyncResultHandler.java
index 04ad24d,0000000..02b9a1a
mode 100644,000000..100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractSyncResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractSyncResultHandler.java
@@@ -1,617 -1,0 +1,652 @@@
 +/*
 + * 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.sync;
 +
 +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.mod.AbstractSubjectMod;
 +import org.apache.syncope.common.lib.to.AbstractSubjectTO;
 +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.ResourceOperation;
++import org.apache.syncope.common.lib.types.UnmatchingRule;
 +import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 +import org.apache.syncope.core.persistence.api.entity.AttributableUtil;
 +import org.apache.syncope.core.persistence.api.entity.task.SyncTask;
 +import org.apache.syncope.core.provisioning.api.AttributableTransformer;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationException;
 +import org.apache.syncope.core.provisioning.api.sync.SyncActions;
- import org.apache.syncope.core.provisioning.api.sync.ProvisioningResult;
 +import org.apache.syncope.core.misc.security.UnauthorizedRoleException;
++import org.apache.syncope.core.provisioning.api.sync.ProvisioningResult;
 +import org.apache.syncope.core.provisioning.api.sync.SyncopeSyncResultHandler;
 +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;
 +
 +public abstract class AbstractSyncResultHandler extends AbstractSyncopeResultHandler<SyncTask, SyncActions>
 +        implements SyncopeSyncResultHandler {
 +
 +    @Autowired
 +    protected SyncUtilities syncUtilities;
 +
 +    @Autowired
 +    protected AttributableTransformer attrTransformer;
 +
 +    protected abstract AttributableUtil getAttributableUtil();
 +
 +    protected abstract String getName(AbstractSubjectTO subjectTO);
 +
 +    protected abstract AbstractSubjectTO getSubjectTO(long key);
 +
 +    protected abstract AbstractSubjectMod getSubjectMod(AbstractSubjectTO subjectTO, SyncDelta delta);
 +
 +    protected abstract AbstractSubjectTO create(AbstractSubjectTO subjectTO, SyncDelta _delta, ProvisioningResult result);
 +
 +    protected abstract AbstractSubjectTO link(AbstractSubjectTO before, ProvisioningResult result, boolean unlink);
 +
 +    protected abstract AbstractSubjectTO update(AbstractSubjectTO before, AbstractSubjectMod subjectMod,
 +            SyncDelta delta, ProvisioningResult result);
 +
 +    protected abstract void deprovision(Long key, boolean unlink);
 +
 +    protected abstract void delete(Long key);
 +
 +    @Override
 +    public boolean handle(final SyncDelta delta) {
 +        try {
 +            doHandle(delta);
 +            return true;
 +        } catch (JobExecutionException e) {
 +            LOG.error("Synchronization failed", e);
 +            return false;
 +        }
 +    }
 +
 +    protected List<ProvisioningResult> assign(final SyncDelta delta, final AttributableUtil attrUtil)
 +            throws JobExecutionException {
 +        if (!profile.getTask().isPerformCreate()) {
 +            LOG.debug("SyncTask not configured for create");
 +            return Collections.<ProvisioningResult>emptyList();
 +        }
 +
 +        final AbstractSubjectTO subjectTO =
 +                connObjectUtil.getSubjectTO(delta.getObject(), profile.getTask(), attrUtil);
 +
 +        subjectTO.getResources().add(profile.getTask().getResource().getKey());
 +
 +        final ProvisioningResult result = new ProvisioningResult();
 +        result.setOperation(ResourceOperation.CREATE);
 +        result.setSubjectType(attrUtil.getType());
 +        result.setStatus(ProvisioningResult.Status.SUCCESS);
 +
 +        // Attributable transformation (if configured)
 +        AbstractSubjectTO transformed = attrTransformer.transform(subjectTO);
 +        LOG.debug("Transformed: {}", transformed);
 +
 +        result.setName(getName(transformed));
 +
 +        if (profile.isDryRun()) {
 +            result.setId(0L);
 +        } else {
 +            SyncDelta _delta = delta;
 +            for (SyncActions action : profile.getActions()) {
 +                _delta = action.beforeAssign(this.getProfile(), _delta, transformed);
 +            }
 +
-             create(transformed, _delta, attrUtil, "assign", result);
++            create(transformed, _delta, attrUtil, UnmatchingRule.toEventName(UnmatchingRule.ASSIGN), result);
 +        }
 +
 +        return Collections.singletonList(result);
 +    }
 +
 +    protected List<ProvisioningResult> create(final SyncDelta delta, final AttributableUtil attrUtil)
 +            throws JobExecutionException {
 +
 +        if (!profile.getTask().isPerformCreate()) {
 +            LOG.debug("SyncTask not configured for create");
 +            return Collections.<ProvisioningResult>emptyList();
 +        }
 +
 +        final AbstractSubjectTO subjectTO =
 +                connObjectUtil.getSubjectTO(delta.getObject(), profile.getTask(), attrUtil);
 +
 +        // Attributable transformation (if configured)
 +        AbstractSubjectTO transformed = attrTransformer.transform(subjectTO);
 +        LOG.debug("Transformed: {}", transformed);
 +
 +        final ProvisioningResult result = new ProvisioningResult();
 +        result.setOperation(ResourceOperation.CREATE);
 +        result.setSubjectType(attrUtil.getType());
 +        result.setStatus(ProvisioningResult.Status.SUCCESS);
 +
 +        result.setName(getName(transformed));
 +
 +        if (profile.isDryRun()) {
 +            result.setId(0L);
 +        } else {
 +            SyncDelta _delta = delta;
 +            for (SyncActions action : profile.getActions()) {
 +                _delta = action.beforeProvision(this.getProfile(), _delta, transformed);
 +            }
 +
-             create(transformed, _delta, attrUtil, "provision", result);
++            create(transformed, _delta, attrUtil, UnmatchingRule.toEventName(UnmatchingRule.PROVISION), result);
 +        }
 +
 +        return Collections.<ProvisioningResult>singletonList(result);
 +    }
 +
 +    private void create(
 +            final AbstractSubjectTO subjectTO,
 +            final SyncDelta delta,
 +            final AttributableUtil attrUtil,
 +            final String operation,
 +            final ProvisioningResult result)
 +            throws JobExecutionException {
 +
 +        Object output;
 +        Result resultStatus;
 +
 +        try {
 +            AbstractSubjectTO actual = create(subjectTO, delta, result);
 +            result.setName(getName(actual));
 +            output = actual;
 +            resultStatus = Result.SUCCESS;
 +
 +            for (SyncActions action : profile.getActions()) {
 +                action.after(this.getProfile(), delta, actual, result);
 +            }
 +        } catch (PropagationException e) {
 +            // A propagation failure doesn't imply a synchronization failure.
 +            // The propagation exception status will be reported into the propagation task execution.
 +            LOG.error("Could not propagate {} {}", attrUtil.getType(), delta.getUid().getUidValue(), e);
 +            output = e;
 +            resultStatus = Result.FAILURE;
 +        } catch (Exception e) {
 +            result.setStatus(ProvisioningResult.Status.FAILURE);
 +            result.setMessage(ExceptionUtils.getRootCauseMessage(e));
 +            LOG.error("Could not create {} {} ", attrUtil.getType(), delta.getUid().getUidValue(), e);
 +            output = e;
 +            resultStatus = Result.FAILURE;
 +        }
 +
 +        audit(operation, resultStatus, null, output, delta);
 +    }
 +
 +    protected List<ProvisioningResult> update(SyncDelta delta, final List<Long> subjects,
 +            final AttributableUtil attrUtil)
 +            throws JobExecutionException {
 +
 +        if (!profile.getTask().isPerformUpdate()) {
 +            LOG.debug("SyncTask not configured for update");
 +            return Collections.<ProvisioningResult>emptyList();
 +        }
 +
 +        LOG.debug("About to update {}", subjects);
 +
 +        List<ProvisioningResult> results = new ArrayList<>();
 +
 +        for (Long key : subjects) {
 +            LOG.debug("About to update {}", key);
 +
 +            final ProvisioningResult result = new ProvisioningResult();
 +            result.setOperation(ResourceOperation.UPDATE);
 +            result.setSubjectType(attrUtil.getType());
 +            result.setStatus(ProvisioningResult.Status.SUCCESS);
 +            result.setId(key);
 +
 +            AbstractSubjectTO before = getSubjectTO(key);
 +            if (before == null) {
 +                result.setStatus(ProvisioningResult.Status.FAILURE);
 +                result.setMessage(String.format("Subject '%s(%d)' not found", attrUtil.getType().name(), key));
 +            } else {
 +                result.setName(getName(before));
 +            }
 +
 +            Result resultStatus;
 +            Object output;
 +            if (!profile.isDryRun()) {
 +                if (before == null) {
 +                    resultStatus = Result.FAILURE;
 +                    output = null;
 +                } else {
 +                    try {
 +                        final AbstractSubjectMod attributableMod = getSubjectMod(before, delta);
 +
 +                        // Attribute value transformation (if configured)
 +                        final AbstractSubjectMod actual = attrTransformer.transform(attributableMod);
 +                        LOG.debug("Transformed: {}", actual);
 +
 +                        for (SyncActions action : profile.getActions()) {
 +                            delta = action.beforeUpdate(this.getProfile(), delta, before, attributableMod);
 +                        }
 +
 +                        final AbstractSubjectTO updated = update(before, attributableMod, delta, result);
 +
 +                        for (SyncActions action : profile.getActions()) {
 +                            action.after(this.getProfile(), delta, updated, result);
 +                        }
 +
 +                        output = updated;
 +                        resultStatus = Result.SUCCESS;
 +                        result.setName(getName(updated));
 +                        LOG.debug("{} {} successfully updated", attrUtil.getType(), key);
 +                    } catch (PropagationException e) {
 +                        // A propagation failure doesn't imply a synchronization failure.
 +                        // The propagation exception status will be reported into the propagation task execution.
 +                        LOG.error("Could not propagate {} {}", attrUtil.getType(), delta.getUid().getUidValue(), e);
 +                        output = e;
 +                        resultStatus = Result.FAILURE;
 +                    } catch (Exception e) {
 +                        result.setStatus(ProvisioningResult.Status.FAILURE);
 +                        result.setMessage(ExceptionUtils.getRootCauseMessage(e));
 +                        LOG.error("Could not update {} {}", attrUtil.getType(), delta.getUid().getUidValue(), e);
 +                        output = e;
 +                        resultStatus = Result.FAILURE;
 +                    }
 +                }
-                 audit("update", resultStatus, before, output, delta);
++                audit(MatchingRule.toEventName(MatchingRule.UPDATE), resultStatus, before, output, delta);
 +            }
 +            results.add(result);
 +        }
 +        return results;
 +    }
 +
 +    protected List<ProvisioningResult> deprovision(
 +            SyncDelta delta,
 +            final List<Long> subjects,
 +            final AttributableUtil attrUtil,
 +            final boolean unlink)
 +            throws JobExecutionException {
 +
 +        if (!profile.getTask().isPerformUpdate()) {
 +            LOG.debug("SyncTask not configured for update");
 +            return Collections.<ProvisioningResult>emptyList();
 +        }
 +
 +        LOG.debug("About to update {}", subjects);
 +
 +        final List<ProvisioningResult> updResults = new ArrayList<>();
 +
 +        for (Long id : subjects) {
 +            LOG.debug("About to unassign resource {}", id);
 +
 +            Object output;
 +            Result resultStatus;
 +
 +            final ProvisioningResult result = new ProvisioningResult();
 +            result.setOperation(ResourceOperation.DELETE);
 +            result.setSubjectType(attrUtil.getType());
 +            result.setStatus(ProvisioningResult.Status.SUCCESS);
 +            result.setId(id);
 +
 +            final AbstractSubjectTO before = getSubjectTO(id);
 +
 +            if (before == null) {
 +                result.setStatus(ProvisioningResult.Status.FAILURE);
 +                result.setMessage(String.format("Subject '%s(%d)' not found", attrUtil.getType().name(), id));
 +            }
 +
 +            if (!profile.isDryRun()) {
 +                if (before == null) {
 +                    resultStatus = Result.FAILURE;
 +                    output = null;
 +                } else {
 +                    result.setName(getName(before));
 +
 +                    try {
 +                        if (unlink) {
 +                            for (SyncActions action : profile.getActions()) {
 +                                action.beforeUnassign(this.getProfile(), delta, before);
 +                            }
 +                        } else {
 +                            for (SyncActions action : profile.getActions()) {
 +                                action.beforeDeprovision(this.getProfile(), delta, before);
 +                            }
 +                        }
 +
 +                        deprovision(id, unlink);
 +                        output = getSubjectTO(id);
 +
 +                        for (SyncActions action : profile.getActions()) {
 +                            action.after(this.getProfile(), delta, AbstractSubjectTO.class.cast(output), result);
 +                        }
 +
 +                        resultStatus = Result.SUCCESS;
 +                        LOG.debug("{} {} successfully updated", attrUtil.getType(), id);
 +                    } catch (PropagationException e) {
 +                        // A propagation failure doesn't imply a synchronization failure.
 +                        // The propagation exception status will be reported into the propagation task execution.
 +                        LOG.error("Could not propagate {} {}", attrUtil.getType(), delta.getUid().getUidValue(), e);
 +                        output = e;
 +                        resultStatus = Result.FAILURE;
 +                    } catch (Exception e) {
 +                        result.setStatus(ProvisioningResult.Status.FAILURE);
 +                        result.setMessage(ExceptionUtils.getRootCauseMessage(e));
 +                        LOG.error("Could not update {} {}", attrUtil.getType(), delta.getUid().getUidValue(), e);
 +                        output = e;
 +                        resultStatus = Result.FAILURE;
 +                    }
 +                }
-                 audit(unlink ? "unassign" : "deprovision", resultStatus, before, output, delta);
++                audit(unlink
++                        ? MatchingRule.toEventName(MatchingRule.UNASSIGN)
++                        : MatchingRule.toEventName(MatchingRule.DEPROVISION), resultStatus, before, output, delta);
 +            }
 +            updResults.add(result);
 +        }
 +
 +        return updResults;
 +    }
 +
 +    protected List<ProvisioningResult> link(
 +            SyncDelta delta,
 +            final List<Long> subjects,
 +            final AttributableUtil attrUtil,
 +            final boolean unlink)
 +            throws JobExecutionException {
 +
 +        if (!profile.getTask().isPerformUpdate()) {
 +            LOG.debug("SyncTask not configured for update");
 +            return Collections.<ProvisioningResult>emptyList();
 +        }
 +
 +        LOG.debug("About to update {}", subjects);
 +
 +        final List<ProvisioningResult> updResults = new ArrayList<>();
 +
 +        for (Long id : subjects) {
 +            LOG.debug("About to unassign resource {}", id);
 +
 +            Object output;
 +            Result resultStatus;
 +
 +            final ProvisioningResult result = new ProvisioningResult();
 +            result.setOperation(ResourceOperation.NONE);
 +            result.setSubjectType(attrUtil.getType());
 +            result.setStatus(ProvisioningResult.Status.SUCCESS);
 +            result.setId(id);
 +
 +            final AbstractSubjectTO before = getSubjectTO(id);
 +
 +            if (before == null) {
 +                result.setStatus(ProvisioningResult.Status.FAILURE);
 +                result.setMessage(String.format("Subject '%s(%d)' not found", attrUtil.getType().name(), id));
 +            }
 +
 +            if (!profile.isDryRun()) {
 +                if (before == null) {
 +                    resultStatus = Result.FAILURE;
 +                    output = null;
 +                } else {
 +                    result.setName(getName(before));
 +
 +                    try {
 +                        if (unlink) {
 +                            for (SyncActions action : profile.getActions()) {
 +                                action.beforeUnlink(this.getProfile(), delta, before);
 +                            }
 +                        } else {
 +                            for (SyncActions action : profile.getActions()) {
 +                                action.beforeLink(this.getProfile(), delta, before);
 +                            }
 +                        }
 +
 +                        output = link(before, result, unlink);
 +
 +                        for (SyncActions action : profile.getActions()) {
 +                            action.after(this.getProfile(), delta, AbstractSubjectTO.class.cast(output), result);
 +                        }
 +
 +                        resultStatus = Result.SUCCESS;
 +                        LOG.debug("{} {} successfully updated", attrUtil.getType(), id);
 +                    } catch (PropagationException e) {
 +                        // A propagation failure doesn't imply a synchronization failure.
 +                        // The propagation exception status will be reported into the propagation task execution.
 +                        LOG.error("Could not propagate {} {}", attrUtil.getType(), delta.getUid().getUidValue(), e);
 +                        output = e;
 +                        resultStatus = Result.FAILURE;
 +                    } catch (Exception e) {
 +                        result.setStatus(ProvisioningResult.Status.FAILURE);
 +                        result.setMessage(ExceptionUtils.getRootCauseMessage(e));
 +                        LOG.error("Could not update {} {}", attrUtil.getType(), delta.getUid().getUidValue(), e);
 +                        output = e;
 +                        resultStatus = Result.FAILURE;
 +                    }
 +                }
-                 audit(unlink ? "unlink" : "link", resultStatus, before, output, delta);
++                audit(unlink ? MatchingRule.toEventName(MatchingRule.UNLINK)
++                        : MatchingRule.toEventName(MatchingRule.LINK), resultStatus, before, output, delta);
 +            }
 +            updResults.add(result);
 +        }
 +
 +        return updResults;
 +    }
 +
 +    protected List<ProvisioningResult> delete(
 +            SyncDelta delta, final List<Long> subjects, final AttributableUtil attrUtil)
 +            throws JobExecutionException {
 +
 +        if (!profile.getTask().isPerformDelete()) {
 +            LOG.debug("SyncTask not configured for delete");
 +            return Collections.<ProvisioningResult>emptyList();
 +        }
 +
 +        LOG.debug("About to delete {}", subjects);
 +
 +        List<ProvisioningResult> delResults = new ArrayList<>();
 +
 +        for (Long id : subjects) {
 +            Object output;
 +            Result resultStatus = Result.FAILURE;
 +
 +            AbstractSubjectTO before = null;
 +            final ProvisioningResult result = new ProvisioningResult();
 +
 +            try {
 +                before = getSubjectTO(id);
 +
 +                result.setId(id);
 +                result.setName(getName(before));
 +                result.setOperation(ResourceOperation.DELETE);
 +                result.setSubjectType(attrUtil.getType());
 +                result.setStatus(ProvisioningResult.Status.SUCCESS);
 +
 +                if (!profile.isDryRun()) {
 +                    for (SyncActions action : profile.getActions()) {
 +                        delta = action.beforeDelete(this.getProfile(), delta, before);
 +                    }
 +
 +                    try {
 +                        delete(id);
 +                        output = null;
 +                        resultStatus = Result.SUCCESS;
 +                    } catch (Exception e) {
 +                        result.setStatus(ProvisioningResult.Status.FAILURE);
 +                        result.setMessage(ExceptionUtils.getRootCauseMessage(e));
 +                        LOG.error("Could not delete {} {}", attrUtil.getType(), id, e);
 +                        output = e;
 +                    }
 +
 +                    for (SyncActions action : profile.getActions()) {
 +                        action.after(this.getProfile(), delta, before, result);
 +                    }
 +
-                     audit("delete", resultStatus, before, output, delta);
++                    audit(ResourceOperation.DELETE.name().toLowerCase(), resultStatus, before, output, delta);
 +                }
 +
 +                delResults.add(result);
 +
 +            } catch (NotFoundException e) {
 +                LOG.error("Could not find {} {}", attrUtil.getType(), id, e);
 +            } catch (UnauthorizedRoleException e) {
 +                LOG.error("Not allowed to read {} {}", attrUtil.getType(), id, e);
 +            } catch (Exception e) {
 +                LOG.error("Could not delete {} {}", attrUtil.getType(), id, e);
 +            }
 +        }
 +
 +        return delResults;
 +    }
 +
++    private List<ProvisioningResult> ignore(SyncDelta delta, final AttributableUtil attrUtil, final boolean matching)
++            throws JobExecutionException {
++
++        LOG.debug("Subject to ignore {}", delta.getObject().getUid().getUidValue());
++
++        final List<ProvisioningResult> ignoreResults = new ArrayList<ProvisioningResult>();
++        final ProvisioningResult result = new ProvisioningResult();
++
++        result.setId(null);
++        result.setName(delta.getObject().getUid().getUidValue());
++        result.setOperation(ResourceOperation.NONE);
++        result.setSubjectType(attrUtil.getType());
++        result.setStatus(ProvisioningResult.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 user(s)/role(s).
 +     *
 +     * @param delta returned by the underlying profile.getConnector()
 +     * @throws JobExecutionException in case of synchronization failure.
 +     */
 +    protected final void doHandle(final SyncDelta delta)
 +            throws JobExecutionException {
 +
 +        final AttributableUtil attrUtil = getAttributableUtil();
 +
 +        LOG.debug("Process {} for {} as {}",
 +                delta.getDeltaType(), delta.getUid().getUidValue(), delta.getObject().getObjectClass());
 +
 +        final String uid = delta.getPreviousUid() == null
 +                ? delta.getUid().getUidValue()
 +                : delta.getPreviousUid().getUidValue();
 +
 +        try {
 +            List<Long> subjectKeys = syncUtilities.findExisting(
 +                    uid, delta.getObject(), profile.getTask().getResource(), attrUtil);
 +
 +            if (subjectKeys.size() > 1) {
 +                switch (profile.getResAct()) {
 +                    case IGNORE:
 +                        throw new IllegalStateException("More than one match " + subjectKeys);
 +
 +                    case FIRSTMATCH:
 +                        subjectKeys = subjectKeys.subList(0, 1);
 +                        break;
 +
 +                    case LASTMATCH:
 +                        subjectKeys = subjectKeys.subList(subjectKeys.size() - 1, subjectKeys.size());
 +                        break;
 +
 +                    default:
 +                    // keep subjectIds as is
 +                }
 +            }
 +
 +            if (SyncDeltaType.CREATE_OR_UPDATE == delta.getDeltaType()) {
 +                if (subjectKeys.isEmpty()) {
 +                    switch (profile.getTask().getUnmatchingRule()) {
 +                        case ASSIGN:
 +                            profile.getResults().addAll(assign(delta, attrUtil));
 +                            break;
 +                        case PROVISION:
 +                            profile.getResults().addAll(create(delta, attrUtil));
 +                            break;
++                        case IGNORE:
++                            profile.getResults().addAll(ignore(delta, attrUtil, false));
++                            break;
 +                        default:
 +                        // do nothing
 +                    }
 +                } else {
 +                    switch (profile.getTask().getMatchingRule()) {
 +                        case UPDATE:
 +                            profile.getResults().addAll(update(delta, subjectKeys, attrUtil));
 +                            break;
 +                        case DEPROVISION:
 +                            profile.getResults().addAll(deprovision(delta, subjectKeys, attrUtil, false));
 +                            break;
 +                        case UNASSIGN:
 +                            profile.getResults().addAll(deprovision(delta, subjectKeys, attrUtil, true));
 +                            break;
 +                        case LINK:
 +                            profile.getResults().addAll(link(delta, subjectKeys, attrUtil, false));
 +                            break;
 +                        case UNLINK:
 +                            profile.getResults().addAll(link(delta, subjectKeys, attrUtil, true));
 +                            break;
++                        case IGNORE:
++                            profile.getResults().addAll(ignore(delta, attrUtil, true));
++                            break;
 +                        default:
 +                        // do nothing
 +                    }
 +                }
 +            } else if (SyncDeltaType.DELETE == delta.getDeltaType()) {
 +                if (subjectKeys.isEmpty()) {
 +                    LOG.debug("No match found for deletion");
 +                } else {
 +                    profile.getResults().addAll(delete(delta, subjectKeys, attrUtil));
 +                }
 +            }
 +        } catch (IllegalStateException | IllegalArgumentException e) {
 +            LOG.warn(e.getMessage());
 +        }
 +    }
 +
 +    private void audit(
 +            final String event,
 +            final Result result,
 +            final Object before,
 +            final Object output,
 +            final Object... input) {
 +
 +        notificationManager.createTasks(
 +                AuditElements.EventCategoryType.SYNCHRONIZATION,
 +                getAttributableUtil().getType().name().toLowerCase(),
 +                profile.getTask().getResource().getKey(),
 +                event,
 +                result,
 +                before,
 +                output,
 +                input);
 +
 +        auditManager.audit(
 +                AuditElements.EventCategoryType.SYNCHRONIZATION,
 +                getAttributableUtil().getType().name().toLowerCase(),
 +                profile.getTask().getResource().getKey(),
 +                event,
 +                result,
 +                before,
 +                output,
 +                input);
 +    }
 +}