/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/calendar/trunk/calendar-impl/impl/src/java/org/sakaiproject/calendar/impl/BaseCalendarService.java $ * $Id: BaseCalendarService.java 132688 2013-12-17 19:50:28Z enietzel@anisakai.com $ *********************************************************************************** * * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009 The Sakai Foundation * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.opensource.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * **********************************************************************************/ package org.sakaiproject.calendar.impl; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import java.text.DateFormat; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.Enumeration; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Observable; import java.util.Properties; import java.util.Set; import java.util.Stack; import java.util.TimeZone; import java.util.Vector; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.URIResolver; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.sax.SAXResult; import javax.xml.transform.stream.StreamSource; import net.fortuna.ical4j.data.CalendarOutputter; import net.fortuna.ical4j.model.DateTime; import net.fortuna.ical4j.model.Property; import net.fortuna.ical4j.model.TimeZoneRegistry; import net.fortuna.ical4j.model.TimeZoneRegistryFactory; import net.fortuna.ical4j.model.component.VEvent; import net.fortuna.ical4j.model.parameter.Value; import net.fortuna.ical4j.model.property.CalScale; import net.fortuna.ical4j.model.property.Comment; import net.fortuna.ical4j.model.property.Description; import net.fortuna.ical4j.model.property.Location; import net.fortuna.ical4j.model.property.Organizer; import net.fortuna.ical4j.model.property.ProdId; import net.fortuna.ical4j.model.property.TzId; import net.fortuna.ical4j.model.property.Uid; import net.fortuna.ical4j.model.property.Version; import org.apache.avalon.framework.logger.ConsoleLogger; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.fop.apps.Driver; import org.apache.fop.apps.FOPException; import org.apache.fop.apps.Options; import org.apache.fop.configuration.Configuration; import org.apache.fop.messaging.MessageHandler; import org.sakaiproject.alias.api.Alias; import org.sakaiproject.alias.api.AliasService; import org.sakaiproject.authz.api.AuthzGroupService; import org.sakaiproject.authz.api.AuthzPermissionException; import org.sakaiproject.authz.api.FunctionManager; import org.sakaiproject.authz.api.GroupNotDefinedException; import org.sakaiproject.authz.api.SecurityService; import org.sakaiproject.calendar.api.Calendar; import org.sakaiproject.calendar.api.CalendarEdit; import org.sakaiproject.calendar.api.CalendarEvent; import org.sakaiproject.calendar.api.CalendarEventEdit; import org.sakaiproject.calendar.api.CalendarEventVector; import org.sakaiproject.calendar.api.CalendarService; import org.sakaiproject.calendar.api.RecurrenceRule; import org.sakaiproject.calendar.api.CalendarEvent.EventAccess; import org.sakaiproject.calendar.cover.ExternalCalendarSubscriptionService; import org.sakaiproject.util.CalendarUtil; import org.sakaiproject.component.api.ServerConfigurationService; import org.sakaiproject.component.cover.ComponentManager; import org.sakaiproject.content.api.ContentHostingService; import org.sakaiproject.content.api.ContentResource; import org.sakaiproject.entity.api.ContextObserver; import org.sakaiproject.entity.api.Edit; import org.sakaiproject.entity.api.Entity; import org.sakaiproject.entity.api.EntityAccessOverloadException; import org.sakaiproject.entity.api.EntityCopyrightException; import org.sakaiproject.entity.api.EntityManager; import org.sakaiproject.entity.api.EntityNotDefinedException; import org.sakaiproject.entity.api.EntityPermissionException; import org.sakaiproject.entity.api.EntityTransferrer; import org.sakaiproject.entity.api.EntityTransferrerRefMigrator; import org.sakaiproject.entity.api.HttpAccess; import org.sakaiproject.entity.api.Reference; import org.sakaiproject.entity.api.ResourceProperties; import org.sakaiproject.entity.api.ResourcePropertiesEdit; import org.sakaiproject.event.api.Event; import org.sakaiproject.event.api.EventTrackingService; import org.sakaiproject.event.api.NotificationService; import org.sakaiproject.exception.IdInvalidException; import org.sakaiproject.exception.IdUnusedException; import org.sakaiproject.exception.IdUsedException; import org.sakaiproject.exception.InUseException; import org.sakaiproject.exception.PermissionException; import org.sakaiproject.id.api.IdManager; import org.sakaiproject.javax.Filter; import org.sakaiproject.memory.api.Cache; import org.sakaiproject.memory.api.CacheRefresher; import org.sakaiproject.memory.api.MemoryService; import org.sakaiproject.site.api.Group; import org.sakaiproject.site.api.Site; import org.sakaiproject.site.api.SiteService; import org.sakaiproject.thread_local.api.ThreadLocalManager; import org.sakaiproject.time.api.Time; import org.sakaiproject.time.api.TimeBreakdown; import org.sakaiproject.time.api.TimeRange; import org.sakaiproject.time.api.TimeService; import org.sakaiproject.tool.api.SessionBindingEvent; import org.sakaiproject.tool.api.SessionBindingListener; import org.sakaiproject.tool.api.SessionManager; import org.sakaiproject.tool.api.ToolManager; import org.sakaiproject.user.api.UserDirectoryService; import org.sakaiproject.user.api.UserNotDefinedException; import org.sakaiproject.util.BaseResourcePropertiesEdit; import org.sakaiproject.util.CalendarUtil; import org.sakaiproject.util.DefaultEntityHandler; import org.sakaiproject.util.DoubleStorageUser; import org.sakaiproject.util.EntityCollections; import org.sakaiproject.util.FormattedText; import org.sakaiproject.util.ResourceLoader; import org.sakaiproject.util.SAXEntityReader; import org.sakaiproject.util.Validator; import org.sakaiproject.util.Web; import org.sakaiproject.util.Xml; import org.sakaiproject.util.cover.LinkMigrationHelper; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import java.util.Map.Entry; /** * <p> * BaseCalendarService is an base implementation of the CalendarService. Extension classes implement object creation, access and storage. * </p> */ public abstract class BaseCalendarService implements CalendarService, DoubleStorageUser, CacheRefresher, ContextObserver, EntityTransferrer, SAXEntityReader, EntityTransferrerRefMigrator { /** Our logger. */ private static Log M_log = LogFactory.getLog(BaseCalendarService.class); /** The initial portion of a relative access point URL. */ protected String m_relativeAccessPoint = null; /** A Cache object for caching: calendars keyed by reference. */ protected Cache m_calendarCache = null; /** A bunch of caches for events: keyed by calendar id, the cache is keyed by event reference. */ protected Hashtable m_eventCaches = null; /** A Storage object for access to calendars and events. */ protected Storage m_storage = null; /** DELIMETER used to separate the list of custom fields for this calendar. */ private final static String ADDFIELDS_DELIMITER = "_,_"; /** Security lock / event root for generic message events to make it a mail event. */ public static final String SECURE_SCHEDULE_ROOT = "calendar."; private TransformerFactory transformerFactory = null; private DocumentBuilder docBuilder = null; private ResourceLoader rb = new ResourceLoader("calendar"); private ContentHostingService contentHostingService; private GroupComparator groupComparator = new GroupComparator(); /** * Access this service from the inner classes. */ protected BaseCalendarService service() { return this; } /** * Construct a Storage object. * * @return The new storage object. */ protected abstract Storage newStorage(); /** * Access the partial URL that forms the root of calendar URLs. * * @param relative * if true, form within the access path only (i.e. starting with /content) * @return the partial URL that forms the root of calendar URLs. */ protected String getAccessPoint(boolean relative) { return (relative ? "" : m_serverConfigurationService.getAccessUrl()) + m_relativeAccessPoint; } // getAccessPoint /** * Check security permission. * * @param lock * The lock id string. * @param reference * The resource's reference string, or null if no resource is involved. * @return true if permitted, false if not. */ protected boolean unlockCheck(String lock, String reference) { return m_securityService.unlock(lock, reference); } // unlockCheck /** * Check security permission. * * @param lock * The lock id string. * @param reference * The resource's reference string, or null if no resource is involved. * @exception PermissionException * thrown if the user does not have access */ protected void unlock(String lock, String reference) throws PermissionException { // check if publicly accessible via export if ( getExportEnabled(reference) && lock.equals(AUTH_READ_CALENDAR) ) return; // otherwise check permissions else if (!m_securityService.unlock(lock, reference)) throw new PermissionException(m_sessionManager.getCurrentSessionUserId(), lock, reference); } // unlock /** * Access the internal reference which can be used to access the calendar from within the system. * * @param context * The context. * @param id * The calendar id. * @return The the internal reference which can be used to access the calendar from within the system. */ public String calendarReference(String context, String id) { return getAccessPoint(true) + Entity.SEPARATOR + REF_TYPE_CALENDAR + Entity.SEPARATOR + context + Entity.SEPARATOR + id; } // calendarReference /** * @inheritDoc */ public String calendarPdfReference(String context, String id, int scheduleType, String timeRangeString, String userName, TimeRange dailyTimeRange) { return getAccessPoint(true) + Entity.SEPARATOR + REF_TYPE_CALENDAR_PDF + Entity.SEPARATOR + context + Entity.SEPARATOR + id + "?" + SCHEDULE_TYPE_PARAMETER_NAME + "=" + Validator.escapeHtml(Integer.valueOf(scheduleType).toString()) + "&" + TIME_RANGE_PARAMETER_NAME + "=" + timeRangeString + "&" + Validator.escapeHtml(USER_NAME_PARAMETER_NAME) + "=" + Validator.escapeUrl(userName) + "&" + DAILY_START_TIME_PARAMETER_NAME + "=" + Validator.escapeHtml(dailyTimeRange.toString()); } /** * @inheritDoc */ public String calendarICalReference(Reference ref) { String context = ref.getContext(); String id = ref.getId(); String alias = null; List aliasList = m_aliasService.getAliases( ref.getReference() ); if ( ! aliasList.isEmpty() ) alias = ((Alias)aliasList.get(0)).getId(); if ( alias != null) return getAccessPoint(true) + Entity.SEPARATOR + REF_TYPE_CALENDAR_ICAL + Entity.SEPARATOR + alias; else return getAccessPoint(true) + Entity.SEPARATOR + REF_TYPE_CALENDAR_ICAL + Entity.SEPARATOR + context + Entity.SEPARATOR + id; } /** * @inheritDoc */ public String calendarSubscriptionReference(String context, String id) { return getAccessPoint(true) + Entity.SEPARATOR + REF_TYPE_CALENDAR_SUBSCRIPTION + Entity.SEPARATOR + context + Entity.SEPARATOR + id; } /** * @inheritDoc */ public boolean getExportEnabled(String ref) { Calendar cal = findCalendar(ref); if ( cal == null ) return false; else return cal.getExportEnabled(); } /** * @inheritDoc */ public void setExportEnabled(String ref, boolean enable) { try { CalendarEdit cal = editCalendar(ref); cal.setExportEnabled(enable); commitCalendar(cal); } catch ( Exception e) { M_log.warn("setExportEnabled(): ", e); } } /** * Access the internal reference which can be used to access the event from within the system. * * @param context * The context. * @param calendarId * The calendar id. * @param id * The event id. * @return The the internal reference which can be used to access the event from within the system. */ public String eventReference(String context, String calendarId, String id) { return getAccessPoint(true) + Entity.SEPARATOR + REF_TYPE_EVENT + Entity.SEPARATOR + context + Entity.SEPARATOR + calendarId + Entity.SEPARATOR + id; } // eventReference /** * Access the internal reference which can be used to access the subscripted event from within the system. * * @param context * The context. * @param calendarId * The calendar id. * @param id * The event id. * @return The the internal reference which can be used to access the subscripted event from within the system. */ public String eventSubscriptionReference(String context, String calendarId, String id) { return getAccessPoint(true) + Entity.SEPARATOR + REF_TYPE_EVENT_SUBSCRIPTION + Entity.SEPARATOR + context + Entity.SEPARATOR + calendarId + Entity.SEPARATOR + id; } // eventSubscriptionReference /** * Takes several calendar References and merges their events from within a given time range. * * @param references * The List of calendar References. * @param range * The time period to use to select events. * @return CalendarEventVector object with the union of all events from the list of calendars in the given time range. */ public CalendarEventVector getEvents(List references, TimeRange range) { CalendarEventVector calendarEventVector = null; if (references != null && range != null) { List allEvents = new ArrayList(); Iterator it = references.iterator(); // Add the events for each calendar in our list. while (it.hasNext()) { String calendarReference = (String) it.next(); Calendar calendarObj = null; try { calendarObj = getCalendar(calendarReference); } catch (IdUnusedException e) { continue; } catch (PermissionException e) { continue; } if (calendarObj != null) { Iterator calEvent = null; try { calEvent = calendarObj.getEvents(range, null).iterator(); } catch (PermissionException e1) { continue; } allEvents.addAll(new CalendarEventVector(calEvent)); } } // Do a sort since each of the events implements the Comparable interface. Collections.sort(allEvents); // Build up a CalendarEventVector and return it. calendarEventVector = new CalendarEventVector(allEvents.iterator()); } return calendarEventVector; } /** * Form a tracking event string based on a security function string. * @param secure The security function string. * @return The event tracking string. */ protected String eventId(String secure) { return SECURE_SCHEDULE_ROOT + secure; } // eventId /** * Access the id generating service and return a unique id. * * @return a unique id. */ protected String getUniqueId() { return m_idManager.createUuid(); } /********************************************************************************************************************************************************************************************************************************************************** * Constructors, Dependencies and their setter methods *********************************************************************************************************************************************************************************************************************************************************/ /** Dependency: MemoryService. */ protected MemoryService m_memoryService = null; /** * Dependency: MemoryService. * * @param service * The MemoryService. */ public void setMemoryService(MemoryService service) { m_memoryService = service; } /** Dependency: IdManager. */ protected IdManager m_idManager = null; /** * Dependency: IdManager. * * @param manager * The IdManager. */ public void setIdManager(IdManager manager) { m_idManager = manager; } /** Configuration: cache, or not. */ protected boolean m_caching = false; /** * Configuration: set the caching * * @param path * The storage path. */ public void setCaching(String value) { m_caching = Boolean.valueOf(value).booleanValue(); } /** Dependency: EntityManager. */ protected EntityManager m_entityManager = null; /** * Dependency: EntityManager. * * @param service * The EntityManager. */ public void setEntityManager(EntityManager service) { m_entityManager = service; } /** Dependency: ServerConfigurationService. */ protected ServerConfigurationService m_serverConfigurationService = null; /** * Dependency: ServerConfigurationService. * * @param service * The ServerConfigurationService. */ public void setServerConfigurationService(ServerConfigurationService service) { m_serverConfigurationService = service; } /** Dependency: AliasService. */ protected AliasService m_aliasService = null; /** Dependency: SiteService. */ protected SiteService m_siteService = null; /** Dependency: AuthzGroupService */ protected AuthzGroupService m_authzGroupService = null; /** Dependency: FunctionManager */ protected FunctionManager m_functionManager = null; /** Dependency: SecurityService */ protected SecurityService m_securityService = null; /** Dependency: EventTrackingService */ protected EventTrackingService m_eventTrackingService = null; /** Depedency: SessionManager */ protected SessionManager m_sessionManager = null; /** Dependency: ThreadLocalManager */ protected ThreadLocalManager m_threadLocalManager = null; /** Dependency: TimeService */ protected TimeService m_timeService = null; /** Dependency: ToolManager */ protected ToolManager m_toolManager = null; /** Dependency: UserDirectoryService */ protected UserDirectoryService m_userDirectoryService = null; /** A map of services used in SAX serialization */ private Map<String, Object> m_services; /** * Dependency: AliasService. * * @param service * The AliasService. */ public void setAliasService(AliasService service) { m_aliasService = service; } /** * Dependency: SiteService. * * @param service * The SiteService. */ public void setSiteService(SiteService service) { m_siteService = service; } /** * Dependency: AuthzGroupService. * * @param authzGroupService * The AuthzGroupService. */ public void setAuthzGroupService(AuthzGroupService authzGroupService) { m_authzGroupService = authzGroupService; } /** * Dependency: FunctionManager. * * @param functionManager * The FunctionManager. */ public void setFunctionManager(FunctionManager functionManager) { m_functionManager = functionManager; } /** * Dependency: SecurityService. * * @param securityService * The SecurityService. */ public void setSecurityService(SecurityService securityService) { m_securityService = securityService; } /** * Dependency: EventTrackingService. * @param eventTrackingService * The EventTrackingService. */ public void setEventTrackingService(EventTrackingService eventTrackingService) { this.m_eventTrackingService = eventTrackingService; } /** * Dependency: SessionManager. * @param sessionManager * The SessionManager. */ public void setSessionManager(SessionManager sessionManager) { this.m_sessionManager = sessionManager; } /** * Dependency: ThreadLocalManager. * @param threadLocalManager * The ThreadLocalManager. */ public void setThreadLocalManager(ThreadLocalManager threadLocalManager) { this.m_threadLocalManager = threadLocalManager; } /** * Dependency: TimeService. * @param timeService * The TimeService. */ public void setTimeService(TimeService timeService) { this.m_timeService = timeService; } /** * Dependency: ToolManager. * @param toolManager * The ToolManager. */ public void setToolManager(ToolManager toolManager) { this.m_toolManager = toolManager; } /** * Dependency: UserDirectoryService. * @param userDirectoryService * The UserDirectoryService. */ public void setUserDirectoryService(UserDirectoryService userDirectoryService) { this.m_userDirectoryService = userDirectoryService; } /********************************************************************************************************************************************************************************************************************************************************** * Init and Destroy *********************************************************************************************************************************************************************************************************************************************************/ /** * Final initialization, once all dependencies are set. */ public void init() { contentHostingService = (ContentHostingService) ComponentManager.get("org.sakaiproject.content.api.ContentHostingService"); try { m_relativeAccessPoint = REFERENCE_ROOT; // construct a storage helper and read m_storage = newStorage(); m_storage.open(); // make the calendar cache if (m_caching) { m_calendarCache = m_memoryService .newCache( "org.sakaiproject.calendar.api.CalendarService.calendarCache", this, getAccessPoint(true) + Entity.SEPARATOR + REF_TYPE_CALENDAR + Entity.SEPARATOR); // make the table to hold the event caches m_eventCaches = new Hashtable(); } // create transformerFactory object needed by generatePDF transformerFactory = TransformerFactory.newInstance(); transformerFactory.setURIResolver( new MyURIResolver(getClass().getClassLoader()) ); // create DocumentBuilder object needed by printSchedule docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); M_log.info("init(): caching: " + m_caching); } catch (Throwable t) { M_log.warn("init(): ", t); } // register as an entity producer m_entityManager.registerEntityProducer(this, REFERENCE_ROOT); // register functions m_functionManager.registerFunction(AUTH_ADD_CALENDAR); m_functionManager.registerFunction(AUTH_REMOVE_CALENDAR_OWN); m_functionManager.registerFunction(AUTH_REMOVE_CALENDAR_ANY); m_functionManager.registerFunction(AUTH_MODIFY_CALENDAR_OWN); m_functionManager.registerFunction(AUTH_MODIFY_CALENDAR_ANY); m_functionManager.registerFunction(AUTH_IMPORT_CALENDAR); m_functionManager.registerFunction(AUTH_SUBSCRIBE_CALENDAR); m_functionManager.registerFunction(AUTH_READ_CALENDAR); m_functionManager.registerFunction(AUTH_ALL_GROUPS_CALENDAR); m_functionManager.registerFunction(AUTH_OPTIONS_CALENDAR); } /** * Returns to uninitialized state. */ public void destroy() { if (m_caching) { m_calendarCache.destroy(); m_calendarCache = null; // TODO: destroy each cache m_eventCaches.clear(); m_eventCaches = null; } m_storage.close(); m_storage = null; M_log.info("destroy()"); } /********************************************************************************************************************************************************************************************************************************************************** * CalendarService implementation *********************************************************************************************************************************************************************************************************************************************************/ /** * Add a new calendar. Must commitCalendar() to make official, or cancelCalendar() when done! * * @param ref * A reference for the calendar. * @return The newly created calendar. * @exception IdUsedException * if the id is not unique. * @exception IdInvalidException * if the id is not made up of valid characters. */ public CalendarEdit addCalendar(String ref) throws IdUsedException, IdInvalidException { // check the name's validity if (!m_entityManager.checkReference(ref)) throw new IdInvalidException(ref); // check for existance if (m_storage.checkCalendar(ref)) { throw new IdUsedException(ref); } // keep it CalendarEdit calendar = m_storage.putCalendar(ref); ((BaseCalendarEdit) calendar).setEvent(EVENT_CREATE_CALENDAR); return calendar; } // addCalendar /** * check permissions for getCalendar(). * * @param ref * The calendar reference. * @return true if the user is allowed to getCalendar(calendarId), false if not. */ public boolean allowGetCalendar(String ref) { if(REF_TYPE_CALENDAR_SUBSCRIPTION.equals(m_entityManager.newReference(ref).getSubType())) return true; return unlockCheck(AUTH_READ_CALENDAR, ref); } // allowGetCalendar /** * Find the calendar, in cache or info store - cache it if newly found. * * @param ref * The calendar reference. * @return The calendar, if found. */ protected Calendar findCalendar(String ref) { Calendar calendar = null; if ((!m_caching) || (m_calendarCache == null) || (m_calendarCache.disabled())) { // TODO: do we really want to do this? -ggolden // if we have done this already in this thread, use that calendar = (Calendar) m_threadLocalManager.get(ref); if (calendar == null) { calendar = m_storage.getCalendar(ref); // "cache" the calendar in the current service in case they are needed again in this thread... if (calendar != null) { m_threadLocalManager.set(ref, calendar); } } return calendar; } // if we have it cached, use it (even if it's cached as a null, a miss) if (m_calendarCache.containsKey(ref)) { calendar = (Calendar) m_calendarCache.get(ref); } // if not in the cache, see if we have it in our info store if ( calendar == null ) //SAK-12447 cache.get can return null on expired { calendar = m_storage.getCalendar(ref); // if so, cache it, even misses m_calendarCache.put(ref, calendar); } return calendar; } // findCalendar /** * Return a specific calendar. * * @param ref * The calendar reference. * @return the Calendar that has the specified name. * @exception IdUnusedException * If this name is not defined for any calendar. * @exception PermissionException * If the user does not have any permissions to the calendar. */ public Calendar getCalendar(String ref) throws IdUnusedException, PermissionException { Reference _ref = m_entityManager.newReference(ref); if(REF_TYPE_CALENDAR_SUBSCRIPTION.equals(_ref.getSubType())) { Calendar c = ExternalCalendarSubscriptionService.getCalendarSubscription(ref); if (c == null) throw new IdUnusedException(ref); return c; } Calendar c = findCalendar(ref); if (c == null) throw new IdUnusedException(ref); // check security (throws if not permitted) unlock(AUTH_READ_CALENDAR, ref); return c; } // getCalendar /** * Remove a calendar that is locked for edit. * * @param calendar * The calendar to remove. * @exception PermissionException * if the user does not have permission to remove a calendar. */ public void removeCalendar(CalendarEdit calendar) throws PermissionException { // check for closed edit if (!calendar.isActiveEdit()) { try { throw new Exception(); } catch (Exception e) { M_log.warn("removeCalendar(): closed CalendarEdit", e); } return; } // check security unlock(AUTH_REMOVE_CALENDAR_ANY, calendar.getReference()); m_storage.removeCalendar(calendar); // track event Event event = m_eventTrackingService.newEvent(EVENT_REMOVE_CALENDAR, calendar.getReference(), true); m_eventTrackingService.post(event); // mark the calendar as removed ((BaseCalendarEdit) calendar).setRemoved(event); // close the edit object ((BaseCalendarEdit) calendar).closeEdit(); // remove any realm defined for this resource try { m_authzGroupService.removeAuthzGroup(m_authzGroupService.getAuthzGroup(calendar.getReference())); } catch (AuthzPermissionException e) { M_log.warn("removeCalendar: removing realm for : " + calendar.getReference() + " : " + e); } catch (GroupNotDefinedException ignore) { } } // removeCalendar /** * Return a List of all the defined calendars. * * @return a List of Calendar objects (may be empty) */ public List getCalendars() { List calendars = new Vector(); if ((!m_caching) || (m_calendarCache == null) || (m_calendarCache.disabled())) { calendars = m_storage.getCalendars(); return calendars; } // if the cache is complete, use it if (m_calendarCache.isComplete()) { // get just the calendars in the cache calendars = m_calendarCache.getAll(); } // otherwise get all the calendars from storage else { // Note: while we are getting from storage, storage might change. These can be processed // after we get the storage entries, and put them in the cache, and mark the cache complete. // -ggolden synchronized (m_calendarCache) { // if we were waiting and it's now complete... if (m_calendarCache.isComplete()) { // get just the calendars in the cache calendars = m_calendarCache.getAll(); return calendars; } // save up any events to the cache until we get past this load m_calendarCache.holdEvents(); calendars = m_storage.getCalendars(); // update the cache, and mark it complete for (int i = 0; i < calendars.size(); i++) { Calendar calendar = (Calendar) calendars.get(i); m_calendarCache.put(calendar.getReference(), calendar); } m_calendarCache.setComplete(); // now we are complete, process any cached events m_calendarCache.processEvents(); } } return calendars; } // getCalendars /** * check permissions for importing calendar events * * @param ref * The calendar reference. * @return true if the user is allowed to import events, false if not. */ public boolean allowImportCalendar(String ref) { return unlockCheck(AUTH_IMPORT_CALENDAR, ref); } // allowImportCalendar /** * check permissions for subscribing external calendars * * @param ref * The calendar reference. * @return true if the user is allowed to subscribe external calendars, false if not. */ public boolean allowSubscribeCalendar(String ref) { return unlockCheck(AUTH_SUBSCRIBE_CALENDAR, ref); } // allowSubscribeCalendar /** * check permissions for editCalendar() * * @param ref * The calendar reference. * @return true if the user is allowed to update the calendar, false if not. */ public boolean allowEditCalendar(String ref) { return unlockCheck(AUTH_MODIFY_CALENDAR_ANY, ref); } // allowEditCalendar /** * check permissions for merge() * @param ref The calendar reference. * @return true if the user is allowed to update the calendar, false if not. */ public boolean allowMergeCalendar(String ref) { String displayMerge = getString("calendar.merge.display", "1"); if(displayMerge != null && !displayMerge.equals("1")) return false; return unlockCheck(AUTH_MODIFY_CALENDAR_ANY, ref); } // allowMergeCalendar /** * Get a locked calendar object for editing. Must commitCalendar() to make official, or cancelCalendar() or removeCalendar() when done! * * @param ref * The calendar reference. * @return A CalendarEdit object for editing. * @exception IdUnusedException * if not found, or if not an CalendarEdit object * @exception PermissionException * if the current user does not have permission to mess with this user. * @exception InUseException * if the Calendar object is locked by someone else. */ public CalendarEdit editCalendar(String ref) throws IdUnusedException, PermissionException, InUseException { // check for existance if (!m_storage.checkCalendar(ref)) { throw new IdUnusedException(ref); } // check security (throws if not permitted) unlock(AUTH_MODIFY_CALENDAR_ANY, ref); // ignore the cache - get the calendar with a lock from the info store CalendarEdit edit = m_storage.editCalendar(ref); if (edit == null) throw new InUseException(ref); ((BaseCalendarEdit) edit).setEvent(EVENT_MODIFY_CALENDAR); return edit; } // editCalendar /** * Commit the changes made to a CalendarEdit object, and release the lock. The CalendarEdit is disabled, and not to be used after this call. * * @param edit * The CalendarEdit object to commit. */ public void commitCalendar(CalendarEdit edit) { // check for closed edit if (!edit.isActiveEdit()) { M_log.warn("commitCalendar(): closed CalendarEdit " + edit.getContext()); return; } m_storage.commitCalendar(edit); // track event Event event = m_eventTrackingService.newEvent(((BaseCalendarEdit) edit).getEvent(), edit.getReference(), true); m_eventTrackingService.post(event); // close the edit object ((BaseCalendarEdit) edit).closeEdit(); } // commitCalendar /** * Cancel the changes made to a CalendarEdit object, and release the lock. The CalendarEdit is disabled, and not to be used after this call. * * @param edit * The CalendarEdit object to commit. */ public void cancelCalendar(CalendarEdit edit) { // check for closed edit if (!edit.isActiveEdit()) { M_log.warn("cancelCalendar(): closed CalendarEventEdit " + edit.getContext()); return; } // release the edit lock m_storage.cancelCalendar(edit); // close the edit object ((BaseCalendarEdit) edit).closeEdit(); } // cancelCalendar /** * {@inheritDoc} */ public RecurrenceRule newRecurrence(String frequency) { if (frequency.equals(DailyRecurrenceRule.FREQ)) { return new DailyRecurrenceRule(); } else if (frequency.equals(WeeklyRecurrenceRule.FREQ)) { return new WeeklyRecurrenceRule(); } else if (frequency.equals(TThRecurrenceRule.FREQ)) { return new TThRecurrenceRule(); } else if (frequency.equals(MWFRecurrenceRule.FREQ)) { return new MWFRecurrenceRule(); } else if (frequency.equals(MonthlyRecurrenceRule.FREQ)) { return new MonthlyRecurrenceRule(); } else if (frequency.equals(YearlyRecurrenceRule.FREQ)) { return new YearlyRecurrenceRule(); } else if (frequency.equals(MWRecurrenceRule.FREQ)) { return new MWRecurrenceRule(); } else if (frequency.equals(SMWRecurrenceRule.FREQ)) { return new SMWRecurrenceRule(); } else if (frequency.equals(SMTWRecurrenceRule.FREQ)) { return new SMTWRecurrenceRule(); } else if (frequency.equals(STTRecurrenceRule.FREQ)) { return new STTRecurrenceRule(); } //add more here return null; } /** * {@inheritDoc} */ public RecurrenceRule newRecurrence(String frequency, int interval) { if (frequency.equals(DailyRecurrenceRule.FREQ)) { return new DailyRecurrenceRule(interval); } else if (frequency.equals(WeeklyRecurrenceRule.FREQ)) { return new WeeklyRecurrenceRule(interval); } else if (frequency.equals(TThRecurrenceRule.FREQ)) { return new TThRecurrenceRule(interval); } else if (frequency.equals(MWFRecurrenceRule.FREQ)) { return new MWFRecurrenceRule(interval); } else if (frequency.equals(MonthlyRecurrenceRule.FREQ)) { return new MonthlyRecurrenceRule(interval); } else if (frequency.equals(YearlyRecurrenceRule.FREQ)) { return new YearlyRecurrenceRule(interval); } else if (frequency.equals(MWRecurrenceRule.FREQ)) { return new MWRecurrenceRule(interval); } else if (frequency.equals(SMWRecurrenceRule.FREQ)) { return new SMWRecurrenceRule(interval); } else if (frequency.equals(SMTWRecurrenceRule.FREQ)) { return new SMTWRecurrenceRule(interval); } else if (frequency.equals(STTRecurrenceRule.FREQ)) { return new STTRecurrenceRule(interval); } //add more here return null; } /** * {@inheritDoc} */ public RecurrenceRule newRecurrence(String frequency, int interval, int count) { M_log.debug("\n"+ frequency +"\nand Internval is \n "+ interval +"count is\n " + count); if (frequency.equals(DailyRecurrenceRule.FREQ)) { return new DailyRecurrenceRule(interval, count); } else if (frequency.equals(WeeklyRecurrenceRule.FREQ)) { return new WeeklyRecurrenceRule(interval, count); } else if (frequency.equals(TThRecurrenceRule.FREQ)) { return new TThRecurrenceRule(interval,count); } else if (frequency.equals(MWFRecurrenceRule.FREQ)) { return new MWFRecurrenceRule(interval,count); } else if (frequency.equals(MonthlyRecurrenceRule.FREQ)) { return new MonthlyRecurrenceRule(interval, count); } else if (frequency.equals(YearlyRecurrenceRule.FREQ)) { return new YearlyRecurrenceRule(interval, count); } else if (frequency.equals(MWRecurrenceRule.FREQ)) { return new MWRecurrenceRule(interval, count); } else if (frequency.equals(SMWRecurrenceRule.FREQ)) { return new SMWRecurrenceRule(interval, count); } else if (frequency.equals(SMTWRecurrenceRule.FREQ)) { return new SMTWRecurrenceRule(interval, count); } else if (frequency.equals(STTRecurrenceRule.FREQ)) { return new STTRecurrenceRule(interval, count); } //add more here return null; } /** * {@inheritDoc} */ public RecurrenceRule newRecurrence(String frequency, int interval, Time until) { if (frequency.equals(DailyRecurrenceRule.FREQ)) { return new DailyRecurrenceRule(interval, until); } else if (frequency.equals(WeeklyRecurrenceRule.FREQ)) { return new WeeklyRecurrenceRule(interval, until); } else if (frequency.equals(TThRecurrenceRule.FREQ)) { return new TThRecurrenceRule(interval,until); } else if (frequency.equals(MWFRecurrenceRule.FREQ)) { return new MWFRecurrenceRule(interval,until); } else if (frequency.equals(MonthlyRecurrenceRule.FREQ)) { return new MonthlyRecurrenceRule(interval, until); } else if (frequency.equals(YearlyRecurrenceRule.FREQ)) { return new YearlyRecurrenceRule(interval, until); } else if (frequency.equals(MWRecurrenceRule.FREQ)) { return new MWRecurrenceRule(interval, until); } else if (frequency.equals(SMWRecurrenceRule.FREQ)) { return new SMWRecurrenceRule(interval, until); } else if (frequency.equals(SMTWRecurrenceRule.FREQ)) { return new SMTWRecurrenceRule(interval, until); } else if (frequency.equals(STTRecurrenceRule.FREQ)) { return new STTRecurrenceRule(interval, until); } //add more here return null; } /********************************************************************************************************************************************************************************************************************************************************** * ResourceService implementation *********************************************************************************************************************************************************************************************************************************************************/ /** * {@inheritDoc} */ public String getLabel() { return "calendar"; } /** * {@inheritDoc} */ public boolean willArchiveMerge() { return true; } /** * {@inheritDoc} */ public HttpAccess getHttpAccess() { return new HttpAccess() { public void handleAccess(HttpServletRequest req, HttpServletResponse res, Reference ref, Collection copyrightAcceptedRefs) throws EntityPermissionException, EntityNotDefinedException, EntityAccessOverloadException, EntityCopyrightException { String calRef = calendarReference(ref.getContext(), SiteService.MAIN_CONTAINER); // we only access the pdf & ical reference if ( !REF_TYPE_CALENDAR_PDF.equals(ref.getSubType()) && !REF_TYPE_CALENDAR_ICAL.equals(ref.getSubType()) ) throw new EntityNotDefinedException(ref.getReference()); // check if ical export is enabled if ( REF_TYPE_CALENDAR_ICAL.equals(ref.getSubType()) && !getExportEnabled(calRef) ) throw new EntityNotDefinedException(ref.getReference()); try { Properties options = new Properties(); Enumeration e = req.getParameterNames(); while (e.hasMoreElements()) { String key = (String) e.nextElement(); String[] values = req.getParameterValues(key); if (values.length == 1) { options.put(key, values[0]); } else { StringBuilder buf = new StringBuilder(); for (int i = 0; i < values.length; i++) { buf.append(values[i] + "^"); } options.put(key, buf.toString()); } } // We need to write to a temporary stream for better speed, plus // so we can get a byte count. Internet Explorer has problems // if we don't make the setContentLength() call. ByteArrayOutputStream outByteStream = new ByteArrayOutputStream(); // Check if PDF document requested if ( REF_TYPE_CALENDAR_PDF.equals(ref.getSubType()) ) { res.addHeader("Content-Disposition", "inline; filename=\"schedule.pdf\""); res.setContentType(PDF_MIME_TYPE); printSchedule(options, outByteStream); } else { List alias = m_aliasService.getAliases(calRef); String aliasName = "schedule.ics"; if ( ! alias.isEmpty() ) aliasName = ((Alias)alias.get(0)).getId(); // update date/time reference Time modDate = findCalendar(calRef).getModified(); if ( modDate == null ) modDate = m_timeService.newTime(0); res.addHeader("Content-Disposition", "inline; filename=\"" + Web.encodeFileName(req, aliasName) + "\""); res.setContentType(ICAL_MIME_TYPE); res.setDateHeader("Last-Modified", modDate.getTime() ); printICalSchedule(calRef, res.getOutputStream()); } res.setContentLength(outByteStream.size()); if (outByteStream.size() > 0) { // Increase the buffer size for more speed. res.setBufferSize(outByteStream.size()); } OutputStream out = null; try { out = res.getOutputStream(); if (outByteStream.size() > 0) { outByteStream.writeTo(out); } out.flush(); out.close(); } catch (Throwable ignore) { } finally { if (out != null) { try { out.close(); } catch (Throwable ignore) { } } } } catch (Throwable t) { throw new EntityNotDefinedException(ref.getReference()); } } }; } /** * {@inheritDoc} */ public boolean parseEntityReference(String reference, Reference ref) { if (reference.startsWith(CalendarService.REFERENCE_ROOT)) { String[] parts = reference.split(Entity.SEPARATOR); String subType = null; String context = null; String id = null; String container = null; // the first part will be null, then next the service, the third will be "calendar" or "event" if (parts.length > 2) { subType = parts[2]; if (REF_TYPE_CALENDAR.equals(subType) || REF_TYPE_CALENDAR_PDF.equals(subType) || REF_TYPE_CALENDAR_ICAL.equals(subType) || REF_TYPE_CALENDAR_SUBSCRIPTION.equals(subType)) { // next is the context id if (parts.length > 3) { context = parts[3]; // next is the optional calendar id if (parts.length > 4) { id = parts[4]; } } } else if (REF_TYPE_EVENT.equals(subType) || REF_TYPE_EVENT_SUBSCRIPTION.equals(subType)) { // next three parts are context, channel (container) and event id if (parts.length > 5) { context = parts[3]; container = parts[4]; id = parts[5]; } } else M_log.warn(".parseEntityReference(): unknown calendar subtype: " + subType + " in ref: " + reference); } // Translate context alias into site id if necessary if ((context != null) && (context.length() > 0)) { if (!m_siteService.siteExists(context)) { try { Calendar calendarObj = getCalendar(m_aliasService.getTarget(context)); context = calendarObj.getContext(); } catch (IdUnusedException ide) { M_log.info(".parseEntityReference():"+ide.toString()); return false; } catch (PermissionException pe) { M_log.info(".parseEntityReference():"+pe.toString()); return false; } catch (Exception e) { M_log.warn(".parseEntityReference(): ", e); return false; } } } // if context still isn't valid, then no valid alias or site was specified if (!m_siteService.siteExists(context)) { M_log.warn(".parseEntityReference() no valid site or alias: " + context); return false; } // build updated reference ref.set(APPLICATION_ID, subType, id, container, context); return true; } return false; } /** * {@inheritDoc} */ public String getEntityDescription(Reference ref) { // double check that it's mine if (!APPLICATION_ID.equals(ref.getType())) return null; String rv = "Calendar: " + ref.getReference(); try { // if this is a calendar if (REF_TYPE_CALENDAR.equals(ref.getSubType()) || REF_TYPE_CALENDAR_PDF.equals(ref.getSubType())) { Calendar cal = getCalendar(ref.getReference()); rv = "Calendar: " + cal.getId() + " (" + cal.getContext() + ")"; } // otherwise an event else if (REF_TYPE_EVENT.equals(ref.getSubType())) { rv = "Event: " + ref.getReference(); } } catch (PermissionException ignore) {} catch (IdUnusedException ignore) {} catch (NullPointerException ignore) {} return rv; } /** * {@inheritDoc} */ public ResourceProperties getEntityResourceProperties(Reference ref) { // double check that it's mine if (!APPLICATION_ID.equals(ref.getType())) return null; ResourceProperties props = null; try { // if this is a calendar if (REF_TYPE_CALENDAR.equals(ref.getSubType()) || REF_TYPE_CALENDAR_PDF.equals(ref.getSubType()) || REF_TYPE_CALENDAR_SUBSCRIPTION.equals(ref.getSubType())) { Calendar cal = getCalendar(ref.getReference()); props = cal.getProperties(); } // otherwise an event else if (REF_TYPE_EVENT.equals(ref.getSubType())) { Calendar cal = getCalendar(calendarReference(ref.getContext(), ref.getContainer())); CalendarEvent event = cal.getEvent(ref.getId()); props = event.getProperties(); } else if (REF_TYPE_EVENT_SUBSCRIPTION.equals(ref.getSubType())) { Calendar cal = getCalendar(calendarSubscriptionReference(ref.getContext(), ref.getContainer())); CalendarEvent event = cal.getEvent(ref.getId()); props = event.getProperties(); } else M_log.warn(".getEntityResourceProperties(): unknown calendar ref subtype: " + ref.getSubType() + " in ref: " + ref.getReference()); } catch (PermissionException e) { M_log.warn(".getEntityResourceProperties(): " + e); } catch (IdUnusedException ignore) { // This just means that the resource once pointed to as an attachment or something has been deleted. // m_logger(this + ".getProperties(): " + e); } catch (NullPointerException e) { M_log.warn(".getEntityResourceProperties(): " + e); } return props; } /** * {@inheritDoc} */ public Entity getEntity(Reference ref) { // double check that it's mine if (!APPLICATION_ID.equals(ref.getType())) return null; Entity rv = null; try { // if this is a calendar if (REF_TYPE_CALENDAR.equals(ref.getSubType()) || REF_TYPE_CALENDAR_PDF.equals(ref.getSubType()) || REF_TYPE_CALENDAR_SUBSCRIPTION.equals(ref.getSubType())) { rv = getCalendar(ref.getReference()); } // otherwise a event else if (REF_TYPE_EVENT.equals(ref.getSubType())) { Calendar cal = getCalendar(calendarReference(ref.getContext(), ref.getContainer())); rv = cal.getEvent(ref.getId()); } else if (REF_TYPE_EVENT_SUBSCRIPTION.equals(ref.getSubType())) { Calendar cal = getCalendar(calendarSubscriptionReference(ref.getContext(), ref.getContainer())); rv = cal.getEvent(ref.getId()); } else M_log.warn("getEntity(): unknown calendar ref subtype: " + ref.getSubType() + " in ref: " + ref.getReference()); } catch (PermissionException e) { M_log.warn("getEntity(): " + e); } catch (IdUnusedException e) { M_log.warn("getEntity(): " + e); } catch (NullPointerException e) { M_log.warn(".getEntity(): " + e); } return rv; } /** * {@inheritDoc} */ public Collection getEntityAuthzGroups(Reference ref, String userId) { // double check that it's mine if (!APPLICATION_ID.equals(ref.getType())) return null; Collection rv = new Vector(); // for events: // if access set to SITE (or PUBLIC), use the event, calendar and site authzGroups. // if access set to GROUPED, use the event, and the groups, but not the calendar or site authzGroups. // if the user has SECURE_ALL_GROUPS in the context, ignore GROUPED access and treat as if SITE // for Calendars: use the calendar and site authzGroups. try { // for event if (REF_TYPE_EVENT.equals(ref.getSubType())) { // event rv.add(ref.getReference()); boolean grouped = false; Collection groups = null; // check SECURE_ALL_GROUPS - if not, check if the event has groups or not // TODO: the last param needs to be a ContextService.getRef(ref.getContext())... or a ref.getContextAuthzGroup() -ggolden if ((userId == null) || ((!m_securityService.isSuperUser(userId)) && (!m_authzGroupService.isAllowed(userId, SECURE_ALL_GROUPS, m_siteService.siteReference(ref.getContext()))))) { // get the calendar to get the message to get group information String calendarRef = calendarReference(ref.getContext(), ref.getContainer()); Calendar c = findCalendar(calendarRef); if (c != null) { CalendarEvent e = ((BaseCalendarEdit) c).findEvent(ref.getId()); if (e != null) { grouped = EventAccess.GROUPED == e.getAccess(); groups = e.getGroups(); } } } if (grouped) { // groups rv.addAll(groups); } // not grouped else { // calendar rv.add(calendarReference(ref.getContext(), ref.getContainer())); // site ref.addSiteContextAuthzGroup(rv); } } // for calendar else { // calendar rv.add(calendarReference(ref.getContext(), ref.getId())); // site ref.addSiteContextAuthzGroup(rv); } } catch (Throwable e) { M_log.warn("getEntityAuthzGroups(): " + e); } return rv; } /** * {@inheritDoc} */ public String getEntityUrl(Reference ref) { // double check that it's mine if (!APPLICATION_ID.equals(ref.getType())) return null; String rv = null; try { // if this is a calendar if (REF_TYPE_CALENDAR.equals(ref.getSubType()) || REF_TYPE_CALENDAR_PDF.equals(ref.getSubType())) { Calendar cal = getCalendar(ref.getReference()); rv = cal.getUrl(); } // otherwise a event else if (REF_TYPE_EVENT.equals(ref.getSubType())) { Calendar cal = getCalendar(calendarReference(ref.getContext(), ref.getContainer())); CalendarEvent event = cal.getEvent(ref.getId()); rv = event.getUrl(); } else M_log.warn("getEntityUrl(): unknown calendar ref subtype: " + ref.getSubType() + " in ref: " + ref.getReference()); } catch (PermissionException e) { M_log.warn(".getEntityUrl(): " + e); } catch (IdUnusedException e) { M_log.warn(".getEntityUrl(): " + e); } catch (NullPointerException e) { M_log.warn(".getEntityUrl(): " + e); } return rv; } /** * {@inheritDoc} */ public String archive(String siteId, Document doc, Stack stack, String archivePath, List attachments) { // prepare the buffer for the results log StringBuilder results = new StringBuilder(); // start with an element with our very own (service) name Element element = doc.createElement(CalendarService.class.getName()); ((Element) stack.peek()).appendChild(element); stack.push(element); // get the channel associated with this site String calRef = calendarReference(siteId, SiteService.MAIN_CONTAINER); results.append("archiving calendar " + calRef + ".\n"); try { // do the channel Calendar cal = getCalendar(calRef); Element containerElement = cal.toXml(doc, stack); stack.push(containerElement); // do the messages in the channel Iterator events = cal.getEvents(null, null).iterator(); while (events.hasNext()) { CalendarEvent event = (CalendarEvent) events.next(); event.toXml(doc, stack); // collect message attachments List atts = event.getAttachments(); for (int i = 0; i < atts.size(); i++) { Reference ref = (Reference) atts.get(i); // if it's in the attachment area, and not already in the list if ((ref.getReference().startsWith("/content/attachment/")) && (!attachments.contains(ref))) { attachments.add(ref); } } } stack.pop(); } catch (Exception any) { M_log.warn(".archve: exception archiving messages for service: " + CalendarService.class.getName() + " channel: " + calRef); } stack.pop(); return results.toString(); } /** * {@inheritDoc} */ public String merge(String siteId, Element root, String archivePath, String fromSiteId, Map attachmentNames, Map userIdTrans, Set userListAllowImport) { // prepare the buffer for the results log StringBuilder results = new StringBuilder(); // get the channel associated with this site String calendarRef = calendarReference(siteId, SiteService.MAIN_CONTAINER); int count = 0; try { Calendar calendar = null; try { calendar = getCalendar(calendarRef); } catch (IdUnusedException e) { CalendarEdit edit = addCalendar(calendarRef); commitCalendar(edit); calendar = edit; } // pass the DOM to get new event ids, and adjust attachments NodeList children2 = root.getChildNodes(); int length2 = children2.getLength(); for (int i2 = 0; i2 < length2; i2++) { Node child2 = children2.item(i2); if (child2.getNodeType() == Node.ELEMENT_NODE) { Element element2 = (Element) child2; // get the "calendar" child if (element2.getTagName().equals("calendar")) { NodeList children3 = element2.getChildNodes(); final int length3 = children3.getLength(); for (int i3 = 0; i3 < length3; i3++) { Node child3 = children3.item(i3); if (child3.getNodeType() == Node.ELEMENT_NODE) { Element element3 = (Element) child3; if (element3.getTagName().equals("properties")) { NodeList children8 = element3.getChildNodes(); final int length8 = children8.getLength(); for (int i8 = 0; i8 < length8; i8++) { Node child8 = children8.item(i8); if (child8.getNodeType() == Node.ELEMENT_NODE) { Element element8 = (Element) child8; // for "event" children if (element8.getTagName().equals("property")) { String pName = element8.getAttribute("name"); if ((pName != null) && (pName.equalsIgnoreCase("CHEF:calendar-fields"))) { String pValue = element8.getAttribute("value"); if ("BASE64".equalsIgnoreCase(element8.getAttribute("enc"))) { pValue = Xml.decodeAttribute(element8, "value"); } if (pValue != null) { try { CalendarEdit calEdit = editCalendar(calendarRef); String calFields = StringUtils.trimToEmpty(calEdit.getEventFields()); if (calFields != null) pValue = calFields + ADDFIELDS_DELIMITER + pValue; calEdit.setEventFields(pValue); commitCalendar(calEdit); } catch (Exception e) { M_log.warn(".merge() when editing calendar: exception: ", e); } } } } } } } // for "event" children if (element3.getTagName().equals("event")) { // adjust the id String oldId = element3.getAttribute("id"); String newId = getUniqueId(); element3.setAttribute("id", newId); // get the attachment kids NodeList children5 = element3.getChildNodes(); final int length5 = children5.getLength(); for (int i5 = 0; i5 < length5; i5++) { Node child5 = children5.item(i5); if (child5.getNodeType() == Node.ELEMENT_NODE) { Element element5 = (Element) child5; // for "attachment" children if (element5.getTagName().equals("attachment")) { // map the attachment area folder name String oldUrl = element5.getAttribute("relative-url"); if (oldUrl.startsWith("/content/attachment/")) { String newUrl = (String) attachmentNames.get(oldUrl); if (newUrl != null) { if (newUrl.startsWith("/attachment/")) newUrl = "/content".concat(newUrl); element5.setAttribute("relative-url", Validator.escapeQuestionMark(newUrl)); } } // map any references to this site to the new site id else if (oldUrl.startsWith("/content/group/" + fromSiteId + "/")) { String newUrl = "/content/group/" + siteId + oldUrl.substring(15 + fromSiteId.length()); element5.setAttribute("relative-url", Validator.escapeQuestionMark(newUrl)); } } } } // create a new message in the calendar CalendarEventEdit edit = calendar.mergeEvent(element3); calendar.commitEvent(edit); count++; } } } } } } } catch (Exception any) { M_log.warn(".merge(): exception: ", any); } results.append("merging calendar " + calendarRef + " (" + count + ") messages.\n"); return results.toString(); } /** * {@inheritDoc} */ public void transferCopyEntities(String fromContext, String toContext, List resourceIds) { transferCopyEntitiesRefMigrator(fromContext, toContext, resourceIds); } public Map<String, String> transferCopyEntitiesRefMigrator(String fromContext, String toContext, List resourceIds) { Map<String, String> transversalMap = new HashMap<String, String>(); // get the channel associated with this site String oCalendarRef = calendarReference(fromContext, SiteService.MAIN_CONTAINER); Calendar oCalendar = null; try { oCalendar = getCalendar(oCalendarRef); // new calendar CalendarEdit nCalendar = null; String nCalendarRef = calendarReference(toContext, SiteService.MAIN_CONTAINER); try { nCalendar = editCalendar(nCalendarRef); } catch (IdUnusedException e) { try { nCalendar = addCalendar(nCalendarRef); } catch (IdUsedException ignore) {} catch (IdInvalidException ignore) {} } catch (PermissionException ignore) {} catch (InUseException ignore) {} if (nCalendar != null) { List oEvents = oCalendar.getEvents(null, null); String oFields = StringUtils.trimToNull(oCalendar.getEventFields()); String nFields = StringUtils.trimToNull(nCalendar.getEventFields()); String allFields = ""; if (oFields != null) { if (nFields != null) { allFields = nFields + ADDFIELDS_DELIMITER + oFields; } else { allFields = oFields; } nCalendar.setEventFields(allFields); } for (int i = 0; i < oEvents.size(); i++) { CalendarEvent oEvent = (CalendarEvent) oEvents.get(i); try { // Skip calendar events based on assignment due dates String assignmentId = oEvent.getField(CalendarUtil.NEW_ASSIGNMENT_DUEDATE_CALENDAR_ASSIGNMENT_ID); if (assignmentId != null && assignmentId.length() > 0) continue; CalendarEvent e = nCalendar.addEvent(oEvent.getRange(), oEvent.getDisplayName(), oEvent.getDescription(), oEvent.getType(), oEvent.getLocation(), oEvent.getAttachments()); try { BaseCalendarEventEdit eEdit = (BaseCalendarEventEdit) nCalendar.getEditEvent(e.getId(),EVENT_ADD_CALENDAR ); // properties ResourcePropertiesEdit p = eEdit.getPropertiesEdit(); p.clear(); p.addAll(oEvent.getProperties()); // attachment List oAttachments = eEdit.getAttachments(); List nAttachments = m_entityManager.newReferenceList(); for (int n = 0; n < oAttachments.size(); n++) { Reference oAttachmentRef = (Reference) oAttachments.get(n); String oAttachmentId = ((Reference) oAttachments.get(n)).getId(); if (oAttachmentId.indexOf(fromContext) != -1) { // replace old site id with new site id in attachments String nAttachmentId = oAttachmentId.replaceAll(fromContext, toContext); try { ContentResource attachment = contentHostingService.getResource(nAttachmentId); nAttachments.add(m_entityManager.newReference(attachment.getReference())); } catch (IdUnusedException ee) { try { ContentResource oAttachment = contentHostingService.getResource(oAttachmentId); try { if (contentHostingService.isAttachmentResource(nAttachmentId)) { // add the new resource into attachment collection area ContentResource attachment = contentHostingService.addAttachmentResource( Validator.escapeResourceName(oAttachment.getProperties().getProperty(ResourceProperties.PROP_DISPLAY_NAME)), toContext, m_toolManager.getTool("sakai.schedule").getTitle(), oAttachment.getContentType(), oAttachment.getContent(), oAttachment.getProperties()); // add to attachment list nAttachments.add(m_entityManager.newReference(attachment.getReference())); } else { // add the new resource into resource area ContentResource attachment = contentHostingService.addResource( Validator.escapeResourceName(oAttachment.getProperties().getProperty(ResourceProperties.PROP_DISPLAY_NAME)), toContext, 1, oAttachment.getContentType(), oAttachment.getContent(), oAttachment.getProperties(), NotificationService.NOTI_NONE); // add to attachment list nAttachments.add(m_entityManager.newReference(attachment.getReference())); } } catch (Exception eeAny) { // if the new resource cannot be added M_log.warn(" cannot add new attachment with id=" + nAttachmentId); } } catch (Exception eAny) { // if cannot find the original attachment, do nothing. M_log.warn(" cannot find the original attachment with id=" + oAttachmentId); } } catch (Exception any) { M_log.warn(this + any.getMessage()); } } else { nAttachments.add(oAttachmentRef); } } eEdit.replaceAttachments(nAttachments); // recurrence rules RecurrenceRule rule = oEvent.getRecurrenceRule(); eEdit.setRecurrenceRule(rule); RecurrenceRule exRule = oEvent.getExclusionRule(); eEdit.setExclusionRule(exRule); // commit new event m_storage.commitEvent(nCalendar, eEdit); transversalMap.put(oEvent.getId(), eEdit.getId()); } catch (InUseException ignore) {} } catch (PermissionException ignore) {} } // commit new calendar m_storage.commitCalendar(nCalendar); ((BaseCalendarEdit) nCalendar).closeEdit(); } // if } catch (IdUnusedException ignore) {} catch (PermissionException ignore) {} return transversalMap; } // importResources /** * {@inheritDoc} */ public void updateEntityReferences(String toContext, Map<String, String> transversalMap){ if(transversalMap != null && transversalMap.size() > 0){ Set<Entry<String, String>> entrySet = (Set<Entry<String, String>>) transversalMap.entrySet(); try { String toSiteId = toContext; String calendarId = calendarReference(toSiteId, SiteService.MAIN_CONTAINER); Calendar calendarObj = getCalendar(calendarId); List calEvents = calendarObj.getEvents(null,null); for (int i = 0; i < calEvents.size(); i++) { try { CalendarEvent ce = (CalendarEvent) calEvents.get(i); String msgBodyFormatted = ce.getDescriptionFormatted(); boolean updated = false; /* Iterator<Entry<String, String>> entryItr = entrySet.iterator(); while(entryItr.hasNext()) { Entry<String, String> entry = (Entry<String, String>) entryItr.next(); String fromContextRef = entry.getKey(); if(msgBodyFormatted.contains(fromContextRef)){ msgBodyFormatted = msgBodyFormatted.replace(fromContextRef, entry.getValue()); updated = true; } } */ StringBuffer msgBodyPreMigrate = new StringBuffer(msgBodyFormatted); msgBodyFormatted = LinkMigrationHelper.migrateAllLinks(entrySet, msgBodyFormatted); if(!msgBodyFormatted.equals(msgBodyPreMigrate.toString())){ // if(updated){ CalendarEventEdit edit = calendarObj.getEditEvent(ce.getId(), org.sakaiproject.calendar.api.CalendarService.EVENT_MODIFY_CALENDAR); edit.setDescriptionFormatted(msgBodyFormatted); calendarObj.commitEvent(edit); } } catch (IdUnusedException e) { M_log.debug(".IdUnusedException " + e); } catch (PermissionException e) { M_log.debug(".PermissionException " + e); } catch (InUseException e) { M_log.debug(".InUseException delete" + e); } } } catch (Exception e) { M_log.info("importSiteClean: End removing Calendar data" + e); } } } /** * @inheritDoc */ public String[] myToolIds() { String[] toolIds = { "sakai.schedule" }; return toolIds; } /** * {@inheritDoc} */ public void contextCreated(String context, boolean toolPlacement) { if (toolPlacement) enableSchedule(context); } /** * {@inheritDoc} */ public void contextUpdated(String context, boolean toolPlacement) { if (toolPlacement) enableSchedule(context); } /** * {@inheritDoc} */ public void contextDeleted(String context, boolean toolPlacement) { disableSchedule(context); } /** * Setup a calendar for the site. * * @param site * The site. */ protected void enableSchedule(String context) { // form the calendar name String calRef = calendarReference(context, SiteService.MAIN_CONTAINER); // see if there's a calendar try { getCalendar(calRef); } catch (IdUnusedException un) { try { // create a calendar CalendarEdit edit = addCalendar(calRef); commitCalendar(edit); } catch (IdUsedException ignore) {} catch (IdInvalidException ignore) {} } catch (PermissionException ignore) {} } /** * Remove a calendar for the site. * * @param site * The site. */ protected void disableSchedule(String context) { // TODO: currently we do not remove a calendar when the tool is removed from the site or the site is deleted -ggolden } /********************************************************************************************************************************************************************************************************************************************************** * Calendar implementation *********************************************************************************************************************************************************************************************************************************************************/ public class BaseCalendarEdit extends Observable implements CalendarEdit, SessionBindingListener { /** The context in which this calendar exists. */ protected String m_context = null; /** Store the unique-in-context calendar id. */ protected String m_id = null; /** The properties. */ protected ResourcePropertiesEdit m_properties = null; /** When true, the calendar has been removed. */ protected boolean m_isRemoved = false; /** The event code for this edit. */ protected String m_event = null; /** Active flag. */ protected boolean m_active = false; /** * Construct with an id. * * @param ref * The calendar reference. */ public BaseCalendarEdit(String ref) { // set the ids Reference r = m_entityManager.newReference(ref); m_context = r.getContext(); m_id = r.getId(); // setup for properties m_properties = new BaseResourcePropertiesEdit(); } // BaseCalendarEdit /** * Construct as a copy of another. * * @param id * The other to copy. */ public BaseCalendarEdit(Calendar other) { // set the ids m_context = other.getContext(); m_id = other.getId(); // setup for properties m_properties = new BaseResourcePropertiesEdit(); m_properties.addAll(other.getProperties()); } // BaseCalendarEdit protected BaseCalendarEdit() { m_properties = new BaseResourcePropertiesEdit(); } /** * Construct from a calendar (and possibly events) already defined in XML in a DOM tree. The Calendar is added to storage. * * @param el * The XML DOM element defining the calendar. */ public BaseCalendarEdit(Element el) { // setup for properties m_properties = new BaseResourcePropertiesEdit(); m_id = el.getAttribute("id"); m_context = el.getAttribute("context"); // the children (properties, ignore events) NodeList children = el.getChildNodes(); final int length = children.getLength(); for (int i = 0; i < length; i++) { Node child = children.item(i); if (child.getNodeType() != Node.ELEMENT_NODE) continue; Element element = (Element) child; // look for properties (ignore possible "event" entries) if (element.getTagName().equals("properties")) { // re-create properties m_properties = new BaseResourcePropertiesEdit(element); } } } // BaseCalendarEdit /** * Set the calendar as removed. * * @param event * The tracking event associated with this action. */ public void setRemoved(Event event) { m_isRemoved = true; // notify observers notify(event); // now clear observers deleteObservers(); } // setRemoved /** * Access the context of the resource. * * @return The context. */ public String getContext() { return m_context; } // getContext /** * Access the id of the resource. * * @return The id. */ public String getId() { return m_id; } // getId /** * Access the URL which can be used to access the resource. * * @return The URL which can be used to access the resource. */ public String getUrl() { return getAccessPoint(false) + SEPARATOR + getId() + SEPARATOR; // %%% needs fixing re: context } // getUrl /** * Access the internal reference which can be used to access the resource from within the system. * * @return The the internal reference which can be used to access the resource from within the system. */ public String getReference() { return calendarReference(m_context, m_id); } // getReference /** * @inheritDoc */ public String getReference(String rootProperty) { return getReference(); } /** * @inheritDoc */ public String getUrl(String rootProperty) { return getUrl(); } /** * Access the collection's properties. * * @return The collection's properties. */ public ResourceProperties getProperties() { return m_properties; } // getProperties /** ** check if this calendar allows ical exports ** @return true if the calender allows exports; false if not **/ public boolean getExportEnabled() { String enable = m_properties.getProperty(CalendarService.PROP_ICAL_ENABLE); return Boolean.valueOf(enable); } /** ** set if this calendar allows ical exports ** @return true if the calender allows exports; false if not **/ public void setExportEnabled( boolean enable ) { m_properties.addProperty(CalendarService.PROP_ICAL_ENABLE, String.valueOf(enable)); } /** ** Get the time of the last modify to this calendar ** @return String representation of current time (may be null if not initialized) **/ public Time getModified() { String timeStr = m_properties.getProperty(ResourceProperties.PROP_MODIFIED_DATE); if ( timeStr == null ) return null; else return m_timeService.newTimeGmt(timeStr); } /** ** Set the time of the last modify for this calendar to now ** @return true if successful; false if not **/ public void setModified() { String currentUser = m_sessionManager.getCurrentSessionUserId(); String now = m_timeService.newTime().toString(); m_properties.addProperty(ResourceProperties.PROP_MODIFIED_BY, currentUser); m_properties.addProperty(ResourceProperties.PROP_MODIFIED_DATE, now); } /** * check permissions for getEvents() and getEvent(). * * @return true if the user is allowed to get events from the calendar, false if not. */ public boolean allowGetEvents() { return unlockCheck(AUTH_READ_CALENDAR, getReference()); } // allowGetEvents /** * {@inheritDoc} */ public boolean allowGetEvent(String eventId) { return unlockCheck(AUTH_READ_CALENDAR, eventReference(m_context, m_id, eventId)); } /** * 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. * * @param range * A time range to limit the iterated events. May be null; all events will be returned. * @param filter * A filtering object to accept events into the iterator, or null if no filtering is desired. * @return a List of all or filtered CalendarEvents in the calendar (may be empty). * @exception PermissionException * if the user does not have read permission to the calendar. */ public List getEvents(TimeRange range, Filter filter) throws PermissionException { // check security (throws if not permitted) unlock(AUTH_READ_CALENDAR, getReference()); List events = new Vector(); if ((!m_caching) || (m_calendarCache == null) || (m_calendarCache.disabled())) { if ( range != null ) events = m_storage.getEvents(this, range.firstTime().getTime(), range.lastTime().getTime() ); else events = m_storage.getEvents(this); } else { // find the event cache Cache eventCache = (Cache) m_eventCaches.get(getReference()); if (eventCache == null) { synchronized (m_eventCaches) { // check again eventCache = (Cache) m_eventCaches.get(getReference()); // if still not there, make one if (eventCache == null) { eventCache = m_memoryService .newCache( "org.sakaiproject.calendar.api.CalendarService.eventCache", service(), eventReference( m_context, m_id, "")); m_eventCaches.put(getReference(), eventCache); } } } // if the cache is complete, use it if (eventCache.isComplete()) { // get just this calendar's events events = eventCache.getAll(); } // otherwise get all the events from storage else { // Note: while we are getting from storage, storage might change. These can be processed // after we get the storage entries, and put them in the cache, and mark the cache complete. // -ggolden synchronized (eventCache) { // if we were waiting and it's now complete... if (eventCache.isComplete()) { // get just this calendar's events events = eventCache.getAll(); } else { // save up any events to the cache until we get past this load eventCache.holdEvents(); // get all the events for the calendar events = m_storage.getEvents(this); // update the cache, and mark it complete for (int i = 0; i < events.size(); i++) { CalendarEvent event = (CalendarEvent) events.get(i); eventCache.put(event.getReference(), event); } eventCache.setComplete(); // now we are complete, process any cached events eventCache.processEvents(); } } } } // now filter out the events to just those in the range // Note: if no range, we won't filter, which means we don't expand recurring events, but just // return it as a single event. This is very good for an archive... -ggolden if (range != null) { events = filterEvents(events, range); } if (events.size() == 0) return events; // filter out based on the filter if (filter != null) { List filtered = new Vector(); for (int i = 0; i < events.size(); i++) { Event event = (Event) events.get(i); if (filter.accept(event)) filtered.add(event); } if (filtered.size() == 0) return filtered; events = filtered; } // remove any events that are grouped, and that the current user does not have permission to see Collection groupsAllowed = getGroupsAllowGetEvent(); List allowedEvents = new Vector(); for (Iterator i = events.iterator(); i.hasNext();) { CalendarEvent event = (CalendarEvent) i.next(); if (event.getAccess() == EventAccess.SITE) { allowedEvents.add(event); } else { // if the user's Groups overlap the event's group refs it's grouped to, keep it if (EntityCollections.isIntersectionEntityRefsToEntities(event.getGroups(), groupsAllowed)) { allowedEvents.add(event); } } } // sort - natural order is date ascending Collections.sort(allowedEvents); return allowedEvents; } // getEvents /** * Filter the events to only those in the time range. * * @param events * The full list of events. * @param range * The time range. * @return A list of events from the incoming list that overlap the given time range. */ protected List filterEvents(List events, TimeRange range) { List filtered = new Vector(); for (int i = 0; i < events.size(); i++) { CalendarEvent event = (CalendarEvent) events.get(i); // resolve the event to the list of events in this range List resolved = ((BaseCalendarEventEdit) event).resolve(range); filtered.addAll(resolved); } return filtered; } // filterEvents /** * Return a specific calendar event, as specified by event id. * * @param eventId * The id of the event to get. * @return the CalendarEvent that has the specified id. * @exception IdUnusedException * If this id is not a defined event in this calendar. * @exception PermissionException * If the user does not have any permissions to read the calendar. */ public CalendarEvent getEvent(String eventId) throws IdUnusedException, PermissionException { // check security on the event (throws if not permitted) unlock(AUTH_READ_CALENDAR, eventReference(m_context, m_id, eventId)); CalendarEvent e = findEvent(eventId); if (e == null) throw new IdUnusedException(eventId); return e; } // getEvent /** * check permissions for addEvent(). * * @return true if the user is allowed to addEvent(...), false if not. */ public boolean allowAddEvent() { // checking allow at the channel (site) level if (allowAddCalendarEvent()) return true; // if not, see if the user has any groups to which adds are allowed return (!getGroupsAllowAddEvent().isEmpty()); } // allowAddEvent /** * @inheritDoc */ public boolean allowAddCalendarEvent() { // check for events that will be calendar (site) -wide: // base the check for SECURE_ADD on the site and the calendar only (not the groups). // check security on the calendar (throws if not permitted) return unlockCheck(AUTH_ADD_CALENDAR, getReference()); } /** * Add a new event to this calendar. * * @param range * The event's time range. * @param displayName * The event's display name (PROP_DISPLAY_NAME) property value. * @param description * The event's description as plain text (PROP_DESCRIPTION) property value. * @param type * The event's calendar event type (PROP_CALENDAR_TYPE) property value. * @param location * The event's calendar event location (PROP_CALENDAR_LOCATION) property value. * @param attachments * The event attachments, a vector of Reference objects. * @return The newly added event. * @exception PermissionException * If the user does not have permission to modify the calendar. */ public CalendarEvent addEvent(TimeRange range, String displayName, String description, String type, String location, EventAccess access, Collection groups, List attachments) throws PermissionException { // securtiy check (any sort (group, site) of add) if (!allowAddEvent()) { throw new PermissionException(m_sessionManager.getCurrentSessionUserId(), eventId(SECURE_ADD), getReference()); } // make one // allocate a new unique event id String id = getUniqueId(); // get a new event in the info store CalendarEventEdit edit = m_storage.putEvent(this, id); ((BaseCalendarEventEdit) edit).setEvent(EVENT_ADD_CALENDAR); // set it up edit.setRange(range); edit.setDisplayName(displayName); edit.setDescription(description); edit.setType(type); edit.setLocation(location); edit.setCreator(); // for site... if (access == EventAccess.SITE) { // if not allowd to SITE, will throw permission exception try { edit.clearGroupAccess(); } catch (PermissionException e) { cancelEvent(edit); throw new PermissionException(m_sessionManager.getCurrentSessionUserId(), eventId(SECURE_ADD), getReference()); } } // for grouped... else { // if not allowed to GROUP, will throw permission exception try { edit.setGroupAccess(groups,true); } catch (PermissionException e) { cancelEvent(edit); throw new PermissionException(m_sessionManager.getCurrentSessionUserId(), eventId(SECURE_ADD), getReference()); } } edit.replaceAttachments(attachments); // commit it commitEvent(edit); return edit; } // addEvent /** * Add a new event to this calendar. * * @param range * The event's time range. * @param displayName * The event's display name (PROP_DISPLAY_NAME) property value. * @param description * The event's description as plain text (PROP_DESCRIPTION) property value. * @param type * The event's calendar event type (PROP_CALENDAR_TYPE) property value. * @param location * The event's calendar event location (PROP_CALENDAR_LOCATION) property value. * @param attachments * The event attachments, a vector of Reference objects. * @return The newly added event. * @exception PermissionException * If the user does not have permission to modify the calendar. */ public CalendarEvent addEvent(TimeRange range, String displayName, String description, String type, String location, List attachments) throws PermissionException { // make one CalendarEventEdit edit = addEvent(); // set it up edit.setRange(range); edit.setDisplayName(displayName); edit.setDescription(description); edit.setType(type); edit.setLocation(location); edit.replaceAttachments(attachments); // commit it commitEvent(edit); return edit; } // addEvent /** * Add a new event to this calendar. Must commitEvent() to make official, or cancelEvent() when done! * * @return The newly added event, locked for update. * @exception PermissionException * If the user does not have write permission to the calendar. */ public CalendarEventEdit addEvent() throws PermissionException { // check security (throws if not permitted) unlock(AUTH_ADD_CALENDAR, getReference()); // allocate a new unique event id String id = getUniqueId(); // get a new event in the info store CalendarEventEdit event = m_storage.putEvent(this, id); ((BaseCalendarEventEdit) event).setEvent(EVENT_ADD_CALENDAR); return event; } // addEvent /** * Merge in a new event as defined in the xml. * * @param el * The event information in XML in a DOM element. * @exception PermissionException * If the user does not have write permission to the calendar. * @exception IdUsedException * if the user id is already used. */ public CalendarEventEdit mergeEvent(Element el) throws PermissionException, IdUsedException { CalendarEvent eventFromXml = (CalendarEvent) newResource(this, el); // check security if ( ! allowAddEvent() ) throw new PermissionException(m_sessionManager.getCurrentSessionUserId(), AUTH_ADD_CALENDAR, getReference()); // reserve a calendar event with this id from the info store - if it's in use, this will return null CalendarEventEdit event = m_storage.putEvent(this, eventFromXml.getId()); if (event == null) { throw new IdUsedException(eventFromXml.getId()); } // transfer from the XML read object to the Edit ((BaseCalendarEventEdit) event).set(eventFromXml); ((BaseCalendarEventEdit) event).setEvent(EVENT_MODIFY_CALENDAR); return event; } // mergeEvent /** * check permissions for removeEvent(). * * @param event * The event from this calendar to remove. * @return true if the user is allowed to removeEvent(event), false if not. */ public boolean allowRemoveEvent(CalendarEvent event) { boolean allowed = false; boolean ownEvent = event.isUserOwner(); // check security to delete any event if ( unlockCheck(AUTH_REMOVE_CALENDAR_ANY, getReference()) ) allowed = true; // check security to delete own event else if ( unlockCheck(AUTH_REMOVE_CALENDAR_OWN, getReference()) && ownEvent ) allowed = true; // but we must also assure, that for grouped events, we can remove it from ALL of the groups if (allowed && (event.getAccess() == EventAccess.GROUPED)) { allowed = EntityCollections.isContainedEntityRefsToEntities(event.getGroups(), getGroupsAllowRemoveEvent(ownEvent)); } return allowed; } // allowRemoveEvent /** * 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). * * @param event * The event from this calendar to remove. */ public void removeEvent(CalendarEventEdit edit) throws PermissionException { removeEvent(edit, MOD_ALL); } // removeEvent /** * Remove an event from the calendar, one locked for edit. * * @param event * The event from this calendar to remove. * @param intention * 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. */ public void removeEvent(CalendarEventEdit edit, int intention) throws PermissionException { // check for closed edit if (!edit.isActiveEdit()) { try { throw new Exception(); } catch (Exception e) { M_log.warn("removeEvent(): closed EventEdit", e); } return; } // securityCheck if (!allowRemoveEvent(edit)) { cancelEvent(edit); throw new PermissionException(m_sessionManager.getCurrentSessionUserId(), AUTH_REMOVE_CALENDAR_ANY, edit.getReference()); } BaseCalendarEventEdit bedit = (BaseCalendarEventEdit) edit; // if the id has a time range encoded, as for one of a sequence of recurring events, separate that out String indivEventEntityRef = null; TimeRange timeRange = null; int sequence = 0; if (bedit.m_id.startsWith("!")) { indivEventEntityRef = bedit.getReference(); String[] parts = bedit.m_id.substring(1).split("!"); try { timeRange = m_timeService.newTimeRange(parts[0]); sequence = Integer.parseInt(parts[1]); bedit.m_id = parts[2]; } catch (Exception ex) { M_log.warn("removeEvent: exception parsing eventId: " + bedit.m_id + " : " + ex); } } // deal with recurring event sequence modification if (timeRange != null) { // delete only this - add it as an exclusion in the edit if (intention == MOD_THIS) { // get the edit back to initial values... so only the exclusion is changed edit = (CalendarEventEdit) m_storage.getEvent(this, bedit.m_id); bedit = (BaseCalendarEventEdit) edit; // add an exclusion for where this one would have been %%% we are changing it, should it be immutable? -ggolden List exclusions = ((ExclusionSeqRecurrenceRule) bedit.getExclusionRule()).getExclusions(); exclusions.add(Integer.valueOf(sequence)); // complete the edit m_storage.commitEvent(this, edit); // post event for excluding the instance m_eventTrackingService.post(m_eventTrackingService.newEvent(EVENT_MODIFY_CALENDAR_EVENT_EXCLUSIONS, indivEventEntityRef, true)); } // delete them all, i.e. the one initial event else { m_storage.removeEvent(this, edit); m_eventTrackingService.post(m_eventTrackingService.newEvent(EVENT_REMOVE_CALENDAR_EVENT, edit.getReference(), true)); } } // else a single event to delete else { m_storage.removeEvent(this, edit); m_eventTrackingService.post(m_eventTrackingService.newEvent(EVENT_REMOVE_CALENDAR_EVENT, edit.getReference(), true)); } // track event Event event = m_eventTrackingService.newEvent(EVENT_MODIFY_CALENDAR, edit.getReference(), true); m_eventTrackingService.post(event); // calendar notification notify(event); // close the edit object ((BaseCalendarEventEdit) edit).closeEdit(); // remove any realm defined for this resource try { m_authzGroupService.removeAuthzGroup(m_authzGroupService.getAuthzGroup(edit.getReference())); } catch (AuthzPermissionException e) { M_log.warn("removeEvent: removing realm for : " + edit.getReference() + " : " + e); } catch (GroupNotDefinedException ignore) { } } // removeEvent /** * check permissions for editEvent() * * @param id * The event id. * @return true if the user is allowed to update the event, false if not. */ public boolean allowEditEvent(String eventId) { CalendarEvent e = findEvent(eventId); if (e == null) return false; boolean ownEvent = e.isUserOwner(); // check security to revise any event if ( unlockCheck(AUTH_MODIFY_CALENDAR_ANY, getReference()) ) return true; // check security to revise own event else if ( unlockCheck(AUTH_MODIFY_CALENDAR_OWN, getReference()) && ownEvent ) return true; // otherwise not authorized else return false; } // allowEditEvent /** * Return a specific calendar event, as specified by event name, locked for update. * Must commitEvent() to make official, or cancelEvent(), or removeEvent() when done! * * @param eventId The id of the event to get. * @param editType add, remove or modifying calendar? * @return the Event that has the specified id. * @exception IdUnusedException * If this name is not a defined event in this calendar. * @exception PermissionException * If the user does not have any permissions to edit the event. * @exception InUseException * if the event is locked for edit by someone else. */ public CalendarEventEdit getEditEvent(String eventId, String editType) throws IdUnusedException, PermissionException, InUseException { // if the id has a time range encoded, as for one of a sequence of recurring events, separate that out TimeRange timeRange = null; int sequence = 0; if (eventId.startsWith("!")) { String[] parts = eventId.substring(1).split("!"); try { timeRange = m_timeService.newTimeRange(parts[0]); sequence = Integer.parseInt(parts[1]); eventId = parts[2]; } catch (Exception ex) { M_log.warn("getEditEvent: exception parsing eventId: " + eventId + " : " + ex); } } CalendarEvent e = findEvent(eventId); if (e == null) throw new IdUnusedException(eventId); // check security if ( editType.equals(EVENT_ADD_CALENDAR) && ! allowAddEvent() ) throw new PermissionException(m_sessionManager.getCurrentSessionUserId(), AUTH_ADD_CALENDAR, getReference()); else if ( editType.equals(EVENT_REMOVE_CALENDAR) && ! allowRemoveEvent(e) ) throw new PermissionException(m_sessionManager.getCurrentSessionUserId(), AUTH_REMOVE_CALENDAR_ANY, getReference()); else if ( editType.equals(EVENT_MODIFY_CALENDAR) && ! allowEditEvent(eventId) ) throw new PermissionException(m_sessionManager.getCurrentSessionUserId(), AUTH_MODIFY_CALENDAR_ANY, getReference()); // ignore the cache - get the CalendarEvent with a lock from the info store CalendarEventEdit edit = m_storage.editEvent(this, eventId); if (edit == null) throw new InUseException(eventId); BaseCalendarEventEdit bedit = (BaseCalendarEventEdit) edit; // if this is one in a sequence, adjust it if (timeRange != null) { // move the specified range into the event's range, storing the base range bedit.m_baseRange = bedit.m_range; bedit.m_range = timeRange; bedit.m_id = '!' + bedit.m_range.toString() + '!' + sequence + '!' + bedit.m_id; } bedit.setEvent(EVENT_MODIFY_CALENDAR); return edit; } // getEditEvent /** * 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 * (MOD_ALL). * * @param edit * The CalendarEventEdit object to commit. */ public void commitEvent(CalendarEventEdit edit) { commitEvent(edit, MOD_ALL); } // commitEvent /** * Commit the changes made to a CalendarEventEdit object, and release the lock. The CalendarEventEdit is disabled, and not to be used after this call. * * @param edit * The CalendarEventEdit object to commit. * @param intention * 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. */ public void commitEvent(CalendarEventEdit edit, int intention) { // check for closed edit if (!edit.isActiveEdit()) { M_log.warn("commitEvent(): closed CalendarEventEdit " + edit.getId()); return; } BaseCalendarEventEdit bedit = (BaseCalendarEventEdit) edit; // If creator doesn't exist, set it now (backward compatibility) if ( edit.getCreator() == null || edit.getCreator().equals("") ) edit.setCreator(); // update modified-by properties for event edit.setModifiedBy(); // if the id has a time range encoded, as for one of a sequence of recurring events, separate that out String indivEventEntityRef = null; TimeRange timeRange = null; int sequence = 0; if (bedit.m_id.startsWith("!")) { indivEventEntityRef = bedit.getReference(); String[] parts = bedit.m_id.substring(1).split("!"); try { timeRange = m_timeService.newTimeRange(parts[0]); sequence = Integer.parseInt(parts[1]); bedit.m_id = parts[2]; } catch (Exception ex) { M_log.warn("commitEvent: exception parsing eventId: " + bedit.m_id + " : " + ex); } } // for recurring event sequence TimeRange newTimeRange = null; BaseCalendarEventEdit newEvent = null; if (timeRange != null) { // if changing this event only if (intention == MOD_THIS) { // make a new event for this one String id = getUniqueId(); newEvent = (BaseCalendarEventEdit) m_storage.putEvent(this, id); newEvent.setPartial(edit); m_storage.commitEvent(this, newEvent); m_eventTrackingService.post(m_eventTrackingService.newEvent(EVENT_MODIFY_CALENDAR, newEvent.getReference(), true)); m_eventTrackingService.post(m_eventTrackingService.newEvent(EVENT_MODIFY_CALENDAR_EVENT_EXCLUDED, newEvent.getReference(), true)); m_eventTrackingService.post(m_eventTrackingService.newEvent(EVENT_MODIFY_CALENDAR_EVENT_EXCLUSIONS, indivEventEntityRef, true)); // get the edit back to initial values... so only the exclusion is changed edit = (CalendarEventEdit) m_storage.getEvent(this, bedit.m_id); bedit = (BaseCalendarEventEdit) edit; // add an exclusion for where this one would have been %%% we are changing it, should it be immutable? -ggolden List exclusions = ((ExclusionSeqRecurrenceRule) bedit.getExclusionRule()).getExclusions(); exclusions.add(Integer.valueOf(sequence)); } // else change the entire sequence (i.e. the one initial event) else { // the time range may have been modified in the edit newTimeRange = bedit.m_range; // restore the real range, that of the base event of a sequence, if this is one of the other events in the sequence. bedit.m_range = bedit.m_baseRange; // adjust the base range if there was an edit to range bedit.m_range.adjust(timeRange, newTimeRange); } } // update the properties // addLiveUpdateProperties(edit.getPropertiesEdit());//%%% postEventsForChanges(bedit); // complete the edit m_storage.commitEvent(this, edit); // track event Event event = m_eventTrackingService.newEvent(bedit.getEvent(), edit.getReference(), true); m_eventTrackingService.post(event); // calendar notification notify(event); // close the edit object bedit.closeEdit(); // Update modify time on calendar this.setModified(); m_storage.commitCalendar(this); // restore this one's range etc so it can be further referenced if (timeRange != null) { // if changing this event only if (intention == MOD_THIS) { // set the edit to the values of the new event bedit.set(newEvent); } // else we changed the sequence else { // move the specified range into the event's range, storing the base range bedit.m_baseRange = bedit.m_range; bedit.m_range = newTimeRange; bedit.m_id = '!' + bedit.m_range.toString() + '!' + sequence + '!' + bedit.m_id; } } } // commitEvent /** * @param newEvent */ public void postEventsForChanges(BaseCalendarEventEdit newEvent) { // determine which events should be posted by comparing with saved properties BaseCalendarEventEdit savedEvent = (BaseCalendarEventEdit) this.findEvent(newEvent.m_id); if(savedEvent == null) { } else { // has type changed? String savedType = savedEvent.getType(); String newType = newEvent.getType(); if(savedType == null || newType == null) { // TODO: is this an error? } else { if (!newType.equals(savedType)) { // post type-change event m_eventTrackingService.post(m_eventTrackingService.newEvent(EVENT_MODIFY_CALENDAR_EVENT_TYPE, newEvent.getReference() + "::" + savedType + "::" + newType, true)); } } // has title changed? if(savedEvent.getDisplayName() != null && ! savedEvent.getDisplayName().equals(newEvent.getDisplayName())) { // post title-change event m_eventTrackingService.post(m_eventTrackingService.newEvent(EVENT_MODIFY_CALENDAR_EVENT_TITLE, newEvent.getReference(), true)); } // has start-time changed? TimeRange savedRange = savedEvent.getRange(); TimeRange newRange = newEvent.getRange(); if(savedRange == null || newRange == null) { // TODO: Is this an error? } else if(savedRange.firstTime() != null && savedRange.firstTime().compareTo(newRange.firstTime()) != 0) { // post time-change event m_eventTrackingService.post(m_eventTrackingService.newEvent(EVENT_MODIFY_CALENDAR_EVENT_TIME, newEvent.getReference(), true)); } // has access changed? if(savedEvent.getAccess() != newEvent.getAccess()) { // post access-change event m_eventTrackingService.post(m_eventTrackingService.newEvent(EVENT_MODIFY_CALENDAR_EVENT_ACCESS, newEvent.getReference(), true)); } else { Collection savedGroups = savedEvent.getGroups(); Collection newGroups = newEvent.getGroups(); if(! (savedGroups.containsAll(newGroups) && newGroups.containsAll(savedGroups))) { // post access-change event m_eventTrackingService.post(m_eventTrackingService.newEvent(EVENT_MODIFY_CALENDAR_EVENT_ACCESS, newEvent.getReference(), true)); } } // has frequency changed (other than exclusions)? RecurrenceRule savedRule = savedEvent.getRecurrenceRule(); RecurrenceRule newRule = newEvent.getRecurrenceRule(); if(savedRule == null && newRule == null) { // do nothing -- no change } else if(savedRule == null || newRule == null) { // post frequency-change event m_eventTrackingService.post(m_eventTrackingService.newEvent(EVENT_MODIFY_CALENDAR_EVENT_FREQUENCY, newEvent.getReference(), true)); } else { // check for changes in properties of the rules // (rule.getCount() rule.getFrequency() rule.getInterval() rule.getUntil() if(savedRule.getCount() != newRule.getCount() || savedRule.getInterval() != newRule.getInterval()) { // post frequency-change event m_eventTrackingService.post(m_eventTrackingService.newEvent(EVENT_MODIFY_CALENDAR_EVENT_FREQUENCY, newEvent.getReference(), true)); } else if((savedRule.getFrequency() != null && ! savedRule.getFrequency().equals(newRule.getFrequency())) || (savedRule.getFrequency() == null && newRule.getFrequency() != null)) { // post frequency-change event m_eventTrackingService.post(m_eventTrackingService.newEvent(EVENT_MODIFY_CALENDAR_EVENT_FREQUENCY, newEvent.getReference(), true)); } else if((savedRule.getUntil() == null && newRule.getUntil() != null) || (savedRule.getUntil() != null && newRule.getUntil() == null) || (savedRule.getUntil() != null && savedRule.getUntil().getTime() != newRule.getUntil().getTime())) { // post frequency-change event m_eventTrackingService.post(m_eventTrackingService.newEvent(EVENT_MODIFY_CALENDAR_EVENT_FREQUENCY, newEvent.getReference(), true)); } } } } /** * Cancel the changes made to a CalendarEventEdit object, and release the lock. The CalendarEventEdit is disabled, and not to be used after this call. * * @param edit * The CalendarEventEdit object to commit. */ public void cancelEvent(CalendarEventEdit edit) { // check for closed edit if (!edit.isActiveEdit()) { Throwable e = new Throwable(); M_log.warn("cancelEvent(): closed CalendarEventEdit", e); return; } BaseCalendarEventEdit bedit = (BaseCalendarEventEdit) edit; // if the id has a time range encoded, as for one of a sequence of recurring events, separate that out TimeRange timeRange = null; int sequence = 0; if (bedit.m_id.startsWith("!")) { String[] parts = bedit.m_id.substring(1).split( "!"); try { timeRange = m_timeService.newTimeRange(parts[0]); sequence = Integer.parseInt(parts[1]); bedit.m_id = parts[2]; } catch (Exception ex) { M_log.warn("commitEvent: exception parsing eventId: " + bedit.m_id + " : " + ex); } } // release the edit lock m_storage.cancelEvent(this, edit); // close the edit object ((BaseCalendarEventEdit) edit).closeEdit(); } // cancelCalendarEvent /** * Return the extra fields kept for each event in this calendar. * * @return the extra fields kept for each event in this calendar, formatted into a single string. %%% */ public String getEventFields() { return m_properties.getPropertyFormatted(ResourceProperties.PROP_CALENDAR_EVENT_FIELDS); } // getEventFields /** * Set the extra fields kept for each event in this calendar. * * @param meta * The extra fields kept for each event in this calendar, formatted into a single string. %%% */ public void setEventFields(String fields) { m_properties.addProperty(ResourceProperties.PROP_CALENDAR_EVENT_FIELDS, fields); } // setEventFields /** * Notify the calendar that it has changed * * @param event * The event that caused the update. */ public void notify(Event event) { // notify observers, sending the tracking event to identify the change setChanged(); notifyObservers(event); } // notify /** * Serialize the resource into XML, adding an element to the doc under the top of the stack element. * * @param doc * The DOM doc to contain the XML (or null for a string return). * @param stack * The DOM elements, the top of which is the containing element of the new "resource" element. * @return The newly added element. */ public Element toXml(Document doc, Stack stack) { Element calendar = doc.createElement("calendar"); if (stack.isEmpty()) { doc.appendChild(calendar); } else { ((Element) stack.peek()).appendChild(calendar); } stack.push(calendar); calendar.setAttribute("context", m_context); calendar.setAttribute("id", m_id); // properties m_properties.toXml(doc, stack); stack.pop(); return calendar; } // toXml /** * Find the event, in cache or info store - cache it if newly found. * * @param eventId * The id of the event. * @return The event, if found. */ protected CalendarEvent findEvent(String eventId) { CalendarEvent e = null; // if the id has a time range encoded, as for one of a sequence of recurring events, separate that out TimeRange timeRange = null; int sequence = 0; if (eventId.startsWith("!")) { String[] parts = eventId.substring(1).split("!"); try { timeRange = m_timeService.newTimeRange(parts[0]); sequence = Integer.parseInt(parts[1]); eventId = parts[2]; } catch (Exception ex) { M_log.warn("findEvent: exception parsing eventId: " + eventId + " : " + ex); } } // events are cached with the full reference as key String key = eventReference(m_context, m_id, eventId); // if cache is disabled, don't use it if ((!m_caching) || (m_calendarCache == null) || (m_calendarCache.disabled())) { // if we have "cached" the entire set of events in the thread, get that and find our message there List events = (List) m_threadLocalManager.get(getReference() + ".events"); if (events != null) { for (Iterator i = events.iterator(); i.hasNext();) { CalendarEvent event = (CalendarEvent) i.next(); if (event.getId().equals(eventId)) { e = event; break; } } } if (e == null) { e = m_storage.getEvent(this, eventId); } } else { // find the event cache Cache eventCache = (Cache) m_eventCaches.get(getReference()); if (eventCache == null) { synchronized (m_eventCaches) { // check again eventCache = (Cache) m_eventCaches.get(getReference()); // if still not there, make one if (eventCache == null) { eventCache = m_memoryService .newCache( "org.sakaiproject.calendar.api.CalendarService.eventCache", service(), eventReference( m_context, m_id, "")); m_eventCaches.put(getReference(), eventCache); } } } // if we have it cached, use it (even if it's cached as a null, a miss) if (eventCache.containsKey(key)) { e = (CalendarEvent) eventCache.get(key); } // if not in the cache, see if we have it in our info store if ( e == null ) //SAK-12447 cache.get can return null on expired { e = m_storage.getEvent(this, eventId); // if so, cache it, even misses eventCache.put(key, e); } } // now we have the primary event, if we have a recurring event sequence time range selector, use it if ((e != null) && (timeRange != null)) { timeRange.adjust(timeRange, e.getRange()); e = new BaseCalendarEventEdit(e, new RecurrenceInstance(timeRange, sequence)); } return e; } // findEvent /** * Access the event code for this edit. * * @return The event code for this edit. */ protected String getEvent() { return m_event; } /** * Set the event code for this edit. * * @param event * The event code for this edit. */ protected void setEvent(String event) { m_event = event; } /** * Access the resource's properties for modification * * @return The resource's properties. */ public ResourcePropertiesEdit getPropertiesEdit() { return m_properties; } // getPropertiesEdit /** * Enable editing. */ protected void activate() { m_active = true; } // activate /** * Check to see if the edit is still active, or has already been closed. * * @return true if the edit is active, false if it's been closed. */ public boolean isActiveEdit() { return m_active; } // isActiveEdit /** * Close the edit object - it cannot be used after this. */ protected void closeEdit() { m_active = false; } // closeEdit /** * {@inheritDoc} */ public Collection getGroupsAllowAddEvent() { return getGroupsAllowFunction(AUTH_ADD_CALENDAR); } /** * {@inheritDoc} */ public Collection getGroupsAllowGetEvent() { return getGroupsAllowFunction(AUTH_READ_CALENDAR); } /** * {@inheritDoc} */ public Collection getGroupsAllowRemoveEvent( boolean own ) { return getGroupsAllowFunction(own ? AUTH_REMOVE_CALENDAR_OWN : AUTH_REMOVE_CALENDAR_ANY ); } /** * Get the groups of this channel's contex-site that the end user has permission to "function" in. * @param function The function to check */ protected Collection getGroupsAllowFunction(String function) { Vector rv = new Vector(); try { // get the channel's site's groups Site site = m_siteService.getSite(m_context); Collection groups = site.getGroups(); // if the user has SECURE_ALL_GROUPS in the context (site), and the function for the calendar (calendar,site), select all site groups if ((m_securityService.isSuperUser()) || (m_authzGroupService.isAllowed(m_sessionManager.getCurrentSessionUserId(), SECURE_ALL_GROUPS, m_siteService.siteReference(m_context)) && unlockCheck(function, getReference()))) { rv.addAll( groups ); Collections.sort( rv, groupComparator ); return (Collection)rv; } // otherwise, check the groups for function // get a list of the group refs, which are authzGroup ids Collection groupRefs = new Vector(); for (Iterator i = groups.iterator(); i.hasNext();) { Group group = (Group) i.next(); groupRefs.add(group.getReference()); } // ask the authzGroup service to filter them down based on function groupRefs = m_authzGroupService.getAuthzGroupsIsAllowed(m_sessionManager.getCurrentSessionUserId(), function, groupRefs); // pick the Group objects from the site's groups to return, those that are in the groupRefs list for (Iterator i = groups.iterator(); i.hasNext();) { Group group = (Group) i.next(); if (groupRefs.contains(group.getReference())) { rv.add(group); } } } catch (IdUnusedException ignore) {} Collections.sort( rv, groupComparator ); return (Collection)rv; } // getGroupsAllowFunction /****************************************************************************************************************************************************************************************************************************************************** * SessionBindingListener implementation *****************************************************************************************************************************************************************************************************************************************************/ public void valueBound(SessionBindingEvent event) { } public void valueUnbound(SessionBindingEvent event) { if (M_log.isDebugEnabled()) M_log.debug("valueUnbound()"); // catch the case where an edit was made but never resolved if (m_active) { cancelCalendar(this); } } // valueUnbound /** * Get a ContentHandler suitable for populating this object from SAX Events * @return */ public ContentHandler getContentHandler(Map<String,Object> services) { final Entity thisEntity = this; return new DefaultEntityHandler() { /* (non-Javadoc) * @see org.sakaiproject.util.DefaultEntityHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes) */ @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if ( doStartElement(uri, localName, qName, attributes) ) { if ( "calendar".equals(qName) && entity == null ) { m_id = attributes.getValue("id"); m_context = attributes.getValue("context"); entity = thisEntity; } else { M_log.warn("Unexpected element "+qName); } } } }; } } // class BaseCalendar /********************************************************************************************************************************************************************************************************************************************************** * CalendarEvent implementation *********************************************************************************************************************************************************************************************************************************************************/ public class BaseCalendarEventEdit implements CalendarEventEdit, SessionBindingListener { /** The calendar in which this event lives. */ protected BaseCalendarEdit m_calendar = null; /** The effective time range. */ protected TimeRange m_range = null; /** * 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). */ protected TimeRange m_baseRange = null; /** The recurrence rule (single rule). */ protected RecurrenceRule m_singleRule = null; /** The exclusion recurrence rule. */ protected RecurrenceRule m_exclusionRule = null; /** The properties. */ protected ResourcePropertiesEdit m_properties = null; /** The event id. */ protected String m_id = null; /** The attachments - dereferencer objects. */ protected List m_attachments = null; /** The event code for this edit. */ protected String m_event = null; /** Active flag. */ protected boolean m_active = false; /** The Collection of groups (authorization group id strings). */ protected Collection m_groups = new Vector(); /** The message access. */ protected EventAccess m_access = EventAccess.SITE; /** * Construct. * * @param calendar * The calendar in which this event lives. * @param id * The event id, unique within the calendar. */ public BaseCalendarEventEdit(Calendar calendar, String id) { m_calendar = (BaseCalendarEdit) calendar; m_id = id; // setup for properties m_properties = new BaseResourcePropertiesEdit(); // init the AttachmentContainer m_attachments = m_entityManager.newReferenceList(); } // BaseCalendarEventEdit /** * Construct as a copy of another event. * * @param other * The other event to copy. */ public BaseCalendarEventEdit(Calendar calendar, CalendarEvent other) { // store the calendar m_calendar = (BaseCalendarEdit) calendar; set(other); } // BaseCalendarEventEdit /** * Construct as a thin copy of another event, with this new time range, and no rules, as part of a recurring event sequence. * * @param other * The other event to copy. * @param ri * The RecurrenceInstance with the time range (and sequence number) to use. */ public BaseCalendarEventEdit(CalendarEvent other, RecurrenceInstance ri) { // store the calendar m_calendar = ((BaseCalendarEventEdit) other).m_calendar; // encode the instance and the other's id into my id m_id = '!' + ri.getRange().toString() + '!' + ri.getSequence() + '!' + ((BaseCalendarEventEdit) other).m_id; // use the new range m_range = (TimeRange) ri.getRange().clone(); m_baseRange = ((BaseCalendarEventEdit) other).m_range; // point at the properties m_properties = ((BaseCalendarEventEdit) other).m_properties; m_access = ((BaseCalendarEventEdit) other).m_access; // point at the groups m_groups = ((BaseCalendarEventEdit) other).m_groups; // point at the attachments m_attachments = ((BaseCalendarEventEdit) other).m_attachments; // point at the rules m_singleRule = ((BaseCalendarEventEdit) other).m_singleRule; m_exclusionRule = ((BaseCalendarEventEdit) other).m_exclusionRule; } // BaseCalendarEventEdit /** * Construct from an existing definition, in xml. * * @param calendar * The calendar in which this event lives. * @param el * The event in XML in a DOM element. */ public BaseCalendarEventEdit(Calendar calendar, Element el) { m_calendar = (BaseCalendarEdit) calendar; m_properties = new BaseResourcePropertiesEdit(); m_attachments = m_entityManager.newReferenceList(); m_id = el.getAttribute("id"); m_range = m_timeService.newTimeRange(el.getAttribute("range")); m_access = CalendarEvent.EventAccess.SITE; String access_str = el.getAttribute("access").toString(); if (access_str.equals(CalendarEvent.EventAccess.GROUPED.toString())) m_access = CalendarEvent.EventAccess.GROUPED; // the children (props / attachments / rules) NodeList children = el.getChildNodes(); final int length = children.getLength(); for (int i = 0; i < length; i++) { Node child = children.item(i); if (child.getNodeType() == Node.ELEMENT_NODE) { Element element = (Element) child; // look for an attachment if (element.getTagName().equals("attachment")) { m_attachments.add(m_entityManager.newReference(element.getAttribute("relative-url"))); } // look for properties else if (element.getTagName().equals("properties")) { // re-create properties m_properties = new BaseResourcePropertiesEdit(element); } else if (element.getTagName().equals("group")) { m_groups.add(element.getAttribute("authzGroup")); } // else look for rules else if (element.getTagName().equals("rules")) { // children are "rule" elements NodeList ruleChildren = element.getChildNodes(); final int ruleChildrenLength = ruleChildren.getLength(); for (int iRuleChildren = 0; iRuleChildren < ruleChildrenLength; iRuleChildren++) { Node ruleChildNode = ruleChildren.item(iRuleChildren); if (ruleChildNode.getNodeType() == Node.ELEMENT_NODE) { Element ruleChildElement = (Element) ruleChildNode; // look for a rule or exclusion rule if (ruleChildElement.getTagName().equals("rule") || ruleChildElement.getTagName().equals("ex-rule")) { // get the rule name - modern style encoding String ruleName = StringUtils.trimToNull(ruleChildElement.getAttribute("name")); // deal with old data if (ruleName == null) { // get the class - this is old CHEF 1.2.10 style encoding String ruleClassOld = ruleChildElement.getAttribute("class"); // use the last class name minus the package if ( ruleClassOld != null ) ruleName = ruleClassOld.substring(ruleClassOld.lastIndexOf('.') + 1); if ( ruleName == null ) M_log.warn("trouble loading rule"); } if (ruleName != null) { // put my package on the class name String ruleClass = this.getClass().getPackage().getName() + "." + ruleName; // construct try { if (ruleChildElement.getTagName().equals("rule")) { m_singleRule = (RecurrenceRule) Class.forName(ruleClass).newInstance(); m_singleRule.set(ruleChildElement); } else // ruleChildElement.getTagName().equals("ex-rule")) { m_exclusionRule = (RecurrenceRule) Class.forName(ruleClass).newInstance(); m_exclusionRule.set(ruleChildElement); } } catch (Exception e) { M_log.warn("trouble loading rule: " + ruleClass + " : " + e); } } } } } } } } } // BaseCalendarEventEdit /** * */ public BaseCalendarEventEdit(Entity container) { m_calendar = (BaseCalendarEdit) container; m_properties = new BaseResourcePropertiesEdit(); m_attachments = m_entityManager.newReferenceList(); } /** * Take all values from this object. * * @param other * The other object to take values from. */ protected void set(CalendarEvent other) { // copy the id m_id = other.getId(); // copy the range m_range = (TimeRange) other.getRange().clone(); // copy the properties m_properties = new BaseResourcePropertiesEdit(); m_properties.addAll(other.getProperties()); m_access = other.getAccess(); m_groups = new Vector(); m_groups.addAll(other.getGroups()); // copy the attachments m_attachments = m_entityManager.newReferenceList(); replaceAttachments(other.getAttachments()); // copy the rules // %%% deep enough? -ggolden m_singleRule = ((BaseCalendarEventEdit) other).m_singleRule; m_exclusionRule = ((BaseCalendarEventEdit) other).m_exclusionRule; } // set /** * Take some values from this object (not id, not rules). * * @param other * The other object to take values from. */ protected void setPartial(CalendarEvent other) { // copy the range m_range = (TimeRange) other.getRange().clone(); // copy the properties m_properties = new BaseResourcePropertiesEdit(); m_properties.addAll(other.getProperties()); m_access = other.getAccess(); m_groups = new Vector(); m_groups.addAll(other.getGroups()); // copy the attachments m_attachments = m_entityManager.newReferenceList(); replaceAttachments(other.getAttachments()); } // setPartial /** * Access the time range * * @return The event time range */ public TimeRange getRange() { // range might be null in the creation process, before the fields are set in an edit, but // after the storage has registered the event and it's id. if (m_range == null) { return m_timeService.newTimeRange(m_timeService.newTime(0)); } // return (TimeRange) m_range.clone(); return m_range; } // getRange /** * Replace the time range * * @param The * new event time range */ public void setRange(TimeRange range) { m_range = (TimeRange) range.clone(); } // setRange /** * Access the display name property (cover for PROP_DISPLAY_NAME). * * @return The event's display name property. */ public String getDisplayName() { return m_properties.getPropertyFormatted(ResourceProperties.PROP_DISPLAY_NAME); } // getDisplayName /** * Set the display name property (cover for PROP_DISPLAY_NAME). * * @param name * The event's display name property. */ public void setDisplayName(String name) { m_properties.addProperty(ResourceProperties.PROP_DISPLAY_NAME, name); } // setDisplayName /** * Access the description property as plain text. * * @return The event's description property. */ public String getDescription() { return FormattedText.convertFormattedTextToPlaintext(getDescriptionFormatted()); } /** * Access the description property as formatted text. * * @return The event's description property. */ public String getDescriptionFormatted() { // %%% JANDERSE the calendar event description can now be formatted text // first try to use the formatted text description; if that isn't found, use the plaintext description String desc = m_properties.getPropertyFormatted(ResourceProperties.PROP_DESCRIPTION + "-html"); if (desc != null && desc.length() > 0) return desc; desc = m_properties.getPropertyFormatted(ResourceProperties.PROP_DESCRIPTION + "-formatted"); desc = FormattedText.convertOldFormattedText(desc); if (desc != null && desc.length() > 0) return desc; desc = FormattedText.convertPlaintextToFormattedText(m_properties .getPropertyFormatted(ResourceProperties.PROP_DESCRIPTION)); return desc; } // getDescriptionFormatted() /** * Set the description property as plain text. * * @param description * The event's description property. */ public void setDescription(String description) { setDescriptionFormatted(FormattedText.convertPlaintextToFormattedText(description)); } /** * Set the description property as formatted text. * * @param description * The event's description property. */ public void setDescriptionFormatted(String description) { // %%% JANDERSE the calendar event description can now be formatted text // save both a formatted and a plaintext version of the description m_properties.addProperty(ResourceProperties.PROP_DESCRIPTION + "-html", description); m_properties.addProperty(ResourceProperties.PROP_DESCRIPTION, FormattedText .convertFormattedTextToPlaintext(description)); } // setDescriptionFormatted() /** * Access the type (cover for PROP_CALENDAR_TYPE). * * @return The event's type property. */ public String getType() { return m_properties.getPropertyFormatted(ResourceProperties.PROP_CALENDAR_TYPE); } // getType /** * Set the type (cover for PROP_CALENDAR_TYPE). * * @param type * The event's type property. */ public void setType(String type) { m_properties.addProperty(ResourceProperties.PROP_CALENDAR_TYPE, type); } // setType /** * Access the location (cover for PROP_CALENDAR_LOCATION). * * @return The event's location property. */ public String getLocation() { return m_properties.getPropertyFormatted(ResourceProperties.PROP_CALENDAR_LOCATION); } // getLocation /** * Gets the recurrence rule, if any. * * @return The recurrence rule, or null if none. */ public RecurrenceRule getRecurrenceRule() { return m_singleRule; } // getRecurrenceRule /** * Gets the exclusion recurrence rule, if any. * * @return The exclusionrecurrence rule, or null if none. */ public RecurrenceRule getExclusionRule() { if (m_exclusionRule == null) m_exclusionRule = new ExclusionSeqRecurrenceRule(); return m_exclusionRule; } // getExclusionRule /* * 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. * * @param range * The time range bounds for the events returned. * @return a List (CalendarEvent) of all events and recurrences within the time range, including this, possibly empty. */ protected List resolve(TimeRange range) { List rv = new Vector(); // for no rules, use the event if it's in range if (m_singleRule == null) { // the actual event if (range.overlaps(getRange())) { rv.add(this); } } // for rules... else { // for a re-occurring event, the time zone where the first event is created // is passed as a parameter (timezone) to correctly generate the instances String timeZoneID = this.getField("createdInTimeZone"); TimeZone timezone = null; if (timeZoneID.equals("")) { timezone = m_timeService.getLocalTimeZone(); } else { timezone = TimeZone.getTimeZone(timeZoneID); } List instances = m_singleRule.generateInstances(this.getRange(), range, timezone); // remove any excluded getExclusionRule().excludeInstances(instances); for (Iterator iRanges = instances.iterator(); iRanges.hasNext();) { RecurrenceInstance ri = (RecurrenceInstance) iRanges.next(); // generate an event object that is exactly like me but with this range and no rules CalendarEvent clone = new BaseCalendarEventEdit(this, ri); rv.add(clone); } } return rv; } // resolve /** * Get the value of an "extra" event field. * * @param name * The name of the field. * @return the value of the "extra" event field. */ public String getField(String name) { name = FormattedText.unEscapeHtml(name); // names are prefixed to form a namespace name = ResourceProperties.PROP_CALENDAR_EVENT_FIELDS + "." + name; return m_properties.getPropertyFormatted(name); } // getField /** * Set the value of an "extra" event field. * * @param name * The "extra" field name * @param value * The value to set, or null to remove the field. */ public void setField(String name, String value) { // names are prefixed to form a namespace name = ResourceProperties.PROP_CALENDAR_EVENT_FIELDS + "." + name; if (value == null) { m_properties.removeProperty(name); } else { m_properties.addProperty(name, value); } } // setField /** * Set the location (cover for PROP_CALENDAR_LOCATION). * * @param location * The event's location property. */ public void setLocation(String location) { m_properties.addProperty(ResourceProperties.PROP_CALENDAR_LOCATION, location); } // setLocation /** * Gets the event creator (userid), if any (cover for PROP_CREATOR). * @return The event's creator property. */ public String getCreator() { return m_properties.getProperty(ResourceProperties.PROP_CREATOR); } // getCreator /** * Returns true if current user is thhe event's owner/creator * @return boolean true or false */ public boolean isUserOwner() { String currentUser = m_sessionManager.getCurrentSessionUserId(); String eventOwner = this.getCreator(); // for backward compatibility, treat unowned event as if it owned by this user return (eventOwner == null || eventOwner.equals("") || (currentUser != null && currentUser.equals(eventOwner)) ); } /** * Set the event creator (cover for PROP_CREATOR) to current user */ public void setCreator() { String currentUser = m_sessionManager.getCurrentSessionUserId(); String now = m_timeService.newTime().toString(); m_properties.addProperty(ResourceProperties.PROP_CREATOR, currentUser); m_properties.addProperty(ResourceProperties.PROP_CREATION_DATE, now); } // setCreator /** * Gets the event modifier (userid), if any (cover for PROP_MODIFIED_BY). * @return The event's modified-by property. */ public String getModifiedBy() { return m_properties.getPropertyFormatted(ResourceProperties.PROP_MODIFIED_BY); } // getModifiedBy /** * Set the event modifier (cover for PROP_MODIFIED_BY) to current user */ public void setModifiedBy() { String currentUser = m_sessionManager.getCurrentSessionUserId(); String now = m_timeService.newTime().toString(); m_properties.addProperty(ResourceProperties.PROP_MODIFIED_BY, currentUser); m_properties.addProperty(ResourceProperties.PROP_MODIFIED_DATE, now); } // setModifiedBy /** * Sets the recurrence rule. * * @param rule * The recurrence rule, or null to clear out the rule. */ public void setRecurrenceRule(RecurrenceRule rule) { m_singleRule = rule; } // setRecurrenceRule /** * Sets the exclusion recurrence rule. * * @param rule * The recurrence rule, or null to clear out the rule. */ public void setExclusionRule(RecurrenceRule rule) { m_exclusionRule = rule; } // setExclusionRule /** * Access the id of the resource. * * @return The id. */ public String getId() { return m_id; } // getId /** * Access the URL which can be used to access the resource. * * @return The URL which can be used to access the resource. */ public String getUrl() { return m_calendar.getUrl() + getId(); } // getUrl /** * Access the internal reference which can be used to access the resource from within the system. * * @return The the internal reference which can be used to access the resource from within the system. */ public String getReference() { return eventReference(m_calendar.getContext(), m_calendar.getId(), getId()); } // getReference /** * @inheritDoc */ public String getReference(String rootProperty) { return getReference(); } /** * @inheritDoc */ public String getUrl(String rootProperty) { return getUrl(); } /** * Access the event's properties. * * @return The event's properties. */ public ResourceProperties getProperties() { return m_properties; } // getProperties /** * Gets a site name for this calendar event */ public String getSiteName() { String calendarName = ""; if (m_calendar != null) { try { Site site = m_siteService.getSite(m_calendar.getContext()); if (site != null) calendarName = site.getTitle(); } catch (IdUnusedException e) { M_log.warn(".getSiteName(): " + e); } } return calendarName; } /** * Notify the event that it has changed. * * @param event * The event that caused the update. */ public void notify(Event event) { m_calendar.notify(event); } // notify /** * Compare one event to another, based on range. * * @param o * The object to be compared. * @return A negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object. */ public int compareTo(Object o) { if (!(o instanceof CalendarEvent)) throw new ClassCastException(); Time mine = getRange().firstTime(); Time other = ((CalendarEvent) o).getRange().firstTime(); if (mine.before(other)) return -1; if (mine.after(other)) return +1; return 0; // %%% perhaps check the rest of the range if the starts are the same? } /** * Serialize the resource into XML, adding an element to the doc under the top of the stack element. * * @param doc * The DOM doc to contain the XML (or null for a string return). * @param stack * The DOM elements, the top of which is the containing element of the new "resource" element. * @return The newly added element. */ public Element toXml(Document doc, Stack stack) { Element event = doc.createElement("event"); if (stack.isEmpty()) { doc.appendChild(event); } else { ((Element) stack.peek()).appendChild(event); } stack.push(event); event.setAttribute("id", getId()); event.setAttribute("range", getRange().toString()); // add access event.setAttribute("access", m_access.toString()); // add groups if ((m_groups != null) && (m_groups.size() > 0)) { for (Iterator i = m_groups.iterator(); i.hasNext();) { String group = (String) i.next(); Element sect = doc.createElement("group"); event.appendChild(sect); sect.setAttribute("authzGroup", group); } } // properties m_properties.toXml(doc, stack); if ((m_attachments != null) && (m_attachments.size() > 0)) { for (int i = 0; i < m_attachments.size(); i++) { Reference attch = (Reference) m_attachments.get(i); Element attachment = doc.createElement("attachment"); event.appendChild(attachment); attachment.setAttribute("relative-url", attch.getReference()); } } // rules if (m_singleRule != null) { Element rules = doc.createElement("rules"); event.appendChild(rules); stack.push(rules); // the rule m_singleRule.toXml(doc, stack); // the exculsions if (m_exclusionRule != null) { m_exclusionRule.toXml(doc, stack); } stack.pop(); } stack.pop(); return event; } // toXml /** * Access the event code for this edit. * * @return The event code for this edit. */ protected String getEvent() { return m_event; } /** * Set the event code for this edit. * * @param event * The event code for this edit. */ protected void setEvent(String event) { m_event = event; } /** * Access the resource's properties for modification * * @return The resource's properties. */ public ResourcePropertiesEdit getPropertiesEdit() { return m_properties; } // getPropertiesEdit /** * Enable editing. */ protected void activate() { m_active = true; } // activate /** * Check to see if the edit is still active, or has already been closed. * * @return true if the edit is active, false if it's been closed. */ public boolean isActiveEdit() { return m_active; } // isActiveEdit /** * Close the edit object - it cannot be used after this. */ protected void closeEdit() { m_active = false; } // closeEdit /****************************************************************************************************************************************************************************************************************************************************** * AttachmentContainer implementation *****************************************************************************************************************************************************************************************************************************************************/ /** * Access the attachments of the event. * * @return An copy of the set of attachments (a ReferenceVector containing Reference objects) (may be empty). */ public List getAttachments() { return m_entityManager.newReferenceList(m_attachments); } // getAttachments /** * Add an attachment. * * @param ref * The attachment Reference. */ public void addAttachment(Reference ref) { m_attachments.add(ref); } // addAttachment /** * Remove an attachment. * * @param ref * The attachment Reference to remove (the one removed will equal this, they need not be ==). */ public void removeAttachment(Reference ref) { m_attachments.remove(ref); } // removeAttachment /** * Replace the attachment set. * * @param attachments * A vector of Reference objects that will become the new set of attachments. */ public void replaceAttachments(List attachments) { m_attachments.clear(); if (attachments != null) { Iterator it = attachments.iterator(); while (it.hasNext()) { m_attachments.add(it.next()); } } } // replaceAttachments /** * Clear all attachments. */ public void clearAttachments() { m_attachments.clear(); } // clearAttachments /** * {@inheritDoc} */ public EventAccess getAccess() { return m_access; } /** * {@inheritDoc} */ public Collection getGroups() { return new Vector(m_groups); } /** * {@inheritDoc} */ public Collection getGroupObjects() { Vector rv = new Vector(); if (m_groups != null) { for (Iterator i = m_groups.iterator(); i.hasNext();) { String groupId = (String) i.next(); Group group = m_siteService.findGroup(groupId); if (group != null) { rv.add(group); } } } return rv; } /** * @inheritDoc */ public void setGroupAccess(Collection groups, boolean own) throws PermissionException { // convenience (and what else are we going to do?) if ((groups == null) || (groups.size() == 0)) { clearGroupAccess(); return; } // is there any change? If we are already grouped, and the group list is the same, ignore the call if ((m_access == EventAccess.GROUPED) && (EntityCollections.isEqualEntityRefsToEntities(m_groups, groups))) return; // isolate any groups that would be removed or added Collection addedGroups = new Vector(); Collection removedGroups = new Vector(); EntityCollections.computeAddedRemovedEntityRefsFromNewEntitiesOldRefs(addedGroups, removedGroups, groups, m_groups); // verify that the user has permission to remove if (removedGroups.size() > 0) { // the Group objects the user has remove permission Collection allowedGroups = m_calendar.getGroupsAllowRemoveEvent(own); for (Iterator i = removedGroups.iterator(); i.hasNext();) { String ref = (String) i.next(); // is ref a group the user can remove from? if ( !EntityCollections.entityCollectionContainsRefString(allowedGroups, ref) ) { throw new PermissionException(m_sessionManager.getCurrentSessionUserId(), "access:group:remove", ref); } } } // verify that the user has permission to add in those contexts if (addedGroups.size() > 0) { // the Group objects the user has add permission Collection allowedGroups = m_calendar.getGroupsAllowAddEvent(); for (Iterator i = addedGroups.iterator(); i.hasNext();) { String ref = (String) i.next(); // is ref a group the user can remove from? if (!EntityCollections.entityCollectionContainsRefString(allowedGroups, ref)) { throw new PermissionException(m_sessionManager.getCurrentSessionUserId(), "access:group:add", ref); } } } // we are clear to perform this m_access = EventAccess.GROUPED; EntityCollections.setEntityRefsFromEntities(m_groups, groups); } /** * @inheritDoc */ public void clearGroupAccess() throws PermissionException { // is there any change? If we are already channel, ignore the call if (m_access == EventAccess.SITE) return; // verify that the user has permission to add in the calendar context boolean allowed = m_calendar.allowAddCalendarEvent(); if (!allowed) { throw new PermissionException(m_sessionManager.getCurrentSessionUserId(), "access:channel", getReference()); } // we are clear to perform this m_access = EventAccess.SITE; m_groups.clear(); } /****************************************************************************************************************************************************************************************************************************************************** * SessionBindingListener implementation *****************************************************************************************************************************************************************************************************************************************************/ public void valueBound(SessionBindingEvent event) { } public void valueUnbound(SessionBindingEvent event) { if (M_log.isDebugEnabled()) M_log.debug("valueUnbound()"); // catch the case where an edit was made but never resolved if (m_active) { m_calendar.cancelEvent(this); } } // valueUnbound /** * Gets the containing calendar's reference. * * @return The containing calendar reference. */ public String getCalendarReference() { return m_calendar.getReference(); } // getCalendarReference public String getGroupRangeForDisplay(Calendar cal) { // TODO: check this - if used for the UI list, it needs the user's groups and the event's groups... -ggolden if (m_access.equals(CalendarEvent.EventAccess.SITE)) { return ""; } else { int count = 0; String allGroupString=""; try { Site site = m_siteService.getSite(cal.getContext()); for (Iterator i= m_groups.iterator(); i.hasNext();) { Group aGroup = site.getGroup((String) i.next()); if (aGroup != null) { count++; if (count > 1) { allGroupString = allGroupString.concat(", ").concat(aGroup.getTitle()); } else { allGroupString = aGroup.getTitle(); } } } } catch (IdUnusedException e) { // No site available. } return allGroupString; } } /** * Get a content handler suitable for populating this object from SAX events * @return */ public ContentHandler getContentHandler(final Map<String,Object> services) { final Entity thisEntity = this; return new DefaultEntityHandler() { /* (non-Javadoc) * @see org.sakaiproject.util.DefaultEntityHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes) */ @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if ( doStartElement(uri, localName, qName, attributes) ) { if ( "event".equals(qName) && entity == null ) { m_id = attributes.getValue("id"); m_range = m_timeService.newTimeRange(attributes .getValue("range")); m_access = CalendarEvent.EventAccess.SITE; String access_str = String.valueOf(attributes .getValue("access")); if (access_str.equals(CalendarEvent.EventAccess.GROUPED .toString())) m_access = CalendarEvent.EventAccess.GROUPED; entity = thisEntity; } else if ("attachment".equals(qName)) { m_attachments.add(m_entityManager.newReference(attributes .getValue("relative-url"))); } else if ("group".equals(qName)) { m_groups.add(attributes.getValue("authzGroup")); } else if ("rules".equals(qName)) { // we can ignore this as its a contianer } else if ("rule".equals(qName) || "ex-rule".equals(qName)) { // get the rule name - modern style encoding String ruleName = StringUtils.trimToNull(attributes.getValue("name")); // deal with old data if (ruleName == null) { // get the class - this is old CHEF 1.2.10 style encoding String ruleClassOld = attributes.getValue("class"); // use the last class name minus the package if ( ruleClassOld != null ) ruleName = ruleClassOld.substring(ruleClassOld.lastIndexOf('.') + 1); if ( ruleName == null ) M_log.warn("trouble loading rule"); } if (ruleName != null) { // put my package on the class name String ruleClass = this.getClass().getPackage().getName() + "." + ruleName; // construct try { if ("rule".equals(qName)) { m_singleRule = (RecurrenceRule) Class.forName(ruleClass).newInstance(); setContentHandler(m_singleRule.getContentHandler(services), uri, localName, qName, attributes); } else // ("ex-rule".equals(qName)) { m_exclusionRule = (RecurrenceRule) Class.forName(ruleClass).newInstance(); setContentHandler(m_exclusionRule.getContentHandler(services), uri, localName, qName, attributes); } } catch (Exception e) { M_log.warn("trouble loading rule: " + ruleClass + " : " + e); } } } else { M_log.warn("Unexpected Element "+qName); } } } }; } } // BaseCalendarEvent /********************************************************************************************************************************************************************************************************************************************************** * Storage implementation *********************************************************************************************************************************************************************************************************************************************************/ protected interface Storage { /** * Open and read. */ public void open(); /** * Write and Close. */ public void close(); /** * Return the identified calendar, or null if not found. */ public Calendar getCalendar(String ref); /** * Return true if the identified calendar exists. */ public boolean checkCalendar(String ref); /** * Get a list of all calendars */ public List getCalendars(); /** * Keep a new calendar. */ public CalendarEdit putCalendar(String ref); /** * Get a calendar locked for update */ public CalendarEdit editCalendar(String ref); /** * Commit a calendar edit. */ public void commitCalendar(CalendarEdit edit); /** * Cancel a calendar edit. */ public void cancelCalendar(CalendarEdit edit); /** * Forget about a calendar. */ public void removeCalendar(CalendarEdit calendar); /** * Get a event from a calendar. */ public CalendarEvent getEvent(Calendar calendar, String eventId); /** * Get a event from a calendar locked for update */ public CalendarEventEdit editEvent(Calendar calendar, String eventId); /** * Commit an edit. */ public void commitEvent(Calendar calendar, CalendarEventEdit edit); /** * Cancel an edit. */ public void cancelEvent(Calendar calendar, CalendarEventEdit edit); /** * Does this events exist in a calendar? */ public boolean checkEvent(Calendar calendar, String eventId); /** * Get all events from a calendar */ public List getEvents(Calendar calendar); /** * Get the events from a calendar, within this time range */ public List getEvents(Calendar calendar, long l, long m); /** * Make and lock a new event. */ public CalendarEventEdit putEvent(Calendar calendar, String id); /** * Forget about a event. */ public void removeEvent(Calendar calendar, CalendarEventEdit edit); } // Storage /********************************************************************************************************************************************************************************************************************************************************** * CacheRefresher implementation (no container) *********************************************************************************************************************************************************************************************************************************************************/ /** * Get a new value for this key whose value has already expired in the cache. * * @param key * The key whose value has expired and needs to be refreshed. * @param oldValue * The old exipred value of the key. * @param event * The event which triggered this refresh. * @return a new value for use in the cache for this key; if null, the entry will be removed. */ public Object refresh(Object key, Object oldValue, Event event) { Object rv = null; // key is a reference Reference ref = m_entityManager.newReference((String) key); // get from storage only (not cache!) // for events if (REF_TYPE_EVENT.equals(ref.getSubType())) { if (M_log.isDebugEnabled()) M_log.debug("refresh(): key " + key + " calendar id : " + ref.getContext() + "/" + ref.getContainer() + " event id : " + ref.getId()); // get calendar (Note: from the cache is ok) Calendar calendar = findCalendar(calendarReference(ref.getContext(), ref.getContainer())); // get the CalendarEvent (Note: not from cache! but only from storage) if (calendar != null) { rv = m_storage.getEvent(calendar, ref.getId()); } } // for calendar else { if (M_log.isDebugEnabled()) M_log.debug("refresh(): key " + key + " calendar id : " + ref.getReference()); // return the calendar (Note: not from cache! but only from storage) rv = m_storage.getCalendar(ref.getReference()); } return rv; } // refresh /********************************************************************************************************************************************************************************************************************************************************** * StorageUser implementation *********************************************************************************************************************************************************************************************************************************************************/ /** * Construct a new continer given just an id. * * @param ref * The reference for the new object. * @return The new containe Resource. */ public Entity newContainer(String ref) { return new BaseCalendarEdit(ref); } /** * Construct a new container resource, from an XML element. * * @param element * The XML. * @return The new container resource. */ public Entity newContainer(Element element) { return new BaseCalendarEdit(element); } /** * Construct a new container resource, as a copy of another * * @param other * The other contianer to copy. * @return The new container resource. */ public Entity newContainer(Entity other) { return new BaseCalendarEdit((Calendar) other); } /** * Construct a new rsource given just an id. * * @param container * The Resource that is the container for the new resource (may be null). * @param id * The id for the new object. * @param others * (options) array of objects to load into the Resource's fields. * @return The new resource. */ public Entity newResource(Entity container, String id, Object[] others) { return new BaseCalendarEventEdit((Calendar) container, id); } /** * Construct a new resource, from an XML element. * * @param container * The Resource that is the container for the new resource (may be null). * @param element * The XML. * @return The new resource from the XML. */ public Entity newResource(Entity container, Element element) { return new BaseCalendarEventEdit((Calendar) container, element); } /** * Construct a new resource from another resource of the same type. * * @param container * The Resource that is the container for the new resource (may be null). * @param other * The other resource. * @return The new resource as a copy of the other. */ public Entity newResource(Entity container, Entity other) { return new BaseCalendarEventEdit((Calendar) container, (CalendarEvent) other); } /** * Construct a new continer given just an id. * * @param ref * The reference for the new object. * @return The new containe Resource. */ public Edit newContainerEdit(String ref) { BaseCalendarEdit rv = new BaseCalendarEdit(ref); rv.activate(); return rv; } /** * Construct a new container resource, from an XML element. * * @param element * The XML. * @return The new container resource. */ public Edit newContainerEdit(Element element) { BaseCalendarEdit rv = new BaseCalendarEdit(element); rv.activate(); return rv; } /** * Construct a new container resource, as a copy of another * * @param other * The other contianer to copy. * @return The new container resource. */ public Edit newContainerEdit(Entity other) { BaseCalendarEdit rv = new BaseCalendarEdit((Calendar) other); rv.activate(); return rv; } /** * Construct a new rsource given just an id. * * @param container * The Resource that is the container for the new resource (may be null). * @param id * The id for the new object. * @param others * (options) array of objects to load into the Resource's fields. * @return The new resource. */ public Edit newResourceEdit(Entity container, String id, Object[] others) { BaseCalendarEventEdit rv = new BaseCalendarEventEdit((Calendar) container, id); rv.activate(); return rv; } /** * Construct a new resource, from an XML element. * * @param container * The Resource that is the container for the new resource (may be null). * @param element * The XML. * @return The new resource from the XML. */ public Edit newResourceEdit(Entity container, Element element) { BaseCalendarEventEdit rv = new BaseCalendarEventEdit((Calendar) container, element); rv.activate(); return rv; } /** * Construct a new resource from another resource of the same type. * * @param container * The Resource that is the container for the new resource (may be null). * @param other * The other resource. * @return The new resource as a copy of the other. */ public Edit newResourceEdit(Entity container, Entity other) { BaseCalendarEventEdit rv = new BaseCalendarEventEdit((Calendar) container, (CalendarEvent) other); rv.activate(); return rv; } /** * Collect the fields that need to be stored outside the XML (for the resource). * * @return An array of field values to store in the record outside the XML (for the resource). */ public Object[] storageFields(Entity r) { Object[] rv = new Object[4]; TimeRange range = ((CalendarEvent) r).getRange(); rv[0] = range.firstTime(); // %%% fudge? rv[1] = range.lastTime(); // %%% fudge? // we use hours rather than ms for the range to reduce the index size in the database // I dont what to use days just incase we want sub day range finds long oneHour = 60L*60L*1000L; rv[2] = (int)(range.firstTime().getTime()/oneHour); rv[3] = (int)(range.lastTime().getTime()/oneHour); // find the end of the sequence RecurrenceRuleBase rr = (RecurrenceRuleBase)((CalendarEvent) r).getRecurrenceRule(); if ( rr != null ) { Time until = rr.getUntil(); if ( until != null ) { rv[3] = (int)(until.getTime()/oneHour); } else { int count = rr.getCount(); int interval = rr.getInterval(); long endevent = range.lastTime().getTime(); if ( count == 0 ) { rv[3] = Integer.MAX_VALUE-1; // hours since epoch, this represnts 9 Oct 246953 07:00:00 } else { String frequency = rr.getFrequency(); GregorianCalendar c = new GregorianCalendar(); c.setTimeInMillis(endevent); c.add(rr.getRecurrenceType(), count*interval); rv[3] = (int)(c.getTimeInMillis()/oneHour); } } } return rv; } /** * Check if this resource is in draft mode. * * @param r * The resource. * @return true if the resource is in draft mode, false if not. */ public boolean isDraft(Entity r) { return false; } /** * Access the resource owner user id. * * @param r * The resource. * @return The resource owner user id. */ public String getOwnerId(Entity r) { return null; } /** * Access the resource date. * * @param r * The resource. * @return The resource date. */ public Time getDate(Entity r) { return null; } /********************************************************************************************************************************************************************************************************************************************************** * PDF file generation *********************************************************************************************************************************************************************************************************************************************************/ // XSL File Names protected final static String DAY_VIEW_XSLT_FILENAME = "schedule.xsl"; protected final static String LIST_VIEW_XSLT_FILENAME = "schlist.xsl"; protected final static String MONTH_VIEW_XSLT_FILENAME = "schedulemm.xsl"; protected final static String WEEK_VIEW_XSLT_FILENAME = "schedule.xsl"; // FOP Configuration protected final static String FOP_USERCONFIG = "fonts/userconfig.xml"; protected final static String FOP_FONTBASEDIR = "fonts"; // Mime Types protected final static String PDF_MIME_TYPE = "application/pdf"; protected final static String ICAL_MIME_TYPE = "text/calendar"; // Constants for time calculations protected static long MILLISECONDS_IN_DAY = (60 * 60 * 24 * 1000); protected final static long MILLISECONDS_IN_HOUR = (60 * 60 * 1000); protected final static long MILLISECONDS_IN_MINUTE = (1000 * 60); protected static final long MINIMUM_EVENT_LENGTH_IN_MSECONDS = (29 * MILLISECONDS_IN_MINUTE); protected static final int SCHEDULE_INTERVAL_IN_MINUTES = 15; protected static final int MAX_OVERLAPPING_COLUMNS = 7; protected static final int TIMESLOT_FOR_OVERLAP_DETECTION_IN_MINUTES = 10; // URL Parameter Constants protected static final String TIME_RANGE_PARAMETER_NAME = "timeRange"; protected static final String DAILY_START_TIME_PARAMETER_NAME = "dailyStartTime"; protected final static String USER_NAME_PARAMETER_NAME = "user"; protected final static String CALENDAR_PARAMETER_BASE_NAME = "calendar"; protected final static String SCHEDULE_TYPE_PARAMETER_NAME = "scheduleType"; // XML Node/Attribute Names protected static final String COLUMN_NODE_NAME = "col"; protected static final String EVENT_NODE_NAME = "event"; protected static final String FACULTY_EVENT_ATTRIBUTE_NAME = "Faculty"; protected static final String FACULTY_NODE = "faculty"; protected static final String DESCRIPTION_NODE = "description"; protected static final String FROM_ATTRIBUTE_STRING = "from"; protected static final String GROUP_NODE = "grp"; protected static final String LIST_DATE_ATTRIBUTE_NAME = "dt"; protected static final String LIST_DAY_OF_MONTH_ATTRIBUTE_NAME = "dayofmonth"; protected static final String LIST_DAY_OF_WEEK_ATTRIBUTE_NAME = "dayofweek"; protected static final String LIST_NODE_NAME = "list"; protected static final String MONTH_NODE_NAME = "month"; protected static final String MAX_CONCURRENT_EVENTS_NAME = "maxConcurrentEvents"; protected static final String PLACE_NODE = "place"; protected static final String ROW_NODE_NAME = "row"; protected static final String SCHEDULE_NODE = "schedule"; protected static final String START_DAY_WEEK_ATTRIBUTE_NAME = "startdayweek"; protected static final String MONTH_ATTRIBUTE_NAME = "month"; protected static final String YEAR_ATTRIBUTE_NAME = "yyyy"; protected static final String START_TIME_ATTRIBUTE_NAME = "start-time"; protected static final String SUB_EVENT_NODE_NAME = "subEvent"; protected static final String TITLE_NODE = "title"; protected static final String TO_ATTRIBUTE_STRING = "to"; protected static final String TYPE_NODE = "type"; protected static final String UID_NODE = "uid"; // Misc. protected static final String HOUR_MINUTE_SEPARATOR = ":"; /** * 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. */ protected class LayoutRow extends ArrayList { // Union of all event time ranges in this row. private TimeRange rowTimeRange; /** * Gets the union of all event time ranges in this row. */ public TimeRange getRowTimeRange() { return rowTimeRange; } /** * Sets the union of all event time ranges in this row. */ public void setRowTimeRange(TimeRange range) { rowTimeRange = range; } } /** * Table used to layout a single day, with potentially overlapping events. */ protected class SingleDayLayoutTable { protected long millisecondsPerTimeslot; protected int numCols; protected int numRows; protected ArrayList rows; // Overall time range for this table. protected TimeRange timeRange; /** * Constructor for SingleDayLayoutTable */ public SingleDayLayoutTable(TimeRange timeRange, int maxNumberOverlappingEvents, int timeslotInMinutes) { this.timeRange = timeRange; numCols = maxNumberOverlappingEvents; millisecondsPerTimeslot = timeslotInMinutes * MILLISECONDS_IN_MINUTE; numRows = getNumberOfRowsNeeded(timeRange); rows = new ArrayList(numRows); for (int i = 0; i < numRows; i++) { ArrayList newRow = new ArrayList(numCols); rows.add(i, newRow); for (int j = 0; j < numCols; j++) { newRow.add(j, new LayoutTableCell()); } } } /** * Adds an event to the SingleDayLayoutTable */ public void addEvent(CalendarEvent calendarEvent) { if (calendarEvent == null) { return; } int startingRow = getStartingRow(roundRangeToMinimumTimeInterval(calendarEvent.getRange())); int numRowsNeeded = getNumberOfRowsNeeded(roundRangeToMinimumTimeInterval(calendarEvent.getRange())); // Trim to the end of the table. if (startingRow + numRowsNeeded >= getNumRows()) { numRowsNeeded = getNumRows() - startingRow; } // Get the first column that has enough sequential free intervals to // contain this event. int columnNumber = getFreeColumn(startingRow, numRowsNeeded); if (columnNumber != -1) { for (int i = startingRow; i < startingRow + numRowsNeeded; i++) { LayoutTableCell cell = getCell(i, columnNumber); // All cells have the calendar event information. cell.setCalendarEvent(calendarEvent); // Only the first cell is marked as such. if (i == startingRow) { cell.setFirstCell(true); } cell.setFirstCellRow(startingRow); cell.setFirstCellColumn(columnNumber); cell.setThisCellRow(i); cell.setThisCellColumn(columnNumber); cell.setNumCellsInEvent(numRowsNeeded); } } } /** * Convert the time range to fall entirely within the time range of the layout table. */ protected TimeRange adjustTimeRangeToLayoutTable(TimeRange eventTimeRange) { Time lowerBound = null, upperBound = null; // // Make sure that the upper/lower bounds fall within the layout table. // if (this.timeRange.firstTime().compareTo(eventTimeRange.firstTime()) > 0) { lowerBound = this.timeRange.firstTime(); } else { lowerBound = eventTimeRange.firstTime(); } if (this.timeRange.lastTime().compareTo(eventTimeRange.lastTime()) < 0) { upperBound = this.timeRange.lastTime(); } else { upperBound = eventTimeRange.lastTime(); } return m_timeService.newTimeRange(lowerBound, upperBound, true, false); } /** * Returns true if there are any events in this or other rows that overlap the event associated with this cell. */ protected boolean cellHasOverlappingEvents(int rowNum, int colNum) { LayoutTableCell cell = this.getFirstCell(rowNum, colNum); // Start at the first cell of this event and check every row // to see if we find any cells in that row that are not empty // and are not one of ours. if (cell != null && !cell.isEmptyCell()) { for (int i = cell.getFirstCellRow(); i < (cell.getFirstCellRow() + cell.getNumCellsInEvent()); i++) { for (int j = 0; j < this.numCols; j++) { LayoutTableCell curCell = this.getCell(i, j); if (curCell != null && !curCell.isEmptyCell() && curCell.getCalendarEvent() != cell.getCalendarEvent()) { return true; } } } } return false; } /** * Get a particular cell. Returns a reference to the actual cell and not a copy. */ protected LayoutTableCell getCell(int rowNum, int colNum) { if (rowNum < 0 || rowNum >= this.numRows || colNum < 0 || colNum >= this.numCols) { // Illegal cell indices return null; } else { ArrayList row = (ArrayList) rows.get(rowNum); return (LayoutTableCell) row.get(colNum); } } /** * Gets the first cell associated with the event that's stored at this row/column */ protected LayoutTableCell getFirstCell(int rowNum, int colNum) { LayoutTableCell cell = this.getCell(rowNum, colNum); if (cell == null || cell.isEmptyCell()) { return null; } else { return getCell(cell.getFirstCellRow(), cell.getFirstCellColumn()); } } /** * Looks for a column where the whole event can be placed. */ protected int getFreeColumn(int rowNum, int numberColumnsNeeded) { // Keep going through the columns until we hit one that has // enough empty cells to accomodate our event. for (int i = 0; i < this.numCols; i++) { boolean foundOccupiedCell = false; for (int j = rowNum; j < rowNum + numberColumnsNeeded; j++) { LayoutTableCell cell = getCell(j, i); if (cell == null) { // Out of range. return -1; } if (!cell.isEmptyCell()) { foundOccupiedCell = true; break; } } if (!foundOccupiedCell) { return i; } } return -1; } /** * 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. */ public List getLayoutRows() { List allRows = new ArrayList(); // Scan all rows in the table. for (int mainRowIndex = 0; mainRowIndex < this.getNumRows(); mainRowIndex++) { // If we hit a starting row, then iterate through all rows of the // event group. if (isStartingRowOfGroup(mainRowIndex)) { LayoutRow newRow = new LayoutRow(); allRows.add(newRow); int numRowsInGroup = getNumberRowsInEventGroup(mainRowIndex); newRow.setRowTimeRange(getTimeRangeForEventGroup(mainRowIndex, numRowsInGroup)); for (int columnIndex = 0; columnIndex < this.getNumCols(); columnIndex++) { List columnList = new ArrayList(); boolean addedCell = false; for (int eventGroupRowIndex = mainRowIndex; eventGroupRowIndex < mainRowIndex + numRowsInGroup; eventGroupRowIndex++) { LayoutTableCell cell = getCell(eventGroupRowIndex, columnIndex); if (cell.isFirstCell()) { columnList.add(cell.getCalendarEvent()); addedCell = true; } } // Don't add to our list unless we actually added a cell. if (addedCell) { newRow.add(columnList); } } // Get ready for the next iteration. Skip those // rows that we have already processed. mainRowIndex += (numRowsInGroup - 1); } } return allRows; } protected int getNumberOfRowsNeeded(TimeRange eventTimeRange) { TimeRange adjustedTimeRange = adjustTimeRangeToLayoutTable(eventTimeRange); // Use the ceiling function to obtain the next highest integral number of time slots. return (int) (Math.ceil((double) (adjustedTimeRange.duration()) / (double) millisecondsPerTimeslot)); } /** * 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. */ protected int getNumberRowsInEventGroup(int rowNum) { int numEventRows = 0; if (isStartingRowOfGroup(rowNum)) { numEventRows++; // Keep going unless we see an all empty row // or another starting row. for (int i = rowNum + 1; i < this.getNumRows() && !isEmptyRow(i) && !isStartingRowOfGroup(i); i++) { numEventRows++; } } return numEventRows; } /** * Gets the total number of columns in the layout table. */ public int getNumCols() { return this.numCols; } /** * Gets the total number of rows in the layout table. */ public int getNumRows() { return rows.size(); } /** * Given a time range, returns the starting row number in the layout table. */ protected int getStartingRow(TimeRange eventTimeRange) { TimeRange adjustedTimeRange = adjustTimeRangeToLayoutTable(eventTimeRange); TimeRange timeRangeToStart = m_timeService.newTimeRange(this.timeRange.firstTime(), adjustedTimeRange.firstTime(), true, true); // // We form a new time range where the ending time is the (adjusted) event // time range and the starting time is the starting time of the layout table. // The number of rows required for this range will be the starting row of the table. // return getNumberOfRowsNeeded(timeRangeToStart); } /** * 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. */ public TimeRange getTimeRangeForEventGroup(int rowNum, int numRowsInThisEventGroup) { Time firstTime = null; Time lastTime = null; for (int i = rowNum; i < rowNum + numRowsInThisEventGroup; i++) { for (int j = 0; j < this.getNumCols(); j++) { LayoutTableCell cell = getCell(i, j); CalendarEvent event = cell.getCalendarEvent(); if (event != null) { TimeRange adjustedTimeRange = adjustTimeRangeToLayoutTable(roundRangeToMinimumTimeInterval(cell .getCalendarEvent().getRange())); // // Replace our earliest time to date with the // time from the event, if the time from the // event is earlier. // if (firstTime == null) { firstTime = adjustedTimeRange.firstTime(); } else { Time eventFirstTime = adjustedTimeRange.firstTime(); if (eventFirstTime.compareTo(firstTime) < 0) { firstTime = eventFirstTime; } } // // Replace our latest time to date with the // time from the event, if the time from the // event is later. // if (lastTime == null) { lastTime = adjustedTimeRange.lastTime(); } else { Time eventLastTime = adjustedTimeRange.lastTime(); if (eventLastTime.compareTo(lastTime) > 0) { lastTime = eventLastTime; } } } } } return m_timeService.newTimeRange(firstTime, lastTime, true, false); } /** * Returns true if this row has only empty cells. */ protected boolean isEmptyRow(int rowNum) { boolean sawNonEmptyCell = false; for (int i = 0; i < this.getNumCols(); i++) { LayoutTableCell cell = getCell(rowNum, i); if (!cell.isEmptyCell()) { sawNonEmptyCell = true; break; } } return !sawNonEmptyCell; } /** * Returns true if this row has only starting cells and no continuation cells. */ protected boolean isStartingRowOfGroup(int rowNum) { boolean sawContinuationCells = false; boolean sawFirstCell = false; for (int i = 0; i < this.getNumCols(); i++) { LayoutTableCell cell = getCell(rowNum, i); if (cell.isContinuationCell()) { sawContinuationCells = true; } if (cell.isFirstCell) { sawFirstCell = true; } } // // In order to be a starting row must have a "first" // cell no continuation cells. // return (!sawContinuationCells && sawFirstCell); } /** * Returns true if there are any cells in this row associated with events which overlap each other in this row or any other row. */ public boolean rowHasOverlappingEvents(int rowNum) { for (int i = 0; i < this.getNumCols(); i++) { if (cellHasOverlappingEvents(rowNum, i)) { return true; } } return false; } } /** * This is a single cell in a layout table (an instance of SingleDayLayoutTable). */ protected class LayoutTableCell { protected CalendarEvent calendarEvent = null; protected int firstCellColumn = -1; protected int firstCellRow = -1; protected boolean isFirstCell = false; protected int numCellsInEvent = 0; protected int thisCellColumn = -1; protected int thisCellRow = -1; /** * Gets the calendar event associated with this cell. */ public CalendarEvent getCalendarEvent() { return calendarEvent; } /** * Gets the first column associated with this cell. */ public int getFirstCellColumn() { return firstCellColumn; } /** * Gets the first row associated with this cell. */ public int getFirstCellRow() { return firstCellRow; } /** * Get the number of cells in this event. */ public int getNumCellsInEvent() { return numCellsInEvent; } /** * Gets the column associated with this particular cell. */ public int getThisCellColumn() { return thisCellColumn; } /** * Gets the row associated with this cell. */ public int getThisCellRow() { return thisCellRow; } /** * Returns true if this cell is a continuation of an event and not the first cell in the event. */ public boolean isContinuationCell() { return !isFirstCell() && !isEmptyCell(); } /** * Returns true if this cell is not associated with any events. */ public boolean isEmptyCell() { return calendarEvent == null; } /** * Returns true if this is the first cell in a column of cells associated with an event. */ public boolean isFirstCell() { return isFirstCell; } /** * Set the calendar event associated with this cell. */ public void setCalendarEvent(CalendarEvent event) { calendarEvent = event; } /** * Set flag indicating that this is the first cell in column of cells associated with an event. */ public void setFirstCell(boolean b) { isFirstCell = b; } /** * Sets a value in this cell to point to the very first cell in the column of cells associated with this event. */ public void setFirstCellColumn(int i) { firstCellColumn = i; } /** * Sets a value in this cell to point to the very first cell in the column of cells associated with this event. */ public void setFirstCellRow(int i) { firstCellRow = i; } /** * Gets the number of cells (if any) in the group of cells associated with this cell by event. */ public void setNumCellsInEvent(int i) { numCellsInEvent = i; } /** * Sets the actual column index for this cell. */ public void setThisCellColumn(int i) { thisCellColumn = i; } /** * Sets the actual row index for this cell. */ public void setThisCellRow(int i) { thisCellRow = i; } } /** * Debugging routine to get a string for a TimeRange. This should probably be in the TimeRange class. */ protected String dumpTimeRange(TimeRange timeRange) { String returnString = ""; if (timeRange != null) { returnString = timeRange.firstTime().toStringLocalFull() + " - " + timeRange.lastTime().toStringLocalFull(); } return returnString; } /** * Takes a DOM structure and renders a PDF * * @param doc * DOM structure * @param xslFileName * XSL file to use to translate the DOM document to FOP */ protected void generatePDF(Document doc, String xslFileName, OutputStream streamOut) { Driver driver = new Driver(); org.apache.avalon.framework.logger.Logger logger = new ConsoleLogger(ConsoleLogger.LEVEL_ERROR); MessageHandler.setScreenLogger(logger); driver.setLogger(logger); try { String baseDir = getClass().getClassLoader().getResource(FOP_FONTBASEDIR).toString(); Configuration.put("fontBaseDir", baseDir); InputStream userConfig = getClass().getClassLoader().getResourceAsStream(FOP_USERCONFIG); new Options(userConfig); } catch (FOPException fe){ M_log.warn(this+".generatePDF: ", fe); } catch(Exception e){ M_log.warn(this+".generatePDF: ", e); } driver.setOutputStream(streamOut); driver.setRenderer(Driver.RENDER_PDF); try { InputStream in = getClass().getClassLoader().getResourceAsStream(xslFileName); Transformer transformer = transformerFactory.newTransformer(new StreamSource(in)); Source src = new DOMSource(doc); java.util.Calendar c = java.util.Calendar.getInstance(m_timeService.getLocalTimeZone(),new ResourceLoader().getLocale()); CalendarUtil calUtil = new CalendarUtil(c); String[] dayNames = calUtil.getCalendarDaysOfWeekNames(true); // Kludge: Xalan in JDK 1.4/1.5 does not properly resolve java classes // (http://xml.apache.org/xalan-j/faq.html#jdk14) // Clean this up in JDK 1.6 and pass ResourceBundle/ArrayList parms transformer.setParameter("dayNames0", dayNames[0]); transformer.setParameter("dayNames1", dayNames[1]); transformer.setParameter("dayNames2", dayNames[2]); transformer.setParameter("dayNames3", dayNames[3]); transformer.setParameter("dayNames4", dayNames[4]); transformer.setParameter("dayNames5", dayNames[5]); transformer.setParameter("dayNames6", dayNames[6]); transformer.setParameter("jan", rb.getString("month.jan")); transformer.setParameter("feb", rb.getString("month.feb")); transformer.setParameter("mar", rb.getString("month.mar")); transformer.setParameter("apr", rb.getString("month.apr")); transformer.setParameter("may", rb.getString("month.may")); transformer.setParameter("jun", rb.getString("month.jun")); transformer.setParameter("jul", rb.getString("month.jul")); transformer.setParameter("aug", rb.getString("month.aug")); transformer.setParameter("sep", rb.getString("month.sep")); transformer.setParameter("oct", rb.getString("month.oct")); transformer.setParameter("nov", rb.getString("month.nov")); transformer.setParameter("dec", rb.getString("month.dec")); transformer.setParameter("site", rb.getString("event.site")); transformer.setParameter("event", rb.getString("event.event")); transformer.setParameter("location", rb.getString("event.location")); transformer.setParameter("type", rb.getString("event.type")); transformer.setParameter("from", rb.getString("event.from")); transformer.setParameter("sched", rb.getString("sched.for")); transformer.transform(src, new SAXResult(driver.getContentHandler())); } catch (TransformerException e) { e.printStackTrace(); M_log.warn(this+".generatePDF(): " + e); return; } } /** * Make a full-day time range given a year, month, and day */ protected TimeRange getFullDayTimeRangeFromYMD(int year, int month, int day) { return m_timeService.newTimeRange(m_timeService.newTimeLocal(year, month, day, 0, 0, 0, 0), m_timeService.newTimeLocal(year, month, day, 23, 59, 59, 999)); } /** * Make a list of days for use in generating an XML document for the list view. */ protected List makeListViewTimeRangeList(TimeRange timeRange, List calendarReferenceList) { // This is used to dimension a hash table. The default load factor is .75. // A rehash isn't done until the number of items in the table is .75 * the number // of items in the capacity. final int DEFAULT_INITIAL_HASH_CAPACITY = 150; List listOfDays = new ArrayList(); // Get a list of merged events. CalendarEventVector calendarEventVector = getEvents(calendarReferenceList, timeRange); Iterator itEvents = calendarEventVector.iterator(); HashMap datesSeenSoFar = new HashMap(DEFAULT_INITIAL_HASH_CAPACITY); while (itEvents.hasNext()) { CalendarEvent event = (CalendarEvent) itEvents.next(); // // Each event may span multiple days, so we need to split each // events's time range into single day slots. // List timeRangeList = splitTimeRangeIntoListOfSingleDayTimeRanges(event.getRange(), null); Iterator itDatesInRange = timeRangeList.iterator(); while (itDatesInRange.hasNext()) { TimeRange curDay = (TimeRange) itDatesInRange.next(); String curDate = curDay.firstTime().toStringLocalDate(); if (!datesSeenSoFar.containsKey(curDate)) { // Add this day to list TimeBreakdown startBreakDown = curDay.firstTime().breakdownLocal(); listOfDays.add(getFullDayTimeRangeFromYMD(startBreakDown.getYear(), startBreakDown.getMonth(), startBreakDown .getDay())); datesSeenSoFar.put(curDate, ""); } } } return listOfDays; } /** * @param scheduleType * daily, weekly, monthly, or list (no yearly). * @param doc * XML output document * @param timeRange * 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. * @param dailyTimeRange * 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.) * @param userID * This is the name of the user whose schedule is being printed. */ protected void generateXMLDocument(int scheduleType, Document doc, TimeRange timeRange, TimeRange dailyTimeRange, List calendarReferenceList, String userID) { // This list will have an entry for every week day that we care about. List timeRangeList = null; TimeRange actualTimeRange = null; Element topLevelMaxConcurrentEvents = null; switch (scheduleType) { case WEEK_VIEW: actualTimeRange = timeRange; timeRangeList = getTimeRangeListForWeek(actualTimeRange, calendarReferenceList, dailyTimeRange); break; case MONTH_VIEW: // Make sure that we trim off the days of the previous and next // month. The time range that we're being passed is "padded" // with extra days to make up a full block of an integral number // of seven day weeks. actualTimeRange = shrinkTimeRangeToCurrentMonth(timeRange); timeRangeList = splitTimeRangeIntoListOfSingleDayTimeRanges(actualTimeRange, null); break; case LIST_VIEW: // // With the list view, we want to come up with a list of days // that have events, not every day in the range. // actualTimeRange = timeRange; timeRangeList = makeListViewTimeRangeList(actualTimeRange, calendarReferenceList); break; case DAY_VIEW: // // We have a single entry in the list for a day. Having a singleton // list may seem wasteful, but it allows us to use one loop below // for all processing. // actualTimeRange = timeRange; timeRangeList = splitTimeRangeIntoListOfSingleDayTimeRanges(actualTimeRange, dailyTimeRange); break; default: M_log.warn(".generateXMLDocument(): bad scheduleType parameter = " + scheduleType); break; } if (timeRangeList != null) { // Create Root Element Element root = doc.createElement(SCHEDULE_NODE); if (userID != null) { writeStringNodeToDom(doc, root, UID_NODE, userID); } // Write out the number of events that we have per timeslot. // This is used to figure out how to display overlapping events. // At this level, assume that we start with 1 event. topLevelMaxConcurrentEvents = writeStringNodeToDom(doc, root, MAX_CONCURRENT_EVENTS_NAME, "1"); // Add a start time node. writeStringNodeToDom(doc, root, START_TIME_ATTRIBUTE_NAME, getTimeString(dailyTimeRange.firstTime())); // Add the Root Element to Document doc.appendChild(root); // // Only add a "month" node with the first numeric day // of the month if we're in the month view. // if (scheduleType == MONTH_VIEW) { CalendarUtil monthCalendar = new CalendarUtil(); // Use the middle of the month since the start/end ranges // may be in an adjacent month. TimeBreakdown breakDown = actualTimeRange.firstTime().breakdownLocal(); monthCalendar.setDay(breakDown.getYear(), breakDown.getMonth(), breakDown.getDay()); int firstDayOfMonth = monthCalendar.getFirstDayOfMonth(breakDown.getMonth() - 1); // Create a list of events for the given day. Element monthElement = doc.createElement(MONTH_NODE_NAME); monthElement.setAttribute(START_DAY_WEEK_ATTRIBUTE_NAME, Integer.toString(firstDayOfMonth)); monthElement.setAttribute(MONTH_ATTRIBUTE_NAME, Integer.toString(breakDown.getMonth())); monthElement.setAttribute(YEAR_ATTRIBUTE_NAME, Integer.toString(breakDown.getYear())); root.appendChild(monthElement); } Iterator itList = timeRangeList.iterator(); int maxNumberOfColumnsPerRow = 1; // Go through all the time ranges (days) while (itList.hasNext()) { TimeRange currentTimeRange = (TimeRange) itList.next(); int maxConcurrentEventsOverListNode = 1; // Get a list of merged events. CalendarEventVector calendarEventVector = getEvents(calendarReferenceList, currentTimeRange); // // We don't need to generate "empty" event lists for the list view. // if (scheduleType == LIST_VIEW && calendarEventVector.size() == 0) { continue; } // Create a list of events for the given day. Element eventList = doc.createElement(LIST_NODE_NAME); // Set the current date eventList.setAttribute(LIST_DATE_ATTRIBUTE_NAME, getDateFromTime(currentTimeRange.firstTime())); eventList.setAttribute(LIST_DAY_OF_MONTH_ATTRIBUTE_NAME, getDayOfMonthFromTime(currentTimeRange.firstTime())); // Set the maximum number of events per timeslot // Assume 1 as a starting point. This may be changed // later on. eventList.setAttribute(MAX_CONCURRENT_EVENTS_NAME, Integer.toString(maxConcurrentEventsOverListNode)); // Calculate the day of the week. java.util.Calendar c = java.util.Calendar.getInstance(m_timeService.getLocalTimeZone(),new ResourceLoader().getLocale()); CalendarUtil cal = new CalendarUtil(c); Time date = currentTimeRange.firstTime(); TimeBreakdown breakdown = date.breakdownLocal(); cal.setDay(breakdown.getYear(), breakdown.getMonth(), breakdown.getDay()); // Set the day of the week as a node attribute. eventList.setAttribute(LIST_DAY_OF_WEEK_ATTRIBUTE_NAME, Integer.toString(cal.getDay_Of_Week(true) - 1)); // Attach this list to the top-level node root.appendChild(eventList); Iterator itEvent = calendarEventVector.iterator(); // // Day and week views use a layout table to assist in constructing the // rowspan information for layout. // if (scheduleType == DAY_VIEW || scheduleType == WEEK_VIEW) { SingleDayLayoutTable layoutTable = new SingleDayLayoutTable(currentTimeRange, MAX_OVERLAPPING_COLUMNS, SCHEDULE_INTERVAL_IN_MINUTES); // Load all the events into our layout table. while (itEvent.hasNext()) { CalendarEvent event = (CalendarEvent) itEvent.next(); layoutTable.addEvent(event); } List layoutRows = layoutTable.getLayoutRows(); Iterator rowIterator = layoutRows.iterator(); // Iterate through the list of rows. while (rowIterator.hasNext()) { LayoutRow layoutRow = (LayoutRow) rowIterator.next(); TimeRange rowTimeRange = layoutRow.getRowTimeRange(); if (maxNumberOfColumnsPerRow < layoutRow.size()) { maxNumberOfColumnsPerRow = layoutRow.size(); } if (maxConcurrentEventsOverListNode < layoutRow.size()) { maxConcurrentEventsOverListNode = layoutRow.size(); } Element eventNode = doc.createElement(EVENT_NODE_NAME); eventList.appendChild(eventNode); // Add the "from" time as an attribute. eventNode.setAttribute(FROM_ATTRIBUTE_STRING, getTimeString(rowTimeRange.firstTime())); // Add the "to" time as an attribute. eventNode.setAttribute(TO_ATTRIBUTE_STRING, getTimeString(performEndMinuteKludge(rowTimeRange.lastTime() .breakdownLocal()))); Element rowNode = doc.createElement(ROW_NODE_NAME); // Set an attribute indicating the number of columns in this row. rowNode.setAttribute(MAX_CONCURRENT_EVENTS_NAME, Integer.toString(layoutRow.size())); eventNode.appendChild(rowNode); Iterator layoutRowIterator = layoutRow.iterator(); // Iterate through our list of column lists. while (layoutRowIterator.hasNext()) { Element columnNode = doc.createElement(COLUMN_NODE_NAME); rowNode.appendChild(columnNode); List columnList = (List) layoutRowIterator.next(); Iterator columnListIterator = columnList.iterator(); // Iterate through the list of columns. while (columnListIterator.hasNext()) { CalendarEvent event = (CalendarEvent) columnListIterator.next(); generateXMLEvent(doc, columnNode, event, SUB_EVENT_NODE_NAME, rowTimeRange, true, false, false); } } } } else { // Generate XML for all the events. while (itEvent.hasNext()) { CalendarEvent event = (CalendarEvent) itEvent.next(); generateXMLEvent(doc, eventList, event, EVENT_NODE_NAME, currentTimeRange, false, false, (scheduleType == LIST_VIEW ? true : false)); } } // Update this event after having gone through all the rows. eventList.setAttribute(MAX_CONCURRENT_EVENTS_NAME, Integer.toString(maxConcurrentEventsOverListNode)); } // Set the node value way up at the head of the document to indicate // what the maximum number of columns was for the entire document. topLevelMaxConcurrentEvents.getFirstChild().setNodeValue(Integer.toString(maxNumberOfColumnsPerRow)); } } /** * @param ical * iCal object * @param calendarReferenceList * This is the name of the user whose schedule is being printed. * @return Number of events generated in ical object */ protected int generateICal(net.fortuna.ical4j.model.Calendar ical, String calendarReference) { int numEvents = 0; // This list will have an entry for every week day that we care about. TimeRange currentTimeRange = getICalTimeRange(); // Get a list of events. List calList = new ArrayList(); calList.add(calendarReference); CalendarEventVector calendarEventVector = getEvents(calList, currentTimeRange); Iterator itEvent = calendarEventVector.iterator(); // Generate XML for all the events. while (itEvent.hasNext()) { CalendarEvent event = (CalendarEvent) itEvent.next(); DateTime icalStartDate = new DateTime(event.getRange().firstTime().getTime()); long seconds = event.getRange().duration() / 1000; String timeString = "PT" + String.valueOf(seconds) + "S"; net.fortuna.ical4j.model.Dur duration = new net.fortuna.ical4j.model.Dur( timeString ); VEvent icalEvent = new VEvent(icalStartDate, duration, event.getDisplayName() ); net.fortuna.ical4j.model.parameter.TzId tzId = new net.fortuna.ical4j.model.parameter.TzId( m_timeService.getLocalTimeZone().getID() ); icalEvent.getProperty(Property.DTSTART).getParameters().add(tzId); icalEvent.getProperty(Property.DTSTART).getParameters().add(Value.DATE_TIME); icalEvent.getProperties().add(new Uid(event.getId())); // build the description, adding links to attachments if necessary StringBuffer description = new StringBuffer(""); if ( event.getDescription() != null && !event.getDescription().equals("") ) description.append(event.getDescription()); List attachments = event.getAttachments(); if(attachments != null){ for (Iterator iter = attachments.iterator(); iter.hasNext();) { Reference attachment = (Reference) iter.next(); description.append("\n"); description.append(attachment.getUrl()); description.append("\n"); } } if(description.length() > 0) { //Replace \r with \n icalEvent.getProperties().add(new Description(description.toString().replace('\r', '\n'))); } if ( event.getLocation() != null && !event.getLocation().equals("") ) icalEvent.getProperties().add(new Location(event.getLocation())); try { String organizer = m_userDirectoryService.getUser( event.getCreator() ).getDisplayName(); organizer = organizer.replaceAll(" ","%20"); // get rid of illegal URI characters icalEvent.getProperties().add(new Organizer(new URI("CN="+organizer))); } catch (UserNotDefinedException e) {} // ignore catch (URISyntaxException e) {} // ignore StringBuffer comment = new StringBuffer(event.getType()); comment.append(" ("); comment.append(event.getSiteName()); comment.append(")"); icalEvent.getProperties().add(new Comment(comment.toString())); ical.getComponents().add( icalEvent ); numEvents++; /* TBD: add to VEvent: recurring schedule, ... RecurenceRUle x = event.getRecurrenceRule(); */ } return numEvents; } /* Given a current date via the calendarUtil paramter, returns a TimeRange for the year, * 6 months either side of the current date. (calculate milleseconds in 6 months) */ private static long SIX_MONTHS = (long)1000 * (long)60 * (long)60 * (long)24 * (long)183; public TimeRange getICalTimeRange() { Time now = m_timeService.newTime(); Time startTime = m_timeService.newTime( now.getTime() - SIX_MONTHS ); Time endTime = m_timeService.newTime( now.getTime() + SIX_MONTHS ); return m_timeService.newTimeRange(startTime,endTime,true,true); } /** * Trim the range that is passed in to the containing time range. */ protected TimeRange trimTimeRange(TimeRange containingRange, TimeRange rangeToTrim) { long containingRangeStartTime = containingRange.firstTime().getTime(); long containingRangeEndTime = containingRange.lastTime().getTime(); long rangeToTrimStartTime = rangeToTrim.firstTime().getTime(); long rangeToTrimEndTime = rangeToTrim.lastTime().getTime(); long trimmedStartTime = 0, trimmedEndTime = 0; trimmedStartTime = Math.min(Math.max(containingRangeStartTime, rangeToTrimStartTime), containingRangeEndTime); trimmedEndTime = Math.max(Math.min(containingRangeEndTime, rangeToTrimEndTime), rangeToTrimStartTime); return m_timeService.newTimeRange(m_timeService.newTime(trimmedStartTime), m_timeService.newTime(trimmedEndTime), true, false); } /** * Rounds a time range up to a minimum interval. */ protected TimeRange roundRangeToMinimumTimeInterval(TimeRange timeRange) { TimeRange roundedTimeRange = timeRange; if (timeRange.duration() < MINIMUM_EVENT_LENGTH_IN_MSECONDS) { roundedTimeRange = m_timeService.newTimeRange(timeRange.firstTime().getTime(), MINIMUM_EVENT_LENGTH_IN_MSECONDS); } return roundedTimeRange; } /** * Generates the XML for an event. */ protected void generateXMLEvent(Document doc, Element parent, CalendarEvent event, String eventNodeName, TimeRange containingTimeRange, boolean forceMinimumTime, boolean hideGroupIfNoSpace, boolean performEndTimeKludge) { Element eventElement = doc.createElement(eventNodeName); TimeRange trimmedTimeRange = trimTimeRange(containingTimeRange, event.getRange()); // Optionally force the event to have a minimum time slot. if (forceMinimumTime) { trimmedTimeRange = roundRangeToMinimumTimeInterval(trimmedTimeRange); } // Add the "from" time as an attribute. eventElement.setAttribute(FROM_ATTRIBUTE_STRING, getTimeString(trimmedTimeRange.firstTime())); // Add the "to" time as an attribute. Time endTime = null; // Optionally adjust the end time if (performEndTimeKludge) { endTime = performEndMinuteKludge(trimmedTimeRange.lastTime().breakdownLocal()); } else { endTime = trimmedTimeRange.lastTime(); } eventElement.setAttribute(TO_ATTRIBUTE_STRING, getTimeString(endTime)); // // Add the group (really "site") node // Provide that we have space or if we've been told we need to display it. // if (!hideGroupIfNoSpace || trimmedTimeRange.duration() > MINIMUM_EVENT_LENGTH_IN_MSECONDS) { writeStringNodeToDom(doc, eventElement, GROUP_NODE, event.getSiteName()); } // Add the display name node. writeStringNodeToDom(doc, eventElement, TITLE_NODE, event.getDisplayName()); // Add the event type node. writeStringNodeToDom(doc, eventElement, TYPE_NODE, getEventDescription(event.getType())); // Add the place/location node. writeStringNodeToDom(doc, eventElement, PLACE_NODE, event.getLocation()); // If a "Faculty" extra field is present, then add the node. writeStringNodeToDom(doc, eventElement, FACULTY_NODE, event.getField(FACULTY_EVENT_ATTRIBUTE_NAME)); // If a "Description" field is present, then add the node. writeStringNodeToDom(doc, eventElement, DESCRIPTION_NODE, event.getDescription()); parent.appendChild(eventElement); } protected String getEventDescription(String type){ ResourceLoader rl = new ResourceLoader("calendar"); if ((type!=null) && (type.trim()!="")){ if (type.equals("Academic Calendar")) return rl.getString("legend.key1"); else if (type.equals("Activity")) return rl.getString("legend.key2"); else if (type.equals("Cancellation")) return rl.getString("legend.key3"); else if (type.equals("Class section - Discussion")) return rl.getString("legend.key4"); else if (type.equals("Class section - Lab")) return rl.getString("legend.key5"); else if (type.equals("Class section - Lecture")) return rl.getString("legend.key6"); else if (type.equals("Class section - Small Group")) return rl.getString("legend.key7"); else if (type.equals("Class session")) return rl.getString("legend.key8"); else if (type.equals("Computer Session")) return rl.getString("legend.key9"); else if (type.equals("Deadline")) return rl.getString("legend.key10"); else if (type.equals("Exam")) return rl.getString("legend.key11"); else if (type.equals("Meeting")) return rl.getString("legend.key12"); else if (type.equals("Multidisciplinary Conference")) return rl.getString("legend.key13"); else if (type.equals("Quiz")) return rl.getString("legend.key14"); else if (type.equals("Special event")) return rl.getString("legend.key15"); else if (type.equals("Web Assignment")) return rl.getString("legend.key16"); else if (type.equals("Teletutoria")) return rl.getString("legend.key17"); else return rl.getString("legend.key2"); }else{ return rl.getString("legend.key2"); } } /* * Gets the daily start time parameter from a Properties object filled from URL parameters. */ protected TimeRange getDailyStartTimeFromParameters(Properties parameters) { return getTimeRangeParameterByName(parameters, DAILY_START_TIME_PARAMETER_NAME); } /** * Gets the standard date string from the time parameter */ protected String getDateFromTime(Time time) { TimeBreakdown timeBreakdown = time.breakdownLocal(); DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT,rb.getLocale()); return dateFormat.format(new Date(time.getTime())); } protected String getDayOfMonthFromTime(Time time) { TimeBreakdown timeBreakdown = time.breakdownLocal(); return Integer.toString(timeBreakdown.getDay()); } /** * Gets the schedule type from a Properties object (filled from a URL parameter list). */ protected int getScheduleTypeFromParameterList(Properties parameters) { int scheduleType = UNKNOWN_VIEW; // Get the type of schedule (daily, weekly, etc.) String scheduleTypeString = (String) parameters.get(SCHEDULE_TYPE_PARAMETER_NAME); scheduleType = Integer.parseInt(scheduleTypeString); return scheduleType; } /** * Access some named configuration value as a string. * * @param name * The configuration value name. * @param dflt * The value to return if not found. * @return The configuration value with this name, or the default value if not found. */ protected String getString(String name, String dflt) { return m_serverConfigurationService.getString(name, dflt); } /* * Gets the time range parameter from a Properties object filled from URL parameters. */ protected TimeRange getTimeRangeFromParameters(Properties parameters) { return getTimeRangeParameterByName(parameters, TIME_RANGE_PARAMETER_NAME); } /** * Generates a list of time ranges for a week. Each range in the list is a day. * * @param timeRange start & end date range * @param calendarReferenceList list of calendar(s) * @param dailyTimeRange start and end hour/minute time range */ protected ArrayList getTimeRangeListForWeek(TimeRange timeRange, List calendarReferenceList, TimeRange dailyTimeRange) { TimeBreakdown startBreakdown = timeRange.firstTime().breakdownLocal(); GregorianCalendar startCalendarDate = (GregorianCalendar)GregorianCalendar.getInstance(m_timeService.getLocalTimeZone(), rb.getLocale()); startCalendarDate.set(startBreakdown.getYear(), startBreakdown.getMonth() - 1, startBreakdown.getDay(), 0, 0, 0); ArrayList weekDayTimeRanges = new ArrayList(); TimeBreakdown startBreakDown = dailyTimeRange.firstTime().breakdownLocal(); TimeBreakdown endBreakDown = dailyTimeRange.lastTime().breakdownLocal(); // Search all seven weekdays // Note: no assumption can be made regarding the first day being Sunday, // since in some locales, the first weekday is Monday. for (int i = 0; i <= 6; i++) { // // Use the same start/end times for all days. // Time curStartTime = m_timeService.newTimeLocal(startCalendarDate.get(GregorianCalendar.YEAR), startCalendarDate .get(GregorianCalendar.MONTH) + 1, startCalendarDate.get(GregorianCalendar.DAY_OF_MONTH), startBreakDown .getHour(), startBreakDown.getMin(), startBreakDown.getSec(), startBreakDown.getMs()); Time curEndTime = m_timeService.newTimeLocal(startCalendarDate.get(GregorianCalendar.YEAR), startCalendarDate .get(GregorianCalendar.MONTH) + 1, startCalendarDate.get(GregorianCalendar.DAY_OF_MONTH), endBreakDown .getHour(), endBreakDown.getMin(), endBreakDown.getSec(), endBreakDown.getMs()); TimeRange newTimeRange = m_timeService.newTimeRange(curStartTime, curEndTime, true, false); weekDayTimeRanges.add(newTimeRange); // Move to the next day. startCalendarDate.add(GregorianCalendar.DATE, 1); } return weekDayTimeRanges; } /** * Utility routine to get a time range parameter from the URL parameters store in a Properties object. */ protected TimeRange getTimeRangeParameterByName(Properties parameters, String name) { // Now get the time range. String timeRangeString = (String) parameters.get(name); TimeRange timeRange = null; timeRange = m_timeService.newTimeRange(timeRangeString); return timeRange; } /** * Gets a standard time string give the time parameter. */ protected String getTimeString(Time time) { TimeBreakdown timeBreakdown = time.breakdownLocal(); DecimalFormat twoDecimalDigits = new DecimalFormat("00"); return timeBreakdown.getHour() + HOUR_MINUTE_SEPARATOR + twoDecimalDigits.format(timeBreakdown.getMin()); } /** * Given a schedule type, the appropriate XSLT file is returned */ protected String getXSLFileNameForScheduleType(int scheduleType) { // get a relative path to the file String baseFileName = ""; switch (scheduleType) { case WEEK_VIEW: baseFileName = WEEK_VIEW_XSLT_FILENAME; break; case DAY_VIEW: baseFileName = DAY_VIEW_XSLT_FILENAME; break; case MONTH_VIEW: baseFileName = MONTH_VIEW_XSLT_FILENAME; break; case LIST_VIEW: baseFileName = LIST_VIEW_XSLT_FILENAME; break; default: M_log.debug("PrintFileGeneration.getXSLFileNameForScheduleType(): unexpected scehdule type = " + scheduleType); break; } return baseFileName; } /** * 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 should really be in a central place so that the velocity template can see it as well. */ protected Time performEndMinuteKludge(TimeBreakdown breakDown) { int endMin = breakDown.getMin(); int endHour = breakDown.getHour(); int tmpMinVal = endMin % TIMESLOT_FOR_OVERLAP_DETECTION_IN_MINUTES; if (tmpMinVal == 4 || tmpMinVal == 9) { endMin = endMin + 1; if (endMin == 60) { endMin = 00; endHour = endHour + 1; } } return m_timeService.newTimeLocal(breakDown.getYear(), breakDown.getMonth(), breakDown.getDay(), endHour, endMin, breakDown .getSec(), breakDown.getMs()); } protected List getCalendarReferenceList() throws PermissionException { // Get the list of calendars.from user session List calendarReferenceList = (List)m_sessionManager.getCurrentSession().getAttribute(SESSION_CALENDAR_LIST); // check if there is any calendar to which the user has acces Iterator it = calendarReferenceList.iterator(); int permissionCount = calendarReferenceList.size(); while (it.hasNext()) { String calendarReference = (String) it.next(); try { getCalendar(calendarReference); } catch (IdUnusedException e) { continue; } catch (PermissionException e) { permissionCount--; continue; } } // if no permission to any of the calendars, throw exception and do nothing // the expection will be caught by AccessServlet.doPrintingRequest() if (permissionCount == 0) { throw new PermissionException("", "", ""); } return calendarReferenceList; } protected void printICalSchedule(String calRef, OutputStream os) throws PermissionException { // generate iCal text file net.fortuna.ical4j.model.Calendar ical = new net.fortuna.ical4j.model.Calendar(); ical.getProperties().add(new ProdId("-//SakaiProject//iCal4j 1.0//EN")); ical.getProperties().add(Version.VERSION_2_0); ical.getProperties().add(CalScale.GREGORIAN); TimeZoneRegistry registry = TimeZoneRegistryFactory.getInstance().createRegistry(); TzId tzId = new TzId( m_timeService.getLocalTimeZone().getID() ); ical.getComponents().add(registry.getTimeZone(tzId.getValue()).getVTimeZone()); CalendarOutputter icalOut = new CalendarOutputter(); int numEvents = generateICal(ical, calRef); try { if ( numEvents > 0 ) icalOut.output( ical, os ); } catch (Exception e) { M_log.warn(".printICalSchedule(): ", e); } } /** * Called by the servlet to service a get/post requesting a calendar in PDF format. */ protected void printSchedule(Properties parameters, OutputStream os) throws PermissionException { // Get the user name. String userName = (String) parameters.get(USER_NAME_PARAMETER_NAME); // Get the list of calendars.from user session List calendarReferenceList = getCalendarReferenceList(); // Get the type of schedule (daily, weekly, etc.) int scheduleType = getScheduleTypeFromParameterList(parameters); // Now get the time range. TimeRange timeRange = getTimeRangeFromParameters(parameters); Document document = docBuilder.newDocument(); generateXMLDocument(scheduleType, document, timeRange, getDailyStartTimeFromParameters(parameters), calendarReferenceList, userName); generatePDF(document, getXSLFileNameForScheduleType(scheduleType), os); } /** * 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. */ protected TimeRange shrinkTimeRangeToCurrentMonth(TimeRange expandedTimeRange) { long millisecondsInWeek = (7 * MILLISECONDS_IN_DAY); Time startTime = expandedTimeRange.firstTime(); // Grab something in the middle of the time range so that we know that we're // in the right month. Time somewhereInTheMonthTime = m_timeService.newTime(startTime.getTime() + 2 * millisecondsInWeek); TimeBreakdown somewhereInTheMonthBreakdown = somewhereInTheMonthTime.breakdownLocal(); CalendarUtil calendar = new CalendarUtil(); calendar.setDay(somewhereInTheMonthBreakdown.getYear(), somewhereInTheMonthBreakdown.getMonth(), somewhereInTheMonthBreakdown.getDay()); int numDaysInMonth = calendar.getNumberOfDays(); // // Construct a new time range starting on the first day of the month and ending on // the last day at one millisecond before midnight. // return m_timeService.newTimeRange(m_timeService.newTimeLocal(somewhereInTheMonthBreakdown.getYear(), somewhereInTheMonthBreakdown.getMonth(), 1, 0, 0, 0, 0), m_timeService.newTimeLocal(somewhereInTheMonthBreakdown .getYear(), somewhereInTheMonthBreakdown.getMonth(), numDaysInMonth, 23, 59, 59, 999)); } /** * Calculate the number of days in a range of time given two dates. * * @param startMonth * (zero based, 0-11) * @param startDay * (one based, 1-31) * @param endYear * (one based, 1-31) * @param endMonth * (zero based, 0-11 */ protected long getNumberDaysGivenTwoDates(int startYear, int startMonth, int startDay, int endYear, int endMonth, int endDay) { GregorianCalendar startDate = new GregorianCalendar(); GregorianCalendar endDate = new GregorianCalendar(); startDate.set(startYear, startMonth, startDay, 0, 0, 0); endDate.set(endYear, endMonth, endDay, 0, 0, 0); long duration = endDate.getTime().getTime() - startDate.getTime().getTime(); // Allow for daylight savings time. return ((duration + MILLISECONDS_IN_HOUR) / (24 * MILLISECONDS_IN_HOUR)) + 1; } /** * Returns a list of daily time ranges for every day in a range. * * @param timeRange * overall time range * @param dailyTimeRange * representative daily time range (start hour/minute, end hour/minute). If null, this parameter is ignored. */ protected ArrayList splitTimeRangeIntoListOfSingleDayTimeRanges(TimeRange timeRange, TimeRange dailyTimeRange) { TimeBreakdown startBreakdown = timeRange.firstTime().breakdownLocal(); TimeBreakdown endBreakdown = timeRange.lastTime().breakdownLocal(); GregorianCalendar startCalendarDate = new GregorianCalendar(); startCalendarDate.set(startBreakdown.getYear(), startBreakdown.getMonth() - 1, startBreakdown.getDay(), 0, 0, 0); long numDaysInTimeRange = getNumberDaysGivenTwoDates(startBreakdown.getYear(), startBreakdown.getMonth() - 1, startBreakdown.getDay(), endBreakdown.getYear(), endBreakdown.getMonth() - 1, endBreakdown.getDay()); ArrayList splitTimeRanges = new ArrayList(); TimeBreakdown dailyStartBreakDown = null; TimeBreakdown dailyEndBreakDown = null; if (dailyTimeRange != null) { dailyStartBreakDown = dailyTimeRange.firstTime().breakdownLocal(); dailyEndBreakDown = dailyTimeRange.lastTime().breakdownLocal(); } for (long i = 0; i < numDaysInTimeRange; i++) { Time curStartTime = null; Time curEndTime = null; if (dailyTimeRange != null) { // // Use the same start/end times for all days. // curStartTime = m_timeService.newTimeLocal(startCalendarDate.get(GregorianCalendar.YEAR), startCalendarDate .get(GregorianCalendar.MONTH) + 1, startCalendarDate.get(GregorianCalendar.DAY_OF_MONTH), dailyStartBreakDown.getHour(), dailyStartBreakDown.getMin(), dailyStartBreakDown.getSec(), dailyStartBreakDown.getMs()); curEndTime = m_timeService.newTimeLocal(startCalendarDate.get(GregorianCalendar.YEAR), startCalendarDate .get(GregorianCalendar.MONTH) + 1, startCalendarDate.get(GregorianCalendar.DAY_OF_MONTH), dailyEndBreakDown .getHour(), dailyEndBreakDown.getMin(), dailyEndBreakDown.getSec(), dailyEndBreakDown.getMs()); splitTimeRanges.add(m_timeService.newTimeRange(curStartTime, curEndTime, true, false)); } else { // // Add a full day range since no start/stop time was specified. // splitTimeRanges.add(getFullDayTimeRangeFromYMD(startCalendarDate.get(GregorianCalendar.YEAR), startCalendarDate .get(GregorianCalendar.MONTH) + 1, startCalendarDate.get(GregorianCalendar.DAY_OF_MONTH))); } // Move to the next day. startCalendarDate.add(GregorianCalendar.DATE, 1); } return splitTimeRanges; } /** * Utility routine to write a string node to the DOM. */ protected Element writeStringNodeToDom(Document doc, Element parent, String nodeName, String nodeValue) { if (nodeValue != null && nodeValue.length() != 0) { Element name = doc.createElement(nodeName); name.appendChild(doc.createTextNode(nodeValue)); parent.appendChild(name); return name; } return null; } /** ** Internal class for resolving stylesheet URIs **/ protected class MyURIResolver implements URIResolver { ClassLoader classLoader = null; /** ** Constructor: use BaseCalendarService ClassLoader **/ public MyURIResolver( ClassLoader classLoader ) { this.classLoader = classLoader; } /** ** Resolve XSLT pathnames invoked within stylesheet (e.g. xsl:import) ** using ClassLoader. ** ** @param href href attribute of XSLT file ** @param base base URI in affect when href attribute encountered ** @return Source object for requested XSLT file **/ public Source resolve( String href, String base ) throws TransformerException { InputStream in = classLoader.getResourceAsStream(href); return (Source)(new StreamSource(in)); } } /** * Get a DefaultHandler so that the StorageUser here can parse using SAX events. * * @see org.sakaiproject.util.SAXEntityReader#getDefaultHandler() */ public DefaultEntityHandler getDefaultHandler(final Map<String,Object> services) { return new DefaultEntityHandler() { /* * (non-Javadoc) * * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, * java.lang.String, java.lang.String, org.xml.sax.Attributes) */ @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (doStartElement(uri, localName, qName, attributes)) { if (entity == null) { if ("calendar".equals(qName)) { BaseCalendarEdit bce = new BaseCalendarEdit(); entity = bce; setContentHandler(bce.getContentHandler(services), uri, localName, qName, attributes); } else if ("event".equals(qName)) { BaseCalendarEventEdit bcee = new BaseCalendarEventEdit( container); entity = bcee; setContentHandler(bcee.getContentHandler(services), uri, localName, qName, attributes); } else { M_log.warn("Unexpected Element in XML ["+qName+"]"); } } } } }; } /* (non-Javadoc) * @see org.sakaiproject.util.SAXEntityReader#getServices() */ public Map<String, Object> getServices() { if ( m_services == null ) { m_services = new HashMap<String, Object>(); m_services.put("timeservice", m_timeService); } return m_services; } public void setServices(Map<String,Object> services) { m_services = services; } public void transferCopyEntities(String fromContext, String toContext, List ids, boolean cleanup) { transferCopyEntitiesRefMigrator(fromContext, toContext, ids, cleanup); } public Map<String, String> transferCopyEntitiesRefMigrator(String fromContext, String toContext, List ids, boolean cleanup) { Map<String, String> transversalMap = new HashMap<String, String>(); try { if(cleanup == true) { String toSiteId = toContext; String calendarId = calendarReference(toSiteId, SiteService.MAIN_CONTAINER); Calendar calendarObj = getCalendar(calendarId); List calEvents = calendarObj.getEvents(null,null); for (int i = 0; i < calEvents.size(); i++) { try { CalendarEvent ce = (CalendarEvent) calEvents.get(i); calendarObj.removeEvent(calendarObj.getEditEvent(ce.getId(), CalendarService.EVENT_REMOVE_CALENDAR)); CalendarEventEdit edit = calendarObj.getEditEvent(ce.getId(), org.sakaiproject.calendar.api.CalendarService.EVENT_REMOVE_CALENDAR); calendarObj.removeEvent(edit); calendarObj.commitEvent(edit); } catch (IdUnusedException e) { M_log.debug(".IdUnusedException " + e); } catch (PermissionException e) { M_log.debug(".PermissionException " + e); } catch (InUseException e) { M_log.debug(".InUseException delete" + e); } } } transversalMap.putAll(transferCopyEntitiesRefMigrator(fromContext, toContext, ids)); } catch (Exception e) { M_log.info("importSiteClean: End removing Calendar data" + e); } return transversalMap; } /** ** Comparator for sorting Group objects **/ private class GroupComparator implements Comparator { public int compare(Object o1, Object o2) { return ((Group)o1).getTitle().compareToIgnoreCase( ((Group)o2).getTitle() ); } } } // BaseCalendarService