/********************************************************************************** * $URL:$ * $Id:$ *********************************************************************************** * * Copyright (c) 2008 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.BufferedInputStream; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.Stack; import java.util.Timer; import java.util.TimerTask; import java.util.Vector; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.authz.api.SecurityService; import org.sakaiproject.calendar.api.Calendar; import org.sakaiproject.calendar.api.CalendarEvent; import org.sakaiproject.calendar.api.CalendarEvent.EventAccess; import org.sakaiproject.calendar.api.CalendarEventEdit; import org.sakaiproject.calendar.api.CalendarImporterService; import org.sakaiproject.calendar.api.CalendarService; import org.sakaiproject.calendar.api.ExternalCalendarSubscriptionService; import org.sakaiproject.calendar.api.ExternalSubscription; import org.sakaiproject.calendar.api.RecurrenceRule; import org.sakaiproject.component.api.ServerConfigurationService; import org.sakaiproject.entity.api.EntityManager; import org.sakaiproject.entity.api.Reference; import org.sakaiproject.entity.api.ResourceProperties; import org.sakaiproject.entity.api.ResourcePropertiesEdit; import org.sakaiproject.exception.IdUnusedException; import org.sakaiproject.exception.IdUsedException; import org.sakaiproject.exception.ImportException; import org.sakaiproject.exception.InUseException; import org.sakaiproject.exception.PermissionException; import org.sakaiproject.id.api.IdManager; import org.sakaiproject.javax.Filter; import org.sakaiproject.site.api.Site; import org.sakaiproject.site.api.SiteService; import org.sakaiproject.site.api.ToolConfiguration; import org.sakaiproject.time.api.Time; import org.sakaiproject.time.api.TimeRange; import org.sakaiproject.time.api.TimeService; import org.sakaiproject.tool.api.SessionManager; import org.sakaiproject.tool.api.ToolManager; import org.sakaiproject.util.BaseResourcePropertiesEdit; import org.sakaiproject.util.FormattedText; import org.w3c.dom.Document; import org.w3c.dom.Element; public class BaseExternalCalendarSubscriptionService implements ExternalCalendarSubscriptionService { /** Logging */ private static Log m_log = LogFactory.getLog(BaseExternalCalendarSubscriptionService.class); /** Schedule tool ID */ private final static String SCHEDULE_TOOL_ID = "sakai.schedule"; /** Default context for institutional subscriptions */ private final static String INSTITUTIONAL_CONTEXT = "!worksite"; /** Default context for user-provided subscriptions */ private final static String USER_CONTEXT = "!user"; /** Default connect timeout when retrieving external subscriptions */ private final static int TIMEOUT = 30000; /** iCal external subscription enable flag */ private boolean enabled = false; /** merge iCal external subscriptions from other sites into My Workspace? */ private boolean mergeIntoMyworkspace = true; /** Column map for iCal processing */ private Map columnMap = null; /** Cache map of Institutional Calendars: <String url, Calendar cal> */ private SubscriptionCache institutionalSubscriptionCache = null; /** Cache map of user Calendars: <String url, Calendar cal> */ private SubscriptionCache usersSubscriptionCache = null; // ###################################################### // Spring services // ###################################################### /** Dependency: CalendarService. */ protected CalendarService m_calendarService = null; /** Dependency: SecurityService */ protected SecurityService m_securityService = null; /** Dependency: SessionManager */ protected SessionManager m_sessionManager = null; /** Dependency: TimeService */ protected TimeService m_timeService = null; /** Dependency: ToolManager */ protected ToolManager m_toolManager = null; /** Dependency: IdManager. */ protected IdManager m_idManager; /** Dependency: CalendarImporterService. */ protected CalendarImporterService m_importerService = null; /** Dependency: ServerConfigurationService. */ protected ServerConfigurationService m_configurationService = null; /** Dependency: EntityManager. */ protected EntityManager m_entityManager = null; /** Dependency: SiteService. */ protected SiteService m_siteService = null; public void setCalendarService(CalendarService service) { this.m_calendarService = service; } public void setServerConfigurationService(ServerConfigurationService service) { this.m_configurationService = service; } public void setCalendarImporterService(CalendarImporterService service) { this.m_importerService = service; } public void setEntityManager(EntityManager service) { this.m_entityManager = service; } public void setSiteService(SiteService service) { this.m_siteService = service; } /** * Dependency: SecurityService. * * @param securityService * The SecurityService. */ public void setSecurityService(SecurityService securityService) { m_securityService = securityService; } /** * Dependency: SessionManager. * @param sessionManager * The SessionManager. */ public void setSessionManager(SessionManager sessionManager) { this.m_sessionManager = sessionManager; } /** * 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: IdManager. * @param idManager * The IdManager. */ public void setIdManager(IdManager idManager) { this.m_idManager = idManager; } public void setInstitutionalSubscriptionCache(SubscriptionCache subscriptionCache) { this.institutionalSubscriptionCache = subscriptionCache; } public void setUserSubscriptionCache(SubscriptionCache subscriptionCache) { this.usersSubscriptionCache = subscriptionCache; } /** Dependency: Timer */ protected Timer m_timer = null; public void setTimer(Timer timer) { this.m_timer = timer; } public void init() { // external calendar subscriptions: enable? enabled = m_configurationService.getBoolean(SAK_PROP_EXTSUBSCRIPTIONS_ENABLED, false); mergeIntoMyworkspace = m_configurationService.getBoolean(SAK_PROP_EXTSUBSCRIPTIONS_MERGEINTOMYWORKSPACE, true); m_log.info("init(): enabled: " + enabled + ", merge from other sites into My Workspace? "+mergeIntoMyworkspace); if (enabled) { // iCal column map try { columnMap = m_importerService .getDefaultColumnMap(CalendarImporterService.ICALENDAR_IMPORT); } catch (ImportException e1) { m_log .error("Unable to get column map for ICal import. External subscriptions will be disabled."); enabled = false; return; } // load institutional calendar subscriptions as timer tasks, this is so that // we don't slow up the loading of sakai. for (final InsitutionalSubscription sub: getInstitutionalSubscriptions()) { m_timer.schedule(new TimerTask() { @Override public void run() { String reference = calendarSubscriptionReference(INSTITUTIONAL_CONTEXT, getIdFromSubscriptionUrl(sub.url)); getCalendarSubscription(reference); } }, 0); } } } public void destroy() { // Nothing to clean up for now. } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } // ###################################################### // PUBLIC methods // ###################################################### /* * (non-Javadoc) * * @see org.sakaiproject.calendar.api.ExternalCalendarSubscriptionService#calendarSubscriptionReference(java.lang.String, * java.lang.String) */ public String calendarSubscriptionReference(String context, String id) { return BaseExternalSubscription.calendarSubscriptionReference(context, id); } /* * (non-Javadoc) * * @see org.sakaiproject.calendar.impl.ExternalCalendarSubscriptionService#getCalendarSubscription(java.lang.String) */ public Calendar getCalendarSubscription(String reference) { if (!isEnabled() || reference == null) return null; // Get Reference and Subscription URL Reference _ref = m_entityManager.newReference(reference); String subscriptionUrl = getSubscriptionUrlFromId(_ref.getId()); if (subscriptionUrl == null || subscriptionUrl.equals("null")) return null; m_log.debug("ExternalCalendarSubscriptionService.getCalendarSubscription(" + reference + ")"); m_log.debug(" |-> subscriptionUrl: " + subscriptionUrl); ExternalSubscription subscription = getExternalSubscription(subscriptionUrl, _ref.getContext()); m_log.debug(" |-> Subscription is " + subscription); if (subscription != null) { m_log.debug(" |-> Calendar is " + subscription.getCalendar()); return subscription.getCalendar(); } else { m_log.debug(" |-> Calendar is NULL"); return null; } } private ExternalSubscription getExternalSubscription(String subscriptionUrl, String context) { // Decide which cache to use. SubscriptionCache cache = (getInstitutionalSubscription(subscriptionUrl) != null)? institutionalSubscriptionCache : usersSubscriptionCache; ExternalSubscription subscription = cache.get(subscriptionUrl); // Did we get it? if (subscription == null) { subscription = loadCalendarSubscriptionFromUrl(subscriptionUrl, context); cache.put(subscription); } return subscription; } public Set<String> getCalendarSubscriptionChannelsForChannels( String primaryCalendarReference, Collection<Object> channels) { Set<String> subscriptionChannels = new HashSet<String>(); Set<String> subscriptionUrlsAdded = new HashSet<String>(); if(isOnWorkspaceTab() && (!mergeIntoMyworkspace || m_securityService.isSuperUser())) { channels = new ArrayList<Object>(); channels.add(primaryCalendarReference); } for (Object channel : channels) { Set<String> channelSubscriptions = getCalendarSubscriptionChannelsForChannel((String) channel); for (String channelSub : channelSubscriptions) { Reference ref = m_entityManager.newReference(channelSub); if (!subscriptionUrlsAdded.contains(ref.getId())) { subscriptionChannels.add(channelSub); subscriptionUrlsAdded.add(ref.getId()); } } } return subscriptionChannels; } /* * (non-Javadoc) * * @see org.sakaiproject.calendar.impl.ExternalCalendarSubscriptionService#getCalendarSubscriptionChannelsForSite() */ public Set<String> getCalendarSubscriptionChannelsForChannel(String reference) { Set<String> channels = new HashSet<String>(); if (!isEnabled() || reference == null) return channels; // get externally subscribed urls from tool config Reference ref = m_entityManager.newReference(reference); Site site = null; try { site = m_siteService.getSite(ref.getContext()); } catch (IdUnusedException e) { m_log .error("ExternalCalendarSubscriptionService.getCalendarSubscriptionChannelsForChannel(): IdUnusedException for context in reference: " + reference); return channels; } ToolConfiguration tc = site.getToolForCommonId(SCHEDULE_TOOL_ID); Properties config = tc == null? null : tc.getConfig(); if (tc != null && config != null) { String prop = config.getProperty(TC_PROP_SUBCRIPTIONS); if (prop != null) { String[] chsPair = prop.split(SUBS_REF_DELIMITER); for (int i = 0; i < chsPair.length; i++) { String[] pair = chsPair[i].split(SUBS_NAME_DELIMITER); channels.add(pair[0]); } } } return channels; } public Set<ExternalSubscription> getAvailableInstitutionalSubscriptionsForChannel( String reference) { Set<ExternalSubscription> subscriptions = new HashSet<ExternalSubscription>(); if (!isEnabled() || reference == null) return subscriptions; Reference ref = m_entityManager.newReference(reference); // If the cache has been flushed then we may need to reload it. for (InsitutionalSubscription sub : getInstitutionalSubscriptions()) { // Need to have way to load these. ExternalSubscription subscription = getExternalSubscription(sub.url, ref.getContext()); if (subscription != null) { subscription.setContext(ref.getContext()); subscriptions.add(subscription); subscription.setCalendar(null); } } return subscriptions; } public Set<ExternalSubscription> getSubscriptionsForChannel(String reference, boolean loadCalendar) { Set<ExternalSubscription> subscriptions = new HashSet<ExternalSubscription>(); if (!isEnabled() || reference == null) return subscriptions; // get externally subscribed urls from tool config Reference ref = m_entityManager.newReference(reference); Site site = null; try { site = m_siteService.getSite(ref.getContext()); } catch (IdUnusedException e) { m_log .error("ExternalCalendarSubscriptionService.getSubscriptionsForChannel(): IdUnusedException for context in reference: " + reference); return subscriptions; } ToolConfiguration tc = site.getToolForCommonId(SCHEDULE_TOOL_ID); Properties config = tc == null? null : tc.getConfig(); if (tc != null && config != null) { String prop = config.getProperty(TC_PROP_SUBCRIPTIONS); if (prop != null) { String[] chsPair = prop.split(SUBS_REF_DELIMITER); for (int i = 0; i < chsPair.length; i++) { String[] pair = chsPair[i].split(SUBS_NAME_DELIMITER); String r = pair[0]; Reference r1 = m_entityManager.newReference(r); String url = getSubscriptionUrlFromId(r1.getId()); String name = null; if (pair.length == 2) name = pair[1]; else { try { name = institutionalSubscriptionCache.get(url) .getSubscriptionName(); } catch (Exception e) { name = url; } } ExternalSubscription subscription = new BaseExternalSubscription( name, url, ref.getContext(), loadCalendar ? getCalendarSubscription(r) : null, isInstitutionalCalendar(r)); subscriptions.add(subscription); } } } return subscriptions; } /* * (non-Javadoc) * * @see org.sakaiproject.calendar.impl.ExternalCalendarSubscriptionService#setSubscriptionsForChannel(String, * Collection<ExternalSubscription>) */ public void setSubscriptionsForChannel(String reference, Collection<ExternalSubscription> subscriptions) { if (!isEnabled() || reference == null) return; // set externally subscriptions in tool config Reference ref = m_entityManager.newReference(reference); Site site = null; try { site = m_siteService.getSite(ref.getContext()); } catch (IdUnusedException e) { m_log .error("ExternalCalendarSubscriptionService.setSubscriptionsForChannel(): IdUnusedException for context in reference: " + reference); return; } ToolConfiguration tc = site.getToolForCommonId(SCHEDULE_TOOL_ID); if (tc != null) { boolean first = true; StringBuffer tmpStr = new StringBuffer(); for (ExternalSubscription subscription : subscriptions) { if (!first) tmpStr.append(SUBS_REF_DELIMITER); first = false; tmpStr.append(subscription.getReference()); if (!subscription.isInstitutional()) tmpStr.append(SUBS_NAME_DELIMITER + subscription.getSubscriptionName()); } Properties config = tc.getConfig(); config.setProperty(TC_PROP_SUBCRIPTIONS, tmpStr.toString()); tc.save(); } } public boolean isInstitutionalCalendar(String reference) { // Get Reference and Subscription URL Reference _ref = m_entityManager.newReference(reference); String subscriptionUrl = getSubscriptionUrlFromId(_ref.getId()); if (subscriptionUrl == null || subscriptionUrl.equals("null")) return false; // Is a institutional subscription? String[] subscriptionURLs = m_configurationService .getStrings(SAK_PROP_EXTSUBSCRIPTIONS_URL); if (subscriptionURLs != null) { for (String url: subscriptionURLs) { if (subscriptionUrl.equals(url)) { return true; } } } return false; } public String getIdFromSubscriptionUrl(String url) { return BaseExternalSubscription.getIdFromSubscriptionUrl(url); } public String getSubscriptionUrlFromId(String id) { return BaseExternalSubscription.getSubscriptionUrlFromId(id); } // ###################################################### // PRIVATE methods // ###################################################### /** * Get the event type for this institutional subscription. * @param url * @return The forced event type or <code>null</code> if it isn't defined. */ String getEventType(String url) { InsitutionalSubscription sub = getInstitutionalSubscription(url); return (sub != null)? sub.eventType: null; } /** * Insitutional subscriptions loaded from configuration. */ class InsitutionalSubscription { String url; String name; String eventType; } InsitutionalSubscription getInstitutionalSubscription(String url) { for (InsitutionalSubscription sub: getInstitutionalSubscriptions()) { if(sub.url.equals(url)) { return sub; } } return null; } List<InsitutionalSubscription> getInstitutionalSubscriptions() { String[] subscriptionURLs = m_configurationService .getStrings(SAK_PROP_EXTSUBSCRIPTIONS_URL); String[] subscriptionNames = m_configurationService .getStrings(SAK_PROP_EXTSUBSCRIPTIONS_NAME); String[] subscriptionEventTypes = m_configurationService .getStrings(SAK_PROP_EXTSUBSCRIPTIONS_EVENTTYPE); ArrayList<InsitutionalSubscription> subs = new ArrayList<InsitutionalSubscription>(); if (subscriptionURLs != null) { for (int i = 0; i < subscriptionURLs.length; i++) { String name = subscriptionNames[i]; String eventType = subscriptionEventTypes[i]; if (name != null) { InsitutionalSubscription sub = new InsitutionalSubscription(); sub.url = subscriptionURLs[i]; sub.name = name; sub.eventType = eventType; subs.add(sub); } } } return subs; } ExternalSubscription loadCalendarSubscriptionFromUrl(String url, String context) { InsitutionalSubscription sub = getInstitutionalSubscription(url); String name = null; String forcedEventType = null; if (sub != null) { name = sub.name; forcedEventType = sub.eventType; } return loadCalendarSubscriptionFromUrl(url, context, name, forcedEventType); } ExternalSubscription loadCalendarSubscriptionFromUrl(String url, String context, String calendarName, String forcedEventType) { ExternalSubscription subscription = new BaseExternalSubscription(calendarName, url, context, null, INSTITUTIONAL_CONTEXT.equals(context)); ExternalCalendarSubscription calendar = null; List<CalendarEvent> events = null; BufferedInputStream stream = null; try { URL _url = new URL(url); if (calendarName == null) calendarName = _url.getFile(); // connect URLConnection conn = _url.openConnection(); conn.addRequestProperty("User-Agent", "Sakai/"+ m_configurationService.getString("sakai.version", "?") + " (Calendar Subscription)"); conn.setConnectTimeout(TIMEOUT); conn.setReadTimeout(TIMEOUT); stream = new BufferedInputStream(conn.getInputStream()); // import events = m_importerService.doImport(CalendarImporterService.ICALENDAR_IMPORT, stream, columnMap, null); String subscriptionId = getIdFromSubscriptionUrl(url); String reference = calendarSubscriptionReference(context, subscriptionId); calendar = new ExternalCalendarSubscription(reference); for (CalendarEvent event : events) { String eventType = event.getType(); if (forcedEventType != null) eventType = forcedEventType; calendar.addEvent(event.getRange(), event.getDisplayName(), event .getDescription(), eventType, event.getLocation(), event .getRecurrenceRule(), null); } calendar.setName(calendarName); subscription.setCalendar(calendar); subscription.setInstitutional(getInstitutionalSubscription(url) != null); m_log.info("Loaded calendar subscription: " + subscription.toString()); } catch (ImportException e) { m_log.error("Error loading calendar subscription '" + calendarName + "' (will NOT retry again): " + url, e); String subscriptionId = getIdFromSubscriptionUrl(url); String reference = calendarSubscriptionReference(context, subscriptionId); calendar = new ExternalCalendarSubscription(reference); calendar.setName(calendarName); subscription.setCalendar(calendar); } catch (PermissionException e) { // This will never be called (for now) e.printStackTrace(); } catch (MalformedURLException e) { m_log.error("Mal-formed URL in calendar subscription '" + calendarName + "': " + url, e); } catch (IOException e) { m_log.error("Unable to read calendar subscription '" + calendarName + "' from URL (I/O Error): " + url, e); } catch (Exception e) { m_log.error("Unknown error occurred while reading calendar subscription '" + calendarName + "' from URL: " + url, e); } finally { if (stream != null) { // Also closes the underlying InputStream try { stream.close(); } catch (IOException e) { // Ignore } } } return subscription; } /** * See if the current tab is the workspace tab (i.e. user site) * @return true if we are currently on the "My Workspace" tab. */ private boolean isOnWorkspaceTab() { return m_siteService.isUserSite(m_toolManager.getCurrentPlacement().getContext()); } // ###################################################### // Support classes // ###################################################### public class ExternalCalendarSubscription implements Calendar { /** Memory storage */ protected Map<String, CalendarEvent> m_storage = new HashMap<String, CalendarEvent>(); /** The context in which this calendar exists. */ protected String m_context = null; /** Store the unique-in-context calendar id. */ protected String m_id = null; /** Store the calendar name. */ protected String m_name = null; /** The properties. */ protected ResourcePropertiesEdit m_properties = null; protected String modifiedDateStr = null; public ExternalCalendarSubscription(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(); } public CalendarEvent addEvent(TimeRange range, String displayName, String description, String type, String location, EventAccess access, Collection groups, List attachments) throws PermissionException { return addEvent(range, displayName, description, type, location, attachments); } public CalendarEvent addEvent(TimeRange range, String displayName, String description, String type, String location, List attachments) throws PermissionException { return addEvent(range, displayName, description, type, location, null, attachments); } public CalendarEvent addEvent(TimeRange range, String displayName, String description, String type, String location, RecurrenceRule rrule, List attachments) throws PermissionException { // allocate a new unique event id // String id = getUniqueId(); String id = getUniqueIdBasedOnFields(displayName, description, type, location); // create event ExternalCalendarEvent edit = new ExternalCalendarEvent(m_context, m_id, id); // set it up edit.setRange(range); edit.setDisplayName(displayName); edit.setDescription(description); edit.setType(type); edit.setLocation(location); edit.setCreator(); if (rrule != null) edit.setRecurrenceRule(rrule); // put in storage m_storage.put(id, edit); return edit; } public CalendarEventEdit addEvent() throws PermissionException { // allocate a new unique event id // String id = getUniqueId(); // create event // CalendarEventEdit event = new ExternalCalendarEvent(this, id); // put in storage // m_storage.put(id, event); return null; } public CalendarEvent addEvent(CalendarEvent event) { // allocate a new unique event id String id = event.getId(); // put in storage m_storage.put(id, event); return event; } public Collection<CalendarEvent> getAllEvents() { return m_storage.values(); } public boolean allowAddCalendarEvent() { return false; } public boolean allowAddEvent() { return false; } public boolean allowEditEvent(String eventId) { return false; } public boolean allowGetEvent(String eventId) { return true; } public boolean allowGetEvents() { return true; } public boolean allowRemoveEvent(CalendarEvent event) { return false; } public void cancelEvent(CalendarEventEdit edit) { } public void commitEvent(CalendarEventEdit edit, int intention) { } public void commitEvent(CalendarEventEdit edit) { } public String getContext() { return m_context; } public CalendarEventEdit getEditEvent(String eventId, String editType) throws IdUnusedException, PermissionException, InUseException { return null; } public CalendarEvent getEvent(String eventId) throws IdUnusedException, PermissionException { return m_storage.get(eventId); } public String getEventFields() { return m_properties .getPropertyFormatted(ResourceProperties.PROP_CALENDAR_EVENT_FIELDS); } public List getEvents(TimeRange range, Filter filter) throws PermissionException { return filterEvents(new ArrayList<CalendarEvent>(m_storage.values()), range); } public boolean getExportEnabled() { return false; } public Collection getGroupsAllowAddEvent() { return new ArrayList(); } public Collection getGroupsAllowGetEvent() { return new ArrayList(); } public Collection getGroupsAllowRemoveEvent(boolean own) { return new ArrayList(); } public Time getModified() { return m_timeService.newTimeGmt(modifiedDateStr); } public CalendarEventEdit mergeEvent(Element el) throws PermissionException, IdUsedException { // TODO Implement mergeEvent() return null; } public void removeEvent(CalendarEventEdit edit, int intention) throws PermissionException { } public void removeEvent(CalendarEventEdit edit) throws PermissionException { } public void setExportEnabled(boolean enable) { } public void setModified() { } public String getId() { return m_id; } public ResourceProperties getProperties() { return m_properties; } public String getReference() { return m_calendarService.calendarSubscriptionReference(m_context, m_id); } protected void setContext(String context) { // set the ids m_context = context; for (CalendarEvent e : m_storage.values()) { // ((ExternalCalendarEvent) e).setCalendar(this); ((ExternalCalendarEvent) e).setCalendarContext(m_context); ((ExternalCalendarEvent) e).setCalendarId(m_id); } } public String getReference(String rootProperty) { return rootProperty + getReference(); } public String getUrl() { // TODO Auto-generated method stub return null; } public String getUrl(String rootProperty) { // TODO Auto-generated method stub return null; } public Element toXml(Document doc, Stack stack) { // TODO Auto-generated method stub return null; } public String getName() { return m_name; } public void setName(String calendarName) { this.m_name = calendarName; } /** * Access the id generating service and return a unique id. * * @return a unique id. */ protected String getUniqueId() { return m_idManager.createUuid(); } protected String getUniqueIdBasedOnFields(String displayName, String description, String type, String location) { StringBuffer key = new StringBuffer(); key.append(displayName + description + type + location); String id = null; int n = 0; boolean unique = false; while (!unique) { byte[] bytes = key.toString().getBytes(); try{ MessageDigest digest = MessageDigest.getInstance("SHA-1"); digest.update(bytes); bytes = digest.digest(); id = getHexStringFromBytes(bytes); }catch(NoSuchAlgorithmException e){ // fall back to Base64 byte[] encoded = Base64.encodeBase64(bytes); id = StringUtils.newStringUtf8(encoded); } if (!m_storage.containsKey(id)) unique = true; else key.append(n++); } return id; } protected String getHexStringFromBytes(byte[] raw) { final String HEXES = "0123456789ABCDEF"; if(raw == null) { return null; } final StringBuilder hex = new StringBuilder(2 * raw.length); for(final byte b : raw) { hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F))); } return hex.toString(); } /** * 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<CalendarEvent> filterEvents(List<CalendarEvent> events, TimeRange range) { List<CalendarEvent> filtered = new ArrayList<CalendarEvent>(); for (int i = 0; i < events.size(); i++) { CalendarEvent event = events.get(i); // resolve the event to the list of events in this range // TODO Support for recurring events List<CalendarEvent> resolved = ((ExternalCalendarEvent) event) .resolve(range); filtered.addAll(resolved); } return filtered; } } public class ExternalCalendarEvent implements CalendarEvent { // protected Calendar m_calendar = null; protected String m_calendar_context = null; protected String m_calendar_id = null; protected ResourcePropertiesEdit m_properties = null; protected String m_id = null; protected String calendarReference = null; protected TimeRange m_range = null; protected TimeRange m_baseRange = null; protected RecurrenceRule m_singleRule = null; protected RecurrenceRule m_exclusionRule = null; public ExternalCalendarEvent(String calendarContext, String calendarId, String id) { this(calendarContext, calendarId, id, null); } public ExternalCalendarEvent(String calendarContext, String calendarId, String id, String eventType) { m_id = id; // m_calendar = calendar; m_calendar_context = calendarContext; m_calendar_id = calendarId; m_properties = new BaseResourcePropertiesEdit(); if (eventType != null) m_properties .addProperty(ResourceProperties.PROP_CALENDAR_TYPE, eventType); } public ExternalCalendarEvent(CalendarEvent other, RecurrenceInstance ri) { // m_calendar = ((ExternalCalendarEvent) other).m_calendar; m_calendar_context = ((ExternalCalendarEvent) other).m_calendar_context; m_calendar_id = ((ExternalCalendarEvent) other).m_calendar_id; // encode the instance and the other's id into my id m_id = '!' + ri.getRange().toString() + '!' + ri.getSequence() + '!' + ((ExternalCalendarEvent) other).m_id; // use the new range m_range = (TimeRange) ri.getRange().clone(); m_baseRange = ((ExternalCalendarEvent) other).m_range; // point at the properties m_properties = ((ExternalCalendarEvent) other).m_properties; // point at the rules m_singleRule = ((ExternalCalendarEvent) other).m_singleRule; m_exclusionRule = ((ExternalCalendarEvent) other).m_exclusionRule; } public EventAccess getAccess() { return CalendarEvent.EventAccess.SITE; } public String getCalendarReference() { // return m_calendar.getReference(); return m_calendarService.calendarSubscriptionReference(m_calendar_context, m_calendar_id); } // protected Calendar getCalendar(){ // return m_calendar; // } // protected void setCalendar(Calendar calendar) { // m_calendar = calendar; // } protected void setCalendarContext(String calendarContext) { m_calendar_context = calendarContext; } protected void setCalendarId(String calendarId) { m_calendar_id = calendarId; } public String getCreator() { return m_properties.getProperty(ResourceProperties.PROP_CREATOR); } public String getDescription() { return FormattedText .convertFormattedTextToPlaintext(getDescriptionFormatted()); } 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; } public String getDisplayName() { return m_properties .getPropertyFormatted(ResourceProperties.PROP_DISPLAY_NAME); } public String getField(String name) { // names are prefixed to form a namespace name = ResourceProperties.PROP_CALENDAR_EVENT_FIELDS + "." + name; return m_properties.getPropertyFormatted(name); } public Collection getGroupObjects() { return new ArrayList(); } public String getGroupRangeForDisplay(Calendar calendar) { return ""; } public Collection getGroups() { return new ArrayList(); } public String getLocation() { return m_properties .getPropertyFormatted(ResourceProperties.PROP_CALENDAR_LOCATION); } public String getModifiedBy() { return m_properties.getPropertyFormatted(ResourceProperties.PROP_MODIFIED_BY); } 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; } public RecurrenceRule getRecurrenceRule() { return m_singleRule; } public RecurrenceRule getExclusionRule() { if (m_exclusionRule == null) m_exclusionRule = new ExclusionSeqRecurrenceRule(); return m_exclusionRule; } 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 { List instances = m_singleRule.generateInstances(this.getRange(), range, m_timeService.getLocalTimeZone()); // 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 ExternalCalendarEvent(this, ri); rv.add(clone); } } return rv; } public void setRecurrenceRule(RecurrenceRule rule) { m_singleRule = rule; } public void setExclusionRule(RecurrenceRule rule) { m_exclusionRule = rule; } public String getType() { return m_properties .getPropertyFormatted(ResourceProperties.PROP_CALENDAR_TYPE); } public boolean isUserOwner() { return false; } public String getId() { return m_id; } protected void setId(String id) { m_id = id; } public ResourceProperties getProperties() { return m_properties; } public String getReference() { // return m_calendar.getReference() + Entity.SEPARATOR + m_id; return m_calendarService.eventSubscriptionReference(m_calendar_context, m_calendar_id, m_id); } public String getReference(String rootProperty) { return rootProperty + getReference(); } public String getUrl() { return null;// m_calendar.getUrl() + getId(); } public String getUrl(String rootProperty) { return rootProperty + getUrl(); } public Element toXml(Document doc, Stack stack) { // TODO Auto-generated method stub return null; } 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; } public List getAttachments() { // TODO Auto-generated method stub return null; } 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); } public void setLocation(String location) { m_properties.addProperty(ResourceProperties.PROP_CALENDAR_LOCATION, location); } public void setType(String type) { m_properties.addProperty(ResourceProperties.PROP_CALENDAR_TYPE, type); } public void setDescription(String description) { setDescriptionFormatted(FormattedText .convertPlaintextToFormattedText(description)); } 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)); } public void setDisplayName(String displayName) { m_properties.addProperty(ResourceProperties.PROP_DISPLAY_NAME, displayName); } public void setRange(TimeRange range) { m_range = (TimeRange) range.clone(); } /** * Gets a site name for this calendar event */ public String getSiteName() { String calendarName = ""; if (m_calendar_context != null) { try { Site site = m_siteService.getSite(m_calendar_context); if (site != null) calendarName = site.getTitle(); } catch (IdUnusedException e) { m_log.warn(".getSiteName(): " + e); } } return calendarName; } } }