http://git-wip-us.apache.org/repos/asf/syncope/blob/97607b16/core/logic/src/test/java/org/apache/syncope/core/logic/NotificationTest.java ---------------------------------------------------------------------- diff --cc core/logic/src/test/java/org/apache/syncope/core/logic/NotificationTest.java index bc425dc,0000000..c5dc13c mode 100644,000000..100644 --- a/core/logic/src/test/java/org/apache/syncope/core/logic/NotificationTest.java +++ b/core/logic/src/test/java/org/apache/syncope/core/logic/NotificationTest.java @@@ -1,656 -1,0 +1,657 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.syncope.core.logic; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import com.icegreen.greenmail.util.GreenMail; +import com.icegreen.greenmail.util.ServerSetup; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.UUID; +import javax.annotation.Resource; +import javax.mail.Flags.Flag; +import javax.mail.Folder; +import javax.mail.Message; +import javax.mail.Session; +import javax.mail.Store; +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.search.GroupFiqlSearchConditionBuilder; +import org.apache.syncope.common.lib.search.UserFiqlSearchConditionBuilder; +import org.apache.syncope.common.lib.to.AttrTO; +import org.apache.syncope.common.lib.to.MembershipTO; +import org.apache.syncope.common.lib.to.NotificationTaskTO; +import org.apache.syncope.common.lib.to.GroupTO; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.lib.types.Entitlement; +import org.apache.syncope.common.lib.types.IntMappingType; +import org.apache.syncope.common.lib.types.TaskType; +import org.apache.syncope.common.lib.types.TraceLevel; +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.PlainSchemaDAO; +import org.apache.syncope.core.persistence.api.dao.TaskDAO; +import org.apache.syncope.core.persistence.api.entity.EntityFactory; +import org.apache.syncope.core.persistence.api.entity.Notification; +import org.apache.syncope.core.persistence.api.entity.conf.CPlainAttr; +import org.apache.syncope.core.persistence.api.entity.task.NotificationTask; +import org.apache.syncope.core.logic.notification.NotificationJob; +import org.apache.syncope.core.misc.security.SyncopeGrantedAuthority; +import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO; +import org.apache.syncope.core.persistence.api.entity.AnyAbout; +import org.apache.syncope.core.persistence.api.entity.conf.CPlainAttrValue; +import org.apache.syncope.core.provisioning.api.notification.NotificationManager; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; + +public class NotificationTest extends AbstractTest { + + private static final String SMTP_HOST = "localhost"; + + private static final int SMTP_PORT = 2525; + + private static final String POP3_HOST = "localhost"; + + private static final int POP3_PORT = 1110; + + private static final String MAIL_ADDRESS = "[email protected]"; + + private static final String MAIL_PASSWORD = "password"; + + private static GreenMail greenMail; + + @Resource(name = "adminUser") + private String adminUser; + + @Autowired + private NotificationDAO notificationDAO; + + @Autowired + private AnyTypeDAO anyTypeDAO; + + @Autowired + private TaskDAO taskDAO; + + @Autowired + private PlainSchemaDAO plainSchemaDAO; + + @Autowired + private ConfDAO confDAO; + + @Autowired + private UserLogic userLogic; + + @Autowired + private GroupLogic groupLogic; + + @Autowired + private TaskLogic taskLogic; + + @Autowired + private NotificationJob notificationJob; + + @Autowired + private NotificationManager notificationManager; + + @Autowired + private JavaMailSender mailSender; + + @Autowired + private EntityFactory entityFactory; + + @BeforeClass + public static void startGreenMail() { + ServerSetup[] config = new ServerSetup[2]; + config[0] = new ServerSetup(SMTP_PORT, SMTP_HOST, ServerSetup.PROTOCOL_SMTP); + config[1] = new ServerSetup(POP3_PORT, POP3_HOST, ServerSetup.PROTOCOL_POP3); + greenMail = new GreenMail(config); + greenMail.setUser(MAIL_ADDRESS, MAIL_PASSWORD); + greenMail.start(); + } + + @AfterClass + public static void stopGreenMail() { + if (greenMail != null) { + greenMail.stop(); + } + } + + private static UserTO getUniqueSampleTO(final String email) { + return getSampleTO(UUID.randomUUID().toString().substring(0, 8) + email); + } + + private static AttrTO attributeTO(final String schema, final String value) { + AttrTO attr = new AttrTO(); + attr.setSchema(schema); + attr.getValues().add(value); + return attr; + } + + private static UserTO getSampleTO(final String email) { + String uid = email; + UserTO userTO = new UserTO(); + userTO.setPassword("password123"); + userTO.setUsername(uid); + userTO.setRealm("/even/two"); + + userTO.getPlainAttrs().add(attributeTO("fullname", uid)); + userTO.getPlainAttrs().add(attributeTO("firstname", uid)); + userTO.getPlainAttrs().add(attributeTO("surname", "surname")); + userTO.getPlainAttrs().add(attributeTO("type", "a type")); + userTO.getPlainAttrs().add(attributeTO("userId", uid)); + userTO.getPlainAttrs().add(attributeTO("email", uid)); + userTO.getPlainAttrs().add(attributeTO("loginDate", new SimpleDateFormat("yyyy-MM-dd").format(new Date()))); + userTO.getDerAttrs().add(attributeTO("cn", null)); + userTO.getVirAttrs().add(attributeTO("virtualdata", "virtualvalue")); + return userTO; + } + + @Before + public void setupSecurity() { + List<GrantedAuthority> authorities = CollectionUtils.collect(Entitlement.values(), + new Transformer<String, GrantedAuthority>() { + + @Override + public GrantedAuthority transform(final String entitlement) { + return new SyncopeGrantedAuthority(entitlement, SyncopeConstants.ROOT_REALM); + } + }, new ArrayList<GrantedAuthority>()); + + UserDetails userDetails = new User(adminUser, "FAKE_PASSWORD", authorities); + Authentication authentication = new TestingAuthenticationToken(userDetails, "FAKE_PASSWORD", authorities); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + @Before + public void setupSMTP() throws Exception { + JavaMailSenderImpl sender = (JavaMailSenderImpl) mailSender; + sender.setDefaultEncoding(SyncopeConstants.DEFAULT_ENCODING); + sender.setHost(SMTP_HOST); + sender.setPort(SMTP_PORT); + } + + private boolean verifyMail(final String sender, final String subject) throws Exception { + LOG.info("Waiting for notification to be sent..."); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + } + + boolean found = false; + Session session = Session.getDefaultInstance(System.getProperties()); + Store store = session.getStore("pop3"); + store.connect(POP3_HOST, POP3_PORT, MAIL_ADDRESS, MAIL_PASSWORD); + + Folder inbox = store.getFolder("INBOX"); + assertNotNull(inbox); + inbox.open(Folder.READ_WRITE); + + Message[] messages = inbox.getMessages(); - for (int i = 0; i < messages.length; i++) { - if (sender.equals(messages[i].getFrom()[0].toString()) && subject.equals(messages[i].getSubject())) { ++ for (Message message : messages) { ++ if (sender.equals(message.getFrom()[0].toString()) && subject.equals(message.getSubject())) { + found = true; - messages[i].setFlag(Flag.DELETED, true); ++ message.setFlag(Flag.DELETED, true); + } + } + + inbox.close(true); + store.close(); + return found; + } + + @Test + public void notifyByMail() throws Exception { + // 1. create suitable notification for subsequent tests + Notification notification = entityFactory.newEntity(Notification.class); + notification.getEvents().add("[REST]:[UserLogic]:[]:[create]:[SUCCESS]"); + + AnyAbout about = entityFactory.newEntity(AnyAbout.class); + about.setNotification(notification); + notification.add(about); + about.setAnyType(anyTypeDAO.findUser()); + about.set(new UserFiqlSearchConditionBuilder().inGroups(7L).query()); + + notification.setRecipients(new UserFiqlSearchConditionBuilder().inGroups(8L).query()); + notification.setSelfAsRecipient(true); + + notification.setRecipientAttrName("email"); + notification.setRecipientAttrType(IntMappingType.UserPlainSchema); + + Random random = new Random(System.currentTimeMillis()); + String sender = "syncopetest-" + random.nextLong() + "@syncope.apache.org"; + notification.setSender(sender); + String subject = "Test notification " + random.nextLong(); + notification.setSubject(subject); + notification.setTemplate("optin"); + + Notification actual = notificationDAO.save(notification); + assertNotNull(actual); + + notificationDAO.flush(); + + // 2. create user + UserTO userTO = getSampleTO(MAIL_ADDRESS); + MembershipTO membershipTO = new MembershipTO(); + membershipTO.setRightKey(7); + userTO.getMemberships().add(membershipTO); + + userLogic.create(userTO, true); + + // 3. force Quartz job execution and verify e-mail + notificationJob.execute(null); + assertTrue(verifyMail(sender, subject)); + + // 4. get NotificationTask id and text body + Long taskId = null; + String textBody = null; + for (NotificationTask task : taskDAO.<NotificationTask>findAll(TaskType.NOTIFICATION)) { + if (sender.equals(task.getSender())) { + taskId = task.getKey(); + textBody = task.getTextBody(); + } + } + assertNotNull(taskId); + assertNotNull(textBody); + assertTrue("Notification mail text doesn't contain expected content.", + textBody.contains("Your email address is [email protected].")); + assertTrue("Notification mail text doesn't contain expected content.", + textBody.contains("Your email address inside a link: " + + "http://localhost/?email=notificationtest%40syncope.apache.org .")); + + // 5. execute Notification task and verify e-mail + taskLogic.execute(taskId, false); + assertTrue(verifyMail(sender, subject)); + } + + @Test + public void issueSYNCOPE192() throws Exception { + // 1. create suitable notification for subsequent tests + Notification notification = entityFactory.newEntity(Notification.class); + notification.getEvents().add("[REST]:[UserLogic]:[]:[create]:[SUCCESS]"); + + AnyAbout about = entityFactory.newEntity(AnyAbout.class); + about.setNotification(notification); + notification.add(about); + about.setAnyType(anyTypeDAO.findUser()); + about.set(new UserFiqlSearchConditionBuilder().inGroups(7L).query()); + + notification.setRecipients(new UserFiqlSearchConditionBuilder().inGroups(8L).query()); + notification.setSelfAsRecipient(true); + + notification.setRecipientAttrName("email"); + notification.setRecipientAttrType(IntMappingType.UserPlainSchema); + + Random random = new Random(System.currentTimeMillis()); + String sender = "syncope192-" + random.nextLong() + "@syncope.apache.org"; + notification.setSender(sender); + String subject = "Test notification " + random.nextLong(); + notification.setSubject(subject); + notification.setTemplate("optin"); + notification.setTraceLevel(TraceLevel.NONE); + + Notification actual = notificationDAO.save(notification); + assertNotNull(actual); + + // 2. create user + UserTO userTO = getSampleTO(MAIL_ADDRESS); + MembershipTO membershipTO = new MembershipTO(); + membershipTO.setRightKey(7); + userTO.getMemberships().add(membershipTO); + + userLogic.create(userTO, true); + + // 3. force Quartz job execution and verify e-mail + notificationJob.execute(null); + assertTrue(verifyMail(sender, subject)); + + // 4. get NotificationTask id + Long taskId = null; + for (NotificationTask task : taskDAO.<NotificationTask>findAll(TaskType.NOTIFICATION)) { + if (sender.equals(task.getSender())) { + taskId = task.getKey(); + } + } + assertNotNull(taskId); + + // 5. verify that last exec status was updated + NotificationTaskTO task = (NotificationTaskTO) taskLogic.read(taskId); + assertNotNull(task); + assertTrue(task.getExecutions().isEmpty()); + assertTrue(task.isExecuted()); + assertTrue(StringUtils.isNotBlank(task.getLatestExecStatus())); + } + + @Test + public void notifyByMailEmptyAbout() throws Exception { + // 1. create suitable notification for subsequent tests + Notification notification = entityFactory.newEntity(Notification.class); + notification.getEvents().add("[REST]:[UserLogic]:[]:[create]:[SUCCESS]"); + notification.setRecipients(new UserFiqlSearchConditionBuilder().inGroups(8L).query()); + notification.setSelfAsRecipient(true); + + notification.setRecipientAttrName("email"); + notification.setRecipientAttrType(IntMappingType.UserPlainSchema); + + Random random = new Random(System.currentTimeMillis()); + String sender = "syncopetest-" + random.nextLong() + "@syncope.apache.org"; + notification.setSender(sender); + String subject = "Test notification " + random.nextLong(); + notification.setSubject(subject); + notification.setTemplate("optin"); + + Notification actual = notificationDAO.save(notification); + assertNotNull(actual); + + notificationDAO.flush(); + + // 2. create user + UserTO userTO = getSampleTO(MAIL_ADDRESS); + MembershipTO membershipTO = new MembershipTO(); + membershipTO.setRightKey(7); + userTO.getMemberships().add(membershipTO); + + userLogic.create(userTO, true); + + // 3. force Quartz job execution and verify e-mail + notificationJob.execute(null); + assertTrue(verifyMail(sender, subject)); + + // 4. get NotificationTask id + Long taskId = null; + for (NotificationTask task : taskDAO.<NotificationTask>findAll(TaskType.NOTIFICATION)) { + if (sender.equals(task.getSender())) { + taskId = task.getKey(); + } + } + assertNotNull(taskId); + + // 5. execute Notification task and verify e-mail + taskLogic.execute(taskId, false); + assertTrue(verifyMail(sender, subject)); + } + + @Test + public void notifyByMailWithRetry() throws Exception { + // 1. create suitable notification for subsequent tests + Notification notification = entityFactory.newEntity(Notification.class); + notification.getEvents().add("[REST]:[UserLogic]:[]:[create]:[SUCCESS]"); + notification.setRecipients(new UserFiqlSearchConditionBuilder().inGroups(8L).query()); + notification.setSelfAsRecipient(true); + + notification.setRecipientAttrName("email"); + notification.setRecipientAttrType(IntMappingType.UserPlainSchema); + + Random random = new Random(System.currentTimeMillis()); + String sender = "syncopetest-" + random.nextLong() + "@syncope.apache.org"; + notification.setSender(sender); + String subject = "Test notification " + random.nextLong(); + notification.setSubject(subject); + notification.setTemplate("optin"); + + Notification actual = notificationDAO.save(notification); + assertNotNull(actual); + + notificationDAO.flush(); + + // 2. create user + UserTO userTO = getSampleTO(MAIL_ADDRESS); + MembershipTO membershipTO = new MembershipTO(); + membershipTO.setRightKey(7); + userTO.getMemberships().add(membershipTO); + + userLogic.create(userTO, true); + + // 3. Set number of retries + CPlainAttr maxRetries = entityFactory.newEntity(CPlainAttr.class); + maxRetries.setSchema(plainSchemaDAO.find("notification.maxRetries")); + CPlainAttrValue maxRetriesValue = entityFactory.newEntity(CPlainAttrValue.class); + maxRetries.add("5", maxRetriesValue); + confDAO.save(maxRetries); + confDAO.flush(); + + // 4. Stop mail server to force error sending mail + stopGreenMail(); + + // 5. force Quartz job execution multiple times + for (int i = 0; i < 10; i++) { + notificationJob.execute(null); + } + + // 6. get NotificationTask, count number of executions + NotificationTask foundTask = null; + for (NotificationTask task : taskDAO.<NotificationTask>findAll(TaskType.NOTIFICATION)) { + if (sender.equals(task.getSender())) { + foundTask = task; + } + } + assertNotNull(foundTask); + assertEquals(6, notificationManager.countExecutionsWithStatus(foundTask.getKey(), + NotificationJob.Status.NOT_SENT.name())); + + // 7. start mail server again + startGreenMail(); + + // 8. reset number of retries + maxRetries = entityFactory.newEntity(CPlainAttr.class); + maxRetries.setSchema(plainSchemaDAO.find("notification.maxRetries")); + maxRetriesValue = entityFactory.newEntity(CPlainAttrValue.class); + maxRetries.add("0", maxRetriesValue); + confDAO.save(maxRetries); + confDAO.flush(); + } + + @Test + public void issueSYNCOPE445() throws Exception { + // 1. create suitable notification for subsequent tests + Notification notification = entityFactory.newEntity(Notification.class); + notification.getEvents().add("[REST]:[UserLogic]:[]:[create]:[SUCCESS]"); + + AnyAbout about = entityFactory.newEntity(AnyAbout.class); + about.setNotification(notification); + notification.add(about); + about.setAnyType(anyTypeDAO.findUser()); + about.set(new UserFiqlSearchConditionBuilder().inGroups(7L).query()); + + notification.setRecipients(new UserFiqlSearchConditionBuilder().inGroups(8L).query()); + notification.setSelfAsRecipient(true); + + notification.setRecipientAttrName("email"); + notification.setRecipientAttrType(IntMappingType.UserPlainSchema); + + notification.getStaticRecipients().add("[email protected]"); + + Random random = new Random(System.currentTimeMillis()); + String sender = "syncopetest-" + random.nextLong() + "@syncope.apache.org"; + notification.setSender(sender); + String subject = "Test notification " + random.nextLong(); + notification.setSubject(subject); + notification.setTemplate("optin"); + + Notification actual = notificationDAO.save(notification); + assertNotNull(actual); + + notificationDAO.flush(); + + // 2. create user + UserTO userTO = getSampleTO(MAIL_ADDRESS); + MembershipTO membershipTO = new MembershipTO(); + membershipTO.setRightKey(7); + userTO.getMemberships().add(membershipTO); + + userLogic.create(userTO, true); + + // 3. force Quartz job execution and verify e-mail + notificationJob.execute(null); + assertTrue(verifyMail(sender, subject)); + + // 4. get NotificationTask id and text body + Long taskId = null; + String textBody = null; + Set<String> recipients = null; + for (NotificationTask task : taskDAO.<NotificationTask>findAll(TaskType.NOTIFICATION)) { + if (sender.equals(task.getSender())) { + taskId = task.getKey(); + textBody = task.getTextBody(); + recipients = task.getRecipients(); + } + } + + assertNotNull(taskId); + assertNotNull(textBody); ++ assertNotNull(recipients); + assertTrue(recipients.contains("[email protected]")); + + // 5. execute Notification task and verify e-mail + taskLogic.execute(taskId, false); + assertTrue(verifyMail(sender, subject)); + } + + @Test + public void issueSYNCOPE492() throws Exception { + // 1. create suitable disabled notification for subsequent tests + Notification notification = entityFactory.newEntity(Notification.class); + notification.getEvents().add("[REST]:[UserLogic]:[]:[create]:[SUCCESS]"); + + AnyAbout about = entityFactory.newEntity(AnyAbout.class); + about.setNotification(notification); + notification.add(about); + about.setAnyType(anyTypeDAO.findUser()); + about.set(new UserFiqlSearchConditionBuilder().inGroups(7L).query()); + + notification.setSelfAsRecipient(true); + + notification.setRecipientAttrName("email"); + notification.setRecipientAttrType(IntMappingType.UserPlainSchema); + + notification.getStaticRecipients().add("[email protected]"); + + Random random = new Random(System.currentTimeMillis()); + String sender = "syncopetest-" + random.nextLong() + "@syncope.apache.org"; + notification.setSender(sender); + String subject = "Test notification " + random.nextLong(); + notification.setSubject(subject); + notification.setTemplate("optin"); + notification.setActive(false); + + Notification actual = notificationDAO.save(notification); + assertNotNull(actual); + + notificationDAO.flush(); + + final int tasksNumberBefore = taskDAO.findAll(TaskType.NOTIFICATION).size(); + + // 2. create user + UserTO userTO = getUniqueSampleTO(MAIL_ADDRESS); + MembershipTO membershipTO = new MembershipTO(); + membershipTO.setRightKey(7); + userTO.getMemberships().add(membershipTO); + + userLogic.create(userTO, true); + + // 3. force Quartz job execution + notificationJob.execute(null); + + // 4. check if number of tasks is not incremented + assertEquals(tasksNumberBefore, taskDAO.findAll(TaskType.NOTIFICATION).size()); + } + + @Test + public void issueSYNCOPE446() throws Exception { + // 1. create suitable notification for subsequent tests + Notification notification = entityFactory.newEntity(Notification.class); + notification.getEvents().add("[REST]:[GroupLogic]:[]:[create]:[SUCCESS]"); + + AnyAbout about = entityFactory.newEntity(AnyAbout.class); + about.setNotification(notification); + notification.add(about); + about.setAnyType(anyTypeDAO.findGroup()); + about.set(new GroupFiqlSearchConditionBuilder().is("name").equalTo("group446").query()); + + notification.setSelfAsRecipient(false); + + notification.setRecipientAttrName("email"); + notification.setRecipientAttrType(IntMappingType.UserPlainSchema); + + notification.getStaticRecipients().add(MAIL_ADDRESS); + + Random random = new Random(System.currentTimeMillis()); + String sender = "syncopetest-" + random.nextLong() + "@syncope.apache.org"; + notification.setSender(sender); + String subject = "Test notification " + random.nextLong(); + notification.setSubject(subject); + notification.setTemplate("optin"); + + Notification actual = notificationDAO.save(notification); + assertNotNull(actual); + + notificationDAO.flush(); + + // 2. create group + GroupTO groupTO = new GroupTO(); + groupTO.setName("group446"); + groupTO.setRealm("/even/two"); + + GroupTO createdGroup = groupLogic.create(groupTO); + assertNotNull(createdGroup); + + // 3. force Quartz job execution and verify e-mail + notificationJob.execute(null); + assertTrue(verifyMail(sender, subject)); + + // 4. get NotificationTask id and text body + Long taskId = null; + String textBody = null; + Set<String> recipients = null; + for (NotificationTask task : taskDAO.<NotificationTask>findAll(TaskType.NOTIFICATION)) { + if (sender.equals(task.getSender())) { + taskId = task.getKey(); + textBody = task.getTextBody(); + recipients = task.getRecipients(); + } + } + + assertNotNull(taskId); + assertNotNull(textBody); + assertTrue(recipients != null && recipients.contains(MAIL_ADDRESS)); + + // 5. execute Notification task and verify e-mail + taskLogic.execute(taskId, false); + assertTrue(verifyMail(sender, subject)); + } +}
http://git-wip-us.apache.org/repos/asf/syncope/blob/97607b16/core/persistence-jpa/src/main/resources/indexes.xml ---------------------------------------------------------------------- diff --cc core/persistence-jpa/src/main/resources/indexes.xml index ae57529,0000000..ff42b84 mode 100644,000000..100644 --- a/core/persistence-jpa/src/main/resources/indexes.xml +++ b/core/persistence-jpa/src/main/resources/indexes.xml @@@ -1,45 -1,0 +1,76 @@@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- +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. +--> +<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> +<properties> + <comment>Additional indexes (in respect to JPA's)</comment> + + <entry key="UPlainAttrValue_stringvalueIndex">CREATE INDEX UAttrValue_stringvalueIndex ON UPlainAttrValue(stringvalue)</entry> + <entry key="UPlainAttrValue_datevalueIndex">CREATE INDEX UAttrValue_datevalueIndex ON UPlainAttrValue(datevalue)</entry> + <entry key="UPlainAttrValue_longvalueIndex">CREATE INDEX UAttrValue_longvalueIndex ON UPlainAttrValue(longvalue)</entry> + <entry key="UPlainAttrValue_doublevalueIndex">CREATE INDEX UAttrValue_doublevalueIndex ON UPlainAttrValue(doublevalue)</entry> + <entry key="UPlainAttrValue_booleanvalueIndex">CREATE INDEX UAttrValue_booleanvalueIndex ON UPlainAttrValue(booleanvalue)</entry> ++ + <entry key="APlainAttrValue_stringvalueIndex">CREATE INDEX AAttrValue_stringvalueIndex ON APlainAttrValue(stringvalue)</entry> + <entry key="APlainAttrValue_datevalueIndex">CREATE INDEX AAttrValue_datevalueIndex ON APlainAttrValue(datevalue)</entry> + <entry key="APlainAttrValue_longvalueIndex">CREATE INDEX AAttrValue_longvalueIndex ON APlainAttrValue(longvalue)</entry> + <entry key="APlainAttrValue_doublevalueIndex">CREATE INDEX AAttrValue_doublevalueIndex ON APlainAttrValue(doublevalue)</entry> + <entry key="APlainAttrValue_booleanvalueIndex">CREATE INDEX AAttrValue_booleanvalueIndex ON APlainAttrValue(booleanvalue)</entry> ++ + <entry key="GPlainAttrValue_stringvalueIndex">CREATE INDEX GAttrValue_stringvalueIndex ON GPlainAttrValue(stringvalue)</entry> + <entry key="GPlainAttrValue_datevalueIndex">CREATE INDEX GAttrValue_datevalueIndex ON GPlainAttrValue(datevalue)</entry> + <entry key="GPlainAttrValue_longvalueIndex">CREATE INDEX GAttrValue_longvalueIndex ON GPlainAttrValue(longvalue)</entry> + <entry key="GPlainAttrValue_doublevalueIndex">CREATE INDEX GAttrValue_doublevalueIndex ON GPlainAttrValue(doublevalue)</entry> + <entry key="GPlainAttrValue_booleanvalueIndex">CREATE INDEX GAttrValue_booleanvalueIndex ON GPlainAttrValue(booleanvalue)</entry> ++ + <entry key="CPlainAttrValue_stringvalueIndex">CREATE INDEX CAttrValue_stringvalueIndex ON CPlainAttrValue(stringvalue)</entry> + <entry key="CPlainAttrValue_datevalueIndex">CREATE INDEX CAttrValue_datevalueIndex ON CPlainAttrValue(datevalue)</entry> + <entry key="CPlainAttrValue_longvalueIndex">CREATE INDEX CAttrValue_longvalueIndex ON CPlainAttrValue(longvalue)</entry> + <entry key="CPlainAttrValue_doublevalueIndex">CREATE INDEX CAttrValue_doublevalueIndex ON CPlainAttrValue(doublevalue)</entry> + <entry key="CPlainAttrValue_booleanvalueIndex">CREATE INDEX CAttrValue_booleanvalueIndex ON CPlainAttrValue(booleanvalue)</entry> ++ ++ <entry key="UMembership_GroupIndex">CREATE INDEX UMembership_GroupIndex ON UMembership(group_id)</entry> ++ <entry key="UMembership_UserIndex">CREATE INDEX UMembership_UserIndex ON UMembership(user_id)</entry> ++ <entry key="AMembership_GroupIndex">CREATE INDEX AMembership_GroupIndex ON AMembership(group_id)</entry> ++ <entry key="AMembership_AnyObjectIndex">CREATE INDEX AMembership_AnyObjectIndex ON AMembership(anyObject_id)</entry> ++ ++ <entry key="URelationship_RightIndex">CREATE INDEX URelationship_RightIndex ON URelationship(anyObject_id)</entry> ++ <entry key="URelationship_LeftIndex">CREATE INDEX URelationship_LeftIndex ON URelationship(user_id)</entry> ++ <entry key="ARelationship_RightIndex">CREATE INDEX ARelationship_RightIndex ON ARelationship(right_anyObject_id)</entry> ++ <entry key="ARelationship_AnyObjectIndex">CREATE INDEX ARelationship_AnyObjectIndex ON ARelationship(left_anyObject_id)</entry> ++ ++ <entry key="UPlainAttrValue_attributeIdIndex">CREATE INDEX UPlainAttrValue_attributeIdIndex on UPlainAttrValue(attribute_id)</entry> ++ <entry key="GPlainAttrValue_attributeIdIndex">CREATE INDEX GPlainAttrValue_attributeIdIndex on GPlainAttrValue(attribute_id)</entry> ++ <entry key="APlainAttrValue_attributeIdIndex">CREATE INDEX APlainAttrValue_attributeIdIndex on APlainAttrValue(attribute_id)</entry> ++ <entry key="CPlainAttrValue_attributeIdIndex">CREATE INDEX CPlainAttrValue_attributeIdIndex on CPlainAttrValue(attribute_id)</entry> ++ ++ <entry key="UPlainAttr_owner_id_index">CREATE INDEX UPlainAttr_owner_id_index on UPlainAttr(owner_id)</entry> ++ <entry key="GPlainAttr_owner_id_index">CREATE INDEX GPlainAttr_owner_id_index on GPlainAttr(owner_id)</entry> ++ <entry key="APlainAttr_owner_id_index">CREATE INDEX APlainAttr_owner_id_index on APlainAttr(owner_id)</entry> ++ ++ <entry key="UDerAttr_owner_id_index">CREATE INDEX UDerAttr_owner_id_index on UDerAttr(owner_id)</entry> ++ <entry key="GDerAttr_owner_id_index">CREATE INDEX GDerAttr_owner_id_index on GDerAttr(owner_id)</entry> ++ <entry key="ADerAttr_owner_id_index">CREATE INDEX ADerAttr_owner_id_index on ADerAttr(owner_id)</entry> ++ ++ <entry key="UVirAttr_owner_id_index">CREATE INDEX UVirAttr_owner_id_index on UVirAttr(owner_id)</entry> ++ <entry key="GVirAttr_owner_id_index">CREATE INDEX GVirAttr_owner_id_index on GVirAttr(owner_id)</entry> ++ <entry key="AVirAttr_owner_id_index">CREATE INDEX AVirAttr_owner_id_index on AVirAttr(owner_id)</entry> ++ + <entry key="Task_executedIndex">CREATE INDEX Task_executedIndex ON Task(executed)</entry> +</properties> http://git-wip-us.apache.org/repos/asf/syncope/blob/97607b16/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AnyObjectDataBinder.java ---------------------------------------------------------------------- diff --cc core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AnyObjectDataBinder.java index b503365,0000000..597b639 mode 100644,000000..100644 --- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AnyObjectDataBinder.java +++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AnyObjectDataBinder.java @@@ -1,35 -1,0 +1,35 @@@ +/* + * 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.api.data; + +import org.apache.syncope.common.lib.mod.AnyObjectMod; +import org.apache.syncope.common.lib.to.AnyObjectTO; +import org.apache.syncope.common.lib.types.PropagationByResource; +import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; + +public interface AnyObjectDataBinder { + + AnyObjectTO getAnyObjectTO(Long key); + - AnyObjectTO getAnyObjectTO(AnyObject anyObject); ++ AnyObjectTO getAnyObjectTO(AnyObject anyObject, boolean details); + + void create(AnyObject anyObject, AnyObjectTO anyObjectTO); + + PropagationByResource update(AnyObject toBeUpdated, AnyObjectMod anyObjectMod); +} http://git-wip-us.apache.org/repos/asf/syncope/blob/97607b16/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/GroupDataBinder.java ---------------------------------------------------------------------- diff --cc core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/GroupDataBinder.java index 95df28a,0000000..f472aef mode 100644,000000..100644 --- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/GroupDataBinder.java +++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/GroupDataBinder.java @@@ -1,36 -1,0 +1,36 @@@ +/* + * 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.api.data; + +import org.apache.syncope.common.lib.mod.GroupMod; +import org.apache.syncope.common.lib.to.GroupTO; +import org.apache.syncope.common.lib.types.PropagationByResource; +import org.apache.syncope.core.persistence.api.entity.group.Group; + +public interface GroupDataBinder { + + GroupTO getGroupTO(Long key); + - GroupTO getGroupTO(Group group); ++ GroupTO getGroupTO(Group group, boolean details); + + Group create(Group group, GroupTO groupTO); + + PropagationByResource update(Group group, GroupMod groupMod); + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/97607b16/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/UserDataBinder.java ---------------------------------------------------------------------- diff --cc core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/UserDataBinder.java index 942c212,0000000..fbfe084 mode 100644,000000..100644 --- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/UserDataBinder.java +++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/UserDataBinder.java @@@ -1,51 -1,0 +1,51 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.syncope.core.provisioning.api.data; + +import org.apache.syncope.common.lib.mod.UserMod; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.lib.types.PropagationByResource; +import org.apache.syncope.core.persistence.api.entity.user.User; + +public interface UserDataBinder { + + UserTO getAuthenticatedUserTO(); + + UserTO getUserTO(String username); + + UserTO getUserTO(Long key); + - UserTO getUserTO(User user); ++ UserTO getUserTO(User user, boolean details); + + void create(User user, UserTO userTO, boolean storePassword); + + /** + * Update user, given UserMod. + * + * @param toBeUpdated user to be updated + * @param userMod bean containing update request + * @return updated user + propagation by resource + * @see PropagationByResource + */ + PropagationByResource update(User toBeUpdated, UserMod userMod); + + boolean verifyPassword(String username, String password); + + boolean verifyPassword(User user, String password); +} http://git-wip-us.apache.org/repos/asf/syncope/blob/97607b16/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java ---------------------------------------------------------------------- diff --cc core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java index 237792b,0000000..258e713 mode 100644,000000..100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java @@@ -1,292 -1,0 +1,297 @@@ +/* + * 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 static org.apache.syncope.core.provisioning.java.data.AbstractAnyDataBinder.LOG; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.Transformer; +import org.apache.syncope.common.lib.SyncopeClientCompositeException; +import org.apache.syncope.common.lib.SyncopeClientException; +import org.apache.syncope.common.lib.mod.AnyObjectMod; +import org.apache.syncope.common.lib.to.AnyObjectTO; +import org.apache.syncope.common.lib.to.MembershipTO; +import org.apache.syncope.common.lib.to.RelationshipTO; +import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.syncope.common.lib.types.ClientExceptionType; +import org.apache.syncope.common.lib.types.PropagationByResource; +import org.apache.syncope.common.lib.types.ResourceOperation; +import org.apache.syncope.core.misc.spring.BeanUtils; +import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO; +import org.apache.syncope.core.persistence.api.entity.AnyType; +import org.apache.syncope.core.persistence.api.entity.anyobject.AMembership; +import org.apache.syncope.core.persistence.api.entity.anyobject.ARelationship; +import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject; +import org.apache.syncope.core.persistence.api.entity.group.Group; +import org.apache.syncope.core.provisioning.api.data.AnyObjectDataBinder; +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 AnyObjectDataBinderImpl extends AbstractAnyDataBinder implements AnyObjectDataBinder { + + private static final String[] IGNORE_PROPERTIES = { + "type", "realm", "auxClasses", "relationships", "memberships", "dynGroups", + "plainAttrs", "derAttrs", "virAttrs", "resources" + }; + + @Autowired + private AnyTypeDAO anyTypeDAO; + + @Transactional(readOnly = true) + @Override + public AnyObjectTO getAnyObjectTO(final Long key) { - return getAnyObjectTO(anyObjectDAO.authFind(key)); ++ return getAnyObjectTO(anyObjectDAO.authFind(key), true); + } + + @Override - public AnyObjectTO getAnyObjectTO(final AnyObject anyObject) { ++ public AnyObjectTO getAnyObjectTO(final AnyObject anyObject, final boolean details) { + AnyObjectTO anyObjectTO = new AnyObjectTO(); + anyObjectTO.setType(anyObject.getType().getKey()); + + BeanUtils.copyProperties(anyObject, anyObjectTO, IGNORE_PROPERTIES); + - virAttrHander.retrieveVirAttrValues(anyObject); ++ if (details) { ++ virAttrHander.retrieveVirAttrValues(anyObject); ++ } ++ + fillTO(anyObjectTO, anyObject.getRealm().getFullPath(), anyObject.getAuxClasses(), + anyObject.getPlainAttrs(), anyObject.getDerAttrs(), anyObject.getVirAttrs(), + anyObjectDAO.findAllResources(anyObject)); + - // relationships - CollectionUtils.collect(anyObject.getRelationships(), new Transformer<ARelationship, RelationshipTO>() { ++ if (details) { ++ // relationships ++ CollectionUtils.collect(anyObject.getRelationships(), new Transformer<ARelationship, RelationshipTO>() { + - @Override - public RelationshipTO transform(final ARelationship relationship) { - return AnyObjectDataBinderImpl.this.getRelationshipTO(relationship); - } ++ @Override ++ public RelationshipTO transform(final ARelationship relationship) { ++ return AnyObjectDataBinderImpl.this.getRelationshipTO(relationship); ++ } + - }, anyObjectTO.getRelationships()); ++ }, anyObjectTO.getRelationships()); + - // memberships - CollectionUtils.collect(anyObject.getMemberships(), new Transformer<AMembership, MembershipTO>() { ++ // memberships ++ CollectionUtils.collect(anyObject.getMemberships(), new Transformer<AMembership, MembershipTO>() { + - @Override - public MembershipTO transform(final AMembership membership) { - return AnyObjectDataBinderImpl.this.getMembershipTO(membership); - } - }, anyObjectTO.getMemberships()); ++ @Override ++ public MembershipTO transform(final AMembership membership) { ++ return AnyObjectDataBinderImpl.this.getMembershipTO(membership); ++ } ++ }, anyObjectTO.getMemberships()); + - // dynamic memberships - CollectionUtils.collect(anyObjectDAO.findDynGroupMemberships(anyObject), new Transformer<Group, Long>() { ++ // dynamic memberships ++ CollectionUtils.collect(anyObjectDAO.findDynGroupMemberships(anyObject), new Transformer<Group, Long>() { + - @Override - public Long transform(final Group group) { - return group.getKey(); - } - }, anyObjectTO.getDynGroups()); ++ @Override ++ public Long transform(final Group group) { ++ return group.getKey(); ++ } ++ }, anyObjectTO.getDynGroups()); ++ } + + return anyObjectTO; + } + + @Override + public void create(final AnyObject anyObject, final AnyObjectTO anyObjectTO) { + AnyType type = anyTypeDAO.find(anyObjectTO.getType()); + if (type == null) { + SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidAnyType); + sce.getElements().add(anyObjectTO.getType()); + throw sce; + } + anyObject.setType(type); + + SyncopeClientCompositeException scce = SyncopeClientException.buildComposite(); + + // relationships + for (RelationshipTO relationshipTO : anyObjectTO.getRelationships()) { + AnyObject otherEnd = anyObjectDAO.find(relationshipTO.getRightKey()); + + if (otherEnd == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("Ignoring invalid anyObject " + relationshipTO.getRightKey()); + } + } else { + ARelationship relationship = null; + if (anyObject.getKey() != null) { + relationship = anyObject.getRelationship(otherEnd.getKey()); + } + if (relationship == null) { + relationship = entityFactory.newEntity(ARelationship.class); + relationship.setRightEnd(otherEnd); + relationship.setLeftEnd(anyObject); + + anyObject.add(relationship); + } + } + } + + // memberships + for (MembershipTO membershipTO : anyObjectTO.getMemberships()) { + Group group = groupDAO.find(membershipTO.getRightKey()); + + if (group == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("Ignoring invalid group " + membershipTO.getGroupName()); + } + } else { + AMembership membership = null; + if (anyObject.getKey() != null) { + membership = anyObject.getMembership(group.getKey()); + } + if (membership == null) { + membership = entityFactory.newEntity(AMembership.class); + membership.setRightEnd(group); + membership.setLeftEnd(anyObject); + + anyObject.add(membership); + } + } + } + + // realm, attributes, derived attributes, virtual attributes and resources + fill(anyObject, anyObjectTO, anyUtilsFactory.getInstance(AnyTypeKind.ANY_OBJECT), scce); + } + + @Override + public PropagationByResource update(final AnyObject toBeUpdated, final AnyObjectMod anyObjectMod) { + // Re-merge any pending change from workflow tasks + final AnyObject anyObject = anyObjectDAO.save(toBeUpdated); + + PropagationByResource propByRes = new PropagationByResource(); + + SyncopeClientCompositeException scce = SyncopeClientException.buildComposite(); + + Collection<String> currentResources = anyObjectDAO.findAllResourceNames(anyObject); + + // fetch connObjectKeys before update + Map<String, String> oldConnObjectKeys = getConnObjectKeys(anyObject); + + // attributes, derived attributes, virtual attributes and resources + propByRes.merge(fill(anyObject, anyObjectMod, anyUtilsFactory.getInstance(AnyTypeKind.ANY_OBJECT), scce)); + + Set<String> toBeDeprovisioned = new HashSet<>(); + Set<String> toBeProvisioned = new HashSet<>(); + + // relationships to be removed + for (Long anyObjectKey : anyObjectMod.getRelationshipsToRemove()) { + LOG.debug("Relationship to be removed for any object {}", anyObjectKey); + + ARelationship relationship = anyObject.getRelationship(anyObjectKey); + if (relationship == null) { + LOG.warn("Invalid anyObject key specified for relationship to be removed: {}", anyObjectKey); + } else { + if (!anyObjectMod.getRelationshipsToAdd().contains(anyObjectKey)) { + anyObject.remove(relationship); + toBeDeprovisioned.addAll(relationship.getRightEnd().getResourceNames()); + } + } + } + + // relationships to be added + for (Long anyObjectKey : anyObjectMod.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 { + ARelationship relationship = anyObject.getRelationship(otherEnd.getKey()); + if (relationship == null) { + relationship = entityFactory.newEntity(ARelationship.class); + relationship.setRightEnd(otherEnd); + relationship.setLeftEnd(anyObject); + + anyObject.add(relationship); + + toBeProvisioned.addAll(otherEnd.getResourceNames()); + } + } + } + + // memberships to be removed + for (Long groupKey : anyObjectMod.getMembershipsToRemove()) { + LOG.debug("Membership to be removed for group {}", groupKey); + + AMembership membership = anyObject.getMembership(groupKey); + if (membership == null) { + LOG.warn("Invalid group key specified for membership to be removed: {}", groupKey); + } else { + if (!anyObjectMod.getMembershipsToAdd().contains(groupKey)) { + anyObject.remove(membership); + toBeDeprovisioned.addAll(membership.getRightEnd().getResourceNames()); + } + } + } + + // memberships to be added + for (Long groupKey : anyObjectMod.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 { + AMembership membership = anyObject.getMembership(group.getKey()); + if (membership == null) { + membership = entityFactory.newEntity(AMembership.class); + membership.setRightEnd(group); + membership.setLeftEnd(anyObject); + + anyObject.add(membership); + + toBeProvisioned.addAll(group.getResourceNames()); + } + } + } + + propByRes.addAll(ResourceOperation.DELETE, toBeDeprovisioned); + propByRes.addAll(ResourceOperation.UPDATE, toBeProvisioned); + + /** + * In case of new memberships all the current resources have to be updated in order to propagate new group and + * membership 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(anyObject); + 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; + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/97607b16/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java ---------------------------------------------------------------------- diff --cc core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java index a393fda,0000000..b40cc88 mode 100644,000000..100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java @@@ -1,229 -1,0 +1,231 @@@ +/* + * 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.Map; +import org.apache.syncope.common.lib.SyncopeClientCompositeException; +import org.apache.syncope.common.lib.SyncopeClientException; +import org.apache.syncope.common.lib.mod.GroupMod; +import org.apache.syncope.common.lib.to.GroupTO; +import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.syncope.common.lib.types.ClientExceptionType; +import org.apache.syncope.common.lib.types.ResourceOperation; +import org.apache.syncope.core.persistence.api.entity.group.Group; +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.GroupDataBinder; +import org.apache.syncope.core.misc.search.SearchCondConverter; +import org.apache.syncope.core.persistence.api.dao.search.SearchCond; +import org.apache.syncope.core.persistence.api.entity.DynGroupMembership; +import org.apache.syncope.core.persistence.api.entity.anyobject.ADynGroupMembership; +import org.apache.syncope.core.persistence.api.entity.user.UDynGroupMembership; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@Transactional(rollbackFor = { Throwable.class }) +public class GroupDataBinderImpl extends AbstractAnyDataBinder implements GroupDataBinder { + + private void setDynMembership(final Group group, final AnyTypeKind anyTypeKind, final String dynMembershipFIQL) { + SearchCond dynMembershipCond = SearchCondConverter.convert(dynMembershipFIQL); + if (!dynMembershipCond.isValid()) { + SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidSearchExpression); + sce.getElements().add(dynMembershipFIQL); + throw sce; + } + + DynGroupMembership<?> dynMembership; + if (anyTypeKind == AnyTypeKind.ANY_OBJECT && group.getADynMembership() == null) { + dynMembership = entityFactory.newEntity(ADynGroupMembership.class); + dynMembership.setGroup(group); + group.setADynMembership((ADynGroupMembership) dynMembership); + } else if (anyTypeKind == AnyTypeKind.USER && group.getUDynMembership() == null) { + dynMembership = entityFactory.newEntity(UDynGroupMembership.class); + dynMembership.setGroup(group); + group.setUDynMembership((UDynGroupMembership) dynMembership); + } else { + dynMembership = anyTypeKind == AnyTypeKind.ANY_OBJECT + ? group.getADynMembership() + : group.getUDynMembership(); + } + dynMembership.setFIQLCond(dynMembershipFIQL); + } + + @Override + public Group create(final Group group, final GroupTO groupTO) { + SyncopeClientCompositeException scce = SyncopeClientException.buildComposite(); + + // name + SyncopeClientException invalidGroups = SyncopeClientException.build(ClientExceptionType.InvalidGroup); + if (groupTO.getName() == null) { + LOG.error("No name specified for this group"); + + invalidGroups.getElements().add("No name specified for this group"); + } else { + group.setName(groupTO.getName()); + } + + // attributes, derived attributes, virtual attributes and resources + fill(group, groupTO, anyUtilsFactory.getInstance(AnyTypeKind.GROUP), scce); + + // owner + if (groupTO.getUserOwner() != null) { + User owner = userDAO.find(groupTO.getUserOwner()); + if (owner == null) { + LOG.warn("Ignoring invalid user specified as owner: {}", groupTO.getUserOwner()); + } else { + group.setUserOwner(owner); + } + } + if (groupTO.getGroupOwner() != null) { + Group owner = groupDAO.find(groupTO.getGroupOwner()); + if (owner == null) { + LOG.warn("Ignoring invalid group specified as owner: {}", groupTO.getGroupOwner()); + } else { + group.setGroupOwner(owner); + } + } + + if (groupTO.getADynMembershipCond() != null) { + setDynMembership(group, AnyTypeKind.ANY_OBJECT, groupTO.getADynMembershipCond()); + } + if (groupTO.getUDynMembershipCond() != null) { + setDynMembership(group, AnyTypeKind.USER, groupTO.getUDynMembershipCond()); + } + + return group; + } + + @Override + public PropagationByResource update(final Group toBeUpdated, final GroupMod groupMod) { + // Re-merge any pending change from workflow tasks + Group group = groupDAO.save(toBeUpdated); + + PropagationByResource propByRes = new PropagationByResource(); + + SyncopeClientCompositeException scce = SyncopeClientException.buildComposite(); + + // fetch connObjectKeys before update + Map<String, String> oldConnObjectKeys = getConnObjectKeys(group); + + // realm + setRealm(group, groupMod); + // name + if (groupMod.getName() != null && !groupMod.getName().equals(group.getName())) { + propByRes.addAll(ResourceOperation.UPDATE, group.getResourceNames()); + + group.setName(groupMod.getName()); + } + + // owner + if (groupMod.getUserOwner() != null) { + group.setUserOwner(groupMod.getUserOwner().getKey() == null + ? null + : userDAO.find(groupMod.getUserOwner().getKey())); + } + if (groupMod.getGroupOwner() != null) { + group.setGroupOwner(groupMod.getGroupOwner().getKey() == null + ? null + : groupDAO.find(groupMod.getGroupOwner().getKey())); + } + + // attributes, derived attributes, virtual attributes and resources + propByRes.merge(fill(group, groupMod, anyUtilsFactory.getInstance(AnyTypeKind.GROUP), scce)); + + // check if some connObjectKey was changed by the update above + Map<String, String> newConnObjectKeys = getConnObjectKeys(group); + for (Map.Entry<String, String> entry : oldConnObjectKeys.entrySet()) { + if (newConnObjectKeys.containsKey(entry.getKey()) + && !entry.getValue().equals(newConnObjectKeys.get(entry.getKey()))) { + + propByRes.addOldConnObjectKey(entry.getKey(), entry.getValue()); + propByRes.add(ResourceOperation.UPDATE, entry.getKey()); + } + } + + // dynamic membership + if (group.getADynMembership() != null && groupMod.getADynMembershipCond() == null) { + group.setADynMembership(null); + } else if (group.getADynMembership() == null && groupMod.getADynMembershipCond() != null) { + setDynMembership(group, AnyTypeKind.ANY_OBJECT, groupMod.getADynMembershipCond()); + } else if (group.getADynMembership() != null && groupMod.getADynMembershipCond() != null + && !group.getADynMembership().getFIQLCond().equals(groupMod.getADynMembershipCond())) { + + group.getADynMembership().getMembers().clear(); + setDynMembership(group, AnyTypeKind.ANY_OBJECT, groupMod.getADynMembershipCond()); + } + if (group.getUDynMembership() != null && groupMod.getUDynMembershipCond() == null) { + group.setUDynMembership(null); + } else if (group.getUDynMembership() == null && groupMod.getUDynMembershipCond() != null) { + setDynMembership(group, AnyTypeKind.USER, groupMod.getUDynMembershipCond()); + } else if (group.getUDynMembership() != null && groupMod.getUDynMembershipCond() != null + && !group.getUDynMembership().getFIQLCond().equals(groupMod.getUDynMembershipCond())) { + + group.getUDynMembership().getMembers().clear(); + setDynMembership(group, AnyTypeKind.USER, groupMod.getUDynMembershipCond()); + } + + return propByRes; + } + + @SuppressWarnings("unchecked") + @Transactional(readOnly = true) + @Override - public GroupTO getGroupTO(final Group group) { - virAttrHander.retrieveVirAttrValues(group); - ++ public GroupTO getGroupTO(final Group group, final boolean details) { + GroupTO groupTO = new GroupTO(); + + // set sys info + groupTO.setCreator(group.getCreator()); + groupTO.setCreationDate(group.getCreationDate()); + groupTO.setLastModifier(group.getLastModifier()); + groupTO.setLastChangeDate(group.getLastChangeDate()); + + groupTO.setKey(group.getKey()); + groupTO.setName(group.getName()); + + if (group.getUserOwner() != null) { + groupTO.setUserOwner(group.getUserOwner().getKey()); + } + if (group.getGroupOwner() != null) { + groupTO.setGroupOwner(group.getGroupOwner().getKey()); + } + ++ if (details) { ++ virAttrHander.retrieveVirAttrValues(group); ++ } ++ + fillTO(groupTO, group.getRealm().getFullPath(), group.getAuxClasses(), + group.getPlainAttrs(), group.getDerAttrs(), group.getVirAttrs(), group.getResources()); + + if (group.getADynMembership() != null) { + groupTO.setADynMembershipCond(group.getADynMembership().getFIQLCond()); + } + if (group.getUDynMembership() != null) { + groupTO.setUDynMembershipCond(group.getUDynMembership().getFIQLCond()); + } + + return groupTO; + } + + @Transactional(readOnly = true) + @Override + public GroupTO getGroupTO(final Long key) { - return getGroupTO(groupDAO.authFind(key)); ++ return getGroupTO(groupDAO.authFind(key), true); + } +}
