ERGroupware
Pascal Robert
Druide informatique
• Let you generate iCalendar files, à la ERCalendar
• Let you connect to groupware solutions
• CalDAV
• MS Exchange 2007/2010
• Zimbra
• Many Bothans have died to provide this framework
What is ERGroupware?
iCalendar generation
iCalendar
• iCalendar is a standard (RFC 2445, written in 1998).
• A iCalendar object can have multiple components (VEVENT,
VTODO, etc.)
• Text file, with Base64 encoding for binary
• Key value
• You can add your own keys
iCalendar example
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Apple Inc.//Mac OS X 10.8.4//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
TRANSP:OPAQUE
DTEND:20130623T101500
UID:DB0E22EA-0828-40A3-BCFD-5A8CC9866CE4
DTSTAMP:20130514T135551Z
LOCATION:Verdun/Lachine/Lasalle
X-MOZ-GENERATION:1
URL;VALUE=URI:http://www.wocommunity.org/wowodc13/cayenne.html
SEQUENCE:10
SUMMARY:Cayenne Training Day
LAST-MODIFIED:20130509T165550Z
DTSTART:20130623T091500
CREATED:20130505T001152Z
END:VEVENT
END:VCALENDAR
UID
• Each component must have a UID
• UID:DB0E22EA-0828-40A3-BCFD-5A8CC9866CE4
• And it must be unique!
Attendees
• Attendees are invitees.
• Can be a individual, group, resource or room. (CUType)
• Also have status: needs action, accepted, etc. (Partstat)
• If you have attendees, you also need an organizer!
iCalendar in ERGroupware
• Similar to ERCalendar, but more modern and better validation.
• Using iCal4j, a solid iCalendar library for Java.
• Component to generate the file.
• Will generate a text/calendar response
iCalendar file generation
ERGWCalendar aCalendar = new ERGWCalendar();
ERGWEvent event = new ERGWEvent(aCalendar);
java.util.Calendar startTime = GregorianCalendar.getInstance();
event.setStartTime(new NSTimestamp(startTime.getTimeInMillis()));
java.util.Calendar endTime = GregorianCalendar.getInstance();
endTime.add(java.util.Calendar.HOUR, 2);
event.setEndTime(new NSTimestamp(endTime.getTimeInMillis()));
event.setClassification(ERGWClassification.PUBLIC);
event.setFreeBusyStatus(ERGWFreeBusyStatus.BUSY_TENTATIVE);
event.setLocation("Montreal");
event.setCategories(new NSArray<String>(new String[] { "Category 1", "Category 2" }));
event.setPriority(ERGWPriority.HIGH);
event.setSummary("WOWODC 2013");
event.setTransparency(ERGWTransparency.TRANSPARENT);
Calendar calendarData = ERGWCalendar.transformToICalObject(aCalendar);
ERGWPublishCalendarPage nextPage = (ERGWPublishCalendarPage)pageWithName(ERGWPublishCalendarPage.class);
nextPage.setCalendar(calendarData);
return nextPage;
CalDAV/CardDAV
• Both based on WebDAV
• Will store iCalendar objects (CalDAV) or vCard objects
(CardDAV)
• Many many server implementations
• But some of them are really basic (Google Calendar...)
• iCal Server is the best implementation
• Many clients too
CalDAV
• Open standard, defined in RFC 4791
• Servers must implements this RFC
• Many extensions
• Scheduling
• Delegations
• Sharing
• etc.
CalDAV
• Calendars are DAV collections.
• DAV collections have properties (ACL, owner, etc.).
• Can create as many collections as you want.
• Other DAV collections exists too.
Features/extensions
• CalDAV servers must send the supported features set in the
response.
• Can do a OPTIONS request to see them.
• Check the DAV header in the response.
OPTIONS request
$ curl --digest --user probert -X OPTIONS -v http://localhost:8008
< 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, calendarserver-sharing-no-scheduling, calendar-
query-extended, calendar-default-alarms, addressbook, extended-mkcol,
calendarserver-principal-property-search, calendarserver-principal-
search
CalDAV and HTTP
• Almost REST-like
• Use HTTP verbs
• GET: fetch a object
• PUT: create OR update a object
• DELETE: delete a object
• MKCALENDAR: create a calendar collection
• Use headers, HTTP authentication (Basic, Digest, Kerberos...)
iCalendar objects
• You store iCalendar objects in calendar collections.
• But you can only store ONE component (VTODO,VEVENT,
etc.) per object.
PUT
• CalDAV don't use POST to create objects.
• Must use PUT
• If URL and UID already exists, will update the object.
• If URL and UID don't exists, will create the object.
• If URL don't exist but UID exist, will conflict.
• Creating a object will return a 201 status code.
• If you copy an object, don't forget to change the UID.
Example of creating event
PUT /calendars/__uids__/AD04C60B-3704-447B-8997-24F5ACF589C7/calendar/C440-473C-8FAD-D3E606159F40.ics HTTP/1.1
If-None-Match: 1f9e6e30-dfdb-4e5c-baf6-6dd107126a68
Content-Type: text/calendar
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Apple Inc.//Mac OS X 10.8.4//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
ATTENDEE;CN="probert@macti.ca";CUTYPE=INDIVIDUAL:mailto:probert@macti.ca
ATTENDEE;CN="Pascal Robert";CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:urn:uuid:AD04C60B-3704-447B-8997-24F5ACF589C7
DTEND;VALUE=DATE:20130616
TRANSP:TRANSPARENT
ORGANIZER;CN="Pascal Robert":urn:uuid:AD04C60B-3704-447B-8997-24F5ACF589C7
UID:B54100D2-C440-473C-8FAD-D3E606159F40
DTSTAMP:20130615T123551Z
LOCATION:Hilton Montréal Bonaventure
SEQUENCE:9
SUMMARY:Test for presentation
DTSTART;VALUE=DATE:20130615
CREATED:20130615T123241Z
END:VEVENT
END:VCALENDAR
CalDAV support in ERGroupware
• Based on iCal4j-connector, which I contributed to.
• Support for most CalDAV operations.
• Get calendars (collections)
• Create calendars
• Fetch/delete/update calendar objects
• Delete collections
• Fetch events by time-range
Connecting
import net.fortuna.ical4j.connector.ObjectStoreException;
import net.fortuna.ical4j.connector.dav.PathResolver;
import er.groupware.caldav.CalDAVStore;
import java.net.URL;
CalDAVStore store = null;
try {
store = new CalDAVStore("probert", "somepassword", new URL("http://127.0.0.1"),
PathResolver.ICAL_SERVER);
}
catch (MalformedURLException e) {
e.printStackTrace();
}
catch (ObjectStoreException e) {
e.printStackTrace();
}
Fetching calendar collections and objects
NSArray<CalDAVCollection> collections = store.getCollections();
for (CalDAVCollection collection: collections) {
NSLog.out.appendln(collection.displayName());
// Fetch all events (e.g, VEVENT objects)
NSArray<ERGWCalendar> events = collection.events();
// Fetch all tasks (e.g, VTOTO objects)
NSArray<ERGWCalendar> tasks = collection.tasks();
}
Manipuling calendar objects
// Create a new event and send it to the server
ERGWCalendar aCalendar = new ERGWCalendar();
ERGWEvent event = new ERGWEvent(aCalendar);
java.util.Calendar startTime = GregorianCalendar.getInstance();
event.setStartTime(new NSTimestamp(startTime.getTimeInMillis()));
java.util.Calendar endTime = GregorianCalendar.getInstance();
endTime.add(java.util.Calendar.HOUR, 2);
event.setEndTime(new NSTimestamp(endTime.getTimeInMillis()));
event.setSummary("WOWODC 2013");
collection.addCalendarObject(aCalendar);
// Update the event and send it to the server
aCalendar.events().objectAtIndex(0).setSummary("WOWODC 2014");
collection.updateCalendarObject(aCalendar);
// Delete the event
collection.removeCalendarObject(aCalendar.events().objectAtIndex(0));
Queries
• REPORT request is the same idea as a SQL SELECT query.
• Let's you find calendar objects with a qualifier.
• "Find all todos that are completed"
• "Find events from June 22 to June 24"
• "Find all rooms"
• Won't work for custom properties
Find events by time range
REPORT /bernard/work/ HTTP/1.1
Depth: 1
Content-Type: application/xml; charset="utf-8"
<?xml version="1.0" encoding="utf-8" ?>
<C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:prop>
<C:calendar-data>
<C:limit-recurrence-set start="20060103T000000Z" end="20060105T000000Z"/>
</C:calendar-data>
</D:prop>
<C:filter>
<C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT">
<C:time-range start="20060103T000000Z" end="20060105T000000Z"/>
</C:comp-filter>
</C:comp-filter>
</C:filter>
</C:calendar-query>
Find participants that didn't not accept invitations
REPORT /bernard/work/ HTTP/1.1
Depth: 1
Content-Type: application/xml; charset="utf-8"
<?xml version="1.0" encoding="utf-8" ?>
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:prop xmlns:D="DAV:">
<D:getetag/>
<C:calendar-data/>
</D:prop>
<C:filter>
<C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT">
<C:prop-filter name="ATTENDEE">
<C:param-filter name="PARTSTAT">
<C:text-match collation="i;ascii-casemap">NEEDS-ACTION</C:text-match>
</C:param-filter>
</C:prop-filter>
</C:comp-filter>
</C:comp-filter>
</C:filter>
</C:calendar-query>
Queries support in ERGroupware
• Query by time range
• CalDavCollection.eventsForTimePeriod(java.util.Date startTime,
java.util.Date endTime)
• Find all individuals
• store.getIndividuals(String name);
• Find rooms or resources
• store.getAllRooms()
• store.getAllResources()
DEMO
MS Exchange
• Exchange Web Service (EWS)
• Based on SOAP
• Same protocol that Outlook 2011 uses
• Most EWS attributes are similar to iCalendar and DAV
collections attributes
Example of request
---[HTTP request - https://webmail.sherweb2010.com/EWS/Exchange.asmx]---
Content-type: text/xml;charset="utf-8"
Soapaction: "http://schemas.microsoft.com/exchange/services/2006/messages/SyncFolderHierarchy"
Accept: text/xml, multipart/related, text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
<?xml version="1.0" ?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Header>
<MailboxCulture xmlns="..." xmlns:ns2="...">en-US</MailboxCulture>
<RequestServerVersion xmlns="..." xmlns:ns2="..." Version="Exchange2007_SP1"/>
</S:Header>
<S:Body>
<ns2:SyncFolderHierarchy xmlns="..." xmlns:ns2="...">
<ns2:FolderShape>
<BaseShape>IdOnly</BaseShape>
<AdditionalProperties>
<FieldURI FieldURI="folder:DisplayName"/>
<FieldURI FieldURI="folder:FolderClass"/>
<ExtendedFieldURI PropertyType="Boolean" PropertyTag="0x10F4"/>
</AdditionalProperties>
</ns2:FolderShape>
</ns2:SyncFolderHierarchy>
</S:Body>
</S:Envelope>
Example of response
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<h:ServerVersionInfo ... Version="Exchange2010_SP2" />
</s:Header>
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<m:SyncFolderHierarchyResponse xmlns:m="..." xmlns:t="..."><m:ResponseMessages>
<m:SyncFolderHierarchyResponseMessage ResponseClass="Success">
<m:ResponseCode>NoError</m:ResponseCode>
<m:SyncState>H4sIAAAAAAAEAO29B2AcSZYlJi9tynt...</m:SyncState>
<m:Changes>
<t:Create>
<t:Folder>
<t:FolderId Id="AAMkAGEw...=" ChangeKey="AQAAABYAAACT3PaCL/2ASLnqcq9Sv0/DAAAAp5Bv"/>
<t:FolderClass>IPF.Note</t:FolderClass>
<t:DisplayName>INBOX</t:DisplayName>
<t:ExtendedProperty>
<t:ExtendedFieldURI PropertyTag="0x10f4" PropertyType="Boolean"/>
<t:Value>false</t:Value>
</t:ExtendedProperty>
</t:Folder>
</t:Create>
</m:Changes>
...
EWS support in ERGroupware
• Generated with JAXB.
• Very basic support outside the generated classes:
• Open connection
• Create folders
• Create task
• Create contact
• Create event
Opening a connection and fetch folders
URL urlToWSDL = ERXApplication.application().resourceManager().pathURLForResourceNamed("Services.wsdl",
"ERGroupware", null);
ExchangeStore store = new ExchangeStore(urlToWSDL, "https://myserver/EWS/Exchange.asmx", "aUser",
"aPassword", "AD.DOMAIN");
store.setServerVersionForRequest(ExchangeVersionType.EXCHANGE_2010_SP_1);
store.setTimeZone(TimeZone.getDefault());
NSArray<ExchangeBaseFolder> folders = store.folders();
for (ExchangeBaseFolder folder: folders) {
NSLog.out.appendln(folder.displayName());
}
Adding an event
for (ExchangeBaseFolder folder: store.folders()) {
if (folder.displayName().equals("Calendar")) {
ERGWCalendar calendar = new ERGWCalendar();
calendar.setCalendarName("Test");
calendar.setTimeZone(TimeZone.getDefault());
Calendar later = GregorianCalendar.getInstance();
later.add(Calendar.HOUR_OF_DAY, 1);
event.setEndTime(new NSTimestamp(later.getTime()));
UidGenerator uidGen = new UidGenerator("allo");
event.setUid(uidGen.generateUid().getValue());
ERGWEvent event = new ERGWEvent(calendar);
event.setIsFullDay(false);
event.setStartTime(new NSTimestamp());
event.setSummary("WOWODC 2013");
event.setLastModifiedDate(new NSTimestamp());
calendar.addEvent(event);
exchangeStore.createCalendarEvent(calendar, (ExchangeCalendarFolder) folder);
}
}
Adding an event
for (ExchangeBaseFolder folder: store.folders()) {
if (folder.displayName().equals("Tasks")) {
ERGWCalendar calendar = new ERGWCalendar();
calendar.setCalendarName("Test");
calendar.setTimeZone(TimeZone.getDefault());
Calendar later = GregorianCalendar.getInstance();
later.add(Calendar.HOUR_OF_DAY, 1);
task.setDueDate(new NSTimestamp(later.getTime()));
UidGenerator uidGen = new UidGenerator("allo");
event.setUid(uidGen.generateUid().getValue());
ERGWTask task = new ERGWTask(calendar);
task.setSummary("Finish that presentation");
task.setLastModifiedDate(new NSTimestamp());
calendar.addEvent(event);
exchangeStore.createTask(calendar, (ExchangeCalendarFolder) folder);
}
}
Adding a contact
for (ExchangeBaseFolder folder: store.folders()) {
if (folder.displayName().equals("Contacts")) {
ERGWContact contact = new ERGWContact();
contact.setGivenName("Pascal");
contact.setFamilyName("Robert");
ERGWContactEmail personalEmail = new ERGWContactEmail();
personalEmail.setEmail("probert@macti.ca");
personalEmail.isPrefered(true);
personalEmail.setTypes(new NSArray<ERGWContactEmailType>(new ERGWContactEmailType[]
{ ERGWContactEmailType.HOME }));
contact.setEmails(new NSArray<ERGWContactEmail>(new ERGWContactEmail[] { personalEmail }));
exchangeStore.createContact(contact, (ExchangeCalendarFolder) folder);
}
}
Create folders
// Create a folder of a specific type
// ERGWFolderType values are: CALENDAR, CONTACTS, TASKS, SEARCH, PLAIN, etc.
store.createFolder("New calendar folder", ERGWFolderType.CALENDAR);
store.createFolder("New contacts folder", ERGWFolderType.CONTACTS);
// Shortcut to create a calendar folder
store.createCalendarFolder(String displayName)
// Shortcut to create a contacts folder
store.createContactsFolder(String displayName)
Zimbra
• SOAP and REST-based API.
• Have API for everything: management, data and Web client
(Zimlets).
• Very basic support in ERGroupware (need to migrate a lot of
code)
• Connect to store
• Add folder
Example of request
POST /service/soap/CreateAppointmentRequest HTTP/1.1
Content-Type: text/xml; charset=utf-8
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
	 <soap:Header>
	 	 <context xmlns="urn:zimbra"><authToken>0_b6e206e6c3eee19a7e89f717d5972d9d6b9db224</authToken>...</context>
	 </soap:Header>
	 <soap:Body>
	 	 <CreateAppointmentRequest xmlns="urn:zimbraMail">
	 	 	 <m l="10">
	 	 	 	 <inv>
	 	 	 	 	 <comp status="CONF" allDay="0" name="Titre de la rencontre" loc="Montr[0xc3][0xa9]al" class="PUB" transp="O" fb="B">
	 	 	 	 	 	 <s d="20130619T050858" tz="America/Montreal"/>
	 	 	 	 	 	 <e d="20130619T070858" tz="America/Montreal"/>
	 	 	 	 	 	 <or d="Pascal Robert" a="root@zimbra.macti.lan"/>
	 	 	 	 	 	 <at d="Pascal Robert" cutype="IND" a="probert@macti.ca"/>
	 	 	 	 	 	 <at d="Salle 1" cutype="ROO" a="Room1@zimbra.macti.lan"/>
	 	 	 	 	 	 <xprop name="X-RELATED-TO" value="281-280"/>
	 	 	 	 	 </comp>
	 	 	 	 </inv>
	 	 	 	 <su>Titre de la rencontre</su>
	 	 	 	 <mp ct="multipart/mixed"><mp ct="text/plain">
	 	 	 	 	 <content>Une plus long description</content>
	 	 	 	 </mp>
	 	 	 	 <mp ct="text/html">
	 	 	 	 	 <content>&lt;html>&lt;body> Une plus long description&lt;/body>&lt;/html></content>
	 	 	 	 </mp>
	 	 	 </m>
	 	 </CreateAppointmentRequest>
	 </soap:Body>
</soap:Envelope>
Example response
HTTP/1.1 200 OK
Content-Type: text/xml;charset=UTF-8
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
	 <soap:Header>
	 	 <context xmlns="urn:zimbra"><session id="11">11</session><change token="602"/>
	 	 	 <notify seq="2">
	 	 	 	 <created>
	 	 	 	 	 <appt id="283" uid="4ec78d56-0061-451d-9825-cb17b43b010b" d="1371632938000" rev="602" s="0" l="10">
	 	 	 	 	 	 <inv id="282" seq="0" compNum="0" type="appt">
	 	 	 	 	 	 	 <tz id="America/Montreal" stdoff="-300" dayname="EDT" dayoff="-240" stdname="EST">...</tz>
	 	 	 	 	 	 	 <comp uid="4ec78d56-0061-451d-9825-cb17b43b010b" d="1371632938000" status="CONF" ...>
	 	 	 	 	 	 	 	 <at d="Pascal Robert" cutype="IND" a="probert@macti.ca" url="probert@macti.ca"/>
	 	 	 	 	 	 	 	 <at d="Salle 1" cutype="ROO" a="Room1@zimbra.macti.lan" url="Room1@zimbra.macti.lan"/>
	 	 	 	 	 	 	 	 <xprop name="X-RELATED-TO" value="281-280"/>
	 	 	 	 	 	 	 	 <desc>Une plus long description</desc>
	 	 	 	 	 	 	 	 <descHtml>&lt;html>&lt;body> Une plus long description&lt;/body>&lt;/html></descHtml>
	 	 	 	 	 	 	 	 <or d="Pascal Robert" a="admin@linux.conatus.lan" url="root@zimbra.macti.lan"/>
	 	 	 	 	 	 	 	 <s u="1371632938000" d="20130619T050858" tz="America/Montreal"/>
	 	 	 	 	 	 	 	 <e d="20130619T070858" u="1371640138000" tz="America/Montreal"/>
	 	 	 	 	 	 	 </comp>
	 	 	 	 	 	 </inv>
	 	 	 	 	 </appt>
	 	 	 	 </created>
	 	 	 	 <modified><folder id="10" i4next="284" s="0" i4ms="602" n="3" uuid="6630b004-40cd-49b4-af4b-eca7bac45be6"/></modified>
	 	 	 </notify>
	 	 </context>
	 </soap:Header>
	 <soap:Body>
	 	 <CreateAppointmentResponse rev="602" ms="602" invId="283-282" apptId="283" calItemId="283" xmlns="urn:zimbraMail"/>
	 </soap:Body>
</soap:Envelope>
TODO
• Move away from ical4j-connector
• Complete CalDAV and CardDAV support
• Complete calendar, contacts and email support for Zimbra
• Management API for Kerio, CGP and Zimbra
Resources
• https://github.com/pascalrobert/ERGroupware/
• http://www.macti.ca/wiki/
• http://www.calconnect.org
• http://wiki.modularity.net.au/ical4j/
• http://msdn.microsoft.com/en-us/library/exchange/bb204119(v=exchg.
140).aspx
Q&A

ERGroupware

  • 1.
  • 2.
    • Let yougenerate iCalendar files, à la ERCalendar • Let you connect to groupware solutions • CalDAV • MS Exchange 2007/2010 • Zimbra • Many Bothans have died to provide this framework What is ERGroupware?
  • 3.
  • 4.
    iCalendar • iCalendar isa standard (RFC 2445, written in 1998). • A iCalendar object can have multiple components (VEVENT, VTODO, etc.) • Text file, with Base64 encoding for binary • Key value • You can add your own keys
  • 5.
    iCalendar example BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//MacOS X 10.8.4//EN CALSCALE:GREGORIAN BEGIN:VEVENT TRANSP:OPAQUE DTEND:20130623T101500 UID:DB0E22EA-0828-40A3-BCFD-5A8CC9866CE4 DTSTAMP:20130514T135551Z LOCATION:Verdun/Lachine/Lasalle X-MOZ-GENERATION:1 URL;VALUE=URI:http://www.wocommunity.org/wowodc13/cayenne.html SEQUENCE:10 SUMMARY:Cayenne Training Day LAST-MODIFIED:20130509T165550Z DTSTART:20130623T091500 CREATED:20130505T001152Z END:VEVENT END:VCALENDAR
  • 6.
    UID • Each componentmust have a UID • UID:DB0E22EA-0828-40A3-BCFD-5A8CC9866CE4 • And it must be unique!
  • 7.
    Attendees • Attendees areinvitees. • Can be a individual, group, resource or room. (CUType) • Also have status: needs action, accepted, etc. (Partstat) • If you have attendees, you also need an organizer!
  • 8.
    iCalendar in ERGroupware •Similar to ERCalendar, but more modern and better validation. • Using iCal4j, a solid iCalendar library for Java. • Component to generate the file. • Will generate a text/calendar response
  • 9.
    iCalendar file generation ERGWCalendaraCalendar = new ERGWCalendar(); ERGWEvent event = new ERGWEvent(aCalendar); java.util.Calendar startTime = GregorianCalendar.getInstance(); event.setStartTime(new NSTimestamp(startTime.getTimeInMillis())); java.util.Calendar endTime = GregorianCalendar.getInstance(); endTime.add(java.util.Calendar.HOUR, 2); event.setEndTime(new NSTimestamp(endTime.getTimeInMillis())); event.setClassification(ERGWClassification.PUBLIC); event.setFreeBusyStatus(ERGWFreeBusyStatus.BUSY_TENTATIVE); event.setLocation("Montreal"); event.setCategories(new NSArray<String>(new String[] { "Category 1", "Category 2" })); event.setPriority(ERGWPriority.HIGH); event.setSummary("WOWODC 2013"); event.setTransparency(ERGWTransparency.TRANSPARENT); Calendar calendarData = ERGWCalendar.transformToICalObject(aCalendar); ERGWPublishCalendarPage nextPage = (ERGWPublishCalendarPage)pageWithName(ERGWPublishCalendarPage.class); nextPage.setCalendar(calendarData); return nextPage;
  • 10.
    CalDAV/CardDAV • Both basedon WebDAV • Will store iCalendar objects (CalDAV) or vCard objects (CardDAV) • Many many server implementations • But some of them are really basic (Google Calendar...) • iCal Server is the best implementation • Many clients too
  • 11.
    CalDAV • Open standard,defined in RFC 4791 • Servers must implements this RFC • Many extensions • Scheduling • Delegations • Sharing • etc.
  • 12.
    CalDAV • Calendars areDAV collections. • DAV collections have properties (ACL, owner, etc.). • Can create as many collections as you want. • Other DAV collections exists too.
  • 13.
    Features/extensions • CalDAV serversmust send the supported features set in the response. • Can do a OPTIONS request to see them. • Check the DAV header in the response.
  • 14.
    OPTIONS request $ curl--digest --user probert -X OPTIONS -v http://localhost:8008 < 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, calendarserver-sharing-no-scheduling, calendar- query-extended, calendar-default-alarms, addressbook, extended-mkcol, calendarserver-principal-property-search, calendarserver-principal- search
  • 15.
    CalDAV and HTTP •Almost REST-like • Use HTTP verbs • GET: fetch a object • PUT: create OR update a object • DELETE: delete a object • MKCALENDAR: create a calendar collection • Use headers, HTTP authentication (Basic, Digest, Kerberos...)
  • 16.
    iCalendar objects • Youstore iCalendar objects in calendar collections. • But you can only store ONE component (VTODO,VEVENT, etc.) per object.
  • 17.
    PUT • CalDAV don'tuse POST to create objects. • Must use PUT • If URL and UID already exists, will update the object. • If URL and UID don't exists, will create the object. • If URL don't exist but UID exist, will conflict. • Creating a object will return a 201 status code. • If you copy an object, don't forget to change the UID.
  • 18.
    Example of creatingevent PUT /calendars/__uids__/AD04C60B-3704-447B-8997-24F5ACF589C7/calendar/C440-473C-8FAD-D3E606159F40.ics HTTP/1.1 If-None-Match: 1f9e6e30-dfdb-4e5c-baf6-6dd107126a68 Content-Type: text/calendar BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.8.4//EN CALSCALE:GREGORIAN BEGIN:VEVENT ATTENDEE;CN="probert@macti.ca";CUTYPE=INDIVIDUAL:mailto:probert@macti.ca ATTENDEE;CN="Pascal Robert";CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:urn:uuid:AD04C60B-3704-447B-8997-24F5ACF589C7 DTEND;VALUE=DATE:20130616 TRANSP:TRANSPARENT ORGANIZER;CN="Pascal Robert":urn:uuid:AD04C60B-3704-447B-8997-24F5ACF589C7 UID:B54100D2-C440-473C-8FAD-D3E606159F40 DTSTAMP:20130615T123551Z LOCATION:Hilton Montréal Bonaventure SEQUENCE:9 SUMMARY:Test for presentation DTSTART;VALUE=DATE:20130615 CREATED:20130615T123241Z END:VEVENT END:VCALENDAR
  • 19.
    CalDAV support inERGroupware • Based on iCal4j-connector, which I contributed to. • Support for most CalDAV operations. • Get calendars (collections) • Create calendars • Fetch/delete/update calendar objects • Delete collections • Fetch events by time-range
  • 20.
    Connecting import net.fortuna.ical4j.connector.ObjectStoreException; import net.fortuna.ical4j.connector.dav.PathResolver; importer.groupware.caldav.CalDAVStore; import java.net.URL; CalDAVStore store = null; try { store = new CalDAVStore("probert", "somepassword", new URL("http://127.0.0.1"), PathResolver.ICAL_SERVER); } catch (MalformedURLException e) { e.printStackTrace(); } catch (ObjectStoreException e) { e.printStackTrace(); }
  • 21.
    Fetching calendar collectionsand objects NSArray<CalDAVCollection> collections = store.getCollections(); for (CalDAVCollection collection: collections) { NSLog.out.appendln(collection.displayName()); // Fetch all events (e.g, VEVENT objects) NSArray<ERGWCalendar> events = collection.events(); // Fetch all tasks (e.g, VTOTO objects) NSArray<ERGWCalendar> tasks = collection.tasks(); }
  • 22.
    Manipuling calendar objects //Create a new event and send it to the server ERGWCalendar aCalendar = new ERGWCalendar(); ERGWEvent event = new ERGWEvent(aCalendar); java.util.Calendar startTime = GregorianCalendar.getInstance(); event.setStartTime(new NSTimestamp(startTime.getTimeInMillis())); java.util.Calendar endTime = GregorianCalendar.getInstance(); endTime.add(java.util.Calendar.HOUR, 2); event.setEndTime(new NSTimestamp(endTime.getTimeInMillis())); event.setSummary("WOWODC 2013"); collection.addCalendarObject(aCalendar); // Update the event and send it to the server aCalendar.events().objectAtIndex(0).setSummary("WOWODC 2014"); collection.updateCalendarObject(aCalendar); // Delete the event collection.removeCalendarObject(aCalendar.events().objectAtIndex(0));
  • 23.
    Queries • REPORT requestis the same idea as a SQL SELECT query. • Let's you find calendar objects with a qualifier. • "Find all todos that are completed" • "Find events from June 22 to June 24" • "Find all rooms" • Won't work for custom properties
  • 24.
    Find events bytime range REPORT /bernard/work/ HTTP/1.1 Depth: 1 Content-Type: application/xml; charset="utf-8" <?xml version="1.0" encoding="utf-8" ?> <C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav"> <D:prop> <C:calendar-data> <C:limit-recurrence-set start="20060103T000000Z" end="20060105T000000Z"/> </C:calendar-data> </D:prop> <C:filter> <C:comp-filter name="VCALENDAR"> <C:comp-filter name="VEVENT"> <C:time-range start="20060103T000000Z" end="20060105T000000Z"/> </C:comp-filter> </C:comp-filter> </C:filter> </C:calendar-query>
  • 25.
    Find participants thatdidn't not accept invitations REPORT /bernard/work/ HTTP/1.1 Depth: 1 Content-Type: application/xml; charset="utf-8" <?xml version="1.0" encoding="utf-8" ?> <C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav"> <D:prop xmlns:D="DAV:"> <D:getetag/> <C:calendar-data/> </D:prop> <C:filter> <C:comp-filter name="VCALENDAR"> <C:comp-filter name="VEVENT"> <C:prop-filter name="ATTENDEE"> <C:param-filter name="PARTSTAT"> <C:text-match collation="i;ascii-casemap">NEEDS-ACTION</C:text-match> </C:param-filter> </C:prop-filter> </C:comp-filter> </C:comp-filter> </C:filter> </C:calendar-query>
  • 26.
    Queries support inERGroupware • Query by time range • CalDavCollection.eventsForTimePeriod(java.util.Date startTime, java.util.Date endTime) • Find all individuals • store.getIndividuals(String name); • Find rooms or resources • store.getAllRooms() • store.getAllResources()
  • 27.
  • 28.
    MS Exchange • ExchangeWeb Service (EWS) • Based on SOAP • Same protocol that Outlook 2011 uses • Most EWS attributes are similar to iCalendar and DAV collections attributes
  • 29.
    Example of request ---[HTTPrequest - https://webmail.sherweb2010.com/EWS/Exchange.asmx]--- Content-type: text/xml;charset="utf-8" Soapaction: "http://schemas.microsoft.com/exchange/services/2006/messages/SyncFolderHierarchy" Accept: text/xml, multipart/related, text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 <?xml version="1.0" ?> <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"> <S:Header> <MailboxCulture xmlns="..." xmlns:ns2="...">en-US</MailboxCulture> <RequestServerVersion xmlns="..." xmlns:ns2="..." Version="Exchange2007_SP1"/> </S:Header> <S:Body> <ns2:SyncFolderHierarchy xmlns="..." xmlns:ns2="..."> <ns2:FolderShape> <BaseShape>IdOnly</BaseShape> <AdditionalProperties> <FieldURI FieldURI="folder:DisplayName"/> <FieldURI FieldURI="folder:FolderClass"/> <ExtendedFieldURI PropertyType="Boolean" PropertyTag="0x10F4"/> </AdditionalProperties> </ns2:FolderShape> </ns2:SyncFolderHierarchy> </S:Body> </S:Envelope>
  • 30.
    Example of response <?xmlversion="1.0" encoding="utf-8"?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Header> <h:ServerVersionInfo ... Version="Exchange2010_SP2" /> </s:Header> <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <m:SyncFolderHierarchyResponse xmlns:m="..." xmlns:t="..."><m:ResponseMessages> <m:SyncFolderHierarchyResponseMessage ResponseClass="Success"> <m:ResponseCode>NoError</m:ResponseCode> <m:SyncState>H4sIAAAAAAAEAO29B2AcSZYlJi9tynt...</m:SyncState> <m:Changes> <t:Create> <t:Folder> <t:FolderId Id="AAMkAGEw...=" ChangeKey="AQAAABYAAACT3PaCL/2ASLnqcq9Sv0/DAAAAp5Bv"/> <t:FolderClass>IPF.Note</t:FolderClass> <t:DisplayName>INBOX</t:DisplayName> <t:ExtendedProperty> <t:ExtendedFieldURI PropertyTag="0x10f4" PropertyType="Boolean"/> <t:Value>false</t:Value> </t:ExtendedProperty> </t:Folder> </t:Create> </m:Changes> ...
  • 31.
    EWS support inERGroupware • Generated with JAXB. • Very basic support outside the generated classes: • Open connection • Create folders • Create task • Create contact • Create event
  • 32.
    Opening a connectionand fetch folders URL urlToWSDL = ERXApplication.application().resourceManager().pathURLForResourceNamed("Services.wsdl", "ERGroupware", null); ExchangeStore store = new ExchangeStore(urlToWSDL, "https://myserver/EWS/Exchange.asmx", "aUser", "aPassword", "AD.DOMAIN"); store.setServerVersionForRequest(ExchangeVersionType.EXCHANGE_2010_SP_1); store.setTimeZone(TimeZone.getDefault()); NSArray<ExchangeBaseFolder> folders = store.folders(); for (ExchangeBaseFolder folder: folders) { NSLog.out.appendln(folder.displayName()); }
  • 33.
    Adding an event for(ExchangeBaseFolder folder: store.folders()) { if (folder.displayName().equals("Calendar")) { ERGWCalendar calendar = new ERGWCalendar(); calendar.setCalendarName("Test"); calendar.setTimeZone(TimeZone.getDefault()); Calendar later = GregorianCalendar.getInstance(); later.add(Calendar.HOUR_OF_DAY, 1); event.setEndTime(new NSTimestamp(later.getTime())); UidGenerator uidGen = new UidGenerator("allo"); event.setUid(uidGen.generateUid().getValue()); ERGWEvent event = new ERGWEvent(calendar); event.setIsFullDay(false); event.setStartTime(new NSTimestamp()); event.setSummary("WOWODC 2013"); event.setLastModifiedDate(new NSTimestamp()); calendar.addEvent(event); exchangeStore.createCalendarEvent(calendar, (ExchangeCalendarFolder) folder); } }
  • 34.
    Adding an event for(ExchangeBaseFolder folder: store.folders()) { if (folder.displayName().equals("Tasks")) { ERGWCalendar calendar = new ERGWCalendar(); calendar.setCalendarName("Test"); calendar.setTimeZone(TimeZone.getDefault()); Calendar later = GregorianCalendar.getInstance(); later.add(Calendar.HOUR_OF_DAY, 1); task.setDueDate(new NSTimestamp(later.getTime())); UidGenerator uidGen = new UidGenerator("allo"); event.setUid(uidGen.generateUid().getValue()); ERGWTask task = new ERGWTask(calendar); task.setSummary("Finish that presentation"); task.setLastModifiedDate(new NSTimestamp()); calendar.addEvent(event); exchangeStore.createTask(calendar, (ExchangeCalendarFolder) folder); } }
  • 35.
    Adding a contact for(ExchangeBaseFolder folder: store.folders()) { if (folder.displayName().equals("Contacts")) { ERGWContact contact = new ERGWContact(); contact.setGivenName("Pascal"); contact.setFamilyName("Robert"); ERGWContactEmail personalEmail = new ERGWContactEmail(); personalEmail.setEmail("probert@macti.ca"); personalEmail.isPrefered(true); personalEmail.setTypes(new NSArray<ERGWContactEmailType>(new ERGWContactEmailType[] { ERGWContactEmailType.HOME })); contact.setEmails(new NSArray<ERGWContactEmail>(new ERGWContactEmail[] { personalEmail })); exchangeStore.createContact(contact, (ExchangeCalendarFolder) folder); } }
  • 36.
    Create folders // Createa folder of a specific type // ERGWFolderType values are: CALENDAR, CONTACTS, TASKS, SEARCH, PLAIN, etc. store.createFolder("New calendar folder", ERGWFolderType.CALENDAR); store.createFolder("New contacts folder", ERGWFolderType.CONTACTS); // Shortcut to create a calendar folder store.createCalendarFolder(String displayName) // Shortcut to create a contacts folder store.createContactsFolder(String displayName)
  • 37.
    Zimbra • SOAP andREST-based API. • Have API for everything: management, data and Web client (Zimlets). • Very basic support in ERGroupware (need to migrate a lot of code) • Connect to store • Add folder
  • 38.
    Example of request POST/service/soap/CreateAppointmentRequest HTTP/1.1 Content-Type: text/xml; charset=utf-8 <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"> <soap:Header> <context xmlns="urn:zimbra"><authToken>0_b6e206e6c3eee19a7e89f717d5972d9d6b9db224</authToken>...</context> </soap:Header> <soap:Body> <CreateAppointmentRequest xmlns="urn:zimbraMail"> <m l="10"> <inv> <comp status="CONF" allDay="0" name="Titre de la rencontre" loc="Montr[0xc3][0xa9]al" class="PUB" transp="O" fb="B"> <s d="20130619T050858" tz="America/Montreal"/> <e d="20130619T070858" tz="America/Montreal"/> <or d="Pascal Robert" a="root@zimbra.macti.lan"/> <at d="Pascal Robert" cutype="IND" a="probert@macti.ca"/> <at d="Salle 1" cutype="ROO" a="Room1@zimbra.macti.lan"/> <xprop name="X-RELATED-TO" value="281-280"/> </comp> </inv> <su>Titre de la rencontre</su> <mp ct="multipart/mixed"><mp ct="text/plain"> <content>Une plus long description</content> </mp> <mp ct="text/html"> <content>&lt;html>&lt;body> Une plus long description&lt;/body>&lt;/html></content> </mp> </m> </CreateAppointmentRequest> </soap:Body> </soap:Envelope>
  • 39.
    Example response HTTP/1.1 200OK Content-Type: text/xml;charset=UTF-8 <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"> <soap:Header> <context xmlns="urn:zimbra"><session id="11">11</session><change token="602"/> <notify seq="2"> <created> <appt id="283" uid="4ec78d56-0061-451d-9825-cb17b43b010b" d="1371632938000" rev="602" s="0" l="10"> <inv id="282" seq="0" compNum="0" type="appt"> <tz id="America/Montreal" stdoff="-300" dayname="EDT" dayoff="-240" stdname="EST">...</tz> <comp uid="4ec78d56-0061-451d-9825-cb17b43b010b" d="1371632938000" status="CONF" ...> <at d="Pascal Robert" cutype="IND" a="probert@macti.ca" url="probert@macti.ca"/> <at d="Salle 1" cutype="ROO" a="Room1@zimbra.macti.lan" url="Room1@zimbra.macti.lan"/> <xprop name="X-RELATED-TO" value="281-280"/> <desc>Une plus long description</desc> <descHtml>&lt;html>&lt;body> Une plus long description&lt;/body>&lt;/html></descHtml> <or d="Pascal Robert" a="admin@linux.conatus.lan" url="root@zimbra.macti.lan"/> <s u="1371632938000" d="20130619T050858" tz="America/Montreal"/> <e d="20130619T070858" u="1371640138000" tz="America/Montreal"/> </comp> </inv> </appt> </created> <modified><folder id="10" i4next="284" s="0" i4ms="602" n="3" uuid="6630b004-40cd-49b4-af4b-eca7bac45be6"/></modified> </notify> </context> </soap:Header> <soap:Body> <CreateAppointmentResponse rev="602" ms="602" invId="283-282" apptId="283" calItemId="283" xmlns="urn:zimbraMail"/> </soap:Body> </soap:Envelope>
  • 40.
    TODO • Move awayfrom ical4j-connector • Complete CalDAV and CardDAV support • Complete calendar, contacts and email support for Zimbra • Management API for Kerio, CGP and Zimbra
  • 41.
    Resources • https://github.com/pascalrobert/ERGroupware/ • http://www.macti.ca/wiki/ •http://www.calconnect.org • http://wiki.modularity.net.au/ical4j/ • http://msdn.microsoft.com/en-us/library/exchange/bb204119(v=exchg. 140).aspx
  • 42.