org.sakaiproject.signup.tool.jsf.organizer.OrganizerSignupMBean.java Source code

Java tutorial

Introduction

Here is the source code for org.sakaiproject.signup.tool.jsf.organizer.OrganizerSignupMBean.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.tool.jsf.organizer;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

import javax.faces.component.UIData;
import javax.faces.component.UIInput;
import javax.faces.model.SelectItem;
import javax.faces.model.SelectItemGroup;
import javax.faces.event.ActionEvent;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.impl.LogFactoryImpl;
import org.sakaiproject.exception.IdUnusedException;
import org.sakaiproject.signup.logic.SignupUser;
import org.sakaiproject.signup.logic.SignupUserActionException;
import org.sakaiproject.signup.logic.messages.SignupEventTrackingInfo;
import org.sakaiproject.signup.model.SignupAttendee;
import org.sakaiproject.signup.model.SignupMeeting;
import org.sakaiproject.signup.model.SignupSite;
import org.sakaiproject.signup.tool.jsf.AttendeeWrapper;
import org.sakaiproject.signup.tool.jsf.SignupMeetingWrapper;
import org.sakaiproject.signup.tool.jsf.SignupUIBaseBean;
import org.sakaiproject.signup.tool.jsf.TimeslotWrapper;
import org.sakaiproject.signup.tool.jsf.attendee.EditCommentSignupMBean;
import org.sakaiproject.signup.tool.jsf.organizer.action.AddAttendee;
import org.sakaiproject.signup.tool.jsf.organizer.action.AddWaiter;
import org.sakaiproject.signup.tool.jsf.organizer.action.CancelAttendee;
import org.sakaiproject.signup.tool.jsf.organizer.action.CancelRestoreTimeslot;
import org.sakaiproject.signup.tool.jsf.organizer.action.LockUnlockTimeslot;
import org.sakaiproject.signup.tool.jsf.organizer.action.MoveAttendee;
import org.sakaiproject.signup.tool.jsf.organizer.action.RemoveWaiter;
import org.sakaiproject.signup.tool.jsf.organizer.action.ReplaceAttendee;
import org.sakaiproject.signup.tool.jsf.organizer.action.SwapAttendee;
import org.sakaiproject.signup.tool.util.Utilities;
import org.sakaiproject.site.api.Site;
import org.sakaiproject.user.api.UserNotDefinedException;

/**
 * <p>
 * This JSF UIBean class will handle information exchanges between Organizer's
 * event/meeting view page:<b>orgSignupMeeting.jsp</b> and backbone system. It
 * provides all the necessary business logic
 * 
 * @author Peter Liu
 * 
 * </P>
 */
public class OrganizerSignupMBean extends SignupUIBaseBean {

    private UIData timeslotWrapperTable;

    private UIInput attendeeTimeSlotWithId;

    private UIInput replacedAttendeeEidOrEmail;

    /* proxy param for eid by user input */
    private String eidOrEmailByUser;

    private UIInput waiterEidOrEmail;

    private UIData attendeeWrapperTable;

    private UIData waiterWrapperTable;

    private List<SelectItem> allAttendees;

    private List<SignupUser> allSignupUsers;

    public static final String DELIMITER = "::";

    private String selectedAction = REPLACE_ACTION;

    private String selectedEditTimeslotId;

    private UIInput selectedTimeslotId;

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

    private boolean addNewAttendee;

    private UIInput addNewAttendeeUserEidOrEmail;

    private UIInput listPendingType;

    private String userActionType = REPLACE_ACTION;

    private String selectedFirstUser = "gl256";

    private String currentUserId;

    private String currentSiteId;

    private CopyMeetingSignupMBean copyMeetingMBean;

    private EditMeetingSignupMBean editMeetingMBean;

    private EditCommentSignupMBean editCommentMBean;

    private CancelRestoreTimeslot cancelRestoreTimeslot;

    private boolean collapsedMeetingInfo;

    private boolean eidInputMode = false;
    public String timeslottoGroup;

    /**
     * This will initialize all the wrapper objects such as
     * SignupMeetingWrapper, SignupTimeslotWrapper etc.
     * 
     * @param meetingWrapper
     *            a SignupMeetingWrapper object.
     */
    public void init(SignupMeetingWrapper meetingWrapper) throws Exception {
        this.eidInputMode = false;
        this.collapsedMeetingInfo = false;
        reset(meetingWrapper);
        loadAllAttendees(meetingWrapper.getMeeting());
    }

    /**
     * This will basically update the Timeslot wrappers after event/meeting is
     * updated
     * 
     * @param meetingWrapper
     *            a SignupMeetingWrapper object.
     */
    public void reset(SignupMeetingWrapper meetingWrapper) {
        setMeetingWrapper(meetingWrapper);
        updateTimeSlotWrappers(meetingWrapper);
        setEidInputMode(meetingWrapper.getMeeting().isEidInputMode());
        if (!isEidInputMode() && (getAllAttendees() == null || getAllAttendees().isEmpty())) {
            loadAllAttendees(meetingWrapper.getMeeting());
        }

        //reset email checkBox value setting
        this.sendEmail = meetingWrapper.getMeeting().isSendEmailByOwner();

    }

    /**
     * This is a JSF action call method by UI to modify the event/meeting.
     * 
     * @return an action outcome string.
     */
    public String modifyMeeting() {
        /* get latest copy of meeting */
        SignupMeeting meeting = null;
        String goMainPageDueToError = updateMeetingwrapper(meeting, ORGANIZER_MEETING_PAGE_URL);
        if (goMainPageDueToError.equals(MAIN_EVENTS_LIST_PAGE_URL))
            return goMainPageDueToError;

        editMeetingMBean.setMeetingWrapper(getMeetingWrapper());
        editMeetingMBean.reset();

        return MODIFY_MEETING_PAGE_URL;
    }

    /**
     * This is a JSF action call method by UI to copy the event/meeting.
     * 
     * @return an action outcome string.
     */
    public String copyMeeting() {
        copyMeetingMBean.setMeetingWrapper(getMeetingWrapper());
        copyMeetingMBean.reset();
        return COPTY_MEETING_PAGE_URL;
    }

    /**
     * This is a JSF action call method by UI to lock/unlock time slot of the
     * event/meeting.
     * 
     * @return an action outcome string.
     */
    public String processLockTsAction() {
        SignupMeeting meeting = null;
        try {
            TimeslotWrapper timeslotWrapper = (TimeslotWrapper) timeslotWrapperTable.getRowData();
            LockUnlockTimeslot lockTimeslot = new LockUnlockTimeslot(getMeetingWrapper().getMeeting(),
                    timeslotWrapper.getTimeSlot(), currentUserId(), currentSiteId(), signupMeetingService);
            meeting = lockTimeslot.lockOrUnlock();
        } catch (SignupUserActionException ue) {
            Utilities.addErrorMessage(ue.getMessage());
        } catch (Exception e) {
            logger.error(Utilities.rb.getString("error.occurred_try_again") + " - " + e.getMessage());
            Utilities.addErrorMessage(Utilities.rb.getString("error.occurred_try_again"));
        }

        return updateMeetingwrapper(meeting, ORGANIZER_MEETING_PAGE_URL);
    }

    /**
     * This is a JSF action call method by UI to go to Cancel confirmation page.
     * 
     * @return an action outcome string.
     */
    public String initiateCancelTimeslot() {
        TimeslotWrapper timeslotWrapper = (TimeslotWrapper) timeslotWrapperTable.getRowData();
        cancelRestoreTimeslot = new CancelRestoreTimeslot(getMeetingWrapper().getMeeting(),
                timeslotWrapper.getTimeSlot(), currentUserId(), currentSiteId(), signupMeetingService);
        return cancelRestoreTimeslot();
    }

    /**
     * This is a JSF action call method by UI to restore time slot of the
     * event/meeting.
     * 
     * @return an action outcome string
     */
    public String restoreTimeslot() {
        TimeslotWrapper timeslotWrapper = (TimeslotWrapper) timeslotWrapperTable.getRowData();
        cancelRestoreTimeslot = new CancelRestoreTimeslot(getMeetingWrapper().getMeeting(),
                timeslotWrapper.getTimeSlot(), currentUserId(), currentSiteId(), signupMeetingService);
        return cancelRestoreTimeslot();
    }

    /**
     * This is a JSF action call method by UI to cancel time slot of the
     * event/meeting.
     * 
     * @return an action outcome string
     */
    public String cancelTimeslot() {
        return cancelRestoreTimeslot();
    }

    /**
     * This is a JSF action call method by UI to view a specific attendee's
     * comment of the event/meeting.
     * 
     * @return an action outcome string.
     */
    public String viewAttendeeComment() {
        String attUserId = (String) Utilities.getRequestParam("attendeeUserId");
        String timeslotId = (String) Utilities.getRequestParam("timeslotId");
        if (attUserId == null || timeslotId == null)
            return "";

        AttendeeWrapper attWrp = findAttendee(timeslotId, attUserId);
        if (attWrp == null)
            return "";

        this.editCommentMBean.init(attWrp, this.getAttendeeRole(attUserId), getMeetingWrapper(), timeslotId);
        return VIEW_COMMENT_PAGE_URL;
    }

    /**
     * find an attendee in a specific time slot
     * @param timeslotId
     * @param userId
     * @return
     */
    private AttendeeWrapper findAttendee(String timeslotId, String userId) {
        if (getTimeslotWrappers() == null || getTimeslotWrappers().isEmpty())
            return null;

        String timeslotPeriod = null;
        for (TimeslotWrapper wrapper : getTimeslotWrappers()) {
            if (wrapper.getTimeSlot().getId().toString().equals(timeslotId)) {
                timeslotPeriod = getSakaiFacade().getTimeService()
                        .newTime(wrapper.getTimeSlot().getStartTime().getTime()).toStringLocalTime() + " - "
                        + getSakaiFacade().getTimeService().newTime(wrapper.getTimeSlot().getEndTime().getTime())
                                .toStringLocalTime();
                List<AttendeeWrapper> attWrp = wrapper.getAttendeeWrappers();
                for (AttendeeWrapper att : attWrp) {
                    if (att.getSignupAttendee().getAttendeeUserId().equals(userId)) {
                        att.setTimeslotPeriod(timeslotPeriod);
                        return att;
                    }
                }
                break;
            }
        }
        return null;
    }

    private String getAttendeeRole(String attendeeUserId) {
        SignupUser sUser = getSakaiFacade().getSignupUser(getMeetingWrapper().getMeeting(), attendeeUserId);
        if (sUser == null)
            return "unknown";
        else
            return sUser.getUserRole().getId();
    }

    private String cancelRestoreTimeslot() {
        if (cancelRestoreTimeslot == null)
            return ORGANIZER_MEETING_PAGE_URL;

        SignupMeeting meeting = null;
        try {
            meeting = cancelRestoreTimeslot.cancelOrRestore();
            if (sendEmail) {
                try {
                    signupMeetingService.sendEmailToParticipantsByOrganizerAction(
                            cancelRestoreTimeslot.getSignupEventTrackingInfo());
                } catch (Exception e) {
                    logger.error(Utilities.rb.getString("email.exception") + " - " + e.getMessage(), e);
                    Utilities.addErrorMessage(Utilities.rb.getString("email.exception"));
                }
            }
        } catch (Exception e) {
            logger.error(Utilities.rb.getString("error.occurred_try_again") + " - " + e.getMessage());
            Utilities.addErrorMessage(Utilities.rb.getString("error.occurred_try_again"));
        }

        return updateMeetingwrapper(meeting, ORGANIZER_MEETING_PAGE_URL);
    }

    /**
     * This is a JSF action call method by UI to edit the attendee in the
     * event/meeting.
     * 
     * @return an action outcome string.
     */
    public String editTimeslotAttendee() {
        SignupMeeting meeting = null;
        try {
            SignupEventTrackingInfo signupEventTrackingInfo = null;
            if (this.userActionType.equals(MOVE_ACTION)) { // old:selectedAction
                signupEventTrackingInfo = moveAttendee();
            }
            if (this.userActionType.equals(REPLACE_ACTION)) {
                try {
                    signupEventTrackingInfo = replaceAttendee();
                } catch (UserNotDefinedException e) {
                    logger.warn(Utilities.rb.getString("exception.no.such.user")
                            + (String) replacedAttendeeEidOrEmail.getValue() + " -- " + e.getMessage());
                    Utilities.addErrorMessage(Utilities.rb.getString("exception.no.such.user")
                            + (String) replacedAttendeeEidOrEmail.getValue());
                    return "";
                }
            }

            if (this.userActionType.equals(SWAP_ACTION)) {
                signupEventTrackingInfo = swapAttendees();
            }

            if (sendEmail) {
                try {
                    signupMeetingService.sendEmailToParticipantsByOrganizerAction(signupEventTrackingInfo);
                } catch (Exception e) {
                    logger.error(Utilities.rb.getString("email.exception") + " - " + e.getMessage(), e);
                    Utilities.addErrorMessage(Utilities.rb.getString("email.exception"));
                }
            }

        } catch (SignupUserActionException ue) {
            Utilities.addErrorMessage(ue.getMessage());

        } catch (Exception e) {
            logger.error(Utilities.rb.getString("error.occurred_try_again") + " - " + e.getMessage());
            Utilities.addErrorMessage(Utilities.rb.getString("error.occurred_try_again"));
        }

        /* reset */
        setSelectedAction(REPLACE_ACTION);

        /*
         * refresh meeting list to catch the changes when go back the main
         * meetings list page
         */
        if (Utilities.getSignupMeetingsBean().isShowMyAppointmentTime())
            Utilities.resetMeetingList();

        return updateMeetingwrapper(meeting, ORGANIZER_MEETING_PAGE_URL);
    }

    /* The logic below assumes an attendee appears only once in a timeslot */
    private SignupEventTrackingInfo swapAttendees() throws Exception {
        SwapAttendee swapAttendee = new SwapAttendee(currentUserId(), currentSiteId(), signupMeetingService);
        TimeslotWrapper currentTimeslotWrapper = (TimeslotWrapper) timeslotWrapperTable.getRowData();

        swapAttendee.swapAttendee(getMeetingWrapper().getMeeting(), currentTimeslotWrapper.getTimeSlot(),
                this.selectedFirstUser, (String) attendeeTimeSlotWithId.getValue());

        return swapAttendee.getSignupEventTrackingInfo();
    }

    private SignupEventTrackingInfo replaceAttendee() throws Exception {
        String userEidOrEmail = null;
        if (isEidInputMode()) {
            userEidOrEmail = getEidOrEmailInputByUser();
        } else {
            userEidOrEmail = (String) replacedAttendeeEidOrEmail.getValue();
        }

        //check if there are multiple email addresses associated with input
        List<String> associatedEids = getEidsForEmail(userEidOrEmail.trim());
        if (associatedEids.size() > 1) {
            throw new SignupUserActionException(
                    MessageFormat.format(Utilities.rb.getString("exception.multiple.eids"),
                            new Object[] { userEidOrEmail, StringUtils.join(associatedEids, ", ") }));
        }

        String replacerUserId = getUserIdForEidOrEmail(userEidOrEmail);
        SignupUser replSignUser = getSakaiFacade().getSignupUser(getMeetingWrapper().getMeeting(), replacerUserId);
        if (replSignUser == null) {
            throw new SignupUserActionException(MessageFormat.format(
                    Utilities.rb.getString("user.has.no.permission.attend"), new Object[] { userEidOrEmail }));
        }

        TimeslotWrapper wrapper = (TimeslotWrapper) timeslotWrapperTable.getRowData();

        ReplaceAttendee replaceAttendee = new ReplaceAttendee(this.currentUserId(), this.currentSiteId(),
                signupMeetingService);
        replaceAttendee.replace(getMeetingWrapper().getMeeting(), wrapper.getTimeSlot(), this.selectedFirstUser,
                replacerUserId, replSignUser.getMainSiteId());

        return replaceAttendee.getSignupEventTrackingInfo();
    }

    private SignupEventTrackingInfo moveAttendee() throws Exception {
        TimeslotWrapper wrapper = (TimeslotWrapper) timeslotWrapperTable.getRowData();

        MoveAttendee moveAttendee = new MoveAttendee(this.currentUserId(), this.currentSiteId(),
                signupMeetingService);
        moveAttendee.move(getMeetingWrapper().getMeeting(), wrapper.getTimeSlot(), this.selectedFirstUser,
                (String) selectedTimeslotId.getValue());

        return moveAttendee.getSignupEventTrackingInfo();

    }

    /**
     * This will load all the potential participants for an event/meeting and
     * wrap it for UI purpose. Due to efficiency issue, it will auto rolled back
     * to Eid-Input mode when the number of users are bigger thank 600 ( defined
     * by <I>MAX_NUM_ATTENDEES_ALLOWED_BEFORE_AUTO_SWITCH_TO_EID_INPUT_MODE</I>}
     * value).
     * 
     * @param meeting
     *            a SignupMeeting object.
     */
    public void loadAllAttendees(SignupMeeting meeting) {
        if (meeting.isEidInputMode()) {
            setEidInputMode(true);
            return;
        }

        try {
            Site site = getSakaiFacade().getSiteService().getSite(getSakaiFacade().getCurrentLocationId());
            if (site != null) {
                int allMemeberSize = site.getMembers() != null ? site.getMembers().size() : 0;
                /*
                 * due to efficiency, user has to input EID instead of using dropdown
                 * user name list
                 */
                /*First check to avoid load all site member up if there is ten of thousends*/
                if (allMemeberSize > MAX_NUM_PARTICIPANTS_FOR_DROPDOWN_BEFORE_AUTO_SWITCH_TO_EID_INPUT_MODE) {
                    setEidInputMode(true);
                    return;
                }
            }
        } catch (IdUnusedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        this.allSignupUsers = sakaiFacade.getAllPossibleAttendees(meeting);
        /*
         * due to efficiency, user has to input EID instead of using dropdown
         * user name list
         */
        if (allSignupUsers != null
                && allSignupUsers.size() > MAX_NUM_PARTICIPANTS_FOR_DROPDOWN_BEFORE_AUTO_SWITCH_TO_EID_INPUT_MODE) {
            setEidInputMode(true);
            return;
        }

        setEidInputMode(false);
        allAttendees = new ArrayList<SelectItem>();
        String previous_displayName = "";
        int index = 0;
        for (SignupUser user : allSignupUsers) {
            if (user.getDisplayName().equals(previous_displayName)) {
                allAttendees.add(new SelectItem(user.getEid(), user.getDisplayName() + "(" + user.getEid() + ")"));
                SelectItem prev_sItem = allAttendees.get(index - 1);
                //checking: not already has eid for triple duplicates case
                if (!prev_sItem.getLabel().contains("(")) {
                    prev_sItem.setLabel(prev_sItem.getLabel() + " (" + prev_sItem.getValue() + ")");
                }

            } else {
                allAttendees.add(new SelectItem(user.getEid(), user.getDisplayName()));
            }

            previous_displayName = user.getDisplayName();
            index++;
        }
    }

    /* This one overwrites the parent one due to two more stuffs to create */
    protected void updateTimeSlotWrappers(SignupMeetingWrapper meetingWrapper) {
        super.updateTimeSlotWrappers(meetingWrapper);
        /* for organizer UI swap-dropdown list */
        createUISwapListForEachTimeSlot(getTimeslotWrappers());
        createMoveAvailabelTimeSlots(getTimeslotWrappers());

    }

    /* create the avaiable timeslot for UI purpose */
    private void createMoveAvailabelTimeSlots(List<TimeslotWrapper> timeslotWrapperList) {

        List<SelectItem> list = new ArrayList<SelectItem>();
        for (TimeslotWrapper wrapper : timeslotWrapperList) {
            list.add(new SelectItem(wrapper.getTimeSlot().getId().toString(), wrapper.getLabel()));
        }

        /*
         * list of avalilable timeslot for move excluding the current timeslot
         */
        for (int i = 0; i < list.size(); i++) {
            List<SelectItem> moveAvailableTimeSlots = new ArrayList<SelectItem>(list);
            moveAvailableTimeSlots.remove(i);// remove the current one

            TimeslotWrapper wrapper = timeslotWrapperList.get(i);
            wrapper.setMoveAvailableTimeSlots(moveAvailableTimeSlots);
        }
    }

    /* create possible swap dropdown choices for UI purpose */
    private void createUISwapListForEachTimeSlot(List<TimeslotWrapper> timeslotWrapperList) {
        List<SelectItem> tsAttendeeGroups = new ArrayList<SelectItem>();
        for (TimeslotWrapper wrapper : timeslotWrapperList) {
            String grpLabel = wrapper.getLabel();
            List<SelectItem> attendeeOnTS = new ArrayList<SelectItem>();

            //clean the list of attendees
            List<SignupAttendee> attendees = getValidAttendees(wrapper.getTimeSlot().getAttendees());

            if (attendees != null && !attendees.isEmpty()) {
                for (SignupAttendee att : attendees) {
                    SelectItem sItem = new SelectItem(
                            wrapper.getTimeSlot().getId() + DELIMITER + att.getAttendeeUserId(),
                            sakaiFacade.getUserDisplayName(att.getAttendeeUserId()));
                    attendeeOnTS.add(sItem);
                }
            }

            SelectItemGroup sigrp = new SelectItemGroup(grpLabel, "Timeslot Name", false,
                    (SelectItem[]) attendeeOnTS.toArray(new SelectItem[0]));
            tsAttendeeGroups.add(sigrp);
        }

        /*
         * list of avalilable timeslot for swap excluding the current timeslot
         */
        for (int i = 0; i < tsAttendeeGroups.size(); i++) {
            List<SelectItem> swapAvailableTimeSlots = new ArrayList<SelectItem>(tsAttendeeGroups);
            swapAvailableTimeSlots.remove(i);// remove the current one

            TimeslotWrapper wrapper = timeslotWrapperList.get(i);
            wrapper.setSwapDropDownList(swapAvailableTimeSlots);
        }
    }

    public UIData getTimeslotWrapperTable() {
        return timeslotWrapperTable;
    }

    public void setTimeslotWrapperTable(UIData timeslotWrapperTable) {
        this.timeslotWrapperTable = timeslotWrapperTable;
    }

    public UIInput getAttendeeTimeSlotWithId() {
        return attendeeTimeSlotWithId;
    }

    public void setAttendeeTimeSlotWithId(UIInput attendeeEid) {
        this.attendeeTimeSlotWithId = attendeeEid;
    }

    public UIInput getWaiterEidOrEmail() {
        return waiterEidOrEmail;
    }

    public void setWaiterEidOrEmail(UIInput waiterEidOrEmail) {
        this.waiterEidOrEmail = waiterEidOrEmail;
    }

    public String prepareAddAttendee() {
        setAddNewAttendee(true);
        setSelectedEditTimeslotId(null);
        return ORGANIZER_MEETING_PAGE_URL;
    }

    /**
     * This is a JSF action call method by UI to add a new attendee into the
     * event/meeting.
     * 
     * @return an action outcome string.
     */
    public String addAttendee() {
        TimeslotWrapper timeslotWrapper = (TimeslotWrapper) timeslotWrapperTable.getRowData();

        String newAttendeeEidOrEmail = null;
        if (isEidInputMode()) {
            newAttendeeEidOrEmail = getEidOrEmailInputByUser();
        } else {
            if (addNewAttendeeUserEidOrEmail != null
                    && ((String) addNewAttendeeUserEidOrEmail.getValue()).trim().length() > 0) {
                newAttendeeEidOrEmail = ((String) addNewAttendeeUserEidOrEmail.getValue()).trim();
            }
        }

        if (StringUtils.isBlank(newAttendeeEidOrEmail)) {
            return ORGANIZER_MEETING_PAGE_URL;
        }

        //check if there are multiple email addresses associated with input
        List<String> associatedEids = getEidsForEmail(newAttendeeEidOrEmail.trim());
        if (associatedEids.size() > 1) {
            Utilities.addErrorMessage(MessageFormat.format(Utilities.rb.getString("exception.multiple.eids"),
                    new Object[] { newAttendeeEidOrEmail, StringUtils.join(associatedEids, ", ") }));
            return ORGANIZER_MEETING_PAGE_URL;
        }

        String newUserId = getUserIdForEidOrEmail(newAttendeeEidOrEmail.trim());
        if (StringUtils.isBlank(newUserId)) {
            Utilities.addErrorMessage(Utilities.rb.getString("exception.no.such.user") + newAttendeeEidOrEmail);
            return ORGANIZER_MEETING_PAGE_URL;
        }

        SignupUser newAttendeeSignUser = getSakaiFacade().getSignupUser(getMeetingWrapper().getMeeting(),
                newUserId);
        if (newAttendeeSignUser == null) {
            Utilities.addErrorMessage(MessageFormat.format(Utilities.rb.getString("user.has.no.permission.attend"),
                    new Object[] { newAttendeeEidOrEmail }));
            return ORGANIZER_MEETING_PAGE_URL;
        }

        SignupAttendee newAttendee = new SignupAttendee(newUserId, newAttendeeSignUser.getMainSiteId());
        timeslotWrapper.setNewAttendee(newAttendee);

        SignupMeeting meeting = null;
        try {
            AddAttendee addAttendee = new AddAttendee(signupMeetingService, currentUserId(), currentSiteId(), true);
            meeting = addAttendee.signup(getMeetingWrapper().getMeeting(), timeslotWrapper.getTimeSlot(),
                    timeslotWrapper.getNewAttendee());

            /*TODO : we may need to re-think adding this sendAtteneeEmail condition here.  We want that the Organizer will be enable to control 
             * the email notification via that checkBox on 'Organizer Meeting' page.
             * */
            boolean sendAttendeeEmail = false;//Utilities.getSignupConfigParamVal("signup.email.notification.attendee.signed.up", true);

            if (sendEmail || sendAttendeeEmail) {
                try {
                    signupMeetingService
                            .sendEmailToParticipantsByOrganizerAction(addAttendee.getSignupEventTrackingInfo());
                } catch (Exception e) {
                    logger.error(Utilities.rb.getString("email.exception") + " - " + e.getMessage(), e);
                    Utilities.addErrorMessage(Utilities.rb.getString("email.exception"));
                }
            }
        } catch (SignupUserActionException ue) {
            Utilities.addErrorMessage(ue.getMessage());

        } catch (Exception e) {
            logger.error(Utilities.rb.getString("error.occurred_try_again") + " - " + e.getMessage());
            Utilities.addErrorMessage(Utilities.rb.getString("error.occurred_try_again"));
        }

        String nextPage = updateMeetingwrapper(meeting, ORGANIZER_MEETING_PAGE_URL);
        // TODO calendar event id;

        if (ORGANIZER_MEETING_PAGE_URL.equals(nextPage)) {
            setAddNewAttendee(false);
            setSelectedTimeslotId(null);
        }
        return nextPage;
    }

    /**
     * This is a JSF action call method by UI to reset user input.
     * 
     * @return an action outcome string.
     */
    public String cancelAddAttendee() {
        setAddNewAttendee(false);
        setAddNewAttendeeUserEidOrEmail(null);

        return ORGANIZER_MEETING_PAGE_URL;
    }

    /**
     * This is a JSF action call method by UI to remove an attendee from the
     * event/meeting.
     * 
     * @return an action outcome string.
     */
    public String removeAttendee() {
        SignupMeeting meeting = null;
        TimeslotWrapper timeslotWrapper = (TimeslotWrapper) timeslotWrapperTable.getRowData();
        String removedUserId = (String) Utilities.getRequestParam(ATTENDEE_USER_ID);
        try {
            CancelAttendee remove = new CancelAttendee(signupMeetingService, currentUserId(), currentSiteId(),
                    true);
            SignupAttendee removedAttendee = new SignupAttendee(removedUserId, currentSiteId());
            meeting = remove.cancelSignup(getMeetingWrapper().getMeeting(), timeslotWrapper.getTimeSlot(),
                    removedAttendee);

            if (sendEmail) {
                try {
                    signupMeetingService
                            .sendEmailToParticipantsByOrganizerAction(remove.getSignupEventTrackingInfo());
                } catch (Exception e) {
                    logger.error(Utilities.rb.getString("email.exception") + " - " + e.getMessage(), e);
                    Utilities.addErrorMessage(Utilities.rb.getString("email.exception"));
                }
            }
        } catch (SignupUserActionException ue) {
            Utilities.addErrorMessage(ue.getMessage());
        } catch (Exception e) {
            logger.error(Utilities.rb.getString("error.occurred_try_again") + " - " + e.getMessage());
            Utilities.addErrorMessage(Utilities.rb.getString("error.occurred_try_again"));
        }

        /*
         * refresh meeting list to catch the changes when go back the main
         * meeting list page
         */
        if (Utilities.getSignupMeetingsBean().isShowMyAppointmentTime())
            Utilities.resetMeetingList();

        return updateMeetingwrapper(meeting, ORGANIZER_MEETING_PAGE_URL);
    }

    /**
     * This is a JSF action call method by UI to add the attendee to a waiting
     * list in the event/meeting.
     * 
     * @return an action outcome string.
     */
    public String addAttendeeToWList() {
        TimeslotWrapper timeslotWrapper = (TimeslotWrapper) timeslotWrapperTable.getRowData();

        String newWaiterEidOrEmail = null;
        if (isEidInputMode()) {
            newWaiterEidOrEmail = getEidOrEmailInputByUser();
        } else {
            if (waiterEidOrEmail != null && ((String) waiterEidOrEmail.getValue()).trim().length() > 0)
                newWaiterEidOrEmail = ((String) waiterEidOrEmail.getValue()).trim();
        }

        if (StringUtils.isBlank(newWaiterEidOrEmail)) {
            return ORGANIZER_MEETING_PAGE_URL;
        }

        //check if there are multiple email addresses associated with input
        List<String> associatedEids = getEidsForEmail(newWaiterEidOrEmail.trim());
        if (associatedEids.size() > 1) {
            Utilities.addErrorMessage(MessageFormat.format(Utilities.rb.getString("exception.multiple.eids"),
                    new Object[] { newWaiterEidOrEmail, StringUtils.join(associatedEids, ", ") }));
            return ORGANIZER_MEETING_PAGE_URL;
        }

        String waiterUserId = getUserIdForEidOrEmail(newWaiterEidOrEmail.trim());
        if (StringUtils.isBlank(waiterUserId)) {
            Utilities.addErrorMessage(Utilities.rb.getString("exception.no.such.user") + newWaiterEidOrEmail);
            return ORGANIZER_MEETING_PAGE_URL;
        }

        SignupUser waiterSignUser = getSakaiFacade().getSignupUser(getMeetingWrapper().getMeeting(), waiterUserId);
        if (waiterSignUser == null) {
            Utilities.addErrorMessage(MessageFormat.format(Utilities.rb.getString("user.has.no.permission.attend"),
                    new Object[] { newWaiterEidOrEmail }));
            return ORGANIZER_MEETING_PAGE_URL;
        }

        SignupAttendee newWaiter = new SignupAttendee(waiterUserId, waiterSignUser.getMainSiteId());
        SignupMeeting meeting = null;
        try {
            AddWaiter addWaiter = new AddWaiter(signupMeetingService, currentUserId(), currentSiteId(),
                    getListOperationType(), true);
            meeting = addWaiter.addToWaitingList(getMeetingWrapper().getMeeting(), timeslotWrapper.getTimeSlot(),
                    newWaiter);
            /* reset */
            resetWaitingListPendingType();
        } catch (SignupUserActionException ue) {
            Utilities.addErrorMessage(ue.getMessage());
        } catch (Exception e) {
            logger.error(Utilities.rb.getString("error.occurred_try_again") + " - " + e.getMessage());
            Utilities.addErrorMessage(Utilities.rb.getString("error.occurred_try_again"));
        }

        // TODO calendar event id;

        return updateMeetingwrapper(meeting, ORGANIZER_MEETING_PAGE_URL);
    }

    private void resetWaitingListPendingType() {
        setListPendingType(null);
    }

    /**
     * This is a JSF action call method by UI to remove the attendee from the
     * waiting list in the event/meeting.
     * 
     * @return an action outcome string.
     */
    public String removeAttendeeFromWList() {
        TimeslotWrapper timeslotWrapper = (TimeslotWrapper) timeslotWrapperTable.getRowData();
        String removedUserId = (String) Utilities.getRequestParam(ATTENDEE_USER_ID);

        if (StringUtils.isBlank(removedUserId)) {
            return ORGANIZER_MEETING_PAGE_URL;
        }
        SignupAttendee removedWaiter = new SignupAttendee(removedUserId, currentSiteId());
        SignupMeeting meeting = null;

        try {
            RemoveWaiter removeWaiter = new RemoveWaiter(signupMeetingService, currentUserId(), currentSiteId(),
                    getListOperationType(), true);
            meeting = removeWaiter.removeFromWaitingList(getMeetingWrapper().getMeeting(),
                    timeslotWrapper.getTimeSlot(), removedWaiter);
        } catch (SignupUserActionException ue) {
            Utilities.addErrorMessage(ue.getMessage());
        } catch (Exception e) {
            logger.error(Utilities.rb.getString("error.occurred_try_again") + " - " + e.getMessage());
            Utilities.addErrorMessage(Utilities.rb.getString("error.occurred_try_again"));
        }

        return updateMeetingwrapper(meeting, ORGANIZER_MEETING_PAGE_URL);
    }

    /**
     * This is a JSF getter method to get the selected attendee.
     * 
     * @return an UIData object.
     */
    public UIData getAttendeeWrapperTable() {
        return attendeeWrapperTable;
    }

    /**
     * This is a setter.
     * 
     * @param attendeeWrapperTable
     *            an UIData object, which is selected by user.
     */
    public void setAttendeeWrapperTable(UIData attendeeWrapperTable) {
        this.attendeeWrapperTable = attendeeWrapperTable;
    }

    /**
     * This is a getter method for UI.
     * 
     * @return an UIData object.
     */
    public UIData getWaiterWrapperTable() {
        return waiterWrapperTable;
    }

    /**
     * This is a setter for UI.
     * 
     * @param waiterWrapperTable
     *            an UIData object.
     */
    public void setWaiterWrapperTable(UIData waiterWrapperTable) {
        this.waiterWrapperTable = waiterWrapperTable;
    }

    /**
     * This is getter for UI.
     * 
     * @return a list of SelectItem objects.
     */
    public List<SelectItem> getAllAttendees() {
        return allAttendees;
    }

    /**
     * This is a setter.
     * 
     * @param allAttendees
     */
    public void setAllAttendees(List<SelectItem> allAttendees) {
        this.allAttendees = allAttendees;
    }

    /**
     * This is a getter for UI.
     * 
     * @return a constant string.
     */
    public String getAttendeeUserId() {
        return ATTENDEE_USER_ID;
    }

    /**
     * This is a getter method for UI.
     * 
     * @return a string value.
     */
    public String getSelectedEditTimeslotId() {
        return selectedEditTimeslotId;
    }

    /**
     * This is a setter.
     * 
     * @param selectedEditTimeslotId
     *            a time slot Id string.
     */
    public void setSelectedEditTimeslotId(String selectedEditTimeslotId) {
        this.selectedEditTimeslotId = selectedEditTimeslotId;
    }

    /**
     * This is a getter for UI.
     * 
     * @return a constant string.
     */
    public String getMoveAction() {
        return MOVE_ACTION;
    }

    /**
     * This is a getter for UI.
     * 
     * @return a constant string
     */
    public String getReplaceAction() {
        return REPLACE_ACTION;
    }

    /**
     * This is a getter for UI.
     * 
     * @return a constant string.
     */
    public String getSwapAction() {
        return SWAP_ACTION;
    }

    /**
     * This is a getter for UI.
     * 
     * @return a constant string.
     */
    public String getOnTopList() {
        return ON_TOP_LIST;
    }

    /**
     * This is a getter for UI.
     * 
     * @return a constant string.
     */
    public String getOnBottomList() {
        return ON_BOTTOM_LIST;
    }

    /**
     * This is a getter.
     * 
     * @return a selected action string.
     */
    public String getSelectedAction() {
        return selectedAction;
    }

    /**
     * This is a setter.
     * 
     * @param selectedAction
     *            a string value.
     */
    public void setSelectedAction(String selectedAction) {
        this.selectedAction = selectedAction;
    }

    /**
     * This is a getter for UI.
     * 
     * @return an UIInput object.
     */
    public UIInput getReplacedAttendeeEidOrEmail() {
        return replacedAttendeeEidOrEmail;
    }

    /**
     * This is a setter.
     * 
     * @param replacedAttendeeEid
     *            an UIInput object.
     */
    public void setReplacedAttendeeEidOrEmail(UIInput replacedAttendeeEidOrEmail) {
        this.replacedAttendeeEidOrEmail = replacedAttendeeEidOrEmail;
    }

    /**
     * This is a getter.
     * 
     * @return an UIInput object.
     */
    public UIInput getSelectedTimeslotId() {
        return selectedTimeslotId;
    }

    /**
     * This is a setter.
     * 
     * @param selectedTimeslotId
     *            a time slot Id string.
     */
    public void setSelectedTimeslotId(UIInput selectedTimeslotId) {
        this.selectedTimeslotId = selectedTimeslotId;
    }

    /**
     * Check if the action is a add-new-attendee.
     */
    public boolean isAddNewAttendee() {
        return addNewAttendee;
    }

    /**
     * This is a setter.
     * 
     * @param addNewAttendee
     *            a boolean value.
     */
    public void setAddNewAttendee(boolean addNewAttendee) {
        this.addNewAttendee = addNewAttendee;
    }

    /**
     * This is a getter.
     * 
     * @return am UIInput object.
     */
    public UIInput getAddNewAttendeeUserEidOrEmail() {
        return addNewAttendeeUserEidOrEmail;
    }

    /**
     * This is a setter.
     * 
     * @param addNewAttendeeUserEidOrEmail
     *            an attendee's Eid or email string.
     */
    public void setAddNewAttendeeUserEidOrEmail(UIInput addNewAttendeeUserEidOrEmail) {
        this.addNewAttendeeUserEidOrEmail = addNewAttendeeUserEidOrEmail;
    }

    /**
     * This is a getter.
     * 
     * @return a user-first selected attendee eid string.
     */
    public String getSelectedFirstUser() {
        return selectedFirstUser;
    }

    /**
     * This is a setter.
     * 
     * @param selectedFirstUser
     *            an eid stirng.
     */
    public void setSelectedFirstUser(String selectedFirstUser) {
        this.selectedFirstUser = selectedFirstUser;
    }

    /**
     * This is a getter for UI.
     * 
     * @return a action type string.
     */
    public String getUserActionType() {
        return userActionType;
    }

    /**
     * This is a setter.
     * 
     * @param userActionType
     *            a action type string.
     */
    public void setUserActionType(String userActionType) {
        this.userActionType = userActionType;
    }

    /**
     * This is a getter, which tells how to pend an attendee into a waiting list
     * (on top or on bottom).
     * 
     * @return an UIInput object.
     */
    public UIInput getListPendingType() {
        return listPendingType;
    }

    /**
     * This is a setter.
     * 
     * @param listPendingType
     *            a pending type string (on top or on bottom).
     */
    public void setListPendingType(UIInput listPendingType) {
        this.listPendingType = listPendingType;
    }

    private String getListOperationType() {
        if (this.listPendingType != null && this.listPendingType.getValue() != null
                && ((String) this.listPendingType.getValue()).trim().length() > 0)
            return ((String) this.listPendingType.getValue()).trim();

        return ON_BOTTOM_LIST;
    }

    private String currentUserId() {
        if (this.currentUserId == null)
            currentUserId = sakaiFacade.getCurrentUserId();

        return currentUserId;
    }

    private String currentSiteId() {
        if (this.currentSiteId == null)
            currentSiteId = sakaiFacade.getCurrentLocationId();

        return currentSiteId;
    }

    /**
     * This is a getter method.
     * 
     * @return a CopyMeetingSignupMBean object.
     */
    public CopyMeetingSignupMBean getCopyMeetingMBean() {
        return copyMeetingMBean;
    }

    /**
     * This is a setter.
     * 
     * @param copyMeetingMBean
     *            a CopyMeetingSignupMBean object.
     */
    public void setCopyMeetingMBean(CopyMeetingSignupMBean copyMeetingMBean) {
        this.copyMeetingMBean = copyMeetingMBean;
    }

    /**
     * This is a getter method.
     * 
     * @return an EditMeetingSignupMBean object.
     */
    public EditMeetingSignupMBean getEditMeetingMBean() {
        return editMeetingMBean;
    }

    /**
     * This is a setter.
     * 
     * @param editMeetingMBean
     *            an EditMeetingSignupMBean object.
     */
    public void setEditMeetingMBean(EditMeetingSignupMBean editMeetingMBean) {
        this.editMeetingMBean = editMeetingMBean;
    }

    /**
     * This is a getter method.
     * 
     * @return a EditCommentSignupMBean object.
     */
    public EditCommentSignupMBean getEditCommentMBean() {
        return editCommentMBean;
    }

    /**
     * This is a setter.
     * 
     * @param editCommentMBean
     *            a EditCommentSignupMBean object.
     */
    public void setEditCommentMBean(EditCommentSignupMBean editCommentMBean) {
        this.editCommentMBean = editCommentMBean;
    }

    /**
     * Check if the user input mode is Eid mode or not.
     * 
     * @return true if the user input mode is Eid mode
     */
    public boolean isEidInputMode() {
        return eidInputMode;
    }

    /**
     * This is a setter.
     * 
     * @param eidInputMode
     *            a boolean value.
     */
    public void setEidInputMode(boolean eidInputMode) {
        this.eidInputMode = eidInputMode;
    }

    /**
     * This is for javascrip UI only.
     * 
     * @return empty string.
     */
    public String getUserInputEidOrEmail() {
        return "";
    }

    /**
     * This is a setter.
     * 
     * @param value
     *            an user input eid or email address value .
     */
    public void setUserInputEidOrEmail(String value) {
        if (StringUtils.isNotBlank(value)) {
            this.eidOrEmailByUser = value;
        }
    }

    /**
     * It's a getter method for UI.
     * 
     * @return a list of SignupSite objects.
     */
    public List<SignupSite> getPublishedSignupSites() {
        return getMeetingWrapper().getMeeting().getSignupSites();
    }

    /* proxy method */
    private String getEidOrEmailInputByUser() {
        String value = this.eidOrEmailByUser;
        this.eidOrEmailByUser = null;// reset for only use once
        return value;
    }

    /**
     * It's a getter method for UI
     * 
     * @return a boolean value
     */
    public boolean isCollapsedMeetingInfo() {
        return collapsedMeetingInfo;
    }

    /**
     * This is a setter method for UI.
     * 
     * @param collapsedMeetingInfo
     *            a boolean value
     */
    public void setCollapsedMeetingInfo(boolean collapsedMeetingInfo) {
        this.collapsedMeetingInfo = collapsedMeetingInfo;
    }

    /**
     * This is to retrieve attr from UI Commandbutton
     * 
     * @param timeslottoGroup
     *            a String value
     */
    public void attrListener(ActionEvent event) {

        timeslottoGroup = (String) Utilities.getActionAttribute(event, "timeslottoGroup");

    }

    /**
     * It's a getter method for UI
     * denotes the direction of the synchronize process
     * @return a String value timeslottoGroup
     */
    public String gettimeslottoGroup() {
        return timeslottoGroup;
    }

    /**
     * This is a setter
     *  @return a String value
     */
    public void setNickname(String timeslottoGroup) {
        this.timeslottoGroup = timeslottoGroup;
    }

    /**
     * Synchronise the users in a timeslot with the users in a group.
     * VT customized to allow the user choose the synchronize direction
         
     * @return url to the same page which will trigger a reload
     */
    public String synchroniseGroupMembership() {

        TimeslotWrapper timeslotWrapper = (TimeslotWrapper) timeslotWrapperTable.getRowData();

        //get groupId for timeslot
        String groupId = timeslotWrapper.getGroupId();
        SignupMeeting meeting = null;

        if (StringUtils.isBlank(groupId)) {
            //TODO. 
            //Create the group. Grab the list of attendees in the timeslot and add all at once.
            //Will need to also save the groupId into the timeslot.
            //For now, we just give a message.

            Utilities.addErrorMessage(Utilities.rb.getString("error.no_group_for_timeslot"));
            return ORGANIZER_MEETING_PAGE_URL;
        } else {
            List<String> attendeeUserIds = convertAttendeeWrappersToUuids(timeslotWrapper.getAttendeeWrappers());

            //process to synchronize the time slot attendees to group

            if (timeslottoGroup != null && !timeslottoGroup.trim().isEmpty()
                    && !sakaiFacade.addUsersToGroup(attendeeUserIds, currentSiteId(), groupId, timeslottoGroup)) {
                Utilities.addErrorMessage(Utilities.rb.getString("error.group_sync_failed"));
                return ORGANIZER_MEETING_PAGE_URL;
            }

            //retrieve all members in group
            List<String> groupMembers = sakaiFacade.getGroupMembers(currentSiteId(), groupId);

            //process to synchronize from site group members to time slot
            if (timeslottoGroup == null || timeslottoGroup.isEmpty()) {

                //1. first to keep the common members of timeslot and group

                List<String> commonmem = new ArrayList<String>(attendeeUserIds);
                commonmem.retainAll(groupMembers);

                //2. only add the group members not existed in timeslot
                groupMembers.removeAll(attendeeUserIds);

                //3. remove the time slot attendees that existed only in timeslot
                try {
                    for (String mem : attendeeUserIds) {
                        if (!commonmem.contains(mem)) {
                            CancelAttendee remove = new CancelAttendee(signupMeetingService, currentUserId(),
                                    currentSiteId(), true);
                            SignupAttendee removedAttendee = new SignupAttendee(mem, currentSiteId());
                            meeting = remove.cancelSignup(getMeetingWrapper().getMeeting(),
                                    timeslotWrapper.getTimeSlot(), removedAttendee);
                            if (sendEmail) {
                                try {
                                    signupMeetingService.sendEmailToParticipantsByOrganizerAction(
                                            remove.getSignupEventTrackingInfo());
                                } catch (Exception e) {
                                    logger.error(Utilities.rb.getString("email.exception") + " - " + e.getMessage(),
                                            e);
                                    Utilities.addErrorMessage(Utilities.rb.getString("email.exception"));
                                }
                            }
                        }
                    }
                } catch (SignupUserActionException ue) {
                    Utilities.addErrorMessage(ue.getMessage());
                } catch (Exception e) {
                    logger.error(Utilities.rb.getString("error.occurred_try_again") + " - " + e.getMessage());
                    Utilities.addErrorMessage(Utilities.rb.getString("error.occurred_try_again"));
                }
            } else {
                //remove all of the existing attendees from this list to remove duplicates
                groupMembers.removeAll(attendeeUserIds);
            }

            //add members and go to return page
            return addAttendeesToTimeslot(currentSiteId(), timeslotWrapper, groupMembers);
        }
    }

    /**
     * Helper to add users to a timeslot and get the return URL
     * @param userId
     * @return
     */
    private String addAttendeesToTimeslot(String siteId, TimeslotWrapper timeslotWrapper, List<String> userIds) {

        boolean errors = false;
        SignupMeeting meeting = null;

        //foreach userId, add to timeslot
        for (String userId : userIds) {

            SignupAttendee attendee = new SignupAttendee(userId, siteId);
            timeslotWrapper.setNewAttendee(attendee);

            try {
                AddAttendee addAttendee = new AddAttendee(signupMeetingService, currentUserId(), currentSiteId(),
                        true);
                meeting = addAttendee.signup(getMeetingWrapper().getMeeting(), timeslotWrapper.getTimeSlot(),
                        timeslotWrapper.getNewAttendee());

                if (sendEmail) {
                    try {
                        signupMeetingService
                                .sendEmailToParticipantsByOrganizerAction(addAttendee.getSignupEventTrackingInfo());
                    } catch (Exception e) {
                        logger.error(Utilities.rb.getString("email.exception") + " - " + e.getMessage(), e);
                    }
                }
            } catch (SignupUserActionException ue) {
                Utilities.addErrorMessage(ue.getMessage());
                logger.error(ue.getMessage());
                errors = true;
                break;

            } catch (Exception e) {
                logger.error(Utilities.rb.getString("error.occurred_try_again") + " - " + e.getMessage(), e);
                errors = true;
                break;
            }
        }

        if (errors) {
            Utilities.addErrorMessage(Utilities.rb.getString("error.occurred_try_again"));
            return ORGANIZER_MEETING_PAGE_URL;
        }

        String nextPage = updateMeetingwrapper(meeting, ORGANIZER_MEETING_PAGE_URL);

        if (ORGANIZER_MEETING_PAGE_URL.equals(nextPage)) {
            setAddNewAttendee(false);
            setSelectedTimeslotId(null);

            Utilities.addInfoMessage(Utilities.rb.getString("group_synchronise_done"));
        }
        return nextPage;
    }

    /**
     * Helper to check if we need to show the email link
     * If we have email addresses, then the link shows up.
     * @return true/false
     */
    public boolean isShowEmailAllAttendeesLink() {
        return StringUtils.isNotBlank(getAllAttendeesEmailAddressesFormatted());
    }

    /**
     * Helper to get the email address for the current user
     * @return
     */
    public String getCurrentUserEmailAddress() {
        return sakaiFacade.getUser(sakaiFacade.getCurrentUserId()).getEmail();
    }
}