ALF-9156 Port the existing (SharePoint Specific) recurring events logic to Java, and add some basic tests for fetching it

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@28917 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Nick Burch
2011-07-11 14:22:48 +00:00
parent 761099b84e
commit cb86321b59
5 changed files with 254 additions and 40 deletions

View File

@@ -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<QName,Serializable> props = new HashMap<QName, Serializable>();
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<String, Object> executeImpl(WebScriptRequest req,
Status status, Cache cache)

View File

@@ -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<QName,Serializable> props = new HashMap<QName, Serializable>();
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");

View File

@@ -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<String, Object> 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<String,String> days = new HashMap<String, String>();
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<String,String> params = new HashMap<String, String>();
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();
}
}

View File

@@ -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");
// Have an ignored event generated
// Will allow us to override this one instance
createIgnoreEvent(req, entry);
var timestamp = new Date().getTime();
var random = Math.round(Math.random() * 10000);
// Create a new entry for this one case
CalendarEntry newEntry = new CalendarEntryDTO();
newEntry.setOutlook(true);
event = eventsFolder.createNode(timestamp + "-" + random + ".ics", "ia:calendarEvent");
event.properties["ia:isOutlook"] = 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"))
{

View File

@@ -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