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

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

[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);
 +    }
 +}