Caldav: implement time-range request

git-svn-id: http://svn.code.sf.net/p/davmail/code/trunk@1253 3d1905a2-6b24-0410-a738-b14d5a86fcbd
This commit is contained in:
mguessan 2010-07-22 23:10:24 +00:00
parent 87aa40b7d1
commit e4460e159f
7 changed files with 240 additions and 9 deletions

View File

@ -687,7 +687,7 @@ public class CaldavConnection extends AbstractConnection {
appendEventsResponses(response, request, events);
} else {
// TODO: handle contacts ?
events = session.getAllEvents(request.getFolderPath());
events = session.searchEvents(request.getFolderPath(), request.timeRangeStart, request.timeRangeEnd);
appendEventsResponses(response, request, events);
}
@ -1201,6 +1201,8 @@ public class CaldavConnection extends AbstractConnection {
protected final HashSet<String> properties = new HashSet<String>();
protected HashSet<String> hrefs;
protected boolean isMultiGet;
protected String timeRangeStart;
protected String timeRangeEnd;
protected CaldavRequest(String command, String path, Map<String, String> headers, String body) throws IOException {
this.command = command;
@ -1376,6 +1378,9 @@ public class CaldavConnection extends AbstractConnection {
} else if ("calendar-multiget".equals(currentElement)
|| "addressbook-multiget".equals(currentElement)) {
isMultiGet = true;
} else if ("time-range".equals(currentElement)) {
timeRangeStart = streamReader.getAttributeValue(null, "start");
timeRangeEnd = streamReader.getAttributeValue(null, "end");
} else if (inProperties) {
properties.add(currentElement);
}

View File

@ -187,7 +187,7 @@ public abstract class ExchangeSession {
LOGGER.debug("Session " + this + " created");
}
protected static String formatSearchDate(Date date) {
public static String formatSearchDate(Date date) {
SimpleDateFormat dateFormatter = new SimpleDateFormat(YYYY_MM_DD_HH_MM_SS, Locale.ENGLISH);
dateFormatter.setTimeZone(GMT_TIMEZONE);
return dateFormatter.format(date);
@ -603,7 +603,10 @@ public abstract class ExchangeSession {
public abstract MessageList searchMessages(String folderName, Set<String> attributes, Condition condition) throws IOException;
protected enum Operator {
Or, And, Not, IsEqualTo, IsGreaterThan, IsGreaterThanOrEqualTo, IsLessThan, IsNull, IsTrue, IsFalse,
Or, And, Not, IsEqualTo,
IsGreaterThan, IsGreaterThanOrEqualTo,
IsLessThan, IsLowerThanOrEqualTo,
IsNull, IsTrue, IsFalse,
Like, StartsWith, Contains
}
@ -796,6 +799,15 @@ public abstract class ExchangeSession {
*/
public abstract Condition lt(String attributeName, String value);
/**
* Lower than or equals condition.
*
* @param attributeName logical Exchange attribute name
* @param value attribute value
* @return condition
*/
public abstract Condition lte(String attributeName, String value);
/**
* Contains condition.
*
@ -2526,13 +2538,51 @@ public abstract class ExchangeSession {
dateCondition = gt("dtstart", formatSearchDate(cal.getTime()));
}
return searchEvents(folderPath, dateCondition);
}
/**
* Search events between start and end.
*
* @param folderPath Exchange folder path
* @param timeRangeStart date range start in zulu format
* @param timeRangeEnd date range start in zulu format
* @return list of calendar events
* @throws IOException on error
*/
public List<Event> searchEvents(String folderPath, String timeRangeStart, String timeRangeEnd) throws IOException {
try {
SimpleDateFormat parser = getZuluDateFormat();
ExchangeSession.MultiCondition andCondition = and();
if (timeRangeStart != null) {
andCondition.add(gte("dtstart", formatSearchDate(parser.parse(timeRangeStart))));
}
if (timeRangeEnd != null) {
andCondition.add(lte("dtend", formatSearchDate(parser.parse(timeRangeEnd))));
}
andCondition.add(equals("instancetype", 0));
return searchEvents(folderPath, ITEM_PROPERTIES, andCondition);
} catch (ParseException e) {
throw new IOException(e);
}
}
/**
* Search calendar events in provided folder.
*
* @param folderPath Exchange folder path
* @param dateCondition date filter
* @return list of calendar events
* @throws IOException on error
*/
public List<Event> searchEvents(String folderPath, Condition dateCondition) throws IOException {
Condition privateCondition = null;
if (isSharedFolder(folderPath)) {
LOGGER.debug("Shared or public calendar: exclude private events");
privateCondition = equals("sensitivity", 0);
}
// instancetype 0 single appointment / 1 master recurring appointment
return searchEvents(folderPath, ITEM_PROPERTIES,
and(or(isNull("instancetype"),
equals("instancetype", 1),
@ -2541,7 +2591,6 @@ public abstract class ExchangeSession {
privateCondition));
}
/**
* Search calendar events or messages in provided folder matching the search query.
*
@ -2551,7 +2600,7 @@ public abstract class ExchangeSession {
* @return list of calendar messages as Event objects
* @throws IOException on error
*/
protected abstract List<Event> searchEvents(String folderPath, Set<String> attributes, Condition condition) throws IOException;
public abstract List<Event> searchEvents(String folderPath, Set<String> attributes, Condition condition) throws IOException;
/**
* convert vcf extension to EML.

View File

@ -398,6 +398,7 @@ public class DavExchangeSession extends ExchangeSession {
operatorMap.put(Operator.IsEqualTo, " = ");
operatorMap.put(Operator.IsGreaterThanOrEqualTo, " >= ");
operatorMap.put(Operator.IsGreaterThan, " > ");
operatorMap.put(Operator.IsLowerThanOrEqualTo, " <= ");
operatorMap.put(Operator.IsLessThan, " < ");
operatorMap.put(Operator.Like, " like ");
operatorMap.put(Operator.IsNull, " is null");
@ -516,6 +517,11 @@ public class DavExchangeSession extends ExchangeSession {
return new AttributeCondition(attributeName, Operator.IsGreaterThanOrEqualTo, value);
}
@Override
public Condition lte(String attributeName, String value) {
return new AttributeCondition(attributeName, Operator.IsLowerThanOrEqualTo, value);
}
@Override
public Condition lt(String attributeName, String value) {
return new AttributeCondition(attributeName, Operator.IsLessThan, value);
@ -1141,7 +1147,7 @@ public class DavExchangeSession extends ExchangeSession {
}
@Override
protected List<ExchangeSession.Event> searchEvents(String folderPath, Set<String> attributes, Condition condition) throws IOException {
public List<ExchangeSession.Event> searchEvents(String folderPath, Set<String> attributes, Condition condition) throws IOException {
List<ExchangeSession.Event> events = new ArrayList<ExchangeSession.Event>();
MultiStatusResponse[] responses = searchItems(folderPath, attributes, and(isFalse("isfolder"), isFalse("ishidden"), condition), FolderQueryTraversal.Shallow);
for (MultiStatusResponse response : responses) {

View File

@ -166,6 +166,7 @@ public class Field {
createField(SCHEMAS_EXCHANGE, "permanenturl");
createField(URN_SCHEMAS_CALENDAR, "instancetype"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:instancetype/Integer
createField(URN_SCHEMAS_CALENDAR, "dtstart"); // 0x10C3 SystemTime
createField(URN_SCHEMAS_CALENDAR, "dtend"); // 0x10C4 SystemTime
createField(SCHEMAS_EXCHANGE, "sensitivity"); // PR_SENSITIVITY 0x0036 Integer
createField(URN_SCHEMAS_CALENDAR, "timezoneid"); // DistinguishedPropertySetType.PublicStrings/urn:schemas:calendar:timezoneid/Integer
createField("processed", 0x65e8, PropertyType.Boolean);// PR_MESSAGE_PROCESSED

View File

@ -439,6 +439,11 @@ public class EwsExchangeSession extends ExchangeSession {
return new AttributeCondition(attributeName, Operator.IsGreaterThanOrEqualTo, value);
}
@Override
public Condition lte(String attributeName, String value) {
return new AttributeCondition(attributeName, Operator.IsLowerThanOrEqualTo, value);
}
@Override
public Condition lt(String attributeName, String value) {
return new AttributeCondition(attributeName, Operator.IsLessThan, value);
@ -906,7 +911,7 @@ public class EwsExchangeSession extends ExchangeSession {
}
@Override
protected List<ExchangeSession.Event> searchEvents(String folderPath, Set<String> attributes, Condition condition) throws IOException {
public List<ExchangeSession.Event> searchEvents(String folderPath, Set<String> attributes, Condition condition) throws IOException {
List<ExchangeSession.Event> events = new ArrayList<ExchangeSession.Event>();
List<EWSMethod.Item> responses = searchItems(folderPath, attributes,
condition,

View File

@ -46,6 +46,7 @@ public class Field {
FIELD_MAP.put("permanenturl", new ExtendedFieldURI(0x670E, ExtendedFieldURI.PropertyType.String)); //PR_FLAT_URL_NAME
FIELD_MAP.put("instancetype", new ExtendedFieldURI(ExtendedFieldURI.DistinguishedPropertySetType.PublicStrings, "urn:schemas:calendar:instancetype"));
FIELD_MAP.put("dtstart", new ExtendedFieldURI(0x10C3, ExtendedFieldURI.PropertyType.SystemTime));
FIELD_MAP.put("dtstart", new ExtendedFieldURI(0x10C4, ExtendedFieldURI.PropertyType.SystemTime));
FIELD_MAP.put("mimeContent", new UnindexedFieldURI("item:MimeContent"));

View File

@ -0,0 +1,164 @@
/*
* DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
* Copyright (C) 2010 Mickael Guessant
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package davmail.caldav;
import davmail.AbstractDavMailTestCase;
import davmail.DavGateway;
import davmail.Settings;
import davmail.exchange.ExchangeSession;
import davmail.exchange.ExchangeSessionFactory;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.MultiStatus;
import org.apache.jackrabbit.webdav.MultiStatusResponse;
import org.apache.jackrabbit.webdav.client.methods.DavMethodBase;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* Test Caldav listener.
*/
public class TestCaldav extends AbstractDavMailTestCase {
class SearchReportMethod extends DavMethodBase {
SearchReportMethod(String path, String stringContent) throws UnsupportedEncodingException {
this(path, stringContent.getBytes("UTF-8"));
}
SearchReportMethod(String path, final byte[] content) {
super(path);
setRequestEntity(new RequestEntity() {
public boolean isRepeatable() {
return true;
}
public void writeRequest(OutputStream outputStream) throws IOException {
outputStream.write(content);
}
public long getContentLength() {
return content.length;
}
public String getContentType() {
return "text/xml;charset=UTF-8";
}
});
}
@Override
public String getName() {
return "REPORT";
}
@Override
protected boolean isSuccess(int statusCode) {
return statusCode == HttpStatus.SC_MULTI_STATUS;
}
}
HttpClient httpClient;
@Override
public void setUp() throws IOException {
super.setUp();
if (httpClient == null) {
// start gateway
DavGateway.start();
httpClient = new HttpClient();
HostConfiguration hostConfig = httpClient.getHostConfiguration();
URI httpURI = new URI("http://localhost:" + Settings.getProperty("davmail.caldavPort"), true);
hostConfig.setHost(httpURI);
AuthScope authScope = new AuthScope(null, -1);
httpClient.getState().setCredentials(authScope, new NTCredentials(Settings.getProperty("davmail.username"), Settings.getProperty("davmail.password"), "", ""));
}
if (session == null) {
session = ExchangeSessionFactory.getInstance(Settings.getProperty("davmail.username"), Settings.getProperty("davmail.password"));
}
}
public void testGetRoot() throws IOException {
GetMethod method = new GetMethod("/");
httpClient.executeMethod(method);
assertEquals(HttpStatus.SC_OK, method.getStatusCode());
}
public void testGetUserRoot() throws IOException {
GetMethod method = new GetMethod("/users/" + session.getEmail() + '/');
httpClient.executeMethod(method);
assertEquals(HttpStatus.SC_OK, method.getStatusCode());
}
public void testGetCalendar() throws IOException {
GetMethod method = new GetMethod("/users/" + session.getEmail() + "/calendar/");
httpClient.executeMethod(method);
assertEquals(HttpStatus.SC_OK, method.getStatusCode());
}
public void testReportCalendar() throws IOException, DavException {
SimpleDateFormat formatter = ExchangeSession.getZuluDateFormat();
Calendar cal = Calendar.getInstance();
Date end = cal.getTime();
cal.add(Calendar.MONTH, -1);
Date start = cal.getTime();
StringBuilder buffer = new StringBuilder();
buffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
buffer.append("<C:calendar-query xmlns:C=\"urn:ietf:params:xml:ns:caldav\" xmlns:D=\"DAV:\">");
buffer.append("<D:prop>");
buffer.append("<C:calendar-data/>");
buffer.append("</D:prop>");
buffer.append("<C:comp-filter name=\"VCALENDAR\">");
buffer.append("<C:comp-filter name=\"VEVENT\">");
buffer.append("<C:time-range start=\"").append(formatter.format(start)).append("\" end=\"").append(formatter.format(end)).append("\"/>");
buffer.append("</C:comp-filter>");
buffer.append("</C:comp-filter>");
buffer.append("<C:filter>");
buffer.append("</C:filter>");
buffer.append("</C:calendar-query>");
SearchReportMethod method = new SearchReportMethod("/users/" + session.getEmail() + "/calendar/",buffer.toString());
httpClient.executeMethod(method);
assertEquals(HttpStatus.SC_MULTI_STATUS, method.getStatusCode());
MultiStatus multiStatus = method.getResponseBodyAsMultiStatus();
MultiStatusResponse[] responses = multiStatus.getResponses();
Set<String> ITEM_PROPERTIES = new HashSet<String>();
ITEM_PROPERTIES.add("instancetype");
List<ExchangeSession.Event> events = session.searchEvents("/users/" + session.getEmail() + "/calendar/", ITEM_PROPERTIES,
session.and(
session.and(
session.gt("dtstart", session.formatSearchDate(start)),
session.lt("dtend", session.formatSearchDate(end))
)
, session.or(session.equals("instancetype", 1), session.equals("instancetype", 0))
)
);
assertEquals(events.size(), responses.length);
}
}