Java tutorial
/* * Copyright 2005 Open Source Applications Foundation * Copyright 2018 Ankush Mishra, Roberto Polli * * 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. * * */ package com.github.caldav4j; import com.github.caldav4j.exceptions.BadStatusException; import com.github.caldav4j.exceptions.CalDAV4JException; import com.github.caldav4j.exceptions.ResourceNotFoundException; import com.github.caldav4j.exceptions.ResourceOutOfDateException; import com.github.caldav4j.methods.*; import com.github.caldav4j.model.request.*; import com.github.caldav4j.model.response.CalendarDataProperty; import com.github.caldav4j.util.*; import net.fortuna.ical4j.model.Calendar; import net.fortuna.ical4j.model.Component; import net.fortuna.ical4j.model.ComponentList; import net.fortuna.ical4j.model.Date; import net.fortuna.ical4j.model.component.CalendarComponent; import net.fortuna.ical4j.model.component.VEvent; import net.fortuna.ical4j.model.component.VTimeZone; import net.fortuna.ical4j.model.property.CalScale; import net.fortuna.ical4j.model.property.ProdId; import net.fortuna.ical4j.model.property.Uid; import net.fortuna.ical4j.model.property.Version; import net.fortuna.ical4j.util.CompatibilityHints; import org.apache.http.Header; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpHead; import org.apache.jackrabbit.webdav.MultiStatus; 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.security.AclProperty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.ConnectException; import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.Random; /** * This class provides a high level API to a calendar collection on a CalDAV server. * * @author robipolli@gmail.com * * implements methods for * - create * - retrieve * - update * - delete * * calendars are retrieved in two ways * - by path (with get methods) * - by custom query (with search methods) * no customized queries should be public in this class */ public class CalDAVCollection extends CalDAVCalendarCollectionBase { private static final Logger log = LoggerFactory.getLogger(CalDAVCollection.class); // configuration settings public CalDAVCollection() { } /** * Creates a new CalDAVCalendar collection with the calendar collection root. * This is a convenience constructor which sets the host, based on the URI * provided, through {@link #getDefaultHttpHost(URI)}. It also sets * the methodfactory, through {@link #setMethodFactory(CalDAV4JMethodFactory)} * * @param uri The path to the collection */ public CalDAVCollection(String uri) { setCalendarCollectionRoot(uri); setMethodFactory(new CalDAV4JMethodFactory()); this.prodId = CalDAVConstants.PROC_ID_DEFAULT; } /** * Creates a new CalDAVCalendar collection with the specified parameters * * @param path The path to the collection * @param httpHost Host information for the CalDAV Server * @param methodFactory methodFactory to obtail HTTP methods from * @param prodId String identifying who creates the iCalendar objects */ public CalDAVCollection(String path, HttpHost httpHost, CalDAV4JMethodFactory methodFactory, String prodId) { setCalendarCollectionRoot(path); this.httpHost = httpHost; this.methodFactory = methodFactory; this.prodId = prodId; } //Configuration Methods /** * Returns the icalendar object which contains the event with the specified * UID. * * @param httpClient the httpClient which will make the request * @param uid The uniqueID of the event to find * @return the Calendar object containing the event with this UID * @throws CalDAV4JException if there was a problem, or if the resource could * not be found. * @deprecated use a less-specialized query */ public Calendar getCalendarForEventUID(HttpClient httpClient, String uid) throws CalDAV4JException { // implement it using a simplequery: here we don't need meta-data/tags return getCalDAVResourceForEventUID(httpClient, uid).getCalendar(); } /** * Gets an icalendar object by GET * * @param httpClient the httpClient which will make the request * @param icsRelativePath the path, relative to the collection path * @return the Calendar object at the specified path * @throws CalDAV4JException on error */ public Calendar getCalendar(HttpClient httpClient, String icsRelativePath) throws CalDAV4JException { return getCalDAVResource(httpClient, getAbsolutePath(icsRelativePath)).getCalendar(); } /** * Retrieve a single calendar by UID / COMPONENT using REPORT * @param httpClient the httpClient which will make the request * @param component Component to query * @param uid UID of the Calendar * @param recurrenceId If not null, then provides the recurrence ID * @return The Calendar with the given UID. null if not found * @throws CalDAV4JException on error */ public Calendar queryCalendar(HttpClient httpClient, String component, String uid, String recurrenceId) throws CalDAV4JException { String filter = String.format("%s : UID==%s", component, uid); if (recurrenceId != null) { filter = String.format("%s, RECURRENCE-ID==%s", filter, recurrenceId); } GenerateQuery gq = new GenerateQuery(component, filter); List<Calendar> cals = queryCalendars(httpClient, gq.generate()); switch (cals.size()) { case 1: return cals.get(0); case 0: return null; default: throw new CalDAV4JException("More than one calendar returned for uid " + uid); } } /** * Returns all Calendars which contain events which have instances who fall within * the two dates. Note that recurring events are NOT expanded. * * @param httpClient the httpClient which will make the request * @param beginDate the beginning of the date range. Must be a UTC date * @param endDate the end of the date range. Must be a UTC date. * @return a List of Calendars * @throws CalDAV4JException if there was a problem * * @deprecated should be implemented by query */ public List<Calendar> getEventResources(HttpClient httpClient, Date beginDate, Date endDate) throws CalDAV4JException { GenerateQuery gq = new GenerateQuery(); gq.setFilter("VEVENT"); gq.setTimeRange(beginDate, endDate); return queryCalendars(httpClient, gq.generate()); } /** * Delete every component with the given UID. As UID is unique in the * collection it should remove only one Calendar resource * * @param httpClient the httpClient which will make the request * @param component Component to remove * @param uid UID to delete * @throws CalDAV4JException on error * * TODO this method should be refined with recurrenceid */ public void delete(HttpClient httpClient, String component, String uid) throws CalDAV4JException { CalDAVResource resource = getCalDAVResourceByUID(httpClient, component, uid); Calendar calendar = resource.getCalendar(); ComponentList eventList = calendar.getComponents().getComponents(component); // get a list of components to remove List<Component> componentsToRemove = new ArrayList<>(); boolean hasOtherEvents = false; for (Object o : eventList) { CalendarComponent event = (CalendarComponent) o; String curUID = ICalendarUtils.getUIDValue(event); if (!uid.equals(curUID)) { hasOtherEvents = true; } else { componentsToRemove.add(event); } } // // remove from calendar the components with the given UID // and PUT the calendar // if (hasOtherEvents) { if (componentsToRemove.size() == 0) { throw new ResourceNotFoundException(ResourceNotFoundException.IdentifierType.UID, uid); } for (Component removeMe : componentsToRemove) { calendar.getComponents().remove(removeMe); } put(httpClient, calendar, UrlUtils.stripHost(resource.getResourceMetadata().getHref()), resource.getResourceMetadata().getETag()); return; } else { delete(httpClient, UrlUtils.stripHost(resource.getResourceMetadata().getHref())); } } /** * Creates a calendar at the specified path * @param httpClient the httpClient which will make the request * @throws CalDAV4JException on error */ public void createCalendar(HttpClient httpClient) throws CalDAV4JException { HttpMkCalendarMethod mkCalendarMethod = null; try { mkCalendarMethod = methodFactory.createMkCalendarMethod(getCalendarCollectionRoot()); HttpResponse response = httpClient.execute(getDefaultHttpHost(mkCalendarMethod.getURI()), mkCalendarMethod); if (!mkCalendarMethod.succeeded(response)) { MethodUtil.StatusToExceptions(mkCalendarMethod, response); } } catch (Exception e) { throw new CalDAV4JException("Trouble executing MKCalendar", e); } finally { if (mkCalendarMethod != null) mkCalendarMethod.reset(); } } /** * @param httpClient the httpClient which will make the request * @param calendar iCal body to place on the server * @param path Path to the new/old resource * @param etag ETag if updation of calendar has to take place. * @throws CalDAV4JException on error */ private void put(HttpClient httpClient, Calendar calendar, String path, String etag) throws CalDAV4JException { CalendarRequest cr = new CalendarRequest(); cr.addEtag(etag); cr.setIfMatch(true); cr.setCalendar(calendar); HttpPutMethod putMethod = methodFactory.createPutMethod(path, cr); try { HttpResponse response = httpClient.execute(getDefaultHttpHost(putMethod.getURI()), putMethod); int statusCode = response.getStatusLine().getStatusCode(); switch (statusCode) { case CalDAVStatus.SC_NO_CONTENT: case CalDAVStatus.SC_CREATED: break; case CalDAVStatus.SC_PRECONDITION_FAILED: throw new ResourceOutOfDateException("Etag was not matched: " + etag); default: throw new BadStatusException(statusCode, putMethod.getMethod(), path); } if (isCacheEnabled()) { Header h = putMethod.getFirstHeader("ETag"); String newEtag = null; if (h != null) { newEtag = h.getValue(); } else { newEtag = getETagbyMultiget(httpClient, path); } cache.putResource(new CalDAVResource(calendar, newEtag, putMethod.getURI().toString())); } } catch (ResourceOutOfDateException | BadStatusException e) { throw e; } catch (Exception e) { throw new CalDAV4JException("Problem executing put method", e); } finally { putMethod.reset(); } } /** * Adds a new Calendar with the given Component and VTimeZone to the collection. * * Tries to use the event UID followed by ".ics" as the name of the * resource, otherwise will use the UID followed by a random number and * ".ics" if the "UID.ics" already exists. * * @param httpClient the httpClient which will make the request * @param vevent The VEvent to put in the Calendar * * @param timezone The VTimeZone of the VEvent if it references one, * otherwise null * @throws CalDAV4JException on error * @return Returns the final UID to the new resource. */ public String add(HttpClient httpClient, CalendarComponent vevent, VTimeZone timezone) throws CalDAV4JException { Calendar calendar = new Calendar(); calendar.getProperties().add(new ProdId(prodId)); calendar.getProperties().add(Version.VERSION_2_0); calendar.getProperties().add(CalScale.GREGORIAN); if (timezone != null) { calendar.getComponents().add(timezone); } calendar.getComponents().add(vevent); return add(httpClient, calendar); } /** * Same as {@link #add(HttpClient, Calendar, boolean)}, with * {@code attemptRetry} as true * @param httpClient the httpClient which will make the request * @param c Calendar to Add * @return UID of added resource. * @throws CalDAV4JException on error * @see #add(HttpClient, Calendar, boolean) */ public String add(HttpClient httpClient, Calendar c) throws CalDAV4JException { return add(httpClient, c, true); } /** * Adds a calendar object to caldav collection using "UID.ics" as file name. * <br><br> * If {@code attemptRetry} is {@code true}, we attempts to retry again when * "UID.ics" already exists on server. It does so, by adding a random number * to the UID. The retry attempt only occurs when a server returns the HTTP * status 412 PRECONDITION_FAILED. * * @param httpClient the httpClient which will make the request * @param c Calendar to Add * @param attemptRetry Sets if the request should be retried in case of error. * @throws CalDAV4JException on error * @return Returns the final UID to the new resource. */ public String add(HttpClient httpClient, Calendar c, boolean attemptRetry) throws CalDAV4JException { Random random = new Random(); // // retry 3 times while caldav server returns PRECONDITION_FAILED // boolean didIt = false; String path = ""; Uid uid = null; for (int x = 0; x < 3 && !didIt; x++) { String resourceName = null; // Sets the UID if null. uid = ICalendarUtils.setUID(c); // change UID at second attempt if (x > 0) { uid.setValue(uid.getValue() + "-" + random.nextInt()); } HttpPutMethod putMethod = createPutMethodForNewResource(uid.getValue() + ".ics", c); HttpResponse response = null; try { response = httpClient.execute(getDefaultHttpHost(putMethod.getURI()), putMethod); if (isCacheEnabled() && putMethod.succeeded(response)) { String etag = UrlUtils.getHeaderPrettyValue(response, CalDAVConstants.HEADER_ETAG); if (etag == null) { etag = getETagbyMultiget(httpClient, putMethod.getURI().toString()); } CalDAVResource calDAVResource = new CalDAVResource(c, etag, putMethod.getURI().toString()); cache.putResource(calDAVResource); } } catch (Exception e) { throw new CalDAV4JException("Trouble executing PUT", e); } int statusCode = response.getStatusLine().getStatusCode(); switch (statusCode) { //Succeeded case CalDAVStatus.SC_CREATED: case CalDAVStatus.SC_NO_CONTENT: didIt = true; break; //Another calendar with the same UID exists. Thus, retry. case CalDAVStatus.SC_PRECONDITION_FAILED: if (attemptRetry) continue; default: MethodUtil.StatusToExceptions(putMethod, response); } } //for return uid.getValue(); } /** * Updates the resource containing the VEvent with the same UID as the given * VEvent with the given VEvent * * @param httpClient the httpClient which will make the request * @param vevent the vevent to update * @param timezone The VTimeZone of the VEvent if it references one, * otherwise null * @throws CalDAV4JException on error */ // TODO: Deal with SEQUENCE public void updateMasterEvent(HttpClient httpClient, VEvent vevent, VTimeZone timezone) throws CalDAV4JException { String uid = ICalendarUtils.getUIDValue(vevent); CalDAVResource resource = getCalDAVResourceByUID(httpClient, Component.VEVENT, uid); Calendar calendar = resource.getCalendar(); //let's find the master event first! VEvent originalVEvent = ICalendarUtils.getMasterEvent(calendar, uid); calendar.getComponents().remove(originalVEvent); calendar.getComponents().add(vevent); if (timezone != null) { VTimeZone originalVTimeZone = ICalendarUtils.getTimezone(calendar); if (originalVTimeZone != null) calendar.getComponents().remove(originalVTimeZone); calendar.getComponents().add(timezone); } put(httpClient, calendar, UrlUtils.stripHost(resource.getResourceMetadata().getHref()), resource.getResourceMetadata().getETag()); } /** * Get a CalDAVResource by UID * it tries * - first by a REPORT * - then by GET /path * @param httpClient the httpClient which will make the request * @param uid UID of the Event * @return CalDAVResource representing the Event * @throws CalDAV4JException on error * @deprecated This query is too specialized @see{getCalDAVResourceByUID()} */ private CalDAVResource getCalDAVResourceForEventUID(HttpClient httpClient, String uid) throws CalDAV4JException { return getCalDAVResourceByUID(httpClient, Component.VEVENT, uid); } /** * * It tries: * - first by a REPORT * - then by GET /path checking that UID=filename * TODO another strategy can be to * - first by GET /path and check UID * - else try by report * as the first case is the most common, I avoid overload the server with REPORT * @param httpClient the httpClient which will make the request * @param component Calendar Component * @param uid UID of retrieved component * @return a Caldav resource containing the component type with the given uid * @throws CalDAV4JException on error * @throws ResourceNotFoundException When resource is not found */ protected CalDAVResource getCalDAVResourceByUID(HttpClient httpClient, String component, String uid) throws CalDAV4JException, ResourceNotFoundException { //first check the cache! String href = cache.getHrefForEventUID(uid); CalDAVResource resource = null; if (href != null) { resource = getCalDAVResource(httpClient, UrlUtils.stripHost(href)); if (resource != null) { return resource; } } else { try { resource = getCalDAVResource(httpClient, getAbsolutePath(uid + ".ics")); if (uid.equals(ICalendarUtils.getUIDValue(ICalendarUtils.getFirstComponent(resource, component)))) { return resource; } } catch (Exception e) { // resource not found: continue... resource = null; } } // then check by calendar query GenerateQuery gq; gq = new GenerateQuery(null, component + " : UID==" + uid); List<CalDAVResource> cr; cr = getCalDAVResources(httpClient, gq.generate()); try { resource = cr.get(0); if (uid.equals(ICalendarUtils.getUIDValue(ICalendarUtils.getFirstComponent(resource, component)))) { cache.putResource(resource); return resource; } else { throw new Exception(); } } catch (Exception e) { throw new ResourceNotFoundException(ResourceNotFoundException.IdentifierType.UID, uid); } } /** * GET the resource at the given path. Will check the cache first, and compare that to the * latest etag obtained using a HEAD request. * * if calendar resource in cache is void, retrieve directly from server (avoid get etag only) * @param httpClient the httpClient which will make the request * @param path to resource * @return CalDAVResource * @throws CalDAV4JException on error */ //FIXME testme protected CalDAVResource getCalDAVResource(HttpClient httpClient, String path) throws CalDAV4JException { CalDAVResource calDAVResource = cache.getResource(getHref(path)); if (calDAVResource == null || calDAVResource.getCalendar() == null) { return getCalDAVResourceFromServer(httpClient, path); } else { String currentEtag = getETag(httpClient, path); return getCalDAVResource(httpClient, path, currentEtag); } } /** * Gets the resource for the given href. Will check the cache first, and if a cached * version exists that has the etag provided it will be returned. Otherwise, it goes * to the server for the resource. * * @param httpClient the httpClient which will make the request * @param path Path to Resource * @param currentEtag Current Etag of the resource * @return Corresponding CalDAVResource * @throws CalDAV4JException on error */ protected CalDAVResource getCalDAVResource(HttpClient httpClient, String path, String currentEtag) throws CalDAV4JException { //first try getting from the cache CalDAVResource calDAVResource = cache.getResource(getHref(path)); //ok, so we got the resource...but has it been changed recently? if (calDAVResource != null && calDAVResource.getCalendar() != null) { // FIXME calDAVResource's calendar should not be null! String cachedEtag = calDAVResource.getResourceMetadata().getETag(); if (cachedEtag.equals(currentEtag)) { return calDAVResource; } } //either the etag was old, or it wasn't in the cache so let's get it //from the server return getCalDAVResourceFromServer(httpClient, path); } /** * Gets a CalDAVResource (not a mere timezone) from the server - in other words DOES NOT check the cache. * Adds the new resource to the cache, replacing any pre-existing version. * On Google Caldav Server, this method skips VTIMEZONE resources as they are used as tombstones * * @param httpClient the httpClient which will make the request * @param path path to resource * @return CalDAVResource * @throws CalDAV4JException on error */ protected CalDAVResource getCalDAVResourceFromServer(HttpClient httpClient, String path) throws CalDAV4JException { CalDAVResource calDAVResource = null; HttpGetMethod getMethod = getMethodFactory().createGetMethod(path); try { HttpResponse response = httpClient.execute(getDefaultHttpHost(getMethod.getURI()), getMethod); if (response.getStatusLine().getStatusCode() != CalDAVStatus.SC_OK) { MethodUtil.StatusToExceptions(getMethod, response); throw new BadStatusException(getMethod, response); } String href = getHref(path); String etag = response.getFirstHeader(CalDAVConstants.HEADER_ETAG).getValue(); Calendar calendar = null; if (isTolerantParsing()) { CompatibilityHints.setHintEnabled(CompatibilityHints.KEY_RELAXED_UNFOLDING, false); CompatibilityHints.setHintEnabled(CompatibilityHints.KEY_RELAXED_PARSING, false); CompatibilityHints.setHintEnabled(CompatibilityHints.KEY_OUTLOOK_COMPATIBILITY, true); CompatibilityHints.setHintEnabled(CompatibilityHints.KEY_RELAXED_VALIDATION, false); } calendar = getMethod.getResponseBodyAsCalendar(response); calDAVResource = new CalDAVResource(); calDAVResource.setCalendar(calendar); calDAVResource.getResourceMetadata().setETag(etag); calDAVResource.getResourceMetadata().setHref(href); cache.putResource(calDAVResource); } catch (BadStatusException e) { throw e; } catch (Exception e) { throw new CalDAV4JException("Problem executing get method", e); } finally { getMethod.reset(); } return calDAVResource; } /** * Deletes a resource at a given path. Also removes it from cache. * * @param httpClient HTTPClient making the request * @param path Path to resource * @throws CalDAV4JException on error */ public void delete(HttpClient httpClient, String path) throws CalDAV4JException { HttpDeleteMethod deleteMethod = new HttpDeleteMethod(path); HttpResponse response = null; try { response = httpClient.execute(httpHost, deleteMethod); } catch (Exception e) { throw new CalDAV4JException("Problem executing delete method", e); } if (response == null || response.getStatusLine().getStatusCode() != CalDAVStatus.SC_NO_CONTENT) { MethodUtil.StatusToExceptions(deleteMethod, response); throw new CalDAV4JException("Problem executing delete method"); } if (isCacheEnabled()) cache.removeResource(getHref(path)); } /** * Replace double slashes * @param relativePath Relative Path, who's absolute Path is to be returned. * @return a path with double slashes removed */ protected String getAbsolutePath(String relativePath) { return (getCalendarCollectionRoot() + relativePath).replaceAll("/+", "/"); } /** * Retrieve etags using HEAD /path/to/resource.ics * * @param httpClient the httpClient which will make the request * @param path Path to the Calendar * @return ETag for calendar * @throws CalDAV4JException on error */ protected String getETag(HttpClient httpClient, String path) throws CalDAV4JException { HttpHead headMethod = new HttpHead(path); try { HttpResponse response = httpClient.execute(getDefaultHttpHost(headMethod.getURI()), headMethod); int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == CalDAVStatus.SC_NOT_FOUND) { throw new ResourceNotFoundException(ResourceNotFoundException.IdentifierType.PATH, path); } if (statusCode != CalDAVStatus.SC_OK) { throw new BadStatusException(headMethod, response); } } catch (IOException e) { throw new CalDAV4JException( "Problem executing HEAD method on: " + getDefaultHttpHost(headMethod.getURI()), e); } Header h = headMethod.getFirstHeader(CalDAVConstants.HEADER_ETAG); String etag = null; if (h != null) { etag = h.getValue(); } else etag = getETagbyMultiget(httpClient, path); return etag; } /** * Retrieves the Etag of the resource pointed by <code>path</code> by using a Multiget Query. * * @param httpClient Client which makes the request. * @param path Path to the Calendar Resource * @return ETag Value of the Resource * @throws CalDAV4JException on error */ protected String getETagbyMultiget(HttpClient httpClient, String path) throws CalDAV4JException { String etag = null; DavPropertyNameSet props = new DavPropertyNameSet(); props.add(DavPropertyName.GETETAG); CalendarMultiget query = new CalendarMultiget(props, null, false, false); query.addHref(path); MultiStatus multiStatus = getMultiStatusforQuery(httpClient, query); for (MultiStatusResponse response : multiStatus.getResponses()) { if (response.getStatus()[0].getStatusCode() == CalDAVStatus.SC_OK) { etag = CalendarDataProperty.getEtagfromResponse(response); } } return etag; } /** * Useful for retrieving a list of UIDs of all events * * @param httpClient the httpClient which will make the request * @param componentName Component whose property is to be returned * @param propertyName Property whose value is to be returned * @param query Query to specify the Calendars * @return a list of property values of events. * @throws CalDAV4JException on error * @deprecated maybe create a method in ICalendarUtils or an "asString()" method */ protected List<String> getComponentProperty(HttpClient httpClient, String componentName, String propertyName, CalendarQuery query) throws CalDAV4JException { List<String> propertyList = new ArrayList<>(); List<Calendar> calendarList = getCalendarLight(httpClient, query); for (Calendar cal : calendarList) { propertyList.add(ICalendarUtils.getPropertyValue(cal.getComponent(componentName), propertyName)); } return propertyList; } /** * Return a list of components using REPORT * @param query Query to return the calendars for. * @param httpClient the httpClient which will make the request * @return a new Calendar list with no elements if 0 * @throws CalDAV4JException on error */ public List<Calendar> queryCalendars(HttpClient httpClient, CalendarQuery query) throws CalDAV4JException { List<Calendar> list = new ArrayList<>(); for (CalDAVResource cr : getCalDAVResources(httpClient, query)) { list.add(cr.getCalendar()); } return list; } /** * Return a list of components using REPORT without * passing through the CaldavResource Cache * * @param httpClient the httpClient which will make the request * @param query Query to get the Calendar from. * @return List of Calendars * @throws CalDAV4JException on error * @deprecated This is still a proposed feature */ public List<Calendar> getCalendarLight(HttpClient httpClient, CalendarQuery query) throws CalDAV4JException { List<Calendar> list = new ArrayList<>(); if (isCacheEnabled()) { query.setCalendarDataProp(null); } HttpCalDAVReportMethod reportMethod = null; try { reportMethod = methodFactory.createCalDAVReportMethod(getCalendarCollectionRoot(), query, CalDAVConstants.DEPTH_1); HttpResponse httpResponse = httpClient.execute(getDefaultHttpHost(reportMethod.getURI()), reportMethod); MultiStatusResponse[] set = reportMethod.getResponseBodyAsMultiStatus(httpResponse).getResponses(); for (MultiStatusResponse response : set) { String etag = CalendarDataProperty.getEtagfromResponse(response); if (isCacheEnabled()) { CalDAVResource resource = getCalDAVResource(httpClient, UrlUtils.stripHost(response.getHref()), etag); list.add(resource.getCalendar()); // XXX check if getCalDAVResource does its caching job cache.putResource(resource); } else { Calendar cal = CalendarDataProperty.getCalendarfromResponse(response); if (cal != null) list.add(cal); } } } catch (Exception he) { throw new CalDAV4JException("Problem executing method", he); } finally { if (reportMethod != null) reportMethod.reset(); } return list; } /** * Get Responses for a specific ReportMethod Query * @param httpClient Client which makes the request. * @param query Query for the Report Method to execute. * @return MultiStatus Response for the Query * @throws CalDAV4JException on error */ public MultiStatus getMultiStatusforQuery(HttpClient httpClient, CalDAVReportRequest query) throws CalDAV4JException { HttpCalDAVReportMethod reportMethod = null; try { reportMethod = methodFactory.createCalDAVReportMethod(getCalendarCollectionRoot(), query, CalDAVConstants.DEPTH_1); HttpResponse response = httpClient.execute(getDefaultHttpHost(reportMethod.getURI()), reportMethod); if (reportMethod.succeeded(response)) return reportMethod.getResponseBodyAsMultiStatus(response); } catch (Exception he) { throw new CalDAV4JException("Problem executing method", he); } finally { if (reportMethod != null) reportMethod.reset(); } return null; } /** * Return a list of caldav resources. * All other methods should use this one * * The use of caching changes the behavior of this method. * if cache is not enable, returns a list of CalDAVResource parsed from the response * if cache is enabled, foreach HREF returned by server: * - retrieve the resource using getCaldavReource(client, string), this method checks cache * * @param httpClient the httpClient which will make the request * @param query Query to get the CalDAV resources for * @return List of CalDAVResource's * @throws CalDAV4JException on error */ protected List<CalDAVResource> getCalDAVResources(HttpClient httpClient, CalendarQuery query) throws CalDAV4JException { boolean usingCache = isCacheEnabled(); if (usingCache) { query.setCalendarDataProp(null); log.debug("Using cache, so I am removing calendar data"); } log.trace("Executing query: " + GenerateQuery.printQuery(query)); HttpCalDAVReportMethod reportMethod = null; List<CalDAVResource> list = new ArrayList<>(); try { reportMethod = methodFactory.createCalDAVReportMethod(getCalendarCollectionRoot(), query, CalDAVConstants.DEPTH_1); HttpResponse httpResponse = httpClient.execute(getDefaultHttpHost(reportMethod.getURI()), reportMethod); log.trace("Parsing response.. "); MultiStatusResponse[] responses = reportMethod.getResponseBodyAsMultiStatus(httpResponse) .getResponses(); for (MultiStatusResponse response : responses) { String etag = CalendarDataProperty.getEtagfromResponse(response); if (usingCache) { CalDAVResource resource = getCalDAVResource(httpClient, UrlUtils.stripHost(response.getHref()), etag); list.add(resource); cache.putResource(resource); } else { if (response != null) { list.add(new CalDAVResource(response)); } } } } catch (ConnectException connEx) { throw new CalDAV4JException("Can't connecto to " + getDefaultHttpHost(reportMethod.getURI()), connEx.getCause()); } catch (Exception he) { throw new CalDAV4JException("Problem executing method", he); } finally { if (reportMethod != null) reportMethod.reset(); } return list; } // // MultiGet queries // /** * * @param httpClient the httpClient which will make the request * @param query Multiget Query to get. * @return Returns a list of Calendar Components represented by the query. * @throws CalDAV4JException on error */ protected List<Calendar> getComponentByMultiget(HttpClient httpClient, CalendarMultiget query) throws CalDAV4JException { if (isCacheEnabled()) { query.setCalendarDataProp(null); } HttpCalDAVReportMethod reportMethod = null; List<Calendar> list = new ArrayList<>(); try { reportMethod = methodFactory.createCalDAVReportMethod(getCalendarCollectionRoot(), query, CalDAVConstants.DEPTH_1); HttpResponse httpResponse = httpClient.execute(getDefaultHttpHost(reportMethod.getURI()), reportMethod); MultiStatusResponse[] e = reportMethod.getResponseBodyAsMultiStatus(httpResponse).getResponses(); for (MultiStatusResponse response : e) { CalDAVResource resource = null; if (isCacheEnabled()) { String etag = CalendarDataProperty.getEtagfromResponse(response); try { resource = getCalDAVResource(httpClient, UrlUtils.stripHost(response.getHref()), etag); list.add(resource.getCalendar()); } catch (Exception e1) { log.warn("Unable to get CalDAVResource for etag: " + etag); e1.printStackTrace(); } } else { list.add(CalendarDataProperty.getCalendarfromResponse(response)); } } } catch (Exception he) { throw new CalDAV4JException("Problem executing method", he); } finally { if (reportMethod != null) reportMethod.reset(); } return list; } /** * Implementing calendar multiget with Properties: getetag, calendar-data * * @see <a href="http://tools.ietf.org/html/rfc4791#section-7.9">RFC 4791 Section 7.9</a> * * @author rpolli, the_antimist * @param httpClient the httpClient which will make the request * @param calendarUris URI's for Multiget * @return List of Calendars based on the uris. * @throws CalDAV4JException on error */ public List<Calendar> multigetCalendarUris(HttpClient httpClient, List<String> calendarUris) throws CalDAV4JException { // first create the calendar query CalendarMultiget query = new CalendarMultiget(); CalendarData calendarData = new CalendarData(); query.addProperty(CalDAVConstants.DNAME_GETETAG); query.setCalendarDataProp(calendarData); query.setHrefs(calendarUris); return getComponentByMultiget(httpClient, query); } /** * Executes a FreeBusyQuery Report as based on * <a href="https://tools.ietf.org/html/rfc4791#section-7.10">RFC 4791 Section 7.10</a> * with a Depth of 1. * * @param httpClient the httpClient which will make the request * @param timeRange timerange to check * @return VFREEBUSY Calendar * @throws CalDAV4JException on error */ public Calendar getFreeBusyQueryCalendar(HttpClient httpClient, TimeRange timeRange) throws CalDAV4JException { return this.getFreeBusyQueryCalendar(httpClient, new FreeBusyQuery(timeRange)); } /** * Executes a FreeBusyQuery Report as based on * <a href="https://tools.ietf.org/html/rfc4791#section-7.10">RFC 4791 Section 7.10</a> * with a Depth of 1. * * @param httpClient the httpClient which will make the request * @param freeBusyQuery Query to execute * @return VFREEBUSY Calendar * @throws CalDAV4JException on error */ public Calendar getFreeBusyQueryCalendar(HttpClient httpClient, FreeBusyQuery freeBusyQuery) throws CalDAV4JException { HttpCalDAVReportMethod reportMethod = null; try { reportMethod = methodFactory.createCalDAVReportMethod(getCalendarCollectionRoot(), freeBusyQuery, CalDAVConstants.DEPTH_1); HttpResponse response = httpClient.execute(getDefaultHttpHost(reportMethod.getURI()), reportMethod); if (reportMethod.succeeded(response)) return reportMethod.getResponseBodyAsCalendar(response); } catch (Exception he) { throw new CalDAV4JException("Problem executing method", he); } finally { if (reportMethod != null) reportMethod.reset(); } return null; } // // HEAD method, useful for testing connection // /** * Uses the HTTP HEAD Method to check if the connection is possible. * @param httpClient HTTPClient to make the request * @return StatusCode * @throws CalDAV4JException when Status is not {@link CalDAVStatus#SC_OK} */ public int testConnection(HttpClient httpClient) throws CalDAV4JException { HttpHead method = new HttpHead(getCalendarCollectionRoot()); HttpResponse response = null; try { response = httpClient.execute(getDefaultHttpHost(method.getURI()), method); } catch (Exception e) { throw new CalDAV4JException(e.getMessage(), new Throwable(e.getCause())); } switch (response.getStatusLine().getStatusCode()) { case CalDAVStatus.SC_OK: break; default: throw new BadStatusException(response.getStatusLine().getStatusCode(), method.getMethod(), getCalendarCollectionRoot()); } return response.getStatusLine().getStatusCode(); } // // manage ACL TODO // /** * Uses PROPFIND to return the list of Aces at Calendar Collection Root. * @param httpClient HTTPClient making the request * @return Returns the list of ACL Properties Aces * @throws CalDAV4JException on error */ public List<AclProperty.Ace> getAces(HttpClient httpClient) throws CalDAV4JException { return getAces(httpClient, null); } /** * Uses PROPFIND to return the list of Aces at the path. * * @param httpClient HTTPClient making the request * @param path Path to Resource * @return Returns the list of ACL Properties Aces * @throws CalDAV4JException on error */ public List<AclProperty.Ace> getAces(HttpClient httpClient, String path) throws CalDAV4JException { DavPropertyNameSet propfind = new DavPropertyNameSet(); propfind.add(CalDAVConstants.DNAME_ACL); HttpPropFindMethod method = null; try { method = methodFactory.createPropFindMethod( getCalendarCollectionRoot() + UrlUtils.defaultString(path, ""), propfind, CalDAVConstants.DEPTH_1); HttpResponse response = httpClient.execute(getDefaultHttpHost(method.getURI()), method); if (method.succeeded(response)) return method.getAces(response, method.getURI().toString()); else { MethodUtil.StatusToExceptions(method, response); return null; } } catch (Exception e) { throw new CalDAV4JException("Error in PROPFIND " + getCalendarCollectionRoot(), e); } finally { if (method != null) method.reset(); } } /** * Used to Set the Aces at the given path. * @param client Client making the request. * @param aces Aces to set. * @param path Path to Resource * @throws CalDAV4JException on error. */ public void setAces(HttpClient client, AclProperty.Ace[] aces, String path) throws CalDAV4JException { HttpAclMethod method = null; try { method = methodFactory.createAclMethod(getCalendarCollectionRoot() + UrlUtils.defaultString(path, ""), new AclProperty(aces)); HttpResponse response = client.execute(getDefaultHttpHost(method.getURI()), method); int status = response.getStatusLine().getStatusCode(); switch (status) { case CalDAVStatus.SC_OK: break; case CalDAVStatus.SC_NOT_FOUND: throw new ResourceNotFoundException(ResourceNotFoundException.IdentifierType.PATH, method.getURI().toString()); case CalDAVStatus.SC_UNAUTHORIZED: default: throw new BadStatusException(status, method.getMethod(), getCalendarCollectionRoot()); } } catch (IOException e) { throw new CalDAV4JException("Error in ACL " + getCalendarCollectionRoot(), e); } finally { if (method != null) method.reset(); } } } //end of class