http://git-wip-us.apache.org/repos/asf/syncope/blob/97607b16/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 6c5e0d6,0000000..b0fb8b0 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,468 -1,0 +1,473 @@@ +/* + * 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.Closure; +import org.apache.commons.collections4.CollectionUtils; +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.mod.UserMod; +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.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.RoleDAO; +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.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; + + @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; + + final String authUsername = AuthContextUtils.getAuthenticatedUsername(); + 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); ++ 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 { + final 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) { + if (LOG.isDebugEnabled()) { + LOG.debug("Ignoring invalid anyObject " + relationshipTO.getRightKey()); + } + } else { + URelationship relationship = null; + if (user.getKey() != null) { + relationship = user.getRelationship(anyObject.getKey()); + } + if (relationship == null) { + relationship = entityFactory.newEntity(URelationship.class); + relationship.setRightEnd(anyObject); + relationship.setLeftEnd(user); + + user.add(relationship); + } + } + } + + // memberships + for (MembershipTO membershipTO : userTO.getMemberships()) { + Group group = groupDAO.find(membershipTO.getRightKey()); + + if (group == null) { + if (LOG.isDebugEnabled()) { + 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()); + } + + @Override + public PropagationByResource update(final User toBeUpdated, final UserMod userMod) { + // 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, userMod); + + // password + if (StringUtils.isNotBlank(userMod.getPassword())) { + setPassword(user, userMod.getPassword(), scce); + user.setChangePwdDate(new Date()); + propByRes.addAll(ResourceOperation.UPDATE, currentResources); + } + + // username + if (userMod.getUsername() != null && !userMod.getUsername().equals(user.getUsername())) { + propByRes.addAll(ResourceOperation.UPDATE, currentResources); + + user.setUsername(userMod.getUsername()); + AuthContextUtils.updateAuthenticatedUsername(userMod.getUsername()); + } + + // security question / answer: + // userMod.getSecurityQuestion() is null => remove user security question and answer + // userMod.getSecurityQuestion() == 0 => don't change anything + // userMod.getSecurityQuestion() > 0 => update user security question and answer + if (userMod.getSecurityQuestion() == null) { + user.setSecurityQuestion(null); + user.setSecurityAnswer(null); + } else if (userMod.getSecurityQuestion() > 0) { + SecurityQuestion securityQuestion = securityQuestionDAO.find(userMod.getSecurityQuestion()); + if (securityQuestion != null) { + user.setSecurityQuestion(securityQuestion); + user.setSecurityAnswer(userMod.getSecurityAnswer()); + } + } + + // roles + CollectionUtils.forAllDo(userMod.getRolesToRemove(), new Closure<Long>() { + + @Override + public void execute(final Long roleKey) { + Role role = roleDAO.find(roleKey); + if (role == null) { + LOG.warn("Ignoring unknown role with id {}", roleKey); + } else { + user.remove(role); + } + } + }); + CollectionUtils.forAllDo(userMod.getRolesToAdd(), new Closure<Long>() { + + @Override + public void execute(final Long roleKey) { + Role role = roleDAO.find(roleKey); + if (role == null) { + LOG.warn("Ignoring unknown role with id {}", roleKey); + } else { + user.add(role); + } + } + }); + + // attributes, derived attributes, virtual attributes and resources + propByRes.merge(fill(user, userMod, anyUtilsFactory.getInstance(AnyTypeKind.USER), scce)); + + Set<String> toBeDeprovisioned = new HashSet<>(); + Set<String> toBeProvisioned = new HashSet<>(); + + // relationships to be removed + for (Long anyObjectKey : userMod.getRelationshipsToRemove()) { + LOG.debug("Relationship to be removed for any object {}", anyObjectKey); + + URelationship relationship = user.getRelationship(anyObjectKey); + if (relationship == null) { + LOG.warn("Invalid anyObject key specified for relationship to be removed: {}", anyObjectKey); + } else { + if (!userMod.getRelationshipsToAdd().contains(anyObjectKey)) { + user.remove(relationship); + toBeDeprovisioned.addAll(relationship.getRightEnd().getResourceNames()); + } + } + } + + // relationships to be added + for (Long anyObjectKey : userMod.getRelationshipsToAdd()) { + LOG.debug("Relationship to be added for any object {}", anyObjectKey); + + AnyObject otherEnd = anyObjectDAO.find(anyObjectKey); + if (otherEnd == null) { + LOG.debug("Ignoring invalid any object {}", anyObjectKey); + } else { + URelationship relationship = user.getRelationship(otherEnd.getKey()); + if (relationship == null) { + relationship = entityFactory.newEntity(URelationship.class); + relationship.setRightEnd(otherEnd); + relationship.setLeftEnd(user); + + user.add(relationship); + + toBeProvisioned.addAll(otherEnd.getResourceNames()); + } + } + } + + // memberships to be removed + for (Long groupKey : userMod.getMembershipsToRemove()) { + LOG.debug("Membership to be removed for group {}", groupKey); + + UMembership membership = user.getMembership(groupKey); + if (membership == null) { + LOG.debug("Invalid group key specified for membership to be removed: {}", groupKey); + } else { + if (!userMod.getMembershipsToAdd().contains(groupKey)) { + user.remove(membership); + toBeDeprovisioned.addAll(membership.getRightEnd().getResourceNames()); + } + } + } + + // memberships to be added + for (Long groupKey : userMod.getMembershipsToAdd()) { + LOG.debug("Membership to be added for group {}", groupKey); + + Group group = groupDAO.find(groupKey); + if (group == null) { + LOG.debug("Ignoring invalid group {}", groupKey); + } else { + UMembership membership = user.getMembership(group.getKey()); + if (membership == null) { + membership = entityFactory.newEntity(UMembership.class); + membership.setRightEnd(group); + membership.setLeftEnd(user); + + user.add(membership); + + toBeProvisioned.addAll(group.getResourceNames()); + } + } + } + + 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) { ++ 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()); + } + - virAttrHander.retrieveVirAttrValues(user); ++ if (details) { ++ virAttrHander.retrieveVirAttrValues(user); ++ } ++ + fillTO(userTO, user.getRealm().getFullPath(), user.getAuxClasses(), + user.getPlainAttrs(), user.getDerAttrs(), user.getVirAttrs(), userDAO.findAllResources(user)); + - // roles - CollectionUtils.collect(user.getRoles(), new Transformer<Role, Long>() { ++ if (details) { ++ // roles ++ CollectionUtils.collect(user.getRoles(), new Transformer<Role, Long>() { + - @Override - public Long transform(final Role role) { - return role.getKey(); - } - }, userTO.getRoles()); ++ @Override ++ public Long transform(final Role role) { ++ return role.getKey(); ++ } ++ }, userTO.getRoles()); + - // relationships - CollectionUtils.collect(user.getRelationships(), new Transformer<URelationship, RelationshipTO>() { ++ // relationships ++ CollectionUtils.collect(user.getRelationships(), new Transformer<URelationship, RelationshipTO>() { + - @Override - public RelationshipTO transform(final URelationship relationship) { - return UserDataBinderImpl.this.getRelationshipTO(relationship); - } ++ @Override ++ public RelationshipTO transform(final URelationship relationship) { ++ return UserDataBinderImpl.this.getRelationshipTO(relationship); ++ } + - }, userTO.getRelationships()); ++ }, userTO.getRelationships()); + - // memberships - CollectionUtils.collect(user.getMemberships(), new Transformer<UMembership, MembershipTO>() { ++ // memberships ++ CollectionUtils.collect(user.getMemberships(), new Transformer<UMembership, MembershipTO>() { + - @Override - public MembershipTO transform(final UMembership membership) { - return UserDataBinderImpl.this.getMembershipTO(membership); - } - }, userTO.getMemberships()); ++ @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>() { ++ // 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 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()); ++ @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)); ++ return getUserTO(userDAO.authFind(username), true); + } + + @Transactional(readOnly = true) + @Override + public UserTO getUserTO(final Long key) { - return getUserTO(userDAO.authFind(key)); ++ return getUserTO(userDAO.authFind(key), true); + } + +}
http://git-wip-us.apache.org/repos/asf/syncope/blob/97607b16/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/NotificationManagerImpl.java ---------------------------------------------------------------------- diff --cc core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/NotificationManagerImpl.java index cdc540c,0000000..65b4618 mode 100644,000000..100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/NotificationManagerImpl.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/NotificationManagerImpl.java @@@ -1,413 -1,0 +1,413 @@@ +/* + * 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.notification; + +import org.apache.syncope.core.provisioning.api.notification.NotificationManager; +import java.io.StringWriter; +import java.util.ArrayList; +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.syncope.common.lib.SyncopeConstants; +import org.apache.syncope.common.lib.to.GroupTO; +import org.apache.syncope.common.lib.to.UserTO; +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.IntMappingType; +import org.apache.syncope.common.lib.to.AnyObjectTO; +import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.syncope.core.persistence.api.dao.ConfDAO; +import org.apache.syncope.core.persistence.api.dao.NotificationDAO; +import org.apache.syncope.core.persistence.api.dao.GroupDAO; +import org.apache.syncope.core.persistence.api.dao.TaskDAO; +import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.dao.search.OrderByClause; +import org.apache.syncope.core.persistence.api.entity.EntityFactory; +import org.apache.syncope.core.persistence.api.entity.Notification; +import org.apache.syncope.core.persistence.api.entity.PlainAttr; +import org.apache.syncope.core.persistence.api.entity.group.Group; +import org.apache.syncope.core.persistence.api.entity.task.NotificationTask; +import org.apache.syncope.core.persistence.api.entity.task.TaskExec; +import org.apache.syncope.core.persistence.api.entity.user.UDerAttr; +import org.apache.syncope.core.persistence.api.entity.user.UPlainAttr; +import org.apache.syncope.core.persistence.api.entity.user.UVirAttr; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.provisioning.api.data.GroupDataBinder; +import org.apache.syncope.core.provisioning.api.data.UserDataBinder; +import org.apache.syncope.core.misc.search.SearchCondConverter; +import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO; +import org.apache.syncope.core.persistence.api.dao.AnySearchDAO; +import org.apache.syncope.core.persistence.api.entity.Any; +import org.apache.syncope.core.persistence.api.entity.AnyAbout; +import org.apache.syncope.core.persistence.api.entity.AnyType; +import org.apache.syncope.core.provisioning.api.VirAttrHandler; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.VelocityEngine; +import org.apache.velocity.context.Context; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.tools.ToolManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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 NotificationManagerImpl implements NotificationManager { + + /** + * Logger. + */ + private static final Logger LOG = LoggerFactory.getLogger(NotificationManager.class); + + public static final String MAIL_TEMPLATES = "mailTemplates/"; + + public static final String MAIL_TEMPLATE_HTML_SUFFIX = ".html.vm"; + + public static final String MAIL_TEMPLATE_TEXT_SUFFIX = ".txt.vm"; + + /** + * Notification DAO. + */ + @Autowired + private NotificationDAO notificationDAO; + + /** + * Configuration DAO. + */ + @Autowired + private ConfDAO confDAO; + + /** + * AnyObject DAO. + */ + @Autowired + private AnyObjectDAO anyObjectDAO; + + /** + * User DAO. + */ + @Autowired + private UserDAO userDAO; + + /** + * Group DAO. + */ + @Autowired + private GroupDAO groupDAO; + + /** + * Search DAO. + */ + @Autowired + private AnySearchDAO searchDAO; + + /** + * Task DAO. + */ + @Autowired + private TaskDAO taskDAO; + + /** + * Velocity template engine. + */ + @Autowired + private VelocityEngine velocityEngine; + + /** + * Velocity tool manager. + */ + @Autowired + private ToolManager velocityToolManager; + + @Autowired + private VirAttrHandler virAttrHander; + + @Autowired + private UserDataBinder userDataBinder; + + @Autowired + private GroupDataBinder groupDataBinder; + + @Autowired + private EntityFactory entityFactory; + + @Transactional(readOnly = true) + @Override + public long getMaxRetries() { + return confDAO.find("notification.maxRetries", "0").getValues().get(0).getLongValue(); + } + + /** + * Create a notification task. + * + * @param notification notification to take as model + * @param any the any object this task is about + * @param model Velocity model + * @return notification task, fully populated + */ + private NotificationTask getNotificationTask( + final Notification notification, + final Any<?, ?, ?> any, + final Map<String, Object> model) { + + if (any != null) { + virAttrHander.retrieveVirAttrValues(any); + } + + List<User> recipients = new ArrayList<>(); + + if (notification.getRecipients() != null) { + recipients.addAll(searchDAO.<User>search(SyncopeConstants.FULL_ADMIN_REALMS, + SearchCondConverter.convert(notification.getRecipients()), + Collections.<OrderByClause>emptyList(), AnyTypeKind.USER)); + } + + if (notification.isSelfAsRecipient() && any instanceof User) { + recipients.add((User) any); + } + + Set<String> recipientEmails = new HashSet<>(); + List<UserTO> recipientTOs = new ArrayList<>(recipients.size()); + for (User recipient : recipients) { + virAttrHander.retrieveVirAttrValues(recipient); + + String email = getRecipientEmail(notification.getRecipientAttrType(), + notification.getRecipientAttrName(), recipient); + if (email == null) { + LOG.warn("{} cannot be notified: {} not found", recipient, notification.getRecipientAttrName()); + } else { + recipientEmails.add(email); - recipientTOs.add(userDataBinder.getUserTO(recipient)); ++ recipientTOs.add(userDataBinder.getUserTO(recipient, true)); + } + } + + if (notification.getStaticRecipients() != null) { + recipientEmails.addAll(notification.getStaticRecipients()); + } + + model.put("recipients", recipientTOs); + model.put("syncopeConf", this.findAllSyncopeConfs()); + model.put("events", notification.getEvents()); + + NotificationTask task = entityFactory.newEntity(NotificationTask.class); + task.setTraceLevel(notification.getTraceLevel()); + task.getRecipients().addAll(recipientEmails); + task.setSender(notification.getSender()); + task.setSubject(notification.getSubject()); + + String htmlBody = mergeTemplateIntoString( + MAIL_TEMPLATES + notification.getTemplate() + MAIL_TEMPLATE_HTML_SUFFIX, model); + String textBody = mergeTemplateIntoString( + MAIL_TEMPLATES + notification.getTemplate() + MAIL_TEMPLATE_TEXT_SUFFIX, model); + + task.setHtmlBody(htmlBody); + task.setTextBody(textBody); + + return task; + } + + private String mergeTemplateIntoString(final String templateLocation, final Map<String, Object> model) { + StringWriter result = new StringWriter(); + try { + Context velocityContext = createVelocityContext(model); + velocityEngine.mergeTemplate(templateLocation, SyncopeConstants.DEFAULT_ENCODING, velocityContext, result); + } catch (VelocityException e) { + LOG.error("Could not get mail body", e); + } catch (RuntimeException e) { + // ensure same behaviour as by using Spring VelocityEngineUtils.mergeTemplateIntoString() + throw e; + } catch (Exception e) { + LOG.error("Could not get mail body", e); + } + + return result.toString(); + } + + /** + * Create a Velocity Context for the given model, to be passed to the template for merging. + * + * @param model Velocity model + * @return Velocity context + */ + protected Context createVelocityContext(final Map<String, Object> model) { + Context toolContext = velocityToolManager.createContext(); + return new VelocityContext(model, toolContext); + } + + @Override + public List<NotificationTask> createTasks( + final AuditElements.EventCategoryType type, + final String category, + final String subcategory, + final String event, + final Result condition, + final Object before, + final Object output, + final Object... input) { + + Any<?, ?, ?> any = null; + + if (before instanceof UserTO) { + any = userDAO.find(((UserTO) before).getKey()); + } else if (output instanceof UserTO) { + any = userDAO.find(((UserTO) output).getKey()); + } else if (before instanceof AnyObjectTO) { + any = anyObjectDAO.find(((AnyObjectTO) before).getKey()); + } else if (output instanceof AnyObjectTO) { + any = anyObjectDAO.find(((AnyObjectTO) output).getKey()); + } else if (before instanceof GroupTO) { + any = groupDAO.find(((GroupTO) before).getKey()); + } else if (output instanceof GroupTO) { + any = groupDAO.find(((GroupTO) output).getKey()); + } + + AnyType anyType = any == null ? null : any.getType(); + LOG.debug("Search notification for [{}]{}", anyType, any); + + List<NotificationTask> notifications = new ArrayList<>(); + for (Notification notification : notificationDAO.findAll()) { + if (LOG.isDebugEnabled()) { + for (AnyAbout about : notification.getAbouts()) { + LOG.debug("Notification about {} defined: {}", about.getAnyType(), about.get()); + } + } + + if (notification.isActive()) { + String currentEvent = AuditLoggerName.buildEvent(type, category, subcategory, event, condition); + if (!notification.getEvents().contains(currentEvent)) { + LOG.debug("No events found about {}", any); + } else if (anyType == null || any == null + || notification.getAbout(anyType) == null + || searchDAO.matches(any, + SearchCondConverter.convert(notification.getAbout(anyType).get()), anyType.getKind())) { + + LOG.debug("Creating notification task for event {} about {}", currentEvent, any); + + final Map<String, Object> model = new HashMap<>(); + model.put("type", type); + model.put("category", category); + model.put("subcategory", subcategory); + model.put("event", event); + model.put("condition", condition); + model.put("before", before); + model.put("output", output); + model.put("input", input); + + if (any instanceof User) { - model.put("user", userDataBinder.getUserTO((User) any)); ++ model.put("user", userDataBinder.getUserTO((User) any, true)); + } else if (any instanceof Group) { - model.put("group", groupDataBinder.getGroupTO((Group) any)); ++ model.put("group", groupDataBinder.getGroupTO((Group) any, true)); + } + + NotificationTask notificationTask = getNotificationTask(notification, any, model); + notificationTask = taskDAO.save(notificationTask); + notifications.add(notificationTask); + } + } else { + LOG.debug("Notification {} is not active, task will not be created", notification.getKey()); + } + } + return notifications; + } + + private String getRecipientEmail( + final IntMappingType recipientAttrType, final String recipientAttrName, final User user) { + + String email = null; + + switch (recipientAttrType) { + case Username: + email = user.getUsername(); + break; + + case UserPlainSchema: + UPlainAttr attr = user.getPlainAttr(recipientAttrName); + if (attr != null) { + email = attr.getValuesAsStrings().isEmpty() ? null : attr.getValuesAsStrings().get(0); + } + break; + + case UserDerivedSchema: + UDerAttr derAttr = user.getDerAttr(recipientAttrName); + if (derAttr != null) { + email = derAttr.getValue(user.getPlainAttrs()); + } + break; + + case UserVirtualSchema: + UVirAttr virAttr = user.getVirAttr(recipientAttrName); + if (virAttr != null) { + email = virAttr.getValues().isEmpty() ? null : virAttr.getValues().get(0); + } + break; + + default: + } + + return email; + } + + @Override + public TaskExec storeExec(final TaskExec execution) { + NotificationTask task = taskDAO.find(execution.getTask().getKey()); + task.addExec(execution); + task.setExecuted(true); + taskDAO.save(task); + // this flush call is needed to generate a value for the execution id + taskDAO.flush(); + return execution; + } + + @Override + public void setTaskExecuted(final Long taskId, final boolean executed) { + NotificationTask task = taskDAO.find(taskId); + task.setExecuted(executed); + taskDAO.save(task); + } + + @Override + public long countExecutionsWithStatus(final Long taskId, final String status) { + NotificationTask task = taskDAO.find(taskId); + long count = 0; + for (TaskExec taskExec : task.getExecs()) { + if (status == null) { + if (taskExec.getStatus() == null) { + count++; + } + } else if (status.equals(taskExec.getStatus())) { + count++; + } + } + return count; + } + + protected Map<String, String> findAllSyncopeConfs() { + Map<String, String> syncopeConfMap = new HashMap<>(); + for (PlainAttr<?> attr : confDAO.get().getPlainAttrs()) { + syncopeConfMap.put(attr.getSchema().getKey(), attr.getValuesAsStrings().get(0)); + } + return syncopeConfMap; + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/97607b16/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AnyObjectPushResultHandlerImpl.java ---------------------------------------------------------------------- diff --cc core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AnyObjectPushResultHandlerImpl.java index 503ce0b,0000000..1d00cc3 mode 100644,000000..100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AnyObjectPushResultHandlerImpl.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AnyObjectPushResultHandlerImpl.java @@@ -1,157 -1,0 +1,157 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.syncope.core.provisioning.java.sync; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.common.lib.mod.AnyObjectMod; +import org.apache.syncope.common.lib.to.AnyTO; +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.entity.Any; +import org.apache.syncope.core.persistence.api.entity.AnyUtils; +import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; +import org.apache.syncope.core.persistence.api.entity.resource.MappingItem; +import org.apache.syncope.core.provisioning.api.TimeoutException; +import org.apache.syncope.core.provisioning.api.sync.AnyObjectPushResultHandler; +import org.identityconnectors.framework.common.objects.ConnectorObject; +import org.identityconnectors.framework.common.objects.ObjectClass; +import org.identityconnectors.framework.common.objects.Uid; + +public class AnyObjectPushResultHandlerImpl extends AbstractPushResultHandler implements AnyObjectPushResultHandler { + + @Override + protected AnyUtils getAnyUtils() { + return anyUtilsFactory.getInstance(AnyTypeKind.GROUP); + } + + @Override + protected Any<?, ?, ?> deprovision(final Any<?, ?, ?> sbj) { - AnyObjectTO before = anyObjectDataBinder.getAnyObjectTO(AnyObject.class.cast(sbj)); ++ AnyObjectTO before = anyObjectDataBinder.getAnyObjectTO(AnyObject.class.cast(sbj), true); + + List<String> noPropResources = new ArrayList<>(before.getResources()); + noPropResources.remove(profile.getTask().getResource().getKey()); + + taskExecutor.execute(propagationManager.getAnyObjectDeleteTasks(before.getKey(), noPropResources)); + + return anyObjectDAO.authFind(before.getKey()); + } + + @Override + protected Any<?, ?, ?> provision(final Any<?, ?, ?> sbj, final Boolean enabled) { - AnyObjectTO before = anyObjectDataBinder.getAnyObjectTO(AnyObject.class.cast(sbj)); ++ AnyObjectTO before = anyObjectDataBinder.getAnyObjectTO(AnyObject.class.cast(sbj), true); + + List<String> noPropResources = new ArrayList<>(before.getResources()); + noPropResources.remove(profile.getTask().getResource().getKey()); + + PropagationByResource propByRes = new PropagationByResource(); + propByRes.add(ResourceOperation.CREATE, profile.getTask().getResource().getKey()); + + taskExecutor.execute(propagationManager.getAnyObjectCreateTasks( + before.getKey(), + Collections.unmodifiableCollection(before.getVirAttrs()), + propByRes, + noPropResources)); + + return anyObjectDAO.authFind(before.getKey()); + } + + @Override + protected Any<?, ?, ?> link(final Any<?, ?, ?> sbj, final Boolean unlink) { + AnyObjectMod anyObjectMod = new AnyObjectMod(); + anyObjectMod.setKey(sbj.getKey()); + + if (unlink) { + anyObjectMod.getResourcesToRemove().add(profile.getTask().getResource().getKey()); + } else { + anyObjectMod.getResourcesToAdd().add(profile.getTask().getResource().getKey()); + } + + awfAdapter.update(anyObjectMod); + + return anyObjectDAO.authFind(sbj.getKey()); + } + + @Override + protected Any<?, ?, ?> unassign(final Any<?, ?, ?> sbj) { + AnyObjectMod anyObjectMod = new AnyObjectMod(); + anyObjectMod.setKey(sbj.getKey()); + anyObjectMod.getResourcesToRemove().add(profile.getTask().getResource().getKey()); + awfAdapter.update(anyObjectMod); + return deprovision(sbj); + } + + @Override + protected Any<?, ?, ?> assign(final Any<?, ?, ?> sbj, final Boolean enabled) { + AnyObjectMod anyObjectMod = new AnyObjectMod(); + anyObjectMod.setKey(sbj.getKey()); + anyObjectMod.getResourcesToAdd().add(profile.getTask().getResource().getKey()); + awfAdapter.update(anyObjectMod); + return provision(sbj, enabled); + } + + @Override + protected String getName(final Any<?, ?, ?> any) { + return StringUtils.EMPTY; + } + + @Override + protected AnyTO getAnyTO(final long key) { + try { + return anyObjectDataBinder.getAnyObjectTO(key); + } catch (Exception e) { + LOG.warn("Error retrieving user {}", key, e); + return null; + } + } + + @Override + protected Any<?, ?, ?> getAny(final long key) { + try { + return anyObjectDAO.authFind(key); + } catch (Exception e) { + LOG.warn("Error retrieving anyObject {}", key, e); + return null; + } + } + + @Override + protected ConnectorObject getRemoteObject(final String connObjectKey, final ObjectClass objectClass) { + ConnectorObject obj = null; + try { + Uid uid = new Uid(connObjectKey); + + obj = profile.getConnector().getObject( + objectClass, + uid, + profile.getConnector().getOperationOptions(Collections.<MappingItem>emptySet())); + } catch (TimeoutException toe) { + LOG.debug("Request timeout", toe); + throw toe; + } catch (RuntimeException ignore) { + LOG.debug("While resolving {}", connObjectKey, ignore); + } + + return obj; + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/97607b16/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/GroupPushResultHandlerImpl.java ---------------------------------------------------------------------- diff --cc core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/GroupPushResultHandlerImpl.java index 78fffbf,0000000..df26d58 mode 100644,000000..100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/GroupPushResultHandlerImpl.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/GroupPushResultHandlerImpl.java @@@ -1,156 -1,0 +1,156 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.syncope.core.provisioning.java.sync; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.apache.syncope.common.lib.mod.GroupMod; +import org.apache.syncope.common.lib.to.AnyTO; +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.entity.Any; +import org.apache.syncope.core.persistence.api.entity.AnyUtils; +import org.apache.syncope.core.persistence.api.entity.group.Group; +import org.apache.syncope.core.persistence.api.entity.resource.MappingItem; +import org.apache.syncope.core.provisioning.api.TimeoutException; +import org.apache.syncope.core.provisioning.api.sync.GroupPushResultHandler; +import org.identityconnectors.framework.common.objects.ConnectorObject; +import org.identityconnectors.framework.common.objects.ObjectClass; +import org.identityconnectors.framework.common.objects.Uid; + +public class GroupPushResultHandlerImpl extends AbstractPushResultHandler implements GroupPushResultHandler { + + @Override + protected AnyUtils getAnyUtils() { + return anyUtilsFactory.getInstance(AnyTypeKind.GROUP); + } + + @Override + protected Any<?, ?, ?> deprovision(final Any<?, ?, ?> sbj) { - GroupTO before = groupDataBinder.getGroupTO(Group.class.cast(sbj)); ++ GroupTO before = groupDataBinder.getGroupTO(Group.class.cast(sbj), true); + + List<String> noPropResources = new ArrayList<>(before.getResources()); + noPropResources.remove(profile.getTask().getResource().getKey()); + + taskExecutor.execute(propagationManager.getGroupDeleteTasks(before.getKey(), noPropResources)); + + return groupDAO.authFind(before.getKey()); + } + + @Override + protected Any<?, ?, ?> provision(final Any<?, ?, ?> sbj, final Boolean enabled) { - GroupTO before = groupDataBinder.getGroupTO(Group.class.cast(sbj)); ++ GroupTO before = groupDataBinder.getGroupTO(Group.class.cast(sbj), true); + + List<String> noPropResources = new ArrayList<>(before.getResources()); + noPropResources.remove(profile.getTask().getResource().getKey()); + + PropagationByResource propByRes = new PropagationByResource(); + propByRes.add(ResourceOperation.CREATE, profile.getTask().getResource().getKey()); + + taskExecutor.execute(propagationManager.getGroupCreateTasks( + before.getKey(), + Collections.unmodifiableCollection(before.getVirAttrs()), + propByRes, + noPropResources)); + + return groupDAO.authFind(before.getKey()); + } + + @Override + protected Any<?, ?, ?> link(final Any<?, ?, ?> sbj, final Boolean unlink) { + GroupMod groupMod = new GroupMod(); + groupMod.setKey(sbj.getKey()); + + if (unlink) { + groupMod.getResourcesToRemove().add(profile.getTask().getResource().getKey()); + } else { + groupMod.getResourcesToAdd().add(profile.getTask().getResource().getKey()); + } + + gwfAdapter.update(groupMod); + + return groupDAO.authFind(sbj.getKey()); + } + + @Override + protected Any<?, ?, ?> unassign(final Any<?, ?, ?> sbj) { + GroupMod groupMod = new GroupMod(); + groupMod.setKey(sbj.getKey()); + groupMod.getResourcesToRemove().add(profile.getTask().getResource().getKey()); + gwfAdapter.update(groupMod); + return deprovision(sbj); + } + + @Override + protected Any<?, ?, ?> assign(final Any<?, ?, ?> sbj, final Boolean enabled) { + GroupMod groupMod = new GroupMod(); + groupMod.setKey(sbj.getKey()); + groupMod.getResourcesToAdd().add(profile.getTask().getResource().getKey()); + gwfAdapter.update(groupMod); + return provision(sbj, enabled); + } + + @Override + protected String getName(final Any<?, ?, ?> any) { + return Group.class.cast(any).getName(); + } + + @Override + protected AnyTO getAnyTO(final long key) { + try { + return groupDataBinder.getGroupTO(key); + } catch (Exception e) { + LOG.warn("Error retrieving user {}", key, e); + return null; + } + } + + @Override + protected Any<?, ?, ?> getAny(final long key) { + try { + return groupDAO.authFind(key); + } catch (Exception e) { + LOG.warn("Error retrieving group {}", key, e); + return null; + } + } + + @Override + protected ConnectorObject getRemoteObject(final String connObjectKey, final ObjectClass objectClass) { + ConnectorObject obj = null; + try { + Uid uid = new Uid(connObjectKey); + + obj = profile.getConnector().getObject( + objectClass, + uid, + profile.getConnector().getOperationOptions(Collections.<MappingItem>emptySet())); + } catch (TimeoutException toe) { + LOG.debug("Request timeout", toe); + throw toe; + } catch (RuntimeException ignore) { + LOG.debug("While resolving {}", connObjectKey, ignore); + } + + return obj; + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/97607b16/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java ---------------------------------------------------------------------- diff --cc core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java index 93afe01,0000000..600b6d0 mode 100644,000000..100644 --- a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java +++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java @@@ -1,289 -1,0 +1,291 @@@ +/* + * 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.rest.cxf.service; + +import static org.apache.syncope.core.rest.cxf.service.AbstractServiceImpl.LOG; + +import java.util.List; +import javax.ws.rs.core.Response; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.Transformer; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.common.lib.SyncopeConstants; +import org.apache.syncope.common.lib.mod.AnyMod; +import org.apache.syncope.common.lib.mod.ResourceAssociationMod; +import org.apache.syncope.common.lib.mod.StatusMod; +import org.apache.syncope.common.lib.to.AnyTO; +import org.apache.syncope.common.lib.to.BulkAction; +import org.apache.syncope.common.lib.to.BulkActionResult; +import org.apache.syncope.common.lib.to.PagedResult; +import org.apache.syncope.common.lib.to.PropagationStatus; +import org.apache.syncope.common.lib.types.ResourceAssociationActionType; +import org.apache.syncope.common.lib.types.ResourceDeassociationActionType; +import org.apache.syncope.common.lib.wrap.ResourceKey; +import org.apache.syncope.common.rest.api.CollectionWrapper; +import org.apache.syncope.common.rest.api.beans.AnyListQuery; +import org.apache.syncope.common.rest.api.beans.AnySearchQuery; +import org.apache.syncope.common.rest.api.service.AnyService; +import org.apache.syncope.core.logic.AbstractAnyLogic; +import org.apache.syncope.core.logic.UserLogic; +import org.apache.syncope.core.persistence.api.dao.search.SearchCond; + +public abstract class AbstractAnyService<TO extends AnyTO, MOD extends AnyMod> + extends AbstractServiceImpl + implements AnyService<TO, MOD> { + + protected abstract AbstractAnyLogic<TO, MOD> getAnyLogic(); + + @Override + public TO read(final Long key) { + return getAnyLogic().read(key); + } + + @Override + public PagedResult<TO> list(final AnyListQuery listQuery) { + CollectionUtils.transform(listQuery.getRealms(), new Transformer<String, String>() { + + @Override + public String transform(final String input) { + return StringUtils.prependIfMissing(input, SyncopeConstants.ROOT_REALM); + } + }); + + return buildPagedResult( + getAnyLogic().list( + listQuery.getPage(), + listQuery.getSize(), + getOrderByClauses(listQuery.getOrderBy()), - listQuery.getRealms()), ++ listQuery.getRealms(), ++ listQuery.isDetails()), + listQuery.getPage(), + listQuery.getSize(), + getAnyLogic().count(listQuery.getRealms())); + } + + @Override + public PagedResult<TO> search(final AnySearchQuery searchQuery) { + CollectionUtils.transform(searchQuery.getRealms(), new Transformer<String, String>() { + + @Override + public String transform(final String input) { + return StringUtils.prependIfMissing(input, SyncopeConstants.ROOT_REALM); + } + }); + + SearchCond cond = getSearchCond(searchQuery.getFiql()); + return buildPagedResult( + getAnyLogic().search( + cond, + searchQuery.getPage(), + searchQuery.getSize(), + getOrderByClauses(searchQuery.getOrderBy()), - searchQuery.getRealms()), ++ searchQuery.getRealms(), ++ searchQuery.isDetails()), + searchQuery.getPage(), + searchQuery.getSize(), + getAnyLogic().searchCount(cond, searchQuery.getRealms())); + } + + @Override + public Response create(final TO anyTO) { + TO created = getAnyLogic().create(anyTO); + return createResponse(created.getKey(), created); + } + + @Override + public Response update(final MOD anyMod) { + TO any = getAnyLogic().read(anyMod.getKey()); + + checkETag(any.getETagValue()); + + TO updated = getAnyLogic().update(anyMod); + return modificationResponse(updated); + } + + @Override + public Response delete(final Long key) { + TO group = getAnyLogic().read(key); + + checkETag(group.getETagValue()); + + TO deleted = getAnyLogic().delete(key); + return modificationResponse(deleted); + } + + @Override + public Response bulkDeassociation( + final Long key, final ResourceDeassociationActionType type, final List<ResourceKey> resourceNames) { + + TO any = getAnyLogic().read(key); + + checkETag(any.getETagValue()); + + TO updated; + switch (type) { + case UNLINK: + updated = getAnyLogic().unlink(key, CollectionWrapper.unwrap(resourceNames)); + break; + + case UNASSIGN: + updated = getAnyLogic().unassign(key, CollectionWrapper.unwrap(resourceNames)); + break; + + case DEPROVISION: + updated = getAnyLogic().deprovision(key, CollectionWrapper.unwrap(resourceNames)); + break; + + default: + updated = getAnyLogic().read(key); + } + + BulkActionResult result = new BulkActionResult(); + + if (type == ResourceDeassociationActionType.UNLINK) { + for (ResourceKey resourceName : resourceNames) { + result.getResults().put(resourceName.getElement(), + updated.getResources().contains(resourceName.getElement()) + ? BulkActionResult.Status.FAILURE + : BulkActionResult.Status.SUCCESS); + } + } else { + for (PropagationStatus propagationStatusTO : updated.getPropagationStatusTOs()) { + result.getResults().put(propagationStatusTO.getResource(), + BulkActionResult.Status.valueOf(propagationStatusTO.getStatus().toString())); + } + } + + return modificationResponse(result); + } + + @Override + public Response bulkAssociation( + final Long key, final ResourceAssociationActionType type, final ResourceAssociationMod associationMod) { + + TO any = getAnyLogic().read(key); + + checkETag(any.getETagValue()); + + TO updated; + switch (type) { + case LINK: + updated = getAnyLogic().link( + key, + CollectionWrapper.unwrap(associationMod.getTargetResources())); + break; + + case ASSIGN: + updated = getAnyLogic().assign( + key, + CollectionWrapper.unwrap(associationMod.getTargetResources()), + associationMod.isChangePwd(), + associationMod.getPassword()); + break; + + case PROVISION: + updated = getAnyLogic().provision( + key, + CollectionWrapper.unwrap(associationMod.getTargetResources()), + associationMod.isChangePwd(), + associationMod.getPassword()); + break; + + default: + updated = getAnyLogic().read(key); + } + + BulkActionResult result = new BulkActionResult(); + + if (type == ResourceAssociationActionType.LINK) { + for (ResourceKey resourceName : associationMod.getTargetResources()) { + result.getResults().put(resourceName.getElement(), + updated.getResources().contains(resourceName.getElement()) + ? BulkActionResult.Status.FAILURE + : BulkActionResult.Status.SUCCESS); + } + } else { + for (PropagationStatus propagationStatusTO : updated.getPropagationStatusTOs()) { + result.getResults().put(propagationStatusTO.getResource(), + BulkActionResult.Status.valueOf(propagationStatusTO.getStatus().toString())); + } + } + + return modificationResponse(result); + } + + @Override + public BulkActionResult bulk(final BulkAction bulkAction) { + AbstractAnyLogic<TO, MOD> logic = getAnyLogic(); + + BulkActionResult result = new BulkActionResult(); + + switch (bulkAction.getOperation()) { + case DELETE: + for (String key : bulkAction.getTargets()) { + try { + result.getResults().put( + String.valueOf(logic.delete(Long.valueOf(key)).getKey()), + BulkActionResult.Status.SUCCESS); + } catch (Exception e) { + LOG.error("Error performing delete for user {}", key, e); + result.getResults().put(key, BulkActionResult.Status.FAILURE); + } + } + break; + + case SUSPEND: + if (logic instanceof UserLogic) { + for (String key : bulkAction.getTargets()) { + StatusMod statusMod = new StatusMod(); + statusMod.setKey(Long.valueOf(key)); + statusMod.setType(StatusMod.ModType.SUSPEND); + try { + result.getResults().put( + String.valueOf(((UserLogic) logic).status(statusMod).getKey()), + BulkActionResult.Status.SUCCESS); + } catch (Exception e) { + LOG.error("Error performing suspend for user {}", key, e); + result.getResults().put(key, BulkActionResult.Status.FAILURE); + } + } + } + break; + + case REACTIVATE: + for (String key : bulkAction.getTargets()) { + StatusMod statusMod = new StatusMod(); + statusMod.setKey(Long.valueOf(key)); + statusMod.setType(StatusMod.ModType.REACTIVATE); + try { + result.getResults().put( + String.valueOf(((UserLogic) logic).status(statusMod).getKey()), + BulkActionResult.Status.SUCCESS); + } catch (Exception e) { + LOG.error("Error performing reactivate for user {}", key, e); + result.getResults().put(key, BulkActionResult.Status.FAILURE); + } + } + break; + + default: + } + + return result; + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/97607b16/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java ---------------------------------------------------------------------- diff --cc core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java index a594bac,0000000..9265bcb mode 100644,000000..100644 --- a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java +++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java @@@ -1,224 -1,0 +1,221 @@@ +/* + * 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.rest.cxf.service; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.EntityTag; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriInfo; +import org.apache.commons.lang3.StringUtils; +import org.apache.cxf.jaxrs.ext.MessageContext; +import org.apache.cxf.jaxrs.ext.search.SearchBean; +import org.apache.cxf.jaxrs.ext.search.SearchCondition; +import org.apache.cxf.jaxrs.ext.search.SearchContext; +import org.apache.syncope.common.lib.AbstractBaseBean; +import org.apache.syncope.common.lib.SyncopeClientException; +import org.apache.syncope.common.lib.to.PagedResult; +import org.apache.syncope.common.lib.types.ClientExceptionType; +import org.apache.syncope.common.rest.api.service.JAXRSService; +import org.apache.syncope.common.rest.api.Preference; +import org.apache.syncope.common.rest.api.RESTHeaders; +import org.apache.syncope.core.misc.search.SearchCondVisitor; +import org.apache.syncope.core.persistence.api.dao.search.OrderByClause; +import org.apache.syncope.core.persistence.api.dao.search.SearchCond; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +abstract class AbstractServiceImpl implements JAXRSService { + - /** - * Logger. - */ + protected static final Logger LOG = LoggerFactory.getLogger(AbstractServiceImpl.class); + + protected static final String OPTIONS_ALLOW = "GET,POST,OPTIONS,HEAD"; + + @Context + protected UriInfo uriInfo; + + @Context + protected MessageContext messageContext; + + @Context + protected SearchContext searchContext; + + /** + * Reads <tt>Prefer</tt> header from request and parses into a <tt>Preference</tt> instance. + * + * @return a <tt>Preference</tt> instance matching the passed <tt>Prefer</tt> header, + * or <tt>Preference.NONE</tt> if missing. + */ + protected Preference getPreference() { + return Preference.fromString(messageContext.getHttpHeaders().getHeaderString(RESTHeaders.PREFER)); + } + + /** + * Builds response to successful <tt>create</tt> request, taking into account any <tt>Prefer</tt> header. + * + * @param id identifier of the created entity + * @param entity the entity just created + * @return response to successful <tt>create</tt> request + */ + protected Response createResponse(final Object id, final Object entity) { + Response.ResponseBuilder builder = Response. + created(uriInfo.getAbsolutePathBuilder().path(String.valueOf(id)).build()). + header(RESTHeaders.RESOURCE_KEY, id); + + switch (getPreference()) { + case RETURN_NO_CONTENT: + break; + + case RETURN_CONTENT: + case NONE: + default: + builder = builder.entity(entity); + break; + + } + if (getPreference() == Preference.RETURN_CONTENT || getPreference() == Preference.RETURN_NO_CONTENT) { + builder = builder.header(RESTHeaders.PREFERENCE_APPLIED, getPreference().toString()); + } + + return builder.build(); + } + + /** + * Builds response to successful modification request, taking into account any <tt>Prefer</tt> header. + * + * @param entity the entity just modified + * @return response to successful modification request + */ + protected Response modificationResponse(final Object entity) { + Response.ResponseBuilder builder; + switch (getPreference()) { + case RETURN_NO_CONTENT: + builder = Response.noContent(); + break; + + case RETURN_CONTENT: + case NONE: + default: + builder = Response.ok(entity); + break; + } + if (getPreference() == Preference.RETURN_CONTENT || getPreference() == Preference.RETURN_NO_CONTENT) { + builder = builder.header(RESTHeaders.PREFERENCE_APPLIED, getPreference().toString()); + } + + return builder.build(); + } + + protected void checkETag(final String etag) { + Response.ResponseBuilder builder = messageContext.getRequest().evaluatePreconditions(new EntityTag(etag)); + if (builder != null) { + SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.ConcurrentModification); + sce.getElements().add("Mismatching ETag value"); + throw sce; + } + } + + protected SearchCond getSearchCond(final String fiql) { + try { + SearchCondVisitor visitor = new SearchCondVisitor(); + SearchCondition<SearchBean> sc = searchContext.getCondition(fiql, SearchBean.class); + sc.accept(visitor); + + return visitor.getQuery(); + } catch (Exception e) { + LOG.error("Invalid FIQL expression: {}", fiql, e); + + SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidSearchExpression); + sce.getElements().add(fiql); + throw sce; + } + } + + protected List<OrderByClause> getOrderByClauses(final String orderBy) { + if (StringUtils.isBlank(orderBy)) { + return Collections.<OrderByClause>emptyList(); + } + + List<OrderByClause> result = new ArrayList<>(); + + for (String clause : orderBy.split(",")) { + String[] elems = clause.split(" "); + + if (elems.length > 0 && StringUtils.isNotBlank(elems[0])) { + OrderByClause obc = new OrderByClause(); + obc.setField(elems[0].trim()); + if (elems.length > 1 && StringUtils.isNotBlank(elems[1])) { + obc.setDirection(elems[1].trim().equalsIgnoreCase(OrderByClause.Direction.ASC.name()) + ? OrderByClause.Direction.ASC : OrderByClause.Direction.DESC); + } + result.add(obc); + } + } + + return result; + } + + /** + * Builds a paged result out of a list of items and additional information. + * + * @param <T> result type + * @param list bare list of items to be returned + * @param page current page + * @param size requested size + * @param totalCount total result size (not considering pagination) + * @return paged result + */ + protected <T extends AbstractBaseBean> PagedResult<T> buildPagedResult( + final List<T> list, final int page, final int size, final int totalCount) { + + PagedResult<T> result = new PagedResult<>(); + result.getResult().addAll(list); + + result.setPage(page); + result.setSize(result.getResult().size()); + result.setTotalCount(totalCount); + + UriBuilder builder = uriInfo.getAbsolutePathBuilder(); + MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters(); + for (Map.Entry<String, List<String>> queryParam : queryParams.entrySet()) { + builder = builder.queryParam(queryParam.getKey(), queryParam.getValue().toArray()); + } + + if (result.getPage() > 1) { + result.setPrev(builder. + replaceQueryParam(PARAM_PAGE, result.getPage() - 1). + replaceQueryParam(PARAM_SIZE, size). + build()); + } + if ((result.getPage() - 1) * size + result.getSize() < totalCount) { + result.setNext(builder. + replaceQueryParam(PARAM_PAGE, result.getPage() + 1). + replaceQueryParam(PARAM_SIZE, size). + build()); + } + + return result; + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/97607b16/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceImpl.java ---------------------------------------------------------------------- diff --cc core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceImpl.java index ce6390a,0000000..859d008 mode 100644,000000..100644 --- a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceImpl.java +++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceImpl.java @@@ -1,71 -1,0 +1,72 @@@ +/* + * 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.rest.cxf.service; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.Transformer; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.common.lib.SyncopeConstants; +import org.apache.syncope.common.lib.mod.AnyObjectMod; +import org.apache.syncope.common.lib.to.AnyObjectTO; +import org.apache.syncope.common.lib.to.PagedResult; +import org.apache.syncope.common.rest.api.beans.AnyListQuery; +import org.apache.syncope.common.rest.api.service.AnyObjectService; +import org.apache.syncope.core.logic.AbstractAnyLogic; +import org.apache.syncope.core.logic.AnyObjectLogic; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class AnyObjectServiceImpl extends AbstractAnyService<AnyObjectTO, AnyObjectMod> implements AnyObjectService { + + @Autowired + private AnyObjectLogic logic; + + @Override + protected AbstractAnyLogic<AnyObjectTO, AnyObjectMod> getAnyLogic() { + return logic; + } + + @Override + public PagedResult<AnyObjectTO> list(final String type, final AnyListQuery listQuery) { + if (StringUtils.isBlank(type)) { + return super.list(listQuery); + } + + CollectionUtils.transform(listQuery.getRealms(), new Transformer<String, String>() { + + @Override + public String transform(final String input) { + return StringUtils.prependIfMissing(input, SyncopeConstants.ROOT_REALM); + } + }); + + return buildPagedResult( + logic.list( + type, + listQuery.getPage(), + listQuery.getSize(), + getOrderByClauses(listQuery.getOrderBy()), - listQuery.getRealms()), ++ listQuery.getRealms(), ++ listQuery.isDetails()), + listQuery.getPage(), + listQuery.getSize(), + getAnyLogic().count(listQuery.getRealms())); + } +}
