You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2015/01/12 17:31:44 UTC
[05/52] [abbrv] [partial] syncope git commit: [SYNCOPE-620] Unit
tests all in
http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/AbstractSubjectSyncResultHandler.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/AbstractSubjectSyncResultHandler.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/AbstractSubjectSyncResultHandler.java
new file mode 100644
index 0000000..3c49eee
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/AbstractSubjectSyncResultHandler.java
@@ -0,0 +1,624 @@
+/*
+ * 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.server.provisioning.java.sync;
+
+import java.util.ArrayList;
+import java.util.Collection;
+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.ResourceOperation;
+import org.apache.syncope.server.persistence.api.dao.NotFoundException;
+import org.apache.syncope.server.persistence.api.dao.UserDAO;
+import org.apache.syncope.server.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.server.persistence.api.entity.task.SyncTask;
+import org.apache.syncope.server.provisioning.api.AttributableTransformer;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationException;
+import org.apache.syncope.server.provisioning.api.sync.SyncActions;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningResult;
+import org.apache.syncope.server.misc.security.UnauthorizedRoleException;
+import org.identityconnectors.framework.common.objects.SyncDelta;
+import org.identityconnectors.framework.common.objects.SyncDeltaType;
+import org.identityconnectors.framework.common.objects.SyncResultsHandler;
+import org.quartz.JobExecutionException;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public abstract class AbstractSubjectSyncResultHandler extends AbstractSyncopeResultHandler<SyncTask, SyncActions>
+ implements SyncResultsHandler {
+
+ @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, profile.getResults());
+ 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);
+ }
+
+ 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);
+ }
+
+ 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> updResults = new ArrayList<>();
+
+ for (Long id : subjects) {
+ LOG.debug("About to update {}", id);
+
+ Object output;
+ AbstractSubjectTO before = null;
+ Result resultStatus;
+
+ final ProvisioningResult result = new ProvisioningResult();
+ result.setOperation(ResourceOperation.UPDATE);
+ result.setSubjectType(attrUtil.getType());
+ result.setStatus(ProvisioningResult.Status.SUCCESS);
+ result.setId(id);
+
+ before = getSubjectTO(id);
+
+ if (before == null) {
+ result.setStatus(ProvisioningResult.Status.FAILURE);
+ result.setMessage(String.format("Subject '%s(%d)' not found", attrUtil.getType().name(), id));
+ } else {
+ result.setName(getName(before));
+ }
+
+ 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(), 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("update", resultStatus, before, output, delta);
+ }
+ updResults.add(result);
+ }
+ return updResults;
+ }
+
+ 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);
+ }
+ 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);
+ }
+ 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);
+ }
+
+ 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;
+ }
+
+ /**
+ * 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, final Collection<ProvisioningResult> syncResults)
+ 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> subjectIds = syncUtilities.findExisting(
+ uid, delta.getObject(), profile.getTask().getResource(), attrUtil);
+
+ if (subjectIds.size() > 1) {
+ switch (profile.getResAct()) {
+ case IGNORE:
+ throw new IllegalStateException("More than one match " + subjectIds);
+
+ case FIRSTMATCH:
+ subjectIds = subjectIds.subList(0, 1);
+ break;
+
+ case LASTMATCH:
+ subjectIds = subjectIds.subList(subjectIds.size() - 1, subjectIds.size());
+ break;
+
+ default:
+ // keep subjectIds as is
+ }
+ }
+
+ if (SyncDeltaType.CREATE_OR_UPDATE == delta.getDeltaType()) {
+ if (subjectIds.isEmpty()) {
+ switch (profile.getTask().getUnmatchingRule()) {
+ case ASSIGN:
+ profile.getResults().addAll(assign(delta, attrUtil));
+ break;
+ case PROVISION:
+ profile.getResults().addAll(create(delta, attrUtil));
+ break;
+ default:
+ // do nothing
+ }
+ } else {
+ switch (profile.getTask().getMatchingRule()) {
+ case UPDATE:
+ profile.getResults().addAll(update(delta, subjectIds, attrUtil));
+ break;
+ case DEPROVISION:
+ profile.getResults().addAll(deprovision(delta, subjectIds, attrUtil, false));
+ break;
+ case UNASSIGN:
+ profile.getResults().addAll(deprovision(delta, subjectIds, attrUtil, true));
+ break;
+ case LINK:
+ profile.getResults().addAll(link(delta, subjectIds, attrUtil, false));
+ break;
+ case UNLINK:
+ profile.getResults().addAll(link(delta, subjectIds, attrUtil, true));
+ break;
+ default:
+ // do nothing
+ }
+ }
+ } else if (SyncDeltaType.DELETE == delta.getDeltaType()) {
+ if (subjectIds.isEmpty()) {
+ LOG.debug("No match found for deletion");
+ } else {
+ profile.getResults().addAll(delete(delta, subjectIds, attrUtil));
+ }
+ }
+ } catch (IllegalStateException e) {
+ LOG.warn(e.getMessage());
+ } catch (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);
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/AbstractSyncopeResultHandler.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/AbstractSyncopeResultHandler.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/AbstractSyncopeResultHandler.java
new file mode 100644
index 0000000..fbd5b7e
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/AbstractSyncopeResultHandler.java
@@ -0,0 +1,126 @@
+/*
+ * 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.server.provisioning.java.sync;
+
+import org.apache.syncope.server.persistence.api.dao.RoleDAO;
+import org.apache.syncope.server.persistence.api.dao.UserDAO;
+import org.apache.syncope.server.persistence.api.entity.AttributableUtilFactory;
+import org.apache.syncope.server.persistence.api.entity.task.ProvisioningTask;
+import org.apache.syncope.server.provisioning.api.RoleProvisioningManager;
+import org.apache.syncope.server.provisioning.api.data.RoleDataBinder;
+import org.apache.syncope.server.provisioning.api.UserProvisioningManager;
+import org.apache.syncope.server.provisioning.api.data.UserDataBinder;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationManager;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationTaskExecutor;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningActions;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningProfile;
+import org.apache.syncope.server.provisioning.api.sync.SyncopeResultHandler;
+import org.apache.syncope.server.misc.AuditManager;
+import org.apache.syncope.server.provisioning.java.notification.NotificationManager;
+import org.apache.syncope.server.misc.ConnObjectUtil;
+import org.apache.syncope.server.workflow.api.RoleWorkflowAdapter;
+import org.apache.syncope.server.workflow.api.UserWorkflowAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public abstract class AbstractSyncopeResultHandler<T extends ProvisioningTask, A extends ProvisioningActions>
+ implements SyncopeResultHandler<T, A> {
+
+ /**
+ * Logger.
+ */
+ protected static final Logger LOG = LoggerFactory.getLogger(AbstractSyncopeResultHandler.class);
+
+ @Autowired
+ protected UserDAO userDAO;
+
+ @Autowired
+ protected RoleDAO roleDAO;
+
+ /**
+ * ConnectorObject util.
+ */
+ @Autowired
+ protected ConnObjectUtil connObjectUtil;
+
+ /**
+ * Notification Manager.
+ */
+ @Autowired
+ protected NotificationManager notificationManager;
+
+ /**
+ * Audit Manager.
+ */
+ @Autowired
+ protected AuditManager auditManager;
+
+ /**
+ * Propagation manager.
+ */
+ @Autowired
+ protected PropagationManager propagationManager;
+
+ /**
+ * Task executor.
+ */
+ @Autowired
+ protected PropagationTaskExecutor taskExecutor;
+
+ /**
+ * User workflow adapter.
+ */
+ @Autowired
+ protected UserWorkflowAdapter uwfAdapter;
+
+ /**
+ * Role workflow adapter.
+ */
+ @Autowired
+ protected RoleWorkflowAdapter rwfAdapter;
+
+ @Autowired
+ protected UserDataBinder userTransfer;
+
+ @Autowired
+ protected RoleDataBinder roleTransfer;
+
+ @Autowired
+ protected UserProvisioningManager userProvisioningManager;
+
+ @Autowired
+ protected RoleProvisioningManager roleProvisioningManager;
+
+ @Autowired
+ protected AttributableUtilFactory attrUtilFactory;
+
+ /**
+ * Sync profile.
+ */
+ protected ProvisioningProfile<T, A> profile;
+
+ public void setProfile(final ProvisioningProfile<T, A> profile) {
+ this.profile = profile;
+ }
+
+ public ProvisioningProfile<T, A> getProfile() {
+ return profile;
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/DBPasswordSyncActions.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/DBPasswordSyncActions.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/DBPasswordSyncActions.java
new file mode 100644
index 0000000..fbd60ba
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/DBPasswordSyncActions.java
@@ -0,0 +1,139 @@
+/*
+ * 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.server.provisioning.java.sync;
+
+import java.util.Iterator;
+import org.apache.syncope.common.lib.mod.AbstractSubjectMod;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.CipherAlgorithm;
+import org.apache.syncope.common.lib.types.ConnConfProperty;
+import org.apache.syncope.server.persistence.api.dao.UserDAO;
+import org.apache.syncope.server.persistence.api.entity.ConnInstance;
+import org.apache.syncope.server.persistence.api.entity.user.User;
+import org.apache.syncope.server.provisioning.api.Connector;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningProfile;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningResult;
+import org.identityconnectors.framework.common.objects.SyncDelta;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * A SyncActions implementation which allows the ability to import passwords from a Database
+ * backend, where the passwords are hashed according to the password cipher algorithm property
+ * of the (DB) Connector and HEX-encoded.
+ */
+public class DBPasswordSyncActions extends DefaultSyncActions {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DBPasswordSyncActions.class);
+
+ private static final String CLEARTEXT = "CLEARTEXT";
+
+ @Autowired
+ private UserDAO userDAO;
+
+ private String encodedPassword;
+
+ private CipherAlgorithm cipher;
+
+ @Transactional(readOnly = true)
+ @Override
+ public <T extends AbstractSubjectTO> SyncDelta beforeProvision(
+ final ProvisioningProfile<?, ?> profile,
+ final SyncDelta delta,
+ final T subject) throws JobExecutionException {
+
+ if (subject instanceof UserTO) {
+ String password = ((UserTO) subject).getPassword();
+ parseEncodedPassword(password, profile.getConnector());
+ }
+
+ return delta;
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public <T extends AbstractSubjectTO, K extends AbstractSubjectMod> SyncDelta beforeUpdate(
+ final ProvisioningProfile<?, ?> profile,
+ final SyncDelta delta,
+ final T subject,
+ final K subjectMod) throws JobExecutionException {
+
+ if (subjectMod instanceof UserMod) {
+ String modPassword = ((UserMod) subjectMod).getPassword();
+ parseEncodedPassword(modPassword, profile.getConnector());
+ }
+
+ return delta;
+ }
+
+ private void parseEncodedPassword(final String password, final Connector connector) {
+ if (password != null) {
+ ConnInstance connInstance = connector.getActiveConnInstance();
+
+ String cipherAlgorithm = getCipherAlgorithm(connInstance);
+ if (!CLEARTEXT.equals(cipherAlgorithm)) {
+ try {
+ encodedPassword = password;
+ cipher = CipherAlgorithm.valueOf(cipherAlgorithm);
+ } catch (IllegalArgumentException e) {
+ LOG.error("Cipher algorithm not allowed: {}", cipherAlgorithm, e);
+ encodedPassword = null;
+ }
+ }
+ }
+ }
+
+ private String getCipherAlgorithm(final ConnInstance connInstance) {
+ String cipherAlgorithm = CLEARTEXT;
+ for (Iterator<ConnConfProperty> propertyIterator = connInstance.getConfiguration().iterator();
+ propertyIterator.hasNext();) {
+
+ ConnConfProperty property = propertyIterator.next();
+ if ("cipherAlgorithm".equals(property.getSchema().getName())
+ && property.getValues() != null && !property.getValues().isEmpty()) {
+
+ return (String) property.getValues().get(0);
+ }
+ }
+ return cipherAlgorithm;
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public <T extends AbstractSubjectTO> void after(
+ final ProvisioningProfile<?, ?> profile,
+ final SyncDelta delta,
+ final T subject,
+ final ProvisioningResult result) throws JobExecutionException {
+
+ if (subject instanceof UserTO && encodedPassword != null && cipher != null) {
+ User syncopeUser = userDAO.find(subject.getKey());
+ if (syncopeUser != null) {
+ syncopeUser.setEncodedPassword(encodedPassword.toUpperCase(), cipher);
+ }
+ encodedPassword = null;
+ cipher = null;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/DefaultPushActions.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/DefaultPushActions.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/DefaultPushActions.java
new file mode 100644
index 0000000..a9ba96e
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/DefaultPushActions.java
@@ -0,0 +1,88 @@
+/*
+ * 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.server.provisioning.java.sync;
+
+import java.util.List;
+import org.apache.syncope.server.persistence.api.entity.Subject;
+import org.apache.syncope.server.provisioning.api.sync.PushActions;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningProfile;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningResult;
+import org.quartz.JobExecutionException;
+
+/**
+ * Default (empty) implementation of PushActions.
+ */
+public abstract class DefaultPushActions implements PushActions {
+
+ @Override
+ public void beforeAll(final ProvisioningProfile<?, ?> profile) throws JobExecutionException {
+ }
+
+ @Override
+ public <T extends Subject<?, ?, ?>> T beforeAssign(final ProvisioningProfile<?, ?> profile, final T subject)
+ throws JobExecutionException {
+
+ return subject;
+ }
+
+ @Override
+ public <T extends Subject<?, ?, ?>> T beforeProvision(final ProvisioningProfile<?, ?> profile, final T subject)
+ throws JobExecutionException {
+
+ return subject;
+ }
+
+ @Override
+ public <T extends Subject<?, ?, ?>> T beforeLink(final ProvisioningProfile<?, ?> profile, final T subject)
+ throws JobExecutionException {
+
+ return subject;
+ }
+
+ @Override
+ public <T extends Subject<?, ?, ?>> T beforeUnassign(final ProvisioningProfile<?, ?> profile, final T subject)
+ throws JobExecutionException {
+
+ return subject;
+ }
+
+ @Override
+ public <T extends Subject<?, ?, ?>> T beforeDeprovision(final ProvisioningProfile<?, ?> profile, final T subject)
+ throws JobExecutionException {
+
+ return subject;
+ }
+
+ @Override
+ public <T extends Subject<?, ?, ?>> T beforeUnlink(final ProvisioningProfile<?, ?> profile, final T subject)
+ throws JobExecutionException {
+
+ return subject;
+ }
+
+ @Override
+ public <T extends Subject<?, ?, ?>> void after(
+ final ProvisioningProfile<?, ?> profile, final T subject, final ProvisioningResult result) throws JobExecutionException {
+ }
+
+ @Override
+ public void afterAll(final ProvisioningProfile<?, ?> profile, final List<ProvisioningResult> results)
+ throws JobExecutionException {
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/DefaultSyncActions.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/DefaultSyncActions.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/DefaultSyncActions.java
new file mode 100644
index 0000000..b957d2a
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/DefaultSyncActions.java
@@ -0,0 +1,115 @@
+/*
+ * 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.server.provisioning.java.sync;
+
+import java.util.List;
+import org.apache.syncope.common.lib.mod.AbstractSubjectMod;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.server.provisioning.api.sync.SyncActions;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningProfile;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningResult;
+import org.identityconnectors.framework.common.objects.SyncDelta;
+import org.quartz.JobExecutionException;
+
+/**
+ * Default (empty) implementation of SyncActions.
+ */
+public abstract class DefaultSyncActions implements SyncActions {
+
+ @Override
+ public void beforeAll(final ProvisioningProfile<?, ?> profile) throws JobExecutionException {
+ }
+
+ @Override
+ public <T extends AbstractSubjectTO, K extends AbstractSubjectMod> SyncDelta beforeUpdate(
+ final ProvisioningProfile<?, ?> profile,
+ final SyncDelta delta,
+ final T subject,
+ final K subjectMod) throws JobExecutionException {
+
+ return delta;
+ }
+
+ @Override
+ public <T extends AbstractSubjectTO> SyncDelta beforeDelete(
+ final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final T subject)
+ throws JobExecutionException {
+
+ return delta;
+ }
+
+ @Override
+ public <T extends AbstractSubjectTO> SyncDelta beforeAssign(
+ final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final T subject)
+ throws JobExecutionException {
+
+ return delta;
+ }
+
+ @Override
+ public <T extends AbstractSubjectTO> SyncDelta beforeProvision(
+ final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final T subject)
+ throws JobExecutionException {
+
+ return delta;
+ }
+
+ @Override
+ public <T extends AbstractSubjectTO> SyncDelta beforeLink(
+ final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final T subject)
+ throws JobExecutionException {
+
+ return delta;
+ }
+
+ @Override
+ public <T extends AbstractSubjectTO> SyncDelta beforeUnassign(
+ final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final T subject)
+ throws JobExecutionException {
+
+ return delta;
+ }
+
+ @Override
+ public <T extends AbstractSubjectTO> SyncDelta beforeDeprovision(
+ final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final T subject)
+ throws JobExecutionException {
+
+ return delta;
+ }
+
+ @Override
+ public <T extends AbstractSubjectTO> SyncDelta beforeUnlink(
+ final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final T subject)
+ throws JobExecutionException {
+
+ return delta;
+ }
+
+ @Override
+ public <T extends AbstractSubjectTO> void after(
+ final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final T subject, final ProvisioningResult result)
+ throws JobExecutionException {
+ }
+
+ @Override
+ public void afterAll(final ProvisioningProfile<?, ?> profile, final List<ProvisioningResult> results)
+ throws JobExecutionException {
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/LDAPMembershipSyncActions.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/LDAPMembershipSyncActions.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/LDAPMembershipSyncActions.java
new file mode 100644
index 0000000..b705c64
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/LDAPMembershipSyncActions.java
@@ -0,0 +1,309 @@
+/*
+ * 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.server.provisioning.java.sync;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import org.apache.syncope.common.lib.mod.AbstractSubjectMod;
+import org.apache.syncope.common.lib.mod.MembershipMod;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditElements.Result;
+import org.apache.syncope.common.lib.types.ConnConfProperty;
+import org.apache.syncope.server.persistence.api.dao.RoleDAO;
+import org.apache.syncope.server.persistence.api.entity.ConnInstance;
+import org.apache.syncope.server.persistence.api.entity.ExternalResource;
+import org.apache.syncope.server.persistence.api.entity.membership.Membership;
+import org.apache.syncope.server.persistence.api.entity.role.Role;
+import org.apache.syncope.server.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.server.persistence.api.entity.task.ProvisioningTask;
+import org.apache.syncope.server.persistence.api.entity.task.SyncTask;
+import org.apache.syncope.server.provisioning.api.Connector;
+import org.apache.syncope.server.provisioning.api.WorkflowResult;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationException;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationManager;
+import org.apache.syncope.server.provisioning.api.propagation.PropagationTaskExecutor;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningProfile;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningResult;
+import org.apache.syncope.server.misc.AuditManager;
+import org.apache.syncope.server.provisioning.java.notification.NotificationManager;
+import org.apache.syncope.server.workflow.api.UserWorkflowAdapter;
+import org.identityconnectors.framework.common.objects.Attribute;
+import org.identityconnectors.framework.common.objects.ObjectClass;
+import org.identityconnectors.framework.common.objects.OperationOptionsBuilder;
+import org.identityconnectors.framework.common.objects.SyncDelta;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Simple action for synchronizing LDAP groups memberships to Syncope role memberships, when the same resource is
+ * configured for both users and roles.
+ *
+ * @see org.apache.syncope.core.propagation.impl.LDAPMembershipPropagationActions
+ */
+public class LDAPMembershipSyncActions extends DefaultSyncActions {
+
+ protected static final Logger LOG = LoggerFactory.getLogger(LDAPMembershipSyncActions.class);
+
+ @Autowired
+ protected RoleDAO roleDAO;
+
+ @Autowired
+ protected UserWorkflowAdapter uwfAdapter;
+
+ @Autowired
+ protected PropagationManager propagationManager;
+
+ @Autowired
+ private PropagationTaskExecutor taskExecutor;
+
+ @Autowired
+ private NotificationManager notificationManager;
+
+ @Autowired
+ private AuditManager auditManager;
+
+ @Autowired
+ private SyncUtilities syncUtilities;
+
+ protected Map<Long, Long> membersBeforeRoleUpdate = Collections.<Long, Long>emptyMap();
+
+ /**
+ * Allows easy subclassing for the ConnId AD connector bundle.
+ *
+ * @param connector A Connector instance to query for the groupMemberAttribute property name
+ * @return the name of the attribute used to keep track of group memberships
+ */
+ protected String getGroupMembershipAttrName(final Connector connector) {
+ ConnInstance connInstance = connector.getActiveConnInstance();
+ Iterator<ConnConfProperty> propertyIterator = connInstance.getConfiguration().iterator();
+ String groupMembershipName = "uniquemember";
+ while (propertyIterator.hasNext()) {
+ ConnConfProperty property = propertyIterator.next();
+ if ("groupMemberAttribute".equals(property.getSchema().getName())
+ && property.getValues() != null && !property.getValues().isEmpty()) {
+
+ groupMembershipName = (String) property.getValues().get(0);
+ break;
+ }
+ }
+
+ return groupMembershipName;
+ }
+
+ /**
+ * Keep track of members of the role being updated <b>before</b> actual update takes place. This is not needed on
+ * <ul> <li>beforeProvision() - because the synchronizing role does not exist yet on Syncope</li> <li>beforeDelete()
+ * -
+ * because role delete cascades as membership removal for all users involved</li> </ul>
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public <T extends AbstractSubjectTO, K extends AbstractSubjectMod> SyncDelta beforeUpdate(
+ final ProvisioningProfile<?, ?> profile,
+ final SyncDelta delta, final T subject, final K subjectMod) throws JobExecutionException {
+
+ if (subject instanceof RoleTO) {
+ // search for all users assigned to given role
+ Role role = roleDAO.find(subject.getKey());
+ if (role != null) {
+ List<Membership> membs = roleDAO.findMemberships(role);
+ // save memberships before role update takes place
+ membersBeforeRoleUpdate = new HashMap<>(membs.size());
+ for (Membership memb : membs) {
+ membersBeforeRoleUpdate.put(memb.getUser().getKey(), memb.getKey());
+ }
+ }
+ }
+
+ return super.beforeUpdate(profile, delta, subject, subjectMod);
+ }
+
+ /**
+ * Build UserMod for adding membership to given user, for given role.
+ *
+ * @param userId user to be assigned membership to given role
+ * @param roleTO role for adding membership
+ * @return UserMod for user update
+ */
+ protected UserMod getUserMod(final Long userId, final RoleTO roleTO) {
+ UserMod userMod = new UserMod();
+ // no actual modification takes place when user has already the role assigned
+ if (membersBeforeRoleUpdate.containsKey(userId)) {
+ membersBeforeRoleUpdate.remove(userId);
+ } else {
+ userMod.setKey(userId);
+
+ MembershipMod membershipMod = new MembershipMod();
+ membershipMod.setRole(roleTO.getKey());
+ userMod.getMembershipsToAdd().add(membershipMod);
+ }
+
+ return userMod;
+ }
+
+ /**
+ * Read values of attribute returned by getGroupMembershipAttrName(); if not present in the given delta, perform an
+ * additional read on the underlying connector.
+ *
+ * @param delta representing the synchronizing role
+ * @param connector associated to the current resource
+ * @return value of attribute returned by
+ * {@link #getGroupMembershipAttrName(org.apache.syncope.core.propagation.Connector) }
+ */
+ protected List<Object> getMembAttrValues(final SyncDelta delta, final Connector connector) {
+ List<Object> result = Collections.<Object>emptyList();
+ String groupMemberName = getGroupMembershipAttrName(connector);
+
+ // first, try to read the configured attribute from delta, returned by the ongoing synchronization
+ Attribute membAttr = delta.getObject().getAttributeByName(groupMemberName);
+ // if not found, perform an additional read on the underlying connector for the same connector object
+ if (membAttr == null) {
+ final OperationOptionsBuilder oob = new OperationOptionsBuilder();
+ oob.setAttributesToGet(groupMemberName);
+ membAttr = connector.getObjectAttribute(ObjectClass.GROUP, delta.getUid(), oob.build(), groupMemberName);
+ }
+ if (membAttr != null && membAttr.getValue() != null) {
+ result = membAttr.getValue();
+ }
+
+ return result;
+ }
+
+ /**
+ * Perform actual modifications (i.e. membership add / remove) for the given role on the given resource.
+ *
+ * @param userMod modifications to perform on the user
+ * @param resourceName resource to be propagated for changes
+ */
+ protected void userUpdate(final UserMod userMod, final String resourceName) {
+ if (userMod.getKey() == 0) {
+ return;
+ }
+
+ Result result;
+
+ WorkflowResult<Map.Entry<UserMod, Boolean>> updated = null;
+
+ try {
+ updated = uwfAdapter.update(userMod);
+
+ List<PropagationTask> tasks = propagationManager.getUserUpdateTaskIds(
+ updated, false, Collections.singleton(resourceName));
+
+ taskExecutor.execute(tasks);
+ result = Result.SUCCESS;
+ } catch (PropagationException e) {
+ result = Result.FAILURE;
+ LOG.error("Could not propagate {}", userMod, e);
+ } catch (Exception e) {
+ result = Result.FAILURE;
+ LOG.error("Could not perform update {}", userMod, e);
+ }
+
+ notificationManager.createTasks(
+ AuditElements.EventCategoryType.SYNCHRONIZATION,
+ this.getClass().getSimpleName(),
+ null,
+ "update",
+ result,
+ null, // searching for before object is too much expensive ...
+ updated == null ? null : updated.getResult().getKey(),
+ userMod,
+ resourceName);
+
+ auditManager.audit(
+ AuditElements.EventCategoryType.SYNCHRONIZATION,
+ this.getClass().getSimpleName(),
+ null,
+ "update",
+ result,
+ null, // searching for before object is too much expensive ...
+ updated == null ? null : updated.getResult().getKey(),
+ userMod,
+ resourceName);
+ }
+
+ /**
+ * Synchronize Syncope memberships with the situation read on the external resource's group.
+ *
+ * @param profile sync profile
+ * @param delta representing the synchronizing role
+ * @param roleTO role after modification performed by the handler
+ * @throws JobExecutionException if anything goes wrong
+ */
+ protected void synchronizeMemberships(
+ final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final RoleTO roleTO) throws
+ JobExecutionException {
+
+ final ProvisioningTask task = profile.getTask();
+ final ExternalResource resource = task.getResource();
+ final Connector connector = profile.getConnector();
+
+ for (Object membValue : getMembAttrValues(delta, connector)) {
+ Long userId = syncUtilities.findMatchingAttributableId(
+ ObjectClass.ACCOUNT,
+ membValue.toString(),
+ profile.getTask().getResource(),
+ profile.getConnector());
+ if (userId != null) {
+ UserMod userMod = getUserMod(userId, roleTO);
+ userUpdate(userMod, resource.getKey());
+ }
+ }
+
+ // finally remove any residual membership that was present before role update but not any more
+ for (Map.Entry<Long, Long> member : membersBeforeRoleUpdate.entrySet()) {
+ UserMod userMod = new UserMod();
+ userMod.setKey(member.getKey());
+ userMod.getMembershipsToRemove().add(member.getValue());
+ userUpdate(userMod, resource.getKey());
+ }
+ }
+
+ /**
+ * Synchronize membership at role synchronization time (because SyncJob first synchronize users then roles).
+ * {@inheritDoc}
+ */
+ @Override
+ public <T extends AbstractSubjectTO> void after(
+ final ProvisioningProfile<?, ?> profile,
+ final SyncDelta delta,
+ final T subject,
+ final ProvisioningResult result) throws JobExecutionException {
+
+ if (!(profile.getTask() instanceof SyncTask)) {
+ return;
+ }
+
+ if (!(subject instanceof RoleTO) || profile.getTask().getResource().getUmapping() == null) {
+ super.after(profile, delta, subject, result);
+ } else {
+ synchronizeMemberships(profile, delta, (RoleTO) subject);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/LDAPPasswordSyncActions.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/LDAPPasswordSyncActions.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/LDAPPasswordSyncActions.java
new file mode 100644
index 0000000..5508cee
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/LDAPPasswordSyncActions.java
@@ -0,0 +1,123 @@
+/*
+ * 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.server.provisioning.java.sync;
+
+import org.apache.syncope.common.lib.mod.AbstractSubjectMod;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.CipherAlgorithm;
+import org.apache.syncope.server.persistence.api.dao.UserDAO;
+import org.apache.syncope.server.persistence.api.entity.user.User;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningProfile;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningResult;
+import org.identityconnectors.framework.common.objects.SyncDelta;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.crypto.codec.Base64;
+import org.springframework.security.crypto.codec.Hex;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * A SyncActions implementation which allows the ability to import passwords from an LDAP backend
+ * that are hashed.
+ */
+public class LDAPPasswordSyncActions extends DefaultSyncActions {
+
+ protected static final Logger LOG = LoggerFactory.getLogger(LDAPPasswordSyncActions.class);
+
+ @Autowired
+ private UserDAO userDAO;
+
+ private String encodedPassword;
+
+ private CipherAlgorithm cipher;
+
+ @Transactional(readOnly = true)
+ @Override
+ public <T extends AbstractSubjectTO> SyncDelta beforeProvision(
+ final ProvisioningProfile<?, ?> profile,
+ final SyncDelta delta,
+ final T subject) throws JobExecutionException {
+
+ if (subject instanceof UserTO) {
+ String password = ((UserTO) subject).getPassword();
+ parseEncodedPassword(password);
+ }
+
+ return delta;
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public <T extends AbstractSubjectTO, K extends AbstractSubjectMod> SyncDelta beforeUpdate(
+ final ProvisioningProfile<?, ?> profile,
+ final SyncDelta delta,
+ final T subject,
+ final K subjectMod) throws JobExecutionException {
+
+ if (subjectMod instanceof UserMod) {
+ String modPassword = ((UserMod) subjectMod).getPassword();
+ parseEncodedPassword(modPassword);
+ }
+
+ return delta;
+ }
+
+ private void parseEncodedPassword(String password) {
+ if (password != null && password.startsWith("{")) {
+ int closingBracketIndex = password.indexOf('}');
+ String digest = password.substring(1, password.indexOf('}'));
+ if (digest != null) {
+ digest = digest.toUpperCase();
+ }
+ try {
+ encodedPassword = password.substring(closingBracketIndex + 1);
+ cipher = CipherAlgorithm.valueOf(digest);
+ } catch (IllegalArgumentException e) {
+ LOG.error("Cipher algorithm not allowed: {}", digest, e);
+ encodedPassword = null;
+ }
+ }
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public <T extends AbstractSubjectTO> void after(
+ final ProvisioningProfile<?, ?> profile,
+ final SyncDelta delta,
+ final T subject,
+ final ProvisioningResult result) throws JobExecutionException {
+
+ if (subject instanceof UserTO && encodedPassword != null && cipher != null) {
+ User syncopeUser = userDAO.find(subject.getKey());
+ if (syncopeUser != null) {
+ byte[] encodedPasswordBytes = Base64.decode(encodedPassword.getBytes());
+ char[] encodedHex = Hex.encode(encodedPasswordBytes);
+ String encodedHexStr = new String(encodedHex).toUpperCase();
+
+ syncopeUser.setEncodedPassword(encodedHexStr, cipher);
+ }
+ encodedPassword = null;
+ cipher = null;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/PushJobImpl.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/PushJobImpl.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/PushJobImpl.java
new file mode 100644
index 0000000..d40b4be
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/PushJobImpl.java
@@ -0,0 +1,176 @@
+/*
+ * 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.server.provisioning.java.sync;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.types.SubjectType;
+import org.apache.syncope.server.persistence.api.RoleEntitlementUtil;
+import org.apache.syncope.server.persistence.api.dao.RoleDAO;
+import org.apache.syncope.server.persistence.api.dao.SubjectSearchDAO;
+import org.apache.syncope.server.persistence.api.dao.UserDAO;
+import org.apache.syncope.server.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.server.persistence.api.entity.role.RMapping;
+import org.apache.syncope.server.persistence.api.entity.role.Role;
+import org.apache.syncope.server.persistence.api.entity.task.PushTask;
+import org.apache.syncope.server.persistence.api.entity.user.UMapping;
+import org.apache.syncope.server.persistence.api.entity.user.User;
+import org.apache.syncope.server.provisioning.api.Connector;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningProfile;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningResult;
+import org.apache.syncope.server.provisioning.api.sync.PushActions;
+import org.apache.syncope.server.misc.spring.ApplicationContextProvider;
+import org.apache.syncope.server.misc.search.SearchCondConverter;
+import org.apache.syncope.server.provisioning.api.job.PushJob;
+import org.quartz.JobExecutionException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+
+/**
+ * Job for executing synchronization (towards external resource) tasks.
+ *
+ * @see AbstractProvisioningJob
+ * @see PushTask
+ */
+public class PushJobImpl extends AbstractProvisioningJob<PushTask, PushActions> implements PushJob {
+
+ /**
+ * User DAO.
+ */
+ @Autowired
+ private UserDAO userDAO;
+
+ /**
+ * Search DAO.
+ */
+ @Autowired
+ private SubjectSearchDAO searchDAO;
+
+ /**
+ * Role DAO.
+ */
+ @Autowired
+ private RoleDAO roleDAO;
+
+ private final int PAGE_SIZE = 1000;
+
+ @Override
+ protected String executeWithSecurityContext(
+ final PushTask pushTask,
+ final Connector connector,
+ final UMapping uMapping,
+ final RMapping rMapping,
+ final boolean dryRun) throws JobExecutionException {
+ LOG.debug("Execute synchronization (push) with resource {}", pushTask.getResource());
+
+ final List<ProvisioningResult> results = new ArrayList<>();
+
+ final Set<Long> authorizations = RoleEntitlementUtil.getRoleKeys(entitlementDAO.findAll());
+
+ final ProvisioningProfile<PushTask, PushActions> profile = new ProvisioningProfile<>(connector, pushTask);
+ profile.getActions().addAll(actions);
+ profile.setDryRun(dryRun);
+ profile.setResAct(null);
+ profile.getResults().addAll(results);
+
+ final UserPushResultHandler uhandler =
+ (UserPushResultHandler) ApplicationContextProvider.getApplicationContext().getBeanFactory().createBean(
+ UserPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
+ uhandler.setProfile(profile);
+
+ final RolePushResultHandler rhandler =
+ (RolePushResultHandler) ApplicationContextProvider.getApplicationContext().getBeanFactory().createBean(
+ RolePushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
+ rhandler.setProfile(profile);
+
+ if (!profile.isDryRun()) {
+ for (PushActions action : actions) {
+ action.beforeAll(profile);
+ }
+ }
+
+ if (uMapping != null) {
+ final int count = userDAO.count(authorizations);
+ for (int page = 1; page <= (count / PAGE_SIZE) + 1; page++) {
+ final List<User> localUsers = getUsers(authorizations, pushTask, page);
+
+ for (User localUser : localUsers) {
+ try {
+ // user propagation
+ uhandler.handle(localUser.getKey());
+ } catch (Exception e) {
+ LOG.warn("Failure pushing user '{}' on '{}'", localUser, pushTask.getResource(), e);
+ throw new JobExecutionException("While pushing users on connector", e);
+ }
+ }
+ }
+ }
+
+ if (rMapping != null) {
+ final List<Role> localRoles = getRoles(authorizations, pushTask);
+
+ for (Role localRole : localRoles) {
+ try {
+ // role propagation
+ rhandler.handle(localRole.getKey());
+ } catch (Exception e) {
+ LOG.warn("Failure pushing role '{}' on '{}'", localRole, pushTask.getResource(), e);
+ throw new JobExecutionException("While pushing roles on connector", e);
+ }
+ }
+ }
+
+ if (!profile.isDryRun()) {
+ for (PushActions action : actions) {
+ action.afterAll(profile, results);
+ }
+ }
+
+ final String result = createReport(results, pushTask.getResource().getSyncTraceLevel(), dryRun);
+
+ LOG.debug("Sync result: {}", result);
+
+ return result;
+ }
+
+ private List<User> getUsers(final Set<Long> authorizations, final PushTask pushTask, final int page) {
+ final String filter = pushTask.getUserFilter();
+ if (StringUtils.isBlank(filter)) {
+ return userDAO.findAll(authorizations, page, PAGE_SIZE);
+ } else {
+ return searchDAO.<User>search(
+ authorizations, SearchCondConverter.convert(filter),
+ Collections.<OrderByClause>emptyList(), SubjectType.USER);
+ }
+ }
+
+ private List<Role> getRoles(final Set<Long> authorizations, final PushTask pushTask) {
+ final String filter = pushTask.getRoleFilter();
+ if (StringUtils.isBlank(filter)) {
+ return roleDAO.findAll();
+ } else {
+ return searchDAO.<Role>search(
+ authorizations, SearchCondConverter.convert(filter),
+ Collections.<OrderByClause>emptyList(), SubjectType.ROLE);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/RolePushResultHandler.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/RolePushResultHandler.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/RolePushResultHandler.java
new file mode 100644
index 0000000..60d6bdb
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/RolePushResultHandler.java
@@ -0,0 +1,154 @@
+/*
+ * 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.server.provisioning.java.sync;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.apache.syncope.common.lib.mod.RoleMod;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.types.PropagationByResource;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.server.persistence.api.entity.Mapping;
+import org.apache.syncope.server.persistence.api.entity.MappingItem;
+import org.apache.syncope.server.persistence.api.entity.Subject;
+import org.apache.syncope.server.persistence.api.entity.role.Role;
+import org.apache.syncope.server.provisioning.api.TimeoutException;
+import org.identityconnectors.framework.common.objects.ConnectorObject;
+import org.identityconnectors.framework.common.objects.ObjectClass;
+import org.identityconnectors.framework.common.objects.Uid;
+
+public class RolePushResultHandler extends AbstractSubjectPushResultHandler {
+
+ @Override
+ protected Subject<?, ?, ?> deprovision(final Subject<?, ?, ?> sbj) {
+ final RoleTO before = roleTransfer.getRoleTO(Role.class.cast(sbj));
+
+ final List<String> noPropResources = new ArrayList<>(before.getResources());
+ noPropResources.remove(profile.getTask().getResource().getKey());
+
+ taskExecutor.execute(propagationManager.getRoleDeleteTaskIds(before.getKey(), noPropResources));
+
+ return roleDAO.authFetch(before.getKey());
+ }
+
+ @Override
+ protected Subject<?, ?, ?> provision(final Subject<?, ?, ?> sbj, final Boolean enabled) {
+ final RoleTO before = roleTransfer.getRoleTO(Role.class.cast(sbj));
+
+ final List<String> noPropResources = new ArrayList<>(before.getResources());
+ noPropResources.remove(profile.getTask().getResource().getKey());
+
+ final PropagationByResource propByRes = new PropagationByResource();
+ propByRes.add(ResourceOperation.CREATE, profile.getTask().getResource().getKey());
+
+ taskExecutor.execute(propagationManager.getRoleCreateTaskIds(
+ before.getKey(),
+ Collections.unmodifiableCollection(before.getVirAttrs()),
+ propByRes,
+ noPropResources));
+
+ return roleDAO.authFetch(before.getKey());
+ }
+
+ @Override
+ protected Subject<?, ?, ?> link(final Subject<?, ?, ?> sbj, final Boolean unlink) {
+ final RoleMod roleMod = new RoleMod();
+ roleMod.setKey(sbj.getKey());
+
+ if (unlink) {
+ roleMod.getResourcesToRemove().add(profile.getTask().getResource().getKey());
+ } else {
+ roleMod.getResourcesToAdd().add(profile.getTask().getResource().getKey());
+ }
+
+ rwfAdapter.update(roleMod);
+
+ return roleDAO.authFetch(sbj.getKey());
+ }
+
+ @Override
+ protected Subject<?, ?, ?> unassign(final Subject<?, ?, ?> sbj) {
+ final RoleMod roleMod = new RoleMod();
+ roleMod.setKey(sbj.getKey());
+ roleMod.getResourcesToRemove().add(profile.getTask().getResource().getKey());
+ rwfAdapter.update(roleMod);
+ return deprovision(sbj);
+ }
+
+ @Override
+ protected Subject<?, ?, ?> assign(final Subject<?, ?, ?> sbj, final Boolean enabled) {
+ final RoleMod roleMod = new RoleMod();
+ roleMod.setKey(sbj.getKey());
+ roleMod.getResourcesToAdd().add(profile.getTask().getResource().getKey());
+ rwfAdapter.update(roleMod);
+ return provision(sbj, enabled);
+ }
+
+ @Override
+ protected String getName(final Subject<?, ?, ?> subject) {
+ return Role.class.cast(subject).getName();
+ }
+
+ @Override
+ protected AbstractSubjectTO getSubjectTO(final long key) {
+ try {
+ return roleTransfer.getRoleTO(roleDAO.authFetch(key));
+ } catch (Exception e) {
+ LOG.warn("Error retrieving user {}", key, e);
+ return null;
+ }
+ }
+
+ @Override
+ protected Subject<?, ?, ?> getSubject(final long key) {
+ try {
+ return roleDAO.authFetch(key);
+ } catch (Exception e) {
+ LOG.warn("Error retrieving role {}", key, e);
+ return null;
+ }
+ }
+
+ @Override
+ protected ConnectorObject getRemoteObject(final String accountId) {
+ ConnectorObject obj = null;
+
+ try {
+ final Uid uid = new Uid(accountId);
+
+ obj = profile.getConnector().getObject(
+ ObjectClass.GROUP,
+ uid,
+ profile.getConnector().getOperationOptions(Collections.<MappingItem>emptySet()));
+ } catch (TimeoutException toe) {
+ LOG.debug("Request timeout", toe);
+ throw toe;
+ } catch (RuntimeException ignore) {
+ LOG.debug("While resolving {}", accountId, ignore);
+ }
+ return obj;
+ }
+
+ @Override
+ protected Mapping<?> getMapping() {
+ return profile.getTask().getResource().getRmapping();
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/235f60fa/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/RoleSyncResultHandler.java
----------------------------------------------------------------------
diff --git a/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/RoleSyncResultHandler.java b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/RoleSyncResultHandler.java
new file mode 100644
index 0000000..d172077
--- /dev/null
+++ b/syncope620/server/provisioning-java/src/main/java/org/apache/syncope/server/provisioning/java/sync/RoleSyncResultHandler.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.server.provisioning.java.sync;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.syncope.common.lib.mod.AbstractSubjectMod;
+import org.apache.syncope.common.lib.mod.AttrMod;
+import org.apache.syncope.common.lib.mod.RoleMod;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.to.PropagationStatus;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.server.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.server.provisioning.api.sync.ProvisioningResult;
+import org.identityconnectors.framework.common.objects.SyncDelta;
+
+public class RoleSyncResultHandler extends AbstractSubjectSyncResultHandler {
+
+ protected Map<Long, String> roleOwnerMap = new HashMap<>();
+
+ public Map<Long, String> getRoleOwnerMap() {
+ return this.roleOwnerMap;
+ }
+
+ @Override
+ protected AttributableUtil getAttributableUtil() {
+ return attrUtilFactory.getInstance(AttributableType.ROLE);
+ }
+
+ @Override
+ protected String getName(final AbstractSubjectTO subjectTO) {
+ return RoleTO.class.cast(subjectTO).getName();
+ }
+
+ @Override
+ protected AbstractSubjectTO getSubjectTO(final long key) {
+ try {
+ return roleTransfer.getRoleTO(roleDAO.authFetch(key));
+ } catch (Exception e) {
+ LOG.warn("Error retrieving role {}", key, e);
+ return null;
+ }
+ }
+
+ @Override
+ protected AbstractSubjectMod getSubjectMod(
+ final AbstractSubjectTO subjectTO, final SyncDelta delta) {
+
+ return connObjectUtil.getAttributableMod(
+ subjectTO.getKey(),
+ delta.getObject(),
+ subjectTO,
+ profile.getTask(),
+ attrUtilFactory.getInstance(AttributableType.ROLE));
+ }
+
+ @Override
+ protected AbstractSubjectTO create(
+ final AbstractSubjectTO subjectTO, final SyncDelta _delta, final ProvisioningResult result) {
+
+ RoleTO roleTO = RoleTO.class.cast(subjectTO);
+
+ Map.Entry<Long, List<PropagationStatus>> created = roleProvisioningManager.create(roleTO, roleOwnerMap,
+ Collections.singleton(profile.getTask().getResource().getKey()));
+
+ roleTO = roleTransfer.getRoleTO(roleDAO.authFetch(created.getKey()));
+
+ result.setId(created.getKey());
+ result.setName(getName(subjectTO));
+
+ return roleTO;
+ }
+
+ @Override
+ protected AbstractSubjectTO link(
+ final AbstractSubjectTO before,
+ final ProvisioningResult result,
+ final boolean unlink) {
+
+ final RoleMod roleMod = new RoleMod();
+ roleMod.setKey(before.getKey());
+
+ if (unlink) {
+ roleMod.getResourcesToRemove().add(profile.getTask().getResource().getKey());
+ } else {
+ roleMod.getResourcesToAdd().add(profile.getTask().getResource().getKey());
+ }
+
+ return roleTransfer.getRoleTO(roleDAO.authFetch(rwfAdapter.update(roleMod).getResult()));
+ }
+
+ @Override
+ protected AbstractSubjectTO update(
+ final AbstractSubjectTO before,
+ final AbstractSubjectMod subjectMod,
+ final SyncDelta delta,
+ final ProvisioningResult result) {
+
+ RoleMod roleMod = RoleMod.class.cast(subjectMod);
+
+ Map.Entry<Long, List<PropagationStatus>> updated = roleProvisioningManager.update(roleMod);
+
+ //moved after role provisioning manager
+ String roleOwner = null;
+ for (AttrMod attrMod : roleMod.getAttrsToUpdate()) {
+ if (attrMod.getSchema().isEmpty()) {
+ roleOwner = attrMod.getValuesToBeAdded().iterator().next();
+ }
+ }
+ if (roleOwner != null) {
+ roleOwnerMap.put(updated.getKey(), roleOwner);
+ }
+
+ final RoleTO after = roleTransfer.getRoleTO(roleDAO.authFetch(updated.getKey()));
+
+ result.setName(getName(after));
+
+ return after;
+ }
+
+ @Override
+ protected void deprovision(final Long id, final boolean unlink) {
+
+ taskExecutor.execute(
+ propagationManager.getRoleDeleteTaskIds(id, profile.getTask().getResource().getKey()));
+
+ if (unlink) {
+ final UserMod userMod = new UserMod();
+ userMod.setKey(id);
+ userMod.getResourcesToRemove().add(profile.getTask().getResource().getKey());
+ }
+ }
+
+ @Override
+ protected void delete(final Long id) {
+ try {
+ taskExecutor.execute(
+ propagationManager.getRoleDeleteTaskIds(id, profile.getTask().getResource().getKey()));
+ } catch (Exception 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 user " + id, e);
+ }
+
+ roleProvisioningManager.delete(id);
+ }
+}