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