org.jasig.schedassist.impl.SchedulingAssistantServiceImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sched-assist-spi Show documentation
Show all versions of sched-assist-spi Show documentation
Scheduling Assistant Common Service Provider implementations.
/**
* 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");
}
}
}
}
}