Integrating with the Liferay 6.2 Calendar app

Hello to all! smiley

This is my first post on liferay.com, so let me introduce myself: my name is Andrea (yes, it's a male name in Italy!) and I'm a software engineer working in the R&D group at SMC Treviso.

In this post I'll talk about the new Calendar app which was shipped with Liferay 6.2, and especially how to integrate it with the world around... I'd like to share my experience on that, because we've seen this kind of integrations may be a "killer application" in intranet (e.g. B2E) solutions and scenarios such as sales force automation, etc.

Speaking about integrations of the Liferay Calendar with client applications and devices, we started by building a CalDAV connector called "Calendar Mobile Sync", which enables users to interact with their calendars from Android and iOS devices, and also from Mozilla Thunderbird. The connector is freely available on the Liferay Marketplace and it's open source! As a technical note, the plugin contains a custom-made implementation of the CalDAV protocol that leverages Liferay's WebDAV framework and its latest enhancements, and it's indeed a good example smiley

Our next step was even bigger, because we managed to integrate the Liferay Calendar with Exchange Server (more information here). This enabled users to manage their Exchange calendars just by using the portlet, creating, editing and deleting events or meetings, booking resources and accepting/declining invitations. Anyway, even if this integration is focused on calendar events, it was designed from the ground up as modular as possible, so we're planning to enhance it for other types of Exchange data, such as tasks, notes, contacts, etc.

A strict requirement was to integrate Exchange Server and the Calendar portlet without altering in any way the code of the portlet itself. For this reason, we decided for a periodic two-way synchronization approach in an external Liferay plugin, which interacts with the Calendar portlet thanks to its service jar and periodically retrieves what events are added, updated or deleted in that time frame. Sharing the Liferay Service Layer between two different plugins is a common approach and there are tons of examples and blog posts about it; much more interesting would be to show you how to actually use those services. This first post is just introductory, so I'm not going to cover every single method; anyway, feel free to ask if you have any questions!

Data structure

The Calendar plugin has three main types of entities:

  • CalendarResource, which represents a generic entity which can have calendars and can be invited. It has className/classPK columns and therefore is linked to other Liferay entities. CalendarResource entities are automatically created for users and sites, and this means that every user and site have their own calendars and can be invited. Anyway, "stand-alone" resources such as meeting rooms, projectors, and other equipment can exist as well, and these CalendarResouce entities point to themselves via their className/classPK columns.
  • Calendar, which obviously represents a Calendar belonging to a resource
  • CalendarBooking, which represents an event contained in a calendar. A booking can be a "master" booking if it's an event or a meeting, or a "child" booking if it represents an invitation, and in this case there is a "parentCalendarBookingId" column which points to the master booking. When creating a meeting, multiple bookings are persisted: one as the "master" booking, and a booking for each resource which is invited to the meeting

Calendar Bookings

Adding, updating and deleting bookings is pretty straighforward:

CalendarBookingLocalServiceUtil.addCalendarBooking(
  userId, calendarId, childCalendarIds, parentCalendarBookingId, titleMap,
  descriptionMap, location, startTime, endTime, allDay, recurrence,
  firstReminder, firstReminderType, secondReminder, secondReminderType,
  serviceContext);

CalendarBookingLocalServiceUtil.updateCalendarBooking(
  userId, calendarBookingId, calendarId, titleMap, descriptionMap, location,
  startTime, endTime, allDay, recurrence, firstReminder, firstReminderType,
  secondReminder, secondReminderType, status, serviceContext);

CalendarBookingLocalServiceUtil.deleteCalendarBooking(calendarBookingId);

Here are some details about the various parameters needed by these methods:

  • userId is the ID of the user who is adding/updating the booking
  • childCalendarIds is used to create a meeting and it's an array containing the IDs of the calendars where the invitations should be created. Usually these are the IDs of the "default" calendars of each invited resource
  • parentCalendarBookingId is used while creating a new invitation. Use CalendarBookingConstants.PARENT_CALENDAR_BOOKING_ID_DEFAULT if creating a master booking
  • startTime and endTime are UNIX Epochs
  • recurrence is a string (following the RFC 2445 format) which contains the recurrence details of the booking. You can use the Recurrence and RecurrenceSerializer classes (package com.liferay.calendar.recurrence) in order to create and parse these strings, but be sure to include google-rfc-2445.jar in your classpath
  • *reminder is in milliseconds
  • *reminderType: only "email" is supported
  • status represents if the invitation has been accepted/declined/accepted tentatively. It can be one of the values of WorkflowConstants: STATUS_APPROVED, STATUS_DENIED, STATUS_PENDING, or CalendarBookingWorkflowConstants.STATUS_MAYBE (which is not in the Calendar's service jar and so you must copy the value of the constant).

Recurrence

Recurrent bookings are supported, and so exceptions can be created. To create a "hole" in a recurrence (that is, a day when the event does not occur) or to stop a recurrence from a certain day, you must delete one or more booking "instances", which are identified by their start time. You can also modify a booking instance, that is create an exception in a specific day with different properties (a different title, different start and end time, a different description, etc.)

CalendarBookingLocalServiceUtil.deleteCalendarBookingInstance(
  calendarBookingId, startTime, allFollowing);

Of course this is just the tip of the iceberg, and hopefully I'll cover more methods and details of the new Liferay 6.2 Calendar portlet. Also, stay tuned to know about our future plans: more integrations and enhancements will come in the future smiley

More Blog Entries

Blogs
Really interesting, thanks for sharing and enlightening us! We're trying to accomplish something very similar in the future, so thank you in advance for sharing the code as open source!
This looks fantastic, Andrea! Thanks for sharing with the community!
Excuse me,
may i have a question? just a few minutes...
i don't know how to get content in the aui-scheduler-event-recorder,
i find out i can get START_DATE and END_DATE,but always can't get CONTENT..
Please help!!THANKS
Hi Andrea,

Though it is your first post, it is an EXCELLENT post, very well written. Really appreciate. We are just implementing a booking application for Malaysian Government Portal where we are contemplating to integrate the booking of custom assets through Liferay Calendar. Your post has come at the right time to give some good insight into these concepts. Keeping making more such posts.

Ahamed Hasan
Author of Liferay Cookbook,
http://mpowerglobal.com/download-cookbook
Dear Andrea,

thanks for sharing the internals of the calendar app!

Now that I started using it I noticed some issues you may shed light on:
* isn't there permission management for the resources? put in other words, how can I deny a user the permission to create a new resource?
* is there a way not to give a user a personal calendar but only organization calendars?
* is there a way to show calendars of all organizations the current user is member of?

Thanks!
[...] Hi Vitina, I see now, it looks some development and integration will be needed to have this working. The only partly related match I could find is the blog post from Andrea:... [...] Read More