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);
 +    }
 +}

Reply via email to