/** * 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.remoting.soap; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Map.Entry; import net.fortuna.ical4j.model.component.VEvent; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jasig.schedassist.CalendarAccountNotFoundException; import org.jasig.schedassist.ICalendarAccountDao; import org.jasig.schedassist.RelationshipDao; import org.jasig.schedassist.SchedulingAssistantService; import org.jasig.schedassist.SchedulingException; import org.jasig.schedassist.impl.owner.AvailableScheduleDao; import org.jasig.schedassist.impl.owner.NotRegisteredException; import org.jasig.schedassist.impl.owner.OwnerDao; import org.jasig.schedassist.impl.visitor.NotAVisitorException; import org.jasig.schedassist.impl.visitor.VisitorDao; import org.jasig.schedassist.messaging.AvailableBlockElement; import org.jasig.schedassist.messaging.AvailableBlockList; import org.jasig.schedassist.messaging.AvailableStatusType; import org.jasig.schedassist.messaging.CancelAppointmentRequest; import org.jasig.schedassist.messaging.CancelAppointmentResponse; import org.jasig.schedassist.messaging.CreateAppointmentRequest; import org.jasig.schedassist.messaging.CreateAppointmentResponse; import org.jasig.schedassist.messaging.GetRelationshipsRequest; import org.jasig.schedassist.messaging.GetRelationshipsResponse; import org.jasig.schedassist.messaging.GetScheduleOwnerByIdRequest; import org.jasig.schedassist.messaging.GetScheduleOwnerByIdResponse; import org.jasig.schedassist.messaging.GetTargetAvailableBlockRequest; import org.jasig.schedassist.messaging.GetTargetAvailableBlockResponse; import org.jasig.schedassist.messaging.IsEligibleRequest; import org.jasig.schedassist.messaging.IsEligibleResponse; import org.jasig.schedassist.messaging.PreferencesElement; import org.jasig.schedassist.messaging.PreferencesSet; import org.jasig.schedassist.messaging.RelationshipElement; import org.jasig.schedassist.messaging.RelationshipList; import org.jasig.schedassist.messaging.ScheduleOwnerElement; import org.jasig.schedassist.messaging.VisibleScheduleRequest; import org.jasig.schedassist.messaging.VisibleScheduleResponse; import org.jasig.schedassist.messaging.VisitorConflictsRequest; import org.jasig.schedassist.messaging.VisitorConflictsResponse; import org.jasig.schedassist.messaging.XMLDataUtils; import org.jasig.schedassist.model.AvailableBlock; import org.jasig.schedassist.model.AvailableStatus; import org.jasig.schedassist.model.ICalendarAccount; import org.jasig.schedassist.model.IScheduleOwner; import org.jasig.schedassist.model.IScheduleVisitor; import org.jasig.schedassist.model.InputFormatException; import org.jasig.schedassist.model.MeetingDurations; import org.jasig.schedassist.model.Preferences; import org.jasig.schedassist.model.Relationship; import org.jasig.schedassist.model.VisibleSchedule; import org.jasig.schedassist.model.VisibleScheduleRequestConstraints; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.ws.server.endpoint.annotation.Endpoint; import org.springframework.ws.server.endpoint.annotation.PayloadRoot; /** * {@link Endpoint} implementation for Available, exposes some of the functionality * provided by {@link SchedulingAssistantService} and {@link RelationshipDao}. * * @author Nicholas Blair, nblair@doit.wisc.edu * @version $Id: RemoteAvailableServiceEndpoint.java 2976 2011-01-25 14:04:08Z npblair $ */ @Endpoint("schedulingAssistantEndpoint") public class SOAPSchedulingAssistantServiceEndpoint implements SOAPSchedulingAssistantOperations { private Log LOG = LogFactory.getLog(this.getClass()); private SchedulingAssistantService schedulingAssistantService; private ICalendarAccountDao calendarAccountDao; private OwnerDao ownerDao; private VisitorDao visitorDao; private RelationshipDao relationshipDao; private AvailableScheduleDao availableScheduleDao; /** * @param schedulingAssistantService the schedulingAssistantService to set */ @Autowired public void setAvailableService(SchedulingAssistantService schedulingAssistantService) { this.schedulingAssistantService = schedulingAssistantService; } /** * @param calendarAccountDao the calendarAccountDao to set */ @Autowired public void setCalendarAccountDao(ICalendarAccountDao calendarAccountDao) { this.calendarAccountDao = calendarAccountDao; } /** * @param ownerDao the ownerDao to set */ @Autowired public void setOwnerDao(final OwnerDao ownerDao) { this.ownerDao = ownerDao; } /** * @param visitorDao the visitorDao to set */ @Autowired public void setVisitorDao(final VisitorDao visitorDao) { this.visitorDao = visitorDao; } /** * @param relationshipDao the relationshipDao to set */ @Autowired public void setRelationshipDao(@Qualifier("composite") RelationshipDao relationshipDao) { this.relationshipDao = relationshipDao; } /** * @param availableScheduleDao the availableScheduleDao to set */ @Autowired public void setAvailableScheduleDao(AvailableScheduleDao availableScheduleDao) { this.availableScheduleDao = availableScheduleDao; } /* * (non-Javadoc) * @see org.jasig.schedassist.remoting.soap.SOAPSchedulingAssistantOperations#isEligible(org.jasig.schedassist.messaging.IsEligibleRequest) */ @Override @PayloadRoot(localPart="IsEligibleRequest", namespace = "https://source.jasig.org/schemas/sched-assist") public IsEligibleResponse isEligible(IsEligibleRequest request) { boolean eligible = false; IsEligibleResponse response = new IsEligibleResponse(); ICalendarAccount account = this.calendarAccountDao.getCalendarAccount(request.getVisitorNetid()); if(null != account) { try { this.visitorDao.toVisitor(account); eligible = true; } catch (NotAVisitorException e) { LOG.debug(request.getVisitorNetid() + " not a visitor"); } } response.setEligible(eligible); return response; } /* * (non-Javadoc) * @see org.jasig.schedassist.remoting.soap.SOAPSchedulingAssistantOperations#getTargetAvailableBlock(org.jasig.schedassist.messaging.GetTargetAvailableBlockRequest) */ @Override @PayloadRoot(localPart = "GetTargetAvailableBlockRequest", namespace = "https://source.jasig.org/schemas/sched-assist") public GetTargetAvailableBlockResponse getTargetAvailableBlock( GetTargetAvailableBlockRequest request) throws NotRegisteredException, SchedulingException { if(LOG.isDebugEnabled()) { LOG.debug(request); } IScheduleOwner owner = ownerDao.locateOwnerByAvailableId(request.getOwnerId()); if(null == owner) { throw new NotRegisteredException("no schedule owner found with id " + request.getOwnerId()); } Date startTime = XMLDataUtils.convertXMLGregorianCalendarToDate(request.getStartTime()); AvailableBlock targetBlock; if(request.isDoubleLength()) { targetBlock = availableScheduleDao.retrieveTargetDoubleLengthBlock(owner, startTime); } else { targetBlock = availableScheduleDao.retrieveTargetBlock(owner, startTime); } GetTargetAvailableBlockResponse response = new GetTargetAvailableBlockResponse(); AvailableBlockElement element = createAvailableBlockElement(targetBlock); response.setAvailableBlockElement(element); return response; } /* * (non-Javadoc) * @see org.jasig.schedassist.remoting.soap.SOAPSchedulingAssistantOperations#getVisibleSchedule(org.jasig.schedassist.messaging.VisibleScheduleRequest) */ @Override @PayloadRoot(localPart = "VisibleScheduleRequest", namespace = "https://source.jasig.org/schemas/sched-assist") public VisibleScheduleResponse getVisibleSchedule(final VisibleScheduleRequest request) throws NotAVisitorException, CalendarAccountNotFoundException, NotRegisteredException { if(LOG.isDebugEnabled()) { LOG.debug(request); } ICalendarAccount visitorCalendarUser = calendarAccountDao.getCalendarAccount( request.getVisitorNetid()); IScheduleVisitor visitor = visitorDao.toVisitor(visitorCalendarUser); IScheduleOwner owner = ownerDao.locateOwnerByAvailableId(request.getOwnerId()); if(null == owner) { throw new NotRegisteredException(request.getOwnerId() + " not currently registered as a schedule owner"); } VisibleScheduleRequestConstraints requestConstraints = VisibleScheduleRequestConstraints.newInstance(owner, request.getWeekStart()); VisibleScheduleResponse response = new VisibleScheduleResponse(); MeetingDurations ownerDurations = owner.getPreferredMeetingDurations(); PreferencesElement durationsElement = new PreferencesElement(); durationsElement.setKey(Preferences.DURATIONS.getKey()); durationsElement.setValue(ownerDurations.getKey()); response.setOwnerMeetingDurationsPreference(durationsElement); VisibleSchedule schedule; if(owner.hasMeetingLimit()) { // we have to look at the whole visible schedule for attendings schedule = schedulingAssistantService.getVisibleSchedule( visitor, owner); if(owner.isExceedingMeetingLimit(schedule.getAttendingCount())) { List<AvailableBlockElement> blockElementList = new ArrayList<AvailableBlockElement>(); // return ONLY the attendings List<AvailableBlock> attendingList = schedule.getAttendingList(); for(AvailableBlock b: attendingList) { AvailableBlockElement element = createAvailableBlockElement(b); element.setStatus(AvailableStatusType.ATTENDING); blockElementList.add(element); } AvailableBlockList listWrapper = new AvailableBlockList(); listWrapper.getAvailableBlockElement().addAll(blockElementList); response.setAvailableBlockList(listWrapper); // set meetingLimitExceeded to true response.setMeetingLimitExceeded(true); // short-circuit return response; } else { // extract subset between start->end schedule = schedule.subset(requestConstraints.getTargetStartDate(), requestConstraints.getTargetEndDate()); } } else { // only pull start->end of schedule schedule = schedulingAssistantService.getVisibleSchedule(visitor, owner, requestConstraints.getTargetStartDate(), requestConstraints.getTargetEndDate()); } List<AvailableBlockElement> blockElementList = new ArrayList<AvailableBlockElement>(); Map<AvailableBlock, AvailableStatus> blockMap = schedule.getBlockMap(); for(Entry<AvailableBlock, AvailableStatus> entry: blockMap.entrySet()) { AvailableBlock block = entry.getKey(); AvailableStatus status = entry.getValue(); AvailableBlockElement element = createAvailableBlockElement(block); switch(status) { case FREE: element.setStatus(AvailableStatusType.FREE); break; case ATTENDING: element.setStatus(AvailableStatusType.ATTENDING); break; case BUSY: element.setStatus(AvailableStatusType.BUSY); break; } blockElementList.add(element); } AvailableBlockList listWrapper = new AvailableBlockList(); listWrapper.getAvailableBlockElement().addAll(blockElementList); response.setAvailableBlockList(listWrapper); if(LOG.isDebugEnabled()) { LOG.debug("visitor " + visitor + " requested visible schedule for owner " + owner); } return response; } /* * (non-Javadoc) * @see org.jasig.schedassist.remoting.soap.SOAPSchedulingAssistantOperations#scheduleAppointment(org.jasig.schedassist.messaging.CreateAppointmentRequest) */ @Override @PayloadRoot(localPart = "CreateAppointmentRequest", namespace = "https://source.jasig.org/schemas/sched-assist") public CreateAppointmentResponse scheduleAppointment(final CreateAppointmentRequest request) throws NotAVisitorException, InputFormatException, SchedulingException, CalendarAccountNotFoundException, NotRegisteredException { ICalendarAccount visitorCalendarUser = calendarAccountDao.getCalendarAccount( request.getVisitorNetid()); IScheduleVisitor visitor = visitorDao.toVisitor(visitorCalendarUser); IScheduleOwner owner = ownerDao.locateOwnerByAvailableId(request.getOwnerId()); if(null == owner) { throw new NotRegisteredException(request.getOwnerId() + " not currently registered as a schedule owner"); } AvailableBlock block = availableScheduleDao.retrieveTargetBlock(owner, XMLDataUtils.convertXMLGregorianCalendarToDate(request.getStartTime())); if(null != block && block.getVisitorLimit() == 1 && owner.getPreferredMeetingDurations().isDoubleLength()) { if(request.getSelectedDuration() == owner.getPreferredMeetingDurations().getMaxLength()) { block = availableScheduleDao.retrieveTargetDoubleLengthBlock(owner, XMLDataUtils.convertXMLGregorianCalendarToDate(request.getStartTime())); } } if(null == block) { throw new SchedulingException("requested time is not available"); } VEvent event = schedulingAssistantService.scheduleAppointment(visitor, owner, block, request.getEventDescription()); CreateAppointmentResponse response = new CreateAppointmentResponse(); response.setStartTime(XMLDataUtils.convertDateToXMLGregorianCalendar(event.getStartDate().getDate())); response.setEndTime(XMLDataUtils.convertDateToXMLGregorianCalendar(event.getEndDate().getDate())); response.setEventLocation(event.getLocation().getValue()); response.setEventTitle(event.getSummary().getValue()); return response; } /* * (non-Javadoc) * @see org.jasig.schedassist.remoting.soap.SOAPSchedulingAssistantOperations#cancelAppointment(org.jasig.schedassist.messaging.CancelAppointmentRequest) */ @Override @PayloadRoot(localPart = "CancelAppointmentRequest", namespace = "https://source.jasig.org/schemas/sched-assist") public CancelAppointmentResponse cancelAppointment(final CancelAppointmentRequest request) throws NotAVisitorException, InputFormatException, SchedulingException, CalendarAccountNotFoundException, NotRegisteredException { ICalendarAccount visitorCalendarUser = calendarAccountDao.getCalendarAccount( request.getVisitorNetid()); IScheduleVisitor visitor = visitorDao.toVisitor(visitorCalendarUser); IScheduleOwner owner = ownerDao.locateOwnerByAvailableId(request.getOwnerId()); if(null == owner) { throw new NotRegisteredException(request.getOwnerId() + " not currently registered as a schedule owner"); } if(null == request.getStartTime() || null == request.getEndTime()) { throw new InputFormatException("start and/or end time not properly set"); } Date startTime = XMLDataUtils.convertXMLGregorianCalendarToDate(request.getStartTime()); if(LOG.isDebugEnabled()) { LOG.debug("start time: " + startTime); } Date endTime = XMLDataUtils.convertXMLGregorianCalendarToDate(request.getEndTime()); if(LOG.isDebugEnabled()) { LOG.debug("end time: " + endTime); } AvailableBlock targetBlock = availableScheduleDao.retrieveTargetBlock(owner, startTime); if(null == targetBlock) { throw new SchedulingException("requested time is not available in schedule"); } if(!targetBlock.getEndTime().equals(endTime)) { // the returned block doesn't match the specified end time - try grabbing doublelength targetBlock = availableScheduleDao.retrieveTargetDoubleLengthBlock(owner, startTime); if(null == targetBlock || !targetBlock.getEndTime().equals(endTime)) { throw new SchedulingException("requested time is not available in schedule"); } } VEvent existingEvent = schedulingAssistantService.getExistingAppointment(targetBlock, owner); schedulingAssistantService.cancelAppointment(visitor, owner, existingEvent, targetBlock, request.getReason()); CancelAppointmentResponse response = new CancelAppointmentResponse(); response.setStartTime(XMLDataUtils.convertDateToXMLGregorianCalendar(targetBlock.getStartTime())); response.setEndTime(XMLDataUtils.convertDateToXMLGregorianCalendar(targetBlock.getEndTime())); return response; } /* * (non-Javadoc) * @see org.jasig.schedassist.remoting.soap.SOAPSchedulingAssistantOperations#getRelationships(org.jasig.schedassist.messaging.GetRelationshipsRequest) */ @Override @PayloadRoot(localPart = "GetRelationshipsRequest", namespace = "https://source.jasig.org/schemas/sched-assist") public GetRelationshipsResponse getRelationships( final GetRelationshipsRequest request) throws NotAVisitorException, CalendarAccountNotFoundException { ICalendarAccount visitorCalendarUser = calendarAccountDao.getCalendarAccount( request.getVisitorNetid()); IScheduleVisitor visitor = visitorDao.toVisitor(visitorCalendarUser); List<Relationship> relationships = relationshipDao.forVisitor(visitor); List<RelationshipElement> elements = new ArrayList<RelationshipElement>(); for(Relationship relationship : relationships) { IScheduleOwner owner = relationship.getOwner(); ScheduleOwnerElement ownerElement = createScheduleOwnerElement(owner); RelationshipElement element = new RelationshipElement(); element.setDescription(relationship.getDescription()); element.setScheduleOwnerElement(ownerElement); elements.add(element); } if(LOG.isDebugEnabled()) { LOG.debug("visitor: " + visitor + ", relationships: " + relationships); } RelationshipList relationshipList = new RelationshipList(); relationshipList.getRelationshipElement().addAll(elements); GetRelationshipsResponse response = new GetRelationshipsResponse(); response.setRelationshipList(relationshipList); return response; } /* * (non-Javadoc) * @see org.jasig.schedassist.remoting.soap.SOAPSchedulingAssistantOperations#getScheduleOwnerById(org.jasig.schedassist.messaging.GetScheduleOwnerByIdRequest) */ @Override @PayloadRoot(localPart = "GetScheduleOwnerByIdRequest", namespace = "https://source.jasig.org/schemas/sched-assist") public GetScheduleOwnerByIdResponse getScheduleOwnerById( GetScheduleOwnerByIdRequest request) throws CalendarAccountNotFoundException, NotRegisteredException { IScheduleOwner owner = ownerDao.locateOwnerByAvailableId(request.getId()); GetScheduleOwnerByIdResponse response = new GetScheduleOwnerByIdResponse(); if(null != owner) { ScheduleOwnerElement element = createScheduleOwnerElement(owner); response.setScheduleOwnerElement(element); return response; } else { throw new NotRegisteredException("no registered ScheduleOwner found for available id: " + request.getId()); } } /* * (non-Javadoc) * @see org.jasig.schedassist.remoting.soap.SOAPSchedulingAssistantOperations#getVisitorConflicts(org.jasig.schedassist.messaging.VisitorConflictsRequest) */ @Override @PayloadRoot(localPart = "VisitorConflictsRequest", namespace = "https://source.jasig.org/schemas/sched-assist") public VisitorConflictsResponse getVisitorConflicts( VisitorConflictsRequest request) throws NotAVisitorException, NotRegisteredException { if(LOG.isDebugEnabled()) { LOG.debug(request); } ICalendarAccount visitorCalendarUser = calendarAccountDao.getCalendarAccount( request.getVisitorNetid()); IScheduleVisitor visitor = visitorDao.toVisitor(visitorCalendarUser); IScheduleOwner owner = ownerDao.locateOwnerByAvailableId(request.getOwnerId()); if(null == owner) { throw new NotRegisteredException(request.getOwnerId() + " not currently registered as a schedule owner"); } VisibleScheduleRequestConstraints requestConstraints = VisibleScheduleRequestConstraints.newInstance(owner, request.getWeekStart()); List<AvailableBlock> conflicts = this.schedulingAssistantService.calculateVisitorConflicts(visitor, owner, requestConstraints.getTargetStartDate(), requestConstraints.getTargetEndDate()); List<AvailableBlockElement> blockElementList = new ArrayList<AvailableBlockElement>(); for(AvailableBlock conflict: conflicts) { AvailableBlockElement element = createAvailableBlockElement(conflict); element.setStatus(AvailableStatusType.BUSY); blockElementList.add(element); } AvailableBlockList listWrapper = new AvailableBlockList(); listWrapper.getAvailableBlockElement().addAll(blockElementList); VisitorConflictsResponse response = new VisitorConflictsResponse(); response.setAvailableBlockList(listWrapper); return response; } /** * Convert an {@link AvailableBlock} into an {@link AvailableBlockElement}. * * @param block * @return */ protected AvailableBlockElement createAvailableBlockElement(final AvailableBlock block) { if(null == block) { return null; } AvailableBlockElement element = new AvailableBlockElement(); element.setEndTime(XMLDataUtils.convertDateToXMLGregorianCalendar(block.getEndTime())); element.setStartTime(XMLDataUtils.convertDateToXMLGregorianCalendar(block.getStartTime())); element.setVisitorLimit(block.getVisitorLimit()); element.setVisitorsAttending(block.getVisitorsAttending()); return element; } /** * Convert a {@link IScheduleOwner} into a {@link ScheduleOwnerElement}. * * @param owner * @return */ protected static ScheduleOwnerElement createScheduleOwnerElement(final IScheduleOwner owner) { ScheduleOwnerElement element = new ScheduleOwnerElement(); element.setFullName(owner.getCalendarAccount().getDisplayName()); element.setId(owner.getId()); element.setNetid(owner.getCalendarAccount().getUsername()); PreferencesSet prefSet = new PreferencesSet(); element.setPreferencesSet(prefSet); Map<Preferences, String> ownerPreferences = owner.getPreferences(); for(Entry<Preferences, String> entry : ownerPreferences.entrySet() ) { PreferencesElement prefElement = new PreferencesElement(); prefElement.setKey(entry.getKey().getKey()); prefElement.setValue(entry.getValue()); element.getPreferencesSet().getPreferencesElement().add(prefElement); } return element; } }