/* ********************************************************************
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.bedework.calsvc.scheduling;
import org.bedework.access.PrivilegeDefs;
import org.bedework.calfacade.BwAttendee;
import org.bedework.calfacade.BwCalendar;
import org.bedework.calfacade.BwEvent;
import org.bedework.calfacade.BwPrincipal;
import org.bedework.calfacade.ScheduleResult;
import org.bedework.calfacade.exc.CalFacadeException;
import org.bedework.calfacade.svc.EventInfo;
import org.bedework.calsvc.CalSvc;
import org.bedework.icalendar.Icalendar;
import org.bedework.util.calendar.IcalDefs;
import org.bedework.util.calendar.ScheduleMethods;
import org.bedework.util.misc.Util;
/** Rather than have a single class steering calls to a number of smaller classes
* we will build up a full implementation by progressively implementing abstract
* classes.
*
* <p>That allows us to split up some rather complex code into appropriate pieces.
*
* <p>This piece handles the organizer to attendee methods
*
* @author douglm
*
*/
public abstract class OrganizerSchedulingHandler extends OutboundSchedulingHandler {
//private static String acceptPartstat = IcalDefs.partstats[IcalDefs.partstatAccepted];
OrganizerSchedulingHandler(final CalSvc svci) {
super(svci);
}
@Override
public ScheduleResult schedule(final EventInfo ei,
final String recipient,
final String fromAttUri,
final boolean iSchedule) throws CalFacadeException {
/* A request (that is we are (re)sending a meeting request) or a publish
*
* <p>We handle the following iTIP methods<ul>
* <li>ADD</li>
* <li>CANCEL</li>
* <li>DECLINECOUNTER</li>
* <li>PUBLISH</li>
* <li>REQUEST</li>
* </ul>
*
* <p>That is, messages from organizer to attendee(s)
*
* <pre>
* Do the usual checks and init
* For each recipient
* If internal to system, add to their inbox
* otherwise add to list of external recipients
*
* If any external recipients - leave in outbox with unprocessed status.
* </pre>
*/
final ScheduleResult sr = new ScheduleResult();
final BwEvent ev = ei.getEvent();
try {
if (!Icalendar.itipRequestMethodType(ev.getScheduleMethod())) {
sr.errorCode = CalFacadeException.schedulingBadMethod;
return sr;
}
/* For each recipient within this system add the event to their inbox.
*
* If there are any external users add it to the outbox and it will be
* mailed to the recipients.
*/
final int outAccess;
final boolean freeBusyRequest = ev.getEntityType() ==
IcalDefs.entityTypeFreeAndBusy;
if (freeBusyRequest) {
// freebusy
outAccess = PrivilegeDefs.privScheduleFreeBusy;
} else {
outAccess = PrivilegeDefs.privScheduleRequest;
}
/* For a request type action the organizer should be the current user. */
if (!initScheduleEvent(ei, false, iSchedule)) {
return sr;
}
/* Do this here to check we have access. We might need the outbox later
*/
BwCalendar outBox = null;
final BwPrincipal currentUser = getPrincipal();
if (!currentUser.getUnauthenticated()) {
outBox = getSpecialCalendar(getPrincipal(),
BwCalendar.calTypeOutbox,
true, outAccess);
}
sendSchedule(sr, ei, recipient, fromAttUri, true);
if ((sr.errorCode != null) || sr.ignored) {
return sr;
}
//if (freeBusyRequest && !imipFreeBusyOk) {
if (freeBusyRequest) {
// Don't ever email freebusy requests
return sr;
}
if (!iSchedule &&
(outBox != null) && // We have something to mail
(!Util.isEmpty(sr.externalRcs))) {
sr.errorCode = addToOutBox(ei, outBox, sr.externalRcs);
}
return sr;
} catch (final Throwable t) {
getSvc().rollbackTransaction();
if (t instanceof CalFacadeException) {
throw (CalFacadeException)t;
}
throw new CalFacadeException(t);
}
}
@Override
public ScheduleResult declineCounter(final EventInfo ei,
final String comment,
final BwAttendee fromAtt) throws CalFacadeException {
final EventInfo outEi = copyEventInfo(ei, getPrincipal());
final BwEvent ev = outEi.getEvent();
ev.setScheduleMethod(ScheduleMethods.methodTypeDeclineCounter);
if (comment != null) {
ev.addComment(null, comment);
}
return schedule(outEi,
fromAtt.getAttendeeUri(), null, false);
}
/* (non-Javadoc)
* @see org.bedework.calsvci.SchedulingI#processResponse(org.bedework.calfacade.svc.EventInfo)
* /
public ScheduleResult processResponse(final EventInfo ei) throws CalFacadeException {
/* Process a response we as the organizer, or their proxy, received from
* an attendee
* /
ScheduleResult sr = new ScheduleResult();
BwEvent ev = ei.getEvent();
EventInfo colEi = getStoredMeeting(ev);
BwCalendar inbox = getSvc().getCalendarsHandler().get(ev.getColPath());
/* The event should have a calendar set to the inbox it came from.
* That inbox may be owned by somebody other than the current user if a
* calendar user has delegated control of their inbox to some other user
* e.g. secretary.
* /
boolean forceDelete = true;
boolean acceptingAll = true;
check: {
if (colEi == null) {
// No corresponding stored meeting
break check;
}
if (inbox.getCalType() != BwCalendar.calTypeInbox) {
sr.errorCode = CalFacadeException.schedulingBadSourceCalendar;
break check;
}
if (ev.getOriginator() == null) {
sr.errorCode = CalFacadeException.schedulingNoOriginator;
break check;
}
String attUri = null;
/* Should be exactly one attendee * /
if (!ev.getSuppressed()) {
Collection<BwAttendee> atts = ev.getAttendees();
if ((atts == null) || (atts.size() != 1)) {
sr.errorCode = CalFacadeException.schedulingExpectOneAttendee;
break check;
}
BwAttendee att = atts.iterator().next();
if (!att.getPartstat().equals(acceptPartstat)) {
acceptingAll = false;
}
attUri = att.getAttendeeUri();
}
if (ei.getNumOverrides() > 0) {
for (EventInfo oei: ei.getOverrides()) {
ev = oei.getEvent();
Collection<BwAttendee> atts = ev.getAttendees();
if ((atts == null) || (atts.size() != 1)) {
sr.errorCode = CalFacadeException.schedulingExpectOneAttendee;
break check;
}
BwAttendee att = atts.iterator().next();
if (!att.getPartstat().equals(acceptPartstat)) {
acceptingAll = false;
}
if (attUri == null) {
attUri = att.getAttendeeUri();
} else if (!attUri.equals(att.getAttendeeUri())) {
sr.errorCode = CalFacadeException.schedulingExpectOneAttendee;
break check;
}
}
}
if (attUri == null) {
sr.errorCode = CalFacadeException.schedulingExpectOneAttendee;
break check;
}
/*TODO If the sequence of the incoming event is lower than the sequence on the
* calendar event we ignore it.
* /
if (!updateOrganizerCopy(colEi, ei, attUri, sr, 0)) {
break check;
}
forceDelete = false;
}
updateInbox(ei, inbox.getOwnerHref(),
acceptingAll, // attendeeAccepting
forceDelete); // forceDelete
return sr;
}
/* ====================================================================
Private methods
==================================================================== */
/*
private boolean updateOrganizerCopy(final EventInfo colEi,
final EventInfo inBoxEi,
final String attUri,
final ScheduleResult sr,
final int action) throws CalFacadeException {
BwEvent inBoxEv = inBoxEi.getEvent();
BwEvent calEv = colEi.getEvent();
/* Only set true if the inbox copy needs to stay as notification.
* Do not set true for status updates
* /
boolean changed = false;
if (debug) {
trace("Update for attendee " + attUri);
}
if (inBoxEv.getScheduleMethod() != Icalendar.methodTypeReply) {
sr.errorCode = CalFacadeException.schedulingBadMethod;
return false;
}
/* If the incoming sequence is less than that in the organizers event
* then ignore the incoming reply?
* /
/* Update the participation status from the incoming attendee * /
BwAttendee calAtt = null;
if (!inBoxEv.getSuppressed()) {
calAtt = calEv.findAttendee(attUri);
if (calAtt == null) {
if (debug) {
trace("Not an attendee of " + calEv);
}
sr.errorCode = CalFacadeException.schedulingUnknownAttendee;
sr.extraInfo = attUri;
return false;
}
// For a recurring instance we replace or we update all recurring instances.
boolean recurringInstance = (calEv instanceof BwEventProxy);
BwAttendee att = inBoxEv.findAttendee(attUri);
if (calAtt.changedBy(att)) {
changed = true;
if (recurringInstance) {
calEv.removeAttendee(att);
calAtt = (BwAttendee)att.clone();
} else {
att.copyTo(calAtt);
}
}
calAtt.setScheduleStatus(getRstat(inBoxEv));
if (recurringInstance) {
calEv.addAttendee(calAtt);
}
// XXX Ensure no name change
if (calEv instanceof BwEventProxy) {
BwEventProxy pr = (BwEventProxy)calEv;
BwEventAnnotation ann = pr.getRef();
ann.setName(null);
}
}
/* The above changed the master - now we need to update or add any overrides
* /
if (calEv.getRecurring() && (inBoxEi.getOverrides() != null)) {
for (EventInfo oei: inBoxEi.getOverrides()) {
BwEvent oev = oei.getEvent();
EventInfo cei = colEi.findOverride(oev.getRecurrenceId() /*, false * /);
/*
if (cei == null) {
// Organizer must have deleted the override.
if (debug) {
trace("Skipping missing override " + oev.getRecurrenceId());
}
continue;
}* /
BwEvent ocalEv = cei.getEvent();
if (((BwEventProxy)ocalEv).getRef().unsaved()) {
// New Override
try {
String rid = oev.getRecurrenceId();
Date dt = new DateTime(rid);
if (calEv.getDtstart().getDateType()) {
// RECUR - fix all day recurrences sometime
if (rid.length() > 8) {
// Try to fix up bad all day recurrence ids. - assume a local timezone
((DateTime)dt).setTimeZone(null);
dt = new Date(dt.toString().substring(0, 8));
}
}
DtStart st = new DtStart(dt);
String tzid = calEv.getDtstart().getTzid();
if (tzid != null) {
TimeZone tz = Timezones.getTz(tzid);
st.setTimeZone(tz);
}
ocalEv.setDtstart(BwDateTime.makeDateTime(st));
ocalEv.setDuration(calEv.getDuration());
ocalEv.setDtend(ocalEv.getDtstart().addDur(new Dur(calEv.getDuration())));
} catch (CalFacadeException cfe) {
throw cfe;
} catch (Throwable t) {
throw new CalFacadeException(t);
}
}
BwAttendee ovatt = oev.findAttendee(attUri);
calAtt = ocalEv.findAttendee(attUri);
if (calAtt == null) {
// Organizer must have removed the attendee.
if (debug) {
trace("Skipping override " + attUri +
" is not attending");
}
continue;
}
if (calAtt.changedBy(ovatt)) {
changed = true;
ocalEv.removeAttendee(ovatt);
calAtt = (BwAttendee)ovatt.clone();
calAtt.setScheduleStatus(getRstat(oev));
ocalEv.addAttendee(calAtt);
}
}
}
boolean noinvites = !changed;
colEi.setReplyUpdate(true);
getSvc().getEventsHandler().update(colEi, noinvites, attUri);
return changed;
}
private String getRstat(final BwEvent ev) {
String rstat = null;
for (BwRequestStatus bwrstat: ev.getRequestStatuses()) {
if (rstat != null) {
rstat += ",";
rstat += bwrstat.getCode();
} else {
rstat = bwrstat.getCode();
}
}
if (rstat == null) {
rstat = IcalDefs.deliveryStatusSuccess;
}
return rstat;
}*/
}