org.jasig.schedassist.impl.SchedulingAssistantServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.jasig.schedassist.impl.SchedulingAssistantServiceImpl.java

Source

/**
 * Licensed to Jasig under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Jasig licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a
 * copy of the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.jasig.schedassist.impl;

import java.util.Date;
import java.util.List;

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

import org.apache.commons.lang.Validate;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.schedassist.ICalendarDataDao;
import org.jasig.schedassist.NoAppointmentExistsException;
import org.jasig.schedassist.SchedulingAssistantService;
import org.jasig.schedassist.SchedulingException;
import org.jasig.schedassist.impl.events.AppointmentCancelledEvent;
import org.jasig.schedassist.impl.events.AppointmentCreatedEvent;
import org.jasig.schedassist.impl.events.AppointmentJoinedEvent;
import org.jasig.schedassist.impl.events.AppointmentLeftEvent;
import org.jasig.schedassist.impl.owner.AvailableScheduleDao;
import org.jasig.schedassist.model.AvailableBlock;
import org.jasig.schedassist.model.AvailableSchedule;
import org.jasig.schedassist.model.AvailableVersion;
import org.jasig.schedassist.model.IEventUtils;
import org.jasig.schedassist.model.IScheduleOwner;
import org.jasig.schedassist.model.IScheduleVisitor;
import org.jasig.schedassist.model.IVisibleScheduleBuilder;
import org.jasig.schedassist.model.VisibleSchedule;
import org.jasig.schedassist.model.VisibleWindow;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;

/**
 * Default implementation of {@link SchedulingAssistantService}.
 * 
 * Note that the scheduleAppointment method is synchronized, as there is
 * no guarantees that the {@link CalendarDao} will reject event creation in case of conflict.
 * 
 * @author Nicholas Blair, nblair@doit.wisc.edu
 * @version $Id: AvailableServiceImpl.java 2891 2010-11-11 16:19:39Z npblair $
 */
@Service("schedulingAssistantService")
public final class SchedulingAssistantServiceImpl
        implements SchedulingAssistantService, ApplicationEventPublisherAware {

    private ICalendarDataDao calendarDao;
    private AvailableScheduleDao availableScheduleDao;
    private ApplicationEventPublisher applicationEventPublisher;
    private IVisibleScheduleBuilder visibleScheduleBuilder;
    private IEventUtils eventUtils;
    private Log LOG = LogFactory.getLog(this.getClass());

    /*
     * (non-Javadoc)
     * @see org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher(org.springframework.context.ApplicationEventPublisher)
     */
    @Autowired
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    /**
     * @param availableScheduleDao the availableScheduleDao to set
     */
    @Autowired
    public void setAvailableScheduleDao(final AvailableScheduleDao availableScheduleDao) {
        this.availableScheduleDao = availableScheduleDao;
    }

    /**
     * @param calendarDataDao the calendarDataDao to set
     */
    @Autowired
    public void setCalendarDataDao(final ICalendarDataDao calendarDataDao) {
        this.calendarDao = calendarDataDao;
    }

    /**
     * @param visibleScheduleBuilder the visibleScheduleBuilder to set
     */
    @Autowired
    public void setVisibleScheduleBuilder(IVisibleScheduleBuilder visibleScheduleBuilder) {
        this.visibleScheduleBuilder = visibleScheduleBuilder;
    }

    /**
     * @param eventUtils the eventUtils to set
     */
    @Autowired
    public void setEventUtils(IEventUtils eventUtils) {
        this.eventUtils = eventUtils;
    }

    /*
     * (non-Javadoc)
     * @see org.jasig.schedassist.SchedulingAssistantService#getExistingAppointment(org.jasig.schedassist.model.AvailableBlock, org.jasig.schedassist.model.IScheduleOwner)
     */
    @Override
    public VEvent getExistingAppointment(AvailableBlock targetBlock, IScheduleOwner owner) {
        VEvent result = calendarDao.getExistingAppointment(owner, targetBlock);
        return result;
    }

    /* (non-Javadoc)
     * @see org.jasig.schedassist.SchedulingAssistantService#getExistingAppointment(org.jasig.schedassist.model.AvailableBlock, org.jasig.schedassist.model.IScheduleOwner, org.jasig.schedassist.model.IScheduleVisitor)
     */
    @Override
    public VEvent getExistingAppointment(AvailableBlock targetBlock, IScheduleOwner owner,
            IScheduleVisitor visitor) {
        VEvent event = getExistingAppointment(targetBlock, owner);
        if (event != null && this.eventUtils.isAttendingAsVisitor(event, visitor.getCalendarAccount())) {
            return event;
        }

        return null;
    }

    /*
     * (non-Javadoc)
     * @see org.jasig.schedassist.SchedulingAssistantService#getVisibleSchedule(org.jasig.schedassist.model.IScheduleVisitor, org.jasig.schedassist.model.IScheduleOwner)
     */
    @Override
    public VisibleSchedule getVisibleSchedule(IScheduleVisitor visitor, IScheduleOwner owner) {
        Date[] windowBoundaries = calculateOwnerWindowBounds(owner);
        VisibleSchedule result = getVisibleSchedule(visitor, owner, windowBoundaries[0], windowBoundaries[1]);
        return result;
    }

    /*
     * (non-Javadoc)
     * @see org.jasig.schedassist.SchedulingAssistantService#getVisibleSchedule(org.jasig.schedassist.model.IScheduleVisitor, org.jasig.schedassist.model.IScheduleOwner, java.util.Date, java.util.Date)
     */
    @Override
    public VisibleSchedule getVisibleSchedule(final IScheduleVisitor visitor, final IScheduleOwner owner,
            final Date start, final Date end) {
        Validate.notNull(start, "start parameter cannot be null");
        Validate.notNull(end, "start parameter cannot be null");

        Date[] windowBoundaries = calculateOwnerWindowBounds(owner);

        Date localStart = start;
        if (start.before(windowBoundaries[0]) || start.after(windowBoundaries[1])) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("ignoring submitted start for getVisibleSchedule: " + start + " (using windowBoundary of "
                        + windowBoundaries[0] + ")");
            }
            localStart = windowBoundaries[0];
        }
        Date localEnd = end;
        if (end.after(windowBoundaries[1])) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("ignoring submitted end for getVisibleSchedule: " + end + " (using windowBoundary of "
                        + windowBoundaries[1] + ")");
            }
            localEnd = windowBoundaries[1];
        }

        Calendar calendar = calendarDao.getCalendar(owner.getCalendarAccount(), localStart, localEnd);
        AvailableSchedule schedule = availableScheduleDao.retrieve(owner);

        VisibleSchedule result = this.visibleScheduleBuilder.calculateVisibleSchedule(localStart, localEnd,
                calendar, schedule, owner, visitor);
        return result;
    }

    /*
     * (non-Javadoc)
     * @see org.jasig.schedassist.SchedulingAssistantService#calculateVisitorConflicts(org.jasig.schedassist.model.IScheduleVisitor, org.jasig.schedassist.model.IScheduleOwner, java.util.Date, java.util.Date)
     */
    @Override
    public List<AvailableBlock> calculateVisitorConflicts(IScheduleVisitor visitor, IScheduleOwner owner,
            Date start, Date end) {

        Date[] windowBoundaries = calculateOwnerWindowBounds(owner);

        Date localStart = start;
        if (start.before(windowBoundaries[0])) {
            localStart = windowBoundaries[0];
        }
        Date localEnd = end;
        if (end.after(windowBoundaries[1])) {
            localEnd = windowBoundaries[1];
        }

        AvailableSchedule availableSchedule = this.availableScheduleDao.retrieve(owner, localStart, localEnd);

        // get the VISITOR's Calendar data
        Calendar calendar = calendarDao.getCalendar(visitor.getCalendarAccount(), localStart, localEnd);

        // calculate a VisibleSchedule using the owner's availability but the Visitor's calendar data
        VisibleSchedule result = this.visibleScheduleBuilder.calculateVisitorConflicts(
                availableSchedule.getScheduleStartTime(), availableSchedule.getScheduleEndTime(), calendar,
                availableSchedule, owner.getPreferredMeetingDurations(), visitor);
        // return only the conflicts (the busy list)
        List<AvailableBlock> visitorConflicts = result.getBusyList();
        return visitorConflicts;
    }

    /**
     * 
     * @param owner
     * @return an array containing 2 {@link Date}s that represent the start and end date/times per the owner's preference
     */
    protected Date[] calculateOwnerWindowBounds(IScheduleOwner owner) {
        VisibleWindow window = owner.getPreferredVisibleWindow();

        Date now = new Date();
        Date startTime = DateUtils.addHours(now, window.getWindowHoursStart());
        Date boundary = DateUtils.addWeeks(now, window.getWindowWeeksEnd());

        return new Date[] { startTime, boundary };
    }

    /*
     * (non-Javadoc)
     * @see org.jasig.schedassist.SchedulingAssistantService#cancelAppointment(org.jasig.schedassist.model.IScheduleVisitor, org.jasig.schedassist.model.IScheduleOwner, net.fortuna.ical4j.model.component.VEvent, org.jasig.schedassist.model.AvailableBlock, java.lang.String)
     */
    @Override
    public void cancelAppointment(IScheduleVisitor visitor, IScheduleOwner owner, VEvent event,
            AvailableBlock block, final String cancelReason) throws SchedulingException {
        if (owner.isSamePerson(visitor)) {
            LOG.warn("ignoring request to cancelAppointment for owner/visitor same person: " + owner);
            return;
        }

        VEvent availableAppointment = calendarDao.getExistingAppointment(owner, block);
        if (null == availableAppointment
                || this.eventUtils.isAttendingMatch(availableAppointment, visitor, owner)) {
            // if this is a 1.0 appointment (no available version set) or visitor limit is 1
            if (null == availableAppointment.getProperty(AvailableVersion.AVAILABLE_VERSION)
                    || block.getVisitorLimit() == 1) {
                calendarDao.cancelAppointment(visitor, owner, availableAppointment);
                if (null != applicationEventPublisher) {
                    applicationEventPublisher.publishEvent(new AppointmentCancelledEvent(availableAppointment,
                            owner, visitor, block, cancelReason));
                }
                return;
            } else {
                int currentVisitorCount = this.eventUtils.getScheduleVisitorCount(availableAppointment);
                if (currentVisitorCount == 1) {
                    // this attendee is the last one, cancel
                    calendarDao.cancelAppointment(visitor, owner, availableAppointment);
                } else {
                    // there are other attendees, just leave
                    calendarDao.leaveAppointment(visitor, owner, availableAppointment);
                }
                if (null != applicationEventPublisher) {
                    applicationEventPublisher
                            .publishEvent(new AppointmentLeftEvent(availableAppointment, owner, visitor, block));
                }
                return;
            }
        } else {
            LOG.error("no appointment found within block " + block);
            throw new NoAppointmentExistsException("no matching appointment can be found");
        }
    }

    /*
     * (non-Javadoc)
     * @see org.jasig.schedassist.SchedulingAssistantService#scheduleAppointment(org.jasig.schedassist.model.IScheduleVisitor, org.jasig.schedassist.model.IScheduleOwner, org.jasig.schedassist.model.AvailableBlock, java.lang.String)
     */
    @Override
    public VEvent scheduleAppointment(IScheduleVisitor visitor, IScheduleOwner owner, AvailableBlock block,
            String eventDescription) throws SchedulingException {
        if (owner.isSamePerson(visitor)) {
            LOG.warn("ignoring request to scheduleAppointment for owner/visitor same person: " + owner);
            return null;
        }

        // assert the requested block is within the owner's current schedule
        AvailableBlock ownerPersistedBlock = availableScheduleDao.retrieveTargetBlock(owner, block.getStartTime());
        if (null == ownerPersistedBlock) {
            throw new SchedulingException("requested time is not available in schedule: " + block);
        }

        if (ownerPersistedBlock.getVisitorLimit() == 1) {
            // check to see if there is a conflict
            calendarDao.checkForConflicts(owner, block);
            // no conflicts, create the appointment
            VEvent event = calendarDao.createAppointment(visitor, owner, block, eventDescription);
            if (null != applicationEventPublisher) {
                applicationEventPublisher
                        .publishEvent(new AppointmentCreatedEvent(event, owner, visitor, block, eventDescription));
            }
            return event;
        } else {
            // owner supports multiple visitors
            // look for an existing appointment
            VEvent existingAppointment = calendarDao.getExistingAppointment(owner, block);
            if (null == existingAppointment) {
                // check to see if there is a conflict
                calendarDao.checkForConflicts(owner, block);
                // lets create it
                VEvent event = calendarDao.createAppointment(visitor, owner, block, eventDescription);
                if (null != applicationEventPublisher) {
                    applicationEventPublisher
                            .publishEvent(new AppointmentJoinedEvent(event, owner, visitor, block));
                }
                return event;
            } else {
                // try to join if attendee count hasn't been exceeded
                int visitorCount = this.eventUtils.getScheduleVisitorCount(existingAppointment);
                if (visitorCount < ownerPersistedBlock.getVisitorLimit()) {
                    // join!
                    VEvent event = calendarDao.joinAppointment(visitor, owner, existingAppointment);
                    if (null != applicationEventPublisher) {
                        applicationEventPublisher
                                .publishEvent(new AppointmentJoinedEvent(event, owner, visitor, block));
                    }
                    return event;
                } else {
                    // visitor limit exceeded
                    throw new SchedulingException("visitor limit for this appointment has been met");
                }
            }
        }
    }

}