org.sakaiproject.signup.logic.SignupEmailFacadeImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.sakaiproject.signup.logic.SignupEmailFacadeImpl.java

Source

/*
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* The Apereo Foundation licenses this file to you under the Educational 
* Community 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://opensource.org/licenses/ecl2.txt
* 
* 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.sakaiproject.signup.logic;

import java.io.File;
import java.util.*;

import lombok.Getter;
import lombok.Setter;

import net.fortuna.ical4j.model.component.VEvent;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.impl.LogFactoryImpl;
import org.apache.commons.validator.EmailValidator;
import org.sakaiproject.email.api.AddressValidationException;
import org.sakaiproject.email.api.Attachment;
import org.sakaiproject.email.api.EmailAddress;
import org.sakaiproject.email.api.EmailMessage;
import org.sakaiproject.email.api.EmailService;
import org.sakaiproject.email.api.NoRecipientsException;
import org.sakaiproject.signup.logic.messages.AddAttendeeEmail;
import org.sakaiproject.signup.logic.messages.AttendeeCancellationEmail;
import org.sakaiproject.signup.logic.messages.AttendeeCancellationOwnEmail;
import org.sakaiproject.signup.logic.messages.AttendeeModifiedCommentEmail;
import org.sakaiproject.signup.logic.messages.AttendeeSignupEmail;
import org.sakaiproject.signup.logic.messages.AttendeeSignupOwnEmail;
import org.sakaiproject.signup.logic.messages.CancelMeetingEmail;
import org.sakaiproject.signup.logic.messages.CancellationEmail;
import org.sakaiproject.signup.logic.messages.EmailDeliverer;
import org.sakaiproject.signup.logic.messages.ModifyMeetingEmail;
import org.sakaiproject.signup.logic.messages.MoveAttendeeEmail;
import org.sakaiproject.signup.logic.messages.NewMeetingEmail;
import org.sakaiproject.signup.logic.messages.OrganizerPreAssignEmail;
import org.sakaiproject.signup.logic.messages.PromoteAttendeeEmail;
import org.sakaiproject.signup.logic.messages.SignupEmailNotification;
import org.sakaiproject.signup.logic.messages.SignupEventTrackingInfo;
import org.sakaiproject.signup.logic.messages.SwapAttendeeEmail;
import org.sakaiproject.signup.model.SignupAttendee;
import org.sakaiproject.signup.model.SignupMeeting;
import org.sakaiproject.signup.model.SignupTimeslot;
import org.sakaiproject.user.api.User;
import org.sakaiproject.user.api.UserDirectoryService;
import org.sakaiproject.user.api.UserNotDefinedException;

import javax.activation.DataSource;
import javax.activation.FileDataSource;

/**
 * <P>
 * This is an implementation of SignupEmailFacade interface, which provided
 * methods for Signup tool to send emails out via emailService
 * </P>
 */
public class SignupEmailFacadeImpl implements SignupEmailFacade {

    @Getter
    @Setter
    private EmailService emailService;

    @Getter
    @Setter
    private UserDirectoryService userDirectoryService;

    @Getter
    @Setter
    private SakaiFacade sakaiFacade;

    @Setter
    private SignupCalendarHelper calendarHelper;

    private Log logger = LogFactoryImpl.getLog(getClass());

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public void sendEmailAllUsers(SignupMeeting meeting, String messageType) throws Exception {
        if (messageType.equals(SIGNUP_NEW_MEETING) || messageType.equals(SIGNUP_MEETING_MODIFIED)
                || messageType.equals(SIGNUP_CANCEL_MEETING)) {
            sendEmailToAllUsers(meeting, messageType);
            return;
        }
    }

    /**
     * {@inheritDoc}
     */
    public void sendEmailToOrganizer(SignupEventTrackingInfo signupEventTrackingInfo) throws Exception {

        boolean isException = false;

        //generate VEvents
        generateVEvents(signupEventTrackingInfo.getMeeting());

        List<SignupTrackingItem> sigupTList = signupEventTrackingInfo.getAttendeeTransferInfos();
        for (SignupTrackingItem item : sigupTList) {
            if (item.getMessageType().equals(SIGNUP_ATTENDEE_SIGNUP)) {
                //User creator = null;
                User participant = null;
                try {
                    //creator = userDirectoryService.getUser(signupEventTrackingInfo.getMeeting().getCreatorUserId())
                    List<User> OwnerAndCoordinators = getMeetingOwnerAndCoordinators(
                            signupEventTrackingInfo.getMeeting());
                    if (OwnerAndCoordinators.isEmpty()) {
                        throw new Exception(
                                "No Organizer/Coordinator is not found and Email is not sent away for oraginzer");
                    }

                    participant = userDirectoryService.getUser(item.getAttendee().getAttendeeUserId());
                    for (User organizer : OwnerAndCoordinators) {
                        SignupEmailNotification email = new AttendeeSignupEmail(organizer, participant,
                                signupEventTrackingInfo.getMeeting(), item.getAddToTimeslot(), this.sakaiFacade);

                        sendEmail(organizer, email);
                    }
                    /*SignupEmailNotification email = new AttendeeSignupEmail(creator, participant,
                          signupEventTrackingInfo.getMeeting(), item.getAddToTimeslot(), this.sakaiFacade);
                        
                    sendEmail(creator, email);*/
                } catch (UserNotDefinedException e) {
                    isException = true;
                }
            }
        }

        if (isException)
            throw new Exception("Email may not be sent out due to error.");

        return;

    }

    private List<User> getMeetingOwnerAndCoordinators(SignupMeeting meeting) {
        Set<User> organizerCoordinators = new LinkedHashSet<User>();
        try {
            User creator = userDirectoryService.getUser(meeting.getCreatorUserId());
            organizerCoordinators.add(creator);
        } catch (UserNotDefinedException e) {
            logger.warn("User is not found and Email is not sent away for oraginzer userId:"
                    + meeting.getCreatorUserId());
        }

        List<String> coordinatorIds = meeting.getCoordinatorIdsList();
        for (String cId : coordinatorIds) {
            try {
                User coUser = userDirectoryService.getUser(cId);
                organizerCoordinators.add(coUser);
            } catch (UserNotDefinedException e) {
                logger.warn("User is not found and Email is not sent away for coordinator userId:" + cId);
            }
        }

        return new ArrayList<User>(organizerCoordinators);
    }

    /**
     * {@inheritDoc}
     */
    public void sendCancellationEmail(SignupEventTrackingInfo signupEventTrackingInfo) throws Exception {

        //generate VEvents
        generateVEvents(signupEventTrackingInfo.getMeeting());

        /* send email to everyone who get promoted during the process */
        // TODO Do we need to send info about promoted guys to organizer?
        List<SignupTrackingItem> sigupTList = signupEventTrackingInfo.getAttendeeTransferInfos();
        for (SignupTrackingItem item : sigupTList) {
            /* no email send to the cancellation guy */
            if (item.getMessageType().equals(SIGNUP_ATTENDEE_PROMOTE)) {
                User attendee = null;
                try {
                    attendee = userDirectoryService.getUser(item.getAttendee().getAttendeeUserId());
                    PromoteAttendeeEmail email = new PromoteAttendeeEmail(attendee, item,
                            signupEventTrackingInfo.getMeeting(), this.sakaiFacade);
                    sendEmail(attendee, email);
                } catch (UserNotDefinedException e) {
                    throw new Exception("User is not found and Email is not sent away for attendee userId: "
                            + item.getAttendee().getAttendeeUserId());
                }
            }

        }

        //User organizer = null;
        User initiator = null;
        try {
            //organizer = userDirectoryService.getUser(signupEventTrackingInfo.getMeeting().getCreatorUserId())
            List<User> OwnerAndCoordinators = getMeetingOwnerAndCoordinators(signupEventTrackingInfo.getMeeting());
            if (OwnerAndCoordinators.isEmpty()) {
                throw new Exception(
                        "No Organizer/Coordinator is not found and Email is not sent away for oraginzer");
            }

            initiator = userDirectoryService.getUser(
                    signupEventTrackingInfo.getInitiatorAllocationInfo().getAttendee().getAttendeeUserId());

            /* send one email to organizer about the update status */
            if (signupEventTrackingInfo.getMeeting().isReceiveEmailByOwner()) {
                for (User organizer : OwnerAndCoordinators) {
                    try {
                        AttendeeCancellationEmail email = new AttendeeCancellationEmail(organizer, initiator,
                                sigupTList, signupEventTrackingInfo.getMeeting(), this.sakaiFacade);
                        sendEmail(organizer, email);
                    } catch (Exception e) {
                        //do nothing: avoid one wrong email address for one user to break all things
                    }
                }
            }

            //also send an email to the attendee
            AttendeeCancellationOwnEmail attendeeEmail = new AttendeeCancellationOwnEmail(initiator, sigupTList,
                    signupEventTrackingInfo.getMeeting(), this.sakaiFacade);
            sendEmail(initiator, attendeeEmail);

        } catch (UserNotDefinedException e) {
            throw new Exception(
                    "User is not found for userId: " + signupEventTrackingInfo.getMeeting().getCreatorUserId());
        }

    }

    /**
     * {@inheritDoc}
     */
    public void sendUpdateCommentEmail(SignupEventTrackingInfo signupEventTrackingInfo) throws Exception {
        try {
            User currentModifier = userDirectoryService.getUser(sakaiFacade.getCurrentUserId());
            boolean isOrganizer = signupEventTrackingInfo.getMeeting().getPermission().isUpdate();
            List<String> coOrganizers = signupEventTrackingInfo.getMeeting().getCoordinatorIdsList();
            User selectedAttendee = userDirectoryService
                    .getUser(signupEventTrackingInfo.getAttendeeComment().getAttendeeId());
            String emailReturnSiteId = sakaiFacade.getCurrentLocationId();
            AttendeeModifiedCommentEmail email = new AttendeeModifiedCommentEmail(currentModifier,
                    signupEventTrackingInfo.getMeeting(), this.sakaiFacade, emailReturnSiteId,
                    signupEventTrackingInfo.getAttendeeComment());

            if (isOrganizer) {
                try {
                    sendEmail(selectedAttendee, email);
                } catch (Exception e) {
                    //do nothing: avoid one wrong email address for one user to break all things
                }
            } else {
                try {
                    //attendee send email out
                    List<User> coUsers = new ArrayList<User>();
                    User organizer = userDirectoryService
                            .getUser(signupEventTrackingInfo.getMeeting().getCreatorUserId());
                    coUsers.add(organizer);
                    if (coOrganizers != null && !coOrganizers.isEmpty()) {
                        for (String userId : coOrganizers) {
                            User coUser = userDirectoryService.getUser(userId);
                            coUsers.add(coUser);
                        }
                    }
                    sendEmail(coUsers, email);
                } catch (Exception e) {
                    //do nothing: avoid one wrong email address for one user to break all things
                }
            }
        } catch (UserNotDefinedException e) {
            throw new Exception(
                    "User is not found for userId: " + signupEventTrackingInfo.getMeeting().getCreatorUserId());
        }
    }

    /**
     * {@inheritDoc}
     */
    public void sendEmailToParticipantsByOrganizerAction(SignupEventTrackingInfo signupEventTrackingInfo)
            throws Exception {

        //generate VEvents
        generateVEvents(signupEventTrackingInfo.getMeeting());

        List<SignupTrackingItem> sigupTList = signupEventTrackingInfo.getAttendeeTransferInfos();
        for (SignupTrackingItem item : sigupTList) {

            User organizer = null;
            User participant = null;
            User participant2 = null;
            try {
                organizer = userDirectoryService.getUser(getSakaiFacade().getCurrentUserId());
                participant = userDirectoryService.getUser(item.getAttendee().getAttendeeUserId());
                if (item.getMessageType().equals(SIGNUP_ATTENDEE_SIGNUP_SWAP)
                        || item.getMessageType().equals(SIGNUP_ATTENDEE_SIGNUP_REPLACE)) {
                    participant2 = userDirectoryService.getUser(item.getReplacedAttendde().getAttendeeUserId());
                }

                SignupEmailNotification email = null;
                if (item.getMessageType().equals(SIGNUP_ATTENDEE_SIGNUP_MOVE))
                    email = new MoveAttendeeEmail(organizer, participant, item,
                            signupEventTrackingInfo.getMeeting(), sakaiFacade);
                else if (item.getMessageType().equals(SIGNUP_ATTENDEE_SIGNUP_SWAP))
                    email = new SwapAttendeeEmail(organizer, participant, participant2, item,
                            signupEventTrackingInfo.getMeeting(), sakaiFacade);
                else if (item.getMessageType().equals(SIGNUP_ATTENDEE_CANCEL))
                    email = new CancellationEmail(organizer, participant, item,
                            signupEventTrackingInfo.getMeeting(), sakaiFacade);
                else if (item.getMessageType().equals(SIGNUP_ATTENDEE_SIGNUP_REPLACE)
                        || item.getMessageType().equals(SIGNUP_ATTENDEE_SIGNUP))
                    email = new AddAttendeeEmail(organizer, participant, item, signupEventTrackingInfo.getMeeting(),
                            sakaiFacade);
                else if (item.getMessageType().equals(SIGNUP_ATTENDEE_PROMOTE))
                    email = new PromoteAttendeeEmail(participant, item, signupEventTrackingInfo.getMeeting(),
                            sakaiFacade);
                else {
                    logger.warn("For attendee(Eid):" + participant.getEid() + " - No such message type:"
                            + item.getMessageType() + " was found and no email was able to send away");
                    return;
                }
                // send email out
                sendEmail(participant, email);

            } catch (UserNotDefinedException e) {
                throw new Exception("User is not found and Email is not sent away for oraginzer userId:"
                        + signupEventTrackingInfo.getMeeting().getCreatorUserId());
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public void sendEmailToAttendee(SignupEventTrackingInfo signupEventTrackingInfo) throws Exception {

        //generate VEvents
        generateVEvents(signupEventTrackingInfo.getMeeting());

        List<SignupTrackingItem> sigupTList = signupEventTrackingInfo.getAttendeeTransferInfos();
        for (SignupTrackingItem item : sigupTList) {
            if (item.getMessageType().equals(SIGNUP_ATTENDEE_SIGNUP)) {
                User participant = null;
                try {
                    participant = userDirectoryService.getUser(item.getAttendee().getAttendeeUserId());
                    SignupEmailNotification email = new AttendeeSignupOwnEmail(participant,
                            signupEventTrackingInfo.getMeeting(), item.getAddToTimeslot(), this.sakaiFacade);
                    sendEmail(participant, email);
                } catch (UserNotDefinedException e) {
                    throw new Exception("User is not found and email has not been sent for participant: "
                            + signupEventTrackingInfo.getMeeting().getCreatorUserId());
                }
            }
        }
        return;
    }

    /* send email via Sakai email Service */
    private void sendEmail(User user, SignupEmailNotification email) {

        logger.debug("sendMail called for user:" + user.getEid());

        try {
            EmailMessage message = convertSignupEmail(email, user);

            if (message != null) {
                emailService.send(message);
            }

        } catch (NoRecipientsException e) {
            logger.error("Cannot send mail. No recipient." + e.getMessage());
        } catch (AddressValidationException e) {
            //this should be caught when adding the email address, since it is validated then.
            logger.warn("Cannot send mail to user: " + user.getEid() + ". Invalid email address."
                    + EmailAddress.toString(e.getInvalidEmailAddresses()));
        }

    }

    /* all email is sent in this manner, so that we can send attachments. So individual users are processed separately
     * There may be issues with this if there are many users. */
    private void sendEmail(List<User> users, SignupEmailNotification email) {
        for (User u : users) {
            sendEmail(u, email);
        }
    }

    @SuppressWarnings("unchecked")
    private List<EmailUserSiteGroup> getUserSiteEmailGroups(List<SignupUser> signupUsers) {
        List<EmailUserSiteGroup> userSiteGroupList = new ArrayList<EmailUserSiteGroup>();
        for (SignupUser signupUser : signupUsers) {
            String siteId = signupUser.getMainSiteId();
            boolean found = false;
            for (EmailUserSiteGroup userSiteGroup : userSiteGroupList) {
                if (siteId.equals(userSiteGroup.getSiteId())) {
                    userSiteGroup.addSignupUser(signupUser);
                    found = true;
                    break;
                }
            }
            if (!found) {
                EmailUserSiteGroup usg = new EmailUserSiteGroup(siteId, signupUser);
                userSiteGroupList.add(usg);
            }
        }

        return userSiteGroupList;
    }

    /* send email to all according to the message type */
    @SuppressWarnings("unchecked")
    private void sendEmailToAllUsers(SignupMeeting meeting, String messageType) throws Exception {

        //generate VEvents
        generateVEvents(meeting);

        List<SignupUser> signupUsers = sakaiFacade.getAllUsers(meeting);

        List<EmailUserSiteGroup> userSiteGroupList = getUserSiteEmailGroups(signupUsers);
        boolean isException = false;
        boolean isAlreadyEmailedToOrganizerCoordinators = false; //avoid duplicated email
        /*divide email-send out by site to site*/
        for (EmailUserSiteGroup emailUserSiteGroup : userSiteGroupList) {
            if (!emailUserSiteGroup.isPublishedSite())
                continue;// skip sending email

            List<String> userIds = null;
            List<User> sakaiUsers = null;
            User organizer = null;
            try {
                SignupEmailNotification email = null;
                if (messageType.equals(SIGNUP_NEW_MEETING)) {
                    organizer = userDirectoryService.getUser(meeting.getCreatorUserId());
                    email = new NewMeetingEmail(organizer, meeting, this.sakaiFacade,
                            emailUserSiteGroup.getSiteId());
                    /*
                     * We have to send it no matter what!!!
                     * send email to pre-assiged people for this site group and
                     * also excluding them for next step.
                     */
                    try {
                        //don't stop to send another email out due to exception
                        sendEmailToPreAssignedAttendee(emailUserSiteGroup, meeting);
                    } catch (Exception e) {
                        isException = true;
                    }

                    /*Check to see if email is for all participants*/
                    if (SEND_EMAIL_ALL_PARTICIPANTS.equals(meeting.getSendEmailToSelectedPeopleOnly())) {
                        /* get the people list excluding pre-assigned ones */
                        userIds = emailUserSiteGroup.getUserInternalIds();
                        sakaiUsers = userDirectoryService.getUsers(userIds);
                    } else {
                        //Case: SEND_EMAIL_ONLY_ORGANIZER_COORDINATORS/SEND_EMAIL_ONLY_SIGNED_UP_ATTENDEES
                        //we have to send to organizers no matter what!!
                        //case: email attendee Only and the email are already sent away
                        /*Now, we need to send email to organizer and coordinators 
                         * since the iCal file is involved*/
                        if (!isAlreadyEmailedToOrganizerCoordinators) {
                            sakaiUsers = getMeetingOwnerAndCoordinators(meeting);
                            isAlreadyEmailedToOrganizerCoordinators = true;
                        }
                    }

                } else if (messageType.equals(SIGNUP_MEETING_MODIFIED)
                        || messageType.equals(SIGNUP_CANCEL_MEETING)) {
                    organizer = userDirectoryService.getUser(getSakaiFacade().getCurrentUserId());

                    //same logic for both, just different emails
                    if (messageType.equals(SIGNUP_MEETING_MODIFIED)) {
                        email = new ModifyMeetingEmail(organizer, meeting, this.sakaiFacade,
                                emailUserSiteGroup.getSiteId());
                    } else if (messageType.equals(SIGNUP_CANCEL_MEETING)) {
                        email = new CancelMeetingEmail(organizer, meeting, this.sakaiFacade,
                                emailUserSiteGroup.getSiteId());
                    }

                    if (SEND_EMAIL_ALL_PARTICIPANTS.equals(meeting.getSendEmailToSelectedPeopleOnly())) {
                        userIds = emailUserSiteGroup.getUserInternalIds();
                        sakaiUsers = userDirectoryService.getUsers(userIds);
                    } else if (SEND_EMAIL_ONLY_ORGANIZER_COORDINATORS
                            .equals(meeting.getSendEmailToSelectedPeopleOnly())) {
                        if (!isAlreadyEmailedToOrganizerCoordinators) {
                            //avoid duplicated email
                            sakaiUsers = getMeetingOwnerAndCoordinators(meeting);
                            isAlreadyEmailedToOrganizerCoordinators = true;
                        }
                    } else if (SEND_EMAIL_ONLY_SIGNED_UP_ATTENDEES
                            .equals(meeting.getSendEmailToSelectedPeopleOnly())) {
                        List<SignupTimeslot> tsList = meeting.getSignupTimeSlots();
                        User user = null;
                        List<SignupUser> sgpUsers = emailUserSiteGroup.getSignupUsers();
                        sakaiUsers = new ArrayList<User>();

                        /*due to iCal file, we need to handle for canceling meeting case for organizer etc.*/
                        //this one may not be needed since SIGNUP_CANCEL_MEETING always should with value:SEND_EMAIL_ALL_PARTICIPANTS
                        //TODO need to find a way: update related ones iCals if the meeting schedule is modified 
                        if (!isAlreadyEmailedToOrganizerCoordinators && messageType.equals(SIGNUP_CANCEL_MEETING)) {
                            sakaiUsers = getMeetingOwnerAndCoordinators(meeting);
                            isAlreadyEmailedToOrganizerCoordinators = true;
                        }

                        if (tsList != null) {
                            for (SignupTimeslot ts : tsList) {
                                List<SignupAttendee> attList = ts.getAttendees();
                                if (attList != null) {
                                    for (SignupAttendee att : attList) {
                                        /*test to see if attendee is in this site-group*/
                                        for (SignupUser spgUser : sgpUsers) {
                                            if (att.getAttendeeUserId().equals(spgUser.getInternalUserId())) {
                                                try {
                                                    user = userDirectoryService.getUser(att.getAttendeeUserId());
                                                } catch (UserNotDefinedException e) {
                                                    logger.warn("User is not found for userId: "
                                                            + att.getAttendeeUserId());
                                                    isException = true;
                                                }
                                                sakaiUsers.add(user);
                                                break;
                                            }
                                        }

                                    }
                                }

                            }
                        }
                    }
                }

                if (email != null) {
                    if (sakaiUsers.size() > 400) {
                        /*Currently, use this only for heavy case.
                         * One drawback: the organizer don't know weather the email is really sent away or not.
                         * There is no error message for user if an unexpected exception happens.
                         * Will add the email-send-error notification at near future time.
                         * */

                        //NO ICS FILE IS SENT VIA THIS METHOD YET
                        EmailDeliverer deliverer = new EmailDeliverer(sakaiUsers, email.getHeader(),
                                email.getMessage(), emailService);
                        Thread t = new Thread(deliverer);
                        t.start();
                    } else {
                        //emailService.sendToUsers(sakaiUsers, email.getHeader(), email.getMessage());
                        sendEmail(sakaiUsers, email);
                    }
                }

            } catch (UserNotDefinedException ue) {
                isException = true;
                logger.warn("User is not found for userId: " + meeting.getCreatorUserId());
            } catch (Exception e) {
                isException = true;
                logger.error("Exception: " + e.getClass() + ": " + e.getMessage());
            }
        }

        if (isException)
            throw new Exception("Some emails may not be sent out due to error.");
    }

    private void sendEmailToPreAssignedAttendee(EmailUserSiteGroup emailUserSiteGroup, SignupMeeting meeting)
            throws Exception {
        List<SignupUser> sgpUsers = emailUserSiteGroup.getSignupUsers();
        List<SignupTimeslot> signupTimeSlots = meeting.getSignupTimeSlots();
        if (signupTimeSlots == null)
            return;

        boolean isExcepiotn = false;
        User currentUser = userDirectoryService.getCurrentUser();
        for (SignupTimeslot timeslot : signupTimeSlots) {
            List<SignupAttendee> attendees = timeslot.getAttendees();
            if (attendees == null)
                continue;

            for (SignupAttendee attendee : attendees) {
                for (Iterator iter = sgpUsers.iterator(); iter.hasNext();) {
                    SignupUser spUser = (SignupUser) iter.next();
                    if (spUser.getInternalUserId().equals(attendee.getAttendeeUserId())) {
                        User user;
                        try {
                            user = userDirectoryService.getUser(attendee.getAttendeeUserId());
                            SignupEmailNotification email = new OrganizerPreAssignEmail(currentUser, meeting,
                                    timeslot, user, this.sakaiFacade, emailUserSiteGroup.getSiteId());
                            sendEmail(user, email);
                        } catch (UserNotDefinedException e) {
                            logger.warn("User is not found for userId: " + attendee.getAttendeeUserId());
                            isExcepiotn = true;
                        }
                        /*
                         * remove it to avoid send new meeting notification
                         * again
                         */
                        iter.remove();
                        break;
                    }
                }

            }
        }

        if (isExcepiotn)
            throw new Exception("User is not found and email may not be sent out.");
    }

    /**
     * This will exlude the pre-assigned attendee for a new meeting since they
     * will receive a different email
     * 
     * @param sakaiUsers
     *            a list of User objects.
     * @param meeting
     *            a SignupMeeting Object.
     */
    private void excludPreAssignedAttendee(List<User> sakaiUsers, SignupMeeting meeting) {
        List<SignupTimeslot> signupTimeSlots = meeting.getSignupTimeSlots();
        if (signupTimeSlots == null)
            return;

        for (SignupTimeslot timeslot : signupTimeSlots) {
            List<SignupAttendee> attendees = timeslot.getAttendees();
            if (attendees == null)
                continue;

            User preAssignedUser = null;
            for (SignupAttendee attendee : attendees) {
                try {
                    preAssignedUser = userDirectoryService.getUser(attendee.getAttendeeUserId());
                    for (Iterator iter = sakaiUsers.iterator(); iter.hasNext();) {
                        User sakaiUser = (User) iter.next();
                        if (sakaiUser.getEid().equals(preAssignedUser.getEid())) {
                            iter.remove();
                        }
                    }

                } catch (UserNotDefinedException e) {
                    logger.warn("User is not found for userId: " + attendee.getAttendeeUserId());
                }

            }
        }
    }

    private class EmailUserSiteGroup {
        private String siteId;

        private boolean publishedSite;

        private List<SignupUser> signupUsers = new ArrayList<SignupUser>();

        public EmailUserSiteGroup(String siteId, SignupUser user) {
            this.siteId = siteId;
            this.signupUsers.add(user);
            this.publishedSite = user.isPublishedSite();
        }

        public void addSignupUser(SignupUser user) {
            this.signupUsers.add(user);
        }

        public String getSiteId() {
            return siteId;
        }

        public void setSiteId(String siteId) {
            this.siteId = siteId;
        }

        public List<SignupUser> getSignupUsers() {
            return signupUsers;
        }

        public void setSignupUsers(List<SignupUser> signupUsers) {
            this.signupUsers = signupUsers;
        }

        public List<String> getUserInternalIds() {
            List<String> userIds = new ArrayList<String>();
            for (SignupUser signupUser : signupUsers) {
                userIds.add(signupUser.getInternalUserId());
            }
            return userIds;
        }

        public boolean isPublishedSite() {
            return publishedSite;
        }

        public void setPublishedSite(boolean publishedSite) {
            this.publishedSite = publishedSite;
        }

    }

    /**
     * Helper to convert a signup email notification into a Sakai EmailMessage, which can encapsulate attachments.
     * 
     * <p>Due to the way the email objects are created, ie one per email that needs to be sent, not one per user, we cannot store any
     * user specific attachments within the email objects themselves. So this method assembles an EmailMessage per user
     * 
     * @param email   - the signup email obj we will extract info from
     * @param recipient - list of user to receive the email
     * @return
     */
    private EmailMessage convertSignupEmail(SignupEmailNotification email, User recipient) {

        EmailMessage message = new EmailMessage();

        //setup message
        message.setHeaders(email.getHeader());
        message.setBody(email.getMessage());

        // Pass a flag to the EmailService to indicate that we want the MIME multipart subtype set to alternative
        // so that an email client can present the message as a meeting invite
        message.setHeader("multipart-subtype", "alternative");

        //note that the headers are largely ignored so we need to repeat some things here that are actually in the headers
        //if these are eventaully converted to proper email templates, this should be alleviated
        message.setSubject(email.getSubject());

        logger.debug("email.getFromAddress(): " + email.getFromAddress());

        message.setFrom(email.getFromAddress());
        message.setContentType("text/html; charset=UTF-8");

        if (!email.isModifyComment()) {
            //skip ICS attachment file for user comment email
            for (Attachment a : collectAttachments(email, recipient)) {
                message.addAttachment(a);
            }
        }

        //add recipient, only if valid email
        String emailAddress = recipient.getEmail();
        if (StringUtils.isNotBlank(emailAddress) && EmailValidator.getInstance().isValid(emailAddress)) {
            message.addRecipient(EmailAddress.RecipientType.TO, recipient.getDisplayName(), emailAddress);
        } else {
            logger.error(
                    "Invalid email for user:" + recipient.getDisplayId() + ". No email will be sent to this user");
            return null;
        }

        return message;
    }

    /**
     * Helper method to collect all attachments that need to go out for this email and to this user
     * @param email   signupemailnotification
     * @param user the user that will receive this email
     * @return list of Attachments
     */
    private List<Attachment> collectAttachments(SignupEmailNotification email, User user) {

        List<Attachment> attachments = new ArrayList<Attachment>();
        attachments.addAll(generateICS(email, user));
        //add more here as required

        return attachments;

    }

    /**
     * Generate an ICS file for the user and the email message type and return as an attachment.
     * 
     * @param email   email obj
     * @param user   User
     * @return
     */
    private List<Attachment> generateICS(SignupEmailNotification email, User user) {

        List<Attachment> attachments = new ArrayList<Attachment>();

        String method = email.isCancellation() ? "CANCEL" : "REQUEST";
        final List<VEvent> events = email.generateEvents(user, calendarHelper);

        if (events.size() > 0) {
            attachments.add(formatICSAttachment(events, method));
        }

        /*
         * Note that there is no notification if a meeting is removed altogether.
         */

        return attachments;
    }

    /**
     * Helper to generate the calendareventedit objects, turn them into VEvents, then write back into the meeting/timeslot objects
     * One is generated for the signupmeeting itself, then one for each timeslot.
     * @param meeting SignupMeeting
     * @return modifiedSignupmeeting with the VEvents injected
     */
    private SignupMeeting generateVEvents(SignupMeeting meeting) {

        //generate VEvent for meeting, add to object
        meeting.setVevent(calendarHelper.generateVEventForMeeting(meeting));

        //now one for each timeslot
        for (SignupTimeslot ts : meeting.getSignupTimeSlots()) {

            //generate VEvent for timeslot, add to object
            ts.setVevent(calendarHelper.generateVEventForTimeslot(meeting, ts));
        }

        return meeting;
    }

    /**
     * Helper to create an ICS calendar from a list of vevents, then turn it into an attachment
     * @param vevents list of vevents
     * @param method the ITIP method for the calendar, e.g. "REQUEST"
     * @return
     */
    private Attachment formatICSAttachment(List<VEvent> vevents, String method) {
        String path = calendarHelper.createCalendarFile(vevents, method);

        // Explicitly define the Content-Type and Content-Diposition headers so the invitation appears inline
        String filename = StringUtils.substringAfterLast(path, File.separator);
        String type = String.format("text/calendar; charset=\"utf-8\"; method=%s; name=signup-invite.ics", method);
        File file = new File(path);
        DataSource dataSource = new Attachment.RenamedDataSource(new FileDataSource(file), filename);
        return new Attachment(dataSource, type, Attachment.ContentDisposition.INLINE);
    }

}