mirror of
https://github.com/moparisthebest/davmail
synced 2024-12-13 11:12:22 -05:00
EWS: in progress refactoring of contacts and events handling
git-svn-id: http://svn.code.sf.net/p/davmail/code/trunk@1090 3d1905a2-6b24-0410-a738-b14d5a86fcbd
This commit is contained in:
parent
7b7fa90edf
commit
d344693d1e
@ -115,15 +115,6 @@ public abstract class ExchangeSession {
|
||||
protected static final Namespace URN_SCHEMAS_CONTACTS = Namespace.getNamespace("urn:schemas:contacts:");
|
||||
|
||||
|
||||
protected static final DavPropertyNameSet EVENT_REQUEST_PROPERTIES = new DavPropertyNameSet();
|
||||
|
||||
static {
|
||||
EVENT_REQUEST_PROPERTIES.add(DavPropertyName.create("permanenturl", SCHEMAS_EXCHANGE));
|
||||
EVENT_REQUEST_PROPERTIES.add(DavPropertyName.GETETAG);
|
||||
EVENT_REQUEST_PROPERTIES.add(DavPropertyName.create("contentclass", DAV));
|
||||
}
|
||||
|
||||
|
||||
protected static final DavPropertyNameSet DISPLAY_NAME = new DavPropertyNameSet();
|
||||
|
||||
static {
|
||||
@ -1552,8 +1543,8 @@ public abstract class ExchangeSession {
|
||||
public abstract class Item {
|
||||
protected String href;
|
||||
protected String permanentUrl;
|
||||
protected String displayName;
|
||||
protected String etag;
|
||||
public String displayName;
|
||||
public String etag;
|
||||
protected String contentClass;
|
||||
protected String noneMatch;
|
||||
/**
|
||||
@ -1578,6 +1569,12 @@ public abstract class ExchangeSession {
|
||||
this.noneMatch = noneMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
protected Item() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return item content type
|
||||
*
|
||||
@ -1593,19 +1590,6 @@ public abstract class ExchangeSession {
|
||||
*/
|
||||
public abstract String getBody() throws HttpException;
|
||||
|
||||
/**
|
||||
* Build Item instance from multistatusResponse info
|
||||
*
|
||||
* @param multiStatusResponse response
|
||||
* @throws URIException on error
|
||||
*/
|
||||
public Item(MultiStatusResponse multiStatusResponse) throws URIException {
|
||||
href = URIUtil.decode(multiStatusResponse.getHref());
|
||||
permanentUrl = getPropertyIfExists(multiStatusResponse.getProperties(HttpStatus.SC_OK), "permanenturl", SCHEMAS_EXCHANGE);
|
||||
etag = getPropertyIfExists(multiStatusResponse.getProperties(HttpStatus.SC_OK), "getetag", DAV);
|
||||
displayName = getPropertyIfExists(multiStatusResponse.getProperties(HttpStatus.SC_OK), "displayname", DAV);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event name (file name part in URL).
|
||||
*
|
||||
@ -1639,24 +1623,21 @@ public abstract class ExchangeSession {
|
||||
/**
|
||||
* Calendar event object
|
||||
*/
|
||||
public class Contact extends Item {
|
||||
/**
|
||||
* Build Contact instance from multistatusResponse info
|
||||
*
|
||||
* @param multiStatusResponse response
|
||||
* @throws URIException on error
|
||||
*/
|
||||
public Contact(MultiStatusResponse multiStatusResponse) throws URIException {
|
||||
super(multiStatusResponse);
|
||||
}
|
||||
public abstract class Contact extends Item {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @inheritDoc
|
||||
*/
|
||||
public Contact(String messageUrl, String contentClass, String itemBody, String etag, String noneMatch) {
|
||||
super(messageUrl.endsWith(".vcf") ? messageUrl.substring(0, messageUrl.length() - 3) + "EML" : messageUrl, contentClass, itemBody, etag, noneMatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected Contact() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert EML extension to vcf.
|
||||
*
|
||||
@ -1787,7 +1768,7 @@ public abstract class ExchangeSession {
|
||||
return list;
|
||||
}
|
||||
|
||||
protected ItemResult createOrUpdate() throws IOException {
|
||||
public ItemResult createOrUpdate() throws IOException {
|
||||
int status = 0;
|
||||
PropPatchMethod propPatchMethod = new PropPatchMethod(URIUtil.encodePath(href), buildProperties());
|
||||
propPatchMethod.setRequestHeader("Translate", "f");
|
||||
@ -1844,16 +1825,7 @@ public abstract class ExchangeSession {
|
||||
/**
|
||||
* Calendar event object.
|
||||
*/
|
||||
public class Event extends Item {
|
||||
/**
|
||||
* Build Event instance from response info.
|
||||
*
|
||||
* @param multiStatusResponse response
|
||||
* @throws URIException on error
|
||||
*/
|
||||
public Event(MultiStatusResponse multiStatusResponse) throws URIException {
|
||||
super(multiStatusResponse);
|
||||
}
|
||||
public abstract class Event extends Item {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
@ -1862,6 +1834,12 @@ public abstract class ExchangeSession {
|
||||
super(messageUrl, contentClass, itemBody, etag, noneMatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected Event() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return "text/calendar;charset=UTF-8";
|
||||
@ -2453,21 +2431,11 @@ public abstract class ExchangeSession {
|
||||
return icsMethod;
|
||||
}
|
||||
|
||||
protected ItemResult createOrUpdate() throws IOException {
|
||||
public ItemResult createOrUpdate() throws IOException {
|
||||
String boundary = UUID.randomUUID().toString();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
MimeOutputStreamWriter writer = new MimeOutputStreamWriter(baos);
|
||||
int status = 0;
|
||||
PutMethod putmethod = new PutMethod(URIUtil.encodePath(href));
|
||||
putmethod.setRequestHeader("Translate", "f");
|
||||
putmethod.setRequestHeader("Overwrite", "f");
|
||||
if (etag != null) {
|
||||
putmethod.setRequestHeader("If-Match", etag);
|
||||
}
|
||||
if (noneMatch != null) {
|
||||
putmethod.setRequestHeader("If-None-Match", noneMatch);
|
||||
}
|
||||
putmethod.setRequestHeader("Content-Type", "message/rfc822");
|
||||
String method = getICSMethod(itemBody);
|
||||
|
||||
writer.writeHeader("Content-Transfer-Encoding", "7bit");
|
||||
@ -2560,54 +2528,30 @@ public abstract class ExchangeSession {
|
||||
writer.writeLn();
|
||||
writer.writeLn("------=_NextPart_" + boundary + "--");
|
||||
writer.close();
|
||||
putmethod.setRequestEntity(new ByteArrayRequestEntity(baos.toByteArray(), "message/rfc822"));
|
||||
try {
|
||||
if (status == 0) {
|
||||
status = httpClient.executeMethod(putmethod);
|
||||
if (status == HttpURLConnection.HTTP_OK) {
|
||||
if (etag != null) {
|
||||
LOGGER.debug("Updated event " + href);
|
||||
} else {
|
||||
LOGGER.warn("Overwritten event " + href);
|
||||
}
|
||||
} else if (status != HttpURLConnection.HTTP_CREATED) {
|
||||
LOGGER.warn("Unable to create or update message " + status + ' ' + putmethod.getStatusLine());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
putmethod.releaseConnection();
|
||||
}
|
||||
ItemResult itemResult = new ItemResult();
|
||||
// 440 means forbidden on Exchange
|
||||
if (status == 440) {
|
||||
status = HttpStatus.SC_FORBIDDEN;
|
||||
}
|
||||
itemResult.status = status;
|
||||
if (putmethod.getResponseHeader("GetETag") != null) {
|
||||
itemResult.etag = putmethod.getResponseHeader("GetETag").getValue();
|
||||
|
||||
ItemResult itemResult;
|
||||
if (status == 0) {
|
||||
itemResult = createOrUpdate(baos.toByteArray());
|
||||
} else {
|
||||
itemResult = new ItemResult();
|
||||
itemResult.status = status;
|
||||
}
|
||||
|
||||
// trigger activeSync push event, only if davmail.forceActiveSyncUpdate setting is true
|
||||
if ((status == HttpStatus.SC_OK || status == HttpStatus.SC_CREATED) &&
|
||||
(Settings.getBooleanProperty("davmail.forceActiveSyncUpdate"))) {
|
||||
ArrayList<DavProperty> propertyList = new ArrayList<DavProperty>();
|
||||
// Set contentclass to make ActiveSync happy
|
||||
propertyList.add(new DefaultDavProperty(DavPropertyName.create("contentclass", DAV), contentClass));
|
||||
// ... but also set PR_INTERNET_CONTENT to preserve custom properties
|
||||
propertyList.add(new DefaultDavProperty(PR_INTERNET_CONTENT, new String(Base64.encodeBase64(baos.toByteArray()))));
|
||||
PropPatchMethod propPatchMethod = new PropPatchMethod(URIUtil.encodePath(href), propertyList);
|
||||
int patchStatus = DavGatewayHttpClientFacade.executeHttpMethod(httpClient, propPatchMethod);
|
||||
if (patchStatus != HttpStatus.SC_MULTI_STATUS) {
|
||||
LOGGER.warn("Unable to patch event to trigger activeSync push");
|
||||
} else {
|
||||
// need to retrieve new etag
|
||||
Item newItem = getItem(href);
|
||||
itemResult.etag = newItem.etag;
|
||||
}
|
||||
}
|
||||
return itemResult;
|
||||
|
||||
}
|
||||
|
||||
protected abstract ItemResult createOrUpdate(byte[] content) throws IOException;
|
||||
|
||||
}
|
||||
|
||||
protected static final List<String> ITEM_PROPERTIES = new ArrayList<String>();
|
||||
|
||||
static {
|
||||
ITEM_PROPERTIES.add("etag");
|
||||
ITEM_PROPERTIES.add("displayname");
|
||||
// calendar CdoInstanceType
|
||||
ITEM_PROPERTIES.add("instancetype");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2617,14 +2561,11 @@ public abstract class ExchangeSession {
|
||||
* @return list of contacts
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public List<Contact> getAllContacts(String folderPath) throws IOException {
|
||||
|
||||
String searchQuery = "Select \"DAV:getetag\", \"http://schemas.microsoft.com/exchange/permanenturl\", \"DAV:displayname\"" +
|
||||
" FROM Scope('SHALLOW TRAVERSAL OF \"" + folderPath + "\"')\n" +
|
||||
" WHERE \"DAV:contentclass\" = 'urn:content-classes:person'\n";
|
||||
return getContacts(folderPath, searchQuery);
|
||||
public List<ExchangeSession.Contact> getAllContacts(String folderName) throws IOException {
|
||||
return searchContacts(folderName, ITEM_PROPERTIES, equals("contentclass", "urn:content-classes:person"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Search contacts in provided folder matching the search query.
|
||||
*
|
||||
@ -2633,14 +2574,7 @@ public abstract class ExchangeSession {
|
||||
* @return list of contacts
|
||||
* @throws IOException on error
|
||||
*/
|
||||
protected List<Contact> getContacts(String folderPath, String searchQuery) throws IOException {
|
||||
List<Contact> contacts = new ArrayList<Contact>();
|
||||
MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executeSearchMethod(httpClient, URIUtil.encodePath(folderPath), searchQuery);
|
||||
for (MultiStatusResponse response : responses) {
|
||||
contacts.add(new Contact(response));
|
||||
}
|
||||
return contacts;
|
||||
}
|
||||
protected abstract List<Contact> searchContacts(String folderName, List<String> attributes, Condition condition) throws IOException;
|
||||
|
||||
/**
|
||||
* Search calendar messages in provided folder.
|
||||
@ -2650,25 +2584,9 @@ public abstract class ExchangeSession {
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public List<Event> getEventMessages(String folderPath) throws IOException {
|
||||
List<Event> result;
|
||||
try {
|
||||
String scheduleStatePropertyName = scheduleStateProperty.getNamespace().getURI() + scheduleStateProperty.getName();
|
||||
String searchQuery = "Select \"DAV:getetag\", \"http://schemas.microsoft.com/exchange/permanenturl\", \"urn:schemas:calendar:instancetype\", \"DAV:displayname\"" +
|
||||
" FROM Scope('SHALLOW TRAVERSAL OF \"" + folderPath + "\"')\n" +
|
||||
" WHERE \"DAV:contentclass\" = 'urn:content-classes:calendarmessage'\n" +
|
||||
" AND (\"" + scheduleStatePropertyName + "\" IS NULL OR NOT \"" + scheduleStatePropertyName + "\" = '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", DAV);
|
||||
result = getEventMessages(folderPath);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return searchEvents(folderPath, ITEM_PROPERTIES,
|
||||
and(equals("contentclass", "urn:content-classes:calendarmessage"),
|
||||
isFalse("processed")));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2680,31 +2598,26 @@ public abstract class ExchangeSession {
|
||||
*/
|
||||
public List<Event> getAllEvents(String folderPath) throws IOException {
|
||||
int caldavPastDelay = Settings.getIntProperty("davmail.caldavPastDelay");
|
||||
String dateCondition = "";
|
||||
Condition dateCondition = null;
|
||||
if (caldavPastDelay != 0) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.add(Calendar.DAY_OF_MONTH, -caldavPastDelay);
|
||||
dateCondition = " AND \"urn:schemas:calendar:dtstart\" > '" + formatSearchDate(cal.getTime()) + "'\n";
|
||||
dateCondition = gt("dtstart", formatSearchDate(cal.getTime()));
|
||||
}
|
||||
|
||||
String privateCondition = "";
|
||||
Condition privateCondition = null;
|
||||
if (isSharedFolder(folderPath)) {
|
||||
LOGGER.debug("Shared or public calendar: exclude private events");
|
||||
privateCondition = " AND \"http://schemas.microsoft.com/exchange/sensitivity\" = 0\n";
|
||||
privateCondition = equals("sensitivity", "0");
|
||||
}
|
||||
|
||||
String searchQuery = "Select \"DAV:getetag\", \"http://schemas.microsoft.com/exchange/permanenturl\", \"urn:schemas:calendar:instancetype\", \"DAV:displayname\"" +
|
||||
" 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" +
|
||||
privateCondition +
|
||||
" ORDER BY \"urn:schemas:calendar:dtstart\" DESC\n";
|
||||
return getEvents(folderPath, searchQuery);
|
||||
|
||||
return searchEvents(folderPath, ITEM_PROPERTIES,
|
||||
and(or(isNull("instancetype"),
|
||||
equals("instancetype", "1"),
|
||||
and(equals("instancetype", "0"), dateCondition)),
|
||||
equals("contentclass", "urn:content-classes:appointment"),
|
||||
privateCondition));
|
||||
}
|
||||
|
||||
|
||||
@ -2716,29 +2629,7 @@ public abstract class ExchangeSession {
|
||||
* @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) {
|
||||
String instancetype = getPropertyIfExists(response.getProperties(HttpStatus.SC_OK), "instancetype", Namespace.getNamespace("urn:schemas:calendar:"));
|
||||
Event event = new Event(response);
|
||||
//noinspection VariableNotUsedInsideIf
|
||||
if (instancetype == null) {
|
||||
// check ics content
|
||||
try {
|
||||
event.getBody();
|
||||
// getBody success => add event or task
|
||||
events.add(event);
|
||||
} catch (HttpException e) {
|
||||
// invalid event: exclude from list
|
||||
LOGGER.warn("Invalid event " + event.displayName + " found at " + response.getHref(), e);
|
||||
}
|
||||
} else {
|
||||
events.add(event);
|
||||
}
|
||||
}
|
||||
return events;
|
||||
}
|
||||
protected abstract List<Event> searchEvents(String folderPath, List<String> attributes, Condition condition) throws IOException;
|
||||
|
||||
/**
|
||||
* convert vcf extension to EML.
|
||||
@ -2762,49 +2653,7 @@ public abstract class ExchangeSession {
|
||||
* @return event object
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public Item getItem(String folderPath, String itemName) throws IOException {
|
||||
String itemPath = folderPath + '/' + convertItemNameToEML(itemName);
|
||||
Item item;
|
||||
try {
|
||||
item = getItem(itemPath);
|
||||
} catch (HttpNotFoundException hnfe) {
|
||||
// failover for Exchange 2007 plus encoding issue
|
||||
String decodedEventName = convertItemNameToEML(itemName).replaceAll("_xF8FF_", "/").replaceAll("_x003F_", "?").replaceAll("'", "''");
|
||||
LOGGER.debug("Item not found at " + itemPath + ", search by displayname: '" + decodedEventName + '\'');
|
||||
ExchangeSession.MessageList messages = searchMessages(folderPath, equals("displayname", decodedEventName));
|
||||
if (!messages.isEmpty()) {
|
||||
item = getItem(messages.get(0).getPermanentUrl());
|
||||
} else {
|
||||
throw hnfe;
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item by url
|
||||
*
|
||||
* @param itemPath Event path
|
||||
* @return event object
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public Item getItem(String itemPath) throws IOException {
|
||||
MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executePropFindMethod(httpClient, URIUtil.encodePath(itemPath), 0, EVENT_REQUEST_PROPERTIES);
|
||||
if (responses.length == 0) {
|
||||
throw new DavMailException("EXCEPTION_EVENT_NOT_FOUND");
|
||||
}
|
||||
String contentClass = getPropertyIfExists(responses[0].getProperties(HttpStatus.SC_OK),
|
||||
"contentclass", DAV);
|
||||
if ("urn:content-classes:person".equals(contentClass)) {
|
||||
return new Contact(responses[0]);
|
||||
} else if ("urn:content-classes:appointment".equals(contentClass)
|
||||
|| "urn:content-classes:calendarmessage".equals(contentClass)) {
|
||||
return new Event(responses[0]);
|
||||
} else {
|
||||
throw new DavMailException("EXCEPTION_EVENT_NOT_FOUND");
|
||||
}
|
||||
}
|
||||
public abstract Item getItem(String folderPath, String itemName) throws IOException;
|
||||
|
||||
/**
|
||||
* Delete event named eventName in folder
|
||||
@ -2905,15 +2754,9 @@ public abstract class ExchangeSession {
|
||||
}
|
||||
}
|
||||
|
||||
protected ItemResult internalCreateOrUpdateContact(String messageUrl, String contentClass, String icsBody, String etag, String noneMatch) throws IOException {
|
||||
Contact contact = new Contact(messageUrl, contentClass, icsBody, etag, noneMatch);
|
||||
return contact.createOrUpdate();
|
||||
}
|
||||
protected abstract ItemResult internalCreateOrUpdateContact(String messageUrl, String contentClass, String icsBody, String etag, String noneMatch) throws IOException;
|
||||
|
||||
protected ItemResult internalCreateOrUpdateEvent(String messageUrl, String contentClass, String icsBody, String etag, String noneMatch) throws IOException {
|
||||
Event event = new Event(messageUrl, contentClass, icsBody, etag, noneMatch);
|
||||
return event.createOrUpdate();
|
||||
}
|
||||
protected abstract ItemResult internalCreateOrUpdateEvent(String messageUrl, String contentClass, String icsBody, String etag, String noneMatch) throws IOException;
|
||||
|
||||
/**
|
||||
* Get folder ctag (change tag).
|
||||
|
@ -26,6 +26,7 @@ import davmail.exception.HttpNotFoundException;
|
||||
import davmail.exchange.ExchangeSession;
|
||||
import davmail.http.DavGatewayHttpClientFacade;
|
||||
import davmail.ui.tray.DavGatewayTray;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.httpclient.*;
|
||||
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
|
||||
import org.apache.commons.httpclient.methods.GetMethod;
|
||||
@ -41,6 +42,7 @@ import org.apache.jackrabbit.webdav.property.*;
|
||||
import org.apache.jackrabbit.webdav.xml.Namespace;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.NoRouteToHostException;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
@ -228,16 +230,22 @@ public class DavExchangeSession extends ExchangeSession {
|
||||
@Override
|
||||
public void appendTo(StringBuilder buffer) {
|
||||
boolean first = true;
|
||||
buffer.append('(');
|
||||
|
||||
for (Condition condition : conditions) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
buffer.append(' ').append(operator).append(' ');
|
||||
if (condition != null) {
|
||||
if (first) {
|
||||
buffer.append('(');
|
||||
first = false;
|
||||
} else {
|
||||
buffer.append(' ').append(operator).append(' ');
|
||||
}
|
||||
condition.appendTo(buffer);
|
||||
}
|
||||
condition.appendTo(buffer);
|
||||
}
|
||||
buffer.append(')');
|
||||
// at least one non empty condition
|
||||
if (!first) {
|
||||
buffer.append(')');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -264,7 +272,7 @@ public class DavExchangeSession extends ExchangeSession {
|
||||
operatorMap.put(Operator.IsLessThan, " < ");
|
||||
operatorMap.put(Operator.Like, " like ");
|
||||
operatorMap.put(Operator.IsNull, " is null");
|
||||
|
||||
operatorMap.put(Operator.IsFalse, " is false");
|
||||
}
|
||||
|
||||
protected static class AttributeCondition extends ExchangeSession.AttributeCondition {
|
||||
@ -386,6 +394,109 @@ public class DavExchangeSession extends ExchangeSession {
|
||||
return new MonoCondition(attributeName, Operator.IsFalse);
|
||||
}
|
||||
|
||||
public class Contact extends ExchangeSession.Contact {
|
||||
/**
|
||||
* Build Contact instance from multistatusResponse info
|
||||
*
|
||||
* @param multiStatusResponse response
|
||||
* @throws URIException on error
|
||||
*/
|
||||
public Contact(MultiStatusResponse multiStatusResponse) throws URIException {
|
||||
href = URIUtil.decode(multiStatusResponse.getHref());
|
||||
DavPropertySet properties = multiStatusResponse.getProperties(HttpStatus.SC_OK);
|
||||
permanentUrl = getPropertyIfExists(properties, "permanenturl", SCHEMAS_EXCHANGE);
|
||||
etag = getPropertyIfExists(properties, "getetag", DAV);
|
||||
displayName = getPropertyIfExists(properties, "displayname", DAV);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public Contact(String messageUrl, String contentClass, String itemBody, String etag, String noneMatch) {
|
||||
super(messageUrl, contentClass, itemBody, etag, noneMatch);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class Event extends ExchangeSession.Event {
|
||||
/**
|
||||
* Build Event instance from response info.
|
||||
*
|
||||
* @param multiStatusResponse response
|
||||
* @throws URIException on error
|
||||
*/
|
||||
public Event(MultiStatusResponse multiStatusResponse) throws URIException {
|
||||
href = URIUtil.decode(multiStatusResponse.getHref());
|
||||
DavPropertySet properties = multiStatusResponse.getProperties(HttpStatus.SC_OK);
|
||||
permanentUrl = getPropertyIfExists(properties, "permanenturl", SCHEMAS_EXCHANGE);
|
||||
etag = getPropertyIfExists(properties, "getetag", DAV);
|
||||
displayName = getPropertyIfExists(properties, "displayname", DAV);
|
||||
}
|
||||
|
||||
public Event(String messageUrl, String contentClass, String itemBody, String etag, String noneMatch) {
|
||||
super(messageUrl, contentClass, itemBody, etag, noneMatch);
|
||||
}
|
||||
|
||||
protected ItemResult createOrUpdate(byte[] messageContent) throws IOException {
|
||||
PutMethod putmethod = new PutMethod(URIUtil.encodePath(href));
|
||||
putmethod.setRequestHeader("Translate", "f");
|
||||
putmethod.setRequestHeader("Overwrite", "f");
|
||||
if (etag != null) {
|
||||
putmethod.setRequestHeader("If-Match", etag);
|
||||
}
|
||||
if (noneMatch != null) {
|
||||
putmethod.setRequestHeader("If-None-Match", noneMatch);
|
||||
}
|
||||
putmethod.setRequestHeader("Content-Type", "message/rfc822");
|
||||
putmethod.setRequestEntity(new ByteArrayRequestEntity(messageContent, "message/rfc822"));
|
||||
int status;
|
||||
try {
|
||||
status = httpClient.executeMethod(putmethod);
|
||||
if (status == HttpURLConnection.HTTP_OK) {
|
||||
if (etag != null) {
|
||||
LOGGER.debug("Updated event " + href);
|
||||
} else {
|
||||
LOGGER.warn("Overwritten event " + href);
|
||||
}
|
||||
} else if (status != HttpURLConnection.HTTP_CREATED) {
|
||||
LOGGER.warn("Unable to create or update message " + status + ' ' + putmethod.getStatusLine());
|
||||
}
|
||||
} finally {
|
||||
putmethod.releaseConnection();
|
||||
}
|
||||
ItemResult itemResult = new ItemResult();
|
||||
// 440 means forbidden on Exchange
|
||||
if (status == 440) {
|
||||
status = HttpStatus.SC_FORBIDDEN;
|
||||
}
|
||||
itemResult.status = status;
|
||||
if (putmethod.getResponseHeader("GetETag") != null) {
|
||||
itemResult.etag = putmethod.getResponseHeader("GetETag").getValue();
|
||||
}
|
||||
|
||||
// trigger activeSync push event, only if davmail.forceActiveSyncUpdate setting is true
|
||||
if ((status == HttpStatus.SC_OK || status == HttpStatus.SC_CREATED) &&
|
||||
(Settings.getBooleanProperty("davmail.forceActiveSyncUpdate"))) {
|
||||
ArrayList<DavConstants> propertyList = new ArrayList<DavConstants>();
|
||||
// Set contentclass to make ActiveSync happy
|
||||
propertyList.add(Field.createDavProperty("contentclass", contentClass));
|
||||
// ... but also set PR_INTERNET_CONTENT to preserve custom properties
|
||||
propertyList.add(Field.createDavProperty("internetContent", new String(Base64.encodeBase64(messageContent))));
|
||||
PropPatchMethod propPatchMethod = new PropPatchMethod(URIUtil.encodePath(href), propertyList);
|
||||
int patchStatus = DavGatewayHttpClientFacade.executeHttpMethod(httpClient, propPatchMethod);
|
||||
if (patchStatus != HttpStatus.SC_MULTI_STATUS) {
|
||||
LOGGER.warn("Unable to patch event to trigger activeSync push");
|
||||
} else {
|
||||
// need to retrieve new etag
|
||||
Item newItem = getItem(href);
|
||||
itemResult.etag = newItem.etag;
|
||||
}
|
||||
}
|
||||
return itemResult;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected Folder buildFolder(MultiStatusResponse entity) throws IOException {
|
||||
String href = URIUtil.decode(entity.getHref());
|
||||
@ -444,7 +555,7 @@ public class DavExchangeSession extends ExchangeSession {
|
||||
FOLDER_PROPERTIES.add(Field.get("contenttag").davPropertyName);
|
||||
FOLDER_PROPERTIES.add(Field.get("lastmodified").davPropertyName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
@ -604,8 +715,58 @@ public class DavExchangeSession extends ExchangeSession {
|
||||
|
||||
@Override
|
||||
public MessageList searchMessages(String folderName, List<String> attributes, Condition condition) throws IOException {
|
||||
String folderUrl = getFolderPath(folderName);
|
||||
MessageList messages = new MessageList();
|
||||
MultiStatusResponse[] responses = searchItems(folderName, attributes, condition);
|
||||
|
||||
for (MultiStatusResponse response : responses) {
|
||||
Message message = buildMessage(response);
|
||||
message.messageList = messages;
|
||||
messages.add(message);
|
||||
}
|
||||
Collections.sort(messages);
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
protected List<ExchangeSession.Contact> searchContacts(String folderName, List<String> attributes, Condition condition) throws IOException {
|
||||
List<ExchangeSession.Contact> contacts = new ArrayList<ExchangeSession.Contact>();
|
||||
MultiStatusResponse[] responses = searchItems(folderName, attributes, condition);
|
||||
for (MultiStatusResponse response : responses) {
|
||||
contacts.add(new Contact(response));
|
||||
}
|
||||
return contacts;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ExchangeSession.Event> searchEvents(String folderName, List<String> attributes, Condition condition) throws IOException {
|
||||
List<ExchangeSession.Event> events = new ArrayList<ExchangeSession.Event>();
|
||||
MultiStatusResponse[] responses = searchItems(folderName, attributes, condition);
|
||||
for (MultiStatusResponse response : responses) {
|
||||
String instancetype = getPropertyIfExists(response.getProperties(HttpStatus.SC_OK), "instancetype");
|
||||
Event event = new Event(response);
|
||||
//noinspection VariableNotUsedInsideIf
|
||||
if (instancetype == null) {
|
||||
// check ics content
|
||||
try {
|
||||
event.getBody();
|
||||
// getBody success => add event or task
|
||||
events.add(event);
|
||||
} catch (HttpException e) {
|
||||
// invalid event: exclude from list
|
||||
LOGGER.warn("Invalid event " + event.displayName + " found at " + response.getHref(), e);
|
||||
}
|
||||
} else {
|
||||
events.add(event);
|
||||
}
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
public MultiStatusResponse[] searchItems(String folderName, List<String> attributes, Condition condition) throws IOException {
|
||||
String folderUrl = getFolderPath(folderName);
|
||||
StringBuilder searchRequest = new StringBuilder();
|
||||
searchRequest.append("Select \"http://schemas.microsoft.com/exchange/permanenturl\" as permanenturl");
|
||||
if (attributes != null) {
|
||||
@ -623,16 +784,73 @@ public class DavExchangeSession extends ExchangeSession {
|
||||
// TODO order by ImapUid
|
||||
//searchRequest.append(" ORDER BY \"urn:schemas:httpmail:date\" ASC");
|
||||
DavGatewayTray.debug(new BundleMessage("LOG_SEARCH_QUERY", searchRequest));
|
||||
MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executeSearchMethod(
|
||||
return DavGatewayHttpClientFacade.executeSearchMethod(
|
||||
httpClient, URIUtil.encodePath(folderUrl), searchRequest.toString());
|
||||
}
|
||||
|
||||
for (MultiStatusResponse response : responses) {
|
||||
Message message = buildMessage(response);
|
||||
message.messageList = messages;
|
||||
messages.add(message);
|
||||
|
||||
protected static final DavPropertyNameSet EVENT_REQUEST_PROPERTIES = new DavPropertyNameSet();
|
||||
|
||||
static {
|
||||
EVENT_REQUEST_PROPERTIES.add(Field.get("permanenturl").davPropertyName);
|
||||
EVENT_REQUEST_PROPERTIES.add(Field.get("etag").davPropertyName);
|
||||
EVENT_REQUEST_PROPERTIES.add(Field.get("contentclass").davPropertyName);
|
||||
EVENT_REQUEST_PROPERTIES.add(Field.get("displayname").davPropertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item getItem(String folderPath, String itemName) throws IOException {
|
||||
String itemPath = folderPath + '/' + convertItemNameToEML(itemName);
|
||||
Item item;
|
||||
try {
|
||||
item = getItem(itemPath);
|
||||
} catch (HttpNotFoundException hnfe) {
|
||||
// failover for Exchange 2007 plus encoding issue
|
||||
String decodedEventName = convertItemNameToEML(itemName).replaceAll("_xF8FF_", "/").replaceAll("_x003F_", "?").replaceAll("'", "''");
|
||||
LOGGER.debug("Item not found at " + itemPath + ", search by displayname: '" + decodedEventName + '\'');
|
||||
ExchangeSession.MessageList messages = searchMessages(folderPath, equals("displayname", decodedEventName));
|
||||
if (!messages.isEmpty()) {
|
||||
item = getItem(messages.get(0).getPermanentUrl());
|
||||
} else {
|
||||
throw hnfe;
|
||||
}
|
||||
}
|
||||
Collections.sort(messages);
|
||||
return messages;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item by url
|
||||
*
|
||||
* @param itemPath Event path
|
||||
* @return event object
|
||||
* @throws IOException on error
|
||||
*/
|
||||
public Item getItem(String itemPath) throws IOException {
|
||||
MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executePropFindMethod(httpClient, URIUtil.encodePath(itemPath), 0, EVENT_REQUEST_PROPERTIES);
|
||||
if (responses.length == 0) {
|
||||
throw new DavMailException("EXCEPTION_EVENT_NOT_FOUND");
|
||||
}
|
||||
String contentClass = getPropertyIfExists(responses[0].getProperties(HttpStatus.SC_OK),
|
||||
"contentclass", DAV);
|
||||
if ("urn:content-classes:person".equals(contentClass)) {
|
||||
return new Contact(responses[0]);
|
||||
} else if ("urn:content-classes:appointment".equals(contentClass)
|
||||
|| "urn:content-classes:calendarmessage".equals(contentClass)) {
|
||||
return new Event(responses[0]);
|
||||
} else {
|
||||
throw new DavMailException("EXCEPTION_EVENT_NOT_FOUND");
|
||||
}
|
||||
}
|
||||
|
||||
public ItemResult internalCreateOrUpdateEvent(String messageUrl, String contentClass, String icsBody, String etag, String noneMatch) throws IOException {
|
||||
Event event = new Event(messageUrl, contentClass, icsBody, etag, noneMatch);
|
||||
return event.createOrUpdate();
|
||||
}
|
||||
|
||||
protected ItemResult internalCreateOrUpdateContact(String messageUrl, String contentClass, String icsBody, String etag, String noneMatch) throws IOException {
|
||||
Contact contact = new Contact(messageUrl, contentClass, icsBody, etag, noneMatch);
|
||||
return contact.createOrUpdate();
|
||||
}
|
||||
|
||||
protected List<DavConstants> buildProperties(Map<String, String> properties) {
|
||||
@ -678,6 +896,7 @@ public class DavExchangeSession extends ExchangeSession {
|
||||
* @param messageBody mail body
|
||||
* @throws IOException when unable to create message
|
||||
*/
|
||||
@Override
|
||||
public void createMessage(String folderPath, String messageName, HashMap<String, String> properties, String messageBody) throws IOException {
|
||||
String messageUrl = URIUtil.encodePathQuery(getFolderPath(folderPath) + '/' + messageName + ".EML");
|
||||
PropPatchMethod patchMethod;
|
||||
@ -753,8 +972,8 @@ public class DavExchangeSession extends ExchangeSession {
|
||||
*/
|
||||
@Override
|
||||
public void deleteMessage(Message message) throws IOException {
|
||||
LOGGER.debug("Delete " + message.permanentUrl + " (" + message.messageUrl + ")");
|
||||
DavGatewayHttpClientFacade.executeDeleteMethod(httpClient, message.permanentUrl);
|
||||
LOGGER.debug("Delete " + message.permanentUrl + " (" + message.messageUrl + ")");
|
||||
DavGatewayHttpClientFacade.executeDeleteMethod(httpClient, message.permanentUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -54,6 +54,7 @@ public class Field {
|
||||
protected static final Namespace SCHEMAS_MAPI_STRING = Namespace.getNamespace("http://schemas.microsoft.com/mapi/string/");
|
||||
protected static final Namespace SCHEMAS_REPL = Namespace.getNamespace("http://schemas.microsoft.com/repl/");
|
||||
protected static final Namespace URN_SCHEMAS_CONTACTS = Namespace.getNamespace("urn:schemas:contacts:");
|
||||
protected static final Namespace URN_SCHEMAS_CALENDAR = Namespace.getNamespace("urn:schemas:calendar:");
|
||||
|
||||
protected static final Namespace SCHEMAS_MAPI_STRING_INTERNET_HEADERS =
|
||||
Namespace.getNamespace(SCHEMAS_MAPI_STRING.getURI() +
|
||||
@ -74,6 +75,7 @@ public class Field {
|
||||
propertyTypeMap.put(PropertyType.Boolean, "000b");
|
||||
propertyTypeMap.put(PropertyType.SystemTime, "0040");
|
||||
propertyTypeMap.put(PropertyType.String, "001f");
|
||||
propertyTypeMap.put(PropertyType.Binary, "0102");
|
||||
propertyTypeMap.put(PropertyType.Custom, "0030");
|
||||
}
|
||||
|
||||
@ -133,9 +135,21 @@ public class Field {
|
||||
|
||||
createField("lastmodified", 0x3008, PropertyType.SystemTime);//PR_LAST_MODIFICATION_TIME DAV:getlastmodified
|
||||
|
||||
|
||||
// failover search
|
||||
createField(DAV, "displayname");
|
||||
|
||||
// items
|
||||
createField("etag", DAV, "getetag");
|
||||
|
||||
// calendar
|
||||
createField(SCHEMAS_EXCHANGE, "permanenturl");
|
||||
createField(URN_SCHEMAS_CALENDAR, "instancetype");
|
||||
createField(URN_SCHEMAS_CALENDAR, "dtstart");
|
||||
createField(SCHEMAS_EXCHANGE, "sensitivity");
|
||||
createField("processed", 0x65e8, PropertyType.Boolean);//PR_MESSAGE_PROCESSED
|
||||
|
||||
createField(DAV, "contentclass");
|
||||
createField("internetContent", 0x6659, PropertyType.Binary);
|
||||
}
|
||||
|
||||
protected static void createField(String alias, int propertyTag, PropertyType propertyType) {
|
||||
|
@ -278,6 +278,7 @@ public class EwsExchangeSession extends ExchangeSession {
|
||||
}
|
||||
|
||||
protected static final HashSet<FieldURI> FOLDER_PROPERTIES = new HashSet<FieldURI>();
|
||||
|
||||
static {
|
||||
FOLDER_PROPERTIES.add(ExtendedFieldURI.PR_URL_COMP_NAME);
|
||||
FOLDER_PROPERTIES.add(ExtendedFieldURI.PR_LAST_MODIFICATION_TIME);
|
||||
@ -313,7 +314,7 @@ public class EwsExchangeSession extends ExchangeSession {
|
||||
String parentFolderPath, FolderId parentFolderId,
|
||||
Condition condition, boolean recursive) throws IOException {
|
||||
FindFolderMethod findFolderMethod = new FindFolderMethod(FolderQueryTraversal.SHALLOW,
|
||||
BaseShape.ID_ONLY, parentFolderId, FOLDER_PROPERTIES, (SearchExpression)condition);
|
||||
BaseShape.ID_ONLY, parentFolderId, FOLDER_PROPERTIES, (SearchExpression) condition);
|
||||
try {
|
||||
httpClient.executeMethod(findFolderMethod);
|
||||
} finally {
|
||||
@ -395,6 +396,31 @@ public class EwsExchangeSession extends ExchangeSession {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Contact> searchContacts(String folderName, List<String> attributes, Condition condition) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Event> searchEvents(String folderPath, List<String> attributes, Condition condition) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item getItem(String folderPath, String itemName) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemResult internalCreateOrUpdateContact(String messageUrl, String contentClass, String icsBody, String etag, String noneMatch) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemResult internalCreateOrUpdateEvent(String messageUrl, String contentClass, String icsBody, String etag, String noneMatch) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
private FolderId getFolderId(String folderPath) throws IOException {
|
||||
String[] folderNames;
|
||||
|
Loading…
Reference in New Issue
Block a user