In the
previous entry the CalDAV protocol was introduced. This protocol is a standard way of managing calendar and scheduling iCalendar data files as WebDAV collections. If you have read the first part the final goal was testing some CalDAV methods against three different providers (
Google Calendar,
Yahoo! Calendar and
Calendar Server -open source version of Apple iCal Calendar Server-). The tester class was developed using
jackrabbit-webdav library for WebDAV protocol and
iCal4j for iCalendar data processing. In the previous post OPTIONS and PROPFIND methods were presented. The first one returns via headers if CalDAV is supported over an URI. The second method returns the properties of the same calendar CalDAV URI.
This entry continues testing CalDAV with the following methods:
- Creation of a calendar VEVENT via PUT method.
- Retrieving the just created VEVENT using calendar-multiget REPORT.
- Querying the VEVENTs using calendar-query REPORT.
- Deletion of the VEVENT.
PUT
The PUT method uploads a resource to a web server. Defined in the HTTP specification PUT sends any file inside body part, in case of CalDAV the only requirement is content was of type iCalendar (
text/calendar in
Content-Type header). Because a new event is going to be created I have added a
If-None-Match conditional header to not perform the PUT if any (*) resource already exists in the same URI.
HttpClient client = ...
PutMethod put = null;
try {
UUID uuid = UUID.randomUUID();
CalendarBuilder builder = new CalendarBuilder();
net.fortuna.ical4j.model.Calendar c = new net.fortuna.ical4j.model.Calendar();
c.getProperties().add(new ProdId("-//Ben Fortuna//iCal4j 1.0//EN"));
c.getProperties().add(Version.VERSION_2_0);
c.getProperties().add(CalScale.GREGORIAN);
TimeZoneRegistry registry = builder.getRegistry();
VTimeZone tz = registry.getTimeZone("Europe/Madrid").getVTimeZone();
c.getComponents().add(tz);
VEvent vevent = new VEvent(new net.fortuna.ical4j.model.Date(),
new Dur(0, 1, 0, 0), "test");
vevent.getProperties().add(new Uid(uuid.toString()));
c.getComponents().add(vevent);
String href = uri + uuid.toString() + ".ics";
put = new PutMethod(href);
put.addRequestHeader("If-None-Match", "*");
put.setRequestEntity(new StringRequestEntity(c.toString(), "text/calendar", "UTF-8"));
client.executeMethod(put);
return href;
} finally {
if (put != null) {
put.releaseConnection();
}
}
This code creates a new iCal4j calendar and assigns timezone and a event to it. After that a PUT method is constructed and the previously iCalendar object is set as its request. In WebDAV (and CalDAV) the client side is the responsible of choosing a name (the URI part) to the resource, here I have used a UUID to generate a random and different
href URI.
As PUT method is almost the same in the three server implementations only Google Calendar trace is presented.
Google Calendar:
PUT /calendar/dav/rickyepoderi@gmail.com/events/fca0402c-f00a-493c-9b48-8cec2a203728.ics HTTP/1.1
If-None-Match: *
User-Agent: Jakarta Commons-HttpClient/3.1
Content-Length: 3041
Content-Type: text/calendar; charset=UTF-8
Authorization: Basic XXXXXXXXXX
Host: www.google.com
BEGIN:VCALENDAR
PRODID:-//Ben Fortuna//iCal4j 1.0//EN
VERSION:2.0
CALSCALE:GREGORIAN
BEGIN:VTIMEZONE
TZID:Europe/Madrid
TZURL:http://tzurl.org/zoneinfo/Europe/Madrid
X-LIC-LOCATION:Europe/Madrid
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19810329T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19961027T030000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
END:STANDARD
BEGIN:STANDARD
TZOFFSETFROM:-001444
TZOFFSETTO:+0000
TZNAME:WET
DTSTART:19010101T011444
RDATE:19010101T011444
END:STANDARD
BEGIN:DAYLIGHT
TZOFFSETFROM:+0000
TZOFFSETTO:+0100
TZNAME:WEST
DTSTART:19170506T000000
RDATE:19170506T000000
RDATE:19180416T000000
RDATE:19190406T000000
RDATE:19240417T000000
RDATE:19260418T000000
RDATE:19270410T000000
RDATE:19280415T000000
RDATE:19290421T000000
RDATE:19370523T000000
RDATE:19380323T000000
RDATE:19390416T000000
RDATE:19400317T000000
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0100
TZOFFSETTO:+0000
TZNAME:WET
DTSTART:19171007T010000
RDATE:19171007T010000
RDATE:19181007T010000
RDATE:19191007T010000
RDATE:19241005T010000
RDATE:19261003T010000
RDATE:19271002T010000
RDATE:19281007T010000
RDATE:19291006T010000
RDATE:19371003T010000
RDATE:19381002T010000
RDATE:19391008T010000
END:STANDARD
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:WEMT
DTSTART:19420503T000000
RDATE:19420503T000000
RDATE:19430418T000000
RDATE:19440416T000000
RDATE:19450415T000000
RDATE:19460414T000000
END:DAYLIGHT
BEGIN:DAYLIGHT
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:WEST
DTSTART:19420902T010000
RDATE:19420902T010000
RDATE:19431004T010000
RDATE:19441011T010000
RDATE:19450930T020000
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19460930T000000
RDATE:19460930T000000
RDATE:19490930T010000
RDATE:19741006T010000
RDATE:19751005T010000
RDATE:19760926T010000
RDATE:19770925T010000
RDATE:19781001T010000
RDATE:19790930T030000
RDATE:19800928T030000
RDATE:19810927T030000
RDATE:19820926T030000
RDATE:19830925T030000
RDATE:19840930T030000
RDATE:19850929T030000
RDATE:19860928T030000
RDATE:19870927T030000
RDATE:19880925T030000
RDATE:19890924T030000
RDATE:19900930T030000
RDATE:19910929T030000
RDATE:19920927T030000
RDATE:19930926T030000
RDATE:19940925T030000
RDATE:19950924T030000
END:STANDARD
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19490430T230000
RDATE:19490430T230000
RDATE:19740413T230000
RDATE:19750419T230000
RDATE:19760327T230000
RDATE:19770402T230000
RDATE:19780402T230000
RDATE:19790401T020000
RDATE:19800406T020000
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0100
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19790101T000000
RDATE:19790101T000000
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
DTSTAMP:20100709T080233Z
DTSTART;VALUE=DATE:20100709
DURATION:PT1H
SUMMARY:test
UID:fca0402c-f00a-493c-9b48-8cec2a203728
END:VEVENT
END:VCALENDAR
HTTP/1.1 201 Created
DAV: 1, calendar-access, calendar-schedule, calendar-proxy
Content-Type: text/calendar; component=vevent; charset=UTF-8
Date: Fri, 09 Jul 2010 08:02:35 GMT
Expires: Fri, 09 Jul 2010 08:02:35 GMT
Cache-Control: private, max-age=0
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Content-Length: 0
Server: GSE
CALENDAR-MULTIGET REPORT
The first real CalDAV method. A REPORT is an extensible mechanism for obtaining information about resources defined in
RFC-3253. The value of a report can depend on additional information specified in the REPORT request body and in the REPORT request headers. CalDAV specification defines some of them and
calendar-multiget is the first one to be tested. It returns the specified data (any CalDAV report lets us define the iCalendar information that is going to be returned) for a list of resources (a list of URIs that represents iCalendar resources). As any other WebDAV method calendar-multiget has a standard XML request (defined via DTD) that is sent to the server. The response (like many other reports) is a multi-status response (a list of resource responses that match the report criteria with the properties requested attached).
So, first of all, I developed some classes that implement all the XML stuff (properties and report itself). All this simple classes are inside a
sample.caldav.xml package and they use the W3C DOM XML parser (jackrabbit-webdav already uses it). Besides jackrabbit-webdav has a
ReportMethod class to execute any report against a WebDAV server, this class uses another one (
ReportInfo) that defines the report to execute. The problem here is jackrabbit assumes all properties to return are specified using only names (simple XML tags like <getetag/> or <getctag/>) and CalDAV properties are more complicated (see the XML classes of the package presented before). For this reason my only chance was extending ReportInfo with a
CalendarMultiGetReportInfo.java that constructs the correct XML elements for this report (toXml method). One thing I did not understand completely was why jackrabbit needs all the reports to be registered inside
ReportType class, this registration uses a Report class which I did not see where is used. For this reason I created and registered an empty report
CalendarMultiGetReport.java. A class for constants called
CalDavConstants.java is also used in a similar way jackrabbit uses
DavConstants.java. The final code to execute the report that retrieves the previously created event is the following.
HttpClient client = ...
ReportMethod report = null;
try {
// append props GETTETAG
DavPropertyNameSet props = new DavPropertyNameSet();
props.add(DavPropertyName.GETETAG);
// append calendar-data
RequestCalendarData calendarData = new RequestCalendarData();
Comp vcalendar = new Comp("VCALENDAR");
vcalendar.getProp().add(new Prop("VERSION"));
vcalendar.getComp().add(new Comp("VEVENT"));
vcalendar.getComp().get(0).getProp().add(new Prop("SUMMARY"));
vcalendar.getComp().get(0).getProp().add(new Prop("UID"));
vcalendar.getComp().get(0).getProp().add(new Prop("DTSTART"));
vcalendar.getComp().get(0).getProp().add(new Prop("DTEND"));
vcalendar.getComp().get(0).getProp().add(new Prop("DURATION"));
vcalendar.getComp().get(0).getProp().add(new Prop("RRULE"));
vcalendar.getComp().get(0).getProp().add(new Prop("RDATE"));
vcalendar.getComp().get(0).getProp().add(new Prop("EXRULE"));
vcalendar.getComp().get(0).getProp().add(new Prop("EXDATE"));
vcalendar.getComp().get(0).getProp().add(new Prop("RECURRENCE-ID"));
vcalendar.getComp().add(new Comp("VTIMEZONE"));
calendarData.setComp(vcalendar);
// create the report
ReportInfo reportInfo = new CalendarMultiGetReportInfo(props,
calendarData, new String[]{createdHref});
report = new ReportMethod(uri, reportInfo);
client.executeMethod(report);
MultiStatus multiStatus = report.getResponseBodyAsMultiStatus();
for (int i = 0; i < multiStatus.getResponses().length; i++) {
MultiStatusResponse multiRes = multiStatus.getResponses()[i];
String href = multiRes.DavPropertySet propSet = multiRes.getProperties(
DavServletResponse.SC_OK);
DavProperty prop = (DavProperty) propSet.get(
CalDavConstants.CALDAV_XML_CALENDAR_DATA, CalDavConstants.CALDAV_NAMESPACE);
System.err.println("HREF: " + href);
CalendarBuilder builder = new CalendarBuilder();
net.fortuna.ical4j.model.Calendar c = builder.build(
new StringReader(prop.getValue()));
System.err.println("calendar-data: " + c.toString());
}
} finally {
if (report != null) {
report.releaseConnection();
}
}
As you see the example requests for VCALENDAR information (only property VERSION) and, inside it, the VEVENT (with only some properties) and the VTIMEZONE (with all properties). The HREF part must be the one returned in the creation part. I added a
ETag (Entity Tag) property as a non CalDAV property to be retrieved. Studying the below communication traces there are some curious results. First open source Calendar Server follows the guidelines and only returns the requested props but google and yahoo! always return all calendar data. And second only Calendar Server returns exactly the same data put before, google and yahoo! have modified the iCalendar resource (some kind of pre-processing before storing it).
Google Calendar:
REPORT /calendar/dav/rickyepoderi@gmail.com/events/ HTTP/1.1
Depth: 0
User-Agent: Jakarta Commons-HttpClient/3.1
Content-Length: 673
Content-Type: text/xml; charset=UTF-8
Authorization: Basic XXXXXXXX
Host: www.google.com
<?xml version="1.0" encoding="UTF-8"?>
<C:calendar-multiget xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:D="DAV:">
<D:prop>
<D:getetag/>
<C:calendar-data>
<C:comp name="VCALENDAR">
<C:prop name="VERSION"/>
<C:comp name="VEVENT">
<C:prop name="SUMMARY"/>
<C:prop name="UID"/>
<C:prop name="DTSTART"/>
<C:prop name="DTEND"/>
<C:prop name="DURATION"/>
<C:prop name="RRULE"/>
<C:prop name="RDATE"/>
<C:prop name="EXRULE"/>
<C:prop name="EXDATE"/>
<C:prop name="RECURRENCE-ID"/>
</C:comp>
<C:comp name="VTIMEZONE"/>
</C:comp>
</C:calendar-data>
</D:prop>
<D:href>https://www.google.com/calendar/dav/rickyepoderi@gmail.com/events/fca0402c-f00a-493c-9b48-8cec2a203728.ics</D:href>
</C:calendar-multiget>
HTTP/1.1 207 Multi-Status
DAV: 1, calendar-access, calendar-schedule, calendar-proxy
Content-Type: application/xml; charset=UTF-8
Date: Fri, 09 Jul 2010 08:02:36 GMT
Expires: Fri, 09 Jul 2010 08:02:36 GMT
Cache-Control: private, max-age=0
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Content-Length: 1059
Server: GSE
<?xml version="1.0" encoding="UTF-8"?>
<D:multistatus xmlns:D="DAV:">
<D:response>
<D:href>https://www.google.com/calendar/dav/rickyepoderi@gmail.com/events/fca0402c-f00a-493c-9b48-8cec2a203728.ics</D:href>
<D:propstat>
<D:status>HTTP/1.1 200 OK</D:status>
<D:prop>
<D:getetag>"63414345755"</D:getetag>
<C:calendar-data xmlns:C="urn:ietf:params:xml:ns:caldav">BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
VERSION:2.0
CALSCALE:GREGORIAN
X-WR-CALNAME:Ricardo Martin
X-WR-TIMEZONE:Europe/Paris
BEGIN:VEVENT
DTSTART:20100708T220000Z
DTEND:20100708T230000Z
DTSTAMP:20100709T080235Z
UID:fca0402c-f00a-493c-9b48-8cec2a203728
CREATED:20100709T080235Z
DESCRIPTION:
LAST-MODIFIED:20100709T080235Z
LOCATION:
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:test
TRANSP:OPAQUE
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:This is an event reminder
TRIGGER:-P0DT0H10M0S
END:VALARM
END:VEVENT
END:VCALENDAR</C:calendar-data>
</D:prop>
</D:propstat>
</D:response>
</D:multistatus>
Yahoo! Calendar:
REPORT /dav/rickyepoderi@yahoo.es/Calendar/Ricardo_Martin/ HTTP/1.1
Depth: 0[\n]
User-Agent: Jakarta Commons-HttpClient/3.1
Content-Length: 691
Content-Type: text/xml; charset=UTF-8
Authorization: Basic XXXXXXXXXX
Host: caldav.calendar.yahoo.com
<?xml version="1.0" encoding="UTF-8"?>
<C:calendar-multiget xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:D="DAV:">
<D:prop>
<D:getetag/>
<C:calendar-data>
<C:comp name="VCALENDAR">
<C:prop name="VERSION"/>
<C:comp name="VEVENT">
<C:prop name="SUMMARY"/>
<C:prop name="UID"/>
<C:prop name="DTSTART"/>
 <C:prop name="DTEND"/>
<C:prop name="DURATION"/>
<C:prop name="RRULE"/>
<C:prop name="RDATE"/>
<C:prop name="EXRULE"/>
<C:prop name="EXDATE"/>
<C:prop name="RECURRENCE-ID"/>
</C:comp>
<C:comp name="VTIMEZONE"/>
</C:comp>
</C:calendar-data>
</D:prop>
<D:href>https://caldav.calendar.yahoo.com/dav/rickyepoderi@yahoo.es/Calendar/Ricardo_Martin/b1e1683b-08d5-4ca3-b537-ed6a0c5867b9.ics</D:href>
</C:calendar-multiget>
HTTP/1.1 207 Multi Status
Date: Fri, 09 Jul 2010 09:39:46 GMT
P3P: policyref="http://info.yahoo.com/w3c/p3p.xml", CP="CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV"
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: version-control, addressbook, extended-mkcol, calendar-proxy
DAV: calendarserver-principal-property-search
Content-Type: text/xml; charset=utf-8
X-Cache: MISS from store138.c104.cal.gq1.yahoo.com
Age: 0
Transfer-Encoding: chunked
Connection: keep-alive
Server: YTS/1.17.23
<?xml version="1.0" encoding="UTF-8"?>
<D:multistatus xmlns:D="DAV:">
<D:response>
<D:href>/dav/rickyepoderi@yahoo.es/Calendar/Ricardo_Martin/b1e1683b-08d5-4ca3-b537-ed6a0c5867b9.ics</D:href>
<D:propstat>
<D:status>HTTP/1.1 200 OK</D:status>
<D:prop>
<D:getetag>"400-400"</D:getetag>
<C:calendar-data xmlns:C="urn:ietf:params:xml:ns:caldav">BEGIN:VCALENDAR
VERSION:2.0
PRODID:Zimbra-Calendar-Provider
BEGIN:VEVENT
UID:b1e1683b-08d5-4ca3-b537-ed6a0c5867b9
SUMMARY:test
DTSTART;VALUE=DATE:20100709
DURATION:PT1H
STATUS:CONFIRMED
CLASS:PUBLIC
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
TRANSP:OPAQUE
X-MICROSOFT-DISALLOW-COUNTER:TRUE
DTSTAMP:20100709T093944Z
SEQUENCE:0
END:VEVENT
END:VCALENDAR</C:calendar-data>
</D:prop>
</D:propstat>
</D:response>
</D:multistatus>
Calendar Server:
REPORT /calendars/users/admin/calendar/ HTTP/1.1
Depth: 0
User-Agent: Jakarta Commons-HttpClient/3.1
Content-Length: 660
Content-Type: text/xml; charset=UTF-8
Authorization: Digest username="admin", realm="Test Realm", nonce="64064457517812303241909094429", uri="/calendars/users/admin/calendar/", response="3fc6f79c4b2c8ef0c60e415429fc864e", algorithm="md5"
Host: localhost:8008
<?xml version="1.0" encoding="UTF-8"?>
<C:calendar-multiget xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:D="DAV:">
<D:prop>
<D:getetag/>
<C:calendar-data>
<C:comp name="VCALENDAR">
<C:prop name="VERSION"/>
<C:comp name="VEVENT">
<C:prop name="SUMMARY"/>
<C:prop name="UID"/>
<C:prop name="DTSTART"/>
<C:prop name="DTEND"/>
<C:prop name="DURATION"/>
<C:prop name="RRULE"/>
<C:prop name="RDATE"/>
<C:prop name="EXRULE"/>
<C:prop name="EXDATE"/>
<C:prop name="RECURRENCE-ID"/>
</C:comp>
<C:comp name="VTIMEZONE"/>
</C:comp>
</C:calendar-data>
</D:prop>
<D:href>http://localhost:8008/calendars/users/admin/calendar/8fbfb266-6f6b-4201-b51a-a75416640a25.ics</D:href>
</C:calendar-multiget>
HTTP/1.1 207 Multi-Status
Content-Length: 3457
Accept-Ranges: bytes
Server: CalendarServer/trunk(r5842M) Twisted/10.0.0 TwistedWeb/9.0.0+r5842
Last-Modified: Fri, 09 Jul 2010 09:49:08 GMT
DAV: 1, access-control, calendar-access, calendar-schedule, calendar-auto-schedule, calendar-availability, inbox-availability, calendar-proxy, calendarserver-private-events, calendarserver-private-comments, calendarserver-sharing, addressbook, calendarserver-principal-property-search
ETag: W/"285DD2-1000-4C36F094
Date: Fri, 09 Jul 2010 09:49:08 GMT
Content-Type: text/xml
<?xml version='1.0' encoding='UTF-8'?>
<multistatus xmlns='DAV:'>
<response>
<href>/calendars/users/admin/calendar/8fbfb266-6f6b-4201-b51a-a75416640a25.ics</href>
<propstat>
<prop>
<getetag>"234cd0ad246d14829d189feca8c0eead"</getetag>
<calendar-data xmlns='urn:ietf:params:xml:ns:caldav'><![CDATA[BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//PYVOBJECT//NONSGML Version 1//EN
BEGIN:VTIMEZONE
TZID:Europe/Madrid
TZURL:http://tzurl.org/zoneinfo/Europe/Madrid
BEGIN:STANDARD
DTSTART:19961027T030000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
END:STANDARD
BEGIN:STANDARD
DTSTART:19010101T011444
RDATE:19010101T011444
TZNAME:WET
TZOFFSETFROM:-001444
TZOFFSETTO:+0000
END:STANDARD
BEGIN:STANDARD
DTSTART:19171007T010000
RDATE:19171007T010000
RDATE:19181007T010000
RDATE:19191007T010000
RDATE:19241005T010000
RDATE:19261003T010000
RDATE:19271002T010000
RDATE:19281007T010000
RDATE:19291006T010000
RDATE:19371003T010000
RDATE:19381002T010000
RDATE:19391008T010000
TZNAME:WET
TZOFFSETFROM:+0100
TZOFFSETTO:+0000
END:STANDARD
BEGIN:STANDARD
DTSTART:19460930T000000
RDATE:19460930T000000
RDATE:19490930T010000
RDATE:19741006T010000
RDATE:19751005T010000
RDATE:19760926T010000
RDATE:19770925T010000
RDATE:19781001T010000
RDATE:19790930T030000
RDATE:19800928T030000
RDATE:19810927T030000
RDATE:19820926T030000
RDATE:19830925T030000
RDATE:19840930T030000
RDATE:19850929T030000
RDATE:19860928T030000
RDATE:19870927T030000
RDATE:19880925T030000
RDATE:19890924T030000
RDATE:19900930T030000
RDATE:19910929T030000
RDATE:19920927T030000
RDATE:19930926T030000
RDATE:19940925T030000
RDATE:19950924T030000
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
END:STANDARD
BEGIN:STANDARD
DTSTART:19790101T000000
RDATE:19790101T000000
TZNAME:CET
TZOFFSETFROM:+0100
TZOFFSETTO:+0100
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19810329T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
END:DAYLIGHT
BEGIN:DAYLIGHT
DTSTART:19170506T000000
RDATE:19170506T000000
RDATE:19180416T000000
RDATE:19190406T000000
RDATE:19240417T000000
RDATE:19260418T000000
RDATE:19270410T000000
RDATE:19280415T000000
RDATE:19290421T000000
RDATE:19370523T000000
RDATE:19380323T000000
RDATE:19390416T000000
RDATE:19400317T000000
END:DAYLIGHT
BEGIN:DAYLIGHT
DTSTART:19420503T000000
RDATE:19420503T000000
RDATE:19430418T000000
RDATE:19440416T000000
RDATE:19450415T000000
RDATE:19460414T000000
TZNAME:WEMT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
END:DAYLIGHT
BEGIN:DAYLIGHT
DTSTART:19420902T010000
RDATE:19420902T010000
RDATE:19431004T010000
RDATE:19441011T010000
RDATE:19450930T020000
TZNAME:WEST
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
END:DAYLIGHT
BEGIN:DAYLIGHT
DTSTART:19490430T230000
RDATE:19490430T230000
RDATE:19740413T230000
RDATE:19750419T230000
RDATE:19760327T230000
RDATE:19770402T230000
RDATE:19780402T230000
RDATE:19790401T020000
RDATE:19800406T020000
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
END:DAYLIGHT
X-LIC-LOCATION:Europe/Madrid
END:VTIMEZONE
BEGIN:VEVENT
UID:8fbfb266-6f6b-4201-b51a-a75416640a25
DTSTART;VALUE=DATE:20100709
DURATION:PT1H
SUMMARY:test
END:VEVENT
END:VCALENDAR
]]></calendar-data>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
</multistatus>
CALENDAR-QUERY REPORT
Calendar-query is other report defined in CalDAV to request calendar entries that fulfill a specified filter (entries between dates, iCalendar data with a specified value in a field and so on). In this case the same package presented before manages the XML stuff and, following the same idea, a
CalendarQueryReportInfo.java extension and
CalendarQueryReport.java was coded. First class is the ReportInfo for this new report and the second one does the registration part.
HttpClient client = ...
ReportMethod calendarQuery = null;
try {
RequestCalendarData calendarData = new RequestCalendarData();
Filter filter = new Filter("VCALENDAR");
filter.getCompFilter().add(new Filter("VEVENT"));
Calendar start = Calendar.getInstance();
start.add(Calendar.MONTH, -1);
Calendar end = Calendar.getInstance();
filter.getCompFilter().get(0).setTimeRange(
new TimeRange(start.getTime(), end.getTime()));
ReportInfo reportInfo = new CalendarQueryReportInfo(calendarData, filter);
calendarQuery = new ReportMethod(uri, reportInfo);
client.executeMethod(calendarQuery);
MultiStatus multiStatus = calendarQuery.getResponseBodyAsMultiStatus();
for (int i = 0; i < multiStatus.getResponses().length; i++) {
MultiStatusResponse multiRes = multiStatus.getResponses()[i];
String href = multiRes.getHref();
DavPropertySet propSet = multiRes.getProperties(DavServletResponse.SC_OK);
DavProperty prop = (DavProperty) propSet.get(
CalDavConstants.CALDAV_XML_CALENDAR_DATA, CalDavConstants.CALDAV_NAMESPACE);
System.err.println("HREF: " + href);
CalendarBuilder builder = new CalendarBuilder();
net.fortuna.ical4j.model.Calendar c = builder.build(
new StringReader(prop.getValue()));
System.err.println("calendar-data: " + c.toString());
}
} finally {
if (calendarQuery != null) {
calendarQuery.releaseConnection();
}
}
The sample report performs a query with a filter that returns all the events in a month. In this case all calendar data is requested.
Google Calendar:
REPORT /calendar/dav/rickyepoderi@gmail.com/events/ HTTP/1.1
Depth: 1
User-Agent: Jakarta Commons-HttpClient/3.1
Content-Length: 342
Content-Type: text/xml; charset=UTF-8
Authorization: Basic XXXXXXXXXX
Host: www.google.com
<?xml version="1.0" encoding="UTF-8"?>
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:D="DAV:">
<D:prop>
<C:calendar-data/>
</D:prop>
<C:filter>
<C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT">
<C:time-range end="20100709T101456Z" start="20100609T101456Z"/>
</C:comp-filter>
</C:comp-filter>
</C:filter>
</C:calendar-query>
HTTP/1.1 207 Multi-Status
DAV: 1, calendar-access, calendar-schedule, calendar-proxy
Content-Type: application/xml; charset=UTF-8
Date: Fri, 09 Jul 2010 10:14:57 GMT
Expires: Fri, 09 Jul 2010 10:14:57 GMT
Cache-Control: private, max-age=0
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Content-Length: 993
Server: GSE
<?xml version="1.0" encoding="UTF-8"?>
<D:multistatus xmlns:D="DAV:">
<D:response>
<D:href>/calendar/dav/rickyepoderi%40gmail.com/events/3fb8047e-4a51-4207-90a9-629e8bed1866.ics</D:href>
<D:propstat>
<D:status>HTTP/1.1 200 OK</D:status>
<D:prop>
<C:calendar-data xmlns:C="urn:ietf:params:xml:ns:caldav">BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
VERSION:2.0
CALSCALE:GREGORIAN
X-WR-CALNAME:Ricardo Martin
X-WR-TIMEZONE:Europe/Paris
BEGIN:VEVENT
DTSTART:20100708T220000Z
DTEND:20100708T230000Z
DTSTAMP:20100709T101456Z
UID:3fb8047e-4a51-4207-90a9-629e8bed1866
CREATED:20100709T101456Z
DESCRIPTION:
LAST-MODIFIED:20100709T101456Z
LOCATION:
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:test
RANSP:OPAQUE
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:This is an event reminder
TRIGGER:-P0DT0H10M0S
END:VALARM
END:VEVENT
END:VCALENDAR</C:calendar-data>
</D:prop>
</D:propstat>
</D:response>
</D:multistatus>
Yahoo! Calendar:
REPORT /dav/rickyepoderi@yahoo.es/Calendar/Ricardo_Martin/ HTTP/1.1
Depth: 1
User-Agent: Jakarta Commons-HttpClient/3.1
Content-Length: 342
Content-Type: text/xml; charset=UTF-8
Authorization: Basic XXXXXXXXXXXXX
Host: caldav.calendar.yahoo.com
<?xml version="1.0" encoding="UTF-8"?>
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:D="DAV:">
<D:prop>
<C:calendar-data/>
</D:prop>
<C:filter>
<C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT">
<C:time-range end="20100709T102151Z" start="20100609T102151Z"/>
</C:comp-filter>
</C:comp-filter>
</C:filter>
</C:calendar-query>
HTTP/1.1 207 Multi Status
Date: Fri, 09 Jul 2010 10:21:52 GMT
P3P: policyref="http://info.yahoo.com/w3c/p3p.xml", CP="CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV"
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: version-control, addressbook, extended-mkcol, calendar-proxy
DAV: calendarserver-principal-property-search
Content-Type: text/xml; charset=utf-8
X-Cache: MISS from store138.c104.cal.gq1.yahoo.com
Age: 0
Transfer-Encoding: chunked
Connection: keep-alive
Server: YTS/1.17.23
<?xml version="1.0" encoding="UTF-8"?>
<D:multistatus xmlns:D="DAV:">
<D:response>
<D:href>/dav/rickyepoderi@yahoo.es/Calendar/Ricardo_Martin/00584253-ab0d-41c0-b1fe-ee391eab6f78.ics</D:href>
<D:propstat>[\n]
<D:status>HTTP/1.1 200 OK</D:status>
<D:prop>
<C:calendar-data xmlns:C="urn:ietf:params:xml:ns:caldav">BEGIN:VCALENDAR
VERSION:2.0
PRODID:Zimbra-Calendar-Provider
BEGIN:VEVENT
UID:00584253-ab0d-41c0-b1fe-ee391eab6f78
SUMMARY:test
DTSTART;VALUE=DATE:20100709
DURATION:PT1H
STATUS:CONFIRMED
CLASS:PUBLIC
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
TRANSP:OPAQUE
X-MICROSOFT-DISALLOW-COUNTER:TRUE
DTSTAMP:20100709T102149Z
SEQUENCE:0
END:VEVENT
END:VCALENDAR</C:calendar-data>
</D:prop>
</D:propstat>
</D:response>
</D:multistatus>
Calendar Server:
REPORT /calendars/users/admin/calendar/ HTTP/1.1
Depth: 1
User-Agent: Jakarta Commons-HttpClient/3.1
Content-Length: 342
Content-Type: text/xml; charset=UTF-8
Authorization: Digest username="admin", realm="Test Realm", nonce="11647135610574514491121002269", uri="/calendars/users/admin/calendar/", response="9fc57dc0becf313811f762083aaefe7c", algorithm="md5"
Host: localhost:8008
<?xml version="1.0" encoding="UTF-8"?>
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:D="DAV:">
<D:prop>
<C:calendar-data/>
</D:prop>
<C:filter>
<C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT">
<C:time-range end="20100709T103056Z" start="20100609T103056Z"/>
</C:comp-filter>
</C:comp-filter>
</C:filter>
</C:calendar-query>
HTTP/1.1 207 Multi-Status
Content-Length: 8512
Accept-Ranges: bytes
Server: CalendarServer/trunk(r5842M) Twisted/10.0.0 TwistedWeb/9.0.0+r5842
Last-Modified: Fri, 09 Jul 2010 10:30:55 GMT
DAV: 1, access-control, calendar-access, calendar-schedule, calendar-auto-schedule, calendar-availability, inbox-availability, calendar-proxy, calendarserver-private-events, calendarserver-private-comments, calendarserver-sharing, addressbook, calendarserver-principal-property-search
ETag: "285DD2-1000-4C36FA5F"
Date: Fri, 09 Jul 2010 10:30:56 GMT
Content-Type: text/xml[\r][\n]
<"?xml version='1.0' encoding='UTF-8'?>
<multistatus xmlns='DAV:'>
<response>
<href>/calendars/users/admin/calendar/2b1785aa-0893-49b6-be52-ede2290bdc4a.ics</href>
<propstat>
<prop>
<calendar-data xmlns='urn:ietf:params:xml:ns:caldav'><![CDATA[BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//Ben Fortuna//iCal4j 1.0//EN
BEGIN:VTIMEZONE
TZID:Europe/Madrid
TZURL:http://tzurl.org/zoneinfo/Europe/Madrid
BEGIN:STANDARD
DTSTART:19961027T030000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
END:STANDARD
BEGIN:STANDARD
DTSTART:19010101T011444
RDATE:19010101T011444
TZNAME:WET
TZOFFSETFROM:-001444
TZOFFSETTO:+0000
END:STANDARD
BEGIN:STANDARD
DTSTART:19171007T010000
RDATE:19171007T010000
RDATE:19181007T010000
RDATE:19191007T010000
RDATE:19241005T010000
RDATE:19261003T010000
RDATE:19271002T010000
RDATE:19281007T010000
RDATE:19291006T010000
RDATE:19371003T010000
RDATE:19381002T010000
RDATE:19391008T010000
TZNAME:WET
TZOFFSETFROM:+0100
TZOFFSETTO:+0000
END:STANDARD
BEGIN:STANDARD
DTSTART:19460930T000000
RDATE:19460930T000000
RDATE:19490930T010000
RDATE:19741006T010000
RDATE:19751005T010000
RDATE:19760926T010000
RDATE:19770925T010000
RDATE:19781001T010000
RDATE:19790930T030000
RDATE:19800928T030000
RDATE:19810927T030000
RDATE:19820926T030000
RDATE:19830925T030000
RDATE:19840930T030000
RDATE:19850929T030000
RDATE:19860928T030000
RDATE:19870927T030000
RDATE:19880925T030000
RDATE:19890924T030000
RDATE:19900930T030000
RDATE:19910929T030000
RDATE:19920927T030000
RDATE:19930926T030000
RDATE:19940925T030000
RDATE:19950924T030000
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
END:STANDARD
BEGIN:STANDARD
DTSTART:19790101T000000
RDATE:19790101T000000
TZNAME:CET
TZOFFSETFROM:+0100
TZOFFSETTO:+0100
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19810329T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
END:DAYLIGHT
BEGIN:DAYLIGHT
DTSTART:19170506T000000
RDATE:19170506T000000
RDATE:19180416T000000
RDATE:19190406T000000
RDATE:19240417T000000
RDATE:19260418T000000
RDATE:19270410T000000
RDATE:19280415T000000
RDATE:19290421T000000
RDATE:19370523T000000
RDATE:19380323T000000
RDATE:19390416T000000
RDATE:19400317T000000
TZNAME:WEST
TZOFFSETFROM:+0000
TZOFFSETTO:+0100
END:DAYLIGHT
BEGIN:DAYLIGHT
DTSTART:19420503T000000
RDATE:19420503T000000
RDATE:19430418T000000
RDATE:19440416T000000
RDATE:19450415T000000
RDATE:19460414T000000
TZNAME:WEMT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
END:DAYLIGHT
BEGIN:DAYLIGHT
DTSTART:19420902T010000
RDATE:19420902T010000
RDATE:19431004T010000
RDATE:19441011T010000
RDATE:19450930T020000
TZNAME:WEST
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
END:DAYLIGHT
BEGIN:DAYLIGHT
DTSTART:19490430T230000
RDATE:19490430T230000
RDATE:19740413T230000
RDATE:19750419T230000
RDATE:19760327T230000
RDATE:19770402T230000
RDATE:19780402T230000
RDATE:19790401T020000
RDATE:19800406T020000
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
END:DAYLIGHT
X-LIC-LOCATION:Europe/Madrid
END:VTIMEZONE
BEGIN:VEVENT
UID:2b1785aa-0893-49b6-be52-ede2290bdc4a
DTSTART;VALUE=DATE:20100709
DURATION:PT1H
DTSTAMP:20100709T103055Z
SUMMARY:test
END:VEVENT
END:VCALENDAR
]]></calendar-data>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</response>
</multistatus>
DELETE
Finally a usual HTTP DELETE method is sent. With it the resource previously created is removed. As in the PUT method the three servers act more or less the same way and only google calendar trace is shown.
HttpClient client = ...
DeleteMethod delete = null;
try {
delete = new DeleteMethod(createdHref);
client.executeMethod(delete);
} finally {
if (delete != null) {
delete.releaseConnection();
}
}
A DELETE method returns
No Content (HTTP code 204) on success.
Google Calendar:
DELETE /calendar/dav/rickyepoderi@gmail.com/events/cf3bdc67-df96-46bf-a0a8-86cd72133097.ics HTTP/1.1
User-Agent: Jakarta Commons-HttpClient/3.1
Content-Length: 0Authorization: Basic XXXXXXXXXX
Host: www.google.com
HTTP/1.1 204 No Content
DAV: 1, calendar-access, calendar-schedule, calendar-proxy
Date: Fri, 09 Jul 2010 10:49:33 GMT
Server: GSE
This little
Main.java is the client class I have used to obtain the communication traces presented in this entries. And, as a conclusion, jackrabbit-webdav and iCal4j have done a good job. Although caldav4j is a more high level CalDAV API and I am sure it can produce faster results, the chosen libraries let us see some very interesting communication and implementation details. I hope the goal of this two-part series has been achieved, getting some little knowledge about CalDAV access programming.
Have a nice weekend!
Comments