All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jasig.schedassist.impl.SchedulingAssistantServiceImpl Maven / Gradle / Ivy

There is a newer version: 1.1.4
Show newest version
/**
 * 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, [email protected]
 * @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#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 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(
				localStart,
				localEnd,
				calendar, 
				availableSchedule, 
				owner.getPreferredMeetingDurations(), visitor);
		// return only the conflicts (the busy list)
		List 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(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(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");
				}
			}
		}
	}
	
	
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy