1
0
mirror of https://github.com/moparisthebest/davmail synced 2024-12-14 11:42:23 -05:00

Caldav: major refactoring, move all ICS methods to Event inner class

git-svn-id: http://svn.code.sf.net/p/davmail/code/trunk@863 3d1905a2-6b24-0410-a738-b14d5a86fcbd
This commit is contained in:
mguessan 2009-11-27 09:34:20 +00:00
parent ca1cb26fd5
commit 1ba600e818

View File

@ -1634,6 +1634,13 @@ public class ExchangeSession {
protected String href; protected String href;
protected String permanentUrl; protected String permanentUrl;
protected String etag; protected String etag;
protected String contentClass;
protected String noneMatch;
/**
* ICS content
*/
protected String icsBody;
protected MimePart getCalendarMimePart(MimeMultipart multiPart) throws IOException, MessagingException { protected MimePart getCalendarMimePart(MimeMultipart multiPart) throws IOException, MessagingException {
MimePart bodyPart = null; MimePart bodyPart = null;
@ -1721,210 +1728,35 @@ public class ExchangeSession {
public String getEtag() { public String getEtag() {
return etag; return etag;
} }
protected String fixTimezoneId(String line, String validTimezoneId) {
return StringUtil.replaceToken(line, "TZID=", ":", validTimezoneId);
} }
/** protected void splitExDate(ICSBufferedWriter result, String line) {
* Search calendar messages in provided folder. int cur = line.lastIndexOf(':') + 1;
* String start = line.substring(0, cur);
* @param folderPath Exchange folder path
* @return list of calendar messages as Event objects for (int next = line.indexOf(',', cur); next != -1; next = line.indexOf(',', cur)) {
* @throws IOException on error String val = line.substring(cur, next);
*/ result.writeLine(start + val);
public List<Event> getEventMessages(String folderPath) throws IOException {
List<Event> result; cur = next + 1;
try {
String searchQuery = "Select \"DAV:getetag\", \"http://schemas.microsoft.com/exchange/permanenturl\"" +
" FROM Scope('SHALLOW TRAVERSAL OF \"" + folderPath + "\"')\n" +
" WHERE \"DAV:contentclass\" = 'urn:content-classes:calendarmessage'\n" +
" AND (NOT \"" + scheduleStateProperty.getNamespace().getURI() + scheduleStateProperty.getName() + "\" = 'CALDAV:schedule-processed')\n" +
" ORDER BY \"urn:schemas:calendar:dtstart\" DESC\n";
result = getEvents(folderPath, searchQuery);
} catch (HttpException e) {
// failover to DAV:comment property on some Exchange servers
if (DEFAULT_SCHEDULE_STATE_PROPERTY.equals(scheduleStateProperty)) {
scheduleStateProperty = DavPropertyName.create("comment", Namespace.getNamespace("DAV:"));
result = getEventMessages(folderPath);
} else {
throw e;
}
}
return result;
} }
/** result.writeLine(start + line.substring(cur));
* Search calendar events in provided folder.
*
* @param folderPath Exchange folder path
* @return list of calendar events
* @throws IOException on error
*/
public List<Event> getAllEvents(String folderPath) throws IOException {
int caldavPastDelay = Settings.getIntProperty("davmail.caldavPastDelay", Integer.MAX_VALUE);
String dateCondition = "";
if (caldavPastDelay != Integer.MAX_VALUE) {
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_MONTH, -caldavPastDelay);
dateCondition = " AND \"urn:schemas:calendar:dtstart\" > '" + formatSearchDate(cal.getTime()) + "'\n";
} }
String searchQuery = "Select \"DAV:getetag\", \"http://schemas.microsoft.com/exchange/permanenturl\"" + protected String getAllDayLine(String line) throws IOException {
" FROM Scope('SHALLOW TRAVERSAL OF \"" + folderPath + "\"')\n" + int keyIndex = line.indexOf(';');
" WHERE (" + int valueIndex = line.lastIndexOf(':');
// VTODO events have a null instancetype int valueEndIndex = line.lastIndexOf('T');
" \"urn:schemas:calendar:instancetype\" is null OR" + if (valueIndex < 0 || valueEndIndex < 0) {
" \"urn:schemas:calendar:instancetype\" = 1\n" + throw new DavMailException("EXCEPTION_INVALID_ICS_LINE", line);
" OR (\"urn:schemas:calendar:instancetype\" = 0\n" +
dateCondition +
" )) AND \"DAV:contentclass\" = 'urn:content-classes:appointment'\n" +
" ORDER BY \"urn:schemas:calendar:dtstart\" DESC\n";
return getEvents(folderPath, searchQuery);
} }
String dateValue = line.substring(valueIndex + 1, valueEndIndex);
String key = line.substring(0, Math.max(keyIndex, valueIndex));
/** return key + ";VALUE=DATE:" + dateValue;
* Search calendar events or messages in provided folder matching the search query.
*
* @param folderPath Exchange folder path
* @param searchQuery Exchange search query
* @return list of calendar messages as Event objects
* @throws IOException on error
*/
protected List<Event> getEvents(String folderPath, String searchQuery) throws IOException {
List<Event> events = new ArrayList<Event>();
MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executeSearchMethod(httpClient, URIUtil.encodePath(folderPath), searchQuery);
for (MultiStatusResponse response : responses) {
events.add(buildEvent(response));
}
return events;
}
/**
* Get event named eventName in folder
*
* @param folderPath Exchange folder path
* @param eventName event name
* @return event object
* @throws IOException on error
*/
public Event getEvent(String folderPath, String eventName) throws IOException {
String eventPath = folderPath + '/' + eventName;
return getEvent(eventPath);
}
/**
* Get event by url
*
* @param eventPath Event path
* @return event object
* @throws IOException on error
*/
public Event getEvent(String eventPath) throws IOException {
MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executePropFindMethod(httpClient, URIUtil.encodePath(eventPath), 0, EVENT_REQUEST_PROPERTIES);
if (responses.length == 0) {
throw new DavMailException("EXCEPTION_EVENT_NOT_FOUND");
}
return buildEvent(responses[0]);
}
/**
* Delete event named eventName in folder
*
* @param folderPath Exchange folder path
* @param eventName event name
* @return HTTP status
* @throws IOException on error
*/
public int deleteEvent(String folderPath, String eventName) throws IOException {
String eventPath = URIUtil.encodePath(folderPath + '/' + eventName);
int status;
if (inboxUrl.endsWith(folderPath)) {
// do not delete calendar messages, mark read and processed
ArrayList<DavProperty> list = new ArrayList<DavProperty>();
list.add(new DefaultDavProperty(scheduleStateProperty, "CALDAV:schedule-processed"));
list.add(new DefaultDavProperty(DavPropertyName.create("read", URN_SCHEMAS_HTTPMAIL), "1"));
PropPatchMethod patchMethod = new PropPatchMethod(eventPath, list);
DavGatewayHttpClientFacade.executeMethod(httpClient, patchMethod);
status = HttpStatus.SC_OK;
} else {
status = DavGatewayHttpClientFacade.executeDeleteMethod(httpClient, eventPath);
}
return status;
}
protected Event buildEvent(MultiStatusResponse calendarResponse) throws URIException {
Event event = new Event();
event.href = URIUtil.decode(calendarResponse.getHref());
event.permanentUrl = getPropertyIfExists(calendarResponse.getProperties(HttpStatus.SC_OK), "permanenturl", SCHEMAS_EXCHANGE);
event.etag = getPropertyIfExists(calendarResponse.getProperties(HttpStatus.SC_OK), "getetag", Namespace.getNamespace("DAV:"));
return event;
}
private static int dumpIndex;
private String defaultSound = "Basso";
protected void dumpICS(String icsBody, boolean fromServer, boolean after) {
String logFileDirectory = Settings.getLogFileDirectory();
// additional setting to activate ICS dump (not available in GUI)
int dumpMax = Settings.getIntProperty("davmail.dumpICS");
if (dumpMax > 0) {
if (dumpIndex > dumpMax) {
// Delete the oldest dump file
final int oldest = dumpIndex - dumpMax;
try {
File[] oldestFiles = (new File(logFileDirectory)).listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
if (name.endsWith(".ics")) {
int dashIndex = name.indexOf('-');
if (dashIndex > 0) {
try {
int fileIndex = Integer.parseInt(name.substring(0, dashIndex));
return fileIndex < oldest;
} catch (NumberFormatException nfe) {
// ignore
}
}
}
return false;
}
});
for (File file : oldestFiles) {
if (!file.delete()) {
LOGGER.warn("Unable to delete " + file.getAbsolutePath());
}
}
} catch (Exception ex) {
LOGGER.warn("Error deleting ics dump: " + ex.getMessage());
}
}
StringBuilder filePath = new StringBuilder();
filePath.append(logFileDirectory).append('/')
.append(dumpIndex)
.append(after ? "-to" : "-from")
.append((after ^ fromServer) ? "-server" : "-client")
.append(".ics");
if ((icsBody != null) && (icsBody.length() > 0)) {
FileWriter fileWriter = null;
try {
fileWriter = new FileWriter(filePath.toString());
fileWriter.write(icsBody);
} catch (IOException e) {
LOGGER.error(e);
} finally {
if (fileWriter != null) {
try {
fileWriter.close();
} catch (IOException e) {
LOGGER.error(e);
}
}
}
}
}
} }
protected String fixICS(String icsBody, boolean fromServer) throws IOException { protected String fixICS(String icsBody, boolean fromServer) throws IOException {
@ -2170,110 +2002,69 @@ public class ExchangeSession {
return result.toString(); return result.toString();
} }
/** protected void dumpICS(String icsBody, boolean fromServer, boolean after) {
* Replace iCal4 (Snow Leopard) principal paths with mailto expression String logFileDirectory = Settings.getLogFileDirectory();
*
* @param value attendee value or ics line // additional setting to activate ICS dump (not available in GUI)
* @return fixed value int dumpMax = Settings.getIntProperty("davmail.dumpICS");
*/ if (dumpMax > 0) {
protected String replaceIcal4Principal(String value) { if (dumpIndex > dumpMax) {
if (value.contains("/principals/__uuids__/")) { // Delete the oldest dump file
return value.replaceAll("/principals/__uuids__/([^/]*)__AT__([^/]*)/", "mailto:$1@$2"); final int oldest = dumpIndex - dumpMax;
} else { try {
return value; File[] oldestFiles = (new File(logFileDirectory)).listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
if (name.endsWith(".ics")) {
int dashIndex = name.indexOf('-');
if (dashIndex > 0) {
try {
int fileIndex = Integer.parseInt(name.substring(0, dashIndex));
return fileIndex < oldest;
} catch (NumberFormatException nfe) {
// ignore
}
}
}
return false;
}
});
for (File file : oldestFiles) {
if (!file.delete()) {
LOGGER.warn("Unable to delete " + file.getAbsolutePath());
}
}
} catch (Exception ex) {
LOGGER.warn("Error deleting ics dump: " + ex.getMessage());
} }
} }
protected String fixTimezoneId(String line, String validTimezoneId) { StringBuilder filePath = new StringBuilder();
return StringUtil.replaceToken(line, "TZID=", ":", validTimezoneId); filePath.append(logFileDirectory).append('/')
.append(dumpIndex)
.append(after ? "-to" : "-from")
.append((after ^ fromServer) ? "-server" : "-client")
.append(".ics");
if ((icsBody != null) && (icsBody.length() > 0)) {
FileWriter fileWriter = null;
try {
fileWriter = new FileWriter(filePath.toString());
fileWriter.write(icsBody);
} catch (IOException e) {
LOGGER.error(e);
} finally {
if (fileWriter != null) {
try {
fileWriter.close();
} catch (IOException e) {
LOGGER.error(e);
} }
protected void splitExDate(ICSBufferedWriter result, String line) {
int cur = line.lastIndexOf(':') + 1;
String start = line.substring(0, cur);
for (int next = line.indexOf(',', cur); next != -1; next = line.indexOf(',', cur)) {
String val = line.substring(cur, next);
result.writeLine(start + val);
cur = next + 1;
} }
result.writeLine(start + line.substring(cur));
}
protected String getAllDayLine(String line) throws IOException {
int keyIndex = line.indexOf(';');
int valueIndex = line.lastIndexOf(':');
int valueEndIndex = line.lastIndexOf('T');
if (valueIndex < 0 || valueEndIndex < 0) {
throw new DavMailException("EXCEPTION_INVALID_ICS_LINE", line);
}
String dateValue = line.substring(valueIndex + 1, valueEndIndex);
String key = line.substring(0, Math.max(keyIndex, valueIndex));
return key + ";VALUE=DATE:" + dateValue;
} }
/**
* Event result object to hold HTTP status and event etag from an event creation/update.
*/
public static class EventResult {
/**
* HTTP status
*/
public int status;
/**
* Event etag from response HTTP header
*/
public String etag;
}
/**
* Build and send the MIME message for the provided ICS event.
*
* @param icsBody event in iCalendar format
* @return HTTP status
* @throws IOException on error
*/
public int sendEvent(String icsBody) throws IOException {
String messageUrl = draftsUrl + '/' + UUID.randomUUID().toString() + ".EML";
int status = internalCreateOrUpdateEvent(messageUrl, "urn:content-classes:calendarmessage", icsBody, null, null).status;
if (status != HttpStatus.SC_CREATED) {
return status;
} else {
MoveMethod method = new MoveMethod(URIUtil.encodePath(messageUrl), URIUtil.encodePath(sendmsgUrl), true);
status = DavGatewayHttpClientFacade.executeHttpMethod(httpClient, method);
if (status != HttpStatus.SC_OK) {
throw DavGatewayHttpClientFacade.buildHttpException(method);
}
return status;
} }
} }
/**
* Create or update event on the Exchange server
*
* @param folderPath Exchange folder path
* @param eventName event name
* @param icsBody event body in iCalendar format
* @param etag previous event etag to detect concurrent updates
* @param noneMatch if-none-match header value
* @return HTTP response event result (status and etag)
* @throws IOException on error
*/
public EventResult createOrUpdateEvent(String folderPath, String eventName, String icsBody, String etag, String noneMatch) throws IOException {
String messageUrl = folderPath + '/' + eventName;
return internalCreateOrUpdateEvent(messageUrl, "urn:content-classes:appointment", icsBody, etag, noneMatch);
}
protected String getICSMethod(String icsBody) {
String icsMethod = StringUtil.getToken(icsBody, "METHOD:", "\r");
if (icsMethod == null) {
// default method is REQUEST
icsMethod = "REQUEST";
}
return icsMethod;
} }
protected String getICSValue(String icsBody, String prefix, String defval) throws IOException { protected String getICSValue(String icsBody, String prefix, String defval) throws IOException {
@ -2311,7 +2102,7 @@ public class ExchangeSession {
return getICSValue(icsBody, "DESCRIPTION:", ""); return getICSValue(icsBody, "DESCRIPTION:", "");
} }
static class Participants { class Participants {
String attendees; String attendees;
String organizer; String organizer;
} }
@ -2320,12 +2111,11 @@ public class ExchangeSession {
* Parse ics event for attendees and organizer. * Parse ics event for attendees and organizer.
* For notifications, only include attendees with RSVP=TRUE or PARTSTAT=NEEDS-ACTION * For notifications, only include attendees with RSVP=TRUE or PARTSTAT=NEEDS-ACTION
* *
* @param icsBody ics event
* @param isNotification get only notified attendees * @param isNotification get only notified attendees
* @return participants * @return participants
* @throws IOException on error * @throws IOException on error
*/ */
protected Participants getParticipants(String icsBody, boolean isNotification) throws IOException { protected Participants getParticipants(boolean isNotification) throws IOException {
HashSet<String> attendees = new HashSet<String>(); HashSet<String> attendees = new HashSet<String>();
String organizer = null; String organizer = null;
BufferedReader reader = null; BufferedReader reader = null;
@ -2380,12 +2170,21 @@ public class ExchangeSession {
return participants; return participants;
} }
protected EventResult internalCreateOrUpdateEvent(String messageUrl, String contentClass, String icsBody, String etag, String noneMatch) throws IOException { protected String getICSMethod(String icsBody) {
String uid = UUID.randomUUID().toString(); String icsMethod = StringUtil.getToken(icsBody, "METHOD:", "\r");
if (icsMethod == null) {
// default method is REQUEST
icsMethod = "REQUEST";
}
return icsMethod;
}
protected EventResult createOrUpdate() throws IOException {
String boundary = UUID.randomUUID().toString();
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(baos, "ASCII"); OutputStreamWriter writer = new OutputStreamWriter(baos, "ASCII");
int status = 0; int status = 0;
PutMethod putmethod = new PutMethod(URIUtil.encodePath(messageUrl)); PutMethod putmethod = new PutMethod(URIUtil.encodePath(href));
putmethod.setRequestHeader("Translate", "f"); putmethod.setRequestHeader("Translate", "f");
putmethod.setRequestHeader("Overwrite", "f"); putmethod.setRequestHeader("Overwrite", "f");
if (etag != null) { if (etag != null) {
@ -2408,7 +2207,7 @@ public class ExchangeSession {
writer.write("\r\n"); writer.write("\r\n");
if ("urn:content-classes:calendarmessage".equals(contentClass)) { if ("urn:content-classes:calendarmessage".equals(contentClass)) {
// need to parse attendees and organizer to build recipients // need to parse attendees and organizer to build recipients
Participants participants = getParticipants(icsBody, true); Participants participants = getParticipants(true);
String recipients; String recipients;
if (email.equalsIgnoreCase(participants.organizer)) { if (email.equalsIgnoreCase(participants.organizer)) {
// current user is organizer => notify all // current user is organizer => notify all
@ -2433,7 +2232,7 @@ public class ExchangeSession {
writer.write("Subject: " + MimeUtility.encodeText(getICSSummary(icsBody), "UTF-8", null) + "\r\n"); writer.write("Subject: " + MimeUtility.encodeText(getICSSummary(icsBody), "UTF-8", null) + "\r\n");
// need to parse attendees and organizer to build recipients // need to parse attendees and organizer to build recipients
Participants participants = getParticipants(icsBody, false); Participants participants = getParticipants(false);
// storing appointment, full recipients header // storing appointment, full recipients header
writer.write("To: "); writer.write("To: ");
if (participants.attendees != null) { if (participants.attendees != null) {
@ -2464,13 +2263,13 @@ public class ExchangeSession {
writer.write("MIME-Version: 1.0\r\n" + writer.write("MIME-Version: 1.0\r\n" +
"Content-Type: multipart/alternative;\r\n" + "Content-Type: multipart/alternative;\r\n" +
"\tboundary=\"----=_NextPart_"); "\tboundary=\"----=_NextPart_");
writer.write(uid); writer.write(boundary);
writer.write("\"\r\n" + writer.write("\"\r\n" +
"\r\n" + "\r\n" +
"This is a multi-part message in MIME format.\r\n" + "This is a multi-part message in MIME format.\r\n" +
"\r\n" + "\r\n" +
"------=_NextPart_"); "------=_NextPart_");
writer.write(uid); writer.write(boundary);
// Write a part of the message that contains the // Write a part of the message that contains the
// ICS description so that invites contain the description text // ICS description so that invites contain the description text
@ -2486,7 +2285,7 @@ public class ExchangeSession {
baos.write(description.getBytes("UTF-8")); baos.write(description.getBytes("UTF-8"));
writer.write("\r\n" + writer.write("\r\n" +
"------=_NextPart_" + "------=_NextPart_" +
uid); boundary);
} }
@ -2503,7 +2302,7 @@ public class ExchangeSession {
writer.flush(); writer.flush();
baos.write(fixICS(icsBody, false).getBytes("UTF-8")); baos.write(fixICS(icsBody, false).getBytes("UTF-8"));
writer.write("\r\n------=_NextPart_"); writer.write("\r\n------=_NextPart_");
writer.write(uid); writer.write(boundary);
writer.write("--\r\n"); writer.write("--\r\n");
writer.close(); writer.close();
putmethod.setRequestEntity(new ByteArrayRequestEntity(baos.toByteArray(), "message/rfc822")); putmethod.setRequestEntity(new ByteArrayRequestEntity(baos.toByteArray(), "message/rfc822"));
@ -2512,9 +2311,9 @@ public class ExchangeSession {
status = httpClient.executeMethod(putmethod); status = httpClient.executeMethod(putmethod);
if (status == HttpURLConnection.HTTP_OK) { if (status == HttpURLConnection.HTTP_OK) {
if (etag != null) { if (etag != null) {
LOGGER.debug("Updated event " + messageUrl); LOGGER.debug("Updated event " + href);
} else { } else {
LOGGER.warn("Overwritten event " + messageUrl); LOGGER.warn("Overwritten event " + href);
} }
} else if (status != HttpURLConnection.HTTP_CREATED) { } else if (status != HttpURLConnection.HTTP_CREATED) {
LOGGER.warn("Unable to create or update message " + status + ' ' + putmethod.getStatusLine()); LOGGER.warn("Unable to create or update message " + status + ' ' + putmethod.getStatusLine());
@ -2538,13 +2337,230 @@ public class ExchangeSession {
(Settings.getBooleanProperty("davmail.forceActiveSyncUpdate"))) { (Settings.getBooleanProperty("davmail.forceActiveSyncUpdate"))) {
ArrayList<DavProperty> propertyList = new ArrayList<DavProperty>(); ArrayList<DavProperty> propertyList = new ArrayList<DavProperty>();
propertyList.add(new DefaultDavProperty(DavPropertyName.create("contentclass", Namespace.getNamespace("DAV:")), contentClass)); propertyList.add(new DefaultDavProperty(DavPropertyName.create("contentclass", Namespace.getNamespace("DAV:")), contentClass));
PropPatchMethod propPatchMethod = new PropPatchMethod(URIUtil.encodePath(messageUrl), propertyList); PropPatchMethod propPatchMethod = new PropPatchMethod(URIUtil.encodePath(href), propertyList);
int patchStatus = DavGatewayHttpClientFacade.executeHttpMethod(httpClient, propPatchMethod); int patchStatus = DavGatewayHttpClientFacade.executeHttpMethod(httpClient, propPatchMethod);
if (patchStatus != HttpStatus.SC_MULTI_STATUS) { if (patchStatus != HttpStatus.SC_MULTI_STATUS) {
LOGGER.warn("Unable to patch event to trigger activeSync push"); LOGGER.warn("Unable to patch event to trigger activeSync push");
} }
} }
return eventResult; return eventResult;
}
}
/**
* Search calendar messages in provided folder.
*
* @param folderPath Exchange folder path
* @return list of calendar messages as Event objects
* @throws IOException on error
*/
public List<Event> getEventMessages(String folderPath) throws IOException {
List<Event> result;
try {
String searchQuery = "Select \"DAV:getetag\", \"http://schemas.microsoft.com/exchange/permanenturl\"" +
" FROM Scope('SHALLOW TRAVERSAL OF \"" + folderPath + "\"')\n" +
" WHERE \"DAV:contentclass\" = 'urn:content-classes:calendarmessage'\n" +
" AND (NOT \"" + scheduleStateProperty.getNamespace().getURI() + scheduleStateProperty.getName() + "\" = 'CALDAV:schedule-processed')\n" +
" ORDER BY \"urn:schemas:calendar:dtstart\" DESC\n";
result = getEvents(folderPath, searchQuery);
} catch (HttpException e) {
// failover to DAV:comment property on some Exchange servers
if (DEFAULT_SCHEDULE_STATE_PROPERTY.equals(scheduleStateProperty)) {
scheduleStateProperty = DavPropertyName.create("comment", Namespace.getNamespace("DAV:"));
result = getEventMessages(folderPath);
} else {
throw e;
}
}
return result;
}
/**
* Search calendar events in provided folder.
*
* @param folderPath Exchange folder path
* @return list of calendar events
* @throws IOException on error
*/
public List<Event> getAllEvents(String folderPath) throws IOException {
int caldavPastDelay = Settings.getIntProperty("davmail.caldavPastDelay", Integer.MAX_VALUE);
String dateCondition = "";
if (caldavPastDelay != Integer.MAX_VALUE) {
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_MONTH, -caldavPastDelay);
dateCondition = " AND \"urn:schemas:calendar:dtstart\" > '" + formatSearchDate(cal.getTime()) + "'\n";
}
String searchQuery = "Select \"DAV:getetag\", \"http://schemas.microsoft.com/exchange/permanenturl\"" +
" FROM Scope('SHALLOW TRAVERSAL OF \"" + folderPath + "\"')\n" +
" WHERE (" +
// VTODO events have a null instancetype
" \"urn:schemas:calendar:instancetype\" is null OR" +
" \"urn:schemas:calendar:instancetype\" = 1\n" +
" OR (\"urn:schemas:calendar:instancetype\" = 0\n" +
dateCondition +
" )) AND \"DAV:contentclass\" = 'urn:content-classes:appointment'\n" +
" ORDER BY \"urn:schemas:calendar:dtstart\" DESC\n";
return getEvents(folderPath, searchQuery);
}
/**
* Search calendar events or messages in provided folder matching the search query.
*
* @param folderPath Exchange folder path
* @param searchQuery Exchange search query
* @return list of calendar messages as Event objects
* @throws IOException on error
*/
protected List<Event> getEvents(String folderPath, String searchQuery) throws IOException {
List<Event> events = new ArrayList<Event>();
MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executeSearchMethod(httpClient, URIUtil.encodePath(folderPath), searchQuery);
for (MultiStatusResponse response : responses) {
events.add(buildEvent(response));
}
return events;
}
/**
* Get event named eventName in folder
*
* @param folderPath Exchange folder path
* @param eventName event name
* @return event object
* @throws IOException on error
*/
public Event getEvent(String folderPath, String eventName) throws IOException {
String eventPath = folderPath + '/' + eventName;
return getEvent(eventPath);
}
/**
* Get event by url
*
* @param eventPath Event path
* @return event object
* @throws IOException on error
*/
public Event getEvent(String eventPath) throws IOException {
MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executePropFindMethod(httpClient, URIUtil.encodePath(eventPath), 0, EVENT_REQUEST_PROPERTIES);
if (responses.length == 0) {
throw new DavMailException("EXCEPTION_EVENT_NOT_FOUND");
}
return buildEvent(responses[0]);
}
/**
* Delete event named eventName in folder
*
* @param folderPath Exchange folder path
* @param eventName event name
* @return HTTP status
* @throws IOException on error
*/
public int deleteEvent(String folderPath, String eventName) throws IOException {
String eventPath = URIUtil.encodePath(folderPath + '/' + eventName);
int status;
if (inboxUrl.endsWith(folderPath)) {
// do not delete calendar messages, mark read and processed
ArrayList<DavProperty> list = new ArrayList<DavProperty>();
list.add(new DefaultDavProperty(scheduleStateProperty, "CALDAV:schedule-processed"));
list.add(new DefaultDavProperty(DavPropertyName.create("read", URN_SCHEMAS_HTTPMAIL), "1"));
PropPatchMethod patchMethod = new PropPatchMethod(eventPath, list);
DavGatewayHttpClientFacade.executeMethod(httpClient, patchMethod);
status = HttpStatus.SC_OK;
} else {
status = DavGatewayHttpClientFacade.executeDeleteMethod(httpClient, eventPath);
}
return status;
}
protected Event buildEvent(MultiStatusResponse calendarResponse) throws URIException {
Event event = new Event();
event.href = URIUtil.decode(calendarResponse.getHref());
event.permanentUrl = getPropertyIfExists(calendarResponse.getProperties(HttpStatus.SC_OK), "permanenturl", SCHEMAS_EXCHANGE);
event.etag = getPropertyIfExists(calendarResponse.getProperties(HttpStatus.SC_OK), "getetag", Namespace.getNamespace("DAV:"));
return event;
}
private static int dumpIndex;
private String defaultSound = "Basso";
/**
* Replace iCal4 (Snow Leopard) principal paths with mailto expression
*
* @param value attendee value or ics line
* @return fixed value
*/
protected String replaceIcal4Principal(String value) {
if (value.contains("/principals/__uuids__/")) {
return value.replaceAll("/principals/__uuids__/([^/]*)__AT__([^/]*)/", "mailto:$1@$2");
} else {
return value;
}
}
/**
* Event result object to hold HTTP status and event etag from an event creation/update.
*/
public static class EventResult {
/**
* HTTP status
*/
public int status;
/**
* Event etag from response HTTP header
*/
public String etag;
}
/**
* Build and send the MIME message for the provided ICS event.
*
* @param icsBody event in iCalendar format
* @return HTTP status
* @throws IOException on error
*/
public int sendEvent(String icsBody) throws IOException {
String messageUrl = draftsUrl + '/' + UUID.randomUUID().toString() + ".EML";
int status = internalCreateOrUpdateEvent(messageUrl, "urn:content-classes:calendarmessage", icsBody, null, null).status;
if (status != HttpStatus.SC_CREATED) {
return status;
} else {
MoveMethod method = new MoveMethod(URIUtil.encodePath(messageUrl), URIUtil.encodePath(sendmsgUrl), true);
status = DavGatewayHttpClientFacade.executeHttpMethod(httpClient, method);
if (status != HttpStatus.SC_OK) {
throw DavGatewayHttpClientFacade.buildHttpException(method);
}
return status;
}
}
/**
* Create or update event on the Exchange server
*
* @param folderPath Exchange folder path
* @param eventName event name
* @param icsBody event body in iCalendar format
* @param etag previous event etag to detect concurrent updates
* @param noneMatch if-none-match header value
* @return HTTP response event result (status and etag)
* @throws IOException on error
*/
public EventResult createOrUpdateEvent(String folderPath, String eventName, String icsBody, String etag, String noneMatch) throws IOException {
String messageUrl = folderPath + '/' + eventName;
return internalCreateOrUpdateEvent(messageUrl, "urn:content-classes:appointment", icsBody, etag, noneMatch);
}
protected EventResult internalCreateOrUpdateEvent(String messageUrl, String contentClass, String icsBody, String etag, String noneMatch) throws IOException {
Event event = new Event();
event.contentClass = contentClass;
event.icsBody = icsBody;
event.href = messageUrl;
event.etag = etag;
event.noneMatch = noneMatch;
return event.createOrUpdate();
} }
/** /**