0001: /**********************************************************************************
0002: * $URL: https://source.sakaiproject.org/svn/calendar/tags/sakai_2-4-1/calendar-impl/impl/src/java/org/sakaiproject/calendar/impl/BaseCalendarService.java $
0003: * $Id: BaseCalendarService.java 29172 2007-04-19 02:09:55Z ajpoland@iupui.edu $
0004: ***********************************************************************************
0005: *
0006: * Copyright (c) 2003, 2004, 2005, 2006 The Sakai Foundation.
0007: *
0008: * Licensed under the Educational Community License, Version 1.0 (the "License");
0009: * you may not use this file except in compliance with the License.
0010: * You may obtain a copy of the License at
0011: *
0012: * http://www.opensource.org/licenses/ecl1.php
0013: *
0014: * Unless required by applicable law or agreed to in writing, software
0015: * distributed under the License is distributed on an "AS IS" BASIS,
0016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0017: * See the License for the specific language governing permissions and
0018: * limitations under the License.
0019: *
0020: **********************************************************************************/package org.sakaiproject.calendar.impl;
0021:
0022: import java.io.ByteArrayOutputStream;
0023: import java.io.InputStream;
0024: import java.io.OutputStream;
0025: import java.text.DecimalFormat;
0026: import java.util.ArrayList;
0027: import java.util.Collection;
0028: import java.util.Collections;
0029: import java.util.Enumeration;
0030: import java.util.GregorianCalendar;
0031: import java.util.HashMap;
0032: import java.util.Hashtable;
0033: import java.util.Iterator;
0034: import java.util.List;
0035: import java.util.Map;
0036: import java.util.Observable;
0037: import java.util.Properties;
0038: import java.util.ResourceBundle;
0039: import java.util.Set;
0040: import java.util.Stack;
0041: import java.util.Vector;
0042:
0043: import javax.servlet.http.HttpServletRequest;
0044: import javax.servlet.http.HttpServletResponse;
0045: import javax.xml.transform.Source;
0046: import javax.xml.transform.Transformer;
0047: import javax.xml.transform.TransformerException;
0048: import javax.xml.transform.TransformerFactory;
0049: import javax.xml.transform.URIResolver;
0050: import javax.xml.transform.dom.DOMSource;
0051: import javax.xml.transform.sax.SAXResult;
0052: import javax.xml.transform.stream.StreamSource;
0053: import javax.xml.parsers.DocumentBuilder;
0054: import javax.xml.parsers.DocumentBuilderFactory;
0055:
0056: import org.apache.avalon.framework.logger.ConsoleLogger;
0057: import org.apache.commons.logging.Log;
0058: import org.apache.commons.logging.LogFactory;
0059: import org.apache.fop.apps.Driver;
0060: import org.apache.fop.messaging.MessageHandler;
0061: import org.sakaiproject.authz.api.AuthzPermissionException;
0062: import org.sakaiproject.authz.api.GroupNotDefinedException;
0063: import org.sakaiproject.authz.cover.AuthzGroupService;
0064: import org.sakaiproject.authz.cover.FunctionManager;
0065: import org.sakaiproject.authz.cover.SecurityService;
0066: import org.sakaiproject.calendar.api.Calendar;
0067: import org.sakaiproject.calendar.api.CalendarEdit;
0068: import org.sakaiproject.calendar.api.CalendarEvent;
0069: import org.sakaiproject.calendar.api.CalendarEventEdit;
0070: import org.sakaiproject.calendar.api.CalendarEventVector;
0071: import org.sakaiproject.calendar.api.CalendarService;
0072: import org.sakaiproject.calendar.api.RecurrenceRule;
0073: import org.sakaiproject.calendar.api.CalendarEvent.EventAccess;
0074: import org.sakaiproject.component.api.ServerConfigurationService;
0075: import org.sakaiproject.content.api.ContentResource;
0076: import org.sakaiproject.content.cover.ContentHostingService;
0077: import org.sakaiproject.entity.api.ContextObserver;
0078: import org.sakaiproject.entity.api.Edit;
0079: import org.sakaiproject.entity.api.Entity;
0080: import org.sakaiproject.entity.api.EntityAccessOverloadException;
0081: import org.sakaiproject.entity.api.EntityCopyrightException;
0082: import org.sakaiproject.entity.api.EntityManager;
0083: import org.sakaiproject.entity.api.EntityNotDefinedException;
0084: import org.sakaiproject.entity.api.EntityPermissionException;
0085: import org.sakaiproject.entity.api.EntityTransferrer;
0086: import org.sakaiproject.entity.api.HttpAccess;
0087: import org.sakaiproject.entity.api.Reference;
0088: import org.sakaiproject.entity.api.ResourceProperties;
0089: import org.sakaiproject.entity.api.ResourcePropertiesEdit;
0090: import org.sakaiproject.event.api.Event;
0091: import org.sakaiproject.event.api.NotificationService;
0092: import org.sakaiproject.event.cover.EventTrackingService;
0093: import org.sakaiproject.exception.IdInvalidException;
0094: import org.sakaiproject.exception.IdUnusedException;
0095: import org.sakaiproject.exception.IdUsedException;
0096: import org.sakaiproject.exception.InUseException;
0097: import org.sakaiproject.exception.PermissionException;
0098: import org.sakaiproject.id.api.IdManager;
0099: import org.sakaiproject.javax.Filter;
0100: import org.sakaiproject.memory.api.Cache;
0101: import org.sakaiproject.memory.api.CacheRefresher;
0102: import org.sakaiproject.memory.api.MemoryService;
0103: import org.sakaiproject.site.api.Group;
0104: import org.sakaiproject.site.api.Site;
0105: import org.sakaiproject.site.cover.SiteService;
0106: import org.sakaiproject.thread_local.cover.ThreadLocalManager;
0107: import org.sakaiproject.time.api.Time;
0108: import org.sakaiproject.time.api.TimeBreakdown;
0109: import org.sakaiproject.time.api.TimeRange;
0110: import org.sakaiproject.time.cover.TimeService;
0111: import org.sakaiproject.tool.api.SessionBindingEvent;
0112: import org.sakaiproject.tool.api.SessionBindingListener;
0113: import org.sakaiproject.tool.cover.SessionManager;
0114: import org.sakaiproject.tool.cover.ToolManager;
0115: import org.sakaiproject.util.BaseResourcePropertiesEdit;
0116: import org.sakaiproject.util.CalendarUtil;
0117: import org.sakaiproject.util.EntityCollections;
0118: import org.sakaiproject.util.FormattedText;
0119: import org.sakaiproject.util.ResourceLoader;
0120: import org.sakaiproject.util.StorageUser;
0121: import org.sakaiproject.util.StringUtil;
0122: import org.sakaiproject.util.Validator;
0123: import org.sakaiproject.util.Xml;
0124: import org.w3c.dom.Document;
0125: import org.w3c.dom.Element;
0126: import org.w3c.dom.Node;
0127: import org.w3c.dom.NodeList;
0128:
0129: /**
0130: * <p>
0131: * BaseCalendarService is an base implementation of the CalendarService. Extension classes implement object creation, access and storage.
0132: * </p>
0133: */
0134: public abstract class BaseCalendarService implements CalendarService,
0135: StorageUser, CacheRefresher, ContextObserver, EntityTransferrer {
0136: /** Our logger. */
0137: private static Log M_log = LogFactory
0138: .getLog(BaseCalendarService.class);
0139:
0140: /** The initial portion of a relative access point URL. */
0141: protected String m_relativeAccessPoint = null;
0142:
0143: /** A Cache object for caching: calendars keyed by reference. */
0144: protected Cache m_calendarCache = null;
0145:
0146: /** A bunch of caches for events: keyed by calendar id, the cache is keyed by event reference. */
0147: protected Hashtable m_eventCaches = null;
0148:
0149: /** A Storage object for access to calendars and events. */
0150: protected Storage m_storage = null;
0151:
0152: /** DELIMETER used to separate the list of custom fields for this calendar. */
0153: private final static String ADDFIELDS_DELIMITER = "_,_";
0154:
0155: /** Security lock / event root for generic message events to make it a mail event. */
0156: public static final String SECURE_SCHEDULE_ROOT = "calendar.";
0157:
0158: private TransformerFactory transformerFactory = null;
0159:
0160: private DocumentBuilder docBuilder = null;
0161:
0162: private ResourceLoader rb = new ResourceLoader("calendarimpl");
0163:
0164: /**
0165: * Access this service from the inner classes.
0166: */
0167: protected BaseCalendarService service() {
0168: return this ;
0169: }
0170:
0171: /**
0172: * Construct a Storage object.
0173: *
0174: * @return The new storage object.
0175: */
0176: protected abstract Storage newStorage();
0177:
0178: /**
0179: * Access the partial URL that forms the root of calendar URLs.
0180: *
0181: * @param relative
0182: * if true, form within the access path only (i.e. starting with /content)
0183: * @return the partial URL that forms the root of calendar URLs.
0184: */
0185: protected String getAccessPoint(boolean relative) {
0186: return (relative ? "" : m_serverConfigurationService
0187: .getAccessUrl())
0188: + m_relativeAccessPoint;
0189:
0190: } // getAccessPoint
0191:
0192: /**
0193: * Check security permission.
0194: *
0195: * @param lock
0196: * The lock id string.
0197: * @param reference
0198: * The resource's reference string, or null if no resource is involved.
0199: * @return true if permitted, false if not.
0200: */
0201: protected boolean unlockCheck(String lock, String reference) {
0202: return SecurityService.unlock(lock, reference);
0203:
0204: } // unlockCheck
0205:
0206: /**
0207: * Check security permission.
0208: *
0209: * @param lock
0210: * The lock id string.
0211: * @param reference
0212: * The resource's reference string, or null if no resource is involved.
0213: * @exception PermissionException
0214: * thrown if the user does not have access
0215: */
0216: protected void unlock(String lock, String reference)
0217: throws PermissionException {
0218: if (!SecurityService.unlock(lock, reference)) {
0219: throw new PermissionException(SessionManager
0220: .getCurrentSessionUserId(), lock, reference);
0221: }
0222:
0223: } // unlock
0224:
0225: /**
0226: * Access the internal reference which can be used to access the calendar from within the system.
0227: *
0228: * @param context
0229: * The context.
0230: * @param id
0231: * The calendar id.
0232: * @return The the internal reference which can be used to access the calendar from within the system.
0233: */
0234: public String calendarReference(String context, String id) {
0235: return getAccessPoint(true) + Entity.SEPARATOR
0236: + REF_TYPE_CALENDAR + Entity.SEPARATOR + context
0237: + Entity.SEPARATOR + id;
0238:
0239: } // calendarReference
0240:
0241: /**
0242: * @inheritDoc
0243: */
0244: public String calendarPdfReference(String context, String id,
0245: int scheduleType, String timeRangeString, String userName,
0246: TimeRange dailyTimeRange) {
0247: return getAccessPoint(true)
0248: + Entity.SEPARATOR
0249: + REF_TYPE_CALENDAR_PDF
0250: + Entity.SEPARATOR
0251: + context
0252: + Entity.SEPARATOR
0253: + id
0254: + "?"
0255: + SCHEDULE_TYPE_PARAMETER_NAME
0256: + "="
0257: + Validator.escapeHtml(new Integer(scheduleType)
0258: .toString()) + "&" + TIME_RANGE_PARAMETER_NAME
0259: + "=" + timeRangeString + "&"
0260: + Validator.escapeHtml(USER_NAME_PARAMETER_NAME) + "="
0261: + Validator.escapeHtml(userName) + "&"
0262: + DAILY_START_TIME_PARAMETER_NAME + "="
0263: + Validator.escapeHtml(dailyTimeRange.toString());
0264: }
0265:
0266: /**
0267: * Access the internal reference which can be used to access the event from within the system.
0268: *
0269: * @param context
0270: * The context.
0271: * @param calendarId
0272: * The calendar id.
0273: * @param id
0274: * The event id.
0275: * @return The the internal reference which can be used to access the event from within the system.
0276: */
0277: public String eventReference(String context, String calendarId,
0278: String id) {
0279: return getAccessPoint(true) + Entity.SEPARATOR + REF_TYPE_EVENT
0280: + Entity.SEPARATOR + context + Entity.SEPARATOR
0281: + calendarId + Entity.SEPARATOR + id;
0282:
0283: } // eventReference
0284:
0285: /**
0286: * Takes several calendar References and merges their events from within a given time range.
0287: *
0288: * @param references
0289: * The List of calendar References.
0290: * @param range
0291: * The time period to use to select events.
0292: * @return CalendarEventVector object with the union of all events from the list of calendars in the given time range.
0293: */
0294: public CalendarEventVector getEvents(List references,
0295: TimeRange range) {
0296: CalendarEventVector calendarEventVector = null;
0297:
0298: if (references != null && range != null) {
0299: List allEvents = new ArrayList();
0300:
0301: Iterator it = references.iterator();
0302:
0303: // Add the events for each calendar in our list.
0304: while (it.hasNext()) {
0305: String calendarReference = (String) it.next();
0306: Calendar calendarObj = null;
0307:
0308: try {
0309: calendarObj = getCalendar(calendarReference);
0310: }
0311:
0312: catch (IdUnusedException e) {
0313: continue;
0314: }
0315:
0316: catch (PermissionException e) {
0317: continue;
0318: }
0319:
0320: if (calendarObj != null) {
0321: Iterator calEvent = null;
0322:
0323: try {
0324: calEvent = calendarObj.getEvents(range, null)
0325: .iterator();
0326: }
0327:
0328: catch (PermissionException e1) {
0329: continue;
0330: }
0331:
0332: allEvents.addAll(new CalendarEventVector(calEvent));
0333: }
0334: }
0335:
0336: // Do a sort since each of the events implements the Comparable interface.
0337: Collections.sort(allEvents);
0338:
0339: // Build up a CalendarEventVector and return it.
0340: calendarEventVector = new CalendarEventVector(allEvents
0341: .iterator());
0342: }
0343:
0344: return calendarEventVector;
0345: }
0346:
0347: /**
0348: * Form a tracking event string based on a security function string.
0349: * @param secure The security function string.
0350: * @return The event tracking string.
0351: */
0352: protected String eventId(String secure) {
0353: return SECURE_SCHEDULE_ROOT + secure;
0354:
0355: } // eventId
0356:
0357: /**
0358: * Access the id generating service and return a unique id.
0359: *
0360: * @return a unique id.
0361: */
0362: protected String getUniqueId() {
0363: return m_idManager.createUuid();
0364: }
0365:
0366: /**********************************************************************************************************************************************************************************************************************************************************
0367: * Constructors, Dependencies and their setter methods
0368: *********************************************************************************************************************************************************************************************************************************************************/
0369:
0370: /** Dependency: MemoryService. */
0371: protected MemoryService m_memoryService = null;
0372:
0373: /**
0374: * Dependency: MemoryService.
0375: *
0376: * @param service
0377: * The MemoryService.
0378: */
0379: public void setMemoryService(MemoryService service) {
0380: m_memoryService = service;
0381: }
0382:
0383: /** Dependency: IdManager. */
0384: protected IdManager m_idManager = null;
0385:
0386: /**
0387: * Dependency: IdManager.
0388: *
0389: * @param manager
0390: * The IdManager.
0391: */
0392: public void setIdManager(IdManager manager) {
0393: m_idManager = manager;
0394: }
0395:
0396: /** Configuration: cache, or not. */
0397: protected boolean m_caching = false;
0398:
0399: /**
0400: * Configuration: set the locks-in-db
0401: *
0402: * @param path
0403: * The storage path.
0404: */
0405: public void setCaching(String value) {
0406: m_caching = new Boolean(value).booleanValue();
0407: }
0408:
0409: /** Dependency: EntityManager. */
0410: protected EntityManager m_entityManager = null;
0411:
0412: /**
0413: * Dependency: EntityManager.
0414: *
0415: * @param service
0416: * The EntityManager.
0417: */
0418: public void setEntityManager(EntityManager service) {
0419: m_entityManager = service;
0420: }
0421:
0422: /** Dependency: ServerConfigurationService. */
0423: protected ServerConfigurationService m_serverConfigurationService = null;
0424:
0425: /**
0426: * Dependency: ServerConfigurationService.
0427: *
0428: * @param service
0429: * The ServerConfigurationService.
0430: */
0431: public void setServerConfigurationService(
0432: ServerConfigurationService service) {
0433: m_serverConfigurationService = service;
0434: }
0435:
0436: /**********************************************************************************************************************************************************************************************************************************************************
0437: * Init and Destroy
0438: *********************************************************************************************************************************************************************************************************************************************************/
0439:
0440: /**
0441: * Final initialization, once all dependencies are set.
0442: */
0443: public void init() {
0444: try {
0445: m_relativeAccessPoint = REFERENCE_ROOT;
0446:
0447: // construct a storage helper and read
0448: m_storage = newStorage();
0449: m_storage.open();
0450:
0451: // make the calendar cache
0452: if (m_caching) {
0453: m_calendarCache = m_memoryService.newCache(this ,
0454: getAccessPoint(true) + Entity.SEPARATOR
0455: + REF_TYPE_CALENDAR + Entity.SEPARATOR);
0456:
0457: // make the table to hold the event caches
0458: m_eventCaches = new Hashtable();
0459: }
0460:
0461: // create transformerFactory object needed by generatePDF
0462: transformerFactory = TransformerFactory.newInstance();
0463: transformerFactory.setURIResolver(new MyURIResolver(
0464: getClass().getClassLoader()));
0465:
0466: // create DocumentBuilder object needed by printSchedule
0467: docBuilder = DocumentBuilderFactory.newInstance()
0468: .newDocumentBuilder();
0469:
0470: M_log.info("init(): caching: " + m_caching);
0471: } catch (Throwable t) {
0472: M_log.warn("init(): ", t);
0473: }
0474:
0475: // register as an entity producer
0476: m_entityManager.registerEntityProducer(this , REFERENCE_ROOT);
0477:
0478: // register functions
0479: FunctionManager.registerFunction(AUTH_ADD_CALENDAR);
0480: FunctionManager.registerFunction(AUTH_REMOVE_CALENDAR_OWN);
0481: FunctionManager.registerFunction(AUTH_REMOVE_CALENDAR_ANY);
0482: FunctionManager.registerFunction(AUTH_MODIFY_CALENDAR_OWN);
0483: FunctionManager.registerFunction(AUTH_MODIFY_CALENDAR_ANY);
0484: FunctionManager.registerFunction(AUTH_IMPORT_CALENDAR);
0485: FunctionManager.registerFunction(AUTH_READ_CALENDAR);
0486: FunctionManager.registerFunction(AUTH_ALL_GROUPS_CALENDAR);
0487: }
0488:
0489: /**
0490: * Returns to uninitialized state.
0491: */
0492: public void destroy() {
0493: if (m_caching) {
0494: m_calendarCache.destroy();
0495: m_calendarCache = null;
0496:
0497: // TODO: destroy each cache
0498: m_eventCaches.clear();
0499: m_eventCaches = null;
0500: }
0501:
0502: m_storage.close();
0503: m_storage = null;
0504:
0505: M_log.info("destroy()");
0506: }
0507:
0508: /**********************************************************************************************************************************************************************************************************************************************************
0509: * CalendarService implementation
0510: *********************************************************************************************************************************************************************************************************************************************************/
0511:
0512: /**
0513: * Add a new calendar. Must commitCalendar() to make official, or cancelCalendar() when done!
0514: *
0515: * @param ref
0516: * A reference for the calendar.
0517: * @return The newly created calendar.
0518: * @exception IdUsedException
0519: * if the id is not unique.
0520: * @exception IdInvalidException
0521: * if the id is not made up of valid characters.
0522: */
0523: public CalendarEdit addCalendar(String ref) throws IdUsedException,
0524: IdInvalidException {
0525: // check the name's validity
0526: if (!m_entityManager.checkReference(ref))
0527: throw new IdInvalidException(ref);
0528:
0529: // check for existance
0530: if (m_storage.checkCalendar(ref)) {
0531: throw new IdUsedException(ref);
0532: }
0533:
0534: // keep it
0535: CalendarEdit calendar = m_storage.putCalendar(ref);
0536:
0537: ((BaseCalendarEdit) calendar).setEvent(EVENT_ADD_CALENDAR);
0538:
0539: return calendar;
0540:
0541: } // addCalendar
0542:
0543: /**
0544: * check permissions for getCalendar().
0545: *
0546: * @param ref
0547: * The calendar reference.
0548: * @return true if the user is allowed to getCalendar(calendarId), false if not.
0549: */
0550: public boolean allowGetCalendar(String ref) {
0551: return unlockCheck(AUTH_READ_CALENDAR, ref);
0552:
0553: } // allowGetCalendar
0554:
0555: /**
0556: * Find the calendar, in cache or info store - cache it if newly found.
0557: *
0558: * @param ref
0559: * The calendar reference.
0560: * @return The calendar, if found.
0561: */
0562: protected Calendar findCalendar(String ref) {
0563: Calendar calendar = null;
0564:
0565: if ((!m_caching) || (m_calendarCache == null)
0566: || (m_calendarCache.disabled())) {
0567: // TODO: do we really want to do this? -ggolden
0568: // if we have done this already in this thread, use that
0569: calendar = (Calendar) ThreadLocalManager.get(ref);
0570: if (calendar == null) {
0571: calendar = m_storage.getCalendar(ref);
0572:
0573: // "cache" the calendar in the current service in case they are needed again in this thread...
0574: if (calendar != null) {
0575: ThreadLocalManager.set(ref, calendar);
0576: }
0577: }
0578:
0579: return calendar;
0580: }
0581:
0582: // if we have it cached, use it (even if it's cached as a null, a miss)
0583: if (m_calendarCache.containsKey(ref)) {
0584: calendar = (Calendar) m_calendarCache.get(ref);
0585: }
0586:
0587: // if not in the cache, see if we have it in our info store
0588: else {
0589: calendar = m_storage.getCalendar(ref);
0590:
0591: // if so, cache it, even misses
0592: m_calendarCache.put(ref, calendar);
0593: }
0594:
0595: return calendar;
0596:
0597: } // findCalendar
0598:
0599: /**
0600: * Return a specific calendar.
0601: *
0602: * @param ref
0603: * The calendar reference.
0604: * @return the Calendar that has the specified name.
0605: * @exception IdUnusedException
0606: * If this name is not defined for any calendar.
0607: * @exception PermissionException
0608: * If the user does not have any permissions to the calendar.
0609: */
0610: public Calendar getCalendar(String ref) throws IdUnusedException,
0611: PermissionException {
0612: Calendar c = findCalendar(ref);
0613: if (c == null)
0614: throw new IdUnusedException(ref);
0615:
0616: // check security (throws if not permitted)
0617: unlock(AUTH_READ_CALENDAR, ref);
0618:
0619: return c;
0620:
0621: } // getCalendar
0622:
0623: /**
0624: * Remove a calendar that is locked for edit.
0625: *
0626: * @param calendar
0627: * The calendar to remove.
0628: * @exception PermissionException
0629: * if the user does not have permission to remove a calendar.
0630: */
0631: public void removeCalendar(CalendarEdit calendar)
0632: throws PermissionException {
0633: // check for closed edit
0634: if (!calendar.isActiveEdit()) {
0635: try {
0636: throw new Exception();
0637: } catch (Exception e) {
0638: M_log.warn("removeCalendar(): closed CalendarEdit", e);
0639: }
0640: return;
0641: }
0642:
0643: // check security
0644: unlock(AUTH_REMOVE_CALENDAR_ANY, calendar.getReference());
0645:
0646: m_storage.removeCalendar(calendar);
0647:
0648: // track event
0649: Event event = EventTrackingService.newEvent(
0650: EVENT_REMOVE_CALENDAR, calendar.getReference(), true);
0651: EventTrackingService.post(event);
0652:
0653: // mark the calendar as removed
0654: ((BaseCalendarEdit) calendar).setRemoved(event);
0655:
0656: // close the edit object
0657: ((BaseCalendarEdit) calendar).closeEdit();
0658:
0659: // remove any realm defined for this resource
0660: try {
0661: AuthzGroupService.removeAuthzGroup(AuthzGroupService
0662: .getAuthzGroup(calendar.getReference()));
0663: } catch (AuthzPermissionException e) {
0664: M_log.warn("removeCalendar: removing realm for : "
0665: + calendar.getReference() + " : " + e);
0666: } catch (GroupNotDefinedException ignore) {
0667: }
0668:
0669: } // removeCalendar
0670:
0671: /**
0672: * Return a List of all the defined calendars.
0673: *
0674: * @return a List of Calendar objects (may be empty)
0675: */
0676: public List getCalendars() {
0677: List calendars = new Vector();
0678:
0679: if ((!m_caching) || (m_calendarCache == null)
0680: || (m_calendarCache.disabled())) {
0681: calendars = m_storage.getCalendars();
0682: return calendars;
0683: }
0684:
0685: // if the cache is complete, use it
0686: if (m_calendarCache.isComplete()) {
0687: // get just the calendars in the cache
0688: calendars = m_calendarCache.getAll();
0689: }
0690:
0691: // otherwise get all the calendars from storage
0692: else {
0693: // Note: while we are getting from storage, storage might change. These can be processed
0694: // after we get the storage entries, and put them in the cache, and mark the cache complete.
0695: // -ggolden
0696: synchronized (m_calendarCache) {
0697: // if we were waiting and it's now complete...
0698: if (m_calendarCache.isComplete()) {
0699: // get just the calendars in the cache
0700: calendars = m_calendarCache.getAll();
0701: return calendars;
0702: }
0703:
0704: // save up any events to the cache until we get past this load
0705: m_calendarCache.holdEvents();
0706:
0707: calendars = m_storage.getCalendars();
0708: // update the cache, and mark it complete
0709: for (int i = 0; i < calendars.size(); i++) {
0710: Calendar calendar = (Calendar) calendars.get(i);
0711: m_calendarCache.put(calendar.getReference(),
0712: calendar);
0713: }
0714:
0715: m_calendarCache.setComplete();
0716:
0717: // now we are complete, process any cached events
0718: m_calendarCache.processEvents();
0719: }
0720: }
0721:
0722: return calendars;
0723:
0724: } // getCalendars
0725:
0726: /**
0727: * check permissions for importing calendar events
0728: *
0729: * @param ref
0730: * The calendar reference.
0731: * @return true if the user is allowed to import events, false if not.
0732: */
0733: public boolean allowImportCalendar(String ref) {
0734: return unlockCheck(AUTH_IMPORT_CALENDAR, ref);
0735:
0736: } // allowImportCalendar
0737:
0738: /**
0739: * check permissions for editCalendar()
0740: *
0741: * @param ref
0742: * The calendar reference.
0743: * @return true if the user is allowed to update the calendar, false if not.
0744: */
0745: public boolean allowEditCalendar(String ref) {
0746: return unlockCheck(AUTH_MODIFY_CALENDAR_ANY, ref);
0747:
0748: } // allowEditCalendar
0749:
0750: /**
0751: * check permissions for merge()
0752: * @param ref The calendar reference.
0753: * @return true if the user is allowed to update the calendar, false if not.
0754: */
0755: public boolean allowMergeCalendar(String ref) {
0756: String displayMerge = getString("calendar.merge.display", "1");
0757:
0758: if (displayMerge != null && !displayMerge.equals("1"))
0759: return false;
0760:
0761: return unlockCheck(AUTH_MODIFY_CALENDAR_ANY, ref);
0762:
0763: } // allowMergeCalendar
0764:
0765: /**
0766: * Get a locked calendar object for editing. Must commitCalendar() to make official, or cancelCalendar() or removeCalendar() when done!
0767: *
0768: * @param ref
0769: * The calendar reference.
0770: * @return A CalendarEdit object for editing.
0771: * @exception IdUnusedException
0772: * if not found, or if not an CalendarEdit object
0773: * @exception PermissionException
0774: * if the current user does not have permission to mess with this user.
0775: * @exception InUseException
0776: * if the Calendar object is locked by someone else.
0777: */
0778: public CalendarEdit editCalendar(String ref)
0779: throws IdUnusedException, PermissionException,
0780: InUseException {
0781: // check for existance
0782: if (!m_storage.checkCalendar(ref)) {
0783: throw new IdUnusedException(ref);
0784: }
0785:
0786: // check security (throws if not permitted)
0787: unlock(AUTH_MODIFY_CALENDAR_ANY, ref);
0788:
0789: // ignore the cache - get the calendar with a lock from the info store
0790: CalendarEdit edit = m_storage.editCalendar(ref);
0791: if (edit == null)
0792: throw new InUseException(ref);
0793:
0794: ((BaseCalendarEdit) edit).setEvent(EVENT_MODIFY_CALENDAR);
0795:
0796: return edit;
0797:
0798: } // editCalendar
0799:
0800: /**
0801: * Commit the changes made to a CalendarEdit object, and release the lock. The CalendarEdit is disabled, and not to be used after this call.
0802: *
0803: * @param edit
0804: * The CalendarEdit object to commit.
0805: */
0806: public void commitCalendar(CalendarEdit edit) {
0807: // check for closed edit
0808: if (!edit.isActiveEdit()) {
0809: try {
0810: throw new Exception();
0811: } catch (Exception e) {
0812: M_log.warn("commitCalendar(): closed CalendarEdit", e);
0813: }
0814: return;
0815: }
0816:
0817: m_storage.commitCalendar(edit);
0818:
0819: // track event
0820: Event event = EventTrackingService.newEvent(
0821: ((BaseCalendarEdit) edit).getEvent(), edit
0822: .getReference(), true);
0823: EventTrackingService.post(event);
0824:
0825: // close the edit object
0826: ((BaseCalendarEdit) edit).closeEdit();
0827:
0828: } // commitCalendar
0829:
0830: /**
0831: * Cancel the changes made to a CalendarEdit object, and release the lock. The CalendarEdit is disabled, and not to be used after this call.
0832: *
0833: * @param edit
0834: * The CalendarEdit object to commit.
0835: */
0836: public void cancelCalendar(CalendarEdit edit) {
0837: // check for closed edit
0838: if (!edit.isActiveEdit()) {
0839: try {
0840: throw new Exception();
0841: } catch (Exception e) {
0842: M_log
0843: .warn(
0844: "cancelCalendar(): closed CalendarEventEdit",
0845: e);
0846: }
0847: return;
0848: }
0849:
0850: // release the edit lock
0851: m_storage.cancelCalendar(edit);
0852:
0853: // close the edit object
0854: ((BaseCalendarEdit) edit).closeEdit();
0855:
0856: } // cancelCalendar
0857:
0858: /**
0859: * {@inheritDoc}
0860: */
0861: public RecurrenceRule newRecurrence(String frequency) {
0862: if (frequency.equals(DailyRecurrenceRule.FREQ)) {
0863: return new DailyRecurrenceRule();
0864: } else if (frequency.equals(WeeklyRecurrenceRule.FREQ)) {
0865: return new WeeklyRecurrenceRule();
0866: } else if (frequency.equals(MonthlyRecurrenceRule.FREQ)) {
0867: return new MonthlyRecurrenceRule();
0868: } else if (frequency.equals(YearlyRecurrenceRule.FREQ)) {
0869: return new YearlyRecurrenceRule();
0870: }
0871:
0872: return null;
0873: }
0874:
0875: /**
0876: * {@inheritDoc}
0877: */
0878: public RecurrenceRule newRecurrence(String frequency, int interval) {
0879: if (frequency.equals(DailyRecurrenceRule.FREQ)) {
0880: return new DailyRecurrenceRule(interval);
0881: } else if (frequency.equals(WeeklyRecurrenceRule.FREQ)) {
0882: return new WeeklyRecurrenceRule(interval);
0883: } else if (frequency.equals(MonthlyRecurrenceRule.FREQ)) {
0884: return new MonthlyRecurrenceRule(interval);
0885: } else if (frequency.equals(YearlyRecurrenceRule.FREQ)) {
0886: return new YearlyRecurrenceRule(interval);
0887: }
0888:
0889: return null;
0890: }
0891:
0892: /**
0893: * {@inheritDoc}
0894: */
0895: public RecurrenceRule newRecurrence(String frequency, int interval,
0896: int count) {
0897: if (frequency.equals(DailyRecurrenceRule.FREQ)) {
0898: return new DailyRecurrenceRule(interval, count);
0899: } else if (frequency.equals(WeeklyRecurrenceRule.FREQ)) {
0900: return new WeeklyRecurrenceRule(interval, count);
0901: } else if (frequency.equals(MonthlyRecurrenceRule.FREQ)) {
0902: return new MonthlyRecurrenceRule(interval, count);
0903: } else if (frequency.equals(YearlyRecurrenceRule.FREQ)) {
0904: return new YearlyRecurrenceRule(interval, count);
0905: }
0906:
0907: return null;
0908: }
0909:
0910: /**
0911: * {@inheritDoc}
0912: */
0913: public RecurrenceRule newRecurrence(String frequency, int interval,
0914: Time until) {
0915: if (frequency.equals(DailyRecurrenceRule.FREQ)) {
0916: return new DailyRecurrenceRule(interval, until);
0917: } else if (frequency.equals(WeeklyRecurrenceRule.FREQ)) {
0918: return new WeeklyRecurrenceRule(interval, until);
0919: } else if (frequency.equals(MonthlyRecurrenceRule.FREQ)) {
0920: return new MonthlyRecurrenceRule(interval, until);
0921: } else if (frequency.equals(YearlyRecurrenceRule.FREQ)) {
0922: return new YearlyRecurrenceRule(interval, until);
0923: }
0924:
0925: return null;
0926: }
0927:
0928: /**********************************************************************************************************************************************************************************************************************************************************
0929: * ResourceService implementation
0930: *********************************************************************************************************************************************************************************************************************************************************/
0931:
0932: /**
0933: * {@inheritDoc}
0934: */
0935: public String getLabel() {
0936: return "calendar";
0937: }
0938:
0939: /**
0940: * {@inheritDoc}
0941: */
0942: public boolean willArchiveMerge() {
0943: return true;
0944: }
0945:
0946: /**
0947: * {@inheritDoc}
0948: */
0949: public HttpAccess getHttpAccess() {
0950: return new HttpAccess() {
0951: public void handleAccess(HttpServletRequest req,
0952: HttpServletResponse res, Reference ref,
0953: Collection copyrightAcceptedRefs)
0954: throws EntityPermissionException,
0955: EntityNotDefinedException,
0956: EntityAccessOverloadException,
0957: EntityCopyrightException {
0958: // we only access the pdf reference
0959: if (!REF_TYPE_CALENDAR_PDF.equals(ref.getSubType()))
0960: throw new EntityNotDefinedException(ref
0961: .getReference());
0962:
0963: // TODO: permissions?
0964:
0965: try {
0966: Properties options = new Properties();
0967: Enumeration e = req.getParameterNames();
0968: while (e.hasMoreElements()) {
0969: String key = (String) e.nextElement();
0970: String[] values = req.getParameterValues(key);
0971: if (values.length == 1) {
0972: options.put(key, values[0]);
0973: } else {
0974: StringBuffer buf = new StringBuffer();
0975: for (int i = 0; i < values.length; i++) {
0976: buf.append(values[i] + "^");
0977: }
0978: options.put(key, buf.toString());
0979: }
0980: }
0981:
0982: // We need to write to a temporary stream for better speed, plus
0983: // so we can get a byte count. Internet Explorer has problems
0984: // if we don't make the setContentLength() call.
0985: ByteArrayOutputStream outByteStream = new ByteArrayOutputStream();
0986: StringBuffer contentType = new StringBuffer();
0987:
0988: printSchedule(options, contentType, outByteStream);
0989:
0990: // Set the mime type for a PDF
0991: res.addHeader("Content-Disposition",
0992: "inline; filename=\"schedule.pdf\"");
0993: res.setContentType(contentType.toString());
0994: res.setContentLength(outByteStream.size());
0995:
0996: if (outByteStream.size() > 0) {
0997: // Increase the buffer size for more speed.
0998: res.setBufferSize(outByteStream.size());
0999: }
1000:
1001: OutputStream out = null;
1002: try {
1003: out = res.getOutputStream();
1004: if (outByteStream.size() > 0) {
1005: outByteStream.writeTo(out);
1006: }
1007: out.flush();
1008: out.close();
1009: } catch (Throwable ignore) {
1010: } finally {
1011: if (out != null) {
1012: try {
1013: out.close();
1014: } catch (Throwable ignore) {
1015: }
1016: }
1017: }
1018: } catch (Throwable t) {
1019: throw new EntityNotDefinedException(ref
1020: .getReference());
1021: }
1022: }
1023: };
1024: }
1025:
1026: /**
1027: * {@inheritDoc}
1028: */
1029: public boolean parseEntityReference(String reference, Reference ref) {
1030: if (reference.startsWith(CalendarService.REFERENCE_ROOT)) {
1031: String[] parts = StringUtil.split(reference,
1032: Entity.SEPARATOR);
1033:
1034: String subType = null;
1035: String context = null;
1036: String id = null;
1037: String container = null;
1038:
1039: // the first part will be null, then next the service, the third will be "calendar" or "event"
1040: if (parts.length > 2) {
1041: subType = parts[2];
1042: if (REF_TYPE_CALENDAR.equals(subType)
1043: || REF_TYPE_CALENDAR_PDF.equals(subType)) {
1044: // next is the context id
1045: if (parts.length > 3) {
1046: context = parts[3];
1047:
1048: // next is the calendar id
1049: if (parts.length > 4) {
1050: id = parts[4];
1051: }
1052: }
1053: } else if (REF_TYPE_EVENT.equals(subType)) {
1054: // next three parts are context, channel (container) and event id
1055: if (parts.length > 5) {
1056: context = parts[3];
1057: container = parts[4];
1058: id = parts[5];
1059: }
1060: } else
1061: M_log
1062: .warn(".parseEntityReference(): unknown calendar subtype: "
1063: + subType + " in ref: " + reference);
1064: }
1065:
1066: ref.set(APPLICATION_ID, subType, id, container, context);
1067:
1068: return true;
1069: }
1070:
1071: return false;
1072: }
1073:
1074: /**
1075: * {@inheritDoc}
1076: */
1077: public String getEntityDescription(Reference ref) {
1078: // double check that it's mine
1079: if (APPLICATION_ID != ref.getType())
1080: return null;
1081:
1082: String rv = "Calendar: " + ref.getReference();
1083:
1084: try {
1085: // if this is a calendar
1086: if (REF_TYPE_CALENDAR.equals(ref.getSubType())
1087: || REF_TYPE_CALENDAR_PDF.equals(ref.getSubType())) {
1088: Calendar cal = getCalendar(ref.getReference());
1089: rv = "Calendar: " + cal.getId() + " ("
1090: + cal.getContext() + ")";
1091: }
1092:
1093: // otherwise an event
1094: else if (REF_TYPE_EVENT.equals(ref.getSubType())) {
1095: rv = "Event: " + ref.getReference();
1096: }
1097: } catch (PermissionException e) {
1098: } catch (IdUnusedException e) {
1099: } catch (NullPointerException e) {
1100: }
1101:
1102: return rv;
1103: }
1104:
1105: /**
1106: * {@inheritDoc}
1107: */
1108: public ResourceProperties getEntityResourceProperties(Reference ref) {
1109: // double check that it's mine
1110: if (APPLICATION_ID != ref.getType())
1111: return null;
1112:
1113: ResourceProperties props = null;
1114:
1115: try {
1116: // if this is a calendar
1117: if (REF_TYPE_CALENDAR.equals(ref.getSubType())
1118: || REF_TYPE_CALENDAR_PDF.equals(ref.getSubType())) {
1119: Calendar cal = getCalendar(ref.getReference());
1120: props = cal.getProperties();
1121: }
1122:
1123: // otherwise an event
1124: else if (REF_TYPE_EVENT.equals(ref.getSubType())) {
1125: Calendar cal = getCalendar(calendarReference(ref
1126: .getContext(), ref.getContainer()));
1127: CalendarEvent event = cal.getEvent(ref.getId());
1128: props = event.getProperties();
1129: }
1130:
1131: else
1132: M_log
1133: .warn(".getEntityResourceProperties(): unknown calendar ref subtype: "
1134: + ref.getSubType()
1135: + " in ref: "
1136: + ref.getReference());
1137: } catch (PermissionException e) {
1138: M_log.warn(".getEntityResourceProperties(): " + e);
1139: } catch (IdUnusedException e) {
1140: // This just means that the resource once pointed to as an attachment or something has been deleted.
1141: // m_logger(this + ".getProperties(): " + e);
1142: } catch (NullPointerException e) {
1143: M_log.warn(".getEntityResourceProperties(): " + e);
1144: }
1145:
1146: return props;
1147: }
1148:
1149: /**
1150: * {@inheritDoc}
1151: */
1152: public Entity getEntity(Reference ref) {
1153: // double check that it's mine
1154: if (APPLICATION_ID != ref.getType())
1155: return null;
1156:
1157: Entity rv = null;
1158:
1159: try {
1160: // if this is a calendar
1161: if (REF_TYPE_CALENDAR.equals(ref.getSubType())
1162: || REF_TYPE_CALENDAR_PDF.equals(ref.getSubType())) {
1163: rv = getCalendar(ref.getReference());
1164: }
1165:
1166: // otherwise a event
1167: else if (REF_TYPE_EVENT.equals(ref.getSubType())) {
1168: Calendar cal = getCalendar(calendarReference(ref
1169: .getContext(), ref.getContainer()));
1170: rv = cal.getEvent(ref.getId());
1171: }
1172:
1173: else
1174: M_log
1175: .warn("getEntity(): unknown calendar ref subtype: "
1176: + ref.getSubType()
1177: + " in ref: "
1178: + ref.getReference());
1179: } catch (PermissionException e) {
1180: M_log.warn("getEntity(): " + e);
1181: } catch (IdUnusedException e) {
1182: M_log.warn("getEntity(): " + e);
1183: } catch (NullPointerException e) {
1184: M_log.warn(".getEntity(): " + e);
1185: }
1186:
1187: return rv;
1188: }
1189:
1190: /**
1191: * {@inheritDoc}
1192: */
1193: public Collection getEntityAuthzGroups(Reference ref, String userId) {
1194: // double check that it's mine
1195: if (APPLICATION_ID != ref.getType())
1196: return null;
1197:
1198: Collection rv = new Vector();
1199:
1200: // for events:
1201: // if access set to SITE (or PUBLIC), use the event, calendar and site authzGroups.
1202: // if access set to GROUPED, use the event, and the groups, but not the calendar or site authzGroups.
1203: // if the user has SECURE_ALL_GROUPS in the context, ignore GROUPED access and treat as if SITE
1204:
1205: // for Calendars: use the calendar and site authzGroups.
1206:
1207: try {
1208: // for event
1209: if (REF_TYPE_EVENT.equals(ref.getSubType())) {
1210: // event
1211: rv.add(ref.getReference());
1212:
1213: boolean grouped = false;
1214: Collection groups = null;
1215:
1216: // check SECURE_ALL_GROUPS - if not, check if the event has groups or not
1217: // TODO: the last param needs to be a ContextService.getRef(ref.getContext())... or a ref.getContextAuthzGroup() -ggolden
1218: if ((userId == null)
1219: || ((!SecurityService.isSuperUser(userId)) && (!AuthzGroupService
1220: .isAllowed(userId, SECURE_ALL_GROUPS,
1221: SiteService.siteReference(ref
1222: .getContext()))))) {
1223: // get the calendar to get the message to get group information
1224: String calendarRef = calendarReference(ref
1225: .getContext(), ref.getContainer());
1226: Calendar c = findCalendar(calendarRef);
1227: if (c != null) {
1228: CalendarEvent e = ((BaseCalendarEdit) c)
1229: .findEvent(ref.getId());
1230: if (e != null) {
1231: grouped = EventAccess.GROUPED == e
1232: .getAccess();
1233: groups = e.getGroups();
1234: }
1235: }
1236: }
1237:
1238: if (grouped) {
1239: // groups
1240: rv.addAll(groups);
1241: }
1242:
1243: // not grouped
1244: else {
1245: // calendar
1246: rv.add(calendarReference(ref.getContext(), ref
1247: .getContainer()));
1248:
1249: // site
1250: ref.addSiteContextAuthzGroup(rv);
1251: }
1252: }
1253:
1254: // for calendar
1255: else {
1256: // calendar
1257: rv
1258: .add(calendarReference(ref.getContext(), ref
1259: .getId()));
1260:
1261: // site
1262: ref.addSiteContextAuthzGroup(rv);
1263: }
1264: } catch (Throwable e) {
1265: M_log.warn("getEntityAuthzGroups(): " + e);
1266: }
1267:
1268: return rv;
1269: }
1270:
1271: /**
1272: * {@inheritDoc}
1273: */
1274: public String getEntityUrl(Reference ref) {
1275: // double check that it's mine
1276: if (APPLICATION_ID != ref.getType())
1277: return null;
1278:
1279: String rv = null;
1280:
1281: try {
1282: // if this is a calendar
1283: if (REF_TYPE_CALENDAR.equals(ref.getSubType())
1284: || REF_TYPE_CALENDAR_PDF.equals(ref.getSubType())) {
1285: Calendar cal = getCalendar(ref.getReference());
1286: rv = cal.getUrl();
1287: }
1288:
1289: // otherwise a event
1290: else if (REF_TYPE_EVENT.equals(ref.getSubType())) {
1291: Calendar cal = getCalendar(calendarReference(ref
1292: .getContext(), ref.getContainer()));
1293: CalendarEvent event = cal.getEvent(ref.getId());
1294: rv = event.getUrl();
1295: }
1296:
1297: else
1298: M_log
1299: .warn("getEntityUrl(): unknown calendar ref subtype: "
1300: + ref.getSubType()
1301: + " in ref: "
1302: + ref.getReference());
1303: } catch (PermissionException e) {
1304: M_log.warn(".getEntityUrl(): " + e);
1305: } catch (IdUnusedException e) {
1306: M_log.warn(".getEntityUrl(): " + e);
1307: } catch (NullPointerException e) {
1308: M_log.warn(".getEntityUrl(): " + e);
1309: }
1310:
1311: return rv;
1312: }
1313:
1314: /**
1315: * {@inheritDoc}
1316: */
1317: public String archive(String siteId, Document doc, Stack stack,
1318: String archivePath, List attachments) {
1319: // prepare the buffer for the results log
1320: StringBuffer results = new StringBuffer();
1321:
1322: // start with an element with our very own (service) name
1323: Element element = doc.createElement(CalendarService.class
1324: .getName());
1325: ((Element) stack.peek()).appendChild(element);
1326: stack.push(element);
1327:
1328: // get the channel associated with this site
1329: String calRef = calendarReference(siteId,
1330: SiteService.MAIN_CONTAINER);
1331:
1332: results.append("archiving calendar " + calRef + ".\n");
1333:
1334: try {
1335: // do the channel
1336: Calendar cal = getCalendar(calRef);
1337: Element containerElement = cal.toXml(doc, stack);
1338: stack.push(containerElement);
1339:
1340: // do the messages in the channel
1341: Iterator events = cal.getEvents(null, null).iterator();
1342: while (events.hasNext()) {
1343: CalendarEvent event = (CalendarEvent) events.next();
1344: event.toXml(doc, stack);
1345:
1346: // collect message attachments
1347: List atts = event.getAttachments();
1348: for (int i = 0; i < atts.size(); i++) {
1349: Reference ref = (Reference) atts.get(i);
1350: // if it's in the attachment area, and not already in the list
1351: if ((ref.getReference()
1352: .startsWith("/content/attachment/"))
1353: && (!attachments.contains(ref))) {
1354: attachments.add(ref);
1355: }
1356: }
1357: }
1358:
1359: stack.pop();
1360: } catch (Exception any) {
1361: M_log
1362: .warn(".archve: exception archiving messages for service: "
1363: + CalendarService.class.getName()
1364: + " channel: " + calRef);
1365: }
1366:
1367: stack.pop();
1368:
1369: return results.toString();
1370: }
1371:
1372: /**
1373: * {@inheritDoc}
1374: */
1375: public String merge(String siteId, Element root,
1376: String archivePath, String fromSiteId, Map attachmentNames,
1377: Map userIdTrans, Set userListAllowImport) {
1378: Map ids = new HashMap();
1379:
1380: // prepare the buffer for the results log
1381: StringBuffer results = new StringBuffer();
1382:
1383: // get the channel associated with this site
1384: String calendarRef = calendarReference(siteId,
1385: SiteService.MAIN_CONTAINER);
1386:
1387: int count = 0;
1388:
1389: try {
1390: Calendar calendar = null;
1391: try {
1392: calendar = getCalendar(calendarRef);
1393: } catch (IdUnusedException e) {
1394: CalendarEdit edit = addCalendar(calendarRef);
1395: commitCalendar(edit);
1396: calendar = edit;
1397: }
1398:
1399: // pass the DOM to get new event ids, and adjust attachments
1400: NodeList children2 = root.getChildNodes();
1401: int length2 = children2.getLength();
1402: for (int i2 = 0; i2 < length2; i2++) {
1403: Node child2 = children2.item(i2);
1404: if (child2.getNodeType() == Node.ELEMENT_NODE) {
1405: Element element2 = (Element) child2;
1406:
1407: // get the "calendar" child
1408: if (element2.getTagName().equals("calendar")) {
1409: NodeList children3 = element2.getChildNodes();
1410: final int length3 = children3.getLength();
1411: for (int i3 = 0; i3 < length3; i3++) {
1412: Node child3 = children3.item(i3);
1413: if (child3.getNodeType() == Node.ELEMENT_NODE) {
1414: Element element3 = (Element) child3;
1415:
1416: if (element3.getTagName().equals(
1417: "properties")) {
1418: NodeList children8 = element3
1419: .getChildNodes();
1420: final int length8 = children8
1421: .getLength();
1422: for (int i8 = 0; i8 < length8; i8++) {
1423: Node child8 = children8
1424: .item(i8);
1425: if (child8.getNodeType() == Node.ELEMENT_NODE) {
1426: Element element8 = (Element) child8;
1427:
1428: // for "event" children
1429: if (element8.getTagName()
1430: .equals("property")) {
1431: String pName = element8
1432: .getAttribute("name");
1433: if ((pName != null)
1434: && (pName
1435: .equalsIgnoreCase("CHEF:calendar-fields"))) {
1436: String pValue = element8
1437: .getAttribute("value");
1438: if ("BASE64"
1439: .equalsIgnoreCase(element8
1440: .getAttribute("enc"))) {
1441: pValue = Xml
1442: .decodeAttribute(
1443: element8,
1444: "value");
1445: }
1446:
1447: if (pValue != null) {
1448: try {
1449: CalendarEdit calEdit = editCalendar(calendarRef);
1450: String calFields = StringUtil
1451: .trimToNull(calEdit
1452: .getEventFields());
1453:
1454: if (calFields != null)
1455: pValue = calFields
1456: + ADDFIELDS_DELIMITER
1457: + pValue;
1458:
1459: calEdit
1460: .setEventFields(pValue);
1461: commitCalendar(calEdit);
1462: } catch (Exception e) {
1463: M_log
1464: .warn(
1465: ".merge() when editing calendar: exception: ",
1466: e);
1467: }
1468: }
1469: }
1470: }
1471: }
1472: }
1473: }
1474:
1475: // for "event" children
1476: if (element3.getTagName().equals(
1477: "event")) {
1478: // adjust the id
1479: String oldId = element3
1480: .getAttribute("id");
1481: String newId = getUniqueId();
1482: element3.setAttribute("id", newId);
1483:
1484: // get the attachment kids
1485: NodeList children5 = element3
1486: .getChildNodes();
1487: final int length5 = children5
1488: .getLength();
1489: for (int i5 = 0; i5 < length5; i5++) {
1490: Node child5 = children5
1491: .item(i5);
1492: if (child5.getNodeType() == Node.ELEMENT_NODE) {
1493: Element element5 = (Element) child5;
1494:
1495: // for "attachment" children
1496: if (element5
1497: .getTagName()
1498: .equals(
1499: "attachment")) {
1500: // map the attachment area folder name
1501: String oldUrl = element5
1502: .getAttribute("relative-url");
1503: if (oldUrl
1504: .startsWith("/content/attachment/")) {
1505: String newUrl = (String) attachmentNames
1506: .get(oldUrl);
1507: if (newUrl != null) {
1508: if (newUrl
1509: .startsWith("/attachment/"))
1510: newUrl = "/content"
1511: .concat(newUrl);
1512:
1513: element5
1514: .setAttribute(
1515: "relative-url",
1516: Validator
1517: .escapeQuestionMark(newUrl));
1518: }
1519: }
1520:
1521: // map any references to this site to the new site id
1522: else if (oldUrl
1523: .startsWith("/content/group/"
1524: + fromSiteId
1525: + "/")) {
1526: String newUrl = "/content/group/"
1527: + siteId
1528: + oldUrl
1529: .substring(15 + fromSiteId
1530: .length());
1531: element5
1532: .setAttribute(
1533: "relative-url",
1534: Validator
1535: .escapeQuestionMark(newUrl));
1536: }
1537: }
1538: }
1539: }
1540:
1541: // create a new message in the calendar
1542: CalendarEventEdit edit = calendar
1543: .mergeEvent(element3);
1544: calendar.commitEvent(edit);
1545: count++;
1546: }
1547: }
1548: }
1549: }
1550: }
1551: }
1552: } catch (Exception any) {
1553: M_log.warn(".merge(): exception: ", any);
1554: }
1555:
1556: results.append("merging calendar " + calendarRef + " (" + count
1557: + ") messages.\n");
1558: return results.toString();
1559: }
1560:
1561: /**
1562: * {@inheritDoc}
1563: */
1564: public void transferCopyEntities(String fromContext,
1565: String toContext, List resourceIds) {
1566: // get the channel associated with this site
1567: String oCalendarRef = calendarReference(fromContext,
1568: SiteService.MAIN_CONTAINER);
1569:
1570: Calendar oCalendar = null;
1571: try {
1572: oCalendar = getCalendar(oCalendarRef);
1573:
1574: // new calendar
1575: CalendarEdit nCalendar = null;
1576: String nCalendarRef = calendarReference(toContext,
1577: SiteService.MAIN_CONTAINER);
1578: try {
1579: nCalendar = editCalendar(nCalendarRef);
1580: } catch (IdUnusedException e) {
1581: try {
1582: nCalendar = addCalendar(nCalendarRef);
1583: } catch (IdUsedException ee) {
1584: } catch (IdInvalidException ee) {
1585: }
1586: } catch (PermissionException e) {
1587: } catch (InUseException e) {
1588: }
1589:
1590: if (nCalendar != null) {
1591: List oEvents = oCalendar.getEvents(null, null);
1592:
1593: String oFields = StringUtil.trimToNull(oCalendar
1594: .getEventFields());
1595: String nFields = StringUtil.trimToNull(nCalendar
1596: .getEventFields());
1597: String allFields = "";
1598:
1599: if (oFields != null) {
1600: if (nFields != null) {
1601: allFields = nFields + ADDFIELDS_DELIMITER
1602: + oFields;
1603: } else {
1604: allFields = oFields;
1605: }
1606: nCalendar.setEventFields(allFields);
1607: }
1608:
1609: for (int i = 0; i < oEvents.size(); i++) {
1610: CalendarEvent oEvent = (CalendarEvent) oEvents
1611: .get(i);
1612: try {
1613: CalendarEvent e = nCalendar.addEvent(oEvent
1614: .getRange(), oEvent.getDisplayName(),
1615: oEvent.getDescription(), oEvent
1616: .getType(), oEvent
1617: .getLocation(), oEvent
1618: .getAttachments());
1619:
1620: try {
1621: BaseCalendarEventEdit eEdit = (BaseCalendarEventEdit) nCalendar
1622: .getEditEvent(e.getId(),
1623: EVENT_ADD_CALENDAR);
1624: // properties
1625: ResourcePropertiesEdit p = eEdit
1626: .getPropertiesEdit();
1627: p.clear();
1628: p.addAll(oEvent.getProperties());
1629:
1630: // attachment
1631: List oAttachments = eEdit.getAttachments();
1632: List nAttachments = m_entityManager
1633: .newReferenceList();
1634: for (int n = 0; n < oAttachments.size(); n++) {
1635: Reference oAttachmentRef = (Reference) oAttachments
1636: .get(n);
1637: String oAttachmentId = ((Reference) oAttachments
1638: .get(n)).getId();
1639: if (oAttachmentId.indexOf(fromContext) != -1) {
1640: // replace old site id with new site id in attachments
1641: String nAttachmentId = oAttachmentId
1642: .replaceAll(fromContext,
1643: toContext);
1644: try {
1645: ContentResource attachment = ContentHostingService
1646: .getResource(nAttachmentId);
1647: nAttachments
1648: .add(m_entityManager
1649: .newReference(attachment
1650: .getReference()));
1651: } catch (IdUnusedException ee) {
1652: try {
1653: ContentResource oAttachment = ContentHostingService
1654: .getResource(oAttachmentId);
1655: try {
1656: if (ContentHostingService
1657: .isAttachmentResource(nAttachmentId)) {
1658: // add the new resource into attachment collection area
1659: ContentResource attachment = ContentHostingService
1660: .addAttachmentResource(
1661: Validator
1662: .escapeResourceName(oAttachment
1663: .getProperties()
1664: .getProperty(
1665: ResourceProperties.PROP_DISPLAY_NAME)),
1666: ToolManager
1667: .getCurrentPlacement()
1668: .getContext(),
1669: ToolManager
1670: .getTool(
1671: "sakai.schedule")
1672: .getTitle(),
1673: oAttachment
1674: .getContentType(),
1675: oAttachment
1676: .getContent(),
1677: oAttachment
1678: .getProperties());
1679: // add to attachment list
1680: nAttachments
1681: .add(m_entityManager
1682: .newReference(attachment
1683: .getReference()));
1684: } else {
1685: // add the new resource into resource area
1686: ContentResource attachment = ContentHostingService
1687: .addResource(
1688: Validator
1689: .escapeResourceName(oAttachment
1690: .getProperties()
1691: .getProperty(
1692: ResourceProperties.PROP_DISPLAY_NAME)),
1693: ToolManager
1694: .getCurrentPlacement()
1695: .getContext(),
1696: 1,
1697: oAttachment
1698: .getContentType(),
1699: oAttachment
1700: .getContent(),
1701: oAttachment
1702: .getProperties(),
1703: NotificationService.NOTI_NONE);
1704: // add to attachment list
1705: nAttachments
1706: .add(m_entityManager
1707: .newReference(attachment
1708: .getReference()));
1709: }
1710: } catch (Exception eeAny) {
1711: // if the new resource cannot be added
1712: M_log
1713: .warn(" cannot add new attachment with id="
1714: + nAttachmentId);
1715: }
1716: } catch (Exception eAny) {
1717: // if cannot find the original attachment, do nothing.
1718: M_log
1719: .warn(" cannot find the original attachment with id="
1720: + oAttachmentId);
1721: }
1722: } catch (Exception any) {
1723: M_log.warn(this
1724: + any.getMessage());
1725: }
1726:
1727: } else {
1728: nAttachments.add(oAttachmentRef);
1729: }
1730: }
1731: eEdit.replaceAttachments(nAttachments);
1732: // recurrence rule
1733: RecurrenceRule rule = oEvent
1734: .getRecurrenceRule();
1735: eEdit.setRecurrenceRule(rule);
1736:
1737: try {
1738: BaseCalendarEventEdit oEdit = (BaseCalendarEventEdit) oCalendar
1739: .getEditEvent(oEvent.getId(),
1740: EVENT_ADD_CALENDAR);
1741: RecurrenceRule exRule = oEdit
1742: .getExclusionRule();
1743: eEdit.setExclusionRule(exRule);
1744: } catch (Exception error) {
1745: }
1746:
1747: // commit new event
1748: m_storage.commitEvent(nCalendar, eEdit);
1749: } catch (InUseException eee) {
1750: }
1751: } catch (PermissionException ee) {
1752: }
1753: }
1754: // commit new calendar
1755: m_storage.commitCalendar(nCalendar);
1756: ((BaseCalendarEdit) nCalendar).closeEdit();
1757: } // if
1758: } catch (IdUnusedException e) {
1759: } catch (PermissionException e) {
1760: }
1761:
1762: } // importResources
1763:
1764: /**
1765: * @inheritDoc
1766: */
1767: public String[] myToolIds() {
1768: String[] toolIds = { "sakai.schedule" };
1769: return toolIds;
1770: }
1771:
1772: /**
1773: * {@inheritDoc}
1774: */
1775: public void contextCreated(String context, boolean toolPlacement) {
1776: if (toolPlacement)
1777: enableSchedule(context);
1778: }
1779:
1780: /**
1781: * {@inheritDoc}
1782: */
1783: public void contextUpdated(String context, boolean toolPlacement) {
1784: if (toolPlacement)
1785: enableSchedule(context);
1786: }
1787:
1788: /**
1789: * {@inheritDoc}
1790: */
1791: public void contextDeleted(String context, boolean toolPlacement) {
1792: disableSchedule(context);
1793: }
1794:
1795: /**
1796: * Setup a calendar for the site.
1797: *
1798: * @param site
1799: * The site.
1800: */
1801: protected void enableSchedule(String context) {
1802: // form the calendar name
1803: String calRef = calendarReference(context,
1804: SiteService.MAIN_CONTAINER);
1805:
1806: // see if there's a calendar
1807: try {
1808: getCalendar(calRef);
1809: } catch (IdUnusedException un) {
1810: try {
1811: // create a calendar
1812: CalendarEdit edit = addCalendar(calRef);
1813: commitCalendar(edit);
1814: } catch (IdUsedException e) {
1815: } catch (IdInvalidException e) {
1816: }
1817: } catch (PermissionException e) {
1818: }
1819: }
1820:
1821: /**
1822: * Remove a calendar for the site.
1823: *
1824: * @param site
1825: * The site.
1826: */
1827: protected void disableSchedule(String context) {
1828: // TODO: currently we do not remove a calendar when the tool is removed from the site or the site is deleted -ggolden
1829: }
1830:
1831: /**********************************************************************************************************************************************************************************************************************************************************
1832: * Calendar implementation
1833: *********************************************************************************************************************************************************************************************************************************************************/
1834:
1835: public class BaseCalendarEdit extends Observable implements
1836: CalendarEdit, SessionBindingListener {
1837: /** The context in which this calendar exists. */
1838: protected String m_context = null;
1839:
1840: /** Store the unique-in-context calendar id. */
1841: protected String m_id = null;
1842:
1843: /** The properties. */
1844: protected ResourcePropertiesEdit m_properties = null;
1845:
1846: /** When true, the calendar has been removed. */
1847: protected boolean m_isRemoved = false;
1848:
1849: /** The event code for this edit. */
1850: protected String m_event = null;
1851:
1852: /** Active flag. */
1853: protected boolean m_active = false;
1854:
1855: /**
1856: * Construct with an id.
1857: *
1858: * @param ref
1859: * The calendar reference.
1860: */
1861: public BaseCalendarEdit(String ref) {
1862: // set the ids
1863: Reference r = m_entityManager.newReference(ref);
1864: m_context = r.getContext();
1865: m_id = r.getId();
1866:
1867: // setup for properties
1868: m_properties = new BaseResourcePropertiesEdit();
1869:
1870: } // BaseCalendarEdit
1871:
1872: /**
1873: * Construct as a copy of another.
1874: *
1875: * @param id
1876: * The other to copy.
1877: */
1878: public BaseCalendarEdit(Calendar other) {
1879: // set the ids
1880: m_context = other.getContext();
1881: m_id = other.getId();
1882:
1883: // setup for properties
1884: m_properties = new BaseResourcePropertiesEdit();
1885: m_properties.addAll(other.getProperties());
1886:
1887: } // BaseCalendarEdit
1888:
1889: /**
1890: * Construct from a calendar (and possibly events) already defined in XML in a DOM tree. The Calendar is added to storage.
1891: *
1892: * @param el
1893: * The XML DOM element defining the calendar.
1894: */
1895: public BaseCalendarEdit(Element el) {
1896: // setup for properties
1897: m_properties = new BaseResourcePropertiesEdit();
1898:
1899: m_id = el.getAttribute("id");
1900: m_context = el.getAttribute("context");
1901:
1902: // the children (properties, ignore events)
1903: NodeList children = el.getChildNodes();
1904: final int length = children.getLength();
1905: for (int i = 0; i < length; i++) {
1906: Node child = children.item(i);
1907: if (child.getNodeType() != Node.ELEMENT_NODE)
1908: continue;
1909: Element element = (Element) child;
1910:
1911: // look for properties (ignore possible "event" entries)
1912: if (element.getTagName().equals("properties")) {
1913: // re-create properties
1914: m_properties = new BaseResourcePropertiesEdit(
1915: element);
1916: }
1917: }
1918:
1919: } // BaseCalendarEdit
1920:
1921: /**
1922: * Clean up.
1923: */
1924: protected void finalize() {
1925: deleteObservers();
1926:
1927: // catch the case where an edit was made but never resolved
1928: if (m_active) {
1929: cancelCalendar(this );
1930: }
1931:
1932: } // finalize
1933:
1934: /**
1935: * Set the calendar as removed.
1936: *
1937: * @param event
1938: * The tracking event associated with this action.
1939: */
1940: public void setRemoved(Event event) {
1941: m_isRemoved = true;
1942:
1943: // notify observers
1944: notify(event);
1945:
1946: // now clear observers
1947: deleteObservers();
1948:
1949: } // setRemoved
1950:
1951: /**
1952: * Access the context of the resource.
1953: *
1954: * @return The context.
1955: */
1956: public String getContext() {
1957: return m_context;
1958:
1959: } // getContext
1960:
1961: /**
1962: * Access the id of the resource.
1963: *
1964: * @return The id.
1965: */
1966: public String getId() {
1967: return m_id;
1968:
1969: } // getId
1970:
1971: /**
1972: * Access the URL which can be used to access the resource.
1973: *
1974: * @return The URL which can be used to access the resource.
1975: */
1976: public String getUrl() {
1977: return getAccessPoint(false) + SEPARATOR + getId()
1978: + SEPARATOR; // %%% needs fixing re: context
1979:
1980: } // getUrl
1981:
1982: /**
1983: * Access the internal reference which can be used to access the resource from within the system.
1984: *
1985: * @return The the internal reference which can be used to access the resource from within the system.
1986: */
1987: public String getReference() {
1988: return calendarReference(m_context, m_id);
1989:
1990: } // getReference
1991:
1992: /**
1993: * @inheritDoc
1994: */
1995: public String getReference(String rootProperty) {
1996: return getReference();
1997: }
1998:
1999: /**
2000: * @inheritDoc
2001: */
2002: public String getUrl(String rootProperty) {
2003: return getUrl();
2004: }
2005:
2006: /**
2007: * Access the collection's properties.
2008: *
2009: * @return The collection's properties.
2010: */
2011: public ResourceProperties getProperties() {
2012: return m_properties;
2013:
2014: } // getProperties
2015:
2016: /**
2017: * check permissions for getEvents() and getEvent().
2018: *
2019: * @return true if the user is allowed to get events from the calendar, false if not.
2020: */
2021: public boolean allowGetEvents() {
2022: return unlockCheck(AUTH_READ_CALENDAR, getReference());
2023:
2024: } // allowGetEvents
2025:
2026: /**
2027: * {@inheritDoc}
2028: */
2029: public boolean allowGetEvent(String eventId) {
2030: return unlockCheck(AUTH_READ_CALENDAR, eventReference(
2031: m_context, m_id, eventId));
2032: }
2033:
2034: /**
2035: * Return a List of all or filtered events in the calendar. The order in which the events will be found in the iteration is by event start date.
2036: *
2037: * @param range
2038: * A time range to limit the iterated events. May be null; all events will be returned.
2039: * @param filter
2040: * A filtering object to accept events into the iterator, or null if no filtering is desired.
2041: * @return a List of all or filtered CalendarEvents in the calendar (may be empty).
2042: * @exception PermissionException
2043: * if the user does not have read permission to the calendar.
2044: */
2045: public List getEvents(TimeRange range, Filter filter)
2046: throws PermissionException {
2047: // check security (throws if not permitted)
2048: unlock(AUTH_READ_CALENDAR, getReference());
2049:
2050: List events = new Vector();
2051:
2052: if ((!m_caching) || (m_calendarCache == null)
2053: || (m_calendarCache.disabled())) {
2054: // TODO: do we really want to do this? -ggolden
2055: // if we have done this already in this thread, use that
2056: events = (List) ThreadLocalManager.get(getReference()
2057: + ".events");
2058: if (events == null) {
2059: events = m_storage.getEvents(this );
2060:
2061: // "cache" the events in the current service in case they are needed again in this thread...
2062: ThreadLocalManager.set(getReference() + ".events",
2063: events);
2064: }
2065: }
2066:
2067: else {
2068: // find the event cache
2069: Cache eventCache = (Cache) m_eventCaches
2070: .get(getReference());
2071: if (eventCache == null) {
2072: synchronized (m_eventCaches) {
2073: // check again
2074: eventCache = (Cache) m_eventCaches
2075: .get(getReference());
2076:
2077: // if still not there, make one
2078: if (eventCache == null) {
2079: eventCache = m_memoryService.newCache(
2080: service(), eventReference(
2081: m_context, m_id, ""));
2082: m_eventCaches.put(getReference(),
2083: eventCache);
2084: }
2085: }
2086: }
2087:
2088: // if the cache is complete, use it
2089: if (eventCache.isComplete()) {
2090: // get just this calendar's events
2091: events = eventCache.getAll();
2092: }
2093:
2094: // otherwise get all the events from storage
2095: else {
2096: // Note: while we are getting from storage, storage might change. These can be processed
2097: // after we get the storage entries, and put them in the cache, and mark the cache complete.
2098: // -ggolden
2099: synchronized (eventCache) {
2100: // if we were waiting and it's now complete...
2101: if (eventCache.isComplete()) {
2102: // get just this calendar's events
2103: events = eventCache.getAll();
2104: } else {
2105: // save up any events to the cache until we get past this load
2106: eventCache.holdEvents();
2107:
2108: // get all the events for the calendar
2109: events = m_storage.getEvents(this );
2110:
2111: // update the cache, and mark it complete
2112: for (int i = 0; i < events.size(); i++) {
2113: CalendarEvent event = (CalendarEvent) events
2114: .get(i);
2115: eventCache.put(event.getReference(),
2116: event);
2117: }
2118:
2119: eventCache.setComplete();
2120:
2121: // now we are complete, process any cached events
2122: eventCache.processEvents();
2123: }
2124: }
2125: }
2126: }
2127:
2128: if (events.size() == 0)
2129: return events;
2130:
2131: // now filter out the events to just those in the range
2132: // Note: if no range, we won't filter, which means we don't expand recurring events, but just
2133: // return it as a single event. This is very good for an archive... -ggolden
2134: if (range != null) {
2135: events = filterEvents(events, range);
2136: }
2137:
2138: // filter out based on the filter
2139: if (filter != null) {
2140: List filtered = new Vector();
2141: for (int i = 0; i < events.size(); i++) {
2142: Event event = (Event) events.get(i);
2143: if (filter.accept(event))
2144: filtered.add(event);
2145: }
2146: if (filtered.size() == 0)
2147: return filtered;
2148: events = filtered;
2149: }
2150:
2151: // remove any events that are grouped, and that the current user does not have permission to see
2152: Collection groupsAllowed = getGroupsAllowGetEvent();
2153: List allowedEvents = new Vector();
2154: for (Iterator i = events.iterator(); i.hasNext();) {
2155: CalendarEvent event = (CalendarEvent) i.next();
2156: if (event.getAccess() == EventAccess.SITE) {
2157: allowedEvents.add(event);
2158: }
2159:
2160: else {
2161: // if the user's Groups overlap the event's group refs it's grouped to, keep it
2162: if (EntityCollections
2163: .isIntersectionEntityRefsToEntities(event
2164: .getGroups(), groupsAllowed)) {
2165: allowedEvents.add(event);
2166: }
2167: }
2168: }
2169:
2170: // sort - natural order is date ascending
2171: Collections.sort(allowedEvents);
2172:
2173: return allowedEvents;
2174:
2175: } // getEvents
2176:
2177: /**
2178: * Filter the events to only those in the time range.
2179: *
2180: * @param events
2181: * The full list of events.
2182: * @param range
2183: * The time range.
2184: * @return A list of events from the incoming list that overlap the given time range.
2185: */
2186: protected List filterEvents(List events, TimeRange range) {
2187: List filtered = new Vector();
2188: for (int i = 0; i < events.size(); i++) {
2189: CalendarEvent event = (CalendarEvent) events.get(i);
2190:
2191: // resolve the event to the list of events in this range
2192: List resolved = ((BaseCalendarEventEdit) event)
2193: .resolve(range);
2194: filtered.addAll(resolved);
2195: }
2196:
2197: return filtered;
2198:
2199: } // filterEvents
2200:
2201: /**
2202: * Return a specific calendar event, as specified by event id.
2203: *
2204: * @param eventId
2205: * The id of the event to get.
2206: * @return the CalendarEvent that has the specified id.
2207: * @exception IdUnusedException
2208: * If this id is not a defined event in this calendar.
2209: * @exception PermissionException
2210: * If the user does not have any permissions to read the calendar.
2211: */
2212: public CalendarEvent getEvent(String eventId)
2213: throws IdUnusedException, PermissionException {
2214: // check security on the event (throws if not permitted)
2215: unlock(AUTH_READ_CALENDAR, eventReference(m_context, m_id,
2216: eventId));
2217:
2218: CalendarEvent e = findEvent(eventId);
2219:
2220: if (e == null)
2221: throw new IdUnusedException(eventId);
2222:
2223: return e;
2224:
2225: } // getEvent
2226:
2227: /**
2228: * check permissions for addEvent().
2229: *
2230: * @return true if the user is allowed to addEvent(...), false if not.
2231: */
2232: public boolean allowAddEvent() {
2233: // checking allow at the channel (site) level
2234: if (allowAddCalendarEvent())
2235: return true;
2236:
2237: // if not, see if the user has any groups to which adds are allowed
2238: return (!getGroupsAllowAddEvent().isEmpty());
2239:
2240: } // allowAddEvent
2241:
2242: /**
2243: * @inheritDoc
2244: */
2245: public boolean allowAddCalendarEvent() {
2246: // check for events that will be calendar (site) -wide:
2247: // base the check for SECURE_ADD on the site and the calendar only (not the groups).
2248:
2249: // check security on the calendar (throws if not permitted)
2250: return unlockCheck(AUTH_ADD_CALENDAR, getReference());
2251: }
2252:
2253: /**
2254: * Add a new event to this calendar.
2255: *
2256: * @param range
2257: * The event's time range.
2258: * @param displayName
2259: * The event's display name (PROP_DISPLAY_NAME) property value.
2260: * @param description
2261: * The event's description as plain text (PROP_DESCRIPTION) property value.
2262: * @param type
2263: * The event's calendar event type (PROP_CALENDAR_TYPE) property value.
2264: * @param location
2265: * The event's calendar event location (PROP_CALENDAR_LOCATION) property value.
2266: * @param attachments
2267: * The event attachments, a vector of Reference objects.
2268: * @return The newly added event.
2269: * @exception PermissionException
2270: * If the user does not have permission to modify the calendar.
2271: */
2272: public CalendarEvent addEvent(TimeRange range,
2273: String displayName, String description, String type,
2274: String location, EventAccess access, Collection groups,
2275: List attachments) throws PermissionException {
2276: // securtiy check (any sort (group, site) of add)
2277: if (!allowAddEvent()) {
2278: throw new PermissionException(SessionManager
2279: .getCurrentSessionUserId(),
2280: eventId(SECURE_ADD), getReference());
2281: }
2282:
2283: // make one
2284: // allocate a new unique event id
2285: String id = getUniqueId();
2286:
2287: // get a new event in the info store
2288: CalendarEventEdit edit = m_storage.putEvent(this , id);
2289:
2290: ((BaseCalendarEventEdit) edit).setEvent(EVENT_ADD_CALENDAR);
2291:
2292: // set it up
2293: edit.setRange(range);
2294: edit.setDisplayName(displayName);
2295: edit.setDescription(description);
2296: edit.setType(type);
2297: edit.setLocation(location);
2298: edit.setCreator();
2299:
2300: // for site...
2301: if (access == EventAccess.SITE) {
2302: // if not allowd to SITE, will throw permission exception
2303: try {
2304: edit.clearGroupAccess();
2305: } catch (PermissionException e) {
2306: cancelEvent(edit);
2307: throw new PermissionException(SessionManager
2308: .getCurrentSessionUserId(),
2309: eventId(SECURE_ADD), getReference());
2310: }
2311: }
2312:
2313: // for grouped...
2314: else {
2315: // if not allowed to GROUP, will throw permission exception
2316: try {
2317: edit.setGroupAccess(groups, true);
2318: } catch (PermissionException e) {
2319: cancelEvent(edit);
2320: throw new PermissionException(SessionManager
2321: .getCurrentSessionUserId(),
2322: eventId(SECURE_ADD), getReference());
2323: }
2324: }
2325:
2326: edit.replaceAttachments(attachments);
2327:
2328: // commit it
2329: commitEvent(edit);
2330:
2331: return edit;
2332:
2333: } // addEvent
2334:
2335: /**
2336: * Add a new event to this calendar.
2337: *
2338: * @param range
2339: * The event's time range.
2340: * @param displayName
2341: * The event's display name (PROP_DISPLAY_NAME) property value.
2342: * @param description
2343: * The event's description as plain text (PROP_DESCRIPTION) property value.
2344: * @param type
2345: * The event's calendar event type (PROP_CALENDAR_TYPE) property value.
2346: * @param location
2347: * The event's calendar event location (PROP_CALENDAR_LOCATION) property value.
2348: * @param attachments
2349: * The event attachments, a vector of Reference objects.
2350: * @return The newly added event.
2351: * @exception PermissionException
2352: * If the user does not have permission to modify the calendar.
2353: */
2354: public CalendarEvent addEvent(TimeRange range,
2355: String displayName, String description, String type,
2356: String location, List attachments)
2357: throws PermissionException {
2358: // make one
2359: CalendarEventEdit edit = addEvent();
2360:
2361: // set it up
2362: edit.setRange(range);
2363: edit.setDisplayName(displayName);
2364: edit.setDescription(description);
2365: edit.setType(type);
2366: edit.setLocation(location);
2367: edit.replaceAttachments(attachments);
2368:
2369: // commit it
2370: commitEvent(edit);
2371:
2372: return edit;
2373:
2374: } // addEvent
2375:
2376: /**
2377: * Add a new event to this calendar. Must commitEvent() to make official, or cancelEvent() when done!
2378: *
2379: * @return The newly added event, locked for update.
2380: * @exception PermissionException
2381: * If the user does not have write permission to the calendar.
2382: */
2383: public CalendarEventEdit addEvent() throws PermissionException {
2384: // check security (throws if not permitted)
2385: unlock(AUTH_ADD_CALENDAR, getReference());
2386:
2387: // allocate a new unique event id
2388: String id = getUniqueId();
2389:
2390: // get a new event in the info store
2391: CalendarEventEdit event = m_storage.putEvent(this , id);
2392:
2393: ((BaseCalendarEventEdit) event)
2394: .setEvent(EVENT_ADD_CALENDAR);
2395:
2396: return event;
2397:
2398: } // addEvent
2399:
2400: /**
2401: * Merge in a new event as defined in the xml.
2402: *
2403: * @param el
2404: * The event information in XML in a DOM element.
2405: * @exception PermissionException
2406: * If the user does not have write permission to the calendar.
2407: * @exception IdUsedException
2408: * if the user id is already used.
2409: */
2410: public CalendarEventEdit mergeEvent(Element el)
2411: throws PermissionException, IdUsedException {
2412: CalendarEvent eventFromXml = (CalendarEvent) newResource(
2413: this , el);
2414:
2415: // check security
2416: if (!allowAddEvent())
2417: throw new PermissionException(SessionManager
2418: .getCurrentSessionUserId(), AUTH_ADD_CALENDAR,
2419: getReference());
2420: // reserve a calendar event with this id from the info store - if it's in use, this will return null
2421: CalendarEventEdit event = m_storage.putEvent(this ,
2422: eventFromXml.getId());
2423: if (event == null) {
2424: throw new IdUsedException(eventFromXml.getId());
2425: }
2426:
2427: // transfer from the XML read object to the Edit
2428: ((BaseCalendarEventEdit) event).set(eventFromXml);
2429:
2430: ((BaseCalendarEventEdit) event)
2431: .setEvent(EVENT_MODIFY_CALENDAR);
2432:
2433: return event;
2434:
2435: } // mergeEvent
2436:
2437: /**
2438: * check permissions for removeEvent().
2439: *
2440: * @param event
2441: * The event from this calendar to remove.
2442: * @return true if the user is allowed to removeEvent(event), false if not.
2443: */
2444: public boolean allowRemoveEvent(CalendarEvent event) {
2445: boolean allowed = false;
2446: boolean ownEvent = event.isUserOwner();
2447:
2448: // check security to delete any event
2449: if (unlockCheck(AUTH_REMOVE_CALENDAR_ANY, getReference()))
2450: allowed = true;
2451:
2452: // check security to delete own event
2453: else if (unlockCheck(AUTH_REMOVE_CALENDAR_OWN,
2454: getReference())
2455: && ownEvent)
2456: allowed = true;
2457:
2458: // but we must also assure, that for grouped events, we can remove it from ALL of the groups
2459: if (allowed && (event.getAccess() == EventAccess.GROUPED)) {
2460: allowed = EntityCollections
2461: .isContainedEntityRefsToEntities(event
2462: .getGroups(),
2463: getGroupsAllowRemoveEvent(ownEvent));
2464: }
2465:
2466: return allowed;
2467:
2468: } // allowRemoveEvent
2469:
2470: /**
2471: * Remove an event from the calendar, one locked for edit. Note: if the event is a recurring event, the entire sequence is modified by this commit (MOD_ALL).
2472: *
2473: * @param event
2474: * The event from this calendar to remove.
2475: */
2476: public void removeEvent(CalendarEventEdit edit)
2477: throws PermissionException {
2478: removeEvent(edit, MOD_ALL);
2479:
2480: } // removeEvent
2481:
2482: /**
2483: * Remove an event from the calendar, one locked for edit.
2484: *
2485: * @param event
2486: * The event from this calendar to remove.
2487: * @param intention
2488: * The recurring event modification intention, based on values in the CalendarService "MOD_*", used if the event is part of a recurring event sequence to determine how much of the sequence is removed.
2489: */
2490: public void removeEvent(CalendarEventEdit edit, int intention)
2491: throws PermissionException {
2492: // check for closed edit
2493: if (!edit.isActiveEdit()) {
2494: try {
2495: throw new Exception();
2496: } catch (Exception e) {
2497: M_log.warn("removeEvent(): closed EventEdit", e);
2498: }
2499: return;
2500: }
2501:
2502: // securityCheck
2503: if (!allowRemoveEvent(edit)) {
2504: cancelEvent(edit);
2505: throw new PermissionException(SessionManager
2506: .getCurrentSessionUserId(),
2507: AUTH_REMOVE_CALENDAR_ANY, edit.getReference());
2508: }
2509:
2510: BaseCalendarEventEdit bedit = (BaseCalendarEventEdit) edit;
2511:
2512: // if the id has a time range encoded, as for one of a sequence of recurring events, separate that out
2513: TimeRange timeRange = null;
2514: int sequence = 0;
2515: if (bedit.m_id.startsWith("!")) {
2516: String[] parts = StringUtil.split(bedit.m_id
2517: .substring(1), "!");
2518: try {
2519: timeRange = TimeService.newTimeRange(parts[0]);
2520: sequence = Integer.parseInt(parts[1]);
2521: bedit.m_id = parts[2];
2522: } catch (Exception ex) {
2523: M_log
2524: .warn("removeEvent: exception parsing eventId: "
2525: + bedit.m_id + " : " + ex);
2526: }
2527: }
2528:
2529: // deal with recurring event sequence modification
2530: if (timeRange != null) {
2531: // delete only this - add it as an exclusion in the edit
2532: if (intention == MOD_THIS) {
2533: // get the edit back to initial values... so only the exclusion is changed
2534: edit = (CalendarEventEdit) m_storage.getEvent(this ,
2535: bedit.m_id);
2536: bedit = (BaseCalendarEventEdit) edit;
2537:
2538: // add an exclusion for where this one would have been %%% we are changing it, should it be immutable? -ggolden
2539: List exclusions = ((ExclusionSeqRecurrenceRule) bedit
2540: .getExclusionRule()).getExclusions();
2541: exclusions.add(new Integer(sequence));
2542:
2543: // complete the edit
2544: m_storage.commitEvent(this , edit);
2545: }
2546:
2547: // delete them all, i.e. the one initial event
2548: else {
2549: m_storage.removeEvent(this , edit);
2550: }
2551: }
2552:
2553: // else a single event to delete
2554: else {
2555: m_storage.removeEvent(this , edit);
2556: }
2557:
2558: // track event
2559: Event event = EventTrackingService.newEvent(
2560: EVENT_MODIFY_CALENDAR, edit.getReference(), true);
2561: EventTrackingService.post(event);
2562:
2563: // calendar notification
2564: notify(event);
2565:
2566: // close the edit object
2567: ((BaseCalendarEventEdit) edit).closeEdit();
2568:
2569: // remove any realm defined for this resource
2570: try {
2571: AuthzGroupService.removeAuthzGroup(AuthzGroupService
2572: .getAuthzGroup(edit.getReference()));
2573: } catch (AuthzPermissionException e) {
2574: M_log.warn("removeEvent: removing realm for : "
2575: + edit.getReference() + " : " + e);
2576: } catch (GroupNotDefinedException ignore) {
2577: }
2578:
2579: } // removeEvent
2580:
2581: /**
2582: * check permissions for editEvent()
2583: *
2584: * @param id
2585: * The event id.
2586: * @return true if the user is allowed to update the event, false if not.
2587: */
2588: public boolean allowEditEvent(String eventId) {
2589: CalendarEvent e = findEvent(eventId);
2590: if (e == null)
2591: return false;
2592:
2593: boolean ownEvent = e.isUserOwner();
2594:
2595: // check security to revise any event
2596: if (unlockCheck(AUTH_MODIFY_CALENDAR_ANY, getReference()))
2597: return true;
2598:
2599: // check security to revise own event
2600: else if (unlockCheck(AUTH_MODIFY_CALENDAR_OWN,
2601: getReference())
2602: && ownEvent)
2603: return true;
2604:
2605: // otherwise not authorized
2606: else
2607: return false;
2608:
2609: } // allowEditEvent
2610:
2611: /**
2612: * Return a specific calendar event, as specified by event name, locked for update.
2613: * Must commitEvent() to make official, or cancelEvent(), or removeEvent() when done!
2614: *
2615: * @param eventId The id of the event to get.
2616: * @param editType add, remove or modifying calendar?
2617: * @return the Event that has the specified id.
2618: * @exception IdUnusedException
2619: * If this name is not a defined event in this calendar.
2620: * @exception PermissionException
2621: * If the user does not have any permissions to edit the event.
2622: * @exception InUseException
2623: * if the event is locked for edit by someone else.
2624: */
2625: public CalendarEventEdit getEditEvent(String eventId,
2626: String editType) throws IdUnusedException,
2627: PermissionException, InUseException {
2628: // if the id has a time range encoded, as for one of a sequence of recurring events, separate that out
2629: TimeRange timeRange = null;
2630: int sequence = 0;
2631: if (eventId.startsWith("!")) {
2632: String[] parts = StringUtil.split(eventId.substring(1),
2633: "!");
2634: try {
2635: timeRange = TimeService.newTimeRange(parts[0]);
2636: sequence = Integer.parseInt(parts[1]);
2637: eventId = parts[2];
2638: } catch (Exception ex) {
2639: M_log
2640: .warn("getEditEvent: exception parsing eventId: "
2641: + eventId + " : " + ex);
2642: }
2643: }
2644:
2645: CalendarEvent e = findEvent(eventId);
2646: if (e == null)
2647: throw new IdUnusedException(eventId);
2648:
2649: // check security
2650: if (editType.equals(EVENT_ADD_CALENDAR) && !allowAddEvent())
2651: throw new PermissionException(SessionManager
2652: .getCurrentSessionUserId(), AUTH_ADD_CALENDAR,
2653: getReference());
2654: else if (editType.equals(EVENT_REMOVE_CALENDAR)
2655: && !allowRemoveEvent(e))
2656: throw new PermissionException(SessionManager
2657: .getCurrentSessionUserId(),
2658: AUTH_REMOVE_CALENDAR_ANY, getReference());
2659: else if (editType.equals(EVENT_MODIFY_CALENDAR)
2660: && !allowEditEvent(eventId))
2661: throw new PermissionException(SessionManager
2662: .getCurrentSessionUserId(),
2663: AUTH_MODIFY_CALENDAR_ANY, getReference());
2664:
2665: // ignore the cache - get the CalendarEvent with a lock from the info store
2666: CalendarEventEdit edit = m_storage.editEvent(this , eventId);
2667: if (edit == null)
2668: throw new InUseException(eventId);
2669:
2670: BaseCalendarEventEdit bedit = (BaseCalendarEventEdit) edit;
2671:
2672: // if this is one in a sequence, adjust it
2673: if (timeRange != null) {
2674: // move the specified range into the event's range, storing the base range
2675: bedit.m_baseRange = bedit.m_range;
2676: bedit.m_range = timeRange;
2677: bedit.m_id = '!' + bedit.m_range.toString() + '!'
2678: + sequence + '!' + bedit.m_id;
2679: }
2680:
2681: bedit.setEvent(EVENT_MODIFY_CALENDAR);
2682:
2683: return edit;
2684:
2685: } // getEditEvent
2686:
2687: /**
2688: * Commit the changes made to a CalendarEventEdit object, and release the lock. The CalendarEventEdit is disabled, and not to be used after this call. Note: if the event is a recurring event, the entire sequence is modified by this commit
2689: * (MOD_ALL).
2690: *
2691: * @param edit
2692: * The CalendarEventEdit object to commit.
2693: */
2694: public void commitEvent(CalendarEventEdit edit) {
2695: commitEvent(edit, MOD_ALL);
2696:
2697: } // commitEvent
2698:
2699: /**
2700: * Commit the changes made to a CalendarEventEdit object, and release the lock. The CalendarEventEdit is disabled, and not to be used after this call.
2701: *
2702: * @param edit
2703: * The CalendarEventEdit object to commit.
2704: * @param intention
2705: * The recurring event modification intention, based on values in the CalendarService "MOD_*", used if the event is part of a recurring event sequence to determine how much of the sequence is changed by this commmit.
2706: */
2707: public void commitEvent(CalendarEventEdit edit, int intention) {
2708: // check for closed edit
2709: if (!edit.isActiveEdit()) {
2710: try {
2711: throw new Exception();
2712: } catch (Exception e) {
2713: M_log.warn(
2714: "commitEvent(): closed CalendarEventEdit",
2715: e);
2716: }
2717: return;
2718: }
2719:
2720: BaseCalendarEventEdit bedit = (BaseCalendarEventEdit) edit;
2721:
2722: // If creator doesn't exist, set it now (backward compatibility)
2723: if (edit.getCreator() == null
2724: || edit.getCreator().equals(""))
2725: edit.setCreator();
2726:
2727: edit.setModifiedBy(); // update modified-by properties
2728:
2729: // if the id has a time range encoded, as for one of a sequence of recurring events, separate that out
2730: TimeRange timeRange = null;
2731: int sequence = 0;
2732: if (bedit.m_id.startsWith("!")) {
2733: String[] parts = StringUtil.split(bedit.m_id
2734: .substring(1), "!");
2735: try {
2736: timeRange = TimeService.newTimeRange(parts[0]);
2737: sequence = Integer.parseInt(parts[1]);
2738: bedit.m_id = parts[2];
2739: } catch (Exception ex) {
2740: M_log
2741: .warn("commitEvent: exception parsing eventId: "
2742: + bedit.m_id + " : " + ex);
2743: }
2744: }
2745:
2746: // for recurring event sequence
2747: TimeRange newTimeRange = null;
2748: BaseCalendarEventEdit newEvent = null;
2749: if (timeRange != null) {
2750: // if changing this event only
2751: if (intention == MOD_THIS) {
2752: // make a new event for this one
2753: String id = getUniqueId();
2754: newEvent = (BaseCalendarEventEdit) m_storage
2755: .putEvent(this , id);
2756: newEvent.setPartial(edit);
2757: m_storage.commitEvent(this , newEvent);
2758: EventTrackingService.post(EventTrackingService
2759: .newEvent(EVENT_MODIFY_CALENDAR, newEvent
2760: .getReference(), true));
2761:
2762: // get the edit back to initial values... so only the exclusion is changed
2763: edit = (CalendarEventEdit) m_storage.getEvent(this ,
2764: bedit.m_id);
2765: bedit = (BaseCalendarEventEdit) edit;
2766:
2767: // add an exclusion for where this one would have been %%% we are changing it, should it be immutable? -ggolden
2768: List exclusions = ((ExclusionSeqRecurrenceRule) bedit
2769: .getExclusionRule()).getExclusions();
2770: exclusions.add(new Integer(sequence));
2771: }
2772:
2773: // else change the entire sequence (i.e. the one initial event)
2774: else {
2775: // the time range may have been modified in the edit
2776: newTimeRange = bedit.m_range;
2777:
2778: // restore the real range, that of the base event of a sequence, if this is one of the other events in the sequence.
2779: bedit.m_range = bedit.m_baseRange;
2780:
2781: // adjust the base range if there was an edit to range
2782: bedit.m_range.adjust(timeRange, newTimeRange);
2783: }
2784: }
2785:
2786: // update the properties
2787: // addLiveUpdateProperties(edit.getPropertiesEdit());//%%%
2788:
2789: // complete the edit
2790: m_storage.commitEvent(this , edit);
2791:
2792: // track event
2793: Event event = EventTrackingService.newEvent(bedit
2794: .getEvent(), edit.getReference(), true);
2795: EventTrackingService.post(event);
2796:
2797: // calendar notification
2798: notify(event);
2799:
2800: // close the edit object
2801: bedit.closeEdit();
2802:
2803: // restore this one's range etc so it can be further referenced
2804: if (timeRange != null) {
2805: // if changing this event only
2806: if (intention == MOD_THIS) {
2807: // set the edit to the values of the new event
2808: bedit.set(newEvent);
2809: }
2810:
2811: // else we changed the sequence
2812: else {
2813: // move the specified range into the event's range, storing the base range
2814: bedit.m_baseRange = bedit.m_range;
2815: bedit.m_range = newTimeRange;
2816: bedit.m_id = '!' + bedit.m_range.toString() + '!'
2817: + sequence + '!' + bedit.m_id;
2818: }
2819: }
2820:
2821: } // commitEvent
2822:
2823: /**
2824: * Cancel the changes made to a CalendarEventEdit object, and release the lock. The CalendarEventEdit is disabled, and not to be used after this call.
2825: *
2826: * @param edit
2827: * The CalendarEventEdit object to commit.
2828: */
2829: public void cancelEvent(CalendarEventEdit edit) {
2830: // check for closed edit
2831: if (!edit.isActiveEdit()) {
2832: try {
2833: throw new Exception();
2834: } catch (Exception e) {
2835: M_log.warn(
2836: "cancelEvent(): closed CalendarEventEdit",
2837: e);
2838: }
2839: return;
2840: }
2841:
2842: BaseCalendarEventEdit bedit = (BaseCalendarEventEdit) edit;
2843:
2844: // if the id has a time range encoded, as for one of a sequence of recurring events, separate that out
2845: TimeRange timeRange = null;
2846: int sequence = 0;
2847: if (bedit.m_id.startsWith("!")) {
2848: String[] parts = StringUtil.split(bedit.m_id
2849: .substring(1), "!");
2850: try {
2851: timeRange = TimeService.newTimeRange(parts[0]);
2852: sequence = Integer.parseInt(parts[1]);
2853: bedit.m_id = parts[2];
2854: } catch (Exception ex) {
2855: M_log
2856: .warn("commitEvent: exception parsing eventId: "
2857: + bedit.m_id + " : " + ex);
2858: }
2859: }
2860:
2861: // release the edit lock
2862: m_storage.cancelEvent(this , edit);
2863:
2864: // close the edit object
2865: ((BaseCalendarEventEdit) edit).closeEdit();
2866:
2867: } // cancelCalendarEvent
2868:
2869: /**
2870: * Return the extra fields kept for each event in this calendar.
2871: *
2872: * @return the extra fields kept for each event in this calendar, formatted into a single string. %%%
2873: */
2874: public String getEventFields() {
2875: return m_properties
2876: .getPropertyFormatted(ResourceProperties.PROP_CALENDAR_EVENT_FIELDS);
2877:
2878: } // getEventFields
2879:
2880: /**
2881: * Set the extra fields kept for each event in this calendar.
2882: *
2883: * @param meta
2884: * The extra fields kept for each event in this calendar, formatted into a single string. %%%
2885: */
2886: public void setEventFields(String fields) {
2887: m_properties.addProperty(
2888: ResourceProperties.PROP_CALENDAR_EVENT_FIELDS,
2889: fields);
2890:
2891: } // setEventFields
2892:
2893: /**
2894: * Notify the calendar that it has changed
2895: *
2896: * @param event
2897: * The event that caused the update.
2898: */
2899: public void notify(Event event) {
2900: // notify observers, sending the tracking event to identify the change
2901: setChanged();
2902: notifyObservers(event);
2903:
2904: } // notify
2905:
2906: /**
2907: * Serialize the resource into XML, adding an element to the doc under the top of the stack element.
2908: *
2909: * @param doc
2910: * The DOM doc to contain the XML (or null for a string return).
2911: * @param stack
2912: * The DOM elements, the top of which is the containing element of the new "resource" element.
2913: * @return The newly added element.
2914: */
2915: public Element toXml(Document doc, Stack stack) {
2916: Element calendar = doc.createElement("calendar");
2917:
2918: if (stack.isEmpty()) {
2919: doc.appendChild(calendar);
2920: } else {
2921: ((Element) stack.peek()).appendChild(calendar);
2922: }
2923:
2924: stack.push(calendar);
2925:
2926: calendar.setAttribute("context", m_context);
2927: calendar.setAttribute("id", m_id);
2928:
2929: // properties
2930: m_properties.toXml(doc, stack);
2931:
2932: stack.pop();
2933:
2934: return calendar;
2935:
2936: } // toXml
2937:
2938: /**
2939: * Find the event, in cache or info store - cache it if newly found.
2940: *
2941: * @param eventId
2942: * The id of the event.
2943: * @return The event, if found.
2944: */
2945: protected CalendarEvent findEvent(String eventId) {
2946: CalendarEvent e = null;
2947:
2948: // if the id has a time range encoded, as for one of a sequence of recurring events, separate that out
2949: TimeRange timeRange = null;
2950: int sequence = 0;
2951: if (eventId.startsWith("!")) {
2952: String[] parts = StringUtil.split(eventId.substring(1),
2953: "!");
2954: try {
2955: timeRange = TimeService.newTimeRange(parts[0]);
2956: sequence = Integer.parseInt(parts[1]);
2957: eventId = parts[2];
2958: } catch (Exception ex) {
2959: M_log.warn("findEvent: exception parsing eventId: "
2960: + eventId + " : " + ex);
2961: }
2962: }
2963:
2964: // events are cached with the full reference as key
2965: String key = eventReference(m_context, m_id, eventId);
2966:
2967: // if cache is disabled, don't use it
2968: if ((!m_caching) || (m_calendarCache == null)
2969: || (m_calendarCache.disabled())) {
2970: // if we have "cached" the entire set of events in the thread, get that and find our message there
2971: List events = (List) ThreadLocalManager
2972: .get(getReference() + ".events");
2973: if (events != null) {
2974: for (Iterator i = events.iterator(); i.hasNext();) {
2975: CalendarEvent event = (CalendarEvent) i.next();
2976: if (event.getId().equals(eventId)) {
2977: e = event;
2978: break;
2979: }
2980: }
2981: }
2982:
2983: if (e == null) {
2984: e = m_storage.getEvent(this , eventId);
2985: }
2986: }
2987:
2988: else {
2989: // find the event cache
2990: Cache eventCache = (Cache) m_eventCaches
2991: .get(getReference());
2992: if (eventCache == null) {
2993: synchronized (m_eventCaches) {
2994: // check again
2995: eventCache = (Cache) m_eventCaches
2996: .get(getReference());
2997:
2998: // if still not there, make one
2999: if (eventCache == null) {
3000: eventCache = m_memoryService.newCache(
3001: service(), eventReference(
3002: m_context, m_id, ""));
3003: m_eventCaches.put(getReference(),
3004: eventCache);
3005: }
3006: }
3007: }
3008:
3009: // if we have it cached, use it (even if it's cached as a null, a miss)
3010: if (eventCache.containsKey(key)) {
3011: e = (CalendarEvent) eventCache.get(key);
3012: }
3013:
3014: // if not in the cache, see if we have it in our info store
3015: else {
3016: e = m_storage.getEvent(this , eventId);
3017:
3018: // if so, cache it, even misses
3019: eventCache.put(key, e);
3020: }
3021: }
3022:
3023: // now we have the primary event, if we have a recurring event sequence time range selector, use it
3024: if ((e != null) && (timeRange != null)) {
3025: e = new BaseCalendarEventEdit(e,
3026: new RecurrenceInstance(timeRange, sequence));
3027: }
3028:
3029: return e;
3030:
3031: } // findEvent
3032:
3033: /**
3034: * Access the event code for this edit.
3035: *
3036: * @return The event code for this edit.
3037: */
3038: protected String getEvent() {
3039: return m_event;
3040: }
3041:
3042: /**
3043: * Set the event code for this edit.
3044: *
3045: * @param event
3046: * The event code for this edit.
3047: */
3048: protected void setEvent(String event) {
3049: m_event = event;
3050: }
3051:
3052: /**
3053: * Access the resource's properties for modification
3054: *
3055: * @return The resource's properties.
3056: */
3057: public ResourcePropertiesEdit getPropertiesEdit() {
3058: return m_properties;
3059:
3060: } // getPropertiesEdit
3061:
3062: /**
3063: * Enable editing.
3064: */
3065: protected void activate() {
3066: m_active = true;
3067:
3068: } // activate
3069:
3070: /**
3071: * Check to see if the edit is still active, or has already been closed.
3072: *
3073: * @return true if the edit is active, false if it's been closed.
3074: */
3075: public boolean isActiveEdit() {
3076: return m_active;
3077:
3078: } // isActiveEdit
3079:
3080: /**
3081: * Close the edit object - it cannot be used after this.
3082: */
3083: protected void closeEdit() {
3084: m_active = false;
3085:
3086: } // closeEdit
3087:
3088: /**
3089: * {@inheritDoc}
3090: */
3091: public Collection getGroupsAllowAddEvent() {
3092: return getGroupsAllowFunction(AUTH_ADD_CALENDAR);
3093: }
3094:
3095: /**
3096: * {@inheritDoc}
3097: */
3098: public Collection getGroupsAllowGetEvent() {
3099: return getGroupsAllowFunction(AUTH_READ_CALENDAR);
3100: }
3101:
3102: /**
3103: * {@inheritDoc}
3104: */
3105: public Collection getGroupsAllowRemoveEvent(boolean own) {
3106: return getGroupsAllowFunction(own ? AUTH_REMOVE_CALENDAR_OWN
3107: : AUTH_REMOVE_CALENDAR_ANY);
3108: }
3109:
3110: /**
3111: * Get the groups of this channel's contex-site that the end user has permission to "function" in.
3112: * @param function The function to check
3113: */
3114: protected Collection getGroupsAllowFunction(String function) {
3115: Collection rv = new Vector();
3116:
3117: try {
3118: // get the channel's site's groups
3119: Site site = SiteService.getSite(m_context);
3120: Collection groups = site.getGroups();
3121:
3122: // if the user has SECURE_ALL_GROUPS in the context (site), and the function for the calendar (calendar,site), select all site groups
3123: if ((SecurityService.isSuperUser())
3124: || (AuthzGroupService.isAllowed(SessionManager
3125: .getCurrentSessionUserId(),
3126: SECURE_ALL_GROUPS, SiteService
3127: .siteReference(m_context)) && unlockCheck(
3128: function, getReference()))) {
3129: return groups;
3130: }
3131:
3132: // otherwise, check the groups for function
3133:
3134: // get a list of the group refs, which are authzGroup ids
3135: Collection groupRefs = new Vector();
3136: for (Iterator i = groups.iterator(); i.hasNext();) {
3137: Group group = (Group) i.next();
3138: groupRefs.add(group.getReference());
3139: }
3140:
3141: // ask the authzGroup service to filter them down based on function
3142: groupRefs = AuthzGroupService.getAuthzGroupsIsAllowed(
3143: SessionManager.getCurrentSessionUserId(),
3144: function, groupRefs);
3145:
3146: // pick the Group objects from the site's groups to return, those that are in the groupRefs list
3147: for (Iterator i = groups.iterator(); i.hasNext();) {
3148: Group group = (Group) i.next();
3149: if (groupRefs.contains(group.getReference())) {
3150: rv.add(group);
3151: }
3152: }
3153: } catch (IdUnusedException e) {
3154: }
3155:
3156: return rv;
3157:
3158: } // getGroupsAllowFunction
3159:
3160: /******************************************************************************************************************************************************************************************************************************************************
3161: * SessionBindingListener implementation
3162: *****************************************************************************************************************************************************************************************************************************************************/
3163:
3164: public void valueBound(SessionBindingEvent event) {
3165: }
3166:
3167: public void valueUnbound(SessionBindingEvent event) {
3168: if (M_log.isDebugEnabled())
3169: M_log.debug("valueUnbound()");
3170:
3171: // catch the case where an edit was made but never resolved
3172: if (m_active) {
3173: cancelCalendar(this );
3174: }
3175:
3176: } // valueUnbound
3177:
3178: } // class BaseCalendar
3179:
3180: /**********************************************************************************************************************************************************************************************************************************************************
3181: * CalendarEvent implementation
3182: *********************************************************************************************************************************************************************************************************************************************************/
3183:
3184: public class BaseCalendarEventEdit implements CalendarEventEdit,
3185: SessionBindingListener {
3186: /** The calendar in which this event lives. */
3187: protected BaseCalendarEdit m_calendar = null;
3188:
3189: /** The effective time range. */
3190: protected TimeRange m_range = null;
3191:
3192: /**
3193: * The base time range: for non-recurring events, this matches m_range, but for recurring events, it is always the range of the initial event in the sequence (transient).
3194: */
3195: protected TimeRange m_baseRange = null;
3196:
3197: /** The recurrence rule (single rule). */
3198: protected RecurrenceRule m_singleRule = null;
3199:
3200: /** The exclusion recurrence rule. */
3201: protected RecurrenceRule m_exclusionRule = null;
3202:
3203: /** The properties. */
3204: protected ResourcePropertiesEdit m_properties = null;
3205:
3206: /** The event id. */
3207: protected String m_id = null;
3208:
3209: /** The attachments - dereferencer objects. */
3210: protected List m_attachments = null;
3211:
3212: /** The event code for this edit. */
3213: protected String m_event = null;
3214:
3215: /** Active flag. */
3216: protected boolean m_active = false;
3217:
3218: /** The Collection of groups (authorization group id strings). */
3219: protected Collection m_groups = new Vector();
3220:
3221: /** The message access. */
3222: protected EventAccess m_access = EventAccess.SITE;
3223:
3224: /**
3225: * Construct.
3226: *
3227: * @param calendar
3228: * The calendar in which this event lives.
3229: * @param id
3230: * The event id, unique within the calendar.
3231: */
3232: public BaseCalendarEventEdit(Calendar calendar, String id) {
3233: m_calendar = (BaseCalendarEdit) calendar;
3234: m_id = id;
3235:
3236: // setup for properties
3237: m_properties = new BaseResourcePropertiesEdit();
3238:
3239: // init the AttachmentContainer
3240: m_attachments = m_entityManager.newReferenceList();
3241:
3242: } // BaseCalendarEventEdit
3243:
3244: /**
3245: * Construct as a copy of another event.
3246: *
3247: * @param other
3248: * The other event to copy.
3249: */
3250: public BaseCalendarEventEdit(Calendar calendar,
3251: CalendarEvent other) {
3252: // store the calendar
3253: m_calendar = (BaseCalendarEdit) calendar;
3254:
3255: set(other);
3256:
3257: } // BaseCalendarEventEdit
3258:
3259: /**
3260: * Construct as a thin copy of another event, with this new time range, and no rules, as part of a recurring event sequence.
3261: *
3262: * @param other
3263: * The other event to copy.
3264: * @param ri
3265: * The RecurrenceInstance with the time range (and sequence number) to use.
3266: */
3267: public BaseCalendarEventEdit(CalendarEvent other,
3268: RecurrenceInstance ri) {
3269: // store the calendar
3270: m_calendar = ((BaseCalendarEventEdit) other).m_calendar;
3271:
3272: // encode the instance and the other's id into my id
3273: m_id = '!' + ri.getRange().toString() + '!'
3274: + ri.getSequence() + '!'
3275: + ((BaseCalendarEventEdit) other).m_id;
3276:
3277: // use the new range
3278: m_range = (TimeRange) ri.getRange().clone();
3279: m_baseRange = ((BaseCalendarEventEdit) other).m_range;
3280:
3281: // point at the properties
3282: m_properties = ((BaseCalendarEventEdit) other).m_properties;
3283:
3284: m_access = ((BaseCalendarEventEdit) other).m_access;
3285:
3286: // point at the groups
3287: m_groups = ((BaseCalendarEventEdit) other).m_groups;
3288:
3289: // point at the attachments
3290: m_attachments = ((BaseCalendarEventEdit) other).m_attachments;
3291:
3292: // point at the rules
3293: m_singleRule = ((BaseCalendarEventEdit) other).m_singleRule;
3294: m_exclusionRule = ((BaseCalendarEventEdit) other).m_exclusionRule;
3295:
3296: } // BaseCalendarEventEdit
3297:
3298: /**
3299: * Construct from an existing definition, in xml.
3300: *
3301: * @param calendar
3302: * The calendar in which this event lives.
3303: * @param el
3304: * The event in XML in a DOM element.
3305: */
3306: public BaseCalendarEventEdit(Calendar calendar, Element el) {
3307: m_calendar = (BaseCalendarEdit) calendar;
3308: m_properties = new BaseResourcePropertiesEdit();
3309: m_attachments = m_entityManager.newReferenceList();
3310:
3311: m_id = el.getAttribute("id");
3312: m_range = TimeService
3313: .newTimeRange(el.getAttribute("range"));
3314:
3315: m_access = CalendarEvent.EventAccess.SITE;
3316: String access_str = el.getAttribute("access").toString();
3317: if (access_str.equals(CalendarEvent.EventAccess.GROUPED
3318: .toString()))
3319: m_access = CalendarEvent.EventAccess.GROUPED;
3320:
3321: // the children (props / attachments / rules)
3322: NodeList children = el.getChildNodes();
3323: final int length = children.getLength();
3324: for (int i = 0; i < length; i++) {
3325: Node child = children.item(i);
3326: if (child.getNodeType() == Node.ELEMENT_NODE) {
3327: Element element = (Element) child;
3328:
3329: // look for an attachment
3330: if (element.getTagName().equals("attachment")) {
3331: m_attachments.add(m_entityManager
3332: .newReference(element
3333: .getAttribute("relative-url")));
3334: }
3335:
3336: // look for properties
3337: else if (element.getTagName().equals("properties")) {
3338: // re-create properties
3339: m_properties = new BaseResourcePropertiesEdit(
3340: element);
3341: }
3342:
3343: else if (element.getTagName().equals("group")) {
3344: m_groups
3345: .add(element.getAttribute("authzGroup"));
3346: }
3347:
3348: // else look for rules
3349: else if (element.getTagName().equals("rules")) {
3350: // children are "rule" elements
3351: NodeList ruleChildren = element.getChildNodes();
3352: final int ruleChildrenLength = ruleChildren
3353: .getLength();
3354: for (int iRuleChildren = 0; iRuleChildren < ruleChildrenLength; iRuleChildren++) {
3355: Node ruleChildNode = ruleChildren
3356: .item(iRuleChildren);
3357: if (ruleChildNode.getNodeType() == Node.ELEMENT_NODE) {
3358: Element ruleChildElement = (Element) ruleChildNode;
3359:
3360: // look for a rule
3361: if (ruleChildElement.getTagName()
3362: .equals("rule")) {
3363: // get the rule name - modern style encoding
3364: String ruleName = StringUtil
3365: .trimToNull(ruleChildElement
3366: .getAttribute("name"));
3367:
3368: // deal with old data
3369: if (ruleName == null) {
3370: try {
3371: // get the class - this is old CHEF 1.2.10 style encoding
3372: String ruleClassOld = ruleChildElement
3373: .getAttribute("class");
3374:
3375: // use the last class name minus the package
3376: ruleName = ruleClassOld
3377: .substring(ruleClassOld
3378: .lastIndexOf('.') + 1);
3379: } catch (Throwable t) {
3380: M_log
3381: .warn(": trouble loading rule: "
3382: + ruleName
3383: + " : " + t);
3384: }
3385: }
3386:
3387: // put my package on the class name
3388: String ruleClass = this .getClass()
3389: .getPackage().getName()
3390: + "." + ruleName;
3391:
3392: // construct
3393: try {
3394: m_singleRule = (RecurrenceRule) Class
3395: .forName(ruleClass)
3396: .newInstance();
3397: m_singleRule
3398: .set(ruleChildElement);
3399: } catch (Throwable t) {
3400: M_log
3401: .warn(": trouble loading rule: "
3402: + ruleClass
3403: + " : " + t);
3404: }
3405: }
3406:
3407: // look for an exclusion rule
3408: else if (ruleChildElement.getTagName()
3409: .equals("ex-rule")) {
3410: // get the rule name - modern style encoding
3411: String ruleName = StringUtil
3412: .trimToNull(ruleChildElement
3413: .getAttribute("name"));
3414:
3415: // deal with old data
3416: if (ruleName == null) {
3417: try {
3418: // get the class - this is old CHEF 1.2.10 style encoding
3419: String ruleClassOld = ruleChildElement
3420: .getAttribute("class");
3421:
3422: // use the last class name minus the package
3423: ruleName = ruleClassOld
3424: .substring(ruleClassOld
3425: .lastIndexOf('.') + 1);
3426: } catch (Throwable t) {
3427: M_log
3428: .warn(": trouble loading rule: "
3429: + ruleName
3430: + " : " + t);
3431: }
3432: }
3433:
3434: // put my package on the class name
3435: String ruleClass = this .getClass()
3436: .getPackage().getName()
3437: + "." + ruleName;
3438:
3439: // construct
3440: try {
3441: m_exclusionRule = (RecurrenceRule) Class
3442: .forName(ruleClass)
3443: .newInstance();
3444: m_exclusionRule
3445: .set(ruleChildElement);
3446: } catch (Throwable t) {
3447: M_log
3448: .warn(": trouble loading rule: "
3449: + ruleClass
3450: + " : " + t);
3451: }
3452: }
3453: }
3454: }
3455: }
3456: }
3457: }
3458:
3459: } // BaseCalendarEventEdit
3460:
3461: /**
3462: * Take all values from this object.
3463: *
3464: * @param other
3465: * The other object to take values from.
3466: */
3467: protected void set(CalendarEvent other) {
3468: // copy the id
3469: m_id = other.getId();
3470:
3471: // copy the range
3472: m_range = (TimeRange) other.getRange().clone();
3473:
3474: // copy the properties
3475: m_properties = new BaseResourcePropertiesEdit();
3476: m_properties.addAll(other.getProperties());
3477:
3478: m_access = other.getAccess();
3479: m_groups = new Vector();
3480: m_groups.addAll(other.getGroups());
3481:
3482: // copy the attachments
3483: m_attachments = m_entityManager.newReferenceList();
3484: replaceAttachments(other.getAttachments());
3485:
3486: // copy the rules
3487: // %%% deep enough? -ggolden
3488: m_singleRule = ((BaseCalendarEventEdit) other).m_singleRule;
3489: m_exclusionRule = ((BaseCalendarEventEdit) other).m_exclusionRule;
3490:
3491: } // set
3492:
3493: /**
3494: * Take some values from this object (not id, not rules).
3495: *
3496: * @param other
3497: * The other object to take values from.
3498: */
3499: protected void setPartial(CalendarEvent other) {
3500: // copy the range
3501: m_range = (TimeRange) other.getRange().clone();
3502:
3503: // copy the properties
3504: m_properties = new BaseResourcePropertiesEdit();
3505: m_properties.addAll(other.getProperties());
3506:
3507: m_access = other.getAccess();
3508: m_groups = new Vector();
3509: m_groups.addAll(other.getGroups());
3510:
3511: // copy the attachments
3512: m_attachments = m_entityManager.newReferenceList();
3513: replaceAttachments(other.getAttachments());
3514:
3515: } // setPartial
3516:
3517: /**
3518: * Clean up.
3519: */
3520: protected void finalize() {
3521: // catch the case where an edit was made but never resolved
3522: if (m_active) {
3523: m_calendar.cancelEvent(this );
3524: }
3525:
3526: m_calendar = null;
3527:
3528: } // finalize
3529:
3530: /**
3531: * Access the time range
3532: *
3533: * @return The event time range
3534: */
3535: public TimeRange getRange() {
3536: // range might be null in the creation process, before the fields are set in an edit, but
3537: // after the storage has registered the event and it's id.
3538: if (m_range == null) {
3539: return TimeService.newTimeRange(TimeService.newTime(0));
3540: }
3541:
3542: // return (TimeRange) m_range.clone();
3543: return m_range;
3544: } // getRange
3545:
3546: /**
3547: * Replace the time range
3548: *
3549: * @param The
3550: * new event time range
3551: */
3552: public void setRange(TimeRange range) {
3553: m_range = (TimeRange) range.clone();
3554:
3555: } // setRange
3556:
3557: /**
3558: * Access the display name property (cover for PROP_DISPLAY_NAME).
3559: *
3560: * @return The event's display name property.
3561: */
3562: public String getDisplayName() {
3563: return m_properties
3564: .getPropertyFormatted(ResourceProperties.PROP_DISPLAY_NAME);
3565:
3566: } // getDisplayName
3567:
3568: /**
3569: * Set the display name property (cover for PROP_DISPLAY_NAME).
3570: *
3571: * @param name
3572: * The event's display name property.
3573: */
3574: public void setDisplayName(String name) {
3575: m_properties.addProperty(
3576: ResourceProperties.PROP_DISPLAY_NAME, name);
3577:
3578: } // setDisplayName
3579:
3580: /**
3581: * Access the description property as plain text.
3582: *
3583: * @return The event's description property.
3584: */
3585: public String getDescription() {
3586: return FormattedText
3587: .convertFormattedTextToPlaintext(getDescriptionFormatted());
3588: }
3589:
3590: /**
3591: * Access the description property as formatted text.
3592: *
3593: * @return The event's description property.
3594: */
3595: public String getDescriptionFormatted() {
3596: // %%% JANDERSE the calendar event description can now be formatted text
3597: // first try to use the formatted text description; if that isn't found, use the plaintext description
3598: String desc = m_properties
3599: .getPropertyFormatted(ResourceProperties.PROP_DESCRIPTION
3600: + "-html");
3601: if (desc != null && desc.length() > 0)
3602: return desc;
3603: desc = m_properties
3604: .getPropertyFormatted(ResourceProperties.PROP_DESCRIPTION
3605: + "-formatted");
3606: desc = FormattedText.convertOldFormattedText(desc);
3607: if (desc != null && desc.length() > 0)
3608: return desc;
3609: desc = FormattedText
3610: .convertPlaintextToFormattedText(m_properties
3611: .getPropertyFormatted(ResourceProperties.PROP_DESCRIPTION));
3612: return desc;
3613: } // getDescriptionFormatted()
3614:
3615: /**
3616: * Set the description property as plain text.
3617: *
3618: * @param description
3619: * The event's description property.
3620: */
3621: public void setDescription(String description) {
3622: setDescriptionFormatted(FormattedText
3623: .convertPlaintextToFormattedText(description));
3624: }
3625:
3626: /**
3627: * Set the description property as formatted text.
3628: *
3629: * @param description
3630: * The event's description property.
3631: */
3632: public void setDescriptionFormatted(String description) {
3633: // %%% JANDERSE the calendar event description can now be formatted text
3634: // save both a formatted and a plaintext version of the description
3635: m_properties.addProperty(
3636: ResourceProperties.PROP_DESCRIPTION + "-html",
3637: description);
3638: m_properties
3639: .addProperty(
3640: ResourceProperties.PROP_DESCRIPTION,
3641: FormattedText
3642: .convertFormattedTextToPlaintext(description));
3643: } // setDescriptionFormatted()
3644:
3645: /**
3646: * Access the type (cover for PROP_CALENDAR_TYPE).
3647: *
3648: * @return The event's type property.
3649: */
3650: public String getType() {
3651: return m_properties
3652: .getPropertyFormatted(ResourceProperties.PROP_CALENDAR_TYPE);
3653:
3654: } // getType
3655:
3656: /**
3657: * Set the type (cover for PROP_CALENDAR_TYPE).
3658: *
3659: * @param type
3660: * The event's type property.
3661: */
3662: public void setType(String type) {
3663: m_properties.addProperty(
3664: ResourceProperties.PROP_CALENDAR_TYPE, type);
3665:
3666: } // setType
3667:
3668: /**
3669: * Access the location (cover for PROP_CALENDAR_LOCATION).
3670: *
3671: * @return The event's location property.
3672: */
3673: public String getLocation() {
3674: return m_properties
3675: .getPropertyFormatted(ResourceProperties.PROP_CALENDAR_LOCATION);
3676:
3677: } // getLocation
3678:
3679: /**
3680: * Gets the recurrence rule, if any.
3681: *
3682: * @return The recurrence rule, or null if none.
3683: */
3684: public RecurrenceRule getRecurrenceRule() {
3685: return m_singleRule;
3686:
3687: } // getRecurrenceRule
3688:
3689: /**
3690: * Gets the exclusion recurrence rule, if any.
3691: *
3692: * @return The exclusionrecurrence rule, or null if none.
3693: */
3694: protected RecurrenceRule getExclusionRule() {
3695: if (m_exclusionRule == null)
3696: m_exclusionRule = new ExclusionSeqRecurrenceRule();
3697:
3698: return m_exclusionRule;
3699:
3700: } // getExclusionRule
3701:
3702: /*
3703: * public RecurrenceRule getExclusionRuleII() { if (m_exclusionRule == null) m_exclusionRule = new ExclusionSeqRecurrenceRule(); return m_exclusionRule; } // getExclusionRule
3704: */
3705:
3706: /**
3707: * Return a list of all resolved events generated from this event plus it's recurrence rules that fall within the time range, including this event, possibly empty.
3708: *
3709: * @param range
3710: * The time range bounds for the events returned.
3711: * @return a List (CalendarEvent) of all events and recurrences within the time range, including this, possibly empty.
3712: */
3713: protected List resolve(TimeRange range) {
3714: List rv = new Vector();
3715:
3716: // for no rules, use the event if it's in range
3717: if (m_singleRule == null) {
3718: // the actual event
3719: if (range.overlaps(getRange())) {
3720: rv.add(this );
3721: }
3722: }
3723:
3724: // for rules...
3725: else {
3726: List instances = m_singleRule.generateInstances(this
3727: .getRange(), range, TimeService
3728: .getLocalTimeZone());
3729:
3730: // remove any excluded
3731: getExclusionRule().excludeInstances(instances);
3732:
3733: for (Iterator iRanges = instances.iterator(); iRanges
3734: .hasNext();) {
3735: RecurrenceInstance ri = (RecurrenceInstance) iRanges
3736: .next();
3737:
3738: // generate an event object that is exactly like me but with this range and no rules
3739: CalendarEvent clone = new BaseCalendarEventEdit(
3740: this , ri);
3741:
3742: rv.add(clone);
3743: }
3744: }
3745:
3746: return rv;
3747:
3748: } // resolve
3749:
3750: /**
3751: * Get the value of an "extra" event field.
3752: *
3753: * @param name
3754: * The name of the field.
3755: * @return the value of the "extra" event field.
3756: */
3757: public String getField(String name) {
3758: // names are prefixed to form a namespace
3759: name = ResourceProperties.PROP_CALENDAR_EVENT_FIELDS + "."
3760: + name;
3761:
3762: return m_properties.getPropertyFormatted(name);
3763:
3764: } // getField
3765:
3766: /**
3767: * Set the value of an "extra" event field.
3768: *
3769: * @param name
3770: * The "extra" field name
3771: * @param value
3772: * The value to set, or null to remove the field.
3773: */
3774: public void setField(String name, String value) {
3775: // names are prefixed to form a namespace
3776: name = ResourceProperties.PROP_CALENDAR_EVENT_FIELDS + "."
3777: + name;
3778:
3779: if (value == null) {
3780: m_properties.removeProperty(name);
3781: } else {
3782: m_properties.addProperty(name, value);
3783: }
3784:
3785: } // setField
3786:
3787: /**
3788: * Set the location (cover for PROP_CALENDAR_LOCATION).
3789: *
3790: * @param location
3791: * The event's location property.
3792: */
3793: public void setLocation(String location) {
3794: m_properties
3795: .addProperty(
3796: ResourceProperties.PROP_CALENDAR_LOCATION,
3797: location);
3798:
3799: } // setLocation
3800:
3801: /**
3802: * Gets the event creator (userid), if any (cover for PROP_CREATOR).
3803: * @return The event's creator property.
3804: */
3805: public String getCreator() {
3806: return m_properties
3807: .getProperty(ResourceProperties.PROP_CREATOR);
3808:
3809: } // getCreator
3810:
3811: /**
3812: * Returns true if current user is thhe event's owner/creator
3813: * @return boolean true or false
3814: */
3815: public boolean isUserOwner() {
3816: String currentUser = SessionManager
3817: .getCurrentSessionUserId();
3818: String eventOwner = this .getCreator();
3819:
3820: // for backward compatibility, treat unowned event as if it owned by this user
3821: return (eventOwner == null || eventOwner.equals("") || currentUser
3822: .equals(eventOwner));
3823: }
3824:
3825: /**
3826: * Set the event creator (cover for PROP_CREATOR) to current user
3827: */
3828: public void setCreator() {
3829: String currentUser = SessionManager
3830: .getCurrentSessionUserId();
3831: String now = TimeService.newTime().toString();
3832: m_properties.addProperty(ResourceProperties.PROP_CREATOR,
3833: currentUser);
3834: m_properties.addProperty(
3835: ResourceProperties.PROP_CREATION_DATE, now);
3836:
3837: } // setCreator
3838:
3839: /**
3840: * Gets the event modifier (userid), if any (cover for PROP_MODIFIED_BY).
3841: * @return The event's modified-by property.
3842: */
3843: public String getModifiedBy() {
3844: return m_properties
3845: .getPropertyFormatted(ResourceProperties.PROP_MODIFIED_BY);
3846:
3847: } // getModifiedBy
3848:
3849: /**
3850: * Set the event modifier (cover for PROP_MODIFIED_BY) to current user
3851: */
3852: public void setModifiedBy() {
3853: String currentUser = SessionManager
3854: .getCurrentSessionUserId();
3855: String now = TimeService.newTime().toString();
3856: m_properties.addProperty(
3857: ResourceProperties.PROP_MODIFIED_BY, currentUser);
3858: m_properties.addProperty(
3859: ResourceProperties.PROP_MODIFIED_DATE, now);
3860:
3861: } // setModifiedBy
3862:
3863: /**
3864: * Sets the recurrence rule.
3865: *
3866: * @param rule
3867: * The recurrence rule, or null to clear out the rule.
3868: */
3869: public void setRecurrenceRule(RecurrenceRule rule) {
3870: m_singleRule = rule;
3871:
3872: } // setRecurrenceRule
3873:
3874: /**
3875: * Sets the exclusion recurrence rule.
3876: *
3877: * @param rule
3878: * The recurrence rule, or null to clear out the rule.
3879: */
3880: protected void setExclusionRule(RecurrenceRule rule) {
3881: m_exclusionRule = rule;
3882:
3883: } // setExclusionRule
3884:
3885: /**
3886: * Access the id of the resource.
3887: *
3888: * @return The id.
3889: */
3890: public String getId() {
3891: return m_id;
3892:
3893: } // getId
3894:
3895: /**
3896: * Access the URL which can be used to access the resource.
3897: *
3898: * @return The URL which can be used to access the resource.
3899: */
3900: public String getUrl() {
3901: return m_calendar.getUrl() + getId();
3902:
3903: } // getUrl
3904:
3905: /**
3906: * Access the internal reference which can be used to access the resource from within the system.
3907: *
3908: * @return The the internal reference which can be used to access the resource from within the system.
3909: */
3910: public String getReference() {
3911: return eventReference(m_calendar.getContext(), m_calendar
3912: .getId(), getId());
3913:
3914: } // getReference
3915:
3916: /**
3917: * @inheritDoc
3918: */
3919: public String getReference(String rootProperty) {
3920: return getReference();
3921: }
3922:
3923: /**
3924: * @inheritDoc
3925: */
3926: public String getUrl(String rootProperty) {
3927: return getUrl();
3928: }
3929:
3930: /**
3931: * Access the event's properties.
3932: *
3933: * @return The event's properties.
3934: */
3935: public ResourceProperties getProperties() {
3936: return m_properties;
3937:
3938: } // getProperties
3939:
3940: /**
3941: * Notify the event that it has changed.
3942: *
3943: * @param event
3944: * The event that caused the update.
3945: */
3946: public void notify(Event event) {
3947: m_calendar.notify(event);
3948:
3949: } // notify
3950:
3951: /**
3952: * Compare one event to another, based on range.
3953: *
3954: * @param o
3955: * The object to be compared.
3956: * @return A negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object.
3957: */
3958: public int compareTo(Object o) {
3959: if (!(o instanceof CalendarEvent))
3960: throw new ClassCastException();
3961: Time mine = getRange().firstTime();
3962: Time other = ((CalendarEvent) o).getRange().firstTime();
3963:
3964: if (mine.before(other))
3965: return -1;
3966: if (mine.after(other))
3967: return +1;
3968: return 0; // %%% perhaps check the rest of the range if the starts are the same?
3969: }
3970:
3971: /**
3972: * Serialize the resource into XML, adding an element to the doc under the top of the stack element.
3973: *
3974: * @param doc
3975: * The DOM doc to contain the XML (or null for a string return).
3976: * @param stack
3977: * The DOM elements, the top of which is the containing element of the new "resource" element.
3978: * @return The newly added element.
3979: */
3980: public Element toXml(Document doc, Stack stack) {
3981: Element event = doc.createElement("event");
3982:
3983: if (stack.isEmpty()) {
3984: doc.appendChild(event);
3985: } else {
3986: ((Element) stack.peek()).appendChild(event);
3987: }
3988:
3989: stack.push(event);
3990:
3991: event.setAttribute("id", getId());
3992: event.setAttribute("range", getRange().toString());
3993: // add access
3994: event.setAttribute("access", m_access.toString());
3995:
3996: // add groups
3997: if ((m_groups != null) && (m_groups.size() > 0)) {
3998: for (Iterator i = m_groups.iterator(); i.hasNext();) {
3999: String group = (String) i.next();
4000: Element sect = doc.createElement("group");
4001: event.appendChild(sect);
4002: sect.setAttribute("authzGroup", group);
4003: }
4004: }
4005:
4006: // properties
4007: m_properties.toXml(doc, stack);
4008:
4009: if ((m_attachments != null) && (m_attachments.size() > 0)) {
4010: for (int i = 0; i < m_attachments.size(); i++) {
4011: Reference attch = (Reference) m_attachments.get(i);
4012: Element attachment = doc
4013: .createElement("attachment");
4014: event.appendChild(attachment);
4015: attachment.setAttribute("relative-url", attch
4016: .getReference());
4017: }
4018: }
4019:
4020: // rules
4021: if (m_singleRule != null) {
4022: Element rules = doc.createElement("rules");
4023: event.appendChild(rules);
4024: stack.push(rules);
4025:
4026: // the rule
4027: m_singleRule.toXml(doc, stack);
4028:
4029: // the exculsions
4030: if (m_exclusionRule != null) {
4031: m_exclusionRule.toXml(doc, stack);
4032: }
4033:
4034: stack.pop();
4035: }
4036:
4037: stack.pop();
4038:
4039: return event;
4040:
4041: } // toXml
4042:
4043: /**
4044: * Access the event code for this edit.
4045: *
4046: * @return The event code for this edit.
4047: */
4048: protected String getEvent() {
4049: return m_event;
4050: }
4051:
4052: /**
4053: * Set the event code for this edit.
4054: *
4055: * @param event
4056: * The event code for this edit.
4057: */
4058: protected void setEvent(String event) {
4059: m_event = event;
4060: }
4061:
4062: /**
4063: * Access the resource's properties for modification
4064: *
4065: * @return The resource's properties.
4066: */
4067: public ResourcePropertiesEdit getPropertiesEdit() {
4068: return m_properties;
4069:
4070: } // getPropertiesEdit
4071:
4072: /**
4073: * Enable editing.
4074: */
4075: protected void activate() {
4076: m_active = true;
4077:
4078: } // activate
4079:
4080: /**
4081: * Check to see if the edit is still active, or has already been closed.
4082: *
4083: * @return true if the edit is active, false if it's been closed.
4084: */
4085: public boolean isActiveEdit() {
4086: return m_active;
4087:
4088: } // isActiveEdit
4089:
4090: /**
4091: * Close the edit object - it cannot be used after this.
4092: */
4093: protected void closeEdit() {
4094: m_active = false;
4095:
4096: } // closeEdit
4097:
4098: /******************************************************************************************************************************************************************************************************************************************************
4099: * AttachmentContainer implementation
4100: *****************************************************************************************************************************************************************************************************************************************************/
4101:
4102: /**
4103: * Access the attachments of the event.
4104: *
4105: * @return An copy of the set of attachments (a ReferenceVector containing Reference objects) (may be empty).
4106: */
4107: public List getAttachments() {
4108: return m_entityManager.newReferenceList(m_attachments);
4109:
4110: } // getAttachments
4111:
4112: /**
4113: * Add an attachment.
4114: *
4115: * @param ref
4116: * The attachment Reference.
4117: */
4118: public void addAttachment(Reference ref) {
4119: m_attachments.add(ref);
4120:
4121: } // addAttachment
4122:
4123: /**
4124: * Remove an attachment.
4125: *
4126: * @param ref
4127: * The attachment Reference to remove (the one removed will equal this, they need not be ==).
4128: */
4129: public void removeAttachment(Reference ref) {
4130: m_attachments.remove(ref);
4131:
4132: } // removeAttachment
4133:
4134: /**
4135: * Replace the attachment set.
4136: *
4137: * @param attachments
4138: * A vector of Reference objects that will become the new set of attachments.
4139: */
4140: public void replaceAttachments(List attachments) {
4141: m_attachments.clear();
4142:
4143: if (attachments != null) {
4144: Iterator it = attachments.iterator();
4145: while (it.hasNext()) {
4146: m_attachments.add(it.next());
4147: }
4148: }
4149:
4150: } // replaceAttachments
4151:
4152: /**
4153: * Clear all attachments.
4154: */
4155: public void clearAttachments() {
4156: m_attachments.clear();
4157:
4158: } // clearAttachments
4159:
4160: /**
4161: * {@inheritDoc}
4162: */
4163: public EventAccess getAccess() {
4164: return m_access;
4165: }
4166:
4167: /**
4168: * {@inheritDoc}
4169: */
4170: public Collection getGroups() {
4171: return new Vector(m_groups);
4172: }
4173:
4174: /**
4175: * {@inheritDoc}
4176: */
4177: public Collection getGroupObjects() {
4178: Vector rv = new Vector();
4179: if (m_groups != null) {
4180: for (Iterator i = m_groups.iterator(); i.hasNext();) {
4181: String groupId = (String) i.next();
4182: Group group = SiteService.findGroup(groupId);
4183: if (group != null) {
4184: rv.add(group);
4185: }
4186: }
4187: }
4188:
4189: return rv;
4190: }
4191:
4192: /**
4193: * @inheritDoc
4194: */
4195: public void setGroupAccess(Collection groups, boolean own)
4196: throws PermissionException {
4197: // convenience (and what else are we going to do?)
4198: if ((groups == null) || (groups.size() == 0)) {
4199: clearGroupAccess();
4200: return;
4201: }
4202:
4203: // is there any change? If we are already grouped, and the group list is the same, ignore the call
4204: if ((m_access == EventAccess.GROUPED)
4205: && (EntityCollections.isEqualEntityRefsToEntities(
4206: m_groups, groups)))
4207: return;
4208:
4209: // isolate any groups that would be removed or added
4210: Collection addedGroups = new Vector();
4211: Collection removedGroups = new Vector();
4212: EntityCollections
4213: .computeAddedRemovedEntityRefsFromNewEntitiesOldRefs(
4214: addedGroups, removedGroups, groups,
4215: m_groups);
4216:
4217: // verify that the user has permission to remove
4218: if (removedGroups.size() > 0) {
4219: // the Group objects the user has remove permission
4220: Collection allowedGroups = m_calendar
4221: .getGroupsAllowRemoveEvent(own);
4222:
4223: for (Iterator i = removedGroups.iterator(); i.hasNext();) {
4224: String ref = (String) i.next();
4225:
4226: // is ref a group the user can remove from?
4227: if (!EntityCollections
4228: .entityCollectionContainsRefString(
4229: allowedGroups, ref)) {
4230: throw new PermissionException(SessionManager
4231: .getCurrentSessionUserId(),
4232: "access:group:remove", ref);
4233: }
4234: }
4235: }
4236:
4237: // verify that the user has permission to add in those contexts
4238: if (addedGroups.size() > 0) {
4239: // the Group objects the user has add permission
4240: Collection allowedGroups = m_calendar
4241: .getGroupsAllowAddEvent();
4242:
4243: for (Iterator i = addedGroups.iterator(); i.hasNext();) {
4244: String ref = (String) i.next();
4245:
4246: // is ref a group the user can remove from?
4247: if (!EntityCollections
4248: .entityCollectionContainsRefString(
4249: allowedGroups, ref)) {
4250: throw new PermissionException(SessionManager
4251: .getCurrentSessionUserId(),
4252: "access:group:add", ref);
4253: }
4254: }
4255: }
4256:
4257: // we are clear to perform this
4258: m_access = EventAccess.GROUPED;
4259: EntityCollections.setEntityRefsFromEntities(m_groups,
4260: groups);
4261: }
4262:
4263: /**
4264: * @inheritDoc
4265: */
4266: public void clearGroupAccess() throws PermissionException {
4267: // is there any change? If we are already channel, ignore the call
4268: if (m_access == EventAccess.SITE)
4269: return;
4270:
4271: // verify that the user has permission to add in the calendar context
4272: boolean allowed = m_calendar.allowAddCalendarEvent();
4273: if (!allowed) {
4274: throw new PermissionException(SessionManager
4275: .getCurrentSessionUserId(), "access:channel",
4276: getReference());
4277: }
4278:
4279: // we are clear to perform this
4280: m_access = EventAccess.SITE;
4281: m_groups.clear();
4282: }
4283:
4284: /******************************************************************************************************************************************************************************************************************************************************
4285: * SessionBindingListener implementation
4286: *****************************************************************************************************************************************************************************************************************************************************/
4287:
4288: public void valueBound(SessionBindingEvent event) {
4289: }
4290:
4291: public void valueUnbound(SessionBindingEvent event) {
4292: if (M_log.isDebugEnabled())
4293: M_log.debug("valueUnbound()");
4294:
4295: // catch the case where an edit was made but never resolved
4296: if (m_active) {
4297: m_calendar.cancelEvent(this );
4298: }
4299:
4300: } // valueUnbound
4301:
4302: /**
4303: * Gets the containing calendar's reference.
4304: *
4305: * @return The containing calendar reference.
4306: */
4307: public String getCalendarReference() {
4308: return m_calendar.getReference();
4309:
4310: } // getCalendarReference
4311:
4312: public String getGroupRangeForDisplay(Calendar cal) {
4313: // TODO: check this - if used for the UI list, it needs the user's groups and the event's groups... -ggolden
4314: if (m_access.equals(CalendarEvent.EventAccess.SITE)) {
4315: return "";
4316: } else {
4317: int count = 0;
4318: String allGroupString = "";
4319: try {
4320: Site site = SiteService.getSite(cal.getContext());
4321: for (Iterator i = m_groups.iterator(); i.hasNext();) {
4322: Group aGroup = site.getGroup((String) i.next());
4323: if (aGroup != null) {
4324: count++;
4325: if (count > 1) {
4326: allGroupString = allGroupString.concat(
4327: ", ").concat(aGroup.getTitle());
4328: } else {
4329: allGroupString = aGroup.getTitle();
4330: }
4331: }
4332: }
4333: } catch (IdUnusedException e) {
4334: // No site available.
4335: }
4336: return allGroupString;
4337: }
4338: }
4339:
4340: } // BaseCalendarEvent
4341:
4342: /**********************************************************************************************************************************************************************************************************************************************************
4343: * Storage implementation
4344: *********************************************************************************************************************************************************************************************************************************************************/
4345:
4346: protected interface Storage {
4347: /**
4348: * Open and read.
4349: */
4350: public void open();
4351:
4352: /**
4353: * Write and Close.
4354: */
4355: public void close();
4356:
4357: /**
4358: * Return the identified calendar, or null if not found.
4359: */
4360: public Calendar getCalendar(String ref);
4361:
4362: /**
4363: * Return true if the identified calendar exists.
4364: */
4365: public boolean checkCalendar(String ref);
4366:
4367: /**
4368: * Get a list of all calendars
4369: */
4370: public List getCalendars();
4371:
4372: /**
4373: * Keep a new calendar.
4374: */
4375: public CalendarEdit putCalendar(String ref);
4376:
4377: /**
4378: * Get a calendar locked for update
4379: */
4380: public CalendarEdit editCalendar(String ref);
4381:
4382: /**
4383: * Commit a calendar edit.
4384: */
4385: public void commitCalendar(CalendarEdit edit);
4386:
4387: /**
4388: * Cancel a calendar edit.
4389: */
4390: public void cancelCalendar(CalendarEdit edit);
4391:
4392: /**
4393: * Forget about a calendar.
4394: */
4395: public void removeCalendar(CalendarEdit calendar);
4396:
4397: /**
4398: * Get a event from a calendar.
4399: */
4400: public CalendarEvent getEvent(Calendar calendar, String eventId);
4401:
4402: /**
4403: * Get a event from a calendar locked for update
4404: */
4405: public CalendarEventEdit editEvent(Calendar calendar,
4406: String eventId);
4407:
4408: /**
4409: * Commit an edit.
4410: */
4411: public void commitEvent(Calendar calendar,
4412: CalendarEventEdit edit);
4413:
4414: /**
4415: * Cancel an edit.
4416: */
4417: public void cancelEvent(Calendar calendar,
4418: CalendarEventEdit edit);
4419:
4420: /**
4421: * Does this events exist in a calendar?
4422: */
4423: public boolean checkEvent(Calendar calendar, String eventId);
4424:
4425: /**
4426: * Get the events from a calendar (within this time range, or all if null)
4427: */
4428: public List getEvents(Calendar calendar);
4429:
4430: /**
4431: * Make and lock a new event.
4432: */
4433: public CalendarEventEdit putEvent(Calendar calendar, String id);
4434:
4435: /**
4436: * Forget about a event.
4437: */
4438: public void removeEvent(Calendar calendar,
4439: CalendarEventEdit edit);
4440:
4441: } // Storage
4442:
4443: /**********************************************************************************************************************************************************************************************************************************************************
4444: * CacheRefresher implementation (no container)
4445: *********************************************************************************************************************************************************************************************************************************************************/
4446:
4447: /**
4448: * Get a new value for this key whose value has already expired in the cache.
4449: *
4450: * @param key
4451: * The key whose value has expired and needs to be refreshed.
4452: * @param oldValue
4453: * The old exipred value of the key.
4454: * @param event
4455: * The event which triggered this refresh.
4456: * @return a new value for use in the cache for this key; if null, the entry will be removed.
4457: */
4458: public Object refresh(Object key, Object oldValue, Event event) {
4459: Object rv = null;
4460:
4461: // key is a reference
4462: Reference ref = m_entityManager.newReference((String) key);
4463:
4464: // get from storage only (not cache!)
4465:
4466: // for events
4467: if (REF_TYPE_EVENT.equals(ref.getSubType())) {
4468: if (M_log.isDebugEnabled())
4469: M_log.debug("refresh(): key " + key + " calendar id : "
4470: + ref.getContext() + "/" + ref.getContainer()
4471: + " event id : " + ref.getId());
4472:
4473: // get calendar (Note: from the cache is ok)
4474: Calendar calendar = findCalendar(calendarReference(ref
4475: .getContext(), ref.getContainer()));
4476:
4477: // get the CalendarEvent (Note: not from cache! but only from storage)
4478: if (calendar != null) {
4479: rv = m_storage.getEvent(calendar, ref.getId());
4480: }
4481: }
4482:
4483: // for calendar
4484: else {
4485: if (M_log.isDebugEnabled())
4486: M_log.debug("refresh(): key " + key + " calendar id : "
4487: + ref.getReference());
4488:
4489: // return the calendar (Note: not from cache! but only from storage)
4490: rv = m_storage.getCalendar(ref.getReference());
4491: }
4492:
4493: return rv;
4494:
4495: } // refresh
4496:
4497: /**********************************************************************************************************************************************************************************************************************************************************
4498: * StorageUser implementation
4499: *********************************************************************************************************************************************************************************************************************************************************/
4500:
4501: /**
4502: * Construct a new continer given just an id.
4503: *
4504: * @param ref
4505: * The reference for the new object.
4506: * @return The new containe Resource.
4507: */
4508: public Entity newContainer(String ref) {
4509: return new BaseCalendarEdit(ref);
4510: }
4511:
4512: /**
4513: * Construct a new container resource, from an XML element.
4514: *
4515: * @param element
4516: * The XML.
4517: * @return The new container resource.
4518: */
4519: public Entity newContainer(Element element) {
4520: return new BaseCalendarEdit(element);
4521: }
4522:
4523: /**
4524: * Construct a new container resource, as a copy of another
4525: *
4526: * @param other
4527: * The other contianer to copy.
4528: * @return The new container resource.
4529: */
4530: public Entity newContainer(Entity other) {
4531: return new BaseCalendarEdit((Calendar) other);
4532: }
4533:
4534: /**
4535: * Construct a new rsource given just an id.
4536: *
4537: * @param container
4538: * The Resource that is the container for the new resource (may be null).
4539: * @param id
4540: * The id for the new object.
4541: * @param others
4542: * (options) array of objects to load into the Resource's fields.
4543: * @return The new resource.
4544: */
4545: public Entity newResource(Entity container, String id,
4546: Object[] others) {
4547: return new BaseCalendarEventEdit((Calendar) container, id);
4548: }
4549:
4550: /**
4551: * Construct a new resource, from an XML element.
4552: *
4553: * @param container
4554: * The Resource that is the container for the new resource (may be null).
4555: * @param element
4556: * The XML.
4557: * @return The new resource from the XML.
4558: */
4559: public Entity newResource(Entity container, Element element) {
4560: return new BaseCalendarEventEdit((Calendar) container, element);
4561: }
4562:
4563: /**
4564: * Construct a new resource from another resource of the same type.
4565: *
4566: * @param container
4567: * The Resource that is the container for the new resource (may be null).
4568: * @param other
4569: * The other resource.
4570: * @return The new resource as a copy of the other.
4571: */
4572: public Entity newResource(Entity container, Entity other) {
4573: return new BaseCalendarEventEdit((Calendar) container,
4574: (CalendarEvent) other);
4575: }
4576:
4577: /**
4578: * Construct a new continer given just an id.
4579: *
4580: * @param ref
4581: * The reference for the new object.
4582: * @return The new containe Resource.
4583: */
4584: public Edit newContainerEdit(String ref) {
4585: BaseCalendarEdit rv = new BaseCalendarEdit(ref);
4586: rv.activate();
4587: return rv;
4588: }
4589:
4590: /**
4591: * Construct a new container resource, from an XML element.
4592: *
4593: * @param element
4594: * The XML.
4595: * @return The new container resource.
4596: */
4597: public Edit newContainerEdit(Element element) {
4598: BaseCalendarEdit rv = new BaseCalendarEdit(element);
4599: rv.activate();
4600: return rv;
4601: }
4602:
4603: /**
4604: * Construct a new container resource, as a copy of another
4605: *
4606: * @param other
4607: * The other contianer to copy.
4608: * @return The new container resource.
4609: */
4610: public Edit newContainerEdit(Entity other) {
4611: BaseCalendarEdit rv = new BaseCalendarEdit((Calendar) other);
4612: rv.activate();
4613: return rv;
4614: }
4615:
4616: /**
4617: * Construct a new rsource given just an id.
4618: *
4619: * @param container
4620: * The Resource that is the container for the new resource (may be null).
4621: * @param id
4622: * The id for the new object.
4623: * @param others
4624: * (options) array of objects to load into the Resource's fields.
4625: * @return The new resource.
4626: */
4627: public Edit newResourceEdit(Entity container, String id,
4628: Object[] others) {
4629: BaseCalendarEventEdit rv = new BaseCalendarEventEdit(
4630: (Calendar) container, id);
4631: rv.activate();
4632: return rv;
4633: }
4634:
4635: /**
4636: * Construct a new resource, from an XML element.
4637: *
4638: * @param container
4639: * The Resource that is the container for the new resource (may be null).
4640: * @param element
4641: * The XML.
4642: * @return The new resource from the XML.
4643: */
4644: public Edit newResourceEdit(Entity container, Element element) {
4645: BaseCalendarEventEdit rv = new BaseCalendarEventEdit(
4646: (Calendar) container, element);
4647: rv.activate();
4648: return rv;
4649: }
4650:
4651: /**
4652: * Construct a new resource from another resource of the same type.
4653: *
4654: * @param container
4655: * The Resource that is the container for the new resource (may be null).
4656: * @param other
4657: * The other resource.
4658: * @return The new resource as a copy of the other.
4659: */
4660: public Edit newResourceEdit(Entity container, Entity other) {
4661: BaseCalendarEventEdit rv = new BaseCalendarEventEdit(
4662: (Calendar) container, (CalendarEvent) other);
4663: rv.activate();
4664: return rv;
4665: }
4666:
4667: /**
4668: * Collect the fields that need to be stored outside the XML (for the resource).
4669: *
4670: * @return An array of field values to store in the record outside the XML (for the resource).
4671: */
4672: public Object[] storageFields(Entity r) {
4673: Object[] rv = new Object[2];
4674: TimeRange range = ((CalendarEvent) r).getRange();
4675: rv[0] = range.firstTime(); // %%% fudge?
4676: rv[1] = range.lastTime(); // %%% fudge?
4677:
4678: return rv;
4679: }
4680:
4681: /**
4682: * Check if this resource is in draft mode.
4683: *
4684: * @param r
4685: * The resource.
4686: * @return true if the resource is in draft mode, false if not.
4687: */
4688: public boolean isDraft(Entity r) {
4689: return false;
4690: }
4691:
4692: /**
4693: * Access the resource owner user id.
4694: *
4695: * @param r
4696: * The resource.
4697: * @return The resource owner user id.
4698: */
4699: public String getOwnerId(Entity r) {
4700: return null;
4701: }
4702:
4703: /**
4704: * Access the resource date.
4705: *
4706: * @param r
4707: * The resource.
4708: * @return The resource date.
4709: */
4710: public Time getDate(Entity r) {
4711: return null;
4712: }
4713:
4714: /**********************************************************************************************************************************************************************************************************************************************************
4715: * PDF file generation
4716: *********************************************************************************************************************************************************************************************************************************************************/
4717:
4718: // XSL File Names
4719: protected final static String DAY_VIEW_XSLT_FILENAME = "schedule.xsl";
4720:
4721: protected final static String LIST_VIEW_XSLT_FILENAME = "schlist.xsl";
4722:
4723: protected final static String MONTH_VIEW_XSLT_FILENAME = "schedulemm.xsl";
4724:
4725: protected final static String WEEK_VIEW_XSLT_FILENAME = "schedule.xsl";
4726:
4727: // Mime Types
4728: protected final static String PDF_MIME_TYPE = "application/pdf";
4729:
4730: // Constants for time calculations
4731: protected static long MILLISECONDS_IN_DAY = (60 * 60 * 24 * 1000);
4732:
4733: protected final static long MILLISECONDS_IN_HOUR = (60 * 60 * 1000);
4734:
4735: protected final static long MILLISECONDS_IN_MINUTE = (1000 * 60);
4736:
4737: protected static final long MINIMUM_EVENT_LENGTH_IN_MSECONDS = (29 * MILLISECONDS_IN_MINUTE);
4738:
4739: protected static final int SCHEDULE_INTERVAL_IN_MINUTES = 15;
4740:
4741: protected static final int MAX_OVERLAPPING_COLUMNS = 7;
4742:
4743: protected static final int TIMESLOT_FOR_OVERLAP_DETECTION_IN_MINUTES = 10;
4744:
4745: // URL Parameter Constants
4746: protected static final String TIME_RANGE_PARAMETER_NAME = "timeRange";
4747:
4748: protected static final String DAILY_START_TIME_PARAMETER_NAME = "dailyStartTime";
4749:
4750: protected final static String USER_NAME_PARAMETER_NAME = "user";
4751:
4752: protected final static String CALENDAR_PARAMETER_BASE_NAME = "calendar";
4753:
4754: protected final static String SCHEDULE_TYPE_PARAMETER_NAME = "scheduleType";
4755:
4756: // XML Node/Attribute Names
4757: protected static final String COLUMN_NODE_NAME = "col";
4758:
4759: protected static final String EVENT_NODE_NAME = "event";
4760:
4761: protected static final String FACULTY_EVENT_ATTRIBUTE_NAME = "Faculty";
4762:
4763: protected static final String FACULTY_NODE = "faculty";
4764:
4765: protected static final String DESCRIPTION_NODE = "description";
4766:
4767: protected static final String FROM_ATTRIBUTE_STRING = "from";
4768:
4769: protected static final String GROUP_NODE = "grp";
4770:
4771: protected static final String LIST_DATE_ATTRIBUTE_NAME = "dt";
4772:
4773: protected static final String LIST_DAY_OF_WEEK_ATTRIBUTE_NAME = "dayofweek";
4774:
4775: protected static final String LIST_NODE_NAME = "list";
4776:
4777: protected static final String MONTH_NODE_NAME = "month";
4778:
4779: protected static final String MAX_CONCURRENT_EVENTS_NAME = "maxConcurrentEvents";
4780:
4781: protected static final String PLACE_NODE = "place";
4782:
4783: protected static final String ROW_NODE_NAME = "row";
4784:
4785: protected static final String SCHEDULE_NODE = "schedule";
4786:
4787: protected static final String START_DAY_WEEK_ATTRIBUTE_NAME = "startdayweek";
4788:
4789: protected static final String START_TIME_ATTRIBUTE_NAME = "start-time";
4790:
4791: protected static final String SUB_EVENT_NODE_NAME = "subEvent";
4792:
4793: protected static final String TITLE_NODE = "title";
4794:
4795: protected static final String TO_ATTRIBUTE_STRING = "to";
4796:
4797: protected static final String TYPE_NODE = "type";
4798:
4799: protected static final String UID_NODE = "uid";
4800:
4801: // Misc.
4802: protected static final String HOUR_MINUTE_SEPARATOR = ":";
4803:
4804: /**
4805: * This is a container for a list of columns, plus the timerange for all the events contained in the row. This time range is a union of all the separate time ranges.
4806: */
4807: protected class LayoutRow extends ArrayList {
4808: // Union of all event time ranges in this row.
4809: private TimeRange rowTimeRange;
4810:
4811: /**
4812: * Gets the union of all event time ranges in this row.
4813: */
4814: public TimeRange getRowTimeRange() {
4815: return rowTimeRange;
4816: }
4817:
4818: /**
4819: * Sets the union of all event time ranges in this row.
4820: */
4821: public void setRowTimeRange(TimeRange range) {
4822: rowTimeRange = range;
4823: }
4824: }
4825:
4826: /**
4827: * Table used to layout a single day, with potentially overlapping events.
4828: */
4829: protected class SingleDayLayoutTable {
4830: protected long millisecondsPerTimeslot;
4831:
4832: protected int numCols;
4833:
4834: protected int numRows;
4835:
4836: protected ArrayList rows;
4837:
4838: // Overall time range for this table.
4839: protected TimeRange timeRange;
4840:
4841: /**
4842: * Constructor for SingleDayLayoutTable
4843: */
4844: public SingleDayLayoutTable(TimeRange timeRange,
4845: int maxNumberOverlappingEvents, int timeslotInMinutes) {
4846: this .timeRange = timeRange;
4847: numCols = maxNumberOverlappingEvents;
4848:
4849: millisecondsPerTimeslot = timeslotInMinutes
4850: * MILLISECONDS_IN_MINUTE;
4851:
4852: numRows = getNumberOfRowsNeeded(timeRange);
4853:
4854: rows = new ArrayList(numRows);
4855:
4856: for (int i = 0; i < numRows; i++) {
4857: ArrayList newRow = new ArrayList(numCols);
4858:
4859: rows.add(i, newRow);
4860:
4861: for (int j = 0; j < numCols; j++) {
4862: newRow.add(j, new LayoutTableCell());
4863: }
4864: }
4865: }
4866:
4867: /**
4868: * Adds an event to the SingleDayLayoutTable
4869: */
4870: public void addEvent(CalendarEvent calendarEvent) {
4871: if (calendarEvent == null) {
4872: return;
4873: }
4874:
4875: int startingRow = getStartingRow(roundRangeToMinimumTimeInterval(calendarEvent
4876: .getRange()));
4877:
4878: int numRowsNeeded = getNumberOfRowsNeeded(roundRangeToMinimumTimeInterval(calendarEvent
4879: .getRange()));
4880:
4881: // Trim to the end of the table.
4882: if (startingRow + numRowsNeeded >= getNumRows()) {
4883: numRowsNeeded = getNumRows() - startingRow;
4884: }
4885:
4886: // Get the first column that has enough sequential free intervals to
4887: // contain this event.
4888: int columnNumber = getFreeColumn(startingRow, numRowsNeeded);
4889:
4890: if (columnNumber != -1) {
4891: for (int i = startingRow; i < startingRow
4892: + numRowsNeeded; i++) {
4893: LayoutTableCell cell = getCell(i, columnNumber);
4894:
4895: // All cells have the calendar event information.
4896: cell.setCalendarEvent(calendarEvent);
4897:
4898: // Only the first cell is marked as such.
4899: if (i == startingRow) {
4900: cell.setFirstCell(true);
4901: }
4902:
4903: cell.setFirstCellRow(startingRow);
4904: cell.setFirstCellColumn(columnNumber);
4905:
4906: cell.setThisCellRow(i);
4907: cell.setThisCellColumn(columnNumber);
4908:
4909: cell.setNumCellsInEvent(numRowsNeeded);
4910: }
4911: }
4912: }
4913:
4914: /**
4915: * Convert the time range to fall entirely within the time range of the layout table.
4916: */
4917: protected TimeRange adjustTimeRangeToLayoutTable(
4918: TimeRange eventTimeRange) {
4919: Time lowerBound = null, upperBound = null;
4920:
4921: //
4922: // Make sure that the upper/lower bounds fall within the layout table.
4923: //
4924: if (this .timeRange.firstTime().compareTo(
4925: eventTimeRange.firstTime()) > 0) {
4926: lowerBound = this .timeRange.firstTime();
4927: } else {
4928: lowerBound = eventTimeRange.firstTime();
4929: }
4930:
4931: if (this .timeRange.lastTime().compareTo(
4932: eventTimeRange.lastTime()) < 0) {
4933: upperBound = this .timeRange.lastTime();
4934: } else {
4935: upperBound = eventTimeRange.lastTime();
4936: }
4937:
4938: return TimeService.newTimeRange(lowerBound, upperBound,
4939: true, false);
4940: }
4941:
4942: /**
4943: * Returns true if there are any events in this or other rows that overlap the event associated with this cell.
4944: */
4945: protected boolean cellHasOverlappingEvents(int rowNum,
4946: int colNum) {
4947: LayoutTableCell cell = this .getFirstCell(rowNum, colNum);
4948:
4949: // Start at the first cell of this event and check every row
4950: // to see if we find any cells in that row that are not empty
4951: // and are not one of ours.
4952: if (cell != null && !cell.isEmptyCell()) {
4953: for (int i = cell.getFirstCellRow(); i < (cell
4954: .getFirstCellRow() + cell.getNumCellsInEvent()); i++) {
4955: for (int j = 0; j < this .numCols; j++) {
4956: LayoutTableCell curCell = this .getCell(i, j);
4957:
4958: if (curCell != null
4959: && !curCell.isEmptyCell()
4960: && curCell.getCalendarEvent() != cell
4961: .getCalendarEvent()) {
4962: return true;
4963: }
4964: }
4965: }
4966: }
4967:
4968: return false;
4969: }
4970:
4971: /**
4972: * Get a particular cell. Returns a reference to the actual cell and not a copy.
4973: */
4974: protected LayoutTableCell getCell(int rowNum, int colNum) {
4975: if (rowNum < 0 || rowNum >= this .numRows || colNum < 0
4976: || colNum >= this .numCols) {
4977: // Illegal cell indices
4978: return null;
4979: } else {
4980: ArrayList row = (ArrayList) rows.get(rowNum);
4981: return (LayoutTableCell) row.get(colNum);
4982: }
4983: }
4984:
4985: /**
4986: * Gets the first cell associated with the event that's stored at this row/column
4987: */
4988: protected LayoutTableCell getFirstCell(int rowNum, int colNum) {
4989: LayoutTableCell cell = this .getCell(rowNum, colNum);
4990:
4991: if (cell == null || cell.isEmptyCell()) {
4992: return null;
4993: } else {
4994: return getCell(cell.getFirstCellRow(), cell
4995: .getFirstCellColumn());
4996: }
4997: }
4998:
4999: /**
5000: * Looks for a column where the whole event can be placed.
5001: */
5002: protected int getFreeColumn(int rowNum, int numberColumnsNeeded) {
5003: // Keep going through the columns until we hit one that has
5004: // enough empty cells to accomodate our event.
5005: for (int i = 0; i < this .numCols; i++) {
5006: boolean foundOccupiedCell = false;
5007:
5008: for (int j = rowNum; j < rowNum + numberColumnsNeeded; j++) {
5009: LayoutTableCell cell = getCell(j, i);
5010:
5011: if (cell == null) {
5012: // Out of range.
5013: return -1;
5014: }
5015:
5016: if (!cell.isEmptyCell()) {
5017: foundOccupiedCell = true;
5018: break;
5019: }
5020: }
5021:
5022: if (!foundOccupiedCell) {
5023: return i;
5024: }
5025: }
5026:
5027: return -1;
5028: }
5029:
5030: /**
5031: * Creates a list of lists of lists. The outer list is a list of rows. Each row is a list of columns. Each column is a list of column values.
5032: */
5033: public List getLayoutRows() {
5034: List allRows = new ArrayList();
5035:
5036: // Scan all rows in the table.
5037: for (int mainRowIndex = 0; mainRowIndex < this .getNumRows(); mainRowIndex++) {
5038: // If we hit a starting row, then iterate through all rows of the
5039: // event group.
5040: if (isStartingRowOfGroup(mainRowIndex)) {
5041: LayoutRow newRow = new LayoutRow();
5042: allRows.add(newRow);
5043:
5044: int numRowsInGroup = getNumberRowsInEventGroup(mainRowIndex);
5045:
5046: newRow.setRowTimeRange(getTimeRangeForEventGroup(
5047: mainRowIndex, numRowsInGroup));
5048:
5049: for (int columnIndex = 0; columnIndex < this
5050: .getNumCols(); columnIndex++) {
5051: List columnList = new ArrayList();
5052: boolean addedCell = false;
5053:
5054: for (int eventGroupRowIndex = mainRowIndex; eventGroupRowIndex < mainRowIndex
5055: + numRowsInGroup; eventGroupRowIndex++) {
5056: LayoutTableCell cell = getCell(
5057: eventGroupRowIndex, columnIndex);
5058:
5059: if (cell.isFirstCell()) {
5060: columnList.add(cell.getCalendarEvent());
5061: addedCell = true;
5062: }
5063: }
5064:
5065: // Don't add to our list unless we actually added a cell.
5066: if (addedCell) {
5067: newRow.add(columnList);
5068: }
5069: }
5070:
5071: // Get ready for the next iteration. Skip those
5072: // rows that we have already processed.
5073: mainRowIndex += (numRowsInGroup - 1);
5074: }
5075: }
5076:
5077: return allRows;
5078: }
5079:
5080: protected int getNumberOfRowsNeeded(TimeRange eventTimeRange) {
5081: TimeRange adjustedTimeRange = adjustTimeRangeToLayoutTable(eventTimeRange);
5082:
5083: // Use the ceiling function to obtain the next highest integral number of time slots.
5084: return (int) (Math.ceil((double) (adjustedTimeRange
5085: .duration())
5086: / (double) millisecondsPerTimeslot));
5087: }
5088:
5089: /**
5090: * Gets the number of rows in an event group. This function assumes that the row that it starts on is the starting row of the group.
5091: */
5092: protected int getNumberRowsInEventGroup(int rowNum) {
5093: int numEventRows = 0;
5094:
5095: if (isStartingRowOfGroup(rowNum)) {
5096: numEventRows++;
5097:
5098: // Keep going unless we see an all empty row
5099: // or another starting row.
5100: for (int i = rowNum + 1; i < this .getNumRows()
5101: && !isEmptyRow(i) && !isStartingRowOfGroup(i); i++) {
5102: numEventRows++;
5103: }
5104: }
5105:
5106: return numEventRows;
5107: }
5108:
5109: /**
5110: * Gets the total number of columns in the layout table.
5111: */
5112: public int getNumCols() {
5113: return this .numCols;
5114: }
5115:
5116: /**
5117: * Gets the total number of rows in the layout table.
5118: */
5119: public int getNumRows() {
5120: return rows.size();
5121: }
5122:
5123: /**
5124: * Given a time range, returns the starting row number in the layout table.
5125: */
5126: protected int getStartingRow(TimeRange eventTimeRange) {
5127: TimeRange adjustedTimeRange = adjustTimeRangeToLayoutTable(eventTimeRange);
5128:
5129: TimeRange timeRangeToStart = TimeService.newTimeRange(
5130: this .timeRange.firstTime(), adjustedTimeRange
5131: .firstTime(), true, true);
5132:
5133: //
5134: // We form a new time range where the ending time is the (adjusted) event
5135: // time range and the starting time is the starting time of the layout table.
5136: // The number of rows required for this range will be the starting row of the table.
5137: //
5138: return getNumberOfRowsNeeded(timeRangeToStart);
5139: }
5140:
5141: /**
5142: * Returns the earliest/latest times for events in this group. This function assumes that the row that it starts on is the starting row of the group.
5143: */
5144: public TimeRange getTimeRangeForEventGroup(int rowNum,
5145: int numRowsInThisEventGroup) {
5146: Time firstTime = null;
5147: Time lastTime = null;
5148:
5149: for (int i = rowNum; i < rowNum + numRowsInThisEventGroup; i++) {
5150: for (int j = 0; j < this .getNumCols(); j++) {
5151: LayoutTableCell cell = getCell(i, j);
5152: CalendarEvent event = cell.getCalendarEvent();
5153:
5154: if (event != null) {
5155: TimeRange adjustedTimeRange = adjustTimeRangeToLayoutTable(roundRangeToMinimumTimeInterval(cell
5156: .getCalendarEvent().getRange()));
5157:
5158: //
5159: // Replace our earliest time to date with the
5160: // time from the event, if the time from the
5161: // event is earlier.
5162: //
5163: if (firstTime == null) {
5164: firstTime = adjustedTimeRange.firstTime();
5165: } else {
5166: Time eventFirstTime = adjustedTimeRange
5167: .firstTime();
5168:
5169: if (eventFirstTime.compareTo(firstTime) < 0) {
5170: firstTime = eventFirstTime;
5171: }
5172: }
5173:
5174: //
5175: // Replace our latest time to date with the
5176: // time from the event, if the time from the
5177: // event is later.
5178: //
5179: if (lastTime == null) {
5180: lastTime = adjustedTimeRange.lastTime();
5181: } else {
5182: Time eventLastTime = adjustedTimeRange
5183: .lastTime();
5184:
5185: if (eventLastTime.compareTo(lastTime) > 0) {
5186: lastTime = eventLastTime;
5187: }
5188: }
5189: }
5190: }
5191: }
5192:
5193: return TimeService.newTimeRange(firstTime, lastTime, true,
5194: false);
5195: }
5196:
5197: /**
5198: * Returns true if this row has only empty cells.
5199: */
5200: protected boolean isEmptyRow(int rowNum) {
5201: boolean sawNonEmptyCell = false;
5202:
5203: for (int i = 0; i < this .getNumCols(); i++) {
5204: LayoutTableCell cell = getCell(rowNum, i);
5205:
5206: if (!cell.isEmptyCell()) {
5207: sawNonEmptyCell = true;
5208: break;
5209: }
5210: }
5211: return !sawNonEmptyCell;
5212: }
5213:
5214: /**
5215: * Returns true if this row has only starting cells and no continuation cells.
5216: */
5217: protected boolean isStartingRowOfGroup(int rowNum) {
5218: boolean sawContinuationCells = false;
5219: boolean sawFirstCell = false;
5220:
5221: for (int i = 0; i < this .getNumCols(); i++) {
5222: LayoutTableCell cell = getCell(rowNum, i);
5223:
5224: if (cell.isContinuationCell()) {
5225: sawContinuationCells = true;
5226: }
5227:
5228: if (cell.isFirstCell) {
5229: sawFirstCell = true;
5230: }
5231: }
5232:
5233: //
5234: // In order to be a starting row must have a "first"
5235: // cell no continuation cells.
5236: //
5237: return (!sawContinuationCells && sawFirstCell);
5238: }
5239:
5240: /**
5241: * Returns true if there are any cells in this row associated with events which overlap each other in this row or any other row.
5242: */
5243: public boolean rowHasOverlappingEvents(int rowNum) {
5244: for (int i = 0; i < this .getNumCols(); i++) {
5245: if (cellHasOverlappingEvents(rowNum, i)) {
5246: return true;
5247: }
5248: }
5249:
5250: return false;
5251: }
5252: }
5253:
5254: /**
5255: * This is a single cell in a layout table (an instance of SingleDayLayoutTable).
5256: */
5257: protected class LayoutTableCell {
5258: protected CalendarEvent calendarEvent = null;
5259:
5260: protected int firstCellColumn = -1;
5261:
5262: protected int firstCellRow = -1;
5263:
5264: protected boolean isFirstCell = false;
5265:
5266: protected int numCellsInEvent = 0;
5267:
5268: protected int this CellColumn = -1;
5269:
5270: protected int this CellRow = -1;
5271:
5272: /**
5273: * Gets the calendar event associated with this cell.
5274: */
5275: public CalendarEvent getCalendarEvent() {
5276: return calendarEvent;
5277: }
5278:
5279: /**
5280: * Gets the first column associated with this cell.
5281: */
5282: public int getFirstCellColumn() {
5283: return firstCellColumn;
5284: }
5285:
5286: /**
5287: * Gets the first row associated with this cell.
5288: */
5289: public int getFirstCellRow() {
5290: return firstCellRow;
5291: }
5292:
5293: /**
5294: * Get the number of cells in this event.
5295: */
5296: public int getNumCellsInEvent() {
5297: return numCellsInEvent;
5298: }
5299:
5300: /**
5301: * Gets the column associated with this particular cell.
5302: */
5303: public int getThisCellColumn() {
5304: return this CellColumn;
5305: }
5306:
5307: /**
5308: * Gets the row associated with this cell.
5309: */
5310: public int getThisCellRow() {
5311: return this CellRow;
5312: }
5313:
5314: /**
5315: * Returns true if this cell is a continuation of an event and not the first cell in the event.
5316: */
5317: public boolean isContinuationCell() {
5318: return !isFirstCell() && !isEmptyCell();
5319: }
5320:
5321: /**
5322: * Returns true if this cell is not associated with any events.
5323: */
5324: public boolean isEmptyCell() {
5325: return calendarEvent == null;
5326: }
5327:
5328: /**
5329: * Returns true if this is the first cell in a column of cells associated with an event.
5330: */
5331: public boolean isFirstCell() {
5332: return isFirstCell;
5333: }
5334:
5335: /**
5336: * Set the calendar event associated with this cell.
5337: */
5338: public void setCalendarEvent(CalendarEvent event) {
5339: calendarEvent = event;
5340: }
5341:
5342: /**
5343: * Set flag indicating that this is the first cell in column of cells associated with an event.
5344: */
5345: public void setFirstCell(boolean b) {
5346: isFirstCell = b;
5347: }
5348:
5349: /**
5350: * Sets a value in this cell to point to the very first cell in the column of cells associated with this event.
5351: */
5352: public void setFirstCellColumn(int i) {
5353: firstCellColumn = i;
5354: }
5355:
5356: /**
5357: * Sets a value in this cell to point to the very first cell in the column of cells associated with this event.
5358: */
5359: public void setFirstCellRow(int i) {
5360: firstCellRow = i;
5361: }
5362:
5363: /**
5364: * Gets the number of cells (if any) in the group of cells associated with this cell by event.
5365: */
5366: public void setNumCellsInEvent(int i) {
5367: numCellsInEvent = i;
5368: }
5369:
5370: /**
5371: * Sets the actual column index for this cell.
5372: */
5373: public void setThisCellColumn(int i) {
5374: this CellColumn = i;
5375: }
5376:
5377: /**
5378: * Sets the actual row index for this cell.
5379: */
5380: public void setThisCellRow(int i) {
5381: this CellRow = i;
5382: }
5383: }
5384:
5385: /**
5386: * Debugging routine to get a string for a TimeRange. This should probably be in the TimeRange class.
5387: */
5388: protected String dumpTimeRange(TimeRange timeRange) {
5389: String returnString = "";
5390:
5391: if (timeRange != null) {
5392: returnString = timeRange.firstTime().toStringLocalFull()
5393: + " - " + timeRange.lastTime().toStringLocalFull();
5394: }
5395:
5396: return returnString;
5397: }
5398:
5399: /**
5400: * Takes a DOM structure and renders a PDF
5401: *
5402: * @param doc
5403: * DOM structure
5404: * @param xslFileName
5405: * XSL file to use to translate the DOM document to FOP
5406: */
5407: protected void generatePDF(Document doc, String xslFileName,
5408: OutputStream streamOut) {
5409: Driver driver = new Driver();
5410:
5411: org.apache.avalon.framework.logger.Logger logger = new ConsoleLogger(
5412: ConsoleLogger.LEVEL_ERROR);
5413: MessageHandler.setScreenLogger(logger);
5414: driver.setLogger(logger);
5415:
5416: driver.setOutputStream(streamOut);
5417: driver.setRenderer(Driver.RENDER_PDF);
5418:
5419: try {
5420: InputStream in = getClass().getClassLoader()
5421: .getResourceAsStream(xslFileName);
5422: Transformer transformer = transformerFactory
5423: .newTransformer(new StreamSource(in));
5424:
5425: Source src = new DOMSource(doc);
5426:
5427: // Kludge: Xalan in JDK 1.4/1.5 does not properly resolve java.util.ResourceBundle
5428: // when passed as parameter, so here we fetch each of the localized strings
5429: // Should be able to clean this up in JDK 1.6 and just pass correct ResourceBundle
5430: transformer.setParameter("sun", rb.getString("day.sun"));
5431: transformer.setParameter("mon", rb.getString("day.mon"));
5432: transformer.setParameter("tues", rb.getString("day.tues"));
5433: transformer.setParameter("wed", rb.getString("day.wed"));
5434: transformer
5435: .setParameter("thurs", rb.getString("day.thurs"));
5436: transformer.setParameter("fri", rb.getString("day.fri"));
5437: transformer.setParameter("sat", rb.getString("day.sat"));
5438:
5439: transformer.setParameter("jan", rb.getString("month.jan"));
5440: transformer.setParameter("feb", rb.getString("month.feb"));
5441: transformer.setParameter("mar", rb.getString("month.mar"));
5442: transformer.setParameter("apr", rb.getString("month.apr"));
5443: transformer.setParameter("may", rb.getString("month.may"));
5444: transformer.setParameter("jun", rb.getString("month.jun"));
5445: transformer.setParameter("jul", rb.getString("month.jul"));
5446: transformer.setParameter("aug", rb.getString("month.aug"));
5447: transformer.setParameter("sep", rb.getString("month.sep"));
5448: transformer.setParameter("oct", rb.getString("month.oct"));
5449: transformer.setParameter("nov", rb.getString("month.nov"));
5450: transformer.setParameter("dec", rb.getString("month.dec"));
5451:
5452: transformer
5453: .setParameter("sched", rb.getString("sched.for"));
5454: transformer.transform(src, new SAXResult(driver
5455: .getContentHandler()));
5456: }
5457:
5458: catch (TransformerException e) {
5459: e.printStackTrace();
5460: M_log.warn(this + ".generatePDF(): " + e);
5461: return;
5462: }
5463: }
5464:
5465: /**
5466: * Make a full-day time range given a year, month, and day
5467: */
5468: protected TimeRange getFullDayTimeRangeFromYMD(int year, int month,
5469: int day) {
5470: return TimeService.newTimeRange(TimeService.newTimeLocal(year,
5471: month, day, 0, 0, 0, 0), TimeService.newTimeLocal(year,
5472: month, day, 23, 59, 59, 999));
5473: }
5474:
5475: /**
5476: * Make a list of days for use in generating an XML document for the list view.
5477: */
5478: protected List makeListViewTimeRangeList(TimeRange timeRange,
5479: List calendarReferenceList) {
5480: // This is used to dimension a hash table. The default load factor is .75.
5481: // A rehash isn't done until the number of items in the table is .75 * the number
5482: // of items in the capacity.
5483: final int DEFAULT_INITIAL_HASH_CAPACITY = 150;
5484:
5485: List listOfDays = new ArrayList();
5486:
5487: // Get a list of merged events.
5488: CalendarEventVector calendarEventVector = getEvents(
5489: calendarReferenceList, timeRange);
5490:
5491: Iterator itEvents = calendarEventVector.iterator();
5492: HashMap datesSeenSoFar = new HashMap(
5493: DEFAULT_INITIAL_HASH_CAPACITY);
5494:
5495: while (itEvents.hasNext()) {
5496:
5497: CalendarEvent event = (CalendarEvent) itEvents.next();
5498:
5499: //
5500: // Each event may span multiple days, so we need to split each
5501: // events's time range into single day slots.
5502: //
5503: List timeRangeList = splitTimeRangeIntoListOfSingleDayTimeRanges(
5504: event.getRange(), null);
5505:
5506: Iterator itDatesInRange = timeRangeList.iterator();
5507:
5508: while (itDatesInRange.hasNext()) {
5509: TimeRange curDay = (TimeRange) itDatesInRange.next();
5510: String curDate = curDay.firstTime().toStringLocalDate();
5511:
5512: if (!datesSeenSoFar.containsKey(curDate)) {
5513: // Add this day to list
5514: TimeBreakdown startBreakDown = curDay.firstTime()
5515: .breakdownLocal();
5516:
5517: listOfDays.add(getFullDayTimeRangeFromYMD(
5518: startBreakDown.getYear(), startBreakDown
5519: .getMonth(), startBreakDown
5520: .getDay()));
5521:
5522: datesSeenSoFar.put(curDate, "");
5523: }
5524: }
5525: }
5526:
5527: return listOfDays;
5528: }
5529:
5530: /**
5531: * @param scheduleType
5532: * daily, weekly, monthly, or list (no yearly).
5533: * @param doc
5534: * XML output document
5535: * @param timeRange
5536: * this is the overall time range. For example, for a weekly schedule, it would be the start/end times for the currently selected week period.
5537: * @param dailyTimeRange
5538: * On a weekly time schedule, even if the overall time range is for a week, you're only looking at a portion of the day (e.g., 8 AM to 6 PM, etc.)
5539: * @param userID
5540: * This is the name of the user whose schedule is being printed.
5541: */
5542: protected void generateXMLDocument(int scheduleType, Document doc,
5543: TimeRange timeRange, TimeRange dailyTimeRange,
5544: List calendarReferenceList, String userID) {
5545:
5546: // This list will have an entry for every week day that we care about.
5547: List timeRangeList = null;
5548: TimeRange actualTimeRange = null;
5549: Element topLevelMaxConcurrentEvents = null;
5550:
5551: switch (scheduleType) {
5552: case WEEK_VIEW:
5553: actualTimeRange = timeRange;
5554: timeRangeList = getTimeRangeListForWeek(actualTimeRange,
5555: calendarReferenceList, dailyTimeRange, true);
5556: break;
5557:
5558: case MONTH_VIEW:
5559: // Make sure that we trim off the days of the previous and next
5560: // month. The time range that we're being passed is "padded"
5561: // with extra days to make up a full block of an integral number
5562: // of seven day weeks.
5563: actualTimeRange = shrinkTimeRangeToCurrentMonth(timeRange);
5564: timeRangeList = splitTimeRangeIntoListOfSingleDayTimeRanges(
5565: actualTimeRange, null);
5566: break;
5567:
5568: case LIST_VIEW:
5569: //
5570: // With the list view, we want to come up with a list of days
5571: // that have events, not every day in the range.
5572: //
5573: actualTimeRange = timeRange;
5574:
5575: timeRangeList = makeListViewTimeRangeList(actualTimeRange,
5576: calendarReferenceList);
5577: break;
5578:
5579: case DAY_VIEW:
5580: //
5581: // We have a single entry in the list for a day. Having a singleton
5582: // list may seem wasteful, but it allows us to use one loop below
5583: // for all processing.
5584: //
5585: actualTimeRange = timeRange;
5586: timeRangeList = splitTimeRangeIntoListOfSingleDayTimeRanges(
5587: actualTimeRange, dailyTimeRange);
5588: break;
5589:
5590: default:
5591: M_log
5592: .warn(".generateXMLDocument(): bad scheduleType parameter = "
5593: + scheduleType);
5594: break;
5595: }
5596:
5597: if (timeRangeList != null) {
5598: // Create Root Element
5599: Element root = doc.createElement(SCHEDULE_NODE);
5600:
5601: if (userID != null) {
5602: writeStringNodeToDom(doc, root, UID_NODE, userID);
5603: }
5604:
5605: // Write out the number of events that we have per timeslot.
5606: // This is used to figure out how to display overlapping events.
5607: // At this level, assume that we start with 1 event.
5608: topLevelMaxConcurrentEvents = writeStringNodeToDom(doc,
5609: root, MAX_CONCURRENT_EVENTS_NAME, "1");
5610:
5611: // Add a start time node.
5612: writeStringNodeToDom(doc, root, START_TIME_ATTRIBUTE_NAME,
5613: getTimeString(dailyTimeRange.firstTime()));
5614:
5615: // Add the Root Element to Document
5616: doc.appendChild(root);
5617:
5618: //
5619: // Only add a "month" node with the first numeric day
5620: // of the month if we're in the month view.
5621: //
5622: if (scheduleType == MONTH_VIEW) {
5623: CalendarUtil monthCalendar = new CalendarUtil();
5624:
5625: // Use the middle of the month since the start/end ranges
5626: // may be in an adjacent month.
5627: TimeBreakdown breakDown = actualTimeRange.firstTime()
5628: .breakdownLocal();
5629:
5630: monthCalendar.setDay(breakDown.getYear(), breakDown
5631: .getMonth(), breakDown.getDay());
5632:
5633: int firstDayOfMonth = monthCalendar
5634: .getFirstDayOfMonth(breakDown.getMonth() - 1);
5635:
5636: // Create a list of events for the given day.
5637: Element monthElement = doc
5638: .createElement(MONTH_NODE_NAME);
5639: monthElement.setAttribute(
5640: START_DAY_WEEK_ATTRIBUTE_NAME, Integer
5641: .toString(firstDayOfMonth));
5642:
5643: root.appendChild(monthElement);
5644: }
5645:
5646: Iterator itList = timeRangeList.iterator();
5647:
5648: int maxNumberOfColumnsPerRow = 1;
5649:
5650: // Go through all the time ranges (days)
5651: while (itList.hasNext()) {
5652: TimeRange currentTimeRange = (TimeRange) itList.next();
5653: int maxConcurrentEventsOverListNode = 1;
5654:
5655: // Get a list of merged events.
5656: CalendarEventVector calendarEventVector = getEvents(
5657: calendarReferenceList, currentTimeRange);
5658:
5659: //
5660: // We don't need to generate "empty" event lists for the list view.
5661: //
5662: if (scheduleType == LIST_VIEW
5663: && calendarEventVector.size() == 0) {
5664: continue;
5665: }
5666:
5667: // Create a list of events for the given day.
5668: Element eventList = doc.createElement(LIST_NODE_NAME);
5669:
5670: // Set the current date
5671: eventList.setAttribute(LIST_DATE_ATTRIBUTE_NAME,
5672: getDateFromTime(currentTimeRange.firstTime()));
5673:
5674: // Set the maximum number of events per timeslot
5675: // Assume 1 as a starting point. This may be changed
5676: // later on.
5677: eventList
5678: .setAttribute(
5679: MAX_CONCURRENT_EVENTS_NAME,
5680: Integer
5681: .toString(maxConcurrentEventsOverListNode));
5682:
5683: // Calculate the day of the week.
5684: CalendarUtil cal = new CalendarUtil();
5685:
5686: Time date = currentTimeRange.firstTime();
5687: TimeBreakdown breakdown = date.breakdownLocal();
5688:
5689: cal.setDay(breakdown.getYear(), breakdown.getMonth(),
5690: breakdown.getDay());
5691:
5692: // Set the day of the week as a node attribute.
5693: eventList.setAttribute(LIST_DAY_OF_WEEK_ATTRIBUTE_NAME,
5694: Integer.toString(cal.getDay_Of_Week() - 1));
5695:
5696: // Attach this list to the top-level node
5697: root.appendChild(eventList);
5698:
5699: Iterator itEvent = calendarEventVector.iterator();
5700:
5701: //
5702: // Day and week views use a layout table to assist in constructing the
5703: // rowspan information for layout.
5704: //
5705: if (scheduleType == DAY_VIEW
5706: || scheduleType == WEEK_VIEW) {
5707: SingleDayLayoutTable layoutTable = new SingleDayLayoutTable(
5708: currentTimeRange, MAX_OVERLAPPING_COLUMNS,
5709: SCHEDULE_INTERVAL_IN_MINUTES);
5710:
5711: // Load all the events into our layout table.
5712: while (itEvent.hasNext()) {
5713: CalendarEvent event = (CalendarEvent) itEvent
5714: .next();
5715: layoutTable.addEvent(event);
5716: }
5717:
5718: List layoutRows = layoutTable.getLayoutRows();
5719:
5720: Iterator rowIterator = layoutRows.iterator();
5721:
5722: // Iterate through the list of rows.
5723: while (rowIterator.hasNext()) {
5724: LayoutRow layoutRow = (LayoutRow) rowIterator
5725: .next();
5726: TimeRange rowTimeRange = layoutRow
5727: .getRowTimeRange();
5728:
5729: if (maxNumberOfColumnsPerRow < layoutRow.size()) {
5730: maxNumberOfColumnsPerRow = layoutRow.size();
5731: }
5732:
5733: if (maxConcurrentEventsOverListNode < layoutRow
5734: .size()) {
5735: maxConcurrentEventsOverListNode = layoutRow
5736: .size();
5737: }
5738:
5739: Element eventNode = doc
5740: .createElement(EVENT_NODE_NAME);
5741: eventList.appendChild(eventNode);
5742:
5743: // Add the "from" time as an attribute.
5744: eventNode
5745: .setAttribute(FROM_ATTRIBUTE_STRING,
5746: getTimeString(rowTimeRange
5747: .firstTime()));
5748:
5749: // Add the "to" time as an attribute.
5750: eventNode
5751: .setAttribute(
5752: TO_ATTRIBUTE_STRING,
5753: getTimeString(performEndMinuteKludge(rowTimeRange
5754: .lastTime()
5755: .breakdownLocal())));
5756:
5757: Element rowNode = doc
5758: .createElement(ROW_NODE_NAME);
5759:
5760: // Set an attribute indicating the number of columns in this row.
5761: rowNode.setAttribute(
5762: MAX_CONCURRENT_EVENTS_NAME, Integer
5763: .toString(layoutRow.size()));
5764:
5765: eventNode.appendChild(rowNode);
5766:
5767: Iterator layoutRowIterator = layoutRow
5768: .iterator();
5769:
5770: // Iterate through our list of column lists.
5771: while (layoutRowIterator.hasNext()) {
5772: Element columnNode = doc
5773: .createElement(COLUMN_NODE_NAME);
5774: rowNode.appendChild(columnNode);
5775:
5776: List columnList = (List) layoutRowIterator
5777: .next();
5778:
5779: Iterator columnListIterator = columnList
5780: .iterator();
5781:
5782: // Iterate through the list of columns.
5783: while (columnListIterator.hasNext()) {
5784: CalendarEvent event = (CalendarEvent) columnListIterator
5785: .next();
5786: generateXMLEvent(doc, columnNode,
5787: event, SUB_EVENT_NODE_NAME,
5788: rowTimeRange, true, false,
5789: false);
5790: }
5791: }
5792: }
5793: } else {
5794: // Generate XML for all the events.
5795: while (itEvent.hasNext()) {
5796: CalendarEvent event = (CalendarEvent) itEvent
5797: .next();
5798: generateXMLEvent(doc, eventList, event,
5799: EVENT_NODE_NAME, currentTimeRange,
5800: false, false,
5801: (scheduleType == LIST_VIEW ? true
5802: : false));
5803: }
5804: }
5805:
5806: // Update this event after having gone through all the rows.
5807: eventList
5808: .setAttribute(
5809: MAX_CONCURRENT_EVENTS_NAME,
5810: Integer
5811: .toString(maxConcurrentEventsOverListNode));
5812:
5813: }
5814:
5815: // Set the node value way up at the head of the document to indicate
5816: // what the maximum number of columns was for the entire document.
5817: topLevelMaxConcurrentEvents.getFirstChild().setNodeValue(
5818: Integer.toString(maxNumberOfColumnsPerRow));
5819: }
5820:
5821: }
5822:
5823: /**
5824: * Trim the range that is passed in to the containing time range.
5825: */
5826: protected TimeRange trimTimeRange(TimeRange containingRange,
5827: TimeRange rangeToTrim) {
5828: long containingRangeStartTime = containingRange.firstTime()
5829: .getTime();
5830: long containingRangeEndTime = containingRange.lastTime()
5831: .getTime();
5832:
5833: long rangeToTrimStartTime = rangeToTrim.firstTime().getTime();
5834: long rangeToTrimEndTime = rangeToTrim.lastTime().getTime();
5835:
5836: long trimmedStartTime = 0, trimmedEndTime = 0;
5837:
5838: trimmedStartTime = Math.min(Math.max(containingRangeStartTime,
5839: rangeToTrimStartTime), containingRangeEndTime);
5840: trimmedEndTime = Math.max(Math.min(containingRangeEndTime,
5841: rangeToTrimEndTime), rangeToTrimStartTime);
5842:
5843: return TimeService.newTimeRange(TimeService
5844: .newTime(trimmedStartTime), TimeService
5845: .newTime(trimmedEndTime), true, false);
5846: }
5847:
5848: /**
5849: * Rounds a time range up to a minimum interval.
5850: */
5851: protected TimeRange roundRangeToMinimumTimeInterval(
5852: TimeRange timeRange) {
5853: TimeRange roundedTimeRange = timeRange;
5854:
5855: if (timeRange.duration() < MINIMUM_EVENT_LENGTH_IN_MSECONDS) {
5856: roundedTimeRange = TimeService.newTimeRange(timeRange
5857: .firstTime().getTime(),
5858: MINIMUM_EVENT_LENGTH_IN_MSECONDS);
5859: }
5860:
5861: return roundedTimeRange;
5862: }
5863:
5864: /**
5865: * Generates the XML for an event.
5866: */
5867: protected void generateXMLEvent(Document doc, Element parent,
5868: CalendarEvent event, String eventNodeName,
5869: TimeRange containingTimeRange, boolean forceMinimumTime,
5870: boolean hideGroupIfNoSpace, boolean performEndTimeKludge) {
5871: Element eventElement = doc.createElement(eventNodeName);
5872:
5873: TimeRange trimmedTimeRange = trimTimeRange(containingTimeRange,
5874: event.getRange());
5875:
5876: // Optionally force the event to have a minimum time slot.
5877: if (forceMinimumTime) {
5878: trimmedTimeRange = roundRangeToMinimumTimeInterval(trimmedTimeRange);
5879: }
5880:
5881: // Add the "from" time as an attribute.
5882: eventElement.setAttribute(FROM_ATTRIBUTE_STRING,
5883: getTimeString(trimmedTimeRange.firstTime()));
5884:
5885: // Add the "to" time as an attribute.
5886: Time endTime = null;
5887:
5888: // Optionally adjust the end time
5889: if (performEndTimeKludge) {
5890: endTime = performEndMinuteKludge(trimmedTimeRange
5891: .lastTime().breakdownLocal());
5892: } else {
5893: endTime = trimmedTimeRange.lastTime();
5894: }
5895:
5896: eventElement.setAttribute(TO_ATTRIBUTE_STRING,
5897: getTimeString(endTime));
5898:
5899: //
5900: // Add the group (really "site") node
5901: // Provide that we have space or if we've been told we need to display it.
5902: //
5903: if (!hideGroupIfNoSpace
5904: || trimmedTimeRange.duration() > MINIMUM_EVENT_LENGTH_IN_MSECONDS) {
5905: writeStringNodeToDom(doc, eventElement, GROUP_NODE,
5906: getSiteName(event));
5907: }
5908:
5909: // Add the display name node.
5910: writeStringNodeToDom(doc, eventElement, TITLE_NODE, event
5911: .getDisplayName());
5912:
5913: // Add the event type node.
5914: writeStringNodeToDom(doc, eventElement, TYPE_NODE, event
5915: .getType());
5916:
5917: // Add the place/location node.
5918: writeStringNodeToDom(doc, eventElement, PLACE_NODE, event
5919: .getLocation());
5920:
5921: // If a "Faculty" extra field is present, then add the node.
5922: writeStringNodeToDom(doc, eventElement, FACULTY_NODE, event
5923: .getField(FACULTY_EVENT_ATTRIBUTE_NAME));
5924:
5925: // If a "Description" field is present, then add the node.
5926: writeStringNodeToDom(doc, eventElement, DESCRIPTION_NODE, event
5927: .getDescription());
5928:
5929: parent.appendChild(eventElement);
5930: }
5931:
5932: /*
5933: * Gets the daily start time parameter from a Properties object filled from URL parameters.
5934: */
5935: protected TimeRange getDailyStartTimeFromParameters(
5936: Properties parameters) {
5937: return getTimeRangeParameterByName(parameters,
5938: DAILY_START_TIME_PARAMETER_NAME);
5939: }
5940:
5941: /**
5942: * Gets the standard date string from the time parameter
5943: */
5944: protected String getDateFromTime(Time time) {
5945: TimeBreakdown timeBreakdown = time.breakdownLocal();
5946:
5947: return timeBreakdown.getMonth() + "/" + timeBreakdown.getDay()
5948: + "/" + timeBreakdown.getYear();
5949: }
5950:
5951: /**
5952: * Gets the schedule type from a Properties object (filled from a URL parameter list).
5953: */
5954: protected int getScheduleTypeFromParameterList(Properties parameters) {
5955: int scheduleType = UNKNOWN_VIEW;
5956:
5957: // Get the type of schedule (daily, weekly, etc.)
5958: String scheduleTypeString = (String) parameters
5959: .get(SCHEDULE_TYPE_PARAMETER_NAME);
5960: scheduleType = Integer.parseInt(scheduleTypeString);
5961:
5962: return scheduleType;
5963: }
5964:
5965: /**
5966: * Gets a site name given a CalendarEvent
5967: */
5968: protected String getSiteName(CalendarEvent event) {
5969: Calendar calendar = null;
5970: String calendarName = "";
5971:
5972: try {
5973: calendar = getCalendar(event.getCalendarReference());
5974: } catch (IdUnusedException e) {
5975: M_log.warn(".getSiteName(): " + e);
5976: } catch (PermissionException e) {
5977: M_log.warn(".getSiteNamee(): " + e);
5978: }
5979:
5980: // Use the context name as the site name.
5981: if (calendar != null) {
5982: Site site = null;
5983:
5984: try {
5985: site = SiteService.getSite(calendar.getContext());
5986:
5987: if (site != null) {
5988: calendarName = site.getTitle();
5989: }
5990: } catch (IdUnusedException e1) {
5991: M_log.warn(".getSiteName(): " + e1);
5992: }
5993: }
5994:
5995: return calendarName;
5996: }
5997:
5998: /**
5999: * Access some named configuration value as a string.
6000: *
6001: * @param name
6002: * The configuration value name.
6003: * @param dflt
6004: * The value to return if not found.
6005: * @return The configuration value with this name, or the default value if not found.
6006: */
6007: protected String getString(String name, String dflt) {
6008: return m_serverConfigurationService.getString(name, dflt);
6009: }
6010:
6011: /*
6012: * Gets the time range parameter from a Properties object filled from URL parameters.
6013: */
6014: protected TimeRange getTimeRangeFromParameters(Properties parameters) {
6015: return getTimeRangeParameterByName(parameters,
6016: TIME_RANGE_PARAMETER_NAME);
6017: }
6018:
6019: /**
6020: * Generates a list of time ranges for a week. Each range in the list is a day.
6021: *
6022: * @param dailyTimeRange
6023: * representative daily time range (start hour/minute, end hour/minute)
6024: * @param skipSaturdayAndSundayIfNoEvents
6025: * if true, then Saturday and Sundary are skipped if there are no events.
6026: */
6027: protected ArrayList getTimeRangeListForWeek(TimeRange timeRange,
6028: List calendarReferenceList, TimeRange dailyTimeRange,
6029: boolean skipSaturdayAndSundayIfNoEvents) {
6030: TimeBreakdown startBreakdown = timeRange.firstTime()
6031: .breakdownLocal();
6032:
6033: GregorianCalendar startCalendarDate = TimeService.getCalendar(
6034: TimeService.getLocalTimeZone(), startBreakdown
6035: .getYear(), startBreakdown.getMonth() - 1,
6036: startBreakdown.getDay(), 0, 0, 0, 0);
6037:
6038: ArrayList weekDayTimeRanges = new ArrayList();
6039:
6040: int sundayDayIndex = 0;
6041: int saturdayDayIndex = 6;
6042: boolean skipSaturdayAndSunday = false;
6043:
6044: TimeBreakdown startBreakDown = dailyTimeRange.firstTime()
6045: .breakdownLocal();
6046:
6047: TimeBreakdown endBreakDown = dailyTimeRange.lastTime()
6048: .breakdownLocal();
6049:
6050: for (int i = sundayDayIndex; i <= saturdayDayIndex; i++) {
6051: //
6052: // Use the same start/end times for all days.
6053: //
6054: Time curStartTime = TimeService.newTimeLocal(
6055: startCalendarDate.get(GregorianCalendar.YEAR),
6056: startCalendarDate.get(GregorianCalendar.MONTH) + 1,
6057: startCalendarDate
6058: .get(GregorianCalendar.DAY_OF_MONTH),
6059: startBreakDown.getHour(), startBreakDown.getMin(),
6060: startBreakDown.getSec(), startBreakDown.getMs());
6061:
6062: Time curEndTime = TimeService.newTimeLocal(
6063: startCalendarDate.get(GregorianCalendar.YEAR),
6064: startCalendarDate.get(GregorianCalendar.MONTH) + 1,
6065: startCalendarDate
6066: .get(GregorianCalendar.DAY_OF_MONTH),
6067: endBreakDown.getHour(), endBreakDown.getMin(),
6068: endBreakDown.getSec(), endBreakDown.getMs());
6069:
6070: TimeRange newTimeRange = TimeService.newTimeRange(
6071: curStartTime, curEndTime, true, false);
6072:
6073: //
6074: // If the Saturday/Sunday skipping feature is specified, then on
6075: // Saturday, if there are no events scheduled, then check Sunday.
6076: // If Sunday also has no events, then we will trim off Saturday/Sunday
6077: //
6078: if (skipSaturdayAndSundayIfNoEvents && i == sundayDayIndex) {
6079: // Get a list of events for Saturday
6080: CalendarEventVector calendarEventVectorSunday = getEvents(
6081: calendarReferenceList, newTimeRange);
6082:
6083: // If there are no Sunday events, check if there are no Saturday events.
6084: if (calendarEventVectorSunday.isEmpty()) {
6085: GregorianCalendar saturdayCalendarDate = (GregorianCalendar) startCalendarDate
6086: .clone();
6087: saturdayCalendarDate.set(
6088: GregorianCalendar.DAY_OF_WEEK,
6089: GregorianCalendar.SATURDAY);
6090:
6091: //
6092: // Use the same start/end times for all days.
6093: //
6094: Time saturdayStartTime = TimeService
6095: .newTimeLocal(
6096: saturdayCalendarDate
6097: .get(GregorianCalendar.YEAR),
6098: saturdayCalendarDate
6099: .get(GregorianCalendar.MONTH) + 1,
6100: saturdayCalendarDate
6101: .get(GregorianCalendar.DAY_OF_MONTH),
6102: startBreakDown.getHour(),
6103: startBreakDown.getMin(),
6104: startBreakDown.getSec(),
6105: startBreakDown.getMs());
6106:
6107: Time saturdayEndTime = TimeService
6108: .newTimeLocal(
6109: saturdayCalendarDate
6110: .get(GregorianCalendar.YEAR),
6111: saturdayCalendarDate
6112: .get(GregorianCalendar.MONTH) + 1,
6113: saturdayCalendarDate
6114: .get(GregorianCalendar.DAY_OF_MONTH),
6115: endBreakDown.getHour(),
6116: endBreakDown.getMin(), endBreakDown
6117: .getSec(), endBreakDown
6118: .getMs());
6119: TimeRange saturdayTimeRange = TimeService
6120: .newTimeRange(saturdayStartTime,
6121: saturdayEndTime, true, false);
6122:
6123: // Get a list of events for Sunday
6124: CalendarEventVector calendarEventVectorSaturday = getEvents(
6125: calendarReferenceList, saturdayTimeRange);
6126:
6127: // If Saturday and Sunday have no events, set a flag to skip them.
6128: if (calendarEventVectorSaturday.isEmpty()) {
6129: skipSaturdayAndSunday = true;
6130: }
6131: }
6132: }
6133:
6134: // If it is not the case that "this is Saturday or Sunday
6135: // and we're skipping Saturday and Sunday", then
6136: // add the range to the list.
6137: if (!((i == sundayDayIndex || i == saturdayDayIndex) && skipSaturdayAndSunday)) {
6138: weekDayTimeRanges.add(newTimeRange);
6139: }
6140:
6141: // Move to the next day.
6142: startCalendarDate.add(GregorianCalendar.DATE, 1);
6143: }
6144:
6145: return weekDayTimeRanges;
6146: }
6147:
6148: /**
6149: * Utility routine to get a time range parameter from the URL parameters store in a Properties object.
6150: */
6151: protected TimeRange getTimeRangeParameterByName(
6152: Properties parameters, String name) {
6153: // Now get the time range.
6154: String timeRangeString = (String) parameters.get(name);
6155:
6156: TimeRange timeRange = null;
6157: timeRange = TimeService.newTimeRange(timeRangeString);
6158:
6159: return timeRange;
6160: }
6161:
6162: /**
6163: * Gets a standard time string give the time parameter.
6164: */
6165: protected String getTimeString(Time time) {
6166: TimeBreakdown timeBreakdown = time.breakdownLocal();
6167:
6168: DecimalFormat twoDecimalDigits = new DecimalFormat("00");
6169:
6170: return timeBreakdown.getHour() + HOUR_MINUTE_SEPARATOR
6171: + twoDecimalDigits.format(timeBreakdown.getMin());
6172: }
6173:
6174: /**
6175: * Given a schedule type, the appropriate XSLT file is returned
6176: */
6177: protected String getXSLFileNameForScheduleType(int scheduleType) {
6178: // get a relative path to the file
6179: String baseFileName = "";
6180:
6181: switch (scheduleType) {
6182: case WEEK_VIEW:
6183: baseFileName = WEEK_VIEW_XSLT_FILENAME;
6184: break;
6185:
6186: case DAY_VIEW:
6187: baseFileName = DAY_VIEW_XSLT_FILENAME;
6188: break;
6189:
6190: case MONTH_VIEW:
6191: baseFileName = MONTH_VIEW_XSLT_FILENAME;
6192: break;
6193:
6194: case LIST_VIEW:
6195: baseFileName = LIST_VIEW_XSLT_FILENAME;
6196: break;
6197:
6198: default:
6199: M_log
6200: .debug("PrintFileGeneration.getXSLFileNameForScheduleType(): unexpected scehdule type = "
6201: + scheduleType);
6202: break;
6203: }
6204:
6205: return baseFileName;
6206: }
6207:
6208: /**
6209: * This routine is used to round the end time. The time is stored at one minute less than the actual end time, but the user will expect to see the end time on the hour. For example, an event that ends at 10:00 is actually stored at 9:59. This code
6210: * should really be in a central place so that the velocity template can see it as well.
6211: */
6212: protected Time performEndMinuteKludge(TimeBreakdown breakDown) {
6213: int endMin = breakDown.getMin();
6214: int endHour = breakDown.getHour();
6215:
6216: int tmpMinVal = endMin
6217: % TIMESLOT_FOR_OVERLAP_DETECTION_IN_MINUTES;
6218:
6219: if (tmpMinVal == 4 || tmpMinVal == 9) {
6220: endMin = endMin + 1;
6221:
6222: if (endMin == 60) {
6223: endMin = 00;
6224: endHour = endHour + 1;
6225: }
6226: }
6227:
6228: return TimeService.newTimeLocal(breakDown.getYear(), breakDown
6229: .getMonth(), breakDown.getDay(), endHour, endMin,
6230: breakDown.getSec(), breakDown.getMs());
6231: }
6232:
6233: /**
6234: * Called by the servlet to service a get/post requesting a calendar in PDF format.
6235: */
6236: protected void printSchedule(Properties parameters,
6237: StringBuffer contentType, OutputStream os)
6238: throws PermissionException {
6239: // Get the user name.
6240: String userName = (String) parameters
6241: .get(USER_NAME_PARAMETER_NAME);
6242:
6243: // Get the list of calendars.from user session
6244: List calendarReferenceList = (List) SessionManager
6245: .getCurrentSession()
6246: .getAttribute(SESSION_CALENDAR_LIST);
6247:
6248: // check if there is any calendar to which the user has acces
6249: Iterator it = calendarReferenceList.iterator();
6250: int permissionCount = calendarReferenceList.size();
6251: while (it.hasNext()) {
6252: String calendarReference = (String) it.next();
6253: try {
6254: getCalendar(calendarReference);
6255: }
6256:
6257: catch (IdUnusedException e) {
6258: continue;
6259: }
6260:
6261: catch (PermissionException e) {
6262: permissionCount--;
6263: continue;
6264: }
6265: }
6266: // if no permission to any of the calendars, throw exception and do nothing
6267: // the expection will be caught by AccessServlet.doPrintingRequest()
6268: if (permissionCount == 0) {
6269: throw new PermissionException("", "", "");
6270: } else {
6271: // Get the type of schedule (daily, weekly, etc.)
6272: int scheduleType = getScheduleTypeFromParameterList(parameters);
6273:
6274: // Now get the time range.
6275: TimeRange timeRange = getTimeRangeFromParameters(parameters);
6276:
6277: Document document = docBuilder.newDocument();
6278:
6279: generateXMLDocument(scheduleType, document, timeRange,
6280: getDailyStartTimeFromParameters(parameters),
6281: calendarReferenceList, userName);
6282:
6283: generatePDF(document,
6284: getXSLFileNameForScheduleType(scheduleType), os);
6285:
6286: // Return the content type so that it can be set in the response.
6287: if (contentType != null) {
6288: contentType.setLength(0);
6289: contentType.append(PDF_MIME_TYPE);
6290: }
6291: }
6292: }
6293:
6294: /**
6295: * The time ranges that we get from the CalendarAction class have days in the week of the first and last weeks padded out to make a full week. This function will shrink this range to only one month.
6296: */
6297: protected TimeRange shrinkTimeRangeToCurrentMonth(
6298: TimeRange expandedTimeRange) {
6299: long millisecondsInWeek = (7 * MILLISECONDS_IN_DAY);
6300:
6301: Time startTime = expandedTimeRange.firstTime();
6302:
6303: // Grab something in the middle of the time range so that we know that we're
6304: // in the right month.
6305: Time somewhereInTheMonthTime = TimeService.newTime(startTime
6306: .getTime()
6307: + 2 * millisecondsInWeek);
6308:
6309: TimeBreakdown somewhereInTheMonthBreakdown = somewhereInTheMonthTime
6310: .breakdownLocal();
6311:
6312: CalendarUtil calendar = new CalendarUtil();
6313:
6314: calendar.setDay(somewhereInTheMonthBreakdown.getYear(),
6315: somewhereInTheMonthBreakdown.getMonth(),
6316: somewhereInTheMonthBreakdown.getDay());
6317:
6318: int numDaysInMonth = calendar.getNumberOfDays();
6319:
6320: //
6321: // Construct a new time range starting on the first day of the month and ending on
6322: // the last day at one millisecond before midnight.
6323: //
6324: return TimeService.newTimeRange(
6325: TimeService.newTimeLocal(somewhereInTheMonthBreakdown
6326: .getYear(), somewhereInTheMonthBreakdown
6327: .getMonth(), 1, 0, 0, 0, 0),
6328: TimeService.newTimeLocal(somewhereInTheMonthBreakdown
6329: .getYear(), somewhereInTheMonthBreakdown
6330: .getMonth(), numDaysInMonth, 23, 59, 59, 999));
6331: }
6332:
6333: /**
6334: * Calculate the number of days in a range of time given two dates.
6335: *
6336: * @param startMonth
6337: * (zero based, 0-11)
6338: * @param startDay
6339: * (one based, 1-31)
6340: * @param endYear
6341: * (one based, 1-31)
6342: * @param endMonth
6343: * (zero based, 0-11
6344: */
6345: protected long getNumberDaysGivenTwoDates(int startYear,
6346: int startMonth, int startDay, int endYear, int endMonth,
6347: int endDay) {
6348: GregorianCalendar startDate = new GregorianCalendar();
6349: GregorianCalendar endDate = new GregorianCalendar();
6350:
6351: startDate.set(startYear, startMonth, startDay, 0, 0, 0);
6352: endDate.set(endYear, endMonth, endDay, 0, 0, 0);
6353:
6354: long duration = endDate.getTime().getTime()
6355: - startDate.getTime().getTime();
6356:
6357: // Allow for daylight savings time.
6358: return ((duration + MILLISECONDS_IN_HOUR) / (24 * MILLISECONDS_IN_HOUR)) + 1;
6359: }
6360:
6361: /**
6362: * Returns a list of daily time ranges for every day in a range.
6363: *
6364: * @param timeRange
6365: * overall time range
6366: * @param dailyTimeRange
6367: * representative daily time range (start hour/minute, end hour/minute). If null, this parameter is ignored.
6368: */
6369: protected ArrayList splitTimeRangeIntoListOfSingleDayTimeRanges(
6370: TimeRange timeRange, TimeRange dailyTimeRange) {
6371:
6372: TimeBreakdown startBreakdown = timeRange.firstTime()
6373: .breakdownLocal();
6374: TimeBreakdown endBreakdown = timeRange.lastTime()
6375: .breakdownLocal();
6376:
6377: GregorianCalendar startCalendarDate = new GregorianCalendar();
6378: startCalendarDate.set(startBreakdown.getYear(), startBreakdown
6379: .getMonth() - 1, startBreakdown.getDay(), 0, 0, 0);
6380:
6381: long numDaysInTimeRange = getNumberDaysGivenTwoDates(
6382: startBreakdown.getYear(),
6383: startBreakdown.getMonth() - 1, startBreakdown.getDay(),
6384: endBreakdown.getYear(), endBreakdown.getMonth() - 1,
6385: endBreakdown.getDay());
6386:
6387: ArrayList splitTimeRanges = new ArrayList();
6388:
6389: TimeBreakdown dailyStartBreakDown = null;
6390: TimeBreakdown dailyEndBreakDown = null;
6391:
6392: if (dailyTimeRange != null) {
6393: dailyStartBreakDown = dailyTimeRange.firstTime()
6394: .breakdownLocal();
6395: dailyEndBreakDown = dailyTimeRange.lastTime()
6396: .breakdownLocal();
6397: }
6398:
6399: for (long i = 0; i < numDaysInTimeRange; i++) {
6400: Time curStartTime = null;
6401: Time curEndTime = null;
6402:
6403: if (dailyTimeRange != null) {
6404: //
6405: // Use the same start/end times for all days.
6406: //
6407: curStartTime = TimeService
6408: .newTimeLocal(
6409: startCalendarDate
6410: .get(GregorianCalendar.YEAR),
6411: startCalendarDate
6412: .get(GregorianCalendar.MONTH) + 1,
6413: startCalendarDate
6414: .get(GregorianCalendar.DAY_OF_MONTH),
6415: dailyStartBreakDown.getHour(),
6416: dailyStartBreakDown.getMin(),
6417: dailyStartBreakDown.getSec(),
6418: dailyStartBreakDown.getMs());
6419:
6420: curEndTime = TimeService.newTimeLocal(startCalendarDate
6421: .get(GregorianCalendar.YEAR), startCalendarDate
6422: .get(GregorianCalendar.MONTH) + 1,
6423: startCalendarDate
6424: .get(GregorianCalendar.DAY_OF_MONTH),
6425: dailyEndBreakDown.getHour(), dailyEndBreakDown
6426: .getMin(), dailyEndBreakDown.getSec(),
6427: dailyEndBreakDown.getMs());
6428:
6429: splitTimeRanges.add(TimeService.newTimeRange(
6430: curStartTime, curEndTime, true, false));
6431: } else {
6432: //
6433: // Add a full day range since no start/stop time was specified.
6434: //
6435: splitTimeRanges
6436: .add(getFullDayTimeRangeFromYMD(
6437: startCalendarDate
6438: .get(GregorianCalendar.YEAR),
6439: startCalendarDate
6440: .get(GregorianCalendar.MONTH) + 1,
6441: startCalendarDate
6442: .get(GregorianCalendar.DAY_OF_MONTH)));
6443: }
6444:
6445: // Move to the next day.
6446: startCalendarDate.add(GregorianCalendar.DATE, 1);
6447: }
6448:
6449: return splitTimeRanges;
6450: }
6451:
6452: /**
6453: * Utility routine to write a string node to the DOM.
6454: */
6455: protected Element writeStringNodeToDom(Document doc,
6456: Element parent, String nodeName, String nodeValue) {
6457: if (nodeValue != null && nodeValue.length() != 0) {
6458: Element name = doc.createElement(nodeName);
6459: name.appendChild(doc.createTextNode(nodeValue));
6460: parent.appendChild(name);
6461: return name;
6462: }
6463:
6464: return null;
6465: }
6466:
6467: /**
6468: ** Internal class for resolving stylesheet URIs
6469: **/
6470: protected class MyURIResolver implements URIResolver {
6471: ClassLoader classLoader = null;
6472:
6473: /**
6474: ** Constructor: use BaseCalendarService ClassLoader
6475: **/
6476: public MyURIResolver(ClassLoader classLoader) {
6477: this .classLoader = classLoader;
6478: }
6479:
6480: /**
6481: ** Resolve XSLT pathnames invoked within stylesheet (e.g. xsl:import)
6482: ** using ClassLoader.
6483: **
6484: ** @param href href attribute of XSLT file
6485: ** @param base base URI in affect when href attribute encountered
6486: ** @return Source object for requested XSLT file
6487: **/
6488: public Source resolve(String href, String base)
6489: throws TransformerException {
6490: InputStream in = classLoader.getResourceAsStream(href);
6491: return (Source) (new StreamSource(in));
6492: }
6493: }
6494:
6495: } // BaseCalendarService
|