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="[email protected]">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 &amp; 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 &amp; 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 "[email protected]".</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;
+       }
+}



Reply via email to