[SYNCOPE-710] 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/2011671c Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/2011671c Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/2011671c Branch: refs/heads/master Commit: 2011671c93ac23a2397bcc8be39a1ca191ed035f Parents: 4133c24 2524280 Author: Francesco Chicchiriccò <[email protected]> Authored: Thu Oct 15 12:05:33 2015 +0200 Committer: Francesco Chicchiriccò <[email protected]> Committed: Thu Oct 15 12:05:33 2015 +0200 ---------------------------------------------------------------------- .../DefaultAnyObjectProvisioningManager.java | 28 +++++-------- .../java/DefaultGroupProvisioningManager.java | 28 +++++-------- .../java/DefaultUserProvisioningManager.java | 42 +++++++++----------- .../java/data/UserDataBinderImpl.java | 13 +++--- .../propagation/PropagationManagerImpl.java | 8 +--- .../activiti/ActivitiUserWorkflowAdapter.java | 17 +++++++- .../workflow/activiti/task/PasswordReset.java | 24 ++++++++++- .../core/workflow/activiti/task/Update.java | 21 +--------- .../core/workflow/api/UserWorkflowAdapter.java | 3 +- .../java/AbstractUserWorkflowAdapter.java | 9 +++-- .../java/DefaultUserWorkflowAdapter.java | 33 ++++++--------- .../processor/AnyObjectUpdateProcessor.java | 29 +++++--------- .../camel/processor/GroupUpdateProcessor.java | 29 +++++--------- .../processor/UserConfirmPwdResetProcessor.java | 14 ++----- .../UserStatusPropagationProcessor.java | 15 ++++--- .../camel/processor/UserUpdateProcessor.java | 21 +++++----- .../syncope/fit/core/reference/UserITCase.java | 34 +++++++++++++++- .../fit/core/reference/VirAttrITCase.java | 4 +- 18 files changed, 188 insertions(+), 184 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultAnyObjectProvisioningManager.java ---------------------------------------------------------------------- diff --cc core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultAnyObjectProvisioningManager.java index bc1dbc2,0000000..d332e23 mode 100644,000000..100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultAnyObjectProvisioningManager.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultAnyObjectProvisioningManager.java @@@ -1,226 -1,0 +1,220 @@@ +/* + * 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; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.syncope.common.lib.patch.AnyObjectPatch; +import org.apache.syncope.common.lib.to.PropagationStatus; +import org.apache.syncope.common.lib.to.AnyObjectTO; +import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.syncope.common.lib.types.PropagationByResource; +import org.apache.syncope.common.lib.types.ResourceOperation; +import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO; +import org.apache.syncope.core.persistence.api.entity.task.PropagationTask; +import org.apache.syncope.core.provisioning.api.AnyObjectProvisioningManager; +import org.apache.syncope.core.provisioning.api.WorkflowResult; +import org.apache.syncope.core.provisioning.api.propagation.PropagationException; +import org.apache.syncope.core.provisioning.api.propagation.PropagationManager; +import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter; +import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor; +import org.apache.syncope.core.misc.spring.ApplicationContextProvider; +import org.apache.syncope.core.provisioning.api.VirAttrHandler; +import org.apache.syncope.core.workflow.api.AnyObjectWorkflowAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +public class DefaultAnyObjectProvisioningManager implements AnyObjectProvisioningManager { + + private static final Logger LOG = LoggerFactory.getLogger(AnyObjectProvisioningManager.class); + + @Autowired + protected AnyObjectWorkflowAdapter awfAdapter; + + @Autowired + protected PropagationManager propagationManager; + + @Autowired + protected PropagationTaskExecutor taskExecutor; + + @Autowired + protected VirAttrHandler virtAttrHandler; + + @Autowired + protected AnyObjectDAO anyObjectDAO; + + @Override + public Pair<Long, List<PropagationStatus>> create(final AnyObjectTO anyObjectTO) { + return create(anyObjectTO, Collections.<String>emptySet()); + } + + @Override + public Pair<Long, List<PropagationStatus>> create( + final AnyObjectTO anyObjectTO, final Set<String> excludedResources) { + + WorkflowResult<Long> created = awfAdapter.create(anyObjectTO); + + List<PropagationTask> tasks = propagationManager.getCreateTasks( + AnyTypeKind.ANY_OBJECT, + created.getResult(), + created.getPropByRes(), + anyObjectTO.getVirAttrs(), + excludedResources); + PropagationReporter propagationReporter = + ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propagationReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propagationReporter.onPrimaryResourceFailure(tasks); + } + + return new ImmutablePair<>(created.getResult(), propagationReporter.getStatuses()); + } + + @Override + public Pair<Long, List<PropagationStatus>> update(final AnyObjectPatch anyObjectPatch) { + return update(anyObjectPatch, Collections.<String>emptySet()); + } + + @Override + public Pair<Long, List<PropagationStatus>> update( + final AnyObjectPatch anyObjectPatch, final Set<String> excludedResources) { + + WorkflowResult<Long> updated = awfAdapter.update(anyObjectPatch); + ++ // SYNCOPE-459: take care of user virtual attributes ... ++ PropagationByResource propByResVirAttr = virtAttrHandler.updateVirtual( ++ updated.getResult(), ++ AnyTypeKind.ANY_OBJECT, ++ anyObjectPatch.getVirAttrs()); ++ if (updated.getPropByRes() == null) { ++ updated.setPropByRes(propByResVirAttr); ++ } else { ++ updated.getPropByRes().merge(propByResVirAttr); ++ } ++ + List<PropagationTask> tasks = propagationManager.getUpdateTasks( + AnyTypeKind.ANY_OBJECT, + updated.getResult(), + false, + null, + updated.getPropByRes(), + anyObjectPatch.getVirAttrs(), + excludedResources); - if (tasks.isEmpty()) { - // SYNCOPE-459: take care of user virtual attributes ... - PropagationByResource propByResVirAttr = virtAttrHandler.updateVirtual( - updated.getResult(), - AnyTypeKind.ANY_OBJECT, - anyObjectPatch.getVirAttrs()); - tasks.addAll(!propByResVirAttr.isEmpty() - ? propagationManager.getUpdateTasks( - AnyTypeKind.ANY_OBJECT, - updated.getResult(), - false, - null, - updated.getPropByRes(), - anyObjectPatch.getVirAttrs(), - null) - : Collections.<PropagationTask>emptyList()); - } + + PropagationReporter propagationReporter = + ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propagationReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propagationReporter.onPrimaryResourceFailure(tasks); + } + + return new ImmutablePair<>(updated.getResult(), propagationReporter.getStatuses()); + } + + @Override + public List<PropagationStatus> delete(final Long key) { + return delete(key, Collections.<String>emptySet()); + } + + @Override + public List<PropagationStatus> delete(final Long key, final Set<String> excludedResources) { + List<PropagationTask> tasks = propagationManager.getDeleteTasks( + AnyTypeKind.ANY_OBJECT, + key, + null, + excludedResources); + PropagationReporter propagationReporter = + ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propagationReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propagationReporter.onPrimaryResourceFailure(tasks); + } + + awfAdapter.delete(key); + + return propagationReporter.getStatuses(); + } + + @Override + public Long unlink(final AnyObjectPatch anyObjectPatch) { + return awfAdapter.update(anyObjectPatch).getResult(); + } + + @Override + public Long link(final AnyObjectPatch anyObjectPatch) { + return awfAdapter.update(anyObjectPatch).getResult(); + } + + @Override + public List<PropagationStatus> provision(final Long key, final Collection<String> resources) { + PropagationByResource propByRes = new PropagationByResource(); + propByRes.addAll(ResourceOperation.UPDATE, resources); + + List<PropagationTask> tasks = propagationManager.getUpdateTasks( + AnyTypeKind.ANY_OBJECT, + key, + false, + null, + propByRes, + null, + null); + PropagationReporter propagationReporter = + ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propagationReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propagationReporter.onPrimaryResourceFailure(tasks); + } + return propagationReporter.getStatuses(); + } + + @Override + public List<PropagationStatus> deprovision(final Long key, final Collection<String> resources) { + PropagationByResource propByRes = new PropagationByResource(); + propByRes.addAll(ResourceOperation.DELETE, resources); + + List<PropagationTask> tasks = propagationManager.getDeleteTasks( + AnyTypeKind.ANY_OBJECT, + key, + propByRes, + CollectionUtils.removeAll(anyObjectDAO.findAllResourceNames(anyObjectDAO.authFind(key)), resources)); + PropagationReporter propagationReporter = + ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propagationReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propagationReporter.onPrimaryResourceFailure(tasks); + } + return propagationReporter.getStatuses(); + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultGroupProvisioningManager.java ---------------------------------------------------------------------- diff --cc core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultGroupProvisioningManager.java index b933488,0000000..0fff2a3 mode 100644,000000..100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultGroupProvisioningManager.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultGroupProvisioningManager.java @@@ -1,285 -1,0 +1,279 @@@ +/* + * 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; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; +import org.apache.commons.collections4.CollectionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.syncope.common.lib.patch.GroupPatch; +import org.apache.syncope.common.lib.to.AttrTO; +import org.apache.syncope.common.lib.to.PropagationStatus; +import org.apache.syncope.common.lib.to.GroupTO; +import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.syncope.common.lib.types.PropagationByResource; +import org.apache.syncope.common.lib.types.ResourceOperation; +import org.apache.syncope.core.persistence.api.dao.GroupDAO; +import org.apache.syncope.core.persistence.api.entity.task.PropagationTask; +import org.apache.syncope.core.provisioning.api.GroupProvisioningManager; +import org.apache.syncope.core.provisioning.api.WorkflowResult; +import org.apache.syncope.core.provisioning.api.propagation.PropagationException; +import org.apache.syncope.core.provisioning.api.propagation.PropagationManager; +import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter; +import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor; +import org.apache.syncope.core.misc.spring.ApplicationContextProvider; +import org.apache.syncope.core.provisioning.api.VirAttrHandler; +import org.apache.syncope.core.workflow.api.GroupWorkflowAdapter; + +public class DefaultGroupProvisioningManager implements GroupProvisioningManager { + + private static final Logger LOG = LoggerFactory.getLogger(GroupProvisioningManager.class); + + @Autowired + protected GroupWorkflowAdapter gwfAdapter; + + @Autowired + protected PropagationManager propagationManager; + + @Autowired + protected PropagationTaskExecutor taskExecutor; + + @Autowired + protected GroupDAO groupDAO; + + @Autowired + protected VirAttrHandler virtAttrHandler; + + @Override + public Pair<Long, List<PropagationStatus>> create(final GroupTO group) { + return create(group, Collections.<String>emptySet()); + } + + @Override + public Pair<Long, List<PropagationStatus>> create(final GroupTO groupTO, final Set<String> excludedResources) { + WorkflowResult<Long> created = gwfAdapter.create(groupTO); + + List<PropagationTask> tasks = propagationManager.getCreateTasks( + AnyTypeKind.GROUP, + created.getResult(), + created.getPropByRes(), + groupTO.getVirAttrs(), + excludedResources); + PropagationReporter propagationReporter = + ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propagationReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propagationReporter.onPrimaryResourceFailure(tasks); + } + + return new ImmutablePair<>(created.getResult(), propagationReporter.getStatuses()); + } + + @Override + public Pair<Long, List<PropagationStatus>> create( + final GroupTO groupTO, final Map<Long, String> groupOwnerMap, final Set<String> excludedResources) { + + WorkflowResult<Long> created = gwfAdapter.create(groupTO); + + // see ConnObjectUtils#getAnyTOFromConnObject for GroupOwnerSchema + AttrTO groupOwner = groupTO.getPlainAttrMap().get(StringUtils.EMPTY); + if (groupOwner != null) { + groupOwnerMap.put(created.getResult(), groupOwner.getValues().iterator().next()); + } + + List<PropagationTask> tasks = propagationManager.getCreateTasks( + AnyTypeKind.GROUP, + created.getResult(), + created.getPropByRes(), + groupTO.getVirAttrs(), + excludedResources); + PropagationReporter propagationReporter = + ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propagationReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propagationReporter.onPrimaryResourceFailure(tasks); + } + + return new ImmutablePair<>(created.getResult(), null); + } + + @Override + public Pair<Long, List<PropagationStatus>> update(final GroupPatch groupPatch) { + return update(groupPatch, Collections.<String>emptySet()); + } + + @Override + public Pair<Long, List<PropagationStatus>> update( + final GroupPatch groupPatch, final Set<String> excludedResources) { + + WorkflowResult<Long> updated = gwfAdapter.update(groupPatch); + ++ // SYNCOPE-459: take care of user virtual attributes ... ++ PropagationByResource propByResVirAttr = virtAttrHandler.updateVirtual( ++ updated.getResult(), ++ AnyTypeKind.GROUP, ++ groupPatch.getVirAttrs()); ++ if (updated.getPropByRes() == null) { ++ updated.setPropByRes(propByResVirAttr); ++ } else { ++ updated.getPropByRes().merge(propByResVirAttr); ++ } ++ + List<PropagationTask> tasks = propagationManager.getUpdateTasks( + AnyTypeKind.GROUP, + updated.getResult(), + false, + null, + updated.getPropByRes(), + groupPatch.getVirAttrs(), + excludedResources); - if (tasks.isEmpty()) { - // SYNCOPE-459: take care of user virtual attributes ... - PropagationByResource propByResVirAttr = virtAttrHandler.updateVirtual( - updated.getResult(), - AnyTypeKind.GROUP, - groupPatch.getVirAttrs()); - tasks.addAll(!propByResVirAttr.isEmpty() - ? propagationManager.getUpdateTasks( - AnyTypeKind.GROUP, - updated.getResult(), - false, - null, - updated.getPropByRes(), - groupPatch.getVirAttrs(), - excludedResources) - : Collections.<PropagationTask>emptyList()); - } + + PropagationReporter propagationReporter = + ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propagationReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propagationReporter.onPrimaryResourceFailure(tasks); + } + + return new ImmutablePair<>(updated.getResult(), propagationReporter.getStatuses()); + } + + @Override + public List<PropagationStatus> delete(final Long key) { + return delete(key, Collections.<String>emptySet()); + } + + @Override + public List<PropagationStatus> delete(final Long key, final Set<String> excludedResources) { + List<PropagationTask> tasks = new ArrayList<>(); + + // Generate propagation tasks for deleting users and any objects from group resources, + // if they are on those resources only because of the reason being deleted (see SYNCOPE-357) + for (Map.Entry<Long, PropagationByResource> entry + : groupDAO.findUsersWithTransitiveResources(key).entrySet()) { + + tasks.addAll(propagationManager.getDeleteTasks( + AnyTypeKind.USER, + entry.getKey(), + entry.getValue(), + excludedResources)); + } + for (Map.Entry<Long, PropagationByResource> entry + : groupDAO.findAnyObjectsWithTransitiveResources(key).entrySet()) { + + tasks.addAll(propagationManager.getDeleteTasks( + AnyTypeKind.ANY_OBJECT, + entry.getKey(), + entry.getValue(), + excludedResources)); + } + + // Generate propagation tasks for deleting this group from resources + tasks.addAll(propagationManager.getDeleteTasks( + AnyTypeKind.GROUP, + key, + null, + null)); + + PropagationReporter propagationReporter = + ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propagationReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propagationReporter.onPrimaryResourceFailure(tasks); + } + + gwfAdapter.delete(key); + + return propagationReporter.getStatuses(); + } + + @Override + public Long unlink(final GroupPatch groupPatch) { + WorkflowResult<Long> updated = gwfAdapter.update(groupPatch); + return updated.getResult(); + } + + @Override + public List<PropagationStatus> provision(final Long key, final Collection<String> resources) { + PropagationByResource propByRes = new PropagationByResource(); + propByRes.addAll(ResourceOperation.UPDATE, resources); + + List<PropagationTask> tasks = propagationManager.getUpdateTasks( + AnyTypeKind.GROUP, + key, + false, + null, + propByRes, + null, + null); + PropagationReporter propagationReporter = + ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propagationReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propagationReporter.onPrimaryResourceFailure(tasks); + } + return propagationReporter.getStatuses(); + } + + @Override + public List<PropagationStatus> deprovision(final Long key, final Collection<String> resources) { + PropagationByResource propByRes = new PropagationByResource(); + propByRes.addAll(ResourceOperation.DELETE, resources); + + List<PropagationTask> tasks = propagationManager.getDeleteTasks( + AnyTypeKind.GROUP, + key, + propByRes, + CollectionUtils.removeAll(groupDAO.authFind(key).getResourceNames(), resources)); + PropagationReporter propagationReporter = + ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propagationReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propagationReporter.onPrimaryResourceFailure(tasks); + } + return propagationReporter.getStatuses(); + } + + @Override + public Long link(final GroupPatch groupPatch) { + return gwfAdapter.update(groupPatch).getResult(); + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultUserProvisioningManager.java ---------------------------------------------------------------------- diff --cc core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultUserProvisioningManager.java index 4a302f2,0000000..a59337c mode 100644,000000..100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultUserProvisioningManager.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultUserProvisioningManager.java @@@ -1,413 -1,0 +1,409 @@@ +/* + * 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; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.Transformer; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.syncope.common.lib.patch.PasswordPatch; +import org.apache.syncope.common.lib.patch.StatusPatch; +import org.apache.syncope.common.lib.patch.StringPatchItem; +import org.apache.syncope.common.lib.patch.UserPatch; +import org.apache.syncope.common.lib.to.PropagationStatus; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.syncope.common.lib.types.PatchOperation; +import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.entity.task.PropagationTask; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.provisioning.api.UserProvisioningManager; +import org.apache.syncope.core.provisioning.api.WorkflowResult; +import org.apache.syncope.common.lib.types.PropagationByResource; +import org.apache.syncope.common.lib.types.ResourceOperation; +import org.apache.syncope.common.lib.types.StatusPatchType; +import org.apache.syncope.core.provisioning.api.propagation.PropagationException; +import org.apache.syncope.core.provisioning.api.propagation.PropagationManager; +import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter; +import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor; +import org.apache.syncope.core.provisioning.api.sync.ProvisioningResult; +import org.apache.syncope.core.misc.spring.ApplicationContextProvider; +import org.apache.syncope.core.provisioning.api.VirAttrHandler; +import org.apache.syncope.core.workflow.api.UserWorkflowAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +public class DefaultUserProvisioningManager implements UserProvisioningManager { + + private static final Logger LOG = LoggerFactory.getLogger(UserProvisioningManager.class); + + @Autowired + protected UserWorkflowAdapter uwfAdapter; + + @Autowired + protected PropagationManager propagationManager; + + @Autowired + protected PropagationTaskExecutor taskExecutor; + + @Autowired + protected VirAttrHandler virtAttrHandler; + + @Autowired + protected UserDAO userDAO; + + @Override + public Pair<Long, List<PropagationStatus>> create(final UserTO userTO) { + return create(userTO, true, false, null, Collections.<String>emptySet()); + } + + @Override + public Pair<Long, List<PropagationStatus>> create(final UserTO userTO, final boolean storePassword) { + return create(userTO, storePassword, false, null, Collections.<String>emptySet()); + } + + @Override + public Pair<Long, List<PropagationStatus>> create(final UserTO userTO, final Set<String> excludedResources) { + return create(userTO, false, false, null, excludedResources); + } + + @Override + public Pair<Long, List<PropagationStatus>> create(final UserTO userTO, final boolean storePassword, + final boolean disablePwdPolicyCheck, final Boolean enabled, final Set<String> excludedResources) { + + WorkflowResult<Pair<Long, Boolean>> created = + uwfAdapter.create(userTO, disablePwdPolicyCheck, enabled, storePassword); + + List<PropagationTask> tasks = propagationManager.getUserCreateTasks( + created.getResult().getKey(), + userTO.getPassword(), + created.getResult().getValue(), + created.getPropByRes(), + userTO.getVirAttrs(), + excludedResources); + PropagationReporter propagationReporter = + ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propagationReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propagationReporter.onPrimaryResourceFailure(tasks); + } + + return new ImmutablePair<>(created.getResult().getKey(), propagationReporter.getStatuses()); + } + + @Override + public Pair<Long, List<PropagationStatus>> update(final UserPatch userPatch) { + WorkflowResult<Pair<UserPatch, Boolean>> updated = uwfAdapter.update(userPatch); + - List<PropagationTask> tasks = propagationManager.getUserUpdateTasks(updated); - if (tasks.isEmpty()) { - // SYNCOPE-459: take care of user virtual attributes ... - PropagationByResource propByResVirAttr = virtAttrHandler.updateVirtual( - updated.getResult().getKey().getKey(), - AnyTypeKind.USER, - userPatch.getVirAttrs()); - if (!propByResVirAttr.isEmpty()) { - tasks.addAll(propagationManager.getUserUpdateTasks(updated, false, null)); - } ++ // SYNCOPE-459: take care of user virtual attributes ... ++ PropagationByResource propByResVirAttr = virtAttrHandler.updateVirtual( ++ updated.getResult().getKey().getKey(), ++ AnyTypeKind.USER, ++ userPatch.getVirAttrs()); ++ if (updated.getPropByRes() == null) { ++ updated.setPropByRes(propByResVirAttr); ++ } else { ++ updated.getPropByRes().merge(propByResVirAttr); + } ++ ++ List<PropagationTask> tasks = propagationManager.getUserUpdateTasks(updated); ++ + PropagationReporter propagationReporter = ApplicationContextProvider.getBeanFactory(). + getBean(PropagationReporter.class); + if (!tasks.isEmpty()) { + try { + taskExecutor.execute(tasks, propagationReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propagationReporter.onPrimaryResourceFailure(tasks); + } + } + + return new ImmutablePair<>(updated.getResult().getKey().getKey(), propagationReporter.getStatuses()); + } + + @Override + public Pair<Long, List<PropagationStatus>> update(final UserPatch userPatch, final Set<String> excludedResources) { + return update(userPatch, userPatch.getKey(), new ProvisioningResult(), null, excludedResources); + } + + @Override + public Pair<Long, List<PropagationStatus>> update(final UserPatch userPatch, final Long key, + final ProvisioningResult result, final Boolean enabled, final Set<String> excludedResources) { + + WorkflowResult<Pair<UserPatch, Boolean>> updated; + try { + updated = uwfAdapter.update(userPatch); + } catch (Exception e) { + LOG.error("Update of user {} failed, trying to sync its status anyway (if configured)", key, e); + + result.setStatus(ProvisioningResult.Status.FAILURE); + result.setMessage("Update failed, trying to sync status anyway (if configured)\n" + e.getMessage()); + + updated = new WorkflowResult<Pair<UserPatch, Boolean>>( + new ImmutablePair<>(userPatch, false), new PropagationByResource(), + new HashSet<String>()); + } + + if (enabled != null) { + User user = userDAO.find(key); + + WorkflowResult<Long> enableUpdate = null; + if (user.isSuspended() == null) { + enableUpdate = uwfAdapter.activate(key, null); + } else if (enabled && user.isSuspended()) { + enableUpdate = uwfAdapter.reactivate(key); + } else if (!enabled && !user.isSuspended()) { + enableUpdate = uwfAdapter.suspend(key); + } + + if (enableUpdate != null) { + if (enableUpdate.getPropByRes() != null) { + updated.getPropByRes().merge(enableUpdate.getPropByRes()); + updated.getPropByRes().purge(); + } + updated.getPerformedTasks().addAll(enableUpdate.getPerformedTasks()); + } + } + + List<PropagationTask> tasks = propagationManager.getUserUpdateTasks( + updated, updated.getResult().getKey().getPassword() != null, excludedResources); + PropagationReporter propagationReporter = ApplicationContextProvider.getBeanFactory(). + getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propagationReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propagationReporter.onPrimaryResourceFailure(tasks); + } + + return new ImmutablePair<>(updated.getResult().getKey().getKey(), propagationReporter.getStatuses()); + } + + @Override + public List<PropagationStatus> delete(final Long key) { + return delete(key, Collections.<String>emptySet()); + } + + @Override + public List<PropagationStatus> delete(final Long key, final Set<String> excludedResources) { + PropagationByResource propByRes = new PropagationByResource(); + propByRes.set(ResourceOperation.DELETE, userDAO.findAllResourceNames(userDAO.authFind(key))); + + // Note here that we can only notify about "delete", not any other + // task defined in workflow process definition: this because this + // information could only be available after uwfAdapter.delete(), which + // will also effectively remove user from db, thus making virtually + // impossible by NotificationManager to fetch required user information + List<PropagationTask> tasks = propagationManager.getDeleteTasks( + AnyTypeKind.USER, + key, + propByRes, + excludedResources); + PropagationReporter propagationReporter = + ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propagationReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propagationReporter.onPrimaryResourceFailure(tasks); + } + + try { + uwfAdapter.delete(key); + } catch (PropagationException e) { + throw e; + } + + return propagationReporter.getStatuses(); + } + + @Override + public Long unlink(final UserPatch userPatch) { + WorkflowResult<Pair<UserPatch, Boolean>> updated = uwfAdapter.update(userPatch); + return updated.getResult().getKey().getKey(); + } + + @Override + public Long link(final UserPatch userPatch) { + return uwfAdapter.update(userPatch).getResult().getKey().getKey(); + } + + @Override + public Pair<Long, List<PropagationStatus>> activate(final StatusPatch statusPatch) { + WorkflowResult<Long> updated = statusPatch.isOnSyncope() + ? uwfAdapter.activate(statusPatch.getKey(), statusPatch.getToken()) + : new WorkflowResult<>(statusPatch.getKey(), null, statusPatch.getType().name().toLowerCase()); + + return new ImmutablePair<>(updated.getResult(), propagateStatus(statusPatch)); + } + + @Override + public Pair<Long, List<PropagationStatus>> reactivate(final StatusPatch statusPatch) { + WorkflowResult<Long> updated = statusPatch.isOnSyncope() + ? uwfAdapter.reactivate(statusPatch.getKey()) + : new WorkflowResult<>(statusPatch.getKey(), null, statusPatch.getType().name().toLowerCase()); + + return new ImmutablePair<>(updated.getResult(), propagateStatus(statusPatch)); + } + + @Override + public Pair<Long, List<PropagationStatus>> suspend(final StatusPatch statusPatch) { + WorkflowResult<Long> updated = statusPatch.isOnSyncope() + ? uwfAdapter.suspend(statusPatch.getKey()) + : new WorkflowResult<>(statusPatch.getKey(), null, statusPatch.getType().name().toLowerCase()); + + return new ImmutablePair<>(updated.getResult(), propagateStatus(statusPatch)); + } + + protected List<PropagationStatus> propagateStatus(final StatusPatch statusPatch) { - Collection<String> noPropResourceNames = CollectionUtils.removeAll( - userDAO.findAllResourceNames(userDAO.find(statusPatch.getKey())), statusPatch.getResources()); - ++ PropagationByResource propByRes = new PropagationByResource(); ++ propByRes.addAll(ResourceOperation.UPDATE, statusPatch.getResources()); + List<PropagationTask> tasks = propagationManager.getUpdateTasks( + AnyTypeKind.USER, + statusPatch.getKey(), + false, + statusPatch.getType() != StatusPatchType.SUSPEND, ++ propByRes, + null, - null, - noPropResourceNames); ++ null); ++ + PropagationReporter propReporter = + ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propReporter.onPrimaryResourceFailure(tasks); + } + + return propReporter.getStatuses(); + + } + + @Override + public void internalSuspend(final Long key) { + Pair<WorkflowResult<Long>, Boolean> updated = uwfAdapter.internalSuspend(key); + + // propagate suspension if and only if it is required by policy + if (updated != null && updated.getValue()) { + UserPatch userPatch = new UserPatch(); + userPatch.setKey(updated.getKey().getResult()); + + List<PropagationTask> tasks = propagationManager.getUserUpdateTasks( + new WorkflowResult<Pair<UserPatch, Boolean>>( + new ImmutablePair<>(userPatch, Boolean.FALSE), + updated.getKey().getPropByRes(), updated.getKey().getPerformedTasks())); + taskExecutor.execute(tasks); + } + } + + @Override + public List<PropagationStatus> provision( + final Long key, final boolean changePwd, final String password, final Collection<String> resources) { + + UserPatch userPatch = new UserPatch(); + userPatch.setKey(key); + userPatch.getResources().addAll(CollectionUtils.collect(resources, + new Transformer<String, StringPatchItem>() { + + @Override + public StringPatchItem transform(final String input) { + return new StringPatchItem.Builder().operation(PatchOperation.ADD_REPLACE).value(input).build(); + } + }, new HashSet<StringPatchItem>())); + + if (changePwd) { + PasswordPatch passwordPatch = new PasswordPatch(); + passwordPatch.setOnSyncope(false); + passwordPatch.getResources().addAll(resources); + passwordPatch.setValue(password); + userPatch.setPassword(passwordPatch); + } + + PropagationByResource propByRes = new PropagationByResource(); + propByRes.addAll(ResourceOperation.UPDATE, resources); + + WorkflowResult<Pair<UserPatch, Boolean>> wfResult = new WorkflowResult<Pair<UserPatch, Boolean>>( + ImmutablePair.of(userPatch, (Boolean) null), propByRes, "update"); + + List<PropagationTask> tasks = propagationManager.getUserUpdateTasks(wfResult, changePwd, null); + PropagationReporter propagationReporter = + ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propagationReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propagationReporter.onPrimaryResourceFailure(tasks); + } + + return propagationReporter.getStatuses(); + } + + @Override + public List<PropagationStatus> deprovision(final Long key, final Collection<String> resources) { + PropagationByResource propByRes = new PropagationByResource(); + propByRes.set(ResourceOperation.DELETE, resources); + + List<PropagationTask> tasks = propagationManager.getDeleteTasks( + AnyTypeKind.USER, + key, + propByRes, + CollectionUtils.removeAll(userDAO.findAllResourceNames(userDAO.authFind(key)), resources)); + PropagationReporter propagationReporter = + ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propagationReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propagationReporter.onPrimaryResourceFailure(tasks); + } + + return propagationReporter.getStatuses(); + } + + @Override + public void requestPasswordReset(final Long key) { + uwfAdapter.requestPasswordReset(key); + } + + @Override + public void confirmPasswordReset(final Long key, final String token, final String password) { - uwfAdapter.confirmPasswordReset(key, token, password); ++ WorkflowResult<Pair<UserPatch, Boolean>> updated = uwfAdapter.confirmPasswordReset(key, token, password); + - UserPatch userPatch = new UserPatch(); - userPatch.setKey(key); - userPatch.setPassword(new PasswordPatch.Builder().value(password).build()); ++ List<PropagationTask> tasks = propagationManager.getUserUpdateTasks(updated); + - List<PropagationTask> tasks = propagationManager.getUserUpdateTasks( - new WorkflowResult<Pair<UserPatch, Boolean>>( - new ImmutablePair<UserPatch, Boolean>(userPatch, null), null, "confirmPasswordReset"), - true, null); + PropagationReporter propReporter = + ApplicationContextProvider.getBeanFactory().getBean(PropagationReporter.class); + try { + taskExecutor.execute(tasks, propReporter); + } catch (PropagationException e) { + LOG.error("Error propagation primary resource", e); + propReporter.onPrimaryResourceFailure(tasks); + } + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java ---------------------------------------------------------------------- diff --cc core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java index 82dfbc0,0000000..3420d27 mode 100644,000000..100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java @@@ -1,499 -1,0 +1,502 @@@ +/* + * 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.data; + +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.annotation.Resource; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.Predicate; +import org.apache.commons.collections4.Transformer; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.common.lib.SyncopeClientCompositeException; +import org.apache.syncope.common.lib.SyncopeClientException; +import org.apache.syncope.common.lib.patch.LongPatchItem; +import org.apache.syncope.common.lib.patch.MembershipPatch; +import org.apache.syncope.common.lib.patch.PasswordPatch; +import org.apache.syncope.common.lib.patch.RelationshipPatch; +import org.apache.syncope.common.lib.patch.UserPatch; +import org.apache.syncope.common.lib.to.MembershipTO; +import org.apache.syncope.common.lib.to.RelationshipTO; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.syncope.common.lib.types.CipherAlgorithm; +import org.apache.syncope.common.lib.types.ClientExceptionType; +import org.apache.syncope.common.lib.types.PatchOperation; +import org.apache.syncope.common.lib.types.ResourceOperation; +import org.apache.syncope.core.persistence.api.dao.ConfDAO; +import org.apache.syncope.core.persistence.api.dao.SecurityQuestionDAO; +import org.apache.syncope.core.persistence.api.entity.group.Group; +import org.apache.syncope.core.persistence.api.entity.user.SecurityQuestion; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.common.lib.types.PropagationByResource; +import org.apache.syncope.core.provisioning.api.data.UserDataBinder; +import org.apache.syncope.core.misc.security.AuthContextUtils; +import org.apache.syncope.core.misc.security.Encryptor; +import org.apache.syncope.core.misc.spring.BeanUtils; +import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO; +import org.apache.syncope.core.persistence.api.dao.RoleDAO; +import org.apache.syncope.core.persistence.api.entity.RelationshipType; +import org.apache.syncope.core.persistence.api.entity.Role; +import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; +import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource; +import org.apache.syncope.core.persistence.api.entity.resource.MappingItem; +import org.apache.syncope.core.persistence.api.entity.resource.Provision; +import org.apache.syncope.core.persistence.api.entity.user.UMembership; +import org.apache.syncope.core.persistence.api.entity.user.URelationship; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@Transactional(rollbackFor = { Throwable.class }) +public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDataBinder { + + private static final String[] IGNORE_PROPERTIES = { + "type", "realm", "auxClasses", "roles", "dynRoles", "relationships", "memberships", "dynGroups", + "plainAttrs", "derAttrs", "virAttrs", "resources", "securityQuestion", "securityAnswer" + }; + + @Autowired + private RoleDAO roleDAO; + + @Autowired + private ConfDAO confDAO; + + @Autowired + private SecurityQuestionDAO securityQuestionDAO; + + @Autowired + private AnyTypeDAO anyTypeDAO; + + @Resource(name = "adminUser") + private String adminUser; + + @Resource(name = "anonymousUser") + private String anonymousUser; + + private final Encryptor encryptor = Encryptor.getInstance(); + + @Transactional(readOnly = true) + @Override + public UserTO getAuthenticatedUserTO() { + final UserTO authUserTO; + + String authUsername = AuthContextUtils.getUsername(); + if (anonymousUser.equals(authUsername)) { + authUserTO = new UserTO(); + authUserTO.setKey(-2); + authUserTO.setUsername(anonymousUser); + } else if (adminUser.equals(authUsername)) { + authUserTO = new UserTO(); + authUserTO.setKey(-1); + authUserTO.setUsername(adminUser); + } else { + User authUser = userDAO.find(authUsername); + authUserTO = getUserTO(authUser, true); + } + + return authUserTO; + } + + @Transactional(readOnly = true) + @Override + public boolean verifyPassword(final String username, final String password) { + return verifyPassword(userDAO.authFind(username), password); + } + + @Transactional(readOnly = true) + @Override + public boolean verifyPassword(final User user, final String password) { + return encryptor.verify(password, user.getCipherAlgorithm(), user.getPassword()); + } + + private void setPassword(final User user, final String password, final SyncopeClientCompositeException scce) { + try { + String algorithm = confDAO.find( + "password.cipher.algorithm", CipherAlgorithm.AES.name()).getValues().get(0).getStringValue(); + CipherAlgorithm predefined = CipherAlgorithm.valueOf(algorithm); + user.setPassword(password, predefined); + } catch (IllegalArgumentException e) { + final SyncopeClientException invalidCiperAlgorithm = + SyncopeClientException.build(ClientExceptionType.NotFound); + invalidCiperAlgorithm.getElements().add(e.getMessage()); + scce.addException(invalidCiperAlgorithm); + + throw scce; + } + } + + @Override + public void create(final User user, final UserTO userTO, final boolean storePassword) { + SyncopeClientCompositeException scce = SyncopeClientException.buildComposite(); + + // roles + for (Long roleKey : userTO.getRoles()) { + Role role = roleDAO.find(roleKey); + if (role == null) { + LOG.warn("Ignoring unknown role with id {}", roleKey); + } else { + user.add(role); + } + } + + // relationships + for (RelationshipTO relationshipTO : userTO.getRelationships()) { + AnyObject anyObject = anyObjectDAO.find(relationshipTO.getRightKey()); + if (anyObject == null) { + LOG.debug("Ignoring invalid anyObject " + relationshipTO.getRightKey()); + } else { + RelationshipType relationshipType = relationshipTypeDAO.find(relationshipTO.getType()); + URelationship relationship = null; + if (user.getKey() != null) { + relationship = user.getRelationship(relationshipType, anyObject.getKey()); + } + if (relationship == null) { + relationship = entityFactory.newEntity(URelationship.class); + relationship.setType(relationshipType); + relationship.setRightEnd(anyObject); + relationship.setLeftEnd(user); + + user.add(relationship); + } + } + } + + // memberships + for (MembershipTO membershipTO : userTO.getMemberships()) { + Group group = groupDAO.find(membershipTO.getRightKey()); + + if (group == null) { + LOG.debug("Ignoring invalid group " + membershipTO.getGroupName()); + } else { + UMembership membership = null; + if (user.getKey() != null) { + membership = user.getMembership(group.getKey()); + } + if (membership == null) { + membership = entityFactory.newEntity(UMembership.class); + membership.setRightEnd(group); + membership.setLeftEnd(user); + + user.add(membership); + } + } + } + + // realm, attributes, derived attributes, virtual attributes and resources + fill(user, userTO, anyUtilsFactory.getInstance(AnyTypeKind.USER), scce); + + // set password + if (StringUtils.isBlank(userTO.getPassword()) || !storePassword) { + LOG.debug("Password was not provided or not required to be stored"); + } else { + setPassword(user, userTO.getPassword(), scce); + } + + // set username + user.setUsername(userTO.getUsername()); + + // security question / answer + if (userTO.getSecurityQuestion() != null) { + SecurityQuestion securityQuestion = securityQuestionDAO.find(userTO.getSecurityQuestion()); + if (securityQuestion != null) { + user.setSecurityQuestion(securityQuestion); + } + } + user.setSecurityAnswer(userTO.getSecurityAnswer()); + + user.setMustChangePassword(userTO.isMustChangePassword()); + } + + private boolean isPasswordMapped(final ExternalResource resource) { + boolean result = false; + + Provision provision = resource.getProvision(anyTypeDAO.findUser()); + if (provision != null && provision.getMapping() != null) { + result = CollectionUtils.exists(provision.getMapping().getItems(), new Predicate<MappingItem>() { + + @Override + public boolean evaluate(final MappingItem item) { + return item.isPassword(); + } + }); + } + + return result; + } + + @Override + public PropagationByResource update(final User toBeUpdated, final UserPatch userPatch) { + // Re-merge any pending change from workflow tasks + final User user = userDAO.save(toBeUpdated); + + PropagationByResource propByRes = new PropagationByResource(); + + SyncopeClientCompositeException scce = SyncopeClientException.buildComposite(); + + Collection<String> currentResources = userDAO.findAllResourceNames(user); + + // fetch connObjectKeys before update + Map<String, String> oldConnObjectKeys = getConnObjectKeys(user); + + // realm + setRealm(user, userPatch); + + // password + if (userPatch.getPassword() != null && StringUtils.isNotBlank(userPatch.getPassword().getValue())) { - setPassword(user, userPatch.getPassword().getValue(), scce); - user.setChangePwdDate(new Date()); - propByRes.addAll(ResourceOperation.UPDATE, currentResources); ++ if (userPatch.getPassword().isOnSyncope()) { ++ setPassword(user, userPatch.getPassword().getValue(), scce); ++ user.setChangePwdDate(new Date()); ++ } ++ ++ propByRes.addAll(ResourceOperation.UPDATE, userPatch.getPassword().getResources()); + } + + // username + if (userPatch.getUsername() != null && StringUtils.isNotBlank(userPatch.getUsername().getValue())) { - propByRes.addAll(ResourceOperation.UPDATE, currentResources); - + String oldUsername = user.getUsername(); + user.setUsername(userPatch.getUsername().getValue()); + + if (oldUsername.equals(AuthContextUtils.getUsername())) { + AuthContextUtils.updateUsername(userPatch.getUsername().getValue()); + } ++ ++ propByRes.addAll(ResourceOperation.UPDATE, currentResources); + } + + // security question / answer: + if (userPatch.getSecurityQuestion() != null) { + if (userPatch.getSecurityQuestion().getValue() == null) { + user.setSecurityQuestion(null); + user.setSecurityAnswer(null); + } else { + SecurityQuestion securityQuestion = + securityQuestionDAO.find(userPatch.getSecurityQuestion().getValue()); + if (securityQuestion != null) { + user.setSecurityQuestion(securityQuestion); + user.setSecurityAnswer(userPatch.getSecurityAnswer().getValue()); + } + } + } + + if (userPatch.getMustChangePassword() != null) { + user.setMustChangePassword(userPatch.getMustChangePassword().getValue()); + } + + // roles + for (LongPatchItem patch : userPatch.getRoles()) { + Role role = roleDAO.find(patch.getValue()); + if (role == null) { + LOG.warn("Ignoring unknown role with key {}", patch.getValue()); + } else { + switch (patch.getOperation()) { + case ADD_REPLACE: + user.add(role); + break; + + case DELETE: + default: + user.remove(role); + } + } + } + + // attributes, derived attributes, virtual attributes and resources + propByRes.merge(fill(user, userPatch, anyUtilsFactory.getInstance(AnyTypeKind.USER), scce)); + + Set<String> toBeDeprovisioned = new HashSet<>(); + Set<String> toBeProvisioned = new HashSet<>(); + + // relationships + for (RelationshipPatch patch : userPatch.getRelationships()) { + if (patch.getRelationshipTO() != null) { + RelationshipType relationshipType = relationshipTypeDAO.find(patch.getRelationshipTO().getType()); + URelationship relationship = + user.getRelationship(relationshipType, patch.getRelationshipTO().getRightKey()); + if (relationship != null) { + user.remove(relationship); + toBeDeprovisioned.addAll(relationship.getRightEnd().getResourceNames()); + } + + if (patch.getOperation() == PatchOperation.ADD_REPLACE) { + AnyObject otherEnd = anyObjectDAO.find(patch.getRelationshipTO().getRightKey()); + if (otherEnd == null) { + LOG.debug("Ignoring invalid any object {}", patch.getRelationshipTO().getRightKey()); + } else { + + relationship = entityFactory.newEntity(URelationship.class); + relationship.setType(relationshipType); + relationship.setRightEnd(otherEnd); + relationship.setLeftEnd(user); + + user.add(relationship); + + toBeProvisioned.addAll(otherEnd.getResourceNames()); + } + } + } + } + + // memberships + for (MembershipPatch patch : userPatch.getMemberships()) { + if (patch.getMembershipTO() != null) { + UMembership membership = user.getMembership(patch.getMembershipTO().getRightKey()); + if (membership != null) { + user.remove(membership); + toBeDeprovisioned.addAll(membership.getRightEnd().getResourceNames()); + } + + if (patch.getOperation() == PatchOperation.ADD_REPLACE) { + Group group = groupDAO.find(patch.getMembershipTO().getRightKey()); + if (group == null) { + LOG.debug("Ignoring invalid group {}", patch.getMembershipTO().getRightKey()); + } else { + membership = entityFactory.newEntity(UMembership.class); + membership.setRightEnd(group); + membership.setLeftEnd(user); + + user.add(membership); + + toBeProvisioned.addAll(group.getResourceNames()); + + // SYNCOPE-686: if password is invertible and we are adding resources with password mapping, + // ensure that they are counted for password propagation + if (toBeUpdated.canDecodePassword()) { + if (userPatch.getPassword() == null) { + userPatch.setPassword(new PasswordPatch()); + } + for (ExternalResource resource : group.getResources()) { + if (isPasswordMapped(resource)) { + userPatch.getPassword().getResources().add(resource.getKey()); + } + } + } + } + } + } + } + + propByRes.addAll(ResourceOperation.DELETE, toBeDeprovisioned); + propByRes.addAll(ResourceOperation.UPDATE, toBeProvisioned); + + // In case of new memberships all current resources need to be updated in order to propagate new group + // attribute values. + if (!toBeDeprovisioned.isEmpty() || !toBeProvisioned.isEmpty()) { + currentResources.removeAll(toBeDeprovisioned); + propByRes.addAll(ResourceOperation.UPDATE, currentResources); + } + + // check if some connObjectKey was changed by the update above + Map<String, String> newcCnnObjectKeys = getConnObjectKeys(user); + for (Map.Entry<String, String> entry : oldConnObjectKeys.entrySet()) { + if (newcCnnObjectKeys.containsKey(entry.getKey()) + && !entry.getValue().equals(newcCnnObjectKeys.get(entry.getKey()))) { + + propByRes.addOldConnObjectKey(entry.getKey(), entry.getValue()); + propByRes.add(ResourceOperation.UPDATE, entry.getKey()); + } + } + + return propByRes; + } + + @Transactional(readOnly = true) + @Override + public UserTO getUserTO(final User user, final boolean details) { + UserTO userTO = new UserTO(); + + BeanUtils.copyProperties(user, userTO, IGNORE_PROPERTIES); + + if (user.getSecurityQuestion() != null) { + userTO.setSecurityQuestion(user.getSecurityQuestion().getKey()); + } + + if (details) { + virAttrHander.retrieveVirAttrValues(user); + } + + fillTO(userTO, user.getRealm().getFullPath(), user.getAuxClasses(), + user.getPlainAttrs(), user.getDerAttrs(), user.getVirAttrs(), userDAO.findAllResources(user)); + + if (details) { + // roles + CollectionUtils.collect(user.getRoles(), new Transformer<Role, Long>() { + + @Override + public Long transform(final Role role) { + return role.getKey(); + } + }, userTO.getRoles()); + + // relationships + CollectionUtils.collect(user.getRelationships(), new Transformer<URelationship, RelationshipTO>() { + + @Override + public RelationshipTO transform(final URelationship relationship) { + return UserDataBinderImpl.this.getRelationshipTO(relationship); + } + + }, userTO.getRelationships()); + + // memberships + CollectionUtils.collect(user.getMemberships(), new Transformer<UMembership, MembershipTO>() { + + @Override + public MembershipTO transform(final UMembership membership) { + return UserDataBinderImpl.this.getMembershipTO(membership); + } + }, userTO.getMemberships()); + + // dynamic memberships + CollectionUtils.collect(userDAO.findDynRoleMemberships(user), new Transformer<Role, Long>() { + + @Override + public Long transform(final Role role) { + return role.getKey(); + } + }, userTO.getDynRoles()); + CollectionUtils.collect(userDAO.findDynGroupMemberships(user), new Transformer<Group, Long>() { + + @Override + public Long transform(final Group group) { + return group.getKey(); + } + }, userTO.getDynGroups()); + } + + return userTO; + } + + @Transactional(readOnly = true) + @Override + public UserTO getUserTO(final String username) { + return getUserTO(userDAO.authFind(username), true); + } + + @Transactional(readOnly = true) + @Override + public UserTO getUserTO(final Long key) { + return getUserTO(userDAO.authFind(key), true); + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/2011671c/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PropagationManagerImpl.java ---------------------------------------------------------------------- diff --cc core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PropagationManagerImpl.java index ebf9c99,0000000..0d15ffd mode 100644,000000..100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PropagationManagerImpl.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PropagationManagerImpl.java @@@ -1,426 -1,0 +1,420 @@@ +/* + * 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.propagation; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.Transformer; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.syncope.common.lib.patch.AttrPatch; +import org.apache.syncope.common.lib.patch.StringPatchItem; +import org.apache.syncope.common.lib.patch.UserPatch; +import org.apache.syncope.common.lib.to.AttrTO; +import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.syncope.common.lib.types.PropagationByResource; +import org.apache.syncope.common.lib.types.ResourceOperation; +import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO; +import org.apache.syncope.core.persistence.api.dao.GroupDAO; +import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.entity.EntityFactory; +import org.apache.syncope.core.persistence.api.entity.VirAttr; +import org.apache.syncope.core.persistence.api.entity.task.PropagationTask; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.provisioning.api.WorkflowResult; +import org.apache.syncope.core.provisioning.api.propagation.PropagationManager; +import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor; +import org.apache.syncope.core.misc.ConnObjectUtils; +import org.apache.syncope.core.misc.MappingUtils; +import org.apache.syncope.core.misc.jexl.JexlUtils; +import org.apache.syncope.core.persistence.api.dao.AnyDAO; +import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO; +import org.apache.syncope.core.persistence.api.entity.Any; +import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource; +import org.apache.syncope.core.persistence.api.entity.resource.MappingItem; +import org.apache.syncope.core.persistence.api.entity.resource.Provision; +import org.apache.syncope.core.provisioning.api.VirAttrHandler; +import org.identityconnectors.framework.common.objects.Attribute; +import org.identityconnectors.framework.common.objects.AttributeBuilder; +import org.identityconnectors.framework.common.objects.AttributeUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +/** + * Manage the data propagation to external resources. + */ +@Transactional(rollbackFor = { Throwable.class }) +public class PropagationManagerImpl implements PropagationManager { + + protected static final Logger LOG = LoggerFactory.getLogger(PropagationManager.class); + + @Autowired + protected AnyObjectDAO anyObjectDAO; + + /** + * User DAO. + */ + @Autowired + protected UserDAO userDAO; + + /** + * Group DAO. + */ + @Autowired + protected GroupDAO groupDAO; + + /** + * Resource DAO. + */ + @Autowired + protected ExternalResourceDAO resourceDAO; + + @Autowired + protected EntityFactory entityFactory; + + /** + * ConnObjectUtils. + */ + @Autowired + protected ConnObjectUtils connObjectUtils; + + @Autowired + protected VirAttrHandler virAttrHandler; + + @Autowired + protected MappingUtils mappingUtils; + + protected Any<?, ?, ?> find(final AnyTypeKind kind, final Long key) { + AnyDAO<? extends Any<?, ?, ?>> dao; + switch (kind) { + case ANY_OBJECT: + dao = anyObjectDAO; + break; + + case GROUP: + dao = groupDAO; + break; + + case USER: + default: + dao = userDAO; + } + + return dao.authFind(key); + } + + @Override + public List<PropagationTask> getCreateTasks( + final AnyTypeKind kind, + final Long key, + final PropagationByResource propByRes, + final Collection<AttrTO> vAttrs, + final Collection<String> noPropResourceNames) { + + Any<?, ?, ?> any = find(kind, key); + if (vAttrs != null && !vAttrs.isEmpty()) { + virAttrHandler.createVirtual(any, vAttrs); + } + + return getCreateTasks(any, null, null, propByRes, noPropResourceNames); + } + + @Override + public List<PropagationTask> getUserCreateTasks( + final Long key, + final String password, + final Boolean enable, + final PropagationByResource propByRes, + final Collection<AttrTO> vAttrs, + final Collection<String> noPropResourceNames) { + + User user = userDAO.authFind(key); + if (vAttrs != null && !vAttrs.isEmpty()) { + virAttrHandler.createVirtual(user, vAttrs); + } + + return getCreateTasks(user, password, enable, propByRes, noPropResourceNames); + } + + protected List<PropagationTask> getCreateTasks( + final Any<?, ?, ?> any, + final String password, + final Boolean enable, + final PropagationByResource propByRes, + final Collection<String> noPropResourceNames) { + + if (propByRes == null || propByRes.isEmpty()) { + return Collections.<PropagationTask>emptyList(); + } + + if (noPropResourceNames != null) { + propByRes.get(ResourceOperation.CREATE).removeAll(noPropResourceNames); + } + + return createTasks(any, password, true, null, enable, false, propByRes); + } + + @Override + public List<PropagationTask> getUpdateTasks( + final AnyTypeKind kind, + final Long key, + final boolean changePwd, + final Boolean enable, + final PropagationByResource propByRes, + final Collection<AttrPatch> vAttrs, + final Collection<String> noPropResourceNames) { + + return getUpdateTasks(find(kind, key), null, changePwd, enable, propByRes, vAttrs, noPropResourceNames); + } + + @Override + public List<PropagationTask> getUserUpdateTasks( + final WorkflowResult<Pair<UserPatch, Boolean>> wfResult, + final boolean changePwd, + final Collection<String> noPropResourceNames) { + + return getUpdateTasks( + userDAO.authFind(wfResult.getResult().getKey().getKey()), + wfResult.getResult().getKey().getPassword() == null + ? null + : wfResult.getResult().getKey().getPassword().getValue(), + changePwd, + wfResult.getResult().getValue(), + wfResult.getPropByRes(), + wfResult.getResult().getKey().getVirAttrs(), + noPropResourceNames); + } + + @Override + public List<PropagationTask> getUserUpdateTasks(final WorkflowResult<Pair<UserPatch, Boolean>> wfResult) { + UserPatch userPatch = wfResult.getResult().getKey(); + + // Propagate password update only to requested resources + List<PropagationTask> tasks = new ArrayList<>(); + if (userPatch.getPassword() == null) { + // a. no specific password propagation request: generate propagation tasks for any resource associated + tasks = getUserUpdateTasks(wfResult, false, null); + } else { + // b. generate the propagation task list in two phases: first the ones containing password, + // the the rest (with no password) + PropagationByResource origPropByRes = new PropagationByResource(); + origPropByRes.merge(wfResult.getPropByRes()); + + Set<String> pwdResourceNames = new HashSet<>(userPatch.getPassword().getResources()); + Collection<String> currentResourceNames = + userDAO.findAllResourceNames(userDAO.authFind(userPatch.getKey())); + pwdResourceNames.retainAll(currentResourceNames); + PropagationByResource pwdPropByRes = new PropagationByResource(); + pwdPropByRes.addAll(ResourceOperation.UPDATE, pwdResourceNames); + if (!pwdPropByRes.isEmpty()) { + Set<String> toBeExcluded = new HashSet<>(currentResourceNames); + toBeExcluded.addAll(CollectionUtils.collect(userPatch.getResources(), + new Transformer<StringPatchItem, String>() { + + @Override + public String transform(final StringPatchItem input) { + return input.getValue(); + } + })); + toBeExcluded.removeAll(pwdResourceNames); + tasks.addAll(getUserUpdateTasks(wfResult, true, toBeExcluded)); + } + + PropagationByResource nonPwdPropByRes = new PropagationByResource(); + nonPwdPropByRes.merge(origPropByRes); + nonPwdPropByRes.removeAll(pwdResourceNames); + nonPwdPropByRes.purge(); + if (!nonPwdPropByRes.isEmpty()) { + tasks.addAll(getUserUpdateTasks(wfResult, false, pwdResourceNames)); + } + } + + return tasks; + } + + protected List<PropagationTask> getUpdateTasks( + final Any<?, ?, ?> any, + final String password, + final boolean changePwd, + final Boolean enable, + final PropagationByResource propByRes, + final Collection<AttrPatch> vAttrs, + final Collection<String> noPropResourceNames) { + + PropagationByResource localPropByRes = virAttrHandler.updateVirtual( + any, + vAttrs == null ? Collections.<AttrPatch>emptySet() : vAttrs); - - if (propByRes == null || propByRes.isEmpty()) { - localPropByRes.addAll(ResourceOperation.UPDATE, any.getResourceNames()); - } else { - localPropByRes.merge(propByRes); - } - ++ localPropByRes.merge(propByRes); + if (noPropResourceNames != null) { + localPropByRes.removeAll(noPropResourceNames); + } + + Map<String, AttrPatch> vAttrsMap = null; + if (vAttrs != null) { + vAttrsMap = new HashMap<>(); + for (AttrPatch attrPatch : vAttrs) { + vAttrsMap.put(attrPatch.getAttrTO().getSchema(), attrPatch); + } + } + + return createTasks(any, password, changePwd, vAttrsMap, enable, false, localPropByRes); + } + + @Override + public List<PropagationTask> getDeleteTasks( + final AnyTypeKind kind, + final Long key, + final PropagationByResource propByRes, + final Collection<String> noPropResourceNames) { + + Any<?, ?, ?> any = find(kind, key); + + PropagationByResource localPropByRes = new PropagationByResource(); + + if (propByRes == null || propByRes.isEmpty()) { + localPropByRes.addAll(ResourceOperation.DELETE, any.getResourceNames()); + } else { + localPropByRes.merge(propByRes); + } + + if (noPropResourceNames != null) { + localPropByRes.removeAll(noPropResourceNames); + } + + return getDeleteTasks(any, localPropByRes, noPropResourceNames); + } + + protected List<PropagationTask> getDeleteTasks( + final Any<?, ?, ?> any, + final PropagationByResource propByRes, + final Collection<String> noPropResourceNames) { + + return createTasks(any, null, false, null, false, true, propByRes); + } + + /** + * Create propagation tasks. + * + * @param any user / group to be provisioned + * @param password cleartext password to be provisioned + * @param changePwd whether password should be included for propagation attributes or not + * @param vAttrs virtual attributes to be maaged + * @param enable whether user must be enabled or not + * @param deleteOnResource whether user / group must be deleted anyway from external resource or not + * @param propByRes operation to be performed per resource + * @return list of propagation tasks created + */ + protected List<PropagationTask> createTasks(final Any<?, ?, ?> any, + final String password, final boolean changePwd, + final Map<String, AttrPatch> vAttrs, + final Boolean enable, final boolean deleteOnResource, final PropagationByResource propByRes) { + + LOG.debug("Provisioning any {}:\n{}", any, propByRes); + + if (!propByRes.get(ResourceOperation.CREATE).isEmpty() && vAttrs != null) { + virAttrHandler.retrieveVirAttrValues(any); + + // update vAttrsToBeUpdated as well + for (VirAttr<?> virAttr : any.getVirAttrs()) { + String schema = virAttr.getSchema().getKey(); + + vAttrs.put(schema, new AttrPatch.Builder(). + attrTO(new AttrTO.Builder().schema(schema).values(virAttr.getValues()).build()). + build()); + } + } + + // Avoid duplicates - see javadoc + propByRes.purge(); + LOG.debug("After purge: {}", propByRes); + + List<PropagationTask> tasks = new ArrayList<>(); + + for (ResourceOperation operation : ResourceOperation.values()) { + for (String resourceName : propByRes.get(operation)) { + ExternalResource resource = resourceDAO.find(resourceName); + Provision provision = resource == null ? null : resource.getProvision(any.getType()); + if (resource == null) { + LOG.error("Invalid resource name specified: {}, ignoring...", resourceName); + } else if (provision == null) { + LOG.error("No provision specified on resource {} for type {}, ignoring...", + resource, any.getType()); + } else if (MappingUtils.getPropagationMappingItems(provision).isEmpty()) { + LOG.warn("Requesting propagation for {} but no propagation mapping provided for {}", + any.getType(), resource); + } else { + PropagationTask task = entityFactory.newEntity(PropagationTask.class); + task.setResource(resource); + task.setObjectClassName( + resource.getProvision(any.getType()).getObjectClass().getObjectClassValue()); + task.setAnyTypeKind(any.getType().getKind()); + if (!deleteOnResource) { + task.setAnyKey(any.getKey()); + } + task.setOperation(operation); + task.setOldConnObjectKey(propByRes.getOldConnObjectKey(resource.getKey())); + + Pair<String, Set<Attribute>> preparedAttrs = mappingUtils.prepareAttrs( + any, password, changePwd, vAttrs, enable, provision); + task.setConnObjectKey(preparedAttrs.getKey()); + + // Check if any of mandatory attributes (in the mapping) is missing or not received any value: + // if so, add special attributes that will be evaluated by PropagationTaskExecutor + List<String> mandatoryMissing = new ArrayList<>(); + List<String> mandatoryNullOrEmpty = new ArrayList<>(); + for (MappingItem item : MappingUtils.getPropagationMappingItems(provision)) { + if (!item.isConnObjectKey() + && JexlUtils.evaluateMandatoryCondition(item.getMandatoryCondition(), any)) { + + Attribute attr = AttributeUtil.find(item.getExtAttrName(), preparedAttrs.getValue()); + if (attr == null) { + mandatoryMissing.add(item.getExtAttrName()); + } else if (attr.getValue() == null || attr.getValue().isEmpty()) { + mandatoryNullOrEmpty.add(item.getExtAttrName()); + } + } + } + if (!mandatoryMissing.isEmpty()) { + preparedAttrs.getValue().add(AttributeBuilder.build( + PropagationTaskExecutor.MANDATORY_MISSING_ATTR_NAME, mandatoryMissing)); + } + if (!mandatoryNullOrEmpty.isEmpty()) { + preparedAttrs.getValue().add(AttributeBuilder.build( + PropagationTaskExecutor.MANDATORY_NULL_OR_EMPTY_ATTR_NAME, mandatoryNullOrEmpty)); + } + + task.setAttributes(preparedAttrs.getValue()); + tasks.add(task); + + LOG.debug("PropagationTask created: {}", task); + } + } + } + + return tasks; + } +}
