org.sakaiproject.signup.tool.jsf.organizer.action.EditMeeting.java Source code

Java tutorial

Introduction

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

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.sakaiproject.component.cover.ServerConfigurationService;
import org.sakaiproject.signup.logic.SakaiFacade;
import org.sakaiproject.signup.logic.SignupEmailFacade;
import org.sakaiproject.signup.logic.SignupMeetingService;
import org.sakaiproject.signup.logic.SignupUserActionException;
import org.sakaiproject.signup.logic.messages.SignupEventTrackingInfoImpl;
import org.sakaiproject.signup.model.MeetingTypes;
import org.sakaiproject.signup.model.SignupAttachment;
import org.sakaiproject.signup.model.SignupAttendee;
import org.sakaiproject.signup.model.SignupMeeting;
import org.sakaiproject.signup.model.SignupTimeslot;
import org.sakaiproject.signup.tool.jsf.TimeslotWrapper;
import org.sakaiproject.signup.tool.jsf.attachment.AttachmentHandler;
import org.sakaiproject.signup.tool.jsf.organizer.UserDefineTimeslotBean;
import org.sakaiproject.signup.tool.util.Utilities;
import org.sakaiproject.signup.util.SignupDateFormat;
import org.springframework.dao.OptimisticLockingFailureException;

/**
 * <p>
 * This class will provide business logic for modifying meeting action
 * by organizer.
 * </P>
 */
public class EditMeeting extends SignupAction implements MeetingTypes {

    private int maxNumOfAttendees;

    private boolean showAttendeeName;

    private int currentNumberOfSlots;

    private int timeSlotDuration;

    private boolean unlimited;

    // private int totalEventDuration;// for group/announcement types

    private SignupMeeting originalMeetingCopy;

    private String signupBeginsType;

    /* singup can start before this minutes/hours/days */
    private int signupBegins;

    /*True means that the current recurring events have already start_Now Type*/
    private boolean isStartNowTypeForRecurEvents = false;

    private boolean signupBeginModifiedByUser;

    private String deadlineTimeType;

    /* singup deadline before this minutes/hours/days */
    private int deadlineTime;

    private boolean convertToNoRecurrent;

    private List<SignupMeeting> savedMeetings;

    private String recurringType = null;

    // may not used
    private Date firstRecurMeetingModifyDate = null;

    private Date lastRecurMeetingModifyDate = null;

    private List<SignupAttachment> currentAttachList;

    private List<SignupAttachment> needToCleanUpAttachInContentHS = new ArrayList<SignupAttachment>();

    private List<SignupAttachment> attachsForFutureRecurrences = new ArrayList<SignupAttachment>();

    private AttachmentHandler attachmentHandler;

    /*keep the final user-modified ts-list*/
    private List<TimeslotWrapper> customTimeSlotWrpList = null;

    private boolean userDefinedTS = false;

    private UserDefineTimeslotBean userDefineTimeslotBean;

    private List<SignupTimeslot> toBedeletedTSList = null;

    private SakaiFacade sakaiFacade;

    private boolean createGroups;

    private String coordinators;

    private boolean sendEmailByOwner;

    //it make sure not running this job multiple times
    private boolean hasProcessedSyn_createGroups_job = false;

    public EditMeeting(String userId, String siteId, SignupMeetingService signupMeetingService,
            AttachmentHandler attachmentHandler, boolean isOrganizer) {
        super(userId, siteId, signupMeetingService, isOrganizer);
        this.attachmentHandler = attachmentHandler;
    }

    public void saveModifiedMeeting(SignupMeeting meeting) throws Exception {
        handleVersion(meeting);

        /*
         * give a warning to user and for announcement type no update is
         * required
         */
        if (!unlimited && this.maxNumOfAttendees < originalMeetingCopy.getMaxNumberOfAttendees()) {
            Utilities.addErrorMessage(
                    Utilities.rb.getString("max.num_attendee_changed_and_attendee_mayOver_limit_inTS"));
        }

        logger.info("Meeting Name:" + meeting.getTitle() + " - UserId:" + userId
                + " - has modified the meeting at meeting time:"
                + SignupDateFormat.format_date_h_mm_a(meeting.getStartTime()));
        if (getMaxNumOfAttendees() > this.originalMeetingCopy.getMaxNumberOfAttendees())
            logger.info("Meeting Name:" + meeting.getTitle() + " - UserId:" + userId
                    + this.signupEventTrackingInfo.getAllAttendeeTransferLogInfo());

        /*cleanup unused attachments*/
        if (needToCleanUpAttachInContentHS != null) {
            for (SignupAttachment one : needToCleanUpAttachInContentHS) {
                getAttachmentHandler().removeAttachmentInContentHost(one);
            }
        }
    }

    /*
     * give it a number of tries to update the event/meeting object into DB
     * storage if this still satisfy the pre-condition regardless some changes
     * in DB storage
     */
    public void handleVersion(SignupMeeting meeting) throws Exception {
        for (int i = 0; i < MAX_NUMBER_OF_RETRY; i++) {
            try {

                this.signupEventTrackingInfo = new SignupEventTrackingInfoImpl();
                /*
                 * TODO current eventTracking is not working for multiple
                 * recurring meetings. It's a bit more complex and need modify
                 * the SignupEventTrackingInfoImpl object or other way.
                 * Currently, no email are sent out to newly promoted people for
                 * multiple recurring meetings.
                 */
                this.signupEventTrackingInfo.setMeeting(meeting);
                //reset it
                this.toBedeletedTSList = null;

                List<SignupMeeting> sMeetings = prepareModify(meeting);
                signupMeetingService.updateModifiedMeetings(sMeetings, this.toBedeletedTSList, true);
                //signupMeetingService.updateMSignupMeetings(sMeetings, this.toBedeletedTSList, true);
                setSavedMeetings(sMeetings);// for email & postCalendar purpose
                return;
            } catch (OptimisticLockingFailureException oe) {
                // don't do any thing
            }
        }
        throw new SignupUserActionException(Utilities.rb.getString("edit.failed_due_to_other_updated_db"));
    }

    private SignupMeeting reloadMeeting(SignupMeeting meeting) {
        return signupMeetingService.loadSignupMeeting(meeting.getId(), userId, siteId);
    }

    /* put the modification into right place */
    private List<SignupMeeting> prepareModify(SignupMeeting userModifiedMeeting) throws Exception {
        List<SignupMeeting> upTodateOrginMeetings = null;
        List<SignupMeeting> newlyModifyMeetings = new ArrayList<SignupMeeting>();

        Long recurrenceId = userModifiedMeeting.getRecurrenceId();
        if (recurrenceId != null && recurrenceId.longValue() > 0 && !isConvertToNoRecurrent()) {
            /* only update the future recurring meeting now now today */
            upTodateOrginMeetings = signupMeetingService.getRecurringSignupMeetings(siteId, userId, recurrenceId,
                    new Date());
            retrieveRecurrenceData(upTodateOrginMeetings);
        } else {
            SignupMeeting upTodateOrginMeeting = reloadMeeting(userModifiedMeeting);
            upTodateOrginMeetings = new ArrayList<SignupMeeting>();
            upTodateOrginMeetings.add(upTodateOrginMeeting);
        }

        /*
         * Since recurring meetings are identical by title, location, etc. only
         * the corresponding one to original one will be checked here.
         */
        /*
         * If someone has changed it before you,it should be caught by versionId
         * since meetings are saved as a bulk.
         */
        SignupMeeting upToDateMatchOne = getCorrespondingMeeting(upTodateOrginMeetings, userModifiedMeeting);
        checkPreCondition(upToDateMatchOne, upTodateOrginMeetings);

        Calendar calendar = Calendar.getInstance();
        int recurNum = 0;
        //initialize to-be-removed TS list for advanced user-defined cases
        this.toBedeletedTSList = new ArrayList<SignupTimeslot>();
        for (SignupMeeting upTodateOrginMeeting : upTodateOrginMeetings) {

            SignupMeeting newlyModifyMeeting = upTodateOrginMeeting;

            /*
             * set event starting time for calendar due to multiple-recurring
             * meetings
             */
            long startTimeChangeDiff = userModifiedMeeting.getStartTime().getTime()
                    - getOriginalMeetingCopy().getStartTime().getTime();
            calendar.setTimeInMillis(upTodateOrginMeeting.getStartTime().getTime() + startTimeChangeDiff);

            /* pass user changes */
            passModifiedValues(userModifiedMeeting, calendar, newlyModifyMeeting, recurNum);
            recurNum++;
            newlyModifyMeetings.add(newlyModifyMeeting);
        }

        return newlyModifyMeetings;
    }

    private void passModifiedValues(SignupMeeting modifiedMeeting, Calendar calendar,
            SignupMeeting newlyModifyMeeting, int recurNum) throws Exception {
        //for Group title synch purpose
        boolean hasMeetingTitleChanged = false;
        if (!newlyModifyMeeting.getTitle().equals(modifiedMeeting.getTitle())) {
            hasMeetingTitleChanged = true;
        }
        newlyModifyMeeting.setTitle(modifiedMeeting.getTitle());
        newlyModifyMeeting.setLocation(modifiedMeeting.getLocation());
        newlyModifyMeeting.setCategory(modifiedMeeting.getCategory());
        newlyModifyMeeting.setCreatorUserId(modifiedMeeting.getCreatorUserId());
        newlyModifyMeeting.setStartTime(calendar.getTime());
        newlyModifyMeeting.setDescription(modifiedMeeting.getDescription());
        newlyModifyMeeting.setLocked(modifiedMeeting.isLocked());
        newlyModifyMeeting.setCanceled(modifiedMeeting.isCanceled());
        newlyModifyMeeting.setReceiveEmailByOwner(modifiedMeeting.isReceiveEmailByOwner());
        newlyModifyMeeting.setAllowWaitList(modifiedMeeting.isAllowWaitList());
        newlyModifyMeeting.setAllowComment(modifiedMeeting.isAllowComment());
        newlyModifyMeeting.setAutoReminder(modifiedMeeting.isAutoReminder());
        newlyModifyMeeting.setEidInputMode(modifiedMeeting.isEidInputMode());
        newlyModifyMeeting.setAllowAttendance(modifiedMeeting.isAllowAttendance());
        newlyModifyMeeting.setCreateGroups(modifiedMeeting.isCreateGroups());
        newlyModifyMeeting.setMaxNumOfSlots(modifiedMeeting.getMaxNumOfSlots());

        newlyModifyMeeting.setSendEmailByOwner(isSendEmailByOwner());
        newlyModifyMeeting.setCoordinatorIds(getCoordinators());

        /*new attachments changes*/
        if (this.currentAttachList != null) {
            updateWithOrigalAttachments(newlyModifyMeeting, this.currentAttachList, recurNum);//what to do with recurrence
        }

        if (newlyModifyMeeting.getMeetingType().equals(INDIVIDUAL) && !isUserDefinedTS()) {
            List<SignupTimeslot> timeslots = newlyModifyMeeting.getSignupTimeSlots();
            /* add increased time slots */
            int newAddTs = getCurrentNumberOfSlots() - timeslots.size();
            for (int i = 0; i < newAddTs; i++) {
                SignupTimeslot newTs = new SignupTimeslot();
                newTs.setMaxNoOfAttendees(maxNumOfAttendees);
                newTs.setDisplayAttendees(showAttendeeName);
                timeslots.add(newTs);
            }
            int rownum = 1;
            for (SignupTimeslot timeslot : timeslots) {
                timeslot.setMaxNoOfAttendees(maxNumOfAttendees);
                timeslot.setDisplayAttendees(showAttendeeName);
                timeslot.setStartTime(calendar.getTime());
                calendar.add(Calendar.MINUTE, timeSlotDuration);
                timeslot.setEndTime(calendar.getTime());
                if (!newlyModifyMeeting.isAllowWaitList()) {
                    timeslot.setWaitingList(null);
                }

                //create the groups for the timeslots, if enabled
                //this also loads the groupId into the timeslot object to be saved afterwards
                if (isCreateGroups() && !this.hasProcessedSyn_createGroups_job) {
                    logger.error("Timeslot groupId: " + timeslot.getGroupId());
                    boolean justCreated = false;
                    //if we don't already have a group for this timeslot, or the group has been deleted, create a group and set into timeslot
                    if (StringUtils.isBlank(timeslot.getGroupId()) || !sakaiFacade
                            .checkForGroup(sakaiFacade.getCurrentLocationId(), timeslot.getGroupId())) {
                        logger.error("Need to create a group for timeslot... ");
                        String title = generateGroupTitle(newlyModifyMeeting.getTitle(), timeslot, rownum);
                        String description = generateGroupDescription(newlyModifyMeeting.getTitle(), timeslot);
                        String groupId = sakaiFacade.createGroup(sakaiFacade.getCurrentLocationId(), title,
                                description, null);
                        logger.error("Created group for timeslot: " + groupId);
                        timeslot.setGroupId(groupId);
                        justCreated = true;
                    }

                    //Synch the group title with new meeting title
                    if (hasMeetingTitleChanged && !justCreated) {
                        //use case: if the group title has been changes via Site-Info tool by removing GROUP_PREFIX : "SIGNUP_", 
                        //it will be not synch any more for that group.
                        String newTitle = generateGroupTitle(newlyModifyMeeting.getTitle(), timeslot, rownum);
                        boolean success = this.sakaiFacade.synchonizeGroupTitle(sakaiFacade.getCurrentLocationId(),
                                timeslot.getGroupId(), newTitle);
                        if (!success) {
                            Utilities.addErrorMessage(Utilities.rb.getString("error.no.change.group.title"));
                        }
                    }
                }

                rownum++;
            }

            if (isCreateGroups()) {
                //JUST NEED TO RUN ONECE, the sequential recurrence meetings need not to create/sync groups again.
                this.hasProcessedSyn_createGroups_job = true;
            }
        }

        /*process user custom-defined timeslot blocks*/
        if (newlyModifyMeeting.getMeetingType().equals(CUSTOM_TIMESLOTS) || isUserDefinedTS()) {
            List<SignupTimeslot> timeslots = newlyModifyMeeting.getSignupTimeSlots();
            UserDefineTimeslotBean uBean = getUserDefineTimeslotBean();
            if (uBean == null || !uBean.MODIFY_MEETING.equals(uBean.getPlaceOrderBean())) {
                throw new SignupUserActionException(
                        MessageFormat.format(Utilities.rb.getString("you.have.multiple.tabs.in.browser"),
                                new Object[] { ServerConfigurationService.getServerName() }));
            }

            uBean.modifyTimesSlotsWithChanges(this.customTimeSlotWrpList, timeslots, calendar, showAttendeeName,
                    toBedeletedTSList);
            if (recurNum == 0) {
                /*TODO currently we only notify attendee for the first event at recurrence
                 * Need implementation for recurrence case*/
                notifyAttendeeIndeleteTimeslots(toBedeletedTSList);
            }
            newlyModifyMeeting.setMeetingType(CUSTOM_TIMESLOTS);
            /*for end time purpose*/
            int duration = getUserDefineTimeslotBean().getEventDuration();
            calendar.add(Calendar.MINUTE, duration);
        }

        if (newlyModifyMeeting.getMeetingType().equals(GROUP)) {
            List<SignupTimeslot> signupTimeSlots = newlyModifyMeeting.getSignupTimeSlots();
            SignupTimeslot timeslot = signupTimeSlots.get(0);
            timeslot.setStartTime(newlyModifyMeeting.getStartTime());

            calendar.add(Calendar.MINUTE, getTimeSlotDuration());
            timeslot.setEndTime(calendar.getTime());
            timeslot.setDisplayAttendees(showAttendeeName);
            int maxAttendees = (unlimited) ? SignupTimeslot.UNLIMITED : maxNumOfAttendees;
            timeslot.setMaxNoOfAttendees(maxAttendees);
            if (!newlyModifyMeeting.isAllowWaitList()) {
                timeslot.setWaitingList(null);
            }
        }

        if (newlyModifyMeeting.getMeetingType().equals(ANNOUNCEMENT)) {
            calendar.add(Calendar.MINUTE, getTimeSlotDuration());

        }
        /*
         * Promoting waiter to attendee status if max size increased and is
         * allowed
         */
        if (maxNumOfAttendees > originalMeetingCopy.getMaxNumberOfAttendees()) {
            promotingWaiters(newlyModifyMeeting);
        }

        // set up meeting end time
        newlyModifyMeeting.setEndTime(calendar.getTime());

        /* setup sign-up begin / deadline */
        setSignupBeginDeadlineData(newlyModifyMeeting, getSignupBegins(), getSignupBeginsType(), getDeadlineTime(),
                getDeadlineTimeType());

        /* This can happen only once to promote recurring meeting to stand-alone */
        if (isConvertToNoRecurrent()) {
            newlyModifyMeeting.setRecurrenceId(null);
        }

        if (newlyModifyMeeting.getRecurrenceId() != null) {
            newlyModifyMeeting.setRepeatType(getRecurringType());
            newlyModifyMeeting.setRepeatUntil(getLastRecurMeetingModifyDate());
        }
    }

    private void notifyAttendeeIndeleteTimeslots(List<SignupTimeslot> toBedeletedTSList) {
        for (SignupTimeslot timeslot : toBedeletedTSList) {
            List<SignupAttendee> attendees = timeslot.getAttendees();
            if (attendees != null && !attendees.isEmpty()) {
                for (SignupAttendee attendee : attendees) {
                    signupEventTrackingInfo.addOrUpdateAttendeeAllocationInfo(attendee, timeslot,
                            SignupEmailFacade.SIGNUP_ATTENDEE_CANCEL, true);
                }
            }
        }
    }

    private SignupMeeting getCorrespondingMeeting(List<SignupMeeting> upTodateOrginMeetings,
            SignupMeeting modifyMeeting) {
        for (SignupMeeting sm : upTodateOrginMeetings) {
            if (sm.getId().equals(modifyMeeting.getId()))
                return sm;
        }
        return null;
    }

    /* Promoting all possible waiters to attendee status */
    private void promotingWaiters(SignupMeeting modifiedMeeting) {
        List<SignupTimeslot> timeSlots = modifiedMeeting.getSignupTimeSlots();
        if (timeSlots == null || timeSlots.isEmpty())
            return;

        for (SignupTimeslot timeslot : timeSlots) {
            List attList = timeslot.getAttendees();
            int maxNumOfAttendees = timeslot.getMaxNoOfAttendees();
            if (attList == null)
                continue;// nobody on wait list for sure

            if (attList.size() < maxNumOfAttendees) {
                int availNum = maxNumOfAttendees - attList.size();
                for (int i = 0; i < availNum; i++) {
                    promoteAttendeeFromWaitingList(modifiedMeeting, timeslot);
                }
            }
        }

    }

    /*
     * Check if there is any update in DB before this one is saved into DB
     * storage.
     */
    private void checkPreCondition(SignupMeeting upTodateMeeting, List<SignupMeeting> upTodateOrginMeetings)
            throws SignupUserActionException {
        if (upTodateMeeting == null || !originalMeetingCopy.getTitle().equals(upTodateMeeting.getTitle())
                || !originalMeetingCopy.getLocation().equals(upTodateMeeting.getLocation())
                || !StringUtils.equals(originalMeetingCopy.getCategory(), upTodateMeeting.getCategory())
                || !StringUtils.equals(originalMeetingCopy.getCreatorUserId(), upTodateMeeting.getCreatorUserId())
                || originalMeetingCopy.getStartTime().getTime() != upTodateMeeting.getStartTime().getTime()
                || originalMeetingCopy.getEndTime().getTime() != upTodateMeeting.getEndTime().getTime()
                || originalMeetingCopy.getSignupBegins().getTime() != upTodateMeeting.getSignupBegins().getTime()
                || originalMeetingCopy.getSignupDeadline().getTime() != upTodateMeeting.getSignupDeadline()
                        .getTime()
                /* TODO more case to consider here */
                || !originalMeetingCopy.getMeetingType().equals(upTodateMeeting.getMeetingType())
                || !((originalMeetingCopy.getRecurrenceId() == null && upTodateMeeting.getRecurrenceId() == null)
                        || (originalMeetingCopy.getRecurrenceId() != null
                                && originalMeetingCopy.getRecurrenceId().equals(upTodateMeeting.getRecurrenceId())))
                || originalMeetingCopy.getNoOfTimeSlots() != upTodateMeeting.getNoOfTimeSlots()
                || originalMeetingCopy.getMaxNumOfSlots().intValue() != upTodateMeeting.getMaxNumOfSlots()
                        .intValue()
                || originalMeetingCopy.isSendEmailByOwner() != upTodateMeeting.isSendEmailByOwner()
                //|| originalMeetingCopy.getCoordinatorIds() !=null && !originalMeetingCopy.getCoordinatorIds().equals(upTodateMeeting.getCoordinatorIds())
                || !((originalMeetingCopy.getDescription() == null && upTodateMeeting.getDescription() == null)
                        || (originalMeetingCopy.getDescription() != null
                                && upTodateMeeting.getDescription() != null)
                                && (originalMeetingCopy.getDescription().length() == upTodateMeeting
                                        .getDescription().length()))
                || checkAttachmentsChanges(upTodateMeeting)
                || checkAdvancedUserDefinedTSCase(originalMeetingCopy, upTodateMeeting, upTodateOrginMeetings)) {
            throw new SignupUserActionException(Utilities.rb.getString("someone.modified.event.content"));
        }
    }

    private boolean checkAdvancedUserDefinedTSCase(SignupMeeting originalMeetingCopy, SignupMeeting upTodateMeeting,
            List<SignupMeeting> upTodateOrginMeetings) throws SignupUserActionException {
        if (!upTodateMeeting.getMeetingType().equals(CUSTOM_TIMESLOTS) && !isUserDefinedTS()) {
            return false;
        }

        List<SignupTimeslot> origTSList = originalMeetingCopy.getSignupTimeSlots();
        /*any unexpected events with timeslots changes on one of the recurrences
         * for example, one TS is deleted via DB by other somehow.
         * It happens very rarely and just for data integrity issue*/
        if (origTSList != null) {
            int numOf_ts = origTSList.size();
            for (SignupMeeting upMeeting : upTodateOrginMeetings) {
                if (numOf_ts != upMeeting.getNoOfTimeSlots()) {
                    throw new SignupUserActionException(
                            "One of the recurring event has been modified without synchronization with others! "
                                    + "You have to disassociate it from the recurring events. The event starting date:"
                                    + upMeeting.getStartTime().toLocaleString());
                }
            }
        }

        /*any changes by others*/
        List<SignupTimeslot> upTodateTSList = upTodateMeeting.getSignupTimeSlots();
        if (origTSList != null && upTodateTSList != null) {
            for (SignupTimeslot oTS : origTSList) {
                if (oTS.getId() == null)
                    continue;

                boolean found = false;
                for (SignupTimeslot uTS : upTodateTSList) {
                    if (oTS.getId().equals(uTS.getId())) {
                        found = true;
                        if (oTS.getMaxNoOfAttendees() != uTS.getMaxNoOfAttendees()) {
                            /*one of the TS Max# has been changed by others */
                            return true;
                        }
                        if (oTS.getStartTime().getTime() != uTS.getStartTime().getTime()
                                || (oTS.getEndTime().getTime() != uTS.getEndTime().getTime())) {
                            return true;
                        }

                        break;
                    }
                }

                if (!found) {
                    return true;//one TS is deleted or replaced by others
                }
            }

            return false;
        }

        return true;//any unexpected case
    }

    private boolean checkAttachmentsChanges(SignupMeeting latestOne) {
        //TODO excluding attendee's attachment???
        List<SignupAttachment> latestList = getEventMainAttachments(latestOne.getSignupAttachments());
        List<SignupAttachment> orignalAttachList = getEventMainAttachments(
                this.originalMeetingCopy.getSignupAttachments());
        if (latestList.isEmpty() != orignalAttachList.isEmpty()) {
            return true;
        }
        if (latestList.size() != orignalAttachList.size()) {
            return true;
        }
        for (int i = 0; i < latestList.size(); i++) {
            Date latest = latestList.get(i).getLastModifiedDate();
            Date orignal = orignalAttachList.get(i).getLastModifiedDate();
            if (!latest.equals(orignal))
                return true;
        }

        return false;
    }

    /**
     * setup the event/meeting's signup begin and deadline time and validate it
     * too
     */
    private void setSignupBeginDeadlineData(SignupMeeting meeting, int signupBegin, String signupBeginType,
            int signupDeadline, String signupDeadlineType) throws Exception {
        Date sBegin = Utilities.subTractTimeToDate(meeting.getStartTime(), signupBegin, signupBeginType);
        Date sDeadline = Utilities.subTractTimeToDate(meeting.getEndTime(), signupDeadline, signupDeadlineType);

        boolean origSignupStarted = originalMeetingCopy.getSignupBegins().before(new Date());// ????
        /* TODO have to pass it in?? */
        if (!START_NOW.equals(signupBeginType) && sBegin.before(new Date()) && !origSignupStarted) {
            // a warning for user
            Utilities.addErrorMessage(
                    Utilities.rb.getString("warning.your.event.singup.begin.time.passed.today.time"));
        }

        if (!isSignupBeginModifiedByUser() && this.isStartNowTypeForRecurEvents) {
            /*do nothing and keep the original value since the Sign-up process is already started
             * No need to re-assign a new start_now value*/
        } else {
            meeting.setSignupBegins(sBegin);
        }

        if (sBegin.after(sDeadline))
            throw new SignupUserActionException(Utilities.rb.getString("signup.deadline.is.before.signup.begin"));

        meeting.setSignupDeadline(sDeadline);
    }

    public int getCurrentNumberOfSlots() {
        return currentNumberOfSlots;
    }

    public void setCurrentNumberOfSlots(int currentNumberOfSlots) {
        this.currentNumberOfSlots = currentNumberOfSlots;
    }

    public int getDeadlineTime() {
        return deadlineTime;
    }

    public void setDeadlineTime(int deadlineTime) {
        this.deadlineTime = deadlineTime;
    }

    public String getDeadlineTimeType() {
        return deadlineTimeType;
    }

    public void setDeadlineTimeType(String deadlineTimeType) {
        this.deadlineTimeType = deadlineTimeType;
    }

    public int getTimeSlotDuration() {
        return timeSlotDuration;
    }

    public void setTimeSlotDuration(int timeSlotDuration) {
        this.timeSlotDuration = timeSlotDuration;
    }

    public int getMaxNumOfAttendees() {
        return maxNumOfAttendees;
    }

    public void setMaxNumOfAttendees(int maxNumOfAttendees) {
        this.maxNumOfAttendees = maxNumOfAttendees;
    }

    public SignupMeeting getOriginalMeetingCopy() {
        return originalMeetingCopy;
    }

    public void setOriginalMeetingCopy(SignupMeeting originalMeetingCopy) {
        this.originalMeetingCopy = originalMeetingCopy;
    }

    public boolean isShowAttendeeName() {
        return showAttendeeName;
    }

    public void setShowAttendeeName(boolean showAttendeeName) {
        this.showAttendeeName = showAttendeeName;
    }

    public int getSignupBegins() {
        return signupBegins;
    }

    public void setSignupBegins(int signupBegins) {
        this.signupBegins = signupBegins;
    }

    public String getSignupBeginsType() {
        return signupBeginsType;
    }

    public void setSignupBeginsType(String signupBeginsType) {
        this.signupBeginsType = signupBeginsType;
    }

    /*
     * public int getTotalEventDuration() { return totalEventDuration; }
     * 
     * public void setTotalEventDuration(int totalEventDuration) {
     * this.totalEventDuration = totalEventDuration; }
     */

    public boolean isUnlimited() {
        return unlimited;
    }

    public void setUnlimited(boolean unlimited) {
        this.unlimited = unlimited;
    }

    public boolean isConvertToNoRecurrent() {
        return convertToNoRecurrent;
    }

    public void setConvertToNoRecurrent(boolean convertToNoRecurrent) {
        this.convertToNoRecurrent = convertToNoRecurrent;
    }

    public List<SignupMeeting> getSavedMeetings() {
        return savedMeetings;
    }

    private void setSavedMeetings(List<SignupMeeting> savedMeetings) {
        this.savedMeetings = savedMeetings;
    }

    public List<SignupAttachment> getCurrentAttachList() {
        return currentAttachList;
    }

    public void setCurrentAttachList(List<SignupAttachment> currentAttachList) {
        this.currentAttachList = currentAttachList;
    }

    public AttachmentHandler getAttachmentHandler() {
        return attachmentHandler;
    }

    public boolean isSignupBeginModifiedByUser() {
        return signupBeginModifiedByUser;
    }

    public void setSignupBeginModifiedByUser(boolean signupBeginModifiedByUser) {
        this.signupBeginModifiedByUser = signupBeginModifiedByUser;
    }

    private void retrieveRecurrenceData(List<SignupMeeting> upTodateOrginMeetings) {
        /*to see if the recurring events have a 'Start_Now' type already*/
        this.isStartNowTypeForRecurEvents = Utilities.testSignupBeginStartNowType(upTodateOrginMeetings);

        String repeatType = upTodateOrginMeetings.get(0).getRepeatType();
        if (repeatType != null && !ONCE_ONLY.equals(repeatType)) {
            int lSize = upTodateOrginMeetings.size();
            setLastRecurMeetingModifyDate(upTodateOrginMeetings.get(lSize - 1).getStartTime());
            setRecurringType(repeatType);
            return;
        }

        /*The following code is to make it old version backward compatible*/
        setFirstRecurMeetingModifyDate(null);
        setLastRecurMeetingModifyDate(null);
        if (upTodateOrginMeetings == null || upTodateOrginMeetings.isEmpty())
            return;

        setFirstRecurMeetingModifyDate(upTodateOrginMeetings.get(0).getStartTime());
        /* in case: it's the last one */
        setLastRecurMeetingModifyDate(upTodateOrginMeetings.get(0).getStartTime());

        int listSize = upTodateOrginMeetings.size();
        if (listSize > 1) {
            setLastRecurMeetingModifyDate(upTodateOrginMeetings.get(listSize - 1).getStartTime());
            Calendar calFirst = Calendar.getInstance();
            Calendar calSecond = Calendar.getInstance();
            /*
             * we can only get approximate estimation by assuming it's a
             * succession
             */
            calFirst.setTime(upTodateOrginMeetings.get(listSize - 2).getStartTime());
            calFirst.set(Calendar.SECOND, 0);
            calFirst.set(Calendar.MILLISECOND, 0);
            calSecond.setTime(upTodateOrginMeetings.get(listSize - 1).getStartTime());
            calSecond.set(Calendar.SECOND, 0);
            calSecond.set(Calendar.MILLISECOND, 0);
            int tmp = calSecond.get(Calendar.DATE);
            int daysDiff = (int) (calSecond.getTimeInMillis() - calFirst.getTimeInMillis()) / DAY_IN_MILLISEC;
            if (daysDiff == perDay)
                setRecurringType(DAILY);
            else if (daysDiff == perWeek)
                setRecurringType(WEEKLY);
            else if (daysDiff == perBiweek)
                setRecurringType(BIWEEKLY);
            else if (daysDiff == 3 && calFirst.get(Calendar.DAY_OF_WEEK) == Calendar.FRIDAY)
                setRecurringType(WEEKDAYS);
        }
    }

    private Date getFirstRecurMeetingModifyDate() {
        return firstRecurMeetingModifyDate;
    }

    private void setFirstRecurMeetingModifyDate(Date firstRecurMeetingModifyDate) {
        this.firstRecurMeetingModifyDate = firstRecurMeetingModifyDate;
    }

    private Date getLastRecurMeetingModifyDate() {
        return lastRecurMeetingModifyDate;
    }

    private void setLastRecurMeetingModifyDate(Date lastRecurMeetingModifyDate) {
        this.lastRecurMeetingModifyDate = lastRecurMeetingModifyDate;
    }

    private String getRecurringType() {
        return recurringType;
    }

    private void setRecurringType(String recurringType) {
        this.recurringType = recurringType;
    }

    private void updateWithOrigalAttachments(SignupMeeting sMeeting, List<SignupAttachment> currentAttachList,
            int recurNum) {
        List<SignupAttachment> upToDateList = sMeeting.getSignupAttachments();
        if (upToDateList == null)
            upToDateList = new ArrayList<SignupAttachment>();

        if (currentAttachList == null)
            currentAttachList = new ArrayList<SignupAttachment>();

        /*case: original one has no attachments*/
        if (upToDateList.isEmpty()) {
            for (SignupAttachment curAttach : currentAttachList) {
                upToDateList.add(getAttachmentHandler().copySignupAttachment(sMeeting, true, curAttach,
                        ATTACH_MODIFY + recurNum));
            }
            sMeeting.setSignupAttachments(upToDateList);
            return;
        }

        if (recurNum == 0) {
            for (int i = upToDateList.size() - 1; i >= 0; i--) {
                String upToDateResourceId = upToDateList.get(i).getResourceId();
                int index = upToDateResourceId.lastIndexOf("/");
                if (index > -1) {
                    upToDateResourceId = upToDateResourceId.substring(0, index + 1) + ATTACH_TEMP + "/"
                            + upToDateResourceId.substring(index + 1, upToDateResourceId.length());
                }
                boolean found = false;
                for (Iterator iter = currentAttachList.iterator(); iter.hasNext();) {
                    SignupAttachment mdOne = (SignupAttachment) iter.next();
                    String tm = mdOne.getResourceId();
                    if (upToDateResourceId.equals(mdOne.getResourceId())) {
                        found = true;
                        this.needToCleanUpAttachInContentHS.add(mdOne);
                        /*duplicated one with original and keep original one*/
                        iter.remove();
                        break;
                    }
                }
                if (!found) {
                    if (upToDateList.get(i).getTimeslotId() == null) {
                        /*not there any more but not the attendee's attachments*/
                        this.needToCleanUpAttachInContentHS.add(upToDateList.get(i));
                        upToDateList.remove(i);
                    }
                }
            }
            /*unchanged attachments needed to copy over for recurrence events purpose*/
            List<SignupAttachment> tempList = new ArrayList<SignupAttachment>(upToDateList);

            for (SignupAttachment curAttach : currentAttachList) {
                upToDateList.add(getAttachmentHandler().copySignupAttachment(sMeeting, true, curAttach,
                        ATTACH_MODIFY + recurNum));
            }
            /*get currentAttachList contain all and ready for upcoming recurring events*/
            for (SignupAttachment tmp : tempList) {
                if (tmp.getTimeslotId() == null)
                    currentAttachList
                            .add(getAttachmentHandler().copySignupAttachment(sMeeting, true, tmp, ATTACH_TEMP));//recurring purpose
            }

        } else {
            /*case: after first recurring events */
            /*clean up*/
            if (upToDateList.size() > 0) {
                for (int i = upToDateList.size() - 1; i >= 0; i--) {
                    SignupAttachment one = upToDateList.get(i);
                    if (one.getTimeslotId() == null) {//only cleanup the main attachments
                        this.needToCleanUpAttachInContentHS.add(one);
                        upToDateList.remove(i);
                    }
                }
            }
            for (SignupAttachment curAttach : currentAttachList) {
                upToDateList.add(getAttachmentHandler().copySignupAttachment(sMeeting, true, curAttach,
                        ATTACH_MODIFY + recurNum));
            }
        }

    }

    public List<TimeslotWrapper> getCustomTimeSlotWrpList() {
        return customTimeSlotWrpList;
    }

    public void setCustomTimeSlotWrpList(List<TimeslotWrapper> customTimeSlotWrpList) {
        this.customTimeSlotWrpList = customTimeSlotWrpList;
    }

    public boolean isUserDefinedTS() {
        return userDefinedTS;
    }

    public void setUserDefinedTS(boolean userDefinedTS) {
        this.userDefinedTS = userDefinedTS;
    }

    public UserDefineTimeslotBean getUserDefineTimeslotBean() {
        return userDefineTimeslotBean;
    }

    public void setUserDefineTimeslotBean(UserDefineTimeslotBean userDefineTimeslotBean) {
        this.userDefineTimeslotBean = userDefineTimeslotBean;
    }

    public SakaiFacade getSakaiFacade() {
        return sakaiFacade;
    }

    public void setSakaiFacade(SakaiFacade sakaiFacade) {
        this.sakaiFacade = sakaiFacade;
    }

    public boolean isCreateGroups() {
        return createGroups;
    }

    public void setCreateGroups(boolean createGroups) {
        this.createGroups = createGroups;
    }

    public String getCoordinators() {
        return coordinators;
    }

    public void setCoordinators(String coordinators) {
        this.coordinators = coordinators;
    }

    public boolean isSendEmailByOwner() {
        return sendEmailByOwner;
    }

    public void setSendEmailByOwner(boolean sendEmailByOwner) {
        this.sendEmailByOwner = sendEmailByOwner;
    }

}