You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openmeetings.apache.org by so...@apache.org on 2016/10/04 04:03:03 UTC
svn commit: r1763226 [4/6] - in /openmeetings/application:
branches/3.2.x/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/calendar/
branches/3.2.x/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/calendar/
branches/3.2.x/ope...
Added: openmeetings/application/trunk/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/calendar/OmCalendar.java
URL: http://svn.apache.org/viewvc/openmeetings/application/trunk/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/calendar/OmCalendar.java?rev=1763226&view=auto
==============================================================================
--- openmeetings/application/trunk/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/calendar/OmCalendar.java (added)
+++ openmeetings/application/trunk/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/calendar/OmCalendar.java Tue Oct 4 04:03:01 2016
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License") + you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openmeetings.db.entity.calendar;
+
+import org.apache.openjpa.persistence.jdbc.ForeignKey;
+import org.apache.openmeetings.db.entity.IDataProviderEntity;
+import org.apache.openmeetings.db.entity.user.User;
+import org.simpleframework.xml.Element;
+import org.simpleframework.xml.Root;
+
+import javax.persistence.*;
+
+@Entity
+@Table(name = "om_calendar")
+@NamedQueries({
+ @NamedQuery(name="getCalendars", query="SELECT c FROM OmCalendar c WHERE c.deleted = false ORDER BY c.id")
+ , @NamedQuery(name="getCalendarbyUser", query="SELECT c FROM OmCalendar c" +
+ " WHERE c.deleted = false AND c.owner.id = :userId AND c.syncType <> OmCalendar$SyncType.GOOGLE_CALENDAR " +
+ "ORDER BY c.id")
+ // OpenJPA has trouble with referencing Subclasses, thus java $ symbol is used
+ // Comes from the OpenJPA Mailing List.
+ , @NamedQuery(name="getGoogleCalendars", query="SELECT c FROM OmCalendar c WHERE c.deleted = false AND c.owner.id = :userId " +
+ "AND c.syncType = OmCalendar$SyncType.GOOGLE_CALENDAR ORDER BY c.id")
+ , @NamedQuery(name="getAppointmentsbyCalendarinRange",
+ query="SELECT a FROM Appointment a "
+ + "WHERE a.deleted = false "
+ + " AND ( "
+ + " (a.start BETWEEN :start AND :end) "
+ + " OR (a.end BETWEEN :start AND :end) "
+ + " OR (a.start < :start AND a.end > :end) "
+ + " )"
+ + " AND a.owner.id = :userId AND a.calendar.id = :calId ")
+ , @NamedQuery(name="getCalendarbyId",
+ query="SELECT c FROM OmCalendar c WHERE c.deleted = false AND c.id = :calId")
+})
+@Root(name="om_calendar")
+public class OmCalendar implements IDataProviderEntity {
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name="id")
+ @Element(name="calendarId", data=true)
+ private Long id;
+
+ @Column(name="title")
+ @Element(name="calendarTitle", data=true, required=false)
+ private String title;
+
+ //Can Also act as the Google Calendar ID for Google Calendars
+ @Column(name="href")
+ @Element(name="calendarHref", data=true)
+ private String href;
+
+ //Can also act as the Google API Key for Google Calendars. Note, not always needed.
+ @Column(name="sync_token")
+ @Element(name="calendarSyncToken", data=true, required=false)
+ private String token;
+
+ @Column(name="deleted")
+ @Element(data=true)
+ private boolean deleted;
+
+ public enum SyncType {
+ NONE,
+ ETAG,
+ CTAG,
+ WEBDAV_SYNC,
+ GOOGLE_CALENDAR
+ }
+
+ @Column(name="sync_type")
+ @Enumerated(EnumType.STRING)
+ @Element(name="sync_type", data=true)
+ private SyncType syncType = SyncType.NONE;
+
+ @ManyToOne(fetch = FetchType.EAGER)
+ @JoinColumn(name="user_id", nullable=true)
+ @ForeignKey(enabled=true)
+ @Element(name="users_id", data=true, required=false)
+ private User owner;
+
+ //Getters + Setters
+ @Override
+ public Long getId() {
+ return id;
+ }
+
+ @Override
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getHref() {
+ return href;
+ }
+
+ public void setHref(String href) {
+ this.href = href;
+ }
+
+ public String getToken() {
+ return token;
+ }
+
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+ public boolean isDeleted() {
+ return deleted;
+ }
+
+ public void setDeleted(boolean deleted) {
+ this.deleted = deleted;
+ }
+
+ public SyncType getSyncType() {
+ return syncType;
+ }
+
+ public void setSyncType(SyncType syncType) {
+ this.syncType = syncType;
+ }
+
+ public User getOwner() {
+ return owner;
+ }
+
+ public void setOwner(User owner) {
+ this.owner = owner;
+ }
+
+ @Override
+ public String toString() {
+ return "Calendar [ id=" + id + ", title=" + title + ", token=" + token + ", href="
+ + href + ", SyncType=" + syncType + ", deleted=" + deleted + ", owner=" + owner + " ]";
+ }
+}
Added: openmeetings/application/trunk/openmeetings-server/src/site/resources/images/CalendarDialog_1.png
URL: http://svn.apache.org/viewvc/openmeetings/application/trunk/openmeetings-server/src/site/resources/images/CalendarDialog_1.png?rev=1763226&view=auto
==============================================================================
Binary file - no diff available.
Propchange: openmeetings/application/trunk/openmeetings-server/src/site/resources/images/CalendarDialog_1.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: openmeetings/application/trunk/openmeetings-server/src/site/resources/images/CalendarDialog_2.png
URL: http://svn.apache.org/viewvc/openmeetings/application/trunk/openmeetings-server/src/site/resources/images/CalendarDialog_2.png?rev=1763226&view=auto
==============================================================================
Binary file - no diff available.
Propchange: openmeetings/application/trunk/openmeetings-server/src/site/resources/images/CalendarDialog_2.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: openmeetings/application/trunk/openmeetings-server/src/site/xdoc/CalDAVandGCal.xml
URL: http://svn.apache.org/viewvc/openmeetings/application/trunk/openmeetings-server/src/site/xdoc/CalDAVandGCal.xml?rev=1763226&view=auto
==============================================================================
--- openmeetings/application/trunk/openmeetings-server/src/site/xdoc/CalDAVandGCal.xml (added)
+++ openmeetings/application/trunk/openmeetings-server/src/site/xdoc/CalDAVandGCal.xml Tue Oct 4 04:03:01 2016
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<document xmlns="http://maven.apache.org/XDOC/2.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
+ <properties>
+ <title>CalDAV and Google Calendar integration</title>
+ <author email="ankushmishra9@gmail.com">Ankush Mishra</author>
+ </properties>
+ <body>
+ <section name="CalDAV and Google Calendar integration">
+ <subsection name="Introduction">
+ <p>This integration brings the ability to use an External CalDAV server or a Google Calendar.
+ Till now the CalDAV server support includes, bidirectional access,
+ but Google Calendar for now supports only a read-only access.</p>
+ <p>Two types of external calendar available:</p>
+ <div>
+ <ul>
+ <li>CalDAV Calendar</li>
+ <li>Google Calendar</li>
+ </ul>
+ </div>
+ </subsection>
+ <subsection name="CalDAV Calendar">
+ <p>On the Calendar Page, after selecting the "Add External Calendar", you should see the Dialog Box as shown.</p>
+ <img src="images/CalendarDialog_1.png" alt="" width="600"/>
+ <br/>
+ <p>
+ By default it should be as shown. In the Title field,
+ it'll be Display Title of the Calendar. The External CalDAV URL, will be either the URL to your <b>Calendar</b> or to the <b>User Principal</b>.
+ Along with this provide the credentials to the Calendar Server, if necessary.</p>
+ <p>In case the URL is the User Principal, then all the Calendars in the Principal will be added to the Calendar page.</p>
+ </subsection>
+ <subsection name="Google Calendar">
+ <p>When the Google Calendar Checkbox is selected, then the calendar is a Google Calendar.
+ The two options, should be about the Google Calendar ID and the API Access Key. To know about how get those, follow the steps below.
+ </p>
+ <img src="images/CalendarDialog_2.png" alt="" width="600"/>
+ <br/>
+ <p>
+ First we, need to make a Google Calendar API key.
+ </p>
+ <div>
+ <ul>
+ <li>Go to the <a href="https://console.developers.google.com/" rel="nofollow">Google Developer Console</a> and create a new project (it might take a second).</li>
+ <li>Once in the project, go to <b>APIs & auth > APIs</b> on the sidebar.</li>
+ <li>Find "Calendar API" in the list and turn it ON.</li>
+ <li>On the sidebar, click <b>APIs & auth > Credentials</b>.</li>
+ <li>In the "Public API access" section, click "Create new Key".</li>
+ <li>Choose "Browser key".</li>
+ <li>If you know what domains will host your calendar, enter them into the box. Otherwise, leave it blank. You can always change it later.</li>
+ <li>Your new API key will appear. It might take second or two before it starts working.</li>
+ </ul>
+ </div>
+ <p>Make your Google Calendar public:</p>
+ <div>
+ <ul>
+ <li>In the Google Calendar interface, locate the "My calendars" area on the left.</li>
+ <li>Hover over the calendar you need and click the downward arrow.</li>
+ <li>A menu will appear. Click "Share this Calendar".</li>
+ <li>Check "Make this calendar public".</li>
+ <li>Make sure "Share only my free/busy information" is <b>unchecked</b>.</li>
+ <li>Click "Save".</li>
+ </ul>
+ </div>
+ <p>Obtain your Google Calendar's ID:</p>
+ <div>
+ <ul>
+ <li>In the Google Calendar interface, locate the "My calendars" area on the left.</li>
+ <li>Hover over the calendar you need and click the downward arrow.</li>
+ <li>A menu will appear. Click "Calendar settings".</li>
+ <li>In the "Calendar Address" section of the screen, you will see your Calendar ID. It will look something like "abcd1234@group.calendar.google.com".</li>
+ </ul>
+ </div>
+ <p>Place these two values in the Dialog Box's fields.
+ Note, at the current state the Google Calendar events, may not be visible on the page,
+ after adding. If you've done the aforementioned steps correctly, then,
+ just Reload the page or go out of the Calendar page for a moment and come back.</p>
+ <p>Note: The Steps mentioned have been taken from <a href="http://fullcalendar.io/docs/google_calendar/" rel="nofollow">here.</a></p>
+ </subsection>
+ </section>
+ </body>
+</document>
Modified: openmeetings/application/trunk/openmeetings-service/pom.xml
URL: http://svn.apache.org/viewvc/openmeetings/application/trunk/openmeetings-service/pom.xml?rev=1763226&r1=1763225&r2=1763226&view=diff
==============================================================================
--- openmeetings/application/trunk/openmeetings-service/pom.xml (original)
+++ openmeetings/application/trunk/openmeetings-service/pom.xml Tue Oct 4 04:03:01 2016
@@ -43,5 +43,17 @@
<artifactId>wicket-core</artifactId>
<version>${wicket.version}</version>
</dependency>
+ <dependency>
+ <groupId>com.github.caldav4j</groupId>
+ <artifactId>caldav4j</artifactId>
+ <version>0.9-SNAPSHOT</version>
+ <exclusions>
+ <exclusion>
+ <!-- Remove the need for ehcache, since we use the Methods directly -->
+ <groupId>net.sf.ehcache</groupId>
+ <artifactId>ehcache</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
</dependencies>
</project>
Added: openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/AppointmentManager.java
URL: http://svn.apache.org/viewvc/openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/AppointmentManager.java?rev=1763226&view=auto
==============================================================================
--- openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/AppointmentManager.java (added)
+++ openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/AppointmentManager.java Tue Oct 4 04:03:01 2016
@@ -0,0 +1,587 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License") + you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openmeetings.service.calendar.caldav;
+
+import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
+import static javax.servlet.http.HttpServletResponse.SC_OK;
+import static org.apache.openmeetings.util.OpenmeetingsVariables.webAppRootKey;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.commons.httpclient.Credentials;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
+import org.apache.commons.httpclient.auth.AuthScope;
+import org.apache.commons.httpclient.methods.OptionsMethod;
+import org.apache.commons.httpclient.params.HttpClientParams;
+import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
+import org.apache.jackrabbit.webdav.DavConstants;
+import org.apache.jackrabbit.webdav.MultiStatusResponse;
+import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
+import org.apache.jackrabbit.webdav.property.DavProperty;
+import org.apache.jackrabbit.webdav.property.DavPropertyName;
+import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
+import org.apache.jackrabbit.webdav.property.DavPropertySet;
+import org.apache.jackrabbit.webdav.xml.DomUtil;
+import org.apache.openmeetings.db.dao.calendar.AppointmentDao;
+import org.apache.openmeetings.db.dao.calendar.OmCalendarDao;
+import org.apache.openmeetings.db.entity.calendar.Appointment;
+import org.apache.openmeetings.db.entity.calendar.OmCalendar;
+import org.apache.openmeetings.db.entity.calendar.OmCalendar.SyncType;
+import org.apache.openmeetings.service.calendar.caldav.handler.CalendarHandler;
+import org.apache.openmeetings.service.calendar.caldav.handler.CtagHandler;
+import org.apache.openmeetings.service.calendar.caldav.handler.EtagsHandler;
+import org.apache.openmeetings.service.calendar.caldav.handler.WebDAVSyncHandler;
+import org.apache.wicket.util.string.Strings;
+import org.osaf.caldav4j.CalDAVConstants;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.w3c.dom.Element;
+
+/**
+ * Class which does syncing and provides respective API's required for performing CalDAV Operations.
+ * @author Ankush Mishra
+ */
+public class AppointmentManager {
+ private static final Logger log = Red5LoggerFactory.getLogger(AppointmentManager.class, webAppRootKey);
+
+ //HttpClient and ConnectionManager Params
+ private static final int IDLE_CONNECTION_TIMEOUT = 30000; // 30 seconds
+ private static final int MAX_HOST_CONNECTIONS = 6; // Number of simultaneous connections to one host
+ private static final int MAX_TOTAL_CONNECTIONS = 10; // Max Connections, at one time in memory.
+ private static final int CONNECTION_MANAGER_TIMEOUT = 1000; // Waits for 1 sec if no empty connection exists
+
+ private MultiThreadedHttpConnectionManager connmanager = null;
+
+ @Autowired
+ private OmCalendarDao calendarDao;
+ @Autowired
+ private AppointmentDao appointmentDao;
+ @Autowired
+ private iCalUtils utils;
+
+ /**
+ * Returns a new HttpClient with the inbuilt connection manager in this.
+ *
+ * @return HttpClient object that was created.
+ */
+ public HttpClient createHttpClient() {
+ if (connmanager == null) {
+ connmanager = new MultiThreadedHttpConnectionManager();
+ HttpConnectionManagerParams params = new HttpConnectionManagerParams();
+ params.setDefaultMaxConnectionsPerHost(MAX_HOST_CONNECTIONS);
+ params.setMaxTotalConnections(MAX_TOTAL_CONNECTIONS);
+ connmanager.setParams(params);
+ }
+
+ HttpClientParams clientParams = new HttpClientParams();
+ clientParams.setConnectionManagerTimeout(CONNECTION_MANAGER_TIMEOUT);
+ return new HttpClient(connmanager);
+ }
+
+ /**
+ * Returns the Path from the Calendar.
+ *
+ * @param client Client which makes the connection.
+ * @param calendar Calendar who's URL is used to get the path from.
+ * @return Path component of the URL.
+ */
+ public String getPathfromCalendar(HttpClient client, OmCalendar calendar) {
+ URI temp = URI.create(calendar.getHref());
+ client.getHostConfiguration().setHost(temp.getHost(), temp.getPort(), temp.getScheme());
+ return temp.getPath();
+ }
+
+ /**
+ * Ensure the URL ends with a, trailing slash, i.e. "/"
+ *
+ * @param str String URL to check.
+ * @return String which has a trailing slash.
+ */
+ private static String ensureTrailingSlash(String str) {
+ return str.endsWith("/") || str.endsWith("\\") ? str : str + "/";
+ }
+
+ /**
+ * Adds the Credentials provided to the given client on the Calendar's URL.
+ *
+ * @param client Client which makes the connection.
+ * @param calendar Calendar whose Host the Credentials are for.
+ * @param credentials Credentials to add
+ */
+ public void provideCredentials(HttpClient client, OmCalendar calendar, Credentials credentials) {
+ if (!Strings.isEmpty(calendar.getHref()) && credentials != null) {
+ URI temp = URI.create(calendar.getHref());
+ client.getHostConfiguration().setHost(temp.getHost(), temp.getPort(), temp.getScheme());
+ client.getState().setCredentials(new AuthScope(temp.getHost(), temp.getPort()), credentials);
+ }
+ }
+
+ /**
+ * Tests if the Calendar's URL can be accessed, or not.
+ *
+ * @param client Client which makes the connection.
+ * @param calendar Calendar whose URL is to be accessed.
+ * @return Returns true for HTTP Status 200, or 204, else false.
+ */
+ public boolean testConnection(HttpClient client, OmCalendar calendar) {
+ cleanupIdleConnections();
+
+ OptionsMethod optionsMethod = null;
+ try {
+ String path = getPathfromCalendar(client, calendar);
+ optionsMethod = new OptionsMethod(path);
+ optionsMethod.setRequestHeader("Accept", "*/*");
+ client.executeMethod(optionsMethod);
+ int status = optionsMethod.getStatusCode();
+ if (status == SC_OK || status == SC_NO_CONTENT) {
+ return true;
+ }
+ } catch (IOException e) {
+ log.error("Error executing OptionsMethod during testConnection.", e);
+ } catch (Exception e) {
+ //Should not ever reach here.
+ log.error("Severe Error in executing OptionsMethod during testConnection.", e);
+ } finally {
+ if (optionsMethod != null) {
+ optionsMethod.releaseConnection();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Create or Update calendar on the database.
+ *
+ * @param calendar Calendar to update or create
+ */
+ public boolean createCalendar(HttpClient client, OmCalendar calendar) {
+ if (calendar.getId() == null && calendar.getSyncType() != SyncType.GOOGLE_CALENDAR) {
+ return discoverCalendars(client, calendar);
+ }
+ calendarDao.update(calendar);
+ return true;
+ }
+
+ /**
+ * Deletes the calendar from the local database.
+ *
+ * @param calendar Calendar to delete
+ */
+ public void deleteCalendar(OmCalendar calendar) {
+ calendarDao.delete(calendar);
+ }
+
+ public List<OmCalendar> getCalendars() {
+ return calendarDao.get();
+ }
+
+ /**
+ * @see OmCalendarDao#get(Long)
+ */
+ public List<OmCalendar> getCalendars(Long userid) {
+ return calendarDao.get(userid);
+ }
+
+ /**
+ * @see OmCalendarDao#getGoogleCalendars(Long)
+ */
+ public List<OmCalendar> getGoogleCalendars(Long userId) {
+ return calendarDao.getGoogleCalendars(userId);
+ }
+
+ /**
+ * Function which when called performs syncing based on the type of Syncing detected.
+ *
+ * @param calendar Calendar who's sync has to take place
+ */
+ public void syncItem(HttpClient client, OmCalendar calendar) {
+ cleanupIdleConnections();
+
+ if (calendar.getSyncType() != SyncType.NONE) {
+ CalendarHandler calendarHandler;
+ String path = getPathfromCalendar(client, calendar);
+
+ switch (calendar.getSyncType()) {
+ case WEBDAV_SYNC:
+ calendarHandler = new WebDAVSyncHandler(path, calendar, client, appointmentDao, utils);
+ break;
+ case CTAG:
+ calendarHandler = new CtagHandler(path, calendar, client, appointmentDao, utils);
+ break;
+ case ETAG:
+ default: //Default is the EtagsHandler.
+ calendarHandler = new EtagsHandler(path, calendar, client, appointmentDao, utils);
+ break;
+ }
+
+ calendarHandler.syncItems();
+ calendarDao.update(calendar);
+ }
+ }
+
+ /**
+ * Syncs all the calendars currrently present on the DB.
+ */
+ public void syncItems(HttpClient client, Long userId) {
+ List<OmCalendar> calendars = getCalendars(userId);
+ for (OmCalendar calendar : calendars) {
+ syncItem(client, calendar);
+ }
+ }
+
+ /**
+ * Function which finds all the calendars of the Principal URL of the calendar
+ */
+ private boolean discoverCalendars(HttpClient client, OmCalendar calendar) {
+ cleanupIdleConnections();
+
+ if (calendar.getSyncType() == SyncType.NONE) {
+ PropFindMethod propFindMethod = null;
+ String userPath = null, homepath = null;
+
+ DavPropertyName curUserPrincipal = DavPropertyName.create("current-user-principal"),
+ calHomeSet = DavPropertyName.create("calendar-home-set", CalDAVConstants.NAMESPACE_CALDAV),
+ suppCalCompSet = DavPropertyName.create("supported-calendar-component-set", CalDAVConstants.NAMESPACE_CALDAV);
+
+ //Find out whether it's a calendar or if we can find the calendar-home or current-user url
+ try {
+ String path = getPathfromCalendar(client, calendar);
+
+ DavPropertyNameSet properties = new DavPropertyNameSet();
+ properties.add(curUserPrincipal);
+ properties.add(calHomeSet);
+ properties.add(DavPropertyName.RESOURCETYPE);
+
+ propFindMethod = new PropFindMethod(path, properties, CalDAVConstants.DEPTH_0);
+ client.executeMethod(propFindMethod);
+
+ if (propFindMethod.succeeded()) {
+ for (MultiStatusResponse response : propFindMethod.getResponseBodyAsMultiStatus().getResponses()) {
+ DavPropertySet set = response.getProperties(SC_OK);
+ DavProperty<?> calhome = set.get(calHomeSet), curPrinci = set.get(curUserPrincipal),
+ resourcetype = set.get(DavPropertyName.RESOURCETYPE);
+
+ if (checkCalendarResourceType(resourcetype)) {
+ //This is a calendar and thus initialize and return
+ return initCalendar(client, calendar);
+ }
+
+ //Else find all the calendars on the Principal and return.
+ if (calhome != null) {
+ //Calendar Home Path
+ homepath = getTextValuefromProperty(calhome);
+ break;
+ } else if (curPrinci != null) {
+ //Current User Principal Path
+ userPath = getTextValuefromProperty(curPrinci);
+ break;
+ }
+ }
+ } else {
+ return false;
+ }
+
+ if (homepath == null && userPath != null) {
+ //If calendar home path wasn't set, then we get it
+ DavPropertyNameSet props = new DavPropertyNameSet();
+ props.add(calHomeSet);
+ propFindMethod = new PropFindMethod(userPath, props, DavConstants.DEPTH_0);
+ client.executeMethod(propFindMethod);
+
+ if (propFindMethod.succeeded()) {
+ for (MultiStatusResponse response : propFindMethod.getResponseBodyAsMultiStatus().getResponses()) {
+ DavPropertySet set = response.getProperties(SC_OK);
+ DavProperty<?> calhome = set.get(calHomeSet);
+
+ if (calhome != null) {
+ homepath = getTextValuefromProperty(calhome);
+ break;
+ }
+ }
+ } else {
+ return false;
+ }
+ }
+
+ if (homepath != null) {
+ DavPropertyNameSet props = new DavPropertyNameSet();
+ props.add(DavPropertyName.RESOURCETYPE);
+ props.add(suppCalCompSet);
+ props.add(DavPropertyName.DISPLAYNAME);
+
+ propFindMethod = new PropFindMethod(homepath, props, DavConstants.DEPTH_1);
+
+ client.executeMethod(propFindMethod);
+
+ if (propFindMethod.succeeded()) {
+ boolean success = false;
+ for (MultiStatusResponse response : propFindMethod.getResponseBodyAsMultiStatus().getResponses()) {
+ boolean isVevent = false, isCalendar;
+
+ DavPropertySet set = response.getProperties(SC_OK);
+ DavProperty<?> p = set.get(suppCalCompSet),
+ resourcetype = set.get(DavPropertyName.RESOURCETYPE),
+ displayname = set.get(DavPropertyName.DISPLAYNAME);
+
+ isCalendar = checkCalendarResourceType(resourcetype);
+
+ if (p != null) {
+ for (Object o : (Collection<?>) p.getValue()) {
+ if (o instanceof Element) {
+ Element e = (Element) o;
+ String name = DomUtil.getAttribute(e, "name", null);
+ if (name.equals("VEVENT")) {
+ isVevent = true;
+ }
+ }
+ }
+ }
+
+ if (isCalendar && isVevent) {
+ success = true;
+ //Get New Calendar
+ OmCalendar tempCalendar = new OmCalendar();
+
+ if (displayname != null) {
+ tempCalendar.setTitle(displayname.getValue().toString());
+ }
+
+ tempCalendar.setHref(client.getHostConfiguration().getHostURL() + response.getHref());
+
+ tempCalendar.setDeleted(false);
+ tempCalendar.setOwner(calendar.getOwner());
+
+ calendarDao.update(tempCalendar);
+ initCalendar(client, tempCalendar);
+ }
+ }
+ return success;
+ }
+ }
+
+ } catch (IOException e) {
+ log.error("Error executing PROPFIND Method, during testConnection.", e);
+ } catch (Exception e) {
+ log.error("Severe Error in executing PROPFIND Method, during testConnection.", e);
+ } finally {
+ if (propFindMethod != null) {
+ propFindMethod.releaseConnection();
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static String getTextValuefromProperty(DavProperty<?> property) {
+ String value = null;
+
+ if (property != null) {
+ for (Object o : (Collection<?>) property.getValue()) {
+ if (o instanceof Element) {
+ Element e = (Element) o;
+ value = DomUtil.getTextTrim(e);
+ break;
+ }
+ }
+ }
+ return value;
+ }
+
+ /**
+ * Returns true if the resourcetype Property has a Calendar Element under it.
+ *
+ * @param resourcetype ResourceType Property
+ * @return True if, resource is Calendar, else false.
+ */
+ private static boolean checkCalendarResourceType(DavProperty<?> resourcetype) {
+ boolean isCalendar = false;
+
+ if (resourcetype != null) {
+ DavPropertyName calProp = DavPropertyName.create("calendar", CalDAVConstants.NAMESPACE_CALDAV);
+
+ for (Object o : (Collection<?>) resourcetype.getValue()) {
+ if (o instanceof Element) {
+ Element e = (Element) o;
+ if (e.getLocalName().equals(calProp.getName())) {
+ isCalendar = true;
+ }
+ }
+ }
+ }
+ return isCalendar;
+ }
+
+ /**
+ * Function to initialize the Calendar on the type of syncing and whether it can be used or not.
+ */
+ private boolean initCalendar(HttpClient client, OmCalendar calendar) {
+
+ if (calendar.getToken() == null || calendar.getSyncType() == SyncType.NONE) {
+ calendarDao.update(calendar);
+
+ PropFindMethod propFindMethod = null;
+
+ try {
+ String path = getPathfromCalendar(client, calendar);
+
+ DavPropertyNameSet properties = new DavPropertyNameSet();
+ properties.add(DavPropertyName.RESOURCETYPE);
+ properties.add(DavPropertyName.DISPLAYNAME);
+ properties.add(CtagHandler.DNAME_GETCTAG);
+ properties.add(WebDAVSyncHandler.DNAME_SYNCTOKEN);
+
+ propFindMethod = new PropFindMethod(path, properties, CalDAVConstants.DEPTH_0);
+ client.executeMethod(propFindMethod);
+
+ if (propFindMethod.succeeded()) {
+
+ for (MultiStatusResponse response : propFindMethod.getResponseBodyAsMultiStatus().getResponses()) {
+ DavPropertySet set = response.getProperties(SC_OK);
+
+ if (calendar.getTitle() == null) {
+ DavProperty<?> property = set.get(DavPropertyName.DISPLAYNAME);
+ calendar.setTitle(property == null ? null : property.getValue().toString());
+ }
+
+ DavProperty<?> ctag = set.get(CtagHandler.DNAME_GETCTAG),
+ syncToken = set.get(WebDAVSyncHandler.DNAME_SYNCTOKEN);
+ if (syncToken != null) {
+ calendar.setSyncType(SyncType.WEBDAV_SYNC);
+ } else if (ctag != null) {
+ calendar.setSyncType(SyncType.CTAG);
+ } else {
+ calendar.setSyncType(SyncType.ETAG);
+ }
+ }
+
+ syncItem(client, calendar);
+ return true;
+ } else {
+ log.error("Error executing PROPFIND Method, with status Code: {}", propFindMethod.getStatusCode());
+ calendar.setSyncType(SyncType.NONE);
+ }
+
+ } catch (IOException e) {
+ log.error("Error executing OptionsMethod during testConnection.", e);
+ } catch (Exception e) {
+ log.error("Severe Error in executing OptionsMethod during testConnection.", e);
+ } finally {
+ if (propFindMethod != null) {
+ propFindMethod.releaseConnection();
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Function for create/updating multiple appointment on the server.
+ * Performs modification alongside of creation new events on the server.
+ *
+ * @param appointment Appointment to create/update.
+ */
+ public boolean updateItem(HttpClient client, Appointment appointment) {
+ cleanupIdleConnections();
+
+ OmCalendar calendar = appointment.getCalendar();
+ SyncType type = calendar.getSyncType();
+ if (type != SyncType.NONE && type != SyncType.GOOGLE_CALENDAR) {
+ CalendarHandler calendarHandler;
+ String path = ensureTrailingSlash(getPathfromCalendar(client, calendar));
+
+ switch (type) {
+ case WEBDAV_SYNC:
+ case CTAG:
+ case ETAG:
+ calendarHandler = new EtagsHandler(path, calendar, client, appointmentDao, utils);
+ break;
+ default:
+ return false;
+ }
+ return calendarHandler.updateItem(appointment);
+ }
+ return false;
+ }
+
+ /**
+ * Delete Appointment on the CalDAV server.
+ * Delete's on the Server only if the ETag of the Appointment is the one on the server,
+ * i.e. only if the Event hasn't changed on the Server.
+ *
+ * @param appointment Appointment to Delete
+ */
+ public boolean deleteItem(HttpClient client, Appointment appointment) {
+ cleanupIdleConnections();
+
+ OmCalendar calendar = appointment.getCalendar();
+ SyncType type = calendar.getSyncType();
+
+ if (type != SyncType.NONE && type != SyncType.GOOGLE_CALENDAR) {
+ CalendarHandler calendarHandler;
+ String path = getPathfromCalendar(client, calendar);
+
+ switch (type) {
+ case WEBDAV_SYNC:
+ case CTAG:
+ case ETAG:
+ calendarHandler = new EtagsHandler(path, calendar, client, appointmentDao, utils);
+ break;
+ default:
+ return false;
+ }
+
+ return calendarHandler.deleteItem(appointment);
+ }
+ return false;
+ }
+
+ /**
+ * Returns the String value of the property, else null.
+ *
+ * @param property Property who's string value is to be returned.
+ * @return String representation of the Property Value.
+ */
+ public static String getTokenFromProperty(DavProperty<?> property) {
+ return (property == null) ? null : property.getValue().toString();
+ }
+
+ /**
+ * Cleans up unused idle connections.
+ */
+ public void cleanupIdleConnections() {
+ if (connmanager != null) {
+ connmanager.closeIdleConnections(IDLE_CONNECTION_TIMEOUT);
+ }
+ }
+
+ /**
+ * Method which is called when the Context is destroyed.
+ */
+ public void destroy() {
+ MultiThreadedHttpConnectionManager.shutdownAll();
+ connmanager = null;
+ }
+}
Added: openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/AbstractCalendarHandler.java
URL: http://svn.apache.org/viewvc/openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/AbstractCalendarHandler.java?rev=1763226&view=auto
==============================================================================
--- openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/AbstractCalendarHandler.java (added)
+++ openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/AbstractCalendarHandler.java Tue Oct 4 04:03:01 2016
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License") + you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openmeetings.service.calendar.caldav.handler;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.openmeetings.db.dao.calendar.AppointmentDao;
+import org.apache.openmeetings.db.entity.calendar.Appointment;
+import org.apache.openmeetings.db.entity.calendar.OmCalendar;
+import org.apache.openmeetings.service.calendar.caldav.iCalUtils;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Abstract Class which contains all the common code for all Handlers.
+ */
+public abstract class AbstractCalendarHandler implements CalendarHandler {
+
+ //TODO: Check if protected is necessary.
+ protected HttpClient client;
+ protected OmCalendar calendar;
+ protected String path;
+ protected iCalUtils utils;
+
+ protected AppointmentDao appointmentDao;
+
+ public AbstractCalendarHandler(String path, OmCalendar calendar, HttpClient client,
+ AppointmentDao appointmentDao, iCalUtils utils)
+ {
+ this.path = path;
+ this.calendar = calendar;
+ this.client = client;
+ this.appointmentDao = appointmentDao;
+ this.utils = utils;
+ }
+
+ public static Map<String, Appointment> listToMap(List<String> keys, List<Appointment> values) {
+ Map<String, Appointment> map = new HashMap<>();
+ for (int i = 0; i < keys.size(); ++i) {
+ map.put(keys.get(i), values.get(i));
+ }
+ return map;
+ }
+}
Added: openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/CalendarHandler.java
URL: http://svn.apache.org/viewvc/openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/CalendarHandler.java?rev=1763226&view=auto
==============================================================================
--- openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/CalendarHandler.java (added)
+++ openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/CalendarHandler.java Tue Oct 4 04:03:01 2016
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License") + you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openmeetings.service.calendar.caldav.handler;
+
+import org.apache.openmeetings.db.entity.calendar.Appointment;
+import org.apache.openmeetings.db.entity.calendar.OmCalendar;
+
+/**
+ * SyncHandler Interface specifies if the class is Sync Handler.
+ * The classes which implement this class can be used to
+ * update items depending on the type of syncing necessary.
+ * <p>
+ * This is a helper class to help Syncing of CalDAV calendars.
+ * <p>
+ * There are currently three types of Sync Handlers. Namely:
+ * <li>ETAGs Sync</li>
+ * <li>CTAGs Sync (Similar to ETAGs but an additional step is necessary)</li>
+ * <li>WebDAV-Sync Report</li>
+ * <li>Calendar-Multiget Report</li>
+ */
+public interface CalendarHandler {
+
+ /**
+ * Function to update all items in the CalDAV calendar. The owner of the appointments
+ * created are the same as the owner of the calendar.
+ * @return Returns the updated calendar after updation of all events.
+ */
+ public OmCalendar syncItems();
+
+ /**
+ * Function for create/updating multiple appointment on the server.
+ * Performs modification alongside of creation new events on the server.
+ * @param appointment Appointment to create/update.
+ * @return <code>True</code> when the updation is a success else <code>False</code>
+ */
+ public boolean updateItem(Appointment appointment);
+
+ /**
+ * Delete Appointment on the server.
+ * @param appointment Appointment to delete
+ * @return <code>True</code> when the deletion is a success else <code>False</code>
+ */
+ public boolean deleteItem(Appointment appointment);
+}
Added: openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/CtagHandler.java
URL: http://svn.apache.org/viewvc/openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/CtagHandler.java?rev=1763226&view=auto
==============================================================================
--- openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/CtagHandler.java (added)
+++ openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/CtagHandler.java Tue Oct 4 04:03:01 2016
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License") + you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openmeetings.service.calendar.caldav.handler;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.jackrabbit.webdav.DavException;
+import org.apache.jackrabbit.webdav.MultiStatusResponse;
+import org.apache.jackrabbit.webdav.property.DavPropertyName;
+import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
+import org.apache.jackrabbit.webdav.property.DavPropertySet;
+import org.apache.jackrabbit.webdav.xml.Namespace;
+import org.apache.openmeetings.db.dao.calendar.AppointmentDao;
+import org.apache.openmeetings.db.entity.calendar.Appointment;
+import org.apache.openmeetings.db.entity.calendar.OmCalendar;
+import org.apache.openmeetings.service.calendar.caldav.AppointmentManager;
+import org.apache.openmeetings.service.calendar.caldav.iCalUtils;
+import org.osaf.caldav4j.CalDAVConstants;
+import org.osaf.caldav4j.methods.PropFindMethod;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+
+import java.io.IOException;
+
+import static org.apache.openmeetings.util.OpenmeetingsVariables.webAppRootKey;
+import static org.apache.jackrabbit.webdav.DavServletResponse.SC_OK;
+
+/**
+ * Class for Syncing through the help of Ctags.
+ * It checks if the Ctag of the Calendar has changed.
+ * If it has then update the events, otherwise leave it as it is.
+ *
+ * @see CalendarHandler
+ */
+public class CtagHandler extends AbstractCalendarHandler {
+ private static final Logger log = Red5LoggerFactory.getLogger(CtagHandler.class, webAppRootKey);
+
+ public static final Namespace NAMESPACE_CALSERVER = Namespace.getNamespace("cs", "http://calendarserver.org/ns/");
+ public static final DavPropertyName DNAME_GETCTAG = DavPropertyName.create("getctag", NAMESPACE_CALSERVER);
+
+ public CtagHandler(String path, OmCalendar calendar, HttpClient client, AppointmentDao appointmentDao, iCalUtils utils) {
+ super(path, calendar, client, appointmentDao, utils);
+ }
+
+ @Override
+ public OmCalendar syncItems() {
+ //Calendar already inited.
+
+ PropFindMethod propFindMethod = null;
+
+ try {
+ DavPropertyNameSet properties = new DavPropertyNameSet();
+ properties.add(DNAME_GETCTAG);
+
+ propFindMethod = new PropFindMethod(path, properties, CalDAVConstants.DEPTH_0);
+ client.executeMethod(propFindMethod);
+
+ if (propFindMethod.succeeded()) {
+ for (MultiStatusResponse response : propFindMethod.getResponseBodyAsMultiStatus().getResponses()) {
+ DavPropertySet set = response.getProperties(SC_OK);
+ String ctag = AppointmentManager.getTokenFromProperty(set.get(DNAME_GETCTAG));
+
+ if (ctag != null && !ctag.equals(calendar.getToken())) {
+ EtagsHandler etagsHandler = new EtagsHandler(path, calendar, client, appointmentDao, utils);
+ etagsHandler.syncItems();
+ calendar.setToken(ctag);
+ }
+ }
+ } else {
+ log.error("Error executing PROPFIND Method, with status Code: "
+ + propFindMethod.getStatusCode());
+ }
+
+ } catch (IOException | DavException e) {
+ log.error("Error during the execution of calendar-multiget Report.", e);
+ } catch (Exception e) {
+ log.error("Severe Error during the execution of calendar-multiget Report.", e);
+ } finally {
+ if (propFindMethod != null) {
+ propFindMethod.releaseConnection();
+ }
+ }
+
+ return calendar;
+ }
+
+ @Override
+ public boolean updateItem(Appointment appointment) {
+ EtagsHandler etagsHandler = new EtagsHandler(path, calendar, client, appointmentDao, utils);
+ return etagsHandler.updateItem(appointment);
+ }
+
+ @Override
+ public boolean deleteItem(Appointment appointment) {
+ EtagsHandler etagsHandler = new EtagsHandler(path, calendar, client, appointmentDao, utils);
+ return etagsHandler.deleteItem(appointment);
+ }
+}
Added: openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/EtagsHandler.java
URL: http://svn.apache.org/viewvc/openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/EtagsHandler.java?rev=1763226&view=auto
==============================================================================
--- openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/EtagsHandler.java (added)
+++ openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/EtagsHandler.java Tue Oct 4 04:03:01 2016
@@ -0,0 +1,266 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License") + you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openmeetings.service.calendar.caldav.handler;
+
+import net.fortuna.ical4j.data.CalendarOutputter;
+import net.fortuna.ical4j.model.Calendar;
+import net.fortuna.ical4j.model.Component;
+
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.jackrabbit.webdav.DavException;
+import org.apache.jackrabbit.webdav.MultiStatusResponse;
+import org.apache.jackrabbit.webdav.property.DavPropertyName;
+import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
+import org.apache.openmeetings.db.dao.calendar.AppointmentDao;
+import org.apache.openmeetings.db.entity.calendar.Appointment;
+import org.apache.openmeetings.db.entity.calendar.OmCalendar;
+import org.apache.openmeetings.db.entity.calendar.OmCalendar.SyncType;
+import org.apache.openmeetings.service.calendar.caldav.iCalUtils;
+import org.apache.wicket.util.string.Strings;
+import org.osaf.caldav4j.CalDAVConstants;
+import org.osaf.caldav4j.methods.CalDAVReportMethod;
+import org.osaf.caldav4j.methods.DeleteMethod;
+import org.osaf.caldav4j.methods.PutMethod;
+import org.osaf.caldav4j.model.request.CalendarData;
+import org.osaf.caldav4j.model.request.CalendarQuery;
+import org.osaf.caldav4j.model.request.CompFilter;
+import org.osaf.caldav4j.model.response.CalendarDataProperty;
+import org.osaf.caldav4j.util.UrlUtils;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.openmeetings.util.OpenmeetingsVariables.webAppRootKey;
+import static org.apache.jackrabbit.webdav.DavServletResponse.SC_OK;
+import static org.apache.jackrabbit.webdav.DavServletResponse.SC_CREATED;
+import static org.apache.jackrabbit.webdav.DavServletResponse.SC_NO_CONTENT;
+import static org.apache.jackrabbit.webdav.DavServletResponse.SC_NOT_FOUND;
+
+/**
+ * Class which handles the Syncing through the use of Etags.
+ * First it sends a Calendar-query, and checks all the etags for events.
+ * Then, for each href and etag, we get, something like key-value pair.
+ * <p>
+ * We then check for three things:
+ * <li>Addition of a new event.</li>
+ * <li>Modification of an existing event.</li>
+ * <li>Deletion of events which are not in the response.</li>
+ */
+public class EtagsHandler extends AbstractCalendarHandler {
+ private static final Logger log = Red5LoggerFactory.getLogger(EtagsHandler.class, webAppRootKey);
+
+ public EtagsHandler(String path, OmCalendar calendar, HttpClient client, AppointmentDao appointmentDao, iCalUtils utils) {
+ super(path, calendar, client, appointmentDao, utils);
+ }
+
+ @Override
+ public OmCalendar syncItems() {
+ Long ownerId = this.calendar.getOwner().getId();
+ Map<String, Appointment> map = listToMap(appointmentDao.getHrefsbyCalendar(calendar.getId()),
+ appointmentDao.getbyCalendar(calendar.getId()));
+
+ DavPropertyNameSet properties = new DavPropertyNameSet();
+ properties.add(DavPropertyName.GETETAG);
+
+ CompFilter vcalendar = new CompFilter(Calendar.VCALENDAR);
+ vcalendar.addCompFilter(new CompFilter(Component.VEVENT));
+
+ CalDAVReportMethod reportMethod = null;
+ try {
+ CalendarQuery query = new CalendarQuery(properties, vcalendar, map.isEmpty() ? new CalendarData() : null, false, false);
+ reportMethod = new CalDAVReportMethod(path, query, CalDAVConstants.DEPTH_1);
+ client.executeMethod(reportMethod);
+ if (reportMethod.succeeded()) {
+ MultiStatusResponse[] multiStatusResponses = reportMethod.getResponseBodyAsMultiStatus().getResponses();
+ if (map.isEmpty()) {
+ //Initializing the Calendar for the first time.
+
+ //Parse the responses into Appointments
+ for (MultiStatusResponse response : multiStatusResponses) {
+ if (response.getStatus()[0].getStatusCode() == SC_OK) {
+ String etag = CalendarDataProperty.getEtagfromResponse(response);
+ Calendar ical = CalendarDataProperty.getCalendarfromResponse(response);
+ Appointment appointments = utils.parseCalendartoAppointment(
+ ical, response.getHref(), etag, calendar);
+
+ appointmentDao.update(appointments, ownerId);
+ }
+ }
+ } else {
+ //Calendar has been inited before
+ List<String> currenthrefs = new ArrayList<String>();
+
+ for (MultiStatusResponse response : multiStatusResponses) {
+ if (response.getStatus()[0].getStatusCode() == SC_OK) {
+ Appointment appointment = map.get(response.getHref());
+
+ //Event updated
+ if (appointment != null) {
+ String origetag = appointment.getEtag(),
+ currentetag = CalendarDataProperty.getEtagfromResponse(response);
+
+ //If etag is modified
+ if (!currentetag.equals(origetag)) {
+ currenthrefs.add(appointment.getHref());
+ }
+ map.remove(response.getHref());
+ } else {
+ // The orig list of events doesn't contain this event.
+ currenthrefs.add(response.getHref());
+ }
+ }
+ }
+
+ //Remaining Events have been deleted on the server, thus delete them
+ for (Map.Entry<String, Appointment> entry : map.entrySet()) {
+ appointmentDao.delete(entry.getValue(), ownerId);
+ }
+
+ //Get the rest of the events through a Multiget Handler.
+ MultigetHandler multigetHandler = new MultigetHandler(currenthrefs, path,
+ calendar, client, appointmentDao, utils);
+ return multigetHandler.syncItems();
+ }
+ } else {
+ log.error("Report Method return Status: {} for calId {} ", reportMethod.getStatusCode(), calendar.getId());
+ }
+ } catch (IOException | DavException e) {
+ log.error("Error during the execution of calendar-multiget Report.", e);
+ } catch (Exception e) {
+ log.error("Severe Error during the execution of calendar-multiget Report.", e);
+ } finally {
+ if (reportMethod != null) {
+ reportMethod.releaseConnection();
+ }
+ }
+
+ return calendar;
+ }
+
+ @Override
+ public boolean updateItem(Appointment appointment) {
+ OmCalendar calendar = appointment.getCalendar();
+ String href;
+
+ if (calendar != null && calendar.getSyncType() != SyncType.NONE) {
+
+ //Store new Appointment on the server
+ PutMethod putMethod = null;
+ try {
+ List<String> hrefs = null;
+ CalendarOutputter calendarOutputter = new CalendarOutputter();
+
+ Calendar ical = utils.parseAppointmenttoCalendar(appointment);
+
+ putMethod = new PutMethod();
+ putMethod.setRequestBody(ical);
+ putMethod.setCalendarOutputter(calendarOutputter);
+
+ if (Strings.isEmpty(appointment.getHref())) {
+ String temp = path + appointment.getIcalId() + ".ics";
+ temp = UrlUtils.removeDoubleSlashes(temp);
+ putMethod.setPath(temp);
+ putMethod.setIfNoneMatch(true);
+ putMethod.setAllEtags(true);
+ } else {
+ putMethod.setPath(appointment.getHref());
+ putMethod.setIfMatch(true);
+ putMethod.addEtag(appointment.getEtag());
+ }
+
+ client.executeMethod(putMethod);
+
+ if (putMethod.getStatusCode() == SC_CREATED ||
+ putMethod.getStatusCode() == SC_NO_CONTENT) {
+ href = putMethod.getPath();
+ appointment.setHref(href);
+
+ //Check if the ETag header was returned.
+ Header etagh = putMethod.getResponseHeader("ETag");
+ if (etagh == null)
+ hrefs = Collections.singletonList(appointment.getHref());
+ else {
+ appointment.setEtag(etagh.getValue());
+ appointmentDao.update(appointment, appointment.getOwner().getId());
+ }
+ } else {
+ //Appointment not created on the server
+ return false;
+ }
+
+ //Get new etags for the ones which didn't return an ETag header
+ MultigetHandler multigetHandler = new MultigetHandler(hrefs, true, path,
+ calendar, client, appointmentDao, utils);
+ multigetHandler.syncItems();
+ return true;
+ } catch (IOException e) {
+ log.error("Error executing OptionsMethod during testConnection.", e);
+ } catch (Exception e) {
+ log.error("Severe Error in executing OptionsMethod during testConnection.", e);
+ } finally {
+ if (putMethod != null) {
+ putMethod.releaseConnection();
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @see CalendarHandler#deleteItem(Appointment)
+ */
+ @Override
+ public boolean deleteItem(Appointment appointment) {
+
+ if (calendar != null && calendar.getSyncType() != SyncType.NONE && !Strings.isEmpty(appointment.getHref())) {
+ DeleteMethod deleteMethod = null;
+ try {
+ deleteMethod = new DeleteMethod(appointment.getHref(), appointment.getEtag());
+
+ log.info("Deleting at location: {} with ETag: {}", appointment.getHref(), appointment.getEtag());
+
+ client.executeMethod(deleteMethod);
+
+ int status = deleteMethod.getStatusCode();
+ if (status == SC_NO_CONTENT || status == SC_OK || status == SC_NOT_FOUND) {
+ log.info("Successfully deleted appointment with id: " + appointment.getId());
+ return true;
+ } else {
+ // Appointment Not deleted
+ }
+ } catch (IOException e) {
+ log.error("Error executing OptionsMethod during testConnection.", e);
+ } catch (Exception e) {
+ log.error("Severe Error in executing OptionsMethod during testConnection.", e);
+ } finally {
+ if (deleteMethod != null) {
+ deleteMethod.releaseConnection();
+ }
+ }
+ }
+ return false;
+ }
+}
Added: openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/MultigetHandler.java
URL: http://svn.apache.org/viewvc/openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/MultigetHandler.java?rev=1763226&view=auto
==============================================================================
--- openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/MultigetHandler.java (added)
+++ openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/MultigetHandler.java Tue Oct 4 04:03:01 2016
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License") + you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openmeetings.service.calendar.caldav.handler;
+
+import net.fortuna.ical4j.model.Calendar;
+import net.fortuna.ical4j.model.Component;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.jackrabbit.webdav.DavException;
+import org.apache.jackrabbit.webdav.MultiStatusResponse;
+import org.apache.jackrabbit.webdav.property.DavPropertyName;
+import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
+import org.apache.openmeetings.db.dao.calendar.AppointmentDao;
+import org.apache.openmeetings.db.entity.calendar.Appointment;
+import org.apache.openmeetings.db.entity.calendar.OmCalendar;
+import org.apache.openmeetings.db.entity.calendar.OmCalendar.SyncType;
+import org.apache.openmeetings.service.calendar.caldav.iCalUtils;
+import org.osaf.caldav4j.CalDAVConstants;
+import org.osaf.caldav4j.methods.CalDAVReportMethod;
+import org.osaf.caldav4j.model.request.CalendarData;
+import org.osaf.caldav4j.model.request.CalendarMultiget;
+import org.osaf.caldav4j.model.request.CompFilter;
+import org.osaf.caldav4j.model.response.CalendarDataProperty;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.jackrabbit.webdav.DavServletResponse.SC_OK;
+import static org.apache.openmeetings.util.OpenmeetingsVariables.webAppRootKey;
+
+/**
+ * Class used to sync a given list of hrefs and update or add new Appointments, whenever feasible.
+ * This class cannot be used to update or delete Appointments, which are handled seperately.
+ * We use the Calendar-Multiget Report Method to handle this type of query.
+ *
+ * @see CalendarHandler
+ */
+public class MultigetHandler extends AbstractCalendarHandler {
+ private static final Logger log = Red5LoggerFactory.getLogger(MultigetHandler.class, webAppRootKey);
+
+ private CalendarMultiget query;
+ private boolean isMultigetDisabled = false, onlyEtag = false;
+
+ public MultigetHandler(List<String> hrefs, boolean onlyEtag, String path, OmCalendar calendar, HttpClient client,
+ AppointmentDao appointmentDao, iCalUtils utils) {
+ super(path, calendar, client, appointmentDao, utils);
+ this.onlyEtag = onlyEtag;
+
+ if (hrefs == null || hrefs.isEmpty() || calendar.getSyncType() == SyncType.NONE) {
+ isMultigetDisabled = true;
+ } else {
+ DavPropertyNameSet properties = new DavPropertyNameSet();
+ properties.add(DavPropertyName.GETETAG);
+
+ CalendarData calendarData = null;
+ if (!onlyEtag) {
+ calendarData = new CalendarData();
+ }
+ CompFilter vcalendar = new CompFilter(Calendar.VCALENDAR);
+ vcalendar.addCompFilter(new CompFilter(Component.VEVENT));
+ query = new CalendarMultiget(properties, calendarData, false, false);
+ query.setHrefs(hrefs);
+ }
+ }
+
+ public MultigetHandler(List<String> hrefs, String path, OmCalendar calendar, HttpClient client,
+ AppointmentDao appointmentDao, iCalUtils utils)
+ {
+ this(hrefs, false, path, calendar, client, appointmentDao, utils);
+ }
+
+ @Override
+ public OmCalendar syncItems() {
+ Long ownerId = this.calendar.getOwner().getId();
+ if (!isMultigetDisabled) {
+
+ CalDAVReportMethod reportMethod = null;
+
+ try {
+ reportMethod = new CalDAVReportMethod(path, query, CalDAVConstants.DEPTH_1);
+
+ client.executeMethod(reportMethod);
+
+ if (reportMethod.succeeded()) {
+ //Map for each Href as key and Appointment as Value.
+ Map<String, Appointment> map = listToMap(appointmentDao.getHrefsbyCalendar(calendar.getId()),
+ appointmentDao.getbyCalendar(calendar.getId()));
+
+ for (MultiStatusResponse response : reportMethod.getResponseBodyAsMultiStatus().getResponses()) {
+ if (response.getStatus()[0].getStatusCode() == SC_OK) {
+ Appointment a = map.get(response.getHref());
+
+ //Check if it's an updated Appointment
+ if (a != null) {
+ String origetag = a.getEtag(),
+ currentetag = CalendarDataProperty.getEtagfromResponse(response);
+
+ //If etag is modified
+ if (!currentetag.equals(origetag)) {
+ if (onlyEtag) {
+ a.setEtag(currentetag);
+ } else {
+ Calendar calendar = CalendarDataProperty.getCalendarfromResponse(response);
+ a = utils.parseCalendartoAppointment(a, calendar, currentetag);
+ }
+ appointmentDao.update(a, ownerId);
+ }
+ } else if (!onlyEtag) {
+ //Else it's a new Appointment
+ // i.e. parse into a new Appointment
+ // Only applicable when we get calendar data along with etag.
+ String etag = CalendarDataProperty.getEtagfromResponse(response);
+ Calendar ical = CalendarDataProperty.getCalendarfromResponse(response);
+ Appointment appointments = utils.parseCalendartoAppointment(
+ ical, response.getHref(), etag, calendar);
+ appointmentDao.update(appointments, ownerId);
+ }
+ }
+ }
+ } else {
+ log.error("Report Method return Status: {} for calId {}", reportMethod.getStatusCode(), calendar.getId());
+ }
+ } catch (IOException | DavException e) {
+ log.error("Error during the execution of calendar-multiget Report.", e);
+ } catch (Exception e) {
+ log.error("Severe Error during the execution of calendar-multiget Report.", e);
+ } finally {
+ if (reportMethod != null) {
+ reportMethod.releaseConnection();
+ }
+ }
+ }
+
+ return calendar;
+ }
+
+ // Doesn't handle Creation, Updation and Deletion of events.
+ // Don't call these for MultigetHandler.
+ @Override
+ public boolean updateItem(Appointment appointment) {
+ return false;
+ }
+
+ @Override
+ public boolean deleteItem(Appointment appointment) {
+ return false;
+ }
+}
Added: openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/WebDAVSyncHandler.java
URL: http://svn.apache.org/viewvc/openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/WebDAVSyncHandler.java?rev=1763226&view=auto
==============================================================================
--- openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/WebDAVSyncHandler.java (added)
+++ openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/handler/WebDAVSyncHandler.java Tue Oct 4 04:03:01 2016
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License") + you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openmeetings.service.calendar.caldav.handler;
+
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.jackrabbit.webdav.MultiStatusResponse;
+import org.apache.jackrabbit.webdav.property.DavPropertyName;
+import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
+import org.apache.openmeetings.db.dao.calendar.AppointmentDao;
+import org.apache.openmeetings.db.entity.calendar.Appointment;
+import org.apache.openmeetings.db.entity.calendar.OmCalendar;
+import org.apache.openmeetings.service.calendar.caldav.iCalUtils;
+import org.apache.openmeetings.service.calendar.caldav.methods.SyncMethod;
+import org.apache.openmeetings.service.calendar.caldav.methods.SyncReportInfo;
+import org.osaf.caldav4j.model.response.CalendarDataProperty;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.jackrabbit.webdav.DavServletResponse.SC_INSUFFICIENT_SPACE_ON_RESOURCE;
+import static org.apache.jackrabbit.webdav.DavServletResponse.SC_OK;
+import static org.apache.jackrabbit.webdav.DavServletResponse.SC_FORBIDDEN;
+import static org.apache.jackrabbit.webdav.DavServletResponse.SC_PRECONDITION_FAILED;
+import static org.apache.jackrabbit.webdav.DavServletResponse.SC_NOT_FOUND;
+import static org.apache.openmeetings.util.OpenmeetingsVariables.webAppRootKey;
+
+/**
+ * Class used to sync events using WebDAV-Sync defined in RFC 6578.
+ * This handles the additional HTTP Status Code 507, which specifies for further sync required.
+ * For syncing, it gets a Sync Report as response, which specifies which files have been added,
+ * modified or deleted.
+ */
+public class WebDAVSyncHandler extends AbstractCalendarHandler {
+ private static final Logger log = Red5LoggerFactory.getLogger(WebDAVSyncHandler.class, webAppRootKey);
+
+ public static final DavPropertyName DNAME_SYNCTOKEN = DavPropertyName.create(SyncReportInfo.XML_SYNC_TOKEN,
+ SyncReportInfo.NAMESPACE);
+
+ public WebDAVSyncHandler(String path, OmCalendar calendar, HttpClient client, AppointmentDao appointmentDao, iCalUtils utils) {
+ super(path, calendar, client, appointmentDao, utils);
+ }
+
+ @Override
+ public OmCalendar syncItems() {
+ boolean additionalSyncNeeded = false;
+
+ SyncMethod syncMethod = null;
+
+ try {
+ DavPropertyNameSet properties = new DavPropertyNameSet();
+ properties.add(DavPropertyName.GETETAG);
+
+ //Create report to get
+ SyncReportInfo reportInfo = new SyncReportInfo(calendar.getToken(), properties,
+ SyncReportInfo.SYNC_LEVEL_1);
+ syncMethod = new SyncMethod(path, reportInfo);
+ client.executeMethod(syncMethod);
+
+ if (syncMethod.succeeded()) {
+ List<String> currenthrefs = new ArrayList<>();
+
+ //Map of Href and the Appointments, belonging to it.
+ Map<String, Appointment> map = listToMap(appointmentDao.getHrefsbyCalendar(calendar.getId()),
+ appointmentDao.getbyCalendar(calendar.getId()));
+
+ for (MultiStatusResponse response : syncMethod.getResponseBodyAsMultiStatus().getResponses()) {
+ int status = response.getStatus()[0].getStatusCode();
+ if (status == SC_OK) {
+ Appointment a = map.get(response.getHref());
+
+ if (a != null) {
+ //Old Event to get
+ String origetag = a.getEtag(),
+ currentetag = CalendarDataProperty.getEtagfromResponse(response);
+
+ //If event modified, only then get it.
+ if (!currentetag.equals(origetag)) {
+ currenthrefs.add(response.getHref());
+ }
+ } else {
+ //New Event, to get
+ currenthrefs.add(response.getHref());
+ }
+ } else if (status == SC_NOT_FOUND) {
+ //Delete the Appointments not found on the server.
+ Appointment a = map.get(response.getHref());
+
+ //Only if the event exists on the database, delete it.
+ if (a != null) {
+ appointmentDao.delete(a, calendar.getOwner().getId());
+ }
+ } else if (status == SC_INSUFFICIENT_SPACE_ON_RESOURCE) {
+ additionalSyncNeeded = true;
+ }
+ }
+
+
+ MultigetHandler multigetHandler = new MultigetHandler(currenthrefs, path,
+ calendar, client, appointmentDao, utils);
+ multigetHandler.syncItems();
+
+ //Set the new token
+ calendar.setToken(syncMethod.getResponseSynctoken());
+ } else if (syncMethod.getStatusCode() == SC_FORBIDDEN ||
+ syncMethod.getStatusCode() == SC_PRECONDITION_FAILED) {
+
+ //Specific case where a server might sometimes forget the sync token
+ //Thus requiring a full sync needed to be done.
+ log.info("Sync Token not accepted by server. Doing a full sync again.");
+ calendar.setToken(null);
+ additionalSyncNeeded = true;
+ } else {
+ log.error("Error in Sync Method Response with status code {}", syncMethod.getStatusCode());
+ }
+
+ } catch (IOException e) {
+ log.error("Error while executing the SyncMethod Report.", e);
+ } catch (Exception e) {
+ log.error("Severe Error while executing the SyncMethod Report.", e);
+ } finally {
+ if (syncMethod != null) {
+ syncMethod.releaseConnection();
+ }
+ }
+
+ return additionalSyncNeeded ? syncItems() : calendar;
+ }
+
+ @Override
+ public boolean updateItem(Appointment appointment) {
+ EtagsHandler etagsHandler = new EtagsHandler(path, calendar, client, appointmentDao, utils);
+ return etagsHandler.updateItem(appointment);
+ }
+
+ @Override
+ public boolean deleteItem(Appointment appointment) {
+ EtagsHandler etagsHandler = new EtagsHandler(path, calendar, client, appointmentDao, utils);
+ return etagsHandler.deleteItem(appointment);
+ }
+}
Added: openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/iCalUtils.java
URL: http://svn.apache.org/viewvc/openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/iCalUtils.java?rev=1763226&view=auto
==============================================================================
--- openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/iCalUtils.java (added)
+++ openmeetings/application/trunk/openmeetings-service/src/main/java/org/apache/openmeetings/service/calendar/caldav/iCalUtils.java Tue Oct 4 04:03:01 2016
@@ -0,0 +1,479 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License") + you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openmeetings.service.calendar.caldav;
+
+import net.fortuna.ical4j.model.Calendar;
+import net.fortuna.ical4j.model.*;
+import net.fortuna.ical4j.model.component.CalendarComponent;
+import net.fortuna.ical4j.model.component.VEvent;
+import net.fortuna.ical4j.model.parameter.Cn;
+import net.fortuna.ical4j.model.parameter.Role;
+import net.fortuna.ical4j.model.property.*;
+import org.apache.commons.lang3.time.FastDateFormat;
+import org.apache.openmeetings.db.dao.user.UserDao;
+import org.apache.openmeetings.db.entity.calendar.Appointment;
+import org.apache.openmeetings.db.entity.calendar.MeetingMember;
+import org.apache.openmeetings.db.entity.calendar.OmCalendar;
+import org.apache.openmeetings.db.entity.room.Room;
+import org.apache.openmeetings.db.entity.user.User;
+import org.apache.openmeetings.db.util.TimezoneUtil;
+import org.apache.wicket.protocol.http.WebSession;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.net.URI;
+import java.text.ParsePosition;
+import java.util.*;
+import java.util.Date;
+import java.util.TimeZone;
+
+import static org.apache.openmeetings.util.OpenmeetingsVariables.webAppRootKey;
+
+/**
+ * Class which provides iCalendar Utilities.
+ * This class's functions could be made static, as they are not instantiated anyway.
+ */
+public class iCalUtils {
+ private static final Logger log = Red5LoggerFactory.getLogger(iCalUtils.class, webAppRootKey);
+ public static final String PROD_ID = "-//Events Calendar//Apache Openmeetings//EN";
+
+ @Autowired
+ private TimezoneUtil timezoneUtil;
+ @Autowired
+ private UserDao userDao;
+
+ /**
+ * Parses the Calendar from the CalDAV server, to a new Appointment.
+ *
+ * @param calendar iCalendar Representation.
+ * @param href Location of the Calendar on the server
+ * @param etag ETag of the calendar.
+ * @param omCalendar The Parent OmCalendar, to which the Appointment belongs.
+ * @return Appointment after parsing.
+ */
+ public Appointment parseCalendartoAppointment(Calendar calendar, String href, String etag, OmCalendar omCalendar) {
+ //Note: By RFC 4791 only one event can be stored in one href.
+
+ Appointment a = new Appointment();
+ a.setId(null);
+ a.setDeleted(false);
+ a.setHref(href);
+ a.setCalendar(omCalendar);
+ a.setOwner(omCalendar.getOwner());
+ a.setRoom(createDefaultRoom());
+ a.setReminder(Appointment.Reminder.none);
+
+ return this.parseCalendartoAppointment(a, calendar, etag);
+ }
+
+ /**
+ * Parses a Calendar with multiple VEvents into Appointments
+ *
+ * @param calendar Calendar to Parse
+ * @param ownerId Owner of the Appointments
+ * @return <code>List</code> of Appointments
+ */
+ public List<Appointment> parseCalendartoAppointments(Calendar calendar, Long ownerId) {
+ List<Appointment> appointments = new ArrayList<>();
+ ComponentList<CalendarComponent> events = calendar.getComponents(Component.VEVENT);
+ User owner = userDao.get(ownerId);
+ TimeZone tz = parseTimeZone(calendar, owner);
+
+ for (CalendarComponent event : events) {
+ Appointment a = new Appointment();
+ a.setOwner(owner);
+ a.setDeleted(false);
+ a.setRoom(createDefaultRoom());
+ a.setReminder(Appointment.Reminder.none);
+ a = addVEventPropertiestoAppointment(a, event, tz);
+ appointments.add(a);
+ }
+ return appointments;
+ }
+
+ /**
+ * Updating Appointments which already exist, by parsing the Calendar. And updating etag.
+ * Doesn't work with complex Recurrences.
+ * Note: Hasn't been tested to acknowledge DST, timezones should acknowledge this.
+ *
+ * @param a Appointment to be updated.
+ * @param calendar iCalendar Representation.
+ * @param etag The ETag of the calendar.
+ * @return Updated Appointment.
+ */
+ public Appointment parseCalendartoAppointment(Appointment a, Calendar calendar, String etag) {
+ if (calendar == null) return a;
+ CalendarComponent event = calendar.getComponent(Component.VEVENT);
+ if (event != null) {
+ TimeZone tz = parseTimeZone(calendar, a.getOwner());
+
+ a.setEtag(etag);
+ a = addVEventPropertiestoAppointment(a, event, tz);
+ }
+ return a;
+ }
+
+ /**
+ * Add properties from the Given VEvent Component to the Appointment
+ *
+ * @param a Appointment to which the properties are to be added
+ * @param event VEvent to parse properties from.
+ * @param tz Timezone of the Event
+ * @return Updated Appointment
+ */
+ private Appointment addVEventPropertiestoAppointment(Appointment a, CalendarComponent event, TimeZone tz) {
+ Property dtstart = event.getProperty(Property.DTSTART),
+ dtend = event.getProperty(Property.DTEND),
+ uid = event.getProperty(Property.UID),
+ dtstamp = event.getProperty(Property.DTSTAMP),
+ description = event.getProperty(Property.DESCRIPTION),
+ summary = event.getProperty(Property.SUMMARY),
+ location = event.getProperty(Property.LOCATION),
+ lastmod = event.getProperty(Property.LAST_MODIFIED),
+ organizer = event.getProperty(Property.ORGANIZER),
+ recur = event.getProperty(Property.RRULE);
+ PropertyList attendees = event.getProperties(Property.ATTENDEE);
+
+ if (uid != null) {
+ a.setIcalId(uid.getValue());
+ }
+
+ Date d = parseDate(dtstart, tz);
+ a.setStart(d);
+ if (dtend == null) {
+ a.setEnd(addTimetoDate(d, java.util.Calendar.HOUR_OF_DAY, 1));
+ } else {
+ a.setEnd(parseDate(dtend, tz));
+ }
+
+ a.setInserted(parseDate(dtstamp, tz));
+
+ if (lastmod != null) {
+ a.setUpdated(parseDate(lastmod, tz));
+ }
+
+ if (description != null) {
+ a.setDescription(description.getValue());
+ }
+
+ if (summary != null) {
+ a.setTitle(summary.getValue());
+ }
+
+ if (location != null) {
+ a.setLocation(location.getValue());
+ }
+
+ if (recur != null) {
+ Parameter freq = recur.getParameter("FREQ");
+ if (freq != null) {
+ if (freq.getValue().equals(Recur.DAILY)) {
+ a.setIsDaily(true);
+ } else if (freq.getValue().equals(Recur.WEEKLY)) {
+ a.setIsWeekly(true);
+ } else if (freq.getValue().equals(Recur.MONTHLY)) {
+ a.setIsMonthly(true);
+ } else if (freq.getValue().equals(Recur.YEARLY)) {
+ a.setIsYearly(true);
+ }
+ }
+ }
+
+ List<MeetingMember> attList = a.getMeetingMembers() == null ? new ArrayList<>() : a.getMeetingMembers();
+
+ //Note this value can be repeated in attendees as well.
+ if (organizer != null) {
+ URI uri = URI.create(organizer.getValue());
+
+ //If the value of the organizer is an email
+ if (uri.getScheme().equals("mailto")) {
+ String email = uri.getSchemeSpecificPart();
+ //Contact or exist and owner
+ User org = userDao.getByEmail(email);
+ if (org == null) {
+ org = userDao.getContact(email, a.getOwner());
+ attList.add(createMeetingMember(a, org));
+ } else if (org.getId() != a.getOwner().getId()) {
+ attList.add(createMeetingMember(a, org));
+ }
+ }
+ }
+
+ if (attendees != null && !attendees.isEmpty()) {
+ for (Property attendee : attendees) {
+ URI uri = URI.create(attendee.getValue());
+ if (uri.getScheme().equals("mailto")) {
+ String email = uri.getSchemeSpecificPart();
+ User u = userDao.getByEmail(email);
+ if (u == null) {
+ u = userDao.getContact(email, a.getOwner());
+ }
+ attList.add(createMeetingMember(a, u));
+ }
+ }
+ }
+
+ a.setMeetingMembers(attList.isEmpty() ? null : attList);
+
+ return a;
+ }
+
+ private static MeetingMember createMeetingMember(Appointment a, User u) {
+ MeetingMember mm = new MeetingMember();
+ mm.setUser(u);
+ mm.setDeleted(false);
+ mm.setInserted(a.getInserted());
+ mm.setUpdated(a.getUpdated());
+ mm.setAppointment(a);
+ return mm;
+ }
+
+ private static Room createDefaultRoom() {
+ Room r = new Room();
+ r.setAppointment(true);
+ if (r.getType() == null) {
+ r.setType(Room.Type.conference);
+ }
+ return r;
+ }
+
+ /**
+ * Parses the VTimezone Component of the given Calendar. If no, VTimezone component is found the
+ * User Timezone is used
+ *
+ * @param calendar Calendar to parse
+ * @param owner Owner of the Calendar
+ * @return Parsed TimeZone
+ */
+ public TimeZone parseTimeZone(Calendar calendar, User owner) {
+ if (calendar != null) {
+ Component timezone = calendar.getComponent(Component.VTIMEZONE);
+ if (timezone != null) {
+ Property tzid = timezone.getProperty(Property.TZID);
+ if (tzid != null) {
+ return timezoneUtil.getTimeZone(tzid.getValue());
+ }
+ }
+ }
+ return timezoneUtil.getTimeZone(owner);
+ }
+
+ /**
+ * Convenience function to parse date from {@link net.fortuna.ical4j.model.Property} to
+ * {@link Date}
+ *
+ * @param dt DATE-TIME Property from which we parse.
+ * @param timeZone Timezone of the Date.
+ * @return {@link java.util.Date} representation of the iCalendar value.
+ */
+ public Date parseDate(Property dt, TimeZone timeZone) {
+ if (dt == null || dt.getValue().equals("")) {
+ return null;
+ }
+
+ String[] acceptedFormats = {"yyyyMMdd'T'HHmmss", "yyyyMMdd'T'HHmmss'Z'", "yyyyMMdd"};
+ Parameter tzid = dt.getParameter(Parameter.TZID);
+ if (tzid == null) {
+ return parseDate(dt.getValue(), acceptedFormats, timeZone);
+ } else {
+ return parseDate(dt.getValue(), acceptedFormats, timezoneUtil.getTimeZone(tzid.getValue()));
+ }
+ }
+
+ /**
+ * Adapted from DateUtils to support Timezones, and parse ical dates into {@link java.util.Date}.
+ * Note: Replace FastDateFormat to java.time, when shifting to Java 8 or higher.
+ *
+ * @param str Date representation in String.
+ * @param patterns Patterns to parse the date against
+ * @param timeZone Timezone of the Date.
+ * @return <code>java.util.Date</code> representation of string or
+ * <code>null</code> if the Date could not be parsed.
+ */
+ public Date parseDate(String str, String[] patterns, TimeZone timeZone) {
+ FastDateFormat parser = null;
+ Locale locale = WebSession.get().getLocale();
+
+ if (str.endsWith("Z")) {
+ timeZone = TimeZone.getTimeZone("UTC");
+ }
+
+ ParsePosition pos = new ParsePosition(0);
+ for (String pattern : patterns) {
+ parser = FastDateFormat.getInstance(pattern, timeZone, locale);
+ pos.setIndex(0);
+ Date date = parser.parse(str, pos);
+ if (date != null && pos.getIndex() == str.length()) {
+ return date;
+ }
+ }
+ log.error("Unable to parse the date: " + str + " at " + -1);
+ return null;
+ }
+
+ /**
+ * Adds a specified amount of time to a Date.
+ *
+ * @param date Date to which time is added
+ * @param field Date Field to which the Amount is added
+ * @param amount Amount to be Added
+ * @return New Date
+ */
+ public Date addTimetoDate(Date date, int field, int amount) {
+ java.util.Calendar c = java.util.Calendar.getInstance();
+ c.setTime(date);
+ c.add(field, amount);
+ return c.getTime();
+ }
+
+ /**
+ * Methods to parse Appointment to iCalendar according RFC 2445
+ *
+ * @param appointment to be converted to iCalendar
+ * @return iCalendar representation of the Appointment
+ * @throws Exception
+ */
+ public Calendar parseAppointmenttoCalendar(Appointment appointment) throws Exception {
+ String tzid = parseTimeZone(null, appointment.getOwner()).getID();
+
+ TimeZoneRegistry registry = TimeZoneRegistryFactory.getInstance().createRegistry();
+
+ net.fortuna.ical4j.model.TimeZone timeZone = registry.getTimeZone(tzid);
+ if (timeZone == null) {
+ throw new Exception("Unable to get time zone by id provided: " + tzid);
+ }
+
+ Calendar icsCalendar = new Calendar();
+ icsCalendar.getProperties().add(new ProdId("-//Events Calendar//Apache Openmeetings//EN"));
+ icsCalendar.getProperties().add(Version.VERSION_2_0);
+ icsCalendar.getProperties().add(CalScale.GREGORIAN);
+ icsCalendar.getComponents().add(timeZone.getVTimeZone());
+
+ DateTime start = new DateTime(appointment.getStart()), end = new DateTime(appointment.getEnd());
+
+ VEvent meeting = new VEvent(start, end, appointment.getTitle());
+ meeting = addVEventpropsfromAppointment(appointment, meeting);
+ icsCalendar.getComponents().add(meeting);
+
+ return icsCalendar;
+ }
+
+ /**
+ * Adds the Appointment Properties to the given VEvent
+ *
+ * @param appointment Appointment whose properties are taken
+ * @param meeting VEvent of the Appointment
+ * @return Updated VEvent
+ */
+ private static VEvent addVEventpropsfromAppointment(Appointment appointment, VEvent meeting) {
+
+ if (appointment.getLocation() != null) {
+ meeting.getProperties().add(new Location(appointment.getLocation()));
+ }
+
+ meeting.getProperties().add(new Description(appointment.getDescription()));
+ meeting.getProperties().add(new Sequence(0));
+ meeting.getProperties().add(Transp.OPAQUE);
+
+ String uid = appointment.getIcalId();
+ Uid ui = null;
+ if (uid == null || uid.length() < 1) {
+ UUID uuid = UUID.randomUUID();
+ appointment.setIcalId(uuid.toString());
+ ui = new Uid(uuid.toString());
+ } else {
+ ui = new Uid(uid);
+ }
+
+ meeting.getProperties().add(ui);
+
+ /* RRule rRule = null;
+ if (appointment.getIsDaily() != null) {
+ rRule = new RRule("FREQ=DAILY");
+ } else if (appointment.getIsWeekly() != null) {
+ rRule = new RRule("FREQ=WEEKLY");
+ } else if (appointment.getIsMonthly() != null) {
+ rRule = new RRule("FREQ=MONTHLY");
+ } else if (appointment.getIsYearly() != null) {
+ rRule = new RRule("FREQ=YEARLY");
+ }
+
+ if (rRule != null) {
+ meeting.getProperties().add(rRule);
+ }
+ */
+
+ if (appointment.getMeetingMembers() != null) {
+ for (MeetingMember meetingMember : appointment.getMeetingMembers()) {
+ Attendee attendee = new Attendee(URI.create("mailto:" +
+ meetingMember.getUser().getAddress().getEmail()));
+ attendee.getParameters().add(Role.REQ_PARTICIPANT);
+ attendee.getParameters().add(new Cn(meetingMember.getUser().getLogin()));
+ meeting.getProperties().add(attendee);
+ }
+ }
+ URI orgUri = URI.create("mailto:" + appointment.getOwner().getAddress().getEmail());
+ Attendee orgAtt = new Attendee(orgUri);
+ orgAtt.getParameters().add(Role.CHAIR);
+ Cn orgCn = new Cn(appointment.getOwner().getLogin());
+ orgAtt.getParameters().add(orgCn);
+ meeting.getProperties().add(orgAtt);
+
+ Organizer organizer = new Organizer(orgUri);
+ organizer.getParameters().add(orgCn);
+ meeting.getProperties().add(organizer);
+
+ return meeting;
+ }
+
+ /**
+ * Parses a List of Appointments into a VCALENDAR component.
+ *
+ * @param appointments List of Appointments for the Calendar
+ * @param ownerId Owner of the Appointments
+ * @return VCALENDAR representation of the Appointments
+ * @throws Exception
+ */
+ public Calendar parseAppointmentstoCalendar(List<Appointment> appointments, Long ownerId) throws Exception {
+ String tzid = parseTimeZone(null, userDao.get(ownerId)).getID();
+
+ TimeZoneRegistry registry = TimeZoneRegistryFactory.getInstance().createRegistry();
+
+ net.fortuna.ical4j.model.TimeZone timeZone = registry.getTimeZone(tzid);
+ if (timeZone == null) {
+ throw new Exception("Unable to get time zone by id provided: " + tzid);
+ }
+
+ Calendar icsCalendar = new Calendar();
+ icsCalendar.getProperties().add(new ProdId(PROD_ID));
+ icsCalendar.getProperties().add(Version.VERSION_2_0);
+ icsCalendar.getProperties().add(CalScale.GREGORIAN);
+ icsCalendar.getComponents().add(timeZone.getVTimeZone());
+
+ for (Appointment appointment : appointments) {
+ DateTime start = new DateTime(appointment.getStart()), end = new DateTime(appointment.getEnd());
+
+ VEvent meeting = new VEvent(start, end, appointment.getTitle());
+ meeting = addVEventpropsfromAppointment(appointment, meeting);
+ icsCalendar.getComponents().add(meeting);
+ }
+ return icsCalendar;
+ }
+}