1
0
mirror of https://github.com/moparisthebest/davmail synced 2024-11-11 20:05:03 -05:00

Caldav: Implement Carddav create (only a few attributes mapped)

git-svn-id: http://svn.code.sf.net/p/davmail/code/trunk@1036 3d1905a2-6b24-0410-a738-b14d5a86fcbd
This commit is contained in:
mguessan 2010-05-07 08:30:12 +00:00
parent 0faf2497db
commit 956f1b26a0
2 changed files with 209 additions and 64 deletions

View File

@ -263,8 +263,8 @@ public class CaldavConnection extends AbstractConnection {
} else if (request.isPut()) { } else if (request.isPut()) {
String etag = request.getHeader("if-match"); String etag = request.getHeader("if-match");
String noneMatch = request.getHeader("if-none-match"); String noneMatch = request.getHeader("if-none-match");
ExchangeSession.EventResult eventResult = session.createOrUpdateEvent(request.getExchangeFolderPath(), lastPath, request.getBody(), etag, noneMatch); ExchangeSession.ItemResult itemResult = session.createOrUpdateItem(request.getExchangeFolderPath(), lastPath, request.getBody(), etag, noneMatch);
sendHttpResponse(eventResult.status, buildEtagHeader(eventResult.etag), null, "", true); sendHttpResponse(itemResult.status, buildEtagHeader(itemResult.etag), null, "", true);
} else if (request.isDelete()) { } else if (request.isDelete()) {
int status = session.deleteEvent(request.getExchangeFolderPath(), lastPath); int status = session.deleteEvent(request.getExchangeFolderPath(), lastPath);

View File

@ -103,6 +103,7 @@ public class ExchangeSession {
protected static final int FREE_BUSY_INTERVAL = 15; protected static final int FREE_BUSY_INTERVAL = 15;
protected static final Namespace DAV = Namespace.getNamespace("DAV:");
protected static final Namespace URN_SCHEMAS_HTTPMAIL = Namespace.getNamespace("urn:schemas:httpmail:"); protected static final Namespace URN_SCHEMAS_HTTPMAIL = Namespace.getNamespace("urn:schemas:httpmail:");
protected static final Namespace SCHEMAS_EXCHANGE = Namespace.getNamespace("http://schemas.microsoft.com/exchange/"); protected static final Namespace SCHEMAS_EXCHANGE = Namespace.getNamespace("http://schemas.microsoft.com/exchange/");
protected static final Namespace SCHEMAS_MAPI_PROPTAG = Namespace.getNamespace("http://schemas.microsoft.com/mapi/proptag/"); protected static final Namespace SCHEMAS_MAPI_PROPTAG = Namespace.getNamespace("http://schemas.microsoft.com/mapi/proptag/");
@ -114,7 +115,7 @@ public class ExchangeSession {
static { static {
EVENT_REQUEST_PROPERTIES.add(DavPropertyName.create("permanenturl", SCHEMAS_EXCHANGE)); EVENT_REQUEST_PROPERTIES.add(DavPropertyName.create("permanenturl", SCHEMAS_EXCHANGE));
EVENT_REQUEST_PROPERTIES.add(DavPropertyName.GETETAG); EVENT_REQUEST_PROPERTIES.add(DavPropertyName.GETETAG);
EVENT_REQUEST_PROPERTIES.add(DavPropertyName.create("contentclass", Namespace.getNamespace("DAV:"))); EVENT_REQUEST_PROPERTIES.add(DavPropertyName.create("contentclass", DAV));
} }
protected static final DavPropertyNameSet WELL_KNOWN_FOLDERS = new DavPropertyNameSet(); protected static final DavPropertyNameSet WELL_KNOWN_FOLDERS = new DavPropertyNameSet();
@ -138,7 +139,7 @@ public class ExchangeSession {
protected static final DavPropertyNameSet FOLDER_PROPERTIES = new DavPropertyNameSet(); protected static final DavPropertyNameSet FOLDER_PROPERTIES = new DavPropertyNameSet();
static { static {
FOLDER_PROPERTIES.add(DavPropertyName.create("contentclass", Namespace.getNamespace("DAV:"))); FOLDER_PROPERTIES.add(DavPropertyName.create("contentclass", DAV));
FOLDER_PROPERTIES.add(DavPropertyName.create("hassubs")); FOLDER_PROPERTIES.add(DavPropertyName.create("hassubs"));
FOLDER_PROPERTIES.add(DavPropertyName.create("nosubs")); FOLDER_PROPERTIES.add(DavPropertyName.create("nosubs"));
FOLDER_PROPERTIES.add(DavPropertyName.create("unreadcount", URN_SCHEMAS_HTTPMAIL)); FOLDER_PROPERTIES.add(DavPropertyName.create("unreadcount", URN_SCHEMAS_HTTPMAIL));
@ -779,7 +780,7 @@ public class ExchangeSession {
message.permanentUrl = getPropertyIfExists(properties, "permanenturl", SCHEMAS_EXCHANGE); message.permanentUrl = getPropertyIfExists(properties, "permanenturl", SCHEMAS_EXCHANGE);
message.size = getIntPropertyIfExists(properties, "x0e080003", SCHEMAS_MAPI_PROPTAG); message.size = getIntPropertyIfExists(properties, "x0e080003", SCHEMAS_MAPI_PROPTAG);
message.uid = getPropertyIfExists(properties, "uid", Namespace.getNamespace("DAV:")); message.uid = getPropertyIfExists(properties, "uid", DAV);
message.imapUid = getLongPropertyIfExists(properties, "x0e230003", SCHEMAS_MAPI_PROPTAG); message.imapUid = getLongPropertyIfExists(properties, "x0e230003", SCHEMAS_MAPI_PROPTAG);
message.read = "1".equals(getPropertyIfExists(properties, "read", URN_SCHEMAS_HTTPMAIL)); message.read = "1".equals(getPropertyIfExists(properties, "read", URN_SCHEMAS_HTTPMAIL));
message.junk = "1".equals(getPropertyIfExists(properties, "x10830003", SCHEMAS_MAPI_PROPTAG)); message.junk = "1".equals(getPropertyIfExists(properties, "x10830003", SCHEMAS_MAPI_PROPTAG));
@ -982,9 +983,9 @@ public class ExchangeSession {
String href = URIUtil.decode(entity.getHref()); String href = URIUtil.decode(entity.getHref());
Folder folder = new Folder(); Folder folder = new Folder();
DavPropertySet properties = entity.getProperties(HttpStatus.SC_OK); DavPropertySet properties = entity.getProperties(HttpStatus.SC_OK);
folder.contentClass = getPropertyIfExists(properties, "contentclass", Namespace.getNamespace("DAV:")); folder.contentClass = getPropertyIfExists(properties, "contentclass", DAV);
folder.hasChildren = "1".equals(getPropertyIfExists(properties, "hassubs", Namespace.getNamespace("DAV:"))); folder.hasChildren = "1".equals(getPropertyIfExists(properties, "hassubs", DAV));
folder.noInferiors = "1".equals(getPropertyIfExists(properties, "nosubs", Namespace.getNamespace("DAV:"))); folder.noInferiors = "1".equals(getPropertyIfExists(properties, "nosubs", DAV));
folder.unreadCount = getIntPropertyIfExists(properties, "unreadcount", URN_SCHEMAS_HTTPMAIL); folder.unreadCount = getIntPropertyIfExists(properties, "unreadcount", URN_SCHEMAS_HTTPMAIL);
folder.ctag = getPropertyIfExists(properties, "contenttag", Namespace.getNamespace("http://schemas.microsoft.com/repl/")); folder.ctag = getPropertyIfExists(properties, "contenttag", Namespace.getNamespace("http://schemas.microsoft.com/repl/"));
folder.etag = getPropertyIfExists(properties, "resourcetag", Namespace.getNamespace("http://schemas.microsoft.com/repl/")); folder.etag = getPropertyIfExists(properties, "resourcetag", Namespace.getNamespace("http://schemas.microsoft.com/repl/"));
@ -1769,8 +1770,9 @@ public class ExchangeSession {
/** /**
* Get message body size. * Get message body size.
*
* @return mime message size * @return mime message size
* @throws IOException on error * @throws IOException on error
* @throws MessagingException on error * @throws MessagingException on error
*/ */
public int getMimeMessageSize() throws IOException, MessagingException { public int getMimeMessageSize() throws IOException, MessagingException {
@ -1781,8 +1783,9 @@ public class ExchangeSession {
/** /**
* Get message body input stream. * Get message body input stream.
*
* @return mime message InputStream * @return mime message InputStream
* @throws IOException on error * @throws IOException on error
* @throws MessagingException on error * @throws MessagingException on error
*/ */
public InputStream getRawInputStream() throws IOException, MessagingException { public InputStream getRawInputStream() throws IOException, MessagingException {
@ -1897,21 +1900,38 @@ public class ExchangeSession {
protected String etag; protected String etag;
protected String contentClass; protected String contentClass;
protected String noneMatch; protected String noneMatch;
/**
* ICS content
*/
protected String itemBody;
/** /**
* Create empty Item. * Build item instance.
*
* @param messageUrl message url
* @param contentClass content class
* @param itemBody item body
* @param etag item etag
* @param noneMatch none match flag
*/ */
public Item() { public Item(String messageUrl, String contentClass, String itemBody, String etag, String noneMatch) {
this.href = messageUrl;
this.contentClass = contentClass;
this.itemBody = itemBody;
this.etag = etag;
this.noneMatch = noneMatch;
} }
/** /**
* Return item content type * Return item content type
*
* @return content type * @return content type
*/ */
public abstract String getContentType(); public abstract String getContentType();
/** /**
* Retrieve item body from Exchange * Retrieve item body from Exchange
*
* @return item body * @return item body
* @throws HttpException on error * @throws HttpException on error
*/ */
@ -1919,14 +1939,15 @@ public class ExchangeSession {
/** /**
* Build Item instance from multistatusResponse info * Build Item instance from multistatusResponse info
*
* @param multiStatusResponse response * @param multiStatusResponse response
* @throws URIException on error * @throws URIException on error
*/ */
public Item(MultiStatusResponse multiStatusResponse) throws URIException { public Item(MultiStatusResponse multiStatusResponse) throws URIException {
href = URIUtil.decode(multiStatusResponse.getHref()); href = URIUtil.decode(multiStatusResponse.getHref());
permanentUrl = getPropertyIfExists(multiStatusResponse.getProperties(HttpStatus.SC_OK), "permanenturl", SCHEMAS_EXCHANGE); permanentUrl = getPropertyIfExists(multiStatusResponse.getProperties(HttpStatus.SC_OK), "permanenturl", SCHEMAS_EXCHANGE);
etag = getPropertyIfExists(multiStatusResponse.getProperties(HttpStatus.SC_OK), "getetag", Namespace.getNamespace("DAV:")); etag = getPropertyIfExists(multiStatusResponse.getProperties(HttpStatus.SC_OK), "getetag", DAV);
displayName = getPropertyIfExists(multiStatusResponse.getProperties(HttpStatus.SC_OK), "displayname", Namespace.getNamespace("DAV:")); displayName = getPropertyIfExists(multiStatusResponse.getProperties(HttpStatus.SC_OK), "displayname", DAV);
} }
/** /**
@ -1958,12 +1979,14 @@ public class ExchangeSession {
return new HttpException(message); return new HttpException(message);
} }
} }
/** /**
* Calendar event object * Calendar event object
*/ */
public class Contact extends Item { public class Contact extends Item {
/** /**
* Build Contact instance from multistatusResponse info * Build Contact instance from multistatusResponse info
*
* @param multiStatusResponse response * @param multiStatusResponse response
* @throws URIException on error * @throws URIException on error
*/ */
@ -1971,6 +1994,42 @@ public class ExchangeSession {
super(multiStatusResponse); super(multiStatusResponse);
} }
/**
* {@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);
}
/**
* Convert EML extension to vcf.
*
* @return item name
*/
@Override
public String getName() {
String name = super.getName();
if (name.endsWith(".EML")) {
name = name.substring(0, name.length() - 3) + "vcf";
}
return name;
}
/**
* Compute vcard uid from name.
*
* @return uid
* @throws URIException on error
*/
protected String getUid() throws URIException {
String uid = getName();
int dotIndex = uid.lastIndexOf('.');
if (dotIndex > 0) {
uid = uid.substring(0, dotIndex);
}
return URIUtil.encodePath(uid);
}
@Override @Override
public String getContentType() { public String getContentType() {
return "text/vcard"; return "text/vcard";
@ -1993,7 +2052,7 @@ public class ExchangeSession {
writer.writeLine("BEGIN:VCARD"); writer.writeLine("BEGIN:VCARD");
writer.writeLine("VERSION:3.0"); writer.writeLine("VERSION:3.0");
writer.write("UID:"); writer.write("UID:");
writer.writeLine(URIUtil.encodePath(getName())); writer.writeLine(getUid());
writer.write("FN:"); writer.write("FN:");
writer.writeLine(getPropertyIfExists(properties, DavPropertyName.create("cn", URN_SCHEMAS_CONTACTS), "")); writer.writeLine(getPropertyIfExists(properties, DavPropertyName.create("cn", URN_SCHEMAS_CONTACTS), ""));
// RFC 2426: Family Name, Given Name, Additional Names, Honorific Prefixes, and Honorific Suffixes // RFC 2426: Family Name, Given Name, Additional Names, Honorific Prefixes, and Honorific Suffixes
@ -2026,25 +2085,98 @@ public class ExchangeSession {
throw buildHttpException(e); throw buildHttpException(e);
} finally { } finally {
if (propFindMethod != null) { if (propFindMethod != null) {
propFindMethod.releaseConnection(); propFindMethod.releaseConnection();
} }
} }
return result; return result;
}
protected List<DavProperty> buildProperties() throws IOException {
ArrayList<DavProperty> list = new ArrayList<DavProperty>();
list.add(new DefaultDavProperty(DavPropertyName.create("contentclass", DAV), contentClass));
list.add(new DefaultDavProperty(DavPropertyName.create("outlookmessageclass", SCHEMAS_EXCHANGE), "IPM.Contact"));
ICSBufferedReader reader = new ICSBufferedReader(new StringReader(itemBody));
String line;
while ((line = reader.readLine()) != null) {
int index = line.indexOf(':');
if (index >= 0) {
String key = line.substring(0, index);
String value = line.substring(index + 1);
if ("FN".equals(key)) {
list.add(new DefaultDavProperty(DavPropertyName.create("cn", URN_SCHEMAS_CONTACTS), value));
list.add(new DefaultDavProperty(DavPropertyName.create("subject", URN_SCHEMAS_HTTPMAIL), value));
list.add(new DefaultDavProperty(DavPropertyName.create("fileas", URN_SCHEMAS_CONTACTS), value));
} else if ("N".equals(key)) {
String[] values = value.split(";");
if (values.length > 0) {
list.add(new DefaultDavProperty(DavPropertyName.create("sn", URN_SCHEMAS_CONTACTS), values[0]));
}
if (values.length > 1) {
list.add(new DefaultDavProperty(DavPropertyName.create("givenName", URN_SCHEMAS_CONTACTS), values[1]));
}
} else if ("TEL;TYPE=cell".equals(key)) {
list.add(new DefaultDavProperty(DavPropertyName.create("mobile", URN_SCHEMAS_CONTACTS), value));
} else if ("TEL;TYPE=work".equals(key)) {
list.add(new DefaultDavProperty(DavPropertyName.create("telephoneNumber", URN_SCHEMAS_CONTACTS), value));
} else if ("TEL;TYPE=home".equals(key)) {
list.add(new DefaultDavProperty(DavPropertyName.create("homePhone", URN_SCHEMAS_CONTACTS), value));
}
}
}
return list;
}
protected ItemResult createOrUpdate() throws IOException {
int status = 0;
PropPatchMethod propPatchMethod = new PropPatchMethod(URIUtil.encodePath(href), buildProperties());
propPatchMethod.setRequestHeader("Translate", "f");
if (etag != null) {
propPatchMethod.setRequestHeader("If-Match", etag);
}
if (noneMatch != null) {
propPatchMethod.setRequestHeader("If-None-Match", noneMatch);
}
try {
status = httpClient.executeMethod(propPatchMethod);
if (status == HttpStatus.SC_MULTI_STATUS) {
if (etag != null) {
LOGGER.debug("Updated contact " + href);
} else {
LOGGER.warn("Overwritten contact " + href);
}
status = HttpStatus.SC_CREATED;
} else {
LOGGER.warn("Unable to create or update contact " + status + ' ' + propPatchMethod.getStatusLine());
}
} finally {
propPatchMethod.releaseConnection();
}
ItemResult itemResult = new ItemResult();
// 440 means forbidden on Exchange
if (status == 440) {
status = HttpStatus.SC_FORBIDDEN;
}
itemResult.status = status;
if (propPatchMethod.getResponseHeader("GetETag") != null) {
itemResult.etag = propPatchMethod.getResponseHeader("GetETag").getValue();
}
return itemResult;
} }
} }
/** /**
* Calendar event object * Calendar event object.
*/ */
public class Event extends Item { public class Event extends Item {
/** /**
* ICS content * Build Event instance from response info.
*/ *
protected String icsBody;
/**
* Build Event instance from multistatusResponse info
* @param multiStatusResponse response * @param multiStatusResponse response
* @throws URIException on error * @throws URIException on error
*/ */
@ -2053,9 +2185,10 @@ public class ExchangeSession {
} }
/** /**
* Create empty event. * {@inheritDoc}
*/ */
public Event() { public Event(String messageUrl, String contentClass, String itemBody, String etag, String noneMatch) {
super(messageUrl, contentClass, itemBody, etag, noneMatch);
} }
@Override @Override
@ -2567,7 +2700,7 @@ public class ExchangeSession {
String organizer = null; String organizer = null;
BufferedReader reader = null; BufferedReader reader = null;
try { try {
reader = new ICSBufferedReader(new StringReader(icsBody)); reader = new ICSBufferedReader(new StringReader(itemBody));
String line; String line;
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
int index = line.indexOf(':'); int index = line.indexOf(':');
@ -2627,7 +2760,7 @@ public class ExchangeSession {
return icsMethod; return icsMethod;
} }
protected EventResult createOrUpdate() throws IOException { protected ItemResult createOrUpdate() throws IOException {
String boundary = UUID.randomUUID().toString(); String boundary = UUID.randomUUID().toString();
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
MimeOutputStreamWriter writer = new MimeOutputStreamWriter(baos); MimeOutputStreamWriter writer = new MimeOutputStreamWriter(baos);
@ -2642,7 +2775,7 @@ public class ExchangeSession {
putmethod.setRequestHeader("If-None-Match", noneMatch); putmethod.setRequestHeader("If-None-Match", noneMatch);
} }
putmethod.setRequestHeader("Content-Type", "message/rfc822"); putmethod.setRequestHeader("Content-Type", "message/rfc822");
String method = getICSMethod(icsBody); String method = getICSMethod(itemBody);
writer.writeHeader("Content-Transfer-Encoding", "7bit"); writer.writeHeader("Content-Transfer-Encoding", "7bit");
writer.writeHeader("Content-class", contentClass); writer.writeHeader("Content-class", contentClass);
@ -2650,7 +2783,7 @@ public class ExchangeSession {
writer.writeHeader("Date", new Date()); writer.writeHeader("Date", new Date());
// Make sure invites have a proper subject line // Make sure invites have a proper subject line
writer.writeHeader("Subject", getICSSummary(icsBody)); writer.writeHeader("Subject", getICSSummary(itemBody));
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
@ -2691,11 +2824,11 @@ public class ExchangeSession {
} }
// if not organizer, set REPLYTIME to force Outlook in attendee mode // if not organizer, set REPLYTIME to force Outlook in attendee mode
if (participants.organizer != null && !email.equalsIgnoreCase(participants.organizer)) { if (participants.organizer != null && !email.equalsIgnoreCase(participants.organizer)) {
if (icsBody.indexOf("METHOD:") < 0) { if (itemBody.indexOf("METHOD:") < 0) {
icsBody = icsBody.replaceAll("BEGIN:VCALENDAR", "BEGIN:VCALENDAR\r\nMETHOD:REQUEST"); itemBody = itemBody.replaceAll("BEGIN:VCALENDAR", "BEGIN:VCALENDAR\r\nMETHOD:REQUEST");
} }
if (icsBody.indexOf("X-MICROSOFT-CDO-REPLYTIME") < 0) { if (itemBody.indexOf("X-MICROSOFT-CDO-REPLYTIME") < 0) {
icsBody = icsBody.replaceAll("END:VEVENT", "X-MICROSOFT-CDO-REPLYTIME:" + itemBody = itemBody.replaceAll("END:VEVENT", "X-MICROSOFT-CDO-REPLYTIME:" +
getZuluDateFormat().format(new Date()) + "\r\nEND:VEVENT"); getZuluDateFormat().format(new Date()) + "\r\nEND:VEVENT");
} }
} }
@ -2710,7 +2843,7 @@ public class ExchangeSession {
// 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
String description = getICSDescription(icsBody).replaceAll("\\\\[Nn]", "\r\n"); String description = getICSDescription(itemBody).replaceAll("\\\\[Nn]", "\r\n");
if (description.length() > 0) { if (description.length() > 0) {
writer.writeHeader("Content-Type", "text/plain;\r\n" + writer.writeHeader("Content-Type", "text/plain;\r\n" +
@ -2730,7 +2863,7 @@ public class ExchangeSession {
writer.writeHeader("Content-Transfer-Encoding", "8bit"); writer.writeHeader("Content-Transfer-Encoding", "8bit");
writer.writeLn(); writer.writeLn();
writer.flush(); writer.flush();
baos.write(fixICS(icsBody, false).getBytes("UTF-8")); baos.write(fixICS(itemBody, false).getBytes("UTF-8"));
writer.writeLn(); writer.writeLn();
writer.writeLn("------=_NextPart_" + boundary + "--"); writer.writeLn("------=_NextPart_" + boundary + "--");
writer.close(); writer.close();
@ -2751,14 +2884,14 @@ public class ExchangeSession {
} finally { } finally {
putmethod.releaseConnection(); putmethod.releaseConnection();
} }
EventResult eventResult = new EventResult(); ItemResult itemResult = new ItemResult();
// 440 means forbidden on Exchange // 440 means forbidden on Exchange
if (status == 440) { if (status == 440) {
status = HttpStatus.SC_FORBIDDEN; status = HttpStatus.SC_FORBIDDEN;
} }
eventResult.status = status; itemResult.status = status;
if (putmethod.getResponseHeader("GetETag") != null) { if (putmethod.getResponseHeader("GetETag") != null) {
eventResult.etag = putmethod.getResponseHeader("GetETag").getValue(); itemResult.etag = putmethod.getResponseHeader("GetETag").getValue();
} }
// trigger activeSync push event, only if davmail.forceActiveSyncUpdate setting is true // trigger activeSync push event, only if davmail.forceActiveSyncUpdate setting is true
@ -2766,7 +2899,7 @@ public class ExchangeSession {
(Settings.getBooleanProperty("davmail.forceActiveSyncUpdate"))) { (Settings.getBooleanProperty("davmail.forceActiveSyncUpdate"))) {
ArrayList<DavProperty> propertyList = new ArrayList<DavProperty>(); ArrayList<DavProperty> propertyList = new ArrayList<DavProperty>();
// Set contentclass to make ActiveSync happy // Set contentclass to make ActiveSync happy
propertyList.add(new DefaultDavProperty(DavPropertyName.create("contentclass", Namespace.getNamespace("DAV:")), contentClass)); propertyList.add(new DefaultDavProperty(DavPropertyName.create("contentclass", DAV), contentClass));
// ... but also set PR_INTERNET_CONTENT to preserve custom properties // ... but also set PR_INTERNET_CONTENT to preserve custom properties
propertyList.add(new DefaultDavProperty(PR_INTERNET_CONTENT, new String(Base64.encodeBase64(baos.toByteArray())))); propertyList.add(new DefaultDavProperty(PR_INTERNET_CONTENT, new String(Base64.encodeBase64(baos.toByteArray()))));
PropPatchMethod propPatchMethod = new PropPatchMethod(URIUtil.encodePath(href), propertyList); PropPatchMethod propPatchMethod = new PropPatchMethod(URIUtil.encodePath(href), propertyList);
@ -2776,10 +2909,10 @@ public class ExchangeSession {
} else { } else {
// need to retrieve new etag // need to retrieve new etag
Item newItem = getItem(href); Item newItem = getItem(href);
eventResult.etag = newItem.etag; itemResult.etag = newItem.etag;
} }
} }
return eventResult; return itemResult;
} }
} }
@ -2836,7 +2969,7 @@ public class ExchangeSession {
} catch (HttpException e) { } catch (HttpException e) {
// failover to DAV:comment property on some Exchange servers // failover to DAV:comment property on some Exchange servers
if (DEFAULT_SCHEDULE_STATE_PROPERTY.equals(scheduleStateProperty)) { if (DEFAULT_SCHEDULE_STATE_PROPERTY.equals(scheduleStateProperty)) {
scheduleStateProperty = DavPropertyName.create("comment", Namespace.getNamespace("DAV:")); scheduleStateProperty = DavPropertyName.create("comment", DAV);
result = getEventMessages(folderPath); result = getEventMessages(folderPath);
} else { } else {
throw e; throw e;
@ -2918,12 +3051,18 @@ public class ExchangeSession {
* Get item named eventName in folder * Get item named eventName in folder
* *
* @param folderPath Exchange folder path * @param folderPath Exchange folder path
* @param itemName event name * @param itemName event name
* @return event object * @return event object
* @throws IOException on error * @throws IOException on error
*/ */
public Item getItem(String folderPath, String itemName) throws IOException { public Item getItem(String folderPath, String itemName) throws IOException {
String itemPath = folderPath + '/' + itemName; String itemPath;
// convert vcf extension to EML
if (itemName.endsWith(".vcf")) {
itemPath = folderPath + '/' + itemName.substring(0, itemName.length() - 3) + "EML";
} else {
itemPath = folderPath + '/' + itemName;
}
Item item; Item item;
try { try {
item = getItem(itemPath); item = getItem(itemPath);
@ -2953,11 +3092,11 @@ public class ExchangeSession {
if (responses.length == 0) { if (responses.length == 0) {
throw new DavMailException("EXCEPTION_EVENT_NOT_FOUND"); throw new DavMailException("EXCEPTION_EVENT_NOT_FOUND");
} }
String contentClass = getPropertyIfExists( responses[0].getProperties(HttpStatus.SC_OK), String contentClass = getPropertyIfExists(responses[0].getProperties(HttpStatus.SC_OK),
"contentclass", Namespace.getNamespace("DAV:")); "contentclass", DAV);
if ("urn:content-classes:person".equals(contentClass)) { if ("urn:content-classes:person".equals(contentClass)) {
return new Contact(responses[0]); return new Contact(responses[0]);
} else if ("urn:content-classes:appointment".equals(contentClass)){ } else if ("urn:content-classes:appointment".equals(contentClass)) {
return new Event(responses[0]); return new Event(responses[0]);
} else { } else {
throw new DavMailException("EXCEPTION_EVENT_NOT_FOUND"); throw new DavMailException("EXCEPTION_EVENT_NOT_FOUND");
@ -3008,7 +3147,7 @@ public class ExchangeSession {
/** /**
* Event result object to hold HTTP status and event etag from an event creation/update. * Event result object to hold HTTP status and event etag from an event creation/update.
*/ */
public static class EventResult { public static class ItemResult {
/** /**
* HTTP status * HTTP status
*/ */
@ -3042,28 +3181,34 @@ public class ExchangeSession {
} }
/** /**
* Create or update event on the Exchange server * Create or update item (event or contact) on the Exchange server
* *
* @param folderPath Exchange folder path * @param folderPath Exchange folder path
* @param eventName event name * @param itemName event name
* @param icsBody event body in iCalendar format * @param itemBody event body in iCalendar format
* @param etag previous event etag to detect concurrent updates * @param etag previous event etag to detect concurrent updates
* @param noneMatch if-none-match header value * @param noneMatch if-none-match header value
* @return HTTP response event result (status and etag) * @return HTTP response event result (status and etag)
* @throws IOException on error * @throws IOException on error
*/ */
public EventResult createOrUpdateEvent(String folderPath, String eventName, String icsBody, String etag, String noneMatch) throws IOException { public ItemResult createOrUpdateItem(String folderPath, String itemName, String itemBody, String etag, String noneMatch) throws IOException {
String messageUrl = folderPath + '/' + eventName; String messageUrl = folderPath + '/' + itemName;
return internalCreateOrUpdateEvent(messageUrl, "urn:content-classes:appointment", icsBody, etag, noneMatch); if (itemBody.startsWith("BEGIN:VCALENDAR")) {
return internalCreateOrUpdateEvent(messageUrl, "urn:content-classes:appointment", itemBody, etag, noneMatch);
} else if (itemBody.startsWith("BEGIN:VCARD")) {
return internalCreateOrUpdateContact(messageUrl, "urn:content-classes:person", itemBody, etag, noneMatch);
} else {
throw new IOException(BundleMessage.format("EXCEPTION_INVALID_MESSAGE_CONTENT", itemBody));
}
} }
protected EventResult internalCreateOrUpdateEvent(String messageUrl, String contentClass, String icsBody, String etag, String noneMatch) throws IOException { protected ItemResult internalCreateOrUpdateContact(String messageUrl, String contentClass, String icsBody, String etag, String noneMatch) throws IOException {
Event event = new Event(); Contact contact = new Contact(messageUrl, contentClass, icsBody, etag, noneMatch);
event.contentClass = contentClass; return contact.createOrUpdate();
event.icsBody = icsBody; }
event.href = messageUrl;
event.etag = etag; protected ItemResult internalCreateOrUpdateEvent(String messageUrl, String contentClass, String icsBody, String etag, String noneMatch) throws IOException {
event.noneMatch = noneMatch; Event event = new Event(messageUrl, contentClass, icsBody, etag, noneMatch);
return event.createOrUpdate(); return event.createOrUpdate();
} }
@ -3156,7 +3301,7 @@ public class ExchangeSession {
if (responses.length == 0) { if (responses.length == 0) {
LOGGER.warn(new BundleMessage("EXCEPTION_UNABLE_TO_GET_MAIL_FOLDER", mailPath)); LOGGER.warn(new BundleMessage("EXCEPTION_UNABLE_TO_GET_MAIL_FOLDER", mailPath));
} else { } else {
displayName = getPropertyIfExists(responses[0].getProperties(HttpStatus.SC_OK), "displayname", Namespace.getNamespace("DAV:")); displayName = getPropertyIfExists(responses[0].getProperties(HttpStatus.SC_OK), "displayname", DAV);
} }
} catch (IOException e) { } catch (IOException e) {
LOGGER.warn(new BundleMessage("EXCEPTION_UNABLE_TO_GET_MAIL_FOLDER", mailPath)); LOGGER.warn(new BundleMessage("EXCEPTION_UNABLE_TO_GET_MAIL_FOLDER", mailPath));
@ -3783,7 +3928,7 @@ public class ExchangeSession {
// failover for Exchange 2007, use PROPPATCH with forced timezone // failover for Exchange 2007, use PROPPATCH with forced timezone
if (fakeEventUrl == null) { if (fakeEventUrl == null) {
ArrayList<DavProperty> propertyList = new ArrayList<DavProperty>(); ArrayList<DavProperty> propertyList = new ArrayList<DavProperty>();
propertyList.add(new DefaultDavProperty(DavPropertyName.create("contentclass", Namespace.getNamespace("DAV:")), "urn:content-classes:appointment")); propertyList.add(new DefaultDavProperty(DavPropertyName.create("contentclass", DAV), "urn:content-classes:appointment"));
propertyList.add(new DefaultDavProperty(DavPropertyName.create("outlookmessageclass", Namespace.getNamespace("http://schemas.microsoft.com/exchange/")), "IPM.Appointment")); propertyList.add(new DefaultDavProperty(DavPropertyName.create("outlookmessageclass", Namespace.getNamespace("http://schemas.microsoft.com/exchange/")), "IPM.Appointment"));
propertyList.add(new DefaultDavProperty(DavPropertyName.create("instancetype", Namespace.getNamespace("urn:schemas:calendar:")), "0")); propertyList.add(new DefaultDavProperty(DavPropertyName.create("instancetype", Namespace.getNamespace("urn:schemas:calendar:")), "0"));
// get forced timezone id from settings // get forced timezone id from settings