Thursday, December 30. 2010
CalDAV and the Whitepages Portlet
In the past weeks the blog received some comments about the old caldav introduction series. If you remember this series consists of two entries, the first one introduces caldav and presents some http/webdav methods and the second post shows some real caldav requests. At that time I decided to not attach the complete project but linking every java file when it goes into stage. I thought this way would result in a better understanding but, obviously, I was wrong. For this reason I finally changed my mind and I am going to present the whole project and a real PoC example.
For some months I have been thinking in improving my little whitepages portlet (shown in both VoIP entries, Skype internet solution and Pidgin/OpenFire custom intranet project). It would be great if the whitepages, besides VoIP presence and interaction capabilities, could show the events or free/busy information for a specified company mate. This way any colleague can see what events has any other mate arranged for today and act accordingly.
It is quite clear that CalDAV is perfect for accessing the company calendar solution. In this case I have not installed any Calendar software (like I did with open sourced Calendar Server in the CalDAV entries) and the whitepages portlet will work against internet google and Yahoo! calendars. Finally I configured Yahoo! as the default calendar cos google does not support free-busy-query in its CalDAV implementation.
Before implementing the whitepages extension I worked with the old CalDAV project. Some jackrabbit libraries have been upgraded to new 2.2.0 version and another report has been implemented: free-busy-query. This report queries for the free/busy information of a user in a specified time. Its response only shows when the user has some events arranged, is busy, and when is free, but not specifying any detail of any event. If you check the libraries, jackrabbit-webdav depends on the old Commons HttpClient 3.1 which was superseded long time ago by a new HttpClient in the HttpComponents project (actual GA version 4.0.3). But its API has been deeply changed and jackrabbit needs some time to upgrade (see bug JCR-2406 for more details). This is an important issue to consider before choosing jackrabbit-webdav and I did not comment it before. As I promised here it is the complete CalDAV NetBeans project (libraries are missing because of space).
As I commented in previous VoIP entries the whitepages is a JSF 2.0 portlet application which uses OpenLDAP as the user repository. Every user entry has several attributes, and some of them are simply displayed (name, telephone,...) and others are treated specially. For example the jpegPhoto attribute is fetched and transformed into an image tag, the mail one is converted into a common email href link and skype/pidgin attribute is interpreted to show the presence and interaction links (start chat, add to my contacts,...).
For calendar integration a new attribute is used. This attribute is going to have the complete URI for the CalDAV Event collection of the user, in case of Yahoo! the attribute will be https://caldav.calendar.yahoo.com/dav/<yahoo-email>/Calendar/<calendar-name>/. But like the other special attributes, the calendar one will not be just presented, the URI will be used to access and query for the today user events. These events will be retrieved and presented to the end user. In order to fetch the events of a user calendar it must be shared with whitepages internal user or be totally public (i.e. any other google or Yahoo! user can see our events). In Yahoo! the calendar accessibility is set in Options → Share Calendar → Other Ways to Share → Public Link and in google in Settings → Calendar settings → Calendars → Click the calendar you want to share → Share this Calendar → Make this calendar public. In both sites there is an option to only share free/busy information and not make the calendar absolutely public (details are hidden and only availability is shown) but, also in both, this option does not work. Google does not support free-busy-query (CalDAV cannot access free/busy information) and in Yahoo! this option is disabled (with a beautiful coming soon message). In a custom calendar solution the whitepages could also be configured to use a superuser with special grants to access any other user calendar. But, as I commented, finally I set Yahoo! URI in the LDAP attribute of my entry and therefore my calendar must be public. The portlet tries first to obtain all the events arranged for today using calendar-query report (this needs all events to be public) and, if this first option does not work, it tries a second free-busy-query report to only show when the person is occupied (only free/busy access is needed).
Here it is a new video. My hypothetic boss wants to see the whitepages improvements before the department meeting at the afternoon and he searches for me in the intranet portal. After checking I have one hour free in the morning and I am online in Skype he decides to chat with me and arrange a meeting. I accept the offer and update my Yahoo! calendar. The update is immediately shown in the portlet.
I hope CalDAV entries were clearer now. This entry has attached the complete caldav project (see above for the link) and shows whitepages as a real example (very simple but a real one). I like the way my little whitepages application is being improved.
Happy new year!
Saturday, July 24. 2010
Introducing CalDAV (Part II)
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);
DavPropertyprop = (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);
DavPropertyprop = (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!
Sunday, July 11. 2010
Introducing CalDAV (Part I)
As you see around CalDAV there are some concepts:
- WebDAV (Web-based Distributed Authoring and Versioning) is an extension to HTTP that allows users to edit and manage files collaboratively on remote web servers. RFC-4918 defines the WebDAV extensions.
In a previous entry a simple CGI was used to solve a SUDOKU and there I explained the GET and POST HTTP methods. HyperText Transfer Protocol (HTTP) has another methods like PUT (uploads a specified resource) or DELETE (deletes a web resource). WebDAV extends HTTP with more of them like PROPFIND (retrieves properties of resources), MKCOL (creates a new WebDAV collection), COPY and MOVE (copies and moves one resource from one URI to another) or LOCK and UNLOCK (creates and removes a shared or exclusive lock over a resource). Several RFCs have improved WebDAV with even more extensions like RFC-3253, versioning extensions to the HTTP/1.1 protocol (CHECKOUT, CHECKIN,...) and many others.
WebDAV RFC chooses XML as the language to use over HTTP communication and there are DTDs (Document Type Definition) to define the XML data exchanged in every method. Subsequent extensions (CalDAV too) do exactly the same in their respective RFCs.
Commonly speaking WebDAV protocol lets people manage collections of files stored in the internet. And many internet providers use it to provide virtual disk services to their customers.
- CalDAV (Calendaring Extensions to WebDAV) is just another extension over WebDAV (RFC-4791) to specify a standard way of accessing, managing and sharing calendaring data based on the iCalendar format. In summary it is a protocol to manage typical calendar resources (calendars, events, to-dos,...) as internet WebDAV collections.
- iCalendar (Internet Calendaring and Scheduling Core Object Specification) is a data format (RFC-5545 replaced RFC-2445 and now defines the standard) for representing and exchanging calendaring and scheduling information such as events, to-dos, journal entries, and free/busy information, independent of any particular calendar service or protocol. So, while CalDAV is the protocol to access and manage calendar resources, iCalendar is the format of the data stored in and retrieved from the server. Other way of saying the same, a calendar server manages collections of iCalendar formatted resources accessed by users via CalDAV protocol.
After it is clear what CalDAV means the next question to answer would be what is the current status of the development libraries about this protocol (in java of course). The little investigation I have done (very scattered I admit) gives us some links:
- CalDAV. The only library (I found) written in Java which directly implements CalDAV protocol is caldav4j. caldav4j is a project hosted by google which extends Apache Slide WebDAV protocol library and uses iCal4j for iCalendar data processing. The main problem with caldav4j is that Apache abandoned Slide in 2007 and therefore it is based on a deprecated library (now Apache advises to consider Jackrabbit project instead).
- WebDAV. Apache Jackrabbit is a fully conforming implementation of the Content Repository for Java Technology API or JCR. Not going deep into JCR this API grew out of the needs of content management systems, which require storage of documents and other binary objects with associated metadata. Any JCR needs WebDAV to provide access to the repository, so Jackrabbit gives a WebDAV protocol library which can be downloaded separately from the rest of the project here. But jackrabbit-webdav is not very well documented and any extension needs some study of the library source code (welcome to open source!).
- iCalendar. For iCalendar file format there is a well old known project called iCal4j. iCal4j is a Java API that provides support for the iCalendar specification including a Parser, Object Model and Generator for iCalendar data streams.
Although caldav4j is a very good option (and I am sure this project will slowly move from Slide to Jackrabbit library) I preferred to use jackrabbit-webdav directly. On one hand I wanted to understand CalDAV protocol (and using a high level API that hides all below communication was a bad idea) and, on the other, it is always better to develop with the correct library (and it is the obvious future option even for caldav4j). Finally I decided to code a little Java test class with the following features:
- The class will use iCal4j and jackrabbit-webdav libraries. Take into account that WebDAV part also uses Apache's HttpClient library.
- The resulting Java program will be tested against three different CalDAV servers:
- Google calendar. Google calendar gives CalDAV access for long time ago and the online help explains how to configure some CalDAV clients (Apple iCal and Mozilla Lightning/Sunbird). In my case my CalDAV URL is the following:
https://www.google.com/calendar/dav/rickyepoderi@gmail.com/events/
- Yahoo! Calendar (which is based on Zimbra). Same as google Yahoo! help provides some information about how to configure Mozilla client. Using that info my Yahoo! calendar is in the following CalDAV URL:
https://caldav.calendar.yahoo.com/dav/rickyepoderi@yahoo.es/Calendar/Ricardo_Martin/
- Calendar Server. The open source porting for Apple Darwin (iCal) Calendar Server. I set Calendar Server up following its quickstart instructions and, as in the other two servers, there is information about how to configure some CalDAV clients. In the default installation there is an admin user with the following CalDAV URL:
http://localhost:8008/calendars/users/admin/calendar/
- Google calendar. Google calendar gives CalDAV access for long time ago and the online help explains how to configure some CalDAV clients (Apple iCal and Mozilla Lightning/Sunbird). In my case my CalDAV URL is the following:
- The Java program will perform the following steps:
- Execute an OPTIONS method. This answers if CalDAV is supported.
- Send a PROPFIND to the root calendar URI of a user. This returns some standard output with calendar information.
- Create a new VEVENT for today using PUT.
- Recover the VEVENT using calendar-multiget REPORT.
- Query for the event using calendar-query REPORT.
- Delete the event.
- Execute an OPTIONS method. This answers if CalDAV is supported.
In this first entry only the first two methods (OPTIONS and PROPFIND) are going to be presented. The rest of them (the ones that really need extensions) will appear in the next post.
OPTIONS
The idea is quite simple, performing a direct HTTP OPTIONS method to the CalDAV URI via Jackrabbit. By default OPTIONS method returns the list of methods supported by the specified URI (in the Allow header). But any CalDAV server must include calendar-access as a field in the DAV response header from an OPTIONS request on any resource that supports any calendar properties, reports, or methods. A value of calendar-access in the DAV header indicates that the server supports all MUST level requirements and REQUIRED features specified by the CalDAV RFC.
Here it is the Java code:
HttpClient client = ...
OptionsMethod options = null;
try {
options = new OptionsMethod(uri);
client.executeMethod(options);
System.err.println(options.getStatusLine());
for (int i = 0; i < options.getResponseHeaders().length; i++) {
System.err.println(options.getResponseHeaders()[i].getName() + ": "
+ options.getResponseHeaders()[i].getValue());
}
} finally {
if (options != null) {
options.releaseConnection();
}
}
If you check the responses for every server some providers support more DAV extensions than others. In this entry I am just checking calendar-access but, of course, there are extensions over CalDAV to support more sophisticated features.
Google Calendar:
OPTIONS /calendar/dav/lrickyepoderi@gmail.com/events/ HTTP/1.1
User-Agent: Jakarta Commons-HttpClient/3.1
Authorization: Basic XXXXXXXXX
Host: www.google.com
HTTP/1.1 200 OK
DAV: 1, calendar-access, calendar-schedule, calendar-proxy
Allow: OPTIONS, PROPFIND, HEAD, GET, REPORT, PROPPATCH, PUT, DELETE, POST
Content-Type: application/xml; charset=UTF-8
Date: Tue, 06 Jul 2010 14:52:17 GMT
Expires: Tue, 06 Jul 2010 14:52:17 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
Yahoo! Calendar:
OPTIONS /dav/rickyepoderi@yahoo.es/Calendar/Ricardo_Martin/ HTTP/1.1
User-Agent: Jakarta Commons-HttpClient/3.1
Authorization: Basic XXXXXXXXX
Host: caldav.calendar.yahoo.com
HTTP/1.1 200 OK
Date: Tue, 06 Jul 2010 15:09:27 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"
Allow: PROPPATCH, COPY, DELETE, POST, MKCALENDAR, GET, REPORT, PROPFIND, PUT
Allow: MOVE, UNLOCK, OPTIONS, ACL, HEAD, LOCK, MKCOL
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
DAV: version-control, addressbook, extended-mkcol, calendar-proxy
DAV: calendarserver-principal-property-search
X-Cache: MISS from store138.c104.cal.gq1.yahoo.com
Content-Type: text/plain; charset=utf-8
Age: 1
Transfer-Encoding: chunked
Connection: keep-alive
Server: YTS/1.17.23
Calendar Server:
OPTIONS /calendars/users/admin/calendar/ HTTP/1.1
User-Agent: Jakarta Commons-HttpClient/3.1
Authorization: Digest username="admin", realm="Test Realm", nonce="19089203782138543154880100606", uri="/calendars/users/admin/calendar/", response="26c3007948457e549d7564bdf558558c", algorithm="md5"
Host: localhost:8008
HTTP/1.1 200 OK
Content-Length: 0
Server: CalendarServer/trunk(r5842M) Twisted/10.0.0 TwistedWeb/9.0.0+r5842
Last-Modified: Sun, 04 Jul 2010 16:45:37 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-4C30BAB1"
Allow: ACL, COPY, DELETE, GET, HEAD, LOCK, MKCALENDAR, MKCOL, MOVE, OPTIONS, POST, PROPFIND, PROPPATCH, PUT, REPORT, TRACE, UNLOCK
Date: Tue, 06 Jul 2010 15:19:57 GMT
PROPFIND
The PROPFIND WebDAV method retrieves properties for a specified resource, in case of a CalDAV collection URI it can return an optional property calendar-description and must return calendar and collection as resourcetype property.
The code for a PROPFIND requesting all properties with DEPTH 0 (only the collection URI) is below. The answer must be a multi-status response (a WebDAV defined XML response with HTTP code 207) with all the properties for every resource found.
HttpClient client = ...
PropFindMethod propFind = null;
try {
propFind = new PropFindMethod(uri,
DavConstants.PROPFIND_ALL_PROP, DavConstants.DEPTH_0);
client.executeMethod(propFind);
MultiStatus multiStatus = propFind.getResponseBodyAsMultiStatus();
} finally {
if (propFind != null) {
propFind.releaseConnection();
}
}
Google Calendar:
PROPFIND /calendar/dav/rickyepoderi@gmail.com/events/ HTTP/1.1
Depth: 0
User-Agent: Jakarta Commons-HttpClient/3.1
Content-Length: 90
Content-Type: text/xml; charset=UTF-8
Authorization: Basic XXXXXXXXX
Host: www.google.com
<?xml version="1.0" encoding="UTF-8"?>
<D:propfind xmlns:D="DAV:">
<D:allprop/>
</D:propfind>
HTTP/1.1 207 Multi-Status
DAV: 1, calendar-access, calendar-schedule, calendar-proxy
Content-Type: application/xml; charset=UTF-8
Date: Tue, 06 Jul 2010 16:04:32 GMT
Expires: Tue, 06 Jul 2010 16:04:32 GMT
<?xml version='1.0' encoding='UTF-8'?>
<D:multistatus xmlns:D="DAV:">
<D:response>
<D:href>/calendar/dav/rickyepoderi@gmail.com/events/</D:href>
<D:propstat>
<D:status>HTTP/1.1 200 OK</D:status>
<D:prop>
<C:calendar-description xmlns:C="urn:ietf:params:xml:ns:caldav">rickyepoderi@gmail.com</C:calendar-description>
<A:calendar-color xmlns:A="http://apple.com/ns/ical/">#2952A3</A:calendar-color>
<D:getcontenttype>text/calendar; component=vevent</D:getcontenttype>
<D:displayname>Ricardo Martin</D:displayname>
<D:resourcetype>
<D:collection />
<C:calendar xmlns:C="urn:ietf:params:xml:ns:caldav" />
</D:resourcetype>
<CS:getctag xmlns:CS="http://calendarserver.org/ns/">63414112150</CS:getctag>
</D:prop>
</D:propstat>
</D:response>
</D:multistatus>
Yahoo! Calendar:
PROPFIND /dav/rickyepoderi@yahoo.es/Calendar/Ricardo_Martin/ HTTP/1.1
Depth: 0
User-Agent: Jakarta Commons-HttpClient/3.1
Content-Length: 90
Content-Type: text/xml; charset=UTF-8
Authorization: Basic XXXXXXXXX
Host: caldav.calendar.yahoo.com
<?xml version="1.0" encoding="UTF-8"?>
<D:propfind xmlns:D="DAV:">
<D:allprop/>
</D:propfind>
HTTP/1.1 207 Multi Status
Date: Tue, 06 Jul 2010 15:59:20 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/</D:href>
<D:propstat>
<D:status>HTTP/1.1 200 OK</D:status>
<D:prop>
<D:getetag>"4-1"</D:getetag>
<D:getcontentlength>0</D:getcontentlength>
<D:creationdate>2010-07-04T03:35:33-07:00</D:creationdate>
<D:resourcetype>
<D:collection/>
<C:calendar xmlns:C="urn:ietf:params:xml:ns:caldav"/>
</D:resourcetype>
<D:getlastmodified>Sun, 04 Jul 2010 03:40:42 -0700 (PDT)</D:getlastmodified>
<D:displayname>Ricardo_Martin</D:displayname>
<CS:getctag xmlns:CS="http://calendarserver.org/ns/">4-201</CS:getctag>
</D:prop>
</D:propstat>
</D:response>
</D:multistatus>
Calendar Server:
PROPFIND /calendars/users/admin/calendar/ HTTP/1.1
Depth: 0
User-Agent: Jakarta Commons-HttpClient/3.1
Content-Length: 90
Content-Type: text/xml; charset=UTF-8
Authorization: Digest username="admin", realm="Test Realm", nonce="2563477371875350179601883528", uri="/calendars/users/admin/calendar/", response="5e1623b0f086e2be5dbb3c349f79ffb2", algorithm="md5"
Host: localhost:8008
<?xml version="1.0" encoding="UTF-8"?>
<D:propfind xmlns:D="DAV:">
<D:allprop/>
</D:propfind>
HTTP/1.1 207 Multi-Status
Content-Length: 1794
Accept-Ranges: bytes
Server: CalendarServer/trunk(r5842M) Twisted/10.0.0 TwistedWeb/9.0.0+r5842
Last-Modified: Tue, 06 Jul 2010 15:20: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: "285DD2-1000-4C3349A8"
Date: Tue, 06 Jul 2010 15:49:27 GMT
Content-Type: text/xml
<?xml version='1.0' encoding='UTF-8'?>
<multistatus xmlns='DAV:'>
<response>
<href>/calendars/users/admin/calendar/</href>
<propstat>
<prop>
<getetag>"285DD2-1000-4C3349A8"</getetag>
<current-user-principal>
<href>/principals/_uids_/admin/</href>
</current-user-principal>
<displayname>calendar</displayname>
<getctag xmlns='http://calendarserver.org/ns/'>76184803-9107-4334-a5d7-25c6490d7997#6</getctag>
<getcontenttype>httpd/unix-directory</getcontenttype>
<supportedlock>
<lockentry>
<lockscope>
<exclusive/>
</lockscope>
<locktype>
<write/>
</locktype>
</lockentry>
<lockentry>
<lockscope>
<shared/>
</lockscope>
<locktype>
<write/>
</locktype>
</lockentry>
</supportedlock>
<resourcetype>
<collection/>
<calendar xmlns='urn:ietf:params:xml:ns:caldav'/>
</resourcetype>
<getcontentlength/>
<schedule-calendar-transp xmlns='urn:ietf:params:xml:ns:caldav'>
<opaque/>
</schedule-calendar-transp>
<getlastmodified>Tue, 06 Jul 2010 17:20:08 GMT</getlastmodified>
<creationdate>2010-07-06T17:20:08Z</creationdate>
<resource-class xmlns='http://twistedmatrix.com/xml_namespace/dav/'>CalDAVFile</resource-class>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
<propstat>
<prop>
<invite xmlns='http://calendarserver.org/ns/'/>
</prop>
<status>HTTP/1.1 404 Not Found
</propstat>
</response>
</multistatus>
In this entry I just introduced CalDAV access and no real CalDAV method has been executed. The previous two methods (OPTIONS and PROPFIND) are defined in HTTP and WebDAV respectively and, although they return some CalDAV exclusive properties and headers, they are not real CalDAV methods. In the next post I will continue with the previously commented steps and, in some of them, jackrabbit-webdav will need to be extended.
See you soon with the sedond part!
Comments