package er.calendar; import java.util.Calendar; import java.util.Enumeration; import java.util.GregorianCalendar; import com.webobjects.appserver.WOApplication; import com.webobjects.appserver.WOComponent; import com.webobjects.appserver.WOContext; import com.webobjects.appserver.WOResponse; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSData; import com.webobjects.foundation.NSMutableArray; import com.webobjects.foundation.NSTimeZone; import com.webobjects.foundation.NSTimestamp; import com.webobjects.foundation.NSTimestampFormatter; /** * ERPublishCalendarPage is a WebObjects component for dynamically * generated iCalendar documents. * <p> * The response created by ERPublishCalendarPage is an iCalendar * document (.ics) containing the events added to ERPublishCalendarPage * by the application (see {@link #addEvent(ERCalendarEvent) addEvent}). * An iCalendar-aware application, such as Apple's iCal, can * subscribe to such a calendar, provided that the page has a fixed * URL (either is the "Main" page, or a direct action serves the page). * <p> * Events added to a ERPublishCalendarPage is objects of any class that * implements {@link ERCalendarEvent the ERCalendarEvent interface}. * Existing classes (for example EOCustomObject subclasses), that * correspond to calendar events, can easily be modified to * implement {@link ERCalendarEvent ERCalendarEvent} and thus be added * directly to ERPublishCalendarPage. If existing classes does not * directly correspond to calendar events, create events from business * data (or some algorithm) using either the included * {@link ERSimpleEvent ERSimpleEvent class}, a subclass of * {@link ERSimpleEvent ERSimpleEvent}, or any other class implementing * {@link ERCalendarEvent the ERCalendarEvent interface}. * * @author Johan Carlberg <johan@oops.se> * @version 1.0, 2002-09-30 */ public class ERPublishCalendarPage extends WOComponent { /** * Do I need to update serialVersionUID? * See section 5.6 <cite>Type Changes Affecting Serialization</cite> on page 51 of the * <a href="http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf">Java Object Serialization Spec</a> */ private static final long serialVersionUID = 1L; protected String calendarName; protected String calendarTimeZone; protected final int maxLineLength = 75; public static String newline = System.getProperty("line.separator"); protected NSMutableArray events; public ERCalendarEvent event; protected NSTimestamp eventTimestamp; protected NSTimestampFormatter dateTimeFormatter; protected NSTimestampFormatter dateFormatter; protected NSTimestampFormatter utcDateTimeFormatter; protected NSTimestampFormatter timeZoneFormatter; /** * Standard constructor for WOComponent subclasses. * * @param context context of a transaction * @see WOComponent#pageWithName(String) * @see WOApplication#pageWithName(String, WOContext) */ public ERPublishCalendarPage(WOContext context) { super(context); events = new NSMutableArray(); calendarTimeZone = new NSTimestampFormatter ("%Z").format (new NSTimestamp()); dateTimeFormatter = new NSTimestampFormatter ("%Y%m%dT%H%M%S"); dateTimeFormatter.setDefaultFormatTimeZone (NSTimeZone.localTimeZone()); dateFormatter = new NSTimestampFormatter ("%Y%m%d"); dateFormatter.setDefaultFormatTimeZone (NSTimeZone.localTimeZone()); utcDateTimeFormatter = new NSTimestampFormatter ("%Y%m%dT%H%M%SZ"); utcDateTimeFormatter.setDefaultFormatTimeZone (NSTimeZone.timeZoneWithName ("UTC", false)); timeZoneFormatter = new NSTimestampFormatter ("%Z"); timeZoneFormatter.setDefaultFormatTimeZone (NSTimeZone.localTimeZone()); } /** * Modifies content encoding to UTF8, and content type to text/calendar. * * @param aResponse the HTTP response that an application returns to a Web server to complete a cycle of the request-response loop * @param aContext context of a transaction */ @Override public void appendToResponse (WOResponse aResponse, WOContext aContext) { eventTimestamp = new NSTimestamp(); aResponse.setContentEncoding("UTF-8"); super.appendToResponse (aResponse, aContext); aResponse.setHeader ("text/calendar","content-type"); try { aResponse.setContent(new NSData(foldLongLinesInString(new String(aResponse.content().bytes(), "UTF-8")).getBytes("UTF-8"))); } catch (java.io.UnsupportedEncodingException exception) { // If encoding is not supported, content of response is left unmodified // (although exceptions will be thrown elsewhere if UTF-8 is unsupported). } } /** * Adds an event to the calendar. * * @param event the event to be included in the calendar * @see ERCalendarEvent * @see #addEventsFromArray(NSArray) */ public void addEvent (ERCalendarEvent event) { events.addObject (event); } /** * Adds an array of events to the calendar. * * @param eventsArray the events to be included in the calendar * @see ERCalendarEvent * @see #addEvent(ERCalendarEvent) */ public void addEventsFromArray (NSArray eventsArray) { events.addObjectsFromArray (eventsArray); } /** * Removes a previously added event from the calendar. * * @param event the event to be removed from the calendar * @see ERCalendarEvent * @see #removeEventsInArray(NSArray) */ public void removeEvent (ERCalendarEvent event) { events.removeObject (event); } /** * Removes an array of previously added events from the calendar. * * @param eventsArray the events to be removed from the calendar * @see ERCalendarEvent * @see #removeEvent(ERCalendarEvent) */ public void removeEventsInArray (NSArray eventsArray) { events.removeObjectsInArray (eventsArray); } public NSMutableArray events() { return events; } /** * @return name of the calendar * @see #setCalendarName(String) */ public String calendarName() { return calendarName; } /** * @return name of the calendar, backslash escaped for inclusion in * iCalendar document. * @see #calendarName */ public String escapedCalendarName() { return escapedString (calendarName); } /** * Sets the name of the calendar. * * @param value name of the calendar * @see #calendarName */ public void setCalendarName (String value) { calendarName = value; } /** * @return originating time zone for the calendar (name of the * system default time zone, if not changed by * {@link #setCalendarTimeZone(String) setCalendarTimeZone} * @see #setCalendarTimeZone(String) */ public String calendarTimeZone() { return calendarTimeZone; } /** * @return time zone name of the calendar, backslash escaped * for inclusion in iCalendar document. * @see #calendarTimeZone */ public String escapedCalendarTimeZone() { return escapedString (calendarTimeZone); } /** * Sets the name of the time zone for the calendar. * * @param value name of the time zone * @see #calendarTimeZone */ public void setCalendarTimeZone (String value) { calendarTimeZone = value; } /** * @return status of the current event, backslash escaped * for inclusion in iCalendar document. * @see ERCalendarEvent#status() */ public String escapedEventStatus() { return escapedString (event.status()); } /** * @return summary of the current event, backslash escaped * for inclusion in iCalendar document. * @see ERCalendarEvent#summary() */ public String escapedEventSummary() { return escapedString (event.summary()); } /** * @return unique id of the current event, backslash escaped * for inclusion in iCalendar document. * @see ERCalendarEvent#uniqueId */ public String escapedEventUniqueId() { return escapedString (event.uniqueId()); } /** * @return timestamp of the current event. * This will always be the current time, as this is * the time the event is converted to an iCalendar * event. */ public NSTimestamp eventTimestamp() { return eventTimestamp; } /** * @return the recurring rule frequency, as one of "YEARLY", "MONTHLY", * "WEEKLY", "DAILY", "HOURLY", "MINUTELY", "SECONDLY" depending * on the value returned by {@link ERCalendarEvent#repeatFrequency * repeatFrequency}. * @see ERCalendarEvent#repeatFrequency() */ public String eventRepeatFrequency() { switch (event.repeatFrequency()) { case Calendar.YEAR: return "YEARLY"; case Calendar.MONTH: return "MONTHLY"; case Calendar.WEEK_OF_YEAR: return "WEEKLY"; case Calendar.DAY_OF_MONTH: return "DAILY"; case Calendar.HOUR_OF_DAY: return "HOURLY"; case Calendar.MINUTE: return "MINUTELY"; case Calendar.SECOND: return "SECONDLY"; default: return null; } } /** * @return month number of a repeating event for use in * the "BYMONTH" parameter. */ public Number eventRepeatMonth() { GregorianCalendar calendarDate = new GregorianCalendar(); calendarDate.setTime (event.startTime()); return Integer.valueOf(calendarDate.get(Calendar.MONTH) + 1); } /** * @return day of week of a repeating event for use in * the "BYDAY" parameter. */ public String eventRepeatDayOfWeekString() { String byDay = ""; if (event.repeatDayOfWeekInMonth() != 0) { byDay = Integer.valueOf(event.repeatDayOfWeekInMonth()).toString(); } switch (event.repeatDayOfWeek()) { case Calendar.SUNDAY: byDay += "SU"; break; case Calendar.MONDAY: byDay += "MO"; break; case Calendar.TUESDAY: byDay += "TU"; break; case Calendar.WEDNESDAY: byDay += "WE"; break; case Calendar.THURSDAY: byDay += "TH"; break; case Calendar.FRIDAY: byDay += "FR"; break; case Calendar.SATURDAY: byDay += "SA"; break; } return byDay; } /** * @return days of month of a repeating event for use in * the "BYMONTHDAY" parameter. */ public String eventRepeatDaysOfMonthString() { return event.repeatDaysOfMonth().componentsJoinedByString (","); } /** * @return formatter for date/time. * Will format date/times as "20021003T191234", * specified in the local time zone. */ public NSTimestampFormatter dateTimeFormatter() { return dateTimeFormatter; } /** * @return formatter for dates. * Will format dates as "20021003", * specified in the local time zone. */ public NSTimestampFormatter dateFormatter() { return dateFormatter; } /** * @return formatter for date/time stamps. * Will format date/times as "20021003T171234Z", * specified in UTC (GMT). */ public NSTimestampFormatter utcDateTimeFormatter() { return utcDateTimeFormatter; } /** * @return formatter for time zone. * Will format date/times as only the name * of the local time zone. */ public NSTimestampFormatter timeZoneFormatter() { return timeZoneFormatter; } /** * @return backslash escaped text string, with special characters * replaced with its backslash escaped equivalent. */ protected String escapedString (String string) { StringBuffer escapedString = new StringBuffer (string); int index; for (index = escapedString.length() - 1; index >= 0; index -= 1) { switch (escapedString.charAt (index)) { case '"': case ';': case ':': case '\\': case ',': escapedString.insert (index, '\\'); break; case '\n': escapedString.setCharAt (index, 'n'); escapedString.insert (index, '\\'); break; } } return escapedString.toString(); } /** * Folds lines that are longer than the maximum allowed 75 characters. * * @param content unfolded iCalendar content * @return folded content, with no line longer than 75 characters */ protected String foldLongLinesInString (String content) { Enumeration enumerator = NSArray.componentsSeparatedByString (content, newline).objectEnumerator(); NSMutableArray foldedContent = new NSMutableArray(); String line; while (enumerator.hasMoreElements()) { line = (String)enumerator.nextElement(); while (line.length() > maxLineLength) { foldedContent.addObject (line.substring (0, 75)); line = " " + line.substring (75); } foldedContent.addObject (line); } return foldedContent.componentsJoinedByString (newline); } }