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/08 14:17:28 UTC
[08/13] syncope git commit: [SYNCOPE-620] server logic in,
tests missing
http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UserLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UserLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UserLogic.java
new file mode 100644
index 0000000..870a075
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UserLogic.java
@@ -0,0 +1,534 @@
+/*
+ * 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.logic;
+
+import org.apache.syncope.server.security.UnauthorizedRoleException;
+import java.lang.reflect.Method;
+import java.security.AccessControlException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.mod.AttrMod;
+import org.apache.syncope.common.lib.mod.StatusMod;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.BulkAction;
+import org.apache.syncope.common.lib.to.BulkActionResult;
+import org.apache.syncope.common.lib.to.BulkActionResult.Status;
+import org.apache.syncope.common.lib.to.MembershipTO;
+import org.apache.syncope.common.lib.to.PropagationStatus;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.SubjectType;
+import org.apache.syncope.persistence.api.RoleEntitlementUtil;
+import org.apache.syncope.persistence.api.dao.ConfDAO;
+import org.apache.syncope.persistence.api.dao.NotFoundException;
+import org.apache.syncope.persistence.api.dao.RoleDAO;
+import org.apache.syncope.persistence.api.dao.SubjectSearchDAO;
+import org.apache.syncope.persistence.api.dao.UserDAO;
+import org.apache.syncope.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.persistence.api.entity.role.Role;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.apache.syncope.provisioning.api.AttributableTransformer;
+import org.apache.syncope.provisioning.api.UserProvisioningManager;
+import org.apache.syncope.provisioning.api.propagation.PropagationManager;
+import org.apache.syncope.provisioning.api.propagation.PropagationTaskExecutor;
+import org.apache.syncope.server.logic.data.UserDataBinder;
+import org.apache.syncope.server.security.AuthContextUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.interceptor.TransactionInterceptor;
+
+/**
+ * Note that this controller does not extend {@link AbstractTransactionalLogic}, hence does not provide any
+ * Spring's Transactional logic at class level.
+ */
+@Component
+public class UserLogic extends AbstractSubjectLogic<UserTO, UserMod> {
+
+ @Autowired
+ protected UserDAO userDAO;
+
+ @Autowired
+ protected RoleDAO roleDAO;
+
+ @Autowired
+ protected SubjectSearchDAO searchDAO;
+
+ @Autowired
+ protected ConfDAO confDAO;
+
+ @Autowired
+ protected UserDataBinder binder;
+
+ @Autowired
+ protected PropagationManager propagationManager;
+
+ @Autowired
+ protected PropagationTaskExecutor taskExecutor;
+
+ @Autowired
+ protected AttributableTransformer attrTransformer;
+
+ @Autowired
+ protected UserProvisioningManager provisioningManager;
+
+ @Transactional(readOnly = true)
+ public boolean isSelfRegAllowed() {
+ return confDAO.find("selfRegistration.allowed", "false").getValues().get(0).getBooleanValue();
+ }
+
+ @Transactional(readOnly = true)
+ public boolean isPwdResetAllowed() {
+ return confDAO.find("passwordReset.allowed", "false").getValues().get(0).getBooleanValue();
+ }
+
+ @Transactional(readOnly = true)
+ public boolean isPwdResetRequiringSecurityQuestions() {
+ return confDAO.find("passwordReset.securityQuestion", "true").getValues().get(0).getBooleanValue();
+ }
+
+ @PreAuthorize("hasRole('USER_READ')")
+ public String getUsername(final Long key) {
+ return binder.getUserTO(key).getUsername();
+ }
+
+ @PreAuthorize("hasRole('USER_READ')")
+ public Long getKey(final String username) {
+ return binder.getUserTO(username).getKey();
+ }
+
+ @PreAuthorize("hasRole('USER_LIST')")
+ @Transactional(readOnly = true, rollbackFor = { Throwable.class })
+ @Override
+ public int count() {
+ return userDAO.count(RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames()));
+ }
+
+ @PreAuthorize("hasRole('USER_LIST')")
+ @Transactional(readOnly = true, rollbackFor = { Throwable.class })
+ @Override
+ public int searchCount(final SearchCond searchCondition) {
+ return searchDAO.count(RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames()),
+ searchCondition, SubjectType.USER);
+ }
+
+ @PreAuthorize("hasRole('USER_LIST')")
+ @Transactional(readOnly = true, rollbackFor = { Throwable.class })
+ @Override
+ public List<UserTO> list(final int page, final int size, final List<OrderByClause> orderBy) {
+ Set<Long> adminRoleIds = RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames());
+
+ List<User> users = userDAO.findAll(adminRoleIds, page, size, orderBy);
+ List<UserTO> userTOs = new ArrayList<UserTO>(users.size());
+ for (User user : users) {
+ userTOs.add(binder.getUserTO(user));
+ }
+
+ return userTOs;
+ }
+
+ @PreAuthorize("isAuthenticated() "
+ + "and not(hasRole(T(org.apache.syncope.common.lib.SyncopeConstants).ANONYMOUS_ENTITLEMENT))")
+ @Transactional(readOnly = true)
+ public UserTO readSelf() {
+ return binder.getAuthenticatedUserTO();
+ }
+
+ @PreAuthorize("hasRole('USER_READ')")
+ @Transactional(readOnly = true)
+ @Override
+ public UserTO read(final Long key) {
+ return binder.getUserTO(key);
+ }
+
+ @PreAuthorize("hasRole('USER_LIST')")
+ @Transactional(readOnly = true)
+ @Override
+ public List<UserTO> search(final SearchCond searchCondition, final int page, final int size,
+ final List<OrderByClause> orderBy) {
+
+ final List<User> matchingUsers = searchDAO.search(
+ RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames()),
+ searchCondition, page, size, orderBy, SubjectType.USER);
+
+ final List<UserTO> result = new ArrayList<>(matchingUsers.size());
+ for (User user : matchingUsers) {
+ result.add(binder.getUserTO(user));
+ }
+
+ return result;
+ }
+
+ @PreAuthorize("isAnonymous() or hasRole(T(org.apache.syncope.common.lib.SyncopeConstants).ANONYMOUS_ENTITLEMENT)")
+ public UserTO createSelf(final UserTO userTO, final boolean storePassword) {
+ return doCreate(userTO, storePassword);
+ }
+
+ @PreAuthorize("hasRole('USER_CREATE')")
+ public UserTO create(final UserTO userTO, final boolean storePassword) {
+ Set<Long> requestRoleIds = new HashSet<Long>(userTO.getMemberships().size());
+ for (MembershipTO membership : userTO.getMemberships()) {
+ requestRoleIds.add(membership.getRoleId());
+ }
+ Set<Long> adminRoleIds = RoleEntitlementUtil.getRoleKeys(AuthContextUtil.getOwnedEntitlementNames());
+ requestRoleIds.removeAll(adminRoleIds);
+ if (!requestRoleIds.isEmpty()) {
+ throw new UnauthorizedRoleException(requestRoleIds);
+ }
+
+ return doCreate(userTO, storePassword);
+ }
+
+ protected UserTO doCreate(final UserTO userTO, final boolean storePassword) {
+ // Attributable transformation (if configured)
+ UserTO actual = attrTransformer.transform(userTO);
+ LOG.debug("Transformed: {}", actual);
+
+ Map.Entry<Long, List<PropagationStatus>> created = provisioningManager.create(actual, storePassword);
+
+ final UserTO savedTO = binder.getUserTO(created.getKey());
+ savedTO.getPropagationStatusTOs().addAll(created.getValue());
+ return savedTO;
+ }
+
+ @PreAuthorize("isAuthenticated() "
+ + "and not(hasRole(T(org.apache.syncope.common.lib.SyncopeConstants).ANONYMOUS_ENTITLEMENT))")
+ public UserTO updateSelf(final UserMod userMod) {
+ UserTO userTO = binder.getAuthenticatedUserTO();
+
+ if (userTO.getKey() != userMod.getKey()) {
+ throw new AccessControlException("Not allowed for user with key " + userMod.getKey());
+ }
+
+ return update(userMod);
+ }
+
+ @PreAuthorize("hasRole('USER_UPDATE')")
+ @Override
+ public UserTO update(final UserMod userMod) {
+ // AttributableMod transformation (if configured)
+ UserMod actual = attrTransformer.transform(userMod);
+ LOG.debug("Transformed: {}", actual);
+
+ // SYNCOPE-501: check if there are memberships to be removed with virtual attributes assigned
+ boolean removeMemberships = false;
+ for (Long membershipId : actual.getMembershipsToRemove()) {
+ if (!binder.fillMembershipVirtual(
+ null,
+ null,
+ membershipId,
+ Collections.<String>emptySet(),
+ Collections.<AttrMod>emptySet(),
+ true).isEmpty()) {
+
+ removeMemberships = true;
+ }
+ }
+
+ Map.Entry<Long, List<PropagationStatus>> updated = provisioningManager.update(actual, removeMemberships);
+
+ final UserTO updatedTO = binder.getUserTO(updated.getKey());
+ updatedTO.getPropagationStatusTOs().addAll(updated.getValue());
+ return updatedTO;
+ }
+
+ protected Map.Entry<Long, List<PropagationStatus>> setStatusOnWfAdapter(final User user,
+ final StatusMod statusMod) {
+ Map.Entry<Long, List<PropagationStatus>> updated;
+
+ switch (statusMod.getType()) {
+ case SUSPEND:
+ updated = provisioningManager.suspend(user, statusMod);
+ break;
+
+ case REACTIVATE:
+ updated = provisioningManager.reactivate(user, statusMod);
+ break;
+
+ case ACTIVATE:
+ default:
+ updated = provisioningManager.activate(user, statusMod);
+ break;
+
+ }
+
+ return updated;
+ }
+
+ @PreAuthorize("hasRole('USER_UPDATE')")
+ @Transactional(rollbackFor = { Throwable.class })
+ public UserTO status(final StatusMod statusMod) {
+ User user = userDAO.authFecthUser(statusMod.getId());
+
+ Map.Entry<Long, List<PropagationStatus>> updated = setStatusOnWfAdapter(user, statusMod);
+ final UserTO savedTO = binder.getUserTO(updated.getKey());
+ savedTO.getPropagationStatusTOs().addAll(updated.getValue());
+ return savedTO;
+ }
+
+ @PreAuthorize("isAnonymous() or hasRole(T(org.apache.syncope.common.lib.SyncopeConstants).ANONYMOUS_ENTITLEMENT)")
+ @Transactional
+ public void requestPasswordReset(final String username, final String securityAnswer) {
+ if (username == null) {
+ throw new NotFoundException("Null username");
+ }
+
+ User user = userDAO.find(username);
+ if (user == null) {
+ throw new NotFoundException("User " + username);
+ }
+
+ if (isPwdResetRequiringSecurityQuestions()
+ && (securityAnswer == null || !securityAnswer.equals(user.getSecurityAnswer()))) {
+
+ throw SyncopeClientException.build(ClientExceptionType.InvalidSecurityAnswer);
+ }
+
+ provisioningManager.requestPasswordReset(user.getKey());
+ }
+
+ @PreAuthorize("isAnonymous() or hasRole(T(org.apache.syncope.common.lib.SyncopeConstants).ANONYMOUS_ENTITLEMENT)")
+ @Transactional
+ public void confirmPasswordReset(final String token, final String password) {
+ User user = userDAO.findByToken(token);
+ if (user == null) {
+ throw new NotFoundException("User with token " + token);
+ }
+ provisioningManager.confirmPasswordReset(user, token, password);
+ }
+
+ @PreAuthorize("isAuthenticated() "
+ + "and not(hasRole(T(org.apache.syncope.common.lib.SyncopeConstants).ANONYMOUS_ENTITLEMENT))")
+ public UserTO deleteSelf() {
+ UserTO userTO = binder.getAuthenticatedUserTO();
+
+ return delete(userTO.getKey());
+ }
+
+ @PreAuthorize("hasRole('USER_DELETE')")
+ @Override
+ public UserTO delete(final Long key) {
+ List<Role> ownedRoles = roleDAO.findOwnedByUser(key);
+ if (!ownedRoles.isEmpty()) {
+ List<String> owned = new ArrayList<String>(ownedRoles.size());
+ for (Role role : ownedRoles) {
+ owned.add(role.getKey() + " " + role.getName());
+ }
+
+ SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.RoleOwnership);
+ sce.getElements().addAll(owned);
+ throw sce;
+ }
+
+ List<PropagationStatus> statuses = provisioningManager.delete(key);
+
+ final UserTO deletedTO;
+ User deleted = userDAO.find(key);
+ if (deleted == null) {
+ deletedTO = new UserTO();
+ deletedTO.setKey(key);
+ } else {
+ deletedTO = binder.getUserTO(key);
+ }
+ deletedTO.getPropagationStatusTOs().addAll(statuses);
+
+ return deletedTO;
+ }
+
+ @PreAuthorize("(hasRole('USER_DELETE') and #bulkAction.operation == #bulkAction.operation.DELETE) or "
+ + "(hasRole('USER_UPDATE') and "
+ + "(#bulkAction.operation == #bulkAction.operation.REACTIVATE or "
+ + "#bulkAction.operation == #bulkAction.operation.SUSPEND))")
+ public BulkActionResult bulk(final BulkAction bulkAction) {
+ BulkActionResult res = new BulkActionResult();
+
+ switch (bulkAction.getOperation()) {
+ case DELETE:
+ for (String key : bulkAction.getTargets()) {
+ try {
+ res.add(delete(Long.valueOf(key)).getKey(), Status.SUCCESS);
+ } catch (Exception e) {
+ LOG.error("Error performing delete for user {}", key, e);
+ res.add(key, Status.FAILURE);
+ }
+ }
+ break;
+
+ case SUSPEND:
+ for (String key : bulkAction.getTargets()) {
+ StatusMod statusMod = new StatusMod();
+ statusMod.setId(Long.valueOf(key));
+ statusMod.setType(StatusMod.ModType.SUSPEND);
+ try {
+ res.add(status(statusMod).getKey(), Status.SUCCESS);
+ } catch (Exception e) {
+ LOG.error("Error performing suspend for user {}", key, e);
+ res.add(key, Status.FAILURE);
+ }
+ }
+ break;
+
+ case REACTIVATE:
+ for (String key : bulkAction.getTargets()) {
+ StatusMod statusMod = new StatusMod();
+ statusMod.setId(Long.valueOf(key));
+ statusMod.setType(StatusMod.ModType.REACTIVATE);
+ try {
+ res.add(status(statusMod).getKey(), Status.SUCCESS);
+ } catch (Exception e) {
+ LOG.error("Error performing reactivate for user {}", key, e);
+ res.add(key, Status.FAILURE);
+ }
+ }
+ break;
+
+ default:
+ }
+
+ return res;
+ }
+
+ @PreAuthorize("hasRole('USER_UPDATE')")
+ @Transactional(rollbackFor = { Throwable.class })
+ @Override
+ public UserTO unlink(final Long key, final Collection<String> resources) {
+ final UserMod userMod = new UserMod();
+ userMod.setKey(key);
+ userMod.getResourcesToRemove().addAll(resources);
+ Long updatedId = provisioningManager.unlink(userMod);
+
+ return binder.getUserTO(updatedId);
+ }
+
+ @PreAuthorize("hasRole('USER_UPDATE')")
+ @Transactional(rollbackFor = { Throwable.class })
+ @Override
+ public UserTO link(final Long key, final Collection<String> resources) {
+ final UserMod userMod = new UserMod();
+ userMod.setKey(key);
+ userMod.getResourcesToAdd().addAll(resources);
+ return binder.getUserTO(provisioningManager.link(userMod));
+ }
+
+ @PreAuthorize("hasRole('USER_UPDATE')")
+ @Transactional(rollbackFor = { Throwable.class })
+ @Override
+ public UserTO unassign(final Long key, final Collection<String> resources) {
+ final UserMod userMod = new UserMod();
+ userMod.setKey(key);
+ userMod.getResourcesToRemove().addAll(resources);
+ return update(userMod);
+ }
+
+ @PreAuthorize("hasRole('USER_UPDATE')")
+ @Transactional(rollbackFor = { Throwable.class })
+ @Override
+ public UserTO assign(
+ final Long key,
+ final Collection<String> resources,
+ final boolean changepwd,
+ final String password) {
+
+ final UserMod userMod = new UserMod();
+ userMod.setKey(key);
+ userMod.getResourcesToAdd().addAll(resources);
+
+ if (changepwd) {
+ StatusMod statusMod = new StatusMod();
+ statusMod.setOnSyncope(false);
+ statusMod.getResourceNames().addAll(resources);
+ userMod.setPwdPropRequest(statusMod);
+ userMod.setPassword(password);
+ }
+
+ return update(userMod);
+ }
+
+ @PreAuthorize("hasRole('USER_UPDATE')")
+ @Transactional(rollbackFor = { Throwable.class })
+ @Override
+ public UserTO deprovision(final Long key, final Collection<String> resources) {
+ final User user = userDAO.authFecthUser(key);
+
+ List<PropagationStatus> statuses = provisioningManager.deprovision(key, resources);
+
+ final UserTO updatedUserTO = binder.getUserTO(user);
+ updatedUserTO.getPropagationStatusTOs().addAll(statuses);
+ return updatedUserTO;
+ }
+
+ @PreAuthorize("hasRole('USER_UPDATE')")
+ @Transactional(readOnly = true)
+ @Override
+ public UserTO provision(
+ final Long key,
+ final Collection<String> resources,
+ final boolean changePwd,
+ final String password) {
+
+ final UserTO original = binder.getUserTO(key);
+
+ //trick: assign and retrieve propagation statuses ...
+ original.getPropagationStatusTOs().addAll(
+ assign(key, resources, changePwd, password).getPropagationStatusTOs());
+
+ // .... rollback.
+ TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
+ return original;
+ }
+
+ @Override
+ protected UserTO resolveReference(final Method method, final Object... args) throws UnresolvedReferenceException {
+ Object id = null;
+
+ if (!"confirmPasswordReset".equals(method.getName()) && ArrayUtils.isNotEmpty(args)) {
+ for (int i = 0; id == null && i < args.length; i++) {
+ if (args[i] instanceof Long) {
+ id = (Long) args[i];
+ } else if (args[i] instanceof String) {
+ id = (String) args[i];
+ } else if (args[i] instanceof UserTO) {
+ id = ((UserTO) args[i]).getKey();
+ } else if (args[i] instanceof UserMod) {
+ id = ((UserMod) args[i]).getKey();
+ }
+ }
+ }
+
+ if ((id != null) && !id.equals(0l)) {
+ try {
+ return id instanceof Long ? binder.getUserTO((Long) id) : binder.getUserTO((String) id);
+ } catch (Throwable ignore) {
+ LOG.debug("Unresolved reference", ignore);
+ throw new UnresolvedReferenceException(ignore);
+ }
+ }
+
+ throw new UnresolvedReferenceException();
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UserWorkflowLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UserWorkflowLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UserWorkflowLogic.java
new file mode 100644
index 0000000..08b5e71
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/UserWorkflowLogic.java
@@ -0,0 +1,131 @@
+/*
+ * 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.logic;
+
+import java.lang.reflect.Method;
+import java.util.AbstractMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.syncope.common.lib.mod.AbstractAttributableMod;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.to.WorkflowFormTO;
+import org.apache.syncope.persistence.api.dao.UserDAO;
+import org.apache.syncope.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.persistence.api.entity.user.User;
+import org.apache.syncope.provisioning.api.propagation.PropagationManager;
+import org.apache.syncope.provisioning.api.propagation.PropagationTaskExecutor;
+import org.apache.syncope.server.logic.data.UserDataBinder;
+import org.apache.syncope.server.workflow.api.UserWorkflowAdapter;
+import org.apache.syncope.provisioning.api.WorkflowResult;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class UserWorkflowLogic extends AbstractTransactionalLogic<WorkflowFormTO> {
+
+ @Autowired
+ private UserWorkflowAdapter uwfAdapter;
+
+ @Autowired
+ private PropagationManager propagationManager;
+
+ @Autowired
+ private PropagationTaskExecutor taskExecutor;
+
+ @Autowired
+ private UserDataBinder binder;
+
+ @Autowired
+ private UserDAO userDAO;
+
+ @PreAuthorize("hasRole('WORKFLOW_FORM_CLAIM')")
+ @Transactional(rollbackFor = { Throwable.class })
+ public WorkflowFormTO claimForm(final String taskId) {
+ return uwfAdapter.claimForm(taskId);
+ }
+
+ @PreAuthorize("hasRole('USER_UPDATE')")
+ public UserTO executeWorkflowTask(final UserTO userTO, final String taskId) {
+ WorkflowResult<Long> updated = uwfAdapter.execute(userTO, taskId);
+
+ UserMod userMod = new UserMod();
+ userMod.setKey(userTO.getKey());
+
+ List<PropagationTask> tasks = propagationManager.getUserUpdateTaskIds(
+ new WorkflowResult<Map.Entry<UserMod, Boolean>>(
+ new AbstractMap.SimpleEntry<UserMod, Boolean>(userMod, null),
+ updated.getPropByRes(), updated.getPerformedTasks()));
+
+ taskExecutor.execute(tasks);
+
+ return binder.getUserTO(updated.getResult());
+ }
+
+ @PreAuthorize("hasRole('WORKFLOW_FORM_READ') and hasRole('USER_READ')")
+ @Transactional(rollbackFor = { Throwable.class })
+ public WorkflowFormTO getFormForUser(final Long key) {
+ User user = userDAO.authFecthUser(key);
+ return uwfAdapter.getForm(user.getWorkflowId());
+ }
+
+ @PreAuthorize("hasRole('WORKFLOW_FORM_LIST')")
+ @Transactional(rollbackFor = { Throwable.class })
+ public List<WorkflowFormTO> getForms() {
+ return uwfAdapter.getForms();
+ }
+
+ @PreAuthorize("hasRole('WORKFLOW_FORM_READ') and hasRole('USER_READ')")
+ @Transactional(rollbackFor = { Throwable.class })
+ public List<WorkflowFormTO> getForms(final Long key, final String formName) {
+ User user = userDAO.authFecthUser(key);
+ return uwfAdapter.getForms(user.getWorkflowId(), formName);
+ }
+
+ @PreAuthorize("hasRole('WORKFLOW_FORM_SUBMIT')")
+ @Transactional(rollbackFor = { Throwable.class })
+ public UserTO submitForm(final WorkflowFormTO form) {
+ WorkflowResult<? extends AbstractAttributableMod> updated = uwfAdapter.submitForm(form);
+
+ // propByRes can be made empty by the workflow definition if no propagation should occur
+ // (for example, with rejected users)
+ if (updated.getResult() instanceof UserMod
+ && updated.getPropByRes() != null && !updated.getPropByRes().isEmpty()) {
+
+ List<PropagationTask> tasks = propagationManager.getUserUpdateTaskIds(
+ new WorkflowResult<Map.Entry<UserMod, Boolean>>(
+ new AbstractMap.SimpleEntry<UserMod, Boolean>((UserMod) updated.getResult(), Boolean.TRUE),
+ updated.getPropByRes(),
+ updated.getPerformedTasks()));
+
+ taskExecutor.execute(tasks);
+ }
+
+ return binder.getUserTO(updated.getResult().getKey());
+ }
+
+ @Override
+ protected WorkflowFormTO resolveReference(final Method method, final Object... args)
+ throws UnresolvedReferenceException {
+
+ throw new UnresolvedReferenceException();
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/WorkflowLogic.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/WorkflowLogic.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/WorkflowLogic.java
new file mode 100644
index 0000000..2e5ae74
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/WorkflowLogic.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.logic;
+
+import java.io.OutputStream;
+import java.lang.reflect.Method;
+import javax.ws.rs.core.MediaType;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.apache.syncope.server.workflow.api.RoleWorkflowAdapter;
+import org.apache.syncope.server.workflow.api.UserWorkflowAdapter;
+import org.apache.syncope.server.workflow.api.WorkflowAdapter;
+import org.apache.syncope.server.workflow.api.WorkflowDefinitionFormat;
+import org.apache.syncope.server.workflow.api.WorkflowException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class WorkflowLogic extends AbstractTransactionalLogic<AbstractBaseBean> {
+
+ @Autowired
+ private UserWorkflowAdapter uwfAdapter;
+
+ @Autowired
+ private RoleWorkflowAdapter rwfAdapter;
+
+ private void exportDefinition(
+ final WorkflowAdapter adapter, final WorkflowDefinitionFormat format, final OutputStream os)
+ throws WorkflowException {
+
+ adapter.exportDefinition(format, os);
+ }
+
+ private WorkflowDefinitionFormat getFormat(final MediaType format) {
+ return format.equals(MediaType.APPLICATION_JSON_TYPE)
+ ? WorkflowDefinitionFormat.JSON
+ : WorkflowDefinitionFormat.XML;
+ }
+
+ @PreAuthorize("hasRole('WORKFLOW_DEF_READ')")
+ @Transactional(readOnly = true)
+ public void exportUserDefinition(final MediaType format, final OutputStream os)
+ throws WorkflowException {
+
+ exportDefinition(uwfAdapter, getFormat(format), os);
+ }
+
+ @PreAuthorize("hasRole('WORKFLOW_DEF_READ')")
+ @Transactional(readOnly = true)
+ public void exportRoleDefinition(final MediaType format, final OutputStream os)
+ throws WorkflowException {
+
+ exportDefinition(rwfAdapter, getFormat(format), os);
+ }
+
+ private void exportDiagram(final WorkflowAdapter adapter, final OutputStream os)
+ throws WorkflowException {
+ adapter.exportDiagram(os);
+ }
+
+ @PreAuthorize("hasRole('WORKFLOW_DEF_READ')")
+ @Transactional(readOnly = true)
+ public void exportUserDiagram(final OutputStream os)
+ throws WorkflowException {
+
+ exportDiagram(uwfAdapter, os);
+ }
+
+ @PreAuthorize("hasRole('WORKFLOW_DEF_READ')")
+ @Transactional(readOnly = true)
+ public void exportRoleDiagram(final OutputStream os)
+ throws WorkflowException {
+
+ exportDiagram(rwfAdapter, os);
+ }
+
+ private void importDefinition(
+ final WorkflowAdapter adapter, final WorkflowDefinitionFormat format, final String definition) {
+ adapter.importDefinition(format, definition);
+ }
+
+ @PreAuthorize("hasRole('WORKFLOW_DEF_UPDATE')")
+ public void importUserDefinition(final MediaType format, final String definition) {
+ importDefinition(uwfAdapter, getFormat(format), definition);
+ }
+
+ @PreAuthorize("hasRole('WORKFLOW_DEF_UPDATE')")
+ public void importRoleDefinition(final MediaType format, final String definition) {
+ importDefinition(rwfAdapter, getFormat(format), definition);
+ }
+
+ @Override
+ protected AbstractBaseBean resolveReference(final Method method, final Object... args)
+ throws UnresolvedReferenceException {
+
+ throw new UnresolvedReferenceException();
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/audit/AuditConnectionFactory.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/audit/AuditConnectionFactory.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/audit/AuditConnectionFactory.java
new file mode 100644
index 0000000..dae9fbf
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/audit/AuditConnectionFactory.java
@@ -0,0 +1,159 @@
+/*
+ * 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.logic.audit;
+
+import java.io.InputStream;
+import java.sql.Connection;
+import java.util.Properties;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.rmi.PortableRemoteObject;
+import javax.sql.DataSource;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathFactory;
+import org.apache.commons.dbcp2.BasicDataSource;
+import org.apache.commons.io.IOUtils;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PropertiesLoaderUtils;
+import org.springframework.jdbc.datasource.DataSourceUtils;
+import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
+import org.springframework.jdbc.datasource.init.ScriptUtils;
+import org.w3c.dom.Document;
+import org.w3c.dom.bootstrap.DOMImplementationRegistry;
+import org.w3c.dom.ls.DOMImplementationLS;
+import org.w3c.dom.ls.LSInput;
+import org.w3c.dom.ls.LSParser;
+
+/**
+ * LOG4J SQL connection factory that first attempts to obtain a {@link javax.sql.DataSource} from the JNDI name
+ * configured in Spring or, when not found, builds a new {@link javax.sql.DataSource DataSource} via Commons DBCP; if
+ * any datasource if found, the SQL init script is used to populate the database.
+ */
+public final class AuditConnectionFactory {
+
+ private static DataSource datasource;
+
+ private static final String PERSISTENCE_CONTEXT = "/persistenceContext.xml";
+
+ static {
+ // 1. Attempts to lookup for configured JNDI datasource (if present and available)
+ InputStream springConf = AuditConnectionFactory.class.getResourceAsStream(PERSISTENCE_CONTEXT);
+ String primary = null;
+ String fallback = null;
+ try {
+ DOMImplementationRegistry reg = DOMImplementationRegistry.newInstance();
+ DOMImplementationLS impl = (DOMImplementationLS) reg.getDOMImplementation("LS");
+ LSParser parser = impl.createLSParser(DOMImplementationLS.MODE_SYNCHRONOUS, null);
+ LSInput lsinput = impl.createLSInput();
+ lsinput.setByteStream(springConf);
+ Document source = parser.parse(lsinput);
+
+ XPathFactory xPathfactory = XPathFactory.newInstance();
+ XPath xpath = xPathfactory.newXPath();
+
+ XPathExpression expr = xpath.compile("//*[local-name()='bean' and @id='persistenceProperties']/"
+ + "child::*[local-name()='property' and @name='primary']/@value");
+ primary = (String) expr.evaluate(source, XPathConstants.STRING);
+ expr = xpath.compile("//*[local-name()='bean' and @id='persistenceProperties']/"
+ + "child::*[local-name()='property' and @name='fallback']/@value");
+ fallback = (String) expr.evaluate(source, XPathConstants.STRING);
+
+ expr = xpath.compile("//*[local-name()='property' and @name='jndiName']/@value");
+ String jndiName = (String) expr.evaluate(source, XPathConstants.STRING);
+
+ Context ctx = new InitialContext();
+ Object obj = ctx.lookup(jndiName);
+
+ datasource = (DataSource) PortableRemoteObject.narrow(obj, DataSource.class);
+ } catch (Exception e) {
+ // ignore
+ } finally {
+ IOUtils.closeQuietly(springConf);
+ }
+
+ // 2. Creates Commons DBCP datasource
+ String initSQLScript = null;
+ try {
+ Resource persistenceProperties = null;
+ if (primary != null) {
+ if (primary.startsWith("file:")) {
+ persistenceProperties = new FileSystemResource(primary.substring(5));
+ }
+ if (primary.startsWith("classpath:")) {
+ persistenceProperties = new ClassPathResource(primary.substring(10));
+ }
+ }
+ if ((persistenceProperties == null || !persistenceProperties.exists()) && fallback != null) {
+ if (fallback.startsWith("file:")) {
+ persistenceProperties = new FileSystemResource(fallback.substring(5));
+ }
+ if (fallback.startsWith("classpath:")) {
+ persistenceProperties = new ClassPathResource(fallback.substring(10));
+ }
+ }
+ Properties persistence = PropertiesLoaderUtils.loadProperties(persistenceProperties);
+
+ initSQLScript = persistence.getProperty("audit.sql");
+
+ if (datasource == null) {
+ BasicDataSource bds = new BasicDataSource();
+ bds.setDriverClassName(persistence.getProperty("jpa.driverClassName"));
+ bds.setUrl(persistence.getProperty("jpa.url"));
+ bds.setUsername(persistence.getProperty("jpa.username"));
+ bds.setPassword(persistence.getProperty("jpa.password"));
+
+ bds.setLogAbandoned(true);
+ bds.setRemoveAbandonedOnBorrow(true);
+ bds.setRemoveAbandonedOnMaintenance(true);
+
+ datasource = bds;
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException("Audit datasource configuration failed", e);
+ }
+
+ // 3. Initializes the chosen datasource
+ ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
+ populator.setScripts(new Resource[] { new ClassPathResource("/audit/" + initSQLScript) });
+ // forces no statement separation
+ populator.setSeparator(ScriptUtils.EOF_STATEMENT_SEPARATOR);
+ Connection conn = DataSourceUtils.getConnection(datasource);
+ try {
+ populator.populate(conn);
+ } finally {
+ DataSourceUtils.releaseConnection(conn, datasource);
+ }
+ }
+
+ public static Connection getConnection() {
+ if (datasource != null) {
+ return DataSourceUtils.getConnection(datasource);
+ }
+
+ throw new IllegalStateException("Audit dataSource init failed: check logs");
+ }
+
+ private AuditConnectionFactory() {
+ // empty constructor for static utility class
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/audit/AuditManager.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/audit/AuditManager.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/audit/AuditManager.java
new file mode 100644
index 0000000..ed674fd
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/audit/AuditManager.java
@@ -0,0 +1,107 @@
+/*
+ * 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.logic.audit;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditElements.Result;
+import org.apache.syncope.common.lib.types.AuditLoggerName;
+import org.apache.syncope.common.lib.types.LoggerLevel;
+import org.apache.syncope.persistence.api.dao.LoggerDAO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+public class AuditManager {
+
+ /**
+ * Logger.
+ */
+ private static final Logger LOG = LoggerFactory.getLogger(AuditManager.class);
+
+ @Autowired
+ private LoggerDAO loggerDAO;
+
+ public void audit(
+ final AuditElements.EventCategoryType type,
+ final String category,
+ final String subcategory,
+ final String event,
+ final Result result,
+ final Object before,
+ final Object output,
+ final Object... input) {
+
+ final Throwable throwable;
+ final StringBuilder message = new StringBuilder();
+
+ message.append("BEFORE:\n");
+ message.append("\t").append(before == null ? "unknown" : before).append("\n");
+
+ message.append("INPUT:\n");
+
+ if (ArrayUtils.isNotEmpty(input)) {
+ for (Object obj : input) {
+ message.append("\t").append(obj == null ? null : obj.toString()).append("\n");
+ }
+ } else {
+ message.append("\t").append("none").append("\n");
+ }
+
+ message.append("OUTPUT:\n");
+
+ if (output instanceof Throwable) {
+ throwable = (Throwable) output;
+ message.append("\t").append(throwable.getMessage());
+ } else {
+ throwable = null;
+ message.append("\t").append(output == null ? "none" : output.toString());
+ }
+
+ AuditLoggerName auditLoggerName = null;
+ try {
+ auditLoggerName = new AuditLoggerName(type, category, subcategory, event, result);
+ } catch (IllegalArgumentException e) {
+ LOG.error("Invalid audit parameters, aborting...", e);
+ }
+
+ if (auditLoggerName != null) {
+ org.apache.syncope.persistence.api.entity.Logger syncopeLogger =
+ loggerDAO.find(auditLoggerName.toLoggerName());
+ if (syncopeLogger != null && syncopeLogger.getLevel() == LoggerLevel.DEBUG) {
+ StringBuilder auditMessage = new StringBuilder();
+
+ final SecurityContext ctx = SecurityContextHolder.getContext();
+ if (ctx != null && ctx.getAuthentication() != null) {
+ auditMessage.append('[').append(ctx.getAuthentication().getName()).append(']').append(' ');
+ }
+ auditMessage.append(message);
+
+ final Logger logger = LoggerFactory.getLogger(auditLoggerName.toLoggerName());
+ if (throwable == null) {
+ logger.debug(auditMessage.toString());
+ } else {
+ logger.debug(auditMessage.toString(), throwable);
+ }
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/syncope/blob/99369c31/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/AbstractAttributableDataBinder.java
----------------------------------------------------------------------
diff --git a/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/AbstractAttributableDataBinder.java b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/AbstractAttributableDataBinder.java
new file mode 100644
index 0000000..d84c62f
--- /dev/null
+++ b/syncope620/server/logic/src/main/java/org/apache/syncope/server/logic/data/AbstractAttributableDataBinder.java
@@ -0,0 +1,918 @@
+/*
+ * 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.logic.data;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+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.mod.AbstractAttributableMod;
+import org.apache.syncope.common.lib.mod.AbstractSubjectMod;
+import org.apache.syncope.common.lib.mod.AttrMod;
+import org.apache.syncope.common.lib.to.AbstractAttributableTO;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.IntMappingType;
+import org.apache.syncope.common.lib.types.MappingPurpose;
+import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
+import org.apache.syncope.persistence.api.dao.DerAttrDAO;
+import org.apache.syncope.persistence.api.dao.DerSchemaDAO;
+import org.apache.syncope.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.persistence.api.dao.MembershipDAO;
+import org.apache.syncope.persistence.api.dao.PlainAttrDAO;
+import org.apache.syncope.persistence.api.dao.PlainAttrValueDAO;
+import org.apache.syncope.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.persistence.api.dao.PolicyDAO;
+import org.apache.syncope.persistence.api.dao.RoleDAO;
+import org.apache.syncope.persistence.api.dao.UserDAO;
+import org.apache.syncope.persistence.api.dao.VirAttrDAO;
+import org.apache.syncope.persistence.api.dao.VirSchemaDAO;
+import org.apache.syncope.persistence.api.entity.Attributable;
+import org.apache.syncope.persistence.api.entity.AttributableUtil;
+import org.apache.syncope.persistence.api.entity.AttributableUtilFactory;
+import org.apache.syncope.persistence.api.entity.DerAttr;
+import org.apache.syncope.persistence.api.entity.DerSchema;
+import org.apache.syncope.persistence.api.entity.EntityFactory;
+import org.apache.syncope.persistence.api.entity.ExternalResource;
+import org.apache.syncope.persistence.api.entity.MappingItem;
+import org.apache.syncope.persistence.api.entity.PlainAttr;
+import org.apache.syncope.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.persistence.api.entity.PlainSchema;
+import org.apache.syncope.persistence.api.entity.Schema;
+import org.apache.syncope.persistence.api.entity.Subject;
+import org.apache.syncope.persistence.api.entity.VirAttr;
+import org.apache.syncope.persistence.api.entity.VirSchema;
+import org.apache.syncope.persistence.api.entity.membership.MDerAttr;
+import org.apache.syncope.persistence.api.entity.membership.MDerAttrTemplate;
+import org.apache.syncope.persistence.api.entity.membership.MPlainAttr;
+import org.apache.syncope.persistence.api.entity.membership.MPlainAttrTemplate;
+import org.apache.syncope.persistence.api.entity.membership.MVirAttr;
+import org.apache.syncope.persistence.api.entity.membership.MVirAttrTemplate;
+import org.apache.syncope.persistence.api.entity.membership.Membership;
+import org.apache.syncope.persistence.api.entity.role.RDerAttr;
+import org.apache.syncope.persistence.api.entity.role.RDerAttrTemplate;
+import org.apache.syncope.persistence.api.entity.role.RPlainAttr;
+import org.apache.syncope.persistence.api.entity.role.RPlainAttrTemplate;
+import org.apache.syncope.persistence.api.entity.role.RVirAttr;
+import org.apache.syncope.persistence.api.entity.role.RVirAttrTemplate;
+import org.apache.syncope.persistence.api.entity.role.Role;
+import org.apache.syncope.persistence.api.entity.user.UDerAttr;
+import org.apache.syncope.persistence.api.entity.user.UDerSchema;
+import org.apache.syncope.persistence.api.entity.user.UPlainAttr;
+import org.apache.syncope.persistence.api.entity.user.UPlainSchema;
+import org.apache.syncope.persistence.api.entity.user.UVirAttr;
+import org.apache.syncope.persistence.api.entity.user.UVirSchema;
+import org.apache.syncope.provisioning.api.propagation.PropagationByResource;
+import org.apache.syncope.server.utils.MappingUtil;
+import org.apache.syncope.server.utils.jexl.JexlUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public abstract class AbstractAttributableDataBinder {
+
+ /**
+ * Logger.
+ */
+ protected static final Logger LOG = LoggerFactory.getLogger(AbstractAttributableDataBinder.class);
+
+ @Autowired
+ protected RoleDAO roleDAO;
+
+ @Autowired
+ protected PlainSchemaDAO plainSchemaDAO;
+
+ @Autowired
+ protected DerSchemaDAO derSchemaDAO;
+
+ @Autowired
+ protected VirSchemaDAO virSchemaDAO;
+
+ @Autowired
+ protected PlainAttrDAO plainAttrDAO;
+
+ @Autowired
+ protected DerAttrDAO derAttrDAO;
+
+ @Autowired
+ protected VirAttrDAO virAttrDAO;
+
+ @Autowired
+ protected PlainAttrValueDAO plainAttrValueDAO;
+
+ @Autowired
+ protected UserDAO userDAO;
+
+ @Autowired
+ protected ExternalResourceDAO resourceDAO;
+
+ @Autowired
+ protected MembershipDAO membershipDAO;
+
+ @Autowired
+ protected PolicyDAO policyDAO;
+
+ @Autowired
+ protected EntityFactory entityFactory;
+
+ @Autowired
+ protected AttributableUtilFactory attrUtilFactory;
+
+ @SuppressWarnings("unchecked")
+ protected <T extends Schema> T getSchema(final String schemaName, final Class<T> reference) {
+ T result = null;
+
+ if (PlainSchema.class.isAssignableFrom(reference)) {
+ result = (T) getPlainSchema(schemaName, (Class<? extends PlainSchema>) reference);
+ } else if (DerSchema.class.isAssignableFrom(reference)) {
+ result = (T) getDerSchema(schemaName, (Class<? extends DerSchema>) reference);
+ } else if (VirSchema.class.isAssignableFrom(reference)) {
+ result = (T) getVirSchema(schemaName, (Class<? extends VirSchema>) reference);
+ }
+
+ return result;
+ }
+
+ protected <T extends PlainSchema> T getPlainSchema(final String schemaName, final Class<T> reference) {
+ T schema = null;
+ if (StringUtils.isNotBlank(schemaName)) {
+ schema = plainSchemaDAO.find(schemaName, reference);
+
+ // safely ignore invalid schemas from AttrTO
+ // see http://code.google.com/p/syncope/issues/detail?id=17
+ if (schema == null) {
+ LOG.debug("Ignoring invalid schema {}", schemaName);
+ } else if (schema.isReadonly()) {
+ schema = null;
+
+ LOG.debug("Ignoring readonly schema {}", schemaName);
+ }
+ }
+
+ return schema;
+ }
+
+ private <T extends DerSchema> T getDerSchema(final String derSchemaName, final Class<T> reference) {
+ T derivedSchema = null;
+ if (StringUtils.isNotBlank(derSchemaName)) {
+ derivedSchema = derSchemaDAO.find(derSchemaName, reference);
+ if (derivedSchema == null) {
+ LOG.debug("Ignoring invalid derived schema {}", derSchemaName);
+ }
+ }
+
+ return derivedSchema;
+ }
+
+ private <T extends VirSchema> T getVirSchema(final String virSchemaName, final Class<T> reference) {
+ T virtualSchema = null;
+ if (StringUtils.isNotBlank(virSchemaName)) {
+ virtualSchema = virSchemaDAO.find(virSchemaName, reference);
+
+ if (virtualSchema == null) {
+ LOG.debug("Ignoring invalid virtual schema {}", virSchemaName);
+ }
+ }
+
+ return virtualSchema;
+ }
+
+ protected void fillAttribute(final List<String> values, final AttributableUtil attributableUtil,
+ final PlainSchema schema, final PlainAttr attr, final SyncopeClientException invalidValues) {
+
+ // if schema is multivalue, all values are considered for addition;
+ // otherwise only the fist one - if provided - is considered
+ List<String> valuesProvided = schema.isMultivalue()
+ ? values
+ : (values.isEmpty()
+ ? Collections.<String>emptyList()
+ : Collections.singletonList(values.iterator().next()));
+
+ for (String value : valuesProvided) {
+ if (value == null || value.isEmpty()) {
+ LOG.debug("Null value for {}, ignoring", schema.getKey());
+ } else {
+ try {
+ attr.addValue(value, attributableUtil);
+ } catch (InvalidPlainAttrValueException e) {
+ LOG.warn("Invalid value for attribute " + schema.getKey() + ": " + value, e);
+
+ invalidValues.getElements().add(schema.getKey() + ": " + value + " - " + e.getMessage());
+ }
+ }
+ }
+ }
+
+ private boolean evaluateMandatoryCondition(final AttributableUtil attrUtil, final ExternalResource resource,
+ final Attributable attributable, final String intAttrName, final IntMappingType intMappingType) {
+
+ boolean result = false;
+
+ final List<MappingItem> mappings = MappingUtil.getMatchingMappingItems(
+ attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION), intAttrName, intMappingType);
+ for (Iterator<MappingItem> itor = mappings.iterator(); itor.hasNext() && !result;) {
+ final MappingItem mapping = itor.next();
+ result |= JexlUtil.evaluateMandatoryCondition(mapping.getMandatoryCondition(), attributable);
+ }
+
+ return result;
+ }
+
+ private boolean evaluateMandatoryCondition(final AttributableUtil attrUtil,
+ final Attributable<?, ?, ?> attributable, final String intAttrName, final IntMappingType intMappingType) {
+
+ boolean result = false;
+
+ if (attributable instanceof Subject) {
+ for (Iterator<? extends ExternalResource> itor = ((Subject<?, ?, ?>) attributable).getResources().iterator();
+ itor.hasNext() && !result;) {
+
+ final ExternalResource resource = itor.next();
+ if (resource.isEnforceMandatoryCondition()) {
+ result |= evaluateMandatoryCondition(attrUtil, resource, attributable, intAttrName, intMappingType);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private SyncopeClientException checkMandatory(final AttributableUtil attrUtil,
+ final Attributable<?, ?, ?> attributable) {
+
+ SyncopeClientException reqValMissing = SyncopeClientException.build(ClientExceptionType.RequiredValuesMissing);
+
+ // Check if there is some mandatory schema defined for which no value has been provided
+ List<? extends PlainSchema> plainSchemas;
+ switch (attrUtil.getType()) {
+ case ROLE:
+ plainSchemas = ((Role) attributable).getAttrTemplateSchemas(RPlainAttrTemplate.class);
+ break;
+
+ case MEMBERSHIP:
+ plainSchemas = ((Membership) attributable).getRole().getAttrTemplateSchemas(MPlainAttrTemplate.class);
+ break;
+
+ case USER:
+ default:
+ plainSchemas = plainSchemaDAO.findAll(attrUtil.plainSchemaClass());
+ }
+ for (PlainSchema schema : plainSchemas) {
+ if (attributable.getPlainAttr(schema.getKey()) == null
+ && !schema.isReadonly()
+ && (JexlUtil.evaluateMandatoryCondition(schema.getMandatoryCondition(), attributable)
+ || evaluateMandatoryCondition(attrUtil, attributable, schema.getKey(),
+ attrUtil.intMappingType()))) {
+
+ LOG.error("Mandatory schema " + schema.getKey() + " not provided with values");
+
+ reqValMissing.getElements().add(schema.getKey());
+ }
+ }
+
+ List<? extends DerSchema> derSchemas;
+ switch (attrUtil.getType()) {
+ case ROLE:
+ derSchemas = ((Role) attributable).getAttrTemplateSchemas(RDerAttrTemplate.class);
+ break;
+
+ case MEMBERSHIP:
+ derSchemas = ((Membership) attributable).getRole().getAttrTemplateSchemas(MDerAttrTemplate.class);
+ break;
+
+ case USER:
+ default:
+ derSchemas = derSchemaDAO.findAll(attrUtil.derSchemaClass());
+ }
+ for (DerSchema derSchema : derSchemas) {
+ if (attributable.getDerAttr(derSchema.getKey()) == null
+ && evaluateMandatoryCondition(attrUtil, attributable, derSchema.getKey(),
+ attrUtil.derIntMappingType())) {
+
+ LOG.error("Mandatory derived schema " + derSchema.getKey() + " does not evaluate to any value");
+
+ reqValMissing.getElements().add(derSchema.getKey());
+ }
+ }
+
+ List<? extends VirSchema> virSchemas;
+ switch (attrUtil.getType()) {
+ case ROLE:
+ virSchemas = ((Role) attributable).getAttrTemplateSchemas(RVirAttrTemplate.class);
+ break;
+
+ case MEMBERSHIP:
+ virSchemas = ((Membership) attributable).getRole().getAttrTemplateSchemas(MVirAttrTemplate.class);
+ break;
+
+ case USER:
+ default:
+ virSchemas = virSchemaDAO.findAll(attrUtil.virSchemaClass());
+ }
+ for (VirSchema virSchema : virSchemas) {
+ if (attributable.getVirAttr(virSchema.getKey()) == null
+ && !virSchema.isReadonly()
+ && evaluateMandatoryCondition(attrUtil, attributable, virSchema.getKey(),
+ attrUtil.virIntMappingType())) {
+
+ LOG.error("Mandatory virtual schema " + virSchema.getKey() + " not provided with values");
+
+ reqValMissing.getElements().add(virSchema.getKey());
+ }
+ }
+
+ return reqValMissing;
+ }
+
+ private void setPlainAttrSchema(final Attributable<?, ?, ?> attributable,
+ final PlainAttr attr, final PlainSchema schema) {
+
+ if (attr instanceof UPlainAttr) {
+ ((UPlainAttr) attr).setSchema((UPlainSchema) schema);
+ } else if (attr instanceof RPlainAttr) {
+ RPlainAttrTemplate template =
+ ((Role) attributable).getAttrTemplate(RPlainAttrTemplate.class, schema.getKey());
+ if (template != null) {
+ ((RPlainAttr) attr).setTemplate(template);
+ }
+ } else if (attr instanceof MPlainAttr) {
+ MPlainAttrTemplate template = ((Membership) attributable).getRole().
+ getAttrTemplate(MPlainAttrTemplate.class, schema.getKey());
+ if (template != null) {
+ ((MPlainAttr) attr).setTemplate(template);
+ }
+ }
+ }
+
+ private void setDerAttrSchema(final Attributable<?, ?, ?> attributable,
+ final DerAttr derAttr, final DerSchema derSchema) {
+
+ if (derAttr instanceof UDerAttr) {
+ ((UDerAttr) derAttr).setSchema((UDerSchema) derSchema);
+ } else if (derAttr instanceof RDerAttr) {
+ RDerAttrTemplate template = ((Role) attributable).
+ getAttrTemplate(RDerAttrTemplate.class, derSchema.getKey());
+ if (template != null) {
+ ((RDerAttr) derAttr).setTemplate(template);
+ }
+ } else if (derAttr instanceof MDerAttr) {
+ MDerAttrTemplate template = ((Membership) attributable).getRole().
+ getAttrTemplate(MDerAttrTemplate.class, derSchema.getKey());
+ if (template != null) {
+ ((MDerAttr) derAttr).setTemplate(template);
+ }
+ }
+ }
+
+ private void setVirAttrSchema(final Attributable<?, ?, ?> attributable,
+ final VirAttr virAttr, final VirSchema virSchema) {
+
+ if (virAttr instanceof UVirAttr) {
+ ((UVirAttr) virAttr).setSchema((UVirSchema) virSchema);
+ } else if (virAttr instanceof RVirAttr) {
+ RVirAttrTemplate template = ((Role) attributable).
+ getAttrTemplate(RVirAttrTemplate.class, virSchema.getKey());
+ if (template != null) {
+ ((RVirAttr) virAttr).setTemplate(template);
+ }
+ } else if (virAttr instanceof MVirAttr) {
+ MVirAttrTemplate template =
+ ((Membership) attributable).getRole().
+ getAttrTemplate(MVirAttrTemplate.class, virSchema.getKey());
+ if (template != null) {
+ ((MVirAttr) virAttr).setTemplate(template);
+ }
+ }
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public PropagationByResource fillVirtual(final Attributable attributable,
+ final Set<String> vAttrsToBeRemoved, final Set<AttrMod> vAttrsToBeUpdated,
+ final AttributableUtil attrUtil) {
+
+ PropagationByResource propByRes = new PropagationByResource();
+
+ final Set<ExternalResource> externalResources = new HashSet<>();
+ if (attributable instanceof Subject) {
+ externalResources.addAll(((Subject<?, ?, ?>) attributable).getResources());
+ }
+
+ if (attributable instanceof Membership) {
+ externalResources.clear();
+ externalResources.addAll(((Membership) attributable).getUser().getResources());
+ }
+
+ // 1. virtual attributes to be removed
+ for (String vAttrToBeRemoved : vAttrsToBeRemoved) {
+ VirSchema virSchema = getVirSchema(vAttrToBeRemoved, attrUtil.virSchemaClass());
+ if (virSchema != null) {
+ VirAttr virAttr = attributable.getVirAttr(virSchema.getKey());
+ if (virAttr == null) {
+ LOG.debug("No virtual attribute found for schema {}", virSchema.getKey());
+ } else {
+ attributable.removeVirAttr(virAttr);
+ virAttrDAO.delete(virAttr);
+ }
+
+ for (ExternalResource resource : resourceDAO.findAll()) {
+ for (MappingItem mapItem : attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION)) {
+ if (virSchema.getKey().equals(mapItem.getIntAttrName())
+ && mapItem.getIntMappingType() == attrUtil.virIntMappingType()
+ && externalResources.contains(resource)) {
+
+ propByRes.add(ResourceOperation.UPDATE, resource.getKey());
+
+ // Using virtual attribute as AccountId must be avoided
+ if (mapItem.isAccountid() && virAttr != null && !virAttr.getValues().isEmpty()) {
+ propByRes.addOldAccountId(resource.getKey(), virAttr.getValues().get(0));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ LOG.debug("Virtual attributes to be removed:\n{}", propByRes);
+
+ // 2. virtual attributes to be updated
+ for (AttrMod vAttrToBeUpdated : vAttrsToBeUpdated) {
+ VirSchema virSchema = getVirSchema(vAttrToBeUpdated.getSchema(), attrUtil.virSchemaClass());
+ VirAttr virAttr = null;
+ if (virSchema != null) {
+ virAttr = attributable.getVirAttr(virSchema.getKey());
+ if (virAttr == null) {
+ virAttr = attrUtil.newVirAttr();
+ setVirAttrSchema(attributable, virAttr, virSchema);
+ if (virAttr.getSchema() == null) {
+ LOG.debug("Ignoring {} because no valid schema or template was found", vAttrToBeUpdated);
+ } else {
+ attributable.addVirAttr(virAttr);
+ }
+ }
+ }
+
+ if (virSchema != null && virAttr != null && virAttr.getSchema() != null) {
+ for (ExternalResource resource : resourceDAO.findAll()) {
+ for (MappingItem mapItem : attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION)) {
+ if (virSchema.getKey().equals(mapItem.getIntAttrName())
+ && mapItem.getIntMappingType() == attrUtil.virIntMappingType()
+ && externalResources.contains(resource)) {
+
+ propByRes.add(ResourceOperation.UPDATE, resource.getKey());
+ }
+ }
+ }
+
+ final List<String> values = new ArrayList<>(virAttr.getValues());
+ values.removeAll(vAttrToBeUpdated.getValuesToBeRemoved());
+ values.addAll(vAttrToBeUpdated.getValuesToBeAdded());
+
+ virAttr.getValues().clear();
+ virAttr.getValues().addAll(values);
+
+ // Owner cannot be specified before otherwise a virtual attribute remove will be invalidated.
+ virAttr.setOwner(attributable);
+ }
+ }
+
+ LOG.debug("Virtual attributes to be added:\n{}", propByRes);
+
+ return propByRes;
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ protected PropagationByResource fill(final Attributable attributable,
+ final AbstractAttributableMod attributableMod, final AttributableUtil attrUtil,
+ final SyncopeClientCompositeException scce) {
+
+ PropagationByResource propByRes = new PropagationByResource();
+
+ SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues);
+
+ if (attributable instanceof Subject && attributableMod instanceof AbstractSubjectMod) {
+ // 1. resources to be removed
+ for (String resourceToBeRemoved : ((AbstractSubjectMod) attributableMod).getResourcesToRemove()) {
+ ExternalResource resource = resourceDAO.find(resourceToBeRemoved);
+ if (resource != null) {
+ propByRes.add(ResourceOperation.DELETE, resource.getKey());
+ ((Subject<?, ?, ?>) attributable).removeResource(resource);
+ }
+ }
+
+ LOG.debug("Resources to be removed:\n{}", propByRes);
+
+ // 2. resources to be added
+ for (String resourceToBeAdded : ((AbstractSubjectMod) attributableMod).getResourcesToAdd()) {
+ ExternalResource resource = resourceDAO.find(resourceToBeAdded);
+ if (resource != null) {
+ propByRes.add(ResourceOperation.CREATE, resource.getKey());
+ ((Subject<?, ?, ?>) attributable).addResource(resource);
+ }
+ }
+
+ LOG.debug("Resources to be added:\n{}", propByRes);
+ }
+
+ // 3. attributes to be removed
+ for (String attributeToBeRemoved : attributableMod.getAttrsToRemove()) {
+ PlainSchema schema = getPlainSchema(attributeToBeRemoved, attrUtil.plainSchemaClass());
+ if (schema != null) {
+ PlainAttr attr = attributable.getPlainAttr(schema.getKey());
+ if (attr == null) {
+ LOG.debug("No attribute found for schema {}", schema);
+ } else {
+ String newValue = null;
+ for (AttrMod mod : attributableMod.getAttrsToUpdate()) {
+ if (schema.getKey().equals(mod.getSchema())) {
+ newValue = mod.getValuesToBeAdded().get(0);
+ }
+ }
+
+ if (!schema.isUniqueConstraint()
+ || (!attr.getUniqueValue().getStringValue().equals(newValue))) {
+
+ attributable.removePlainAttr(attr);
+ plainAttrDAO.delete(attr.getKey(), attrUtil.plainAttrClass());
+ }
+ }
+
+ if (attributable instanceof Subject) {
+ for (ExternalResource resource : resourceDAO.findAll()) {
+ for (MappingItem mapItem : attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION)) {
+ if (schema.getKey().equals(mapItem.getIntAttrName())
+ && mapItem.getIntMappingType() == attrUtil.intMappingType()
+ && ((Subject<?, ?, ?>) attributable).getResources().contains(resource)) {
+
+ propByRes.add(ResourceOperation.UPDATE, resource.getKey());
+
+ if (mapItem.isAccountid() && attr != null
+ && !attr.getValuesAsStrings().isEmpty()) {
+
+ propByRes.addOldAccountId(resource.getKey(),
+ attr.getValuesAsStrings().iterator().next());
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ LOG.debug("Attributes to be removed:\n{}", propByRes);
+
+ // 4. attributes to be updated
+ for (AttrMod attributeMod : attributableMod.getAttrsToUpdate()) {
+ PlainSchema schema = getPlainSchema(attributeMod.getSchema(), attrUtil.plainSchemaClass());
+ PlainAttr attr = null;
+ if (schema != null) {
+ attr = attributable.getPlainAttr(schema.getKey());
+ if (attr == null) {
+ attr = attrUtil.newPlainAttr();
+ setPlainAttrSchema(attributable, attr, schema);
+ if (attr.getSchema() == null) {
+ LOG.debug("Ignoring {} because no valid schema or template was found", attributeMod);
+ } else {
+ attr.setOwner(attributable);
+ attributable.addPlainAttr(attr);
+ }
+ }
+ }
+
+ if (schema != null && attr != null && attr.getSchema() != null) {
+ if (attributable instanceof Subject) {
+ for (ExternalResource resource : resourceDAO.findAll()) {
+ for (MappingItem mapItem : attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION)) {
+ if (schema.getKey().equals(mapItem.getIntAttrName())
+ && mapItem.getIntMappingType() == attrUtil.intMappingType()
+ && ((Subject<?, ?, ?>) attributable).getResources().contains(resource)) {
+
+ propByRes.add(ResourceOperation.UPDATE, resource.getKey());
+ }
+ }
+ }
+ }
+
+ // 1.1 remove values
+ Set<Long> valuesToBeRemoved = new HashSet<>();
+ for (String valueToBeRemoved : attributeMod.getValuesToBeRemoved()) {
+ if (attr.getSchema().isUniqueConstraint()) {
+ if (attr.getUniqueValue() != null
+ && valueToBeRemoved.equals(attr.getUniqueValue().getValueAsString())) {
+
+ valuesToBeRemoved.add(attr.getUniqueValue().getKey());
+ }
+ } else {
+ for (PlainAttrValue mav : attr.getValues()) {
+ if (valueToBeRemoved.equals(mav.getValueAsString())) {
+ valuesToBeRemoved.add(mav.getKey());
+ }
+ }
+ }
+ }
+ for (Long attributeValueId : valuesToBeRemoved) {
+ plainAttrValueDAO.delete(attributeValueId, attrUtil.plainAttrValueClass());
+ }
+
+ // 1.2 add values
+ List<String> valuesToBeAdded = attributeMod.getValuesToBeAdded();
+ if (valuesToBeAdded != null && !valuesToBeAdded.isEmpty()
+ && (!schema.isUniqueConstraint() || attr.getUniqueValue() == null
+ || !valuesToBeAdded.iterator().next().equals(attr.getUniqueValue().getValueAsString()))) {
+
+ fillAttribute(attributeMod.getValuesToBeAdded(), attrUtil, schema, attr, invalidValues);
+ }
+
+ // if no values are in, the attribute can be safely removed
+ if (attr.getValuesAsStrings().isEmpty()) {
+ plainAttrDAO.delete(attr);
+ }
+ }
+ }
+
+ if (!invalidValues.isEmpty()) {
+ scce.addException(invalidValues);
+ }
+
+ LOG.debug("Attributes to be updated:\n{}", propByRes);
+
+ // 5. derived attributes to be removed
+ for (String derAttrToBeRemoved : attributableMod.getDerAttrsToRemove()) {
+ DerSchema derSchema = getDerSchema(derAttrToBeRemoved, attrUtil.derSchemaClass());
+ if (derSchema != null) {
+ DerAttr derAttr = attributable.getDerAttr(derSchema.getKey());
+ if (derAttr == null) {
+ LOG.debug("No derived attribute found for schema {}", derSchema.getKey());
+ } else {
+ derAttrDAO.delete(derAttr);
+ }
+
+ if (attributable instanceof Subject) {
+ for (ExternalResource resource : resourceDAO.findAll()) {
+ for (MappingItem mapItem : attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION)) {
+ if (derSchema.getKey().equals(mapItem.getIntAttrName())
+ && mapItem.getIntMappingType() == attrUtil.derIntMappingType()
+ && ((Subject<?, ?, ?>) attributable).getResources().contains(resource)) {
+
+ propByRes.add(ResourceOperation.UPDATE, resource.getKey());
+
+ if (mapItem.isAccountid() && derAttr != null
+ && !derAttr.getValue(attributable.getPlainAttrs()).isEmpty()) {
+
+ propByRes.addOldAccountId(resource.getKey(),
+ derAttr.getValue(attributable.getPlainAttrs()));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ LOG.debug("Derived attributes to be removed:\n{}", propByRes);
+
+ // 6. derived attributes to be added
+ for (String derAttrToBeAdded : attributableMod.getDerAttrsToAdd()) {
+ DerSchema derSchema = getDerSchema(derAttrToBeAdded, attrUtil.derSchemaClass());
+ if (derSchema != null) {
+ if (attributable instanceof Subject) {
+ for (ExternalResource resource : resourceDAO.findAll()) {
+ for (MappingItem mapItem : attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION)) {
+ if (derSchema.getKey().equals(mapItem.getIntAttrName())
+ && mapItem.getIntMappingType() == attrUtil.derIntMappingType()
+ && ((Subject<?, ?, ?>) attributable).getResources().contains(resource)) {
+
+ propByRes.add(ResourceOperation.UPDATE, resource.getKey());
+ }
+ }
+ }
+ }
+
+ DerAttr derAttr = attrUtil.newDerAttr();
+ setDerAttrSchema(attributable, derAttr, derSchema);
+ if (derAttr.getSchema() == null) {
+ LOG.debug("Ignoring {} because no valid schema or template was found", derAttrToBeAdded);
+ } else {
+ derAttr.setOwner(attributable);
+ attributable.addDerAttr(derAttr);
+ }
+ }
+ }
+
+ LOG.debug("Derived attributes to be added:\n{}", propByRes);
+
+ // 7. virtual attributes: for users and roles this is delegated to PropagationManager
+ if (AttributableType.USER != attrUtil.getType() && AttributableType.ROLE != attrUtil.getType()) {
+ fillVirtual(attributable, attributableMod.getVirAttrsToRemove(),
+ attributableMod.getVirAttrsToUpdate(), attrUtil);
+ }
+
+ // Finally, check if mandatory values are missing
+ SyncopeClientException requiredValuesMissing = checkMandatory(attrUtil, attributable);
+ if (!requiredValuesMissing.isEmpty()) {
+ scce.addException(requiredValuesMissing);
+ }
+
+ // Throw composite exception if there is at least one element set in the composing exceptions
+ if (scce.hasExceptions()) {
+ throw scce;
+ }
+
+ return propByRes;
+ }
+
+ /**
+ * Add virtual attributes and specify values to be propagated.
+ *
+ * @param attributable attributable.
+ * @param vAttrs virtual attributes to be added.
+ * @param attrUtil attributable util.
+ */
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public void fillVirtual(final Attributable attributable, final Collection<AttrTO> vAttrs,
+ final AttributableUtil attrUtil) {
+
+ for (AttrTO attributeTO : vAttrs) {
+ VirAttr virAttr = attributable.getVirAttr(attributeTO.getSchema());
+ if (virAttr == null) {
+ VirSchema virSchema = getVirSchema(attributeTO.getSchema(), attrUtil.virSchemaClass());
+ if (virSchema != null) {
+ virAttr = attrUtil.newVirAttr();
+ setVirAttrSchema(attributable, virAttr, virSchema);
+ if (virAttr.getSchema() == null) {
+ LOG.debug("Ignoring {} because no valid schema or template was found", attributeTO);
+ } else {
+ virAttr.setOwner(attributable);
+ attributable.addVirAttr(virAttr);
+ virAttr.getValues().clear();
+ virAttr.getValues().addAll(attributeTO.getValues());
+ }
+ }
+ } else {
+ virAttr.getValues().clear();
+ virAttr.getValues().addAll(attributeTO.getValues());
+ }
+ }
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ protected void fill(final Attributable attributable, final AbstractAttributableTO attributableTO,
+ final AttributableUtil attributableUtil, final SyncopeClientCompositeException scce) {
+
+ // 1. attributes
+ SyncopeClientException invalidValues = SyncopeClientException.build(ClientExceptionType.InvalidValues);
+
+ // Only consider attributeTO with values
+ for (AttrTO attributeTO : attributableTO.getPlainAttrs()) {
+ if (attributeTO.getValues() != null && !attributeTO.getValues().isEmpty()) {
+ PlainSchema schema = getPlainSchema(attributeTO.getSchema(), attributableUtil.plainSchemaClass());
+
+ if (schema != null) {
+ PlainAttr attr = attributable.getPlainAttr(schema.getKey());
+ if (attr == null) {
+ attr = attributableUtil.newPlainAttr();
+ setPlainAttrSchema(attributable, attr, schema);
+ }
+ if (attr.getSchema() == null) {
+ LOG.debug("Ignoring {} because no valid schema or template was found", attributeTO);
+ } else {
+ fillAttribute(attributeTO.getValues(), attributableUtil, schema, attr, invalidValues);
+
+ if (!attr.getValuesAsStrings().isEmpty()) {
+ attributable.addPlainAttr(attr);
+ attr.setOwner(attributable);
+ }
+ }
+ }
+ }
+ }
+
+ if (!invalidValues.isEmpty()) {
+ scce.addException(invalidValues);
+ }
+
+ // 2. derived attributes
+ for (AttrTO attributeTO : attributableTO.getDerAttrs()) {
+ DerSchema derSchema = getDerSchema(attributeTO.getSchema(), attributableUtil.derSchemaClass());
+
+ if (derSchema != null) {
+ DerAttr derAttr = attributableUtil.newDerAttr();
+ setDerAttrSchema(attributable, derAttr, derSchema);
+ if (derAttr.getSchema() == null) {
+ LOG.debug("Ignoring {} because no valid schema or template was found", attributeTO);
+ } else {
+ derAttr.setOwner(attributable);
+ attributable.addDerAttr(derAttr);
+ }
+ }
+ }
+
+ // 3. user and role virtual attributes will be evaluated by the propagation manager only (if needed).
+ if (AttributableType.USER == attributableUtil.getType()
+ || AttributableType.ROLE == attributableUtil.getType()) {
+
+ for (AttrTO vattrTO : attributableTO.getVirAttrs()) {
+ VirSchema virSchema = getVirSchema(vattrTO.getSchema(), attributableUtil.virSchemaClass());
+
+ if (virSchema != null) {
+ VirAttr virAttr = attributableUtil.newVirAttr();
+ setVirAttrSchema(attributable, virAttr, virSchema);
+ if (virAttr.getSchema() == null) {
+ LOG.debug("Ignoring {} because no valid schema or template was found", vattrTO);
+ } else {
+ virAttr.setOwner(attributable);
+ attributable.addVirAttr(virAttr);
+ }
+ }
+ }
+ }
+
+ fillVirtual(attributable, attributableTO.getVirAttrs(), attributableUtil);
+
+ // 4. resources
+ if (attributable instanceof Subject && attributableTO instanceof AbstractSubjectTO) {
+ for (String resourceName : ((AbstractSubjectTO) attributableTO).getResources()) {
+ ExternalResource resource = resourceDAO.find(resourceName);
+
+ if (resource != null) {
+ ((Subject<?, ?, ?>) attributable).addResource(resource);
+ }
+ }
+ }
+
+ SyncopeClientException requiredValuesMissing = checkMandatory(attributableUtil, attributable);
+ if (!requiredValuesMissing.isEmpty()) {
+ scce.addException(requiredValuesMissing);
+ }
+
+ // Throw composite exception if there is at least one element set in the composing exceptions
+ if (scce.hasExceptions()) {
+ throw scce;
+ }
+ }
+
+ protected void fillTO(final AbstractAttributableTO attributableTO,
+ final Collection<? extends PlainAttr> attrs,
+ final Collection<? extends DerAttr> derAttrs,
+ final Collection<? extends VirAttr> virAttrs,
+ final Collection<? extends ExternalResource> resources) {
+
+ AttrTO attributeTO;
+ for (PlainAttr attr : attrs) {
+ attributeTO = new AttrTO();
+ attributeTO.setSchema(attr.getSchema().getKey());
+ attributeTO.getValues().addAll(attr.getValuesAsStrings());
+ attributeTO.setReadonly(attr.getSchema().isReadonly());
+
+ attributableTO.getPlainAttrs().add(attributeTO);
+ }
+
+ for (DerAttr derAttr : derAttrs) {
+ attributeTO = new AttrTO();
+ attributeTO.setSchema(derAttr.getSchema().getKey());
+ attributeTO.getValues().add(derAttr.getValue(attrs));
+ attributeTO.setReadonly(true);
+
+ attributableTO.getDerAttrs().add(attributeTO);
+ }
+
+ for (VirAttr virAttr : virAttrs) {
+ attributeTO = new AttrTO();
+ attributeTO.setSchema(virAttr.getSchema().getKey());
+ attributeTO.getValues().addAll(virAttr.getValues());
+ attributeTO.setReadonly(virAttr.getSchema().isReadonly());
+
+ attributableTO.getVirAttrs().add(attributeTO);
+ }
+
+ if (attributableTO instanceof AbstractSubjectTO) {
+ for (ExternalResource resource : resources) {
+ ((AbstractSubjectTO) attributableTO).getResources().add(resource.getKey());
+ }
+ }
+ }
+}