diff --git a/source/java/org/alfresco/repo/web/scripts/calendar/AbstractCalendarWebScript.java b/source/java/org/alfresco/repo/web/scripts/calendar/AbstractCalendarWebScript.java index 465181639e..2970eeb5c8 100644 --- a/source/java/org/alfresco/repo/web/scripts/calendar/AbstractCalendarWebScript.java +++ b/source/java/org/alfresco/repo/web/scripts/calendar/AbstractCalendarWebScript.java @@ -19,19 +19,24 @@ package org.alfresco.repo.web.scripts.calendar; import java.io.IOException; +import java.io.Serializable; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; +import org.alfresco.repo.calendar.CalendarModel; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.service.cmr.activities.ActivityService; import org.alfresco.service.cmr.calendar.CalendarEntry; import org.alfresco.service.cmr.calendar.CalendarService; +import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.site.SiteInfo; import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.GUID; import org.alfresco.util.ISO8601DateFormat; import org.json.JSONException; import org.json.JSONObject; @@ -197,6 +202,27 @@ public abstract class AbstractCalendarWebScript extends DeclarativeWebScript return model; } + /** + * For an event that is a recurring event, have an ignored child event + * generated for it + */ + protected NodeRef createIgnoreEvent(WebScriptRequest req, CalendarEntry parent) + { + // Get the date to be ignored + Map props = new HashMap(); + Date date = parseDate(req.getParameter("date")); + props.put(CalendarModel.PROP_IGNORE_EVENT_DATE, date); + + // Create a child node of the event + NodeRef ignored = nodeService.createNode( + parent.getNodeRef(), CalendarModel.ASSOC_IGNORE_EVENT_LIST, + QName.createQName(GUID.generate()), CalendarModel.TYPE_IGNORE_EVENT, props + ).getChildRef(); + + // No further setup is needed + return ignored; + } + @Override protected Map executeImpl(WebScriptRequest req, Status status, Cache cache) diff --git a/source/java/org/alfresco/repo/web/scripts/calendar/CalendarEntryDelete.java b/source/java/org/alfresco/repo/web/scripts/calendar/CalendarEntryDelete.java index 144bd7bc0b..330d2fe84e 100644 --- a/source/java/org/alfresco/repo/web/scripts/calendar/CalendarEntryDelete.java +++ b/source/java/org/alfresco/repo/web/scripts/calendar/CalendarEntryDelete.java @@ -18,16 +18,10 @@ */ package org.alfresco.repo.web.scripts.calendar; -import java.io.Serializable; -import java.util.Date; -import java.util.HashMap; import java.util.Map; -import org.alfresco.repo.calendar.CalendarModel; import org.alfresco.service.cmr.calendar.CalendarEntry; import org.alfresco.service.cmr.site.SiteInfo; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.GUID; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.json.JSONObject; @@ -69,16 +63,8 @@ public class CalendarEntryDelete extends AbstractCalendarWebScript // Special case for "deleting" an instance of a recurring event if(req.getParameter("date") != null && entry.getRecurrenceRule() != null) { - // Get the date to be ignored - Map props = new HashMap(); - Date date = parseDate(req.getParameter("date")); - props.put(CalendarModel.PROP_IGNORE_EVENT_DATE, date); - - // Create a child node of the event - nodeService.createNode( - entry.getNodeRef(), CalendarModel.ASSOC_IGNORE_EVENT_LIST, - QName.createQName(GUID.generate()), CalendarModel.TYPE_IGNORE_EVENT, props - ); + // Have an ignored event generated + createIgnoreEvent(req, entry); // Mark as ignored status.setCode(Status.STATUS_NO_CONTENT, "Recurring entry ignored"); diff --git a/source/java/org/alfresco/repo/web/scripts/calendar/CalendarEntryGet.java b/source/java/org/alfresco/repo/web/scripts/calendar/CalendarEntryGet.java index 7e03c0060e..54bc247ae8 100644 --- a/source/java/org/alfresco/repo/web/scripts/calendar/CalendarEntryGet.java +++ b/source/java/org/alfresco/repo/web/scripts/calendar/CalendarEntryGet.java @@ -18,13 +18,20 @@ */ package org.alfresco.repo.web.scripts.calendar; +import java.text.DateFormat; +import java.text.DateFormatSymbols; +import java.text.SimpleDateFormat; +import java.util.Calendar; import java.util.HashMap; import java.util.Map; import org.alfresco.service.cmr.calendar.CalendarEntry; import org.alfresco.service.cmr.calendar.CalendarEntryDTO; import org.alfresco.service.cmr.site.SiteInfo; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.json.JSONObject; +import org.springframework.extensions.surf.util.I18NUtil; import org.springframework.extensions.webscripts.Cache; import org.springframework.extensions.webscripts.Status; import org.springframework.extensions.webscripts.WebScriptRequest; @@ -37,6 +44,8 @@ import org.springframework.extensions.webscripts.WebScriptRequest; */ public class CalendarEntryGet extends AbstractCalendarWebScript { + private static Log logger = LogFactory.getLog(CalendarEntryGet.class); + @Override protected Map executeImpl(SiteInfo site, String eventName, WebScriptRequest req, JSONObject json, Status status, Cache cache) { @@ -62,7 +71,7 @@ public class CalendarEntryGet extends AbstractCalendarWebScript result.put("outlookuid", entry.getOutlookUID()); result.put("allday", CalendarEntryDTO.isAllDay(entry)); result.put("docfolder", entry.getSharePointDocFolder()); - result.put("recurrence", null); // TODO + result.put("recurrence", buildRecurrenceString(entry)); // Replace nulls with blank strings for the JSON for(String key : result.keySet()) @@ -78,4 +87,141 @@ public class CalendarEntryGet extends AbstractCalendarWebScript model.put("result", result); return model; } + + /** + * This method replicates the pre-existing behaviour for recurring events. + * Rather than try to render the text for them on the client, we instead + * statically render the description text here on the server. + * When we properly support recurring events in the client (and not just + * for SharePoint ones), this can be replaced. + */ + protected String buildRecurrenceString(CalendarEntry event) + { + // If there's no recurrence rules, then there's nothing to do + String recurrence = event.getRecurrenceRule(); + if(recurrence == null || recurrence.trim().length() == 0) + { + return null; + } + + // Get our days of the week, in the current locale + DateFormatSymbols dates = new DateFormatSymbols(I18NUtil.getLocale()); + String[] weekdays = dates.getWeekdays(); + + // And map them based on the outlook two letter codes + Map days = new HashMap(); + days.put("SU", weekdays[Calendar.SUNDAY]); + days.put("MO", weekdays[Calendar.MONDAY]); + days.put("TU", weekdays[Calendar.TUESDAY]); + days.put("WE", weekdays[Calendar.WEDNESDAY]); + days.put("Th", weekdays[Calendar.THURSDAY]); + days.put("FR", weekdays[Calendar.FRIDAY]); + days.put("SA", weekdays[Calendar.SATURDAY]); + + // Turn the string into a useful map + Map params = new HashMap(); + for(String rule : recurrence.split(";")) + { + String[] parts = rule.split("="); + if(parts.length != 2) + { + logger.warn("Invalid rule '" + rule + "' in recurrence: " + recurrence); + } + else + { + params.put(parts[0], parts[1]); + } + } + + // To hold our result + StringBuffer text = new StringBuffer(); + + // Handle the different frequencies + if(params.containsKey("FREQ")) + { + String freq = params.get("FREQ"); + String interval = params.get("INTERVAL"); + if(interval == null) + { + interval = "1"; + } + + if ("WEEKLY".equals(freq)) + { + if ("1".equals(interval)) + { + text.append("Occurs each week on "); + } + else + { + text.append("Occurs every " + interval + " weeks on "); + } + + for(String day : params.get("BYDAY").split(",")) + { + text.append(days.get(day)); + text.append(", "); + } + } + else if ("DAILY".equals(freq)) + { + text.append("Occurs every day "); + } + else if ("MONTHLY".equals(freq)) + { + if (params.get("BYMONTHDAY") != null) + { + text.append("Occurs day " + params.get("BYMONTHDAY")); + } + else if (params.get("BYSETPOS") != null) + { + text.append("Occurs the "); + text.append(days.get(params.get("BYSETPOS"))); + } + text.append(" of every " + interval + " month(s) "); + } + else if ("YEARLY".equals(freq)) + { + if (params.get("BYMONTHDAY") != null) + { + text.append("Occurs every " + params.get("BYMONTHDAY")); + text.append("." + params.get("BYMONTH") + " "); + } + else + { + text.append("Occurs the "); + text.append(days.get(params.get("BYSETPOS"))); + text.append(" of " + params.get("BYMONTH") + " month "); + } + } + else + { + logger.warn("Unsupported recurrence frequency " + freq); + } + } + + // And the rest + DateFormat dFormat = SimpleDateFormat.getDateInstance( + SimpleDateFormat.MEDIUM, I18NUtil.getLocale() + ); + DateFormat tFormat = SimpleDateFormat.getTimeInstance( + SimpleDateFormat.SHORT, I18NUtil.getLocale() + ); + text.append("effective " + dFormat.format(event.getStart())); + + if (params.containsKey("COUNT")) + { + // Nothing to do, is already handled in the recurrence date + } + if (event.getLastRecurrence() != null) + { + text.append(" until " + dFormat.format(event.getLastRecurrence())); + } + + text.append(" from " + tFormat.format(event.getStart())); + text.append(" to " + tFormat.format(event.getEnd())); + + // All done + return text.toString(); + } } diff --git a/source/java/org/alfresco/repo/web/scripts/calendar/CalendarEntryPut.java b/source/java/org/alfresco/repo/web/scripts/calendar/CalendarEntryPut.java index 044bb773a5..adda59484f 100644 --- a/source/java/org/alfresco/repo/web/scripts/calendar/CalendarEntryPut.java +++ b/source/java/org/alfresco/repo/web/scripts/calendar/CalendarEntryPut.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.StringTokenizer; import org.alfresco.service.cmr.calendar.CalendarEntry; +import org.alfresco.service.cmr.calendar.CalendarEntryDTO; import org.alfresco.service.cmr.site.SiteInfo; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -63,29 +64,24 @@ public class CalendarEntryPut extends AbstractCalendarWebScript // Doc folder is a bit special String docFolder = json.getString("docfolder"); - if(entry.getRecurrenceRule() != null) + // Editing recurring events is special and a little bit odd... + if(entry.getRecurrenceRule() != null && !json.has("recurrenceRule")) { - // TODO Handle editing recurring rules - // Needs stuff with ignored events -/* - var prop = new Array(); - var fromParts = params.date.split("-"); - prop["ia:date"] = new Date(fromParts[0],fromParts[1] - 1,fromParts[2]); - editedEvent.createNode(null, "ia:ignoreEvent", prop, "ia:ignoreEventList"); - - var timestamp = new Date().getTime(); - var random = Math.round(Math.random() * 10000); - - event = eventsFolder.createNode(timestamp + "-" + random + ".ics", "ia:calendarEvent"); - event.properties["ia:isOutlook"] = true; - - */ + // Have an ignored event generated + // Will allow us to override this one instance + createIgnoreEvent(req, entry); + + // Create a new entry for this one case + CalendarEntry newEntry = new CalendarEntryDTO(); + newEntry.setOutlook(true); - // TODO Special doc folder stuff if("*NOT_CHANGE*".equals(docFolder)) { - // TODO + newEntry.setSharePointDocFolder(entry.getSharePointDocFolder()); } + + // From here on, "edit" the new version + entry = newEntry; } // Doc folder is a bit special @@ -107,6 +103,32 @@ public class CalendarEntryPut extends AbstractCalendarWebScript // Handle the dates isAllDay = extractDates(entry, json); + // Recurring properties, only changed if keys present + if (json.has("recurrenceRule")) + { + if (json.isNull("recurrenceRule")) + { + entry.setRecurrenceRule(null); + } + else + { + entry.setRecurrenceRule(json.getString("recurrenceRule")); + } + } + if (json.has("recurrenceLastMeeting")) + { + if (json.isNull("recurrenceLastMeeting")) + { + entry.setLastRecurrence(null); + } + else + { + entry.setLastRecurrence( + parseDate(json.getString("recurrenceLastMeeting")) + ); + } + } + // Handle tags if(json.has("tags")) { diff --git a/source/java/org/alfresco/repo/web/scripts/calendar/CalendarRestApiTest.java b/source/java/org/alfresco/repo/web/scripts/calendar/CalendarRestApiTest.java index a980995dd5..a001bcc48f 100644 --- a/source/java/org/alfresco/repo/web/scripts/calendar/CalendarRestApiTest.java +++ b/source/java/org/alfresco/repo/web/scripts/calendar/CalendarRestApiTest.java @@ -23,6 +23,7 @@ import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.site.SiteModel; import org.alfresco.repo.web.scripts.BaseWebScriptTest; +import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.security.MutableAuthenticationService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.site.SiteInfo; @@ -53,6 +54,7 @@ public class CalendarRestApiTest extends BaseWebScriptTest private MutableAuthenticationService authenticationService; private AuthenticationComponent authenticationComponent; private PersonService personService; + private NodeService nodeService; private SiteService siteService; private static final String USER_ONE = "UserOneSecondToo"; @@ -82,6 +84,7 @@ public class CalendarRestApiTest extends BaseWebScriptTest this.authenticationService = (MutableAuthenticationService)getServer().getApplicationContext().getBean("AuthenticationService"); this.authenticationComponent = (AuthenticationComponent)getServer().getApplicationContext().getBean("authenticationComponent"); this.personService = (PersonService)getServer().getApplicationContext().getBean("PersonService"); + this.nodeService = (NodeService)getServer().getApplicationContext().getBean("NodeService"); this.siteService = (SiteService)getServer().getApplicationContext().getBean("SiteService"); // Authenticate as user @@ -246,8 +249,8 @@ public class CalendarRestApiTest extends BaseWebScriptTest /** * Updates the event to be a 2 hour, non-all day event on the 28th of June */ - public JSONObject updateEntry(String name, String what, String where, String description, - int expectedStatus) throws Exception + private JSONObject updateEntry(String name, String what, String where, String description, + boolean withRecurrence, int expectedStatus) throws Exception { String date = "2011/06/28"; String start = "11:30"; @@ -267,6 +270,12 @@ public class CalendarRestApiTest extends BaseWebScriptTest json.put("docfolder", ""); json.put("page", "calendar"); + if(withRecurrence) + { + json.put("recurrenceRule", "FREQ=WEEKLY;INTERVAL=2;BYDAY=WE,FR"); + json.put("recurrenceLastMeeting", "2011-09-11"); + } + Response response = sendRequest(new PutRequest(URL_EVENT_BASE + name, json.toString(), "application/json"), expectedStatus); if (expectedStatus == Status.STATUS_OK) { @@ -370,7 +379,7 @@ public class CalendarRestApiTest extends BaseWebScriptTest // Edit - entry = updateEntry(name, EVENT_TITLE_ONE, "More Where", "More Thing", Status.STATUS_OK); + entry = updateEntry(name, EVENT_TITLE_ONE, "More Where", "More Thing", false, Status.STATUS_OK); assertEquals("Error found " + entry.toString(), false, entry.has("error")); assertEquals(EVENT_TITLE_ONE, entry.getString("summary")); assertEquals("More Where", entry.getString("location")); @@ -403,6 +412,31 @@ public class CalendarRestApiTest extends BaseWebScriptTest // TODO Make it a whole day event and check that + // Make it recurring + entry = updateEntry(name, EVENT_TITLE_ONE, "More Where", "More Thing", true, Status.STATUS_OK); + + // Fetch + entry = getEntry(name, Status.STATUS_OK); + + assertEquals("Error found " + entry.toString(), false, entry.has("error")); + assertEquals(EVENT_TITLE_ONE, entry.getString("what")); + assertEquals(name, entry.getString("name")); + assertEquals("More Where", entry.getString("location")); // Not where... + assertEquals("More Thing", entry.getString("description")); + + assertEquals("false", entry.getString("isoutlook")); + assertEquals("6/28/2011", entry.getString("from")); + assertEquals("6/28/2011", entry.getString("to")); + assertEquals("11:30", entry.getString("start")); + assertEquals("13:30", entry.getString("end")); + assertEquals("false", entry.getString("allday")); + assertEquals( + "Occurs every 2 weeks on Wednesday, Friday, effective " + + "28-Jun-2011 until 11-Sep-2011 from 11:30 to 13:30", + entry.getString("recurrence") + ); + + // Delete sendRequest(new DeleteRequest(URL_EVENT_BASE + name), Status.STATUS_NO_CONTENT); @@ -453,7 +487,7 @@ public class CalendarRestApiTest extends BaseWebScriptTest // Add a third, on the next day JSONObject entry = createEntry(EVENT_TITLE_THREE, "Where3", "Thing 3", Status.STATUS_OK); String name3 = getNameFromEntry(entry); - updateEntry(name3, EVENT_TITLE_THREE, "More Where 3", "More Thing 3", Status.STATUS_OK); + updateEntry(name3, EVENT_TITLE_THREE, "More Where 3", "More Thing 3", false, Status.STATUS_OK); // Check now, should have two days @@ -506,7 +540,7 @@ public class CalendarRestApiTest extends BaseWebScriptTest // Add a third, on the next day JSONObject entry = createEntry(EVENT_TITLE_THREE, "Where3", "Thing 3", Status.STATUS_OK); String name3 = getNameFromEntry(entry); - updateEntry(name3, EVENT_TITLE_THREE, "More Where 3", "More Thing 3", Status.STATUS_OK); + updateEntry(name3, EVENT_TITLE_THREE, "More Where 3", "More Thing 3", false, Status.STATUS_OK); // Check getting all of them