Carddav: refactor Contact creation and create VCardReader

git-svn-id: http://svn.code.sf.net/p/davmail/code/trunk@1134 3d1905a2-6b24-0410-a738-b14d5a86fcbd
This commit is contained in:
mguessan 2010-07-06 09:46:55 +00:00
parent 6e6326ac70
commit b988bd2f12
4 changed files with 249 additions and 54 deletions

View File

@ -22,6 +22,7 @@ import davmail.BundleMessage;
import davmail.Settings;
import davmail.exception.DavMailAuthenticationException;
import davmail.exception.DavMailException;
import davmail.exchange.dav.Field;
import davmail.http.DavGatewayHttpClientFacade;
import davmail.http.DavGatewayOTPPrompt;
import davmail.util.StringUtil;
@ -30,6 +31,7 @@ import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.commons.httpclient.util.URIUtil;
import org.apache.jackrabbit.webdav.DavConstants;
import org.apache.log4j.Logger;
import org.htmlcleaner.CommentToken;
import org.htmlcleaner.HtmlCleaner;
@ -1390,6 +1392,7 @@ public abstract class ExchangeSession {
* @throws IOException on error
* @deprecated move to byte array handling instead
*/
@Deprecated
public void write(OutputStream os, boolean doubleDot) throws IOException {
BufferedReader reader = getContentReader(this);
try {
@ -1590,7 +1593,8 @@ public abstract class ExchangeSession {
* Generic folder item.
*/
public abstract static class Item extends HashMap<String, String> {
protected String href;
protected String folderPath;
protected String itemName;
protected String permanentUrl;
/**
* Display name.
@ -1610,16 +1614,14 @@ public abstract class ExchangeSession {
/**
* Build item instance.
*
* @param messageUrl message url
* @param contentClass content class
* @param itemBody item body
* @param etag item etag
* @param noneMatch none match flag
* @param folderPath folder path
* @param itemName item name class
* @param etag item etag
* @param noneMatch none match flag
*/
public Item(String messageUrl, String contentClass, String itemBody, String etag, String noneMatch) {
this.href = messageUrl;
this.contentClass = contentClass;
this.itemBody = itemBody;
public Item(String folderPath, String itemName, String etag, String noneMatch) {
this.folderPath = folderPath;
this.itemName = itemName;
this.etag = etag;
this.noneMatch = noneMatch;
}
@ -1651,12 +1653,7 @@ public abstract class ExchangeSession {
* @return event name
*/
public String getName() {
int index = href.lastIndexOf('/');
if (index >= 0) {
return href.substring(index + 1);
} else {
return href;
}
return itemName;
}
/**
@ -1673,6 +1670,20 @@ public abstract class ExchangeSession {
LOGGER.warn(message);
return new HttpException(message);
}
public void setHref(String href) {
int index = href.lastIndexOf('/');
if (index >= 0) {
folderPath = href.substring(0, index);
itemName = href.substring(index + 1);
} else {
throw new IllegalArgumentException(href);
}
}
public String getHref() {
return folderPath + '/' + itemName;
}
}
/**
@ -1683,8 +1694,9 @@ public abstract class ExchangeSession {
/**
* @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);
public Contact(String folderPath, String itemName, Map<String, String> properties, String etag, String noneMatch) {
super(folderPath, itemName.endsWith(".vcf") ? itemName.substring(0, itemName.length() - 3) + "EML" : itemName, etag, noneMatch);
this.putAll(properties);
}
/**
@ -1760,7 +1772,7 @@ public abstract class ExchangeSession {
writer.appendProperty("ORG", get("o"), get("department"));
writer.appendProperty("URL;WORK", get("businesshomepage"));
writer.appendProperty("TITLE", get("title"));
writer.appendProperty("NOTE", get("textdescription"));
writer.appendProperty("NOTE", get("description"));
writer.appendProperty("CUSTOM1", get("extensionattribute1"));
writer.appendProperty("CUSTOM2", get("extensionattribute2"));
@ -1796,12 +1808,16 @@ public abstract class ExchangeSession {
* Calendar event object.
*/
public abstract class Event extends Item {
protected String contentClass;
protected String itemBody;
/**
* @inheritDoc
*/
public Event(String messageUrl, String contentClass, String itemBody, String etag, String noneMatch) {
super(messageUrl, contentClass, itemBody, etag, noneMatch);
public Event(String folderPath, String itemName, String contentClass, String itemBody, String etag, String noneMatch) {
super(folderPath, itemName, etag, noneMatch);
this.contentClass = contentClass;
this.itemBody = itemBody;
}
/**
@ -2601,19 +2617,55 @@ public abstract class ExchangeSession {
* @throws IOException on error
*/
public ItemResult createOrUpdateItem(String folderPath, String itemName, String itemBody, String etag, String noneMatch) throws IOException {
String messageUrl = folderPath + '/' + itemName;
if (itemBody.startsWith("BEGIN:VCALENDAR")) {
return internalCreateOrUpdateEvent(messageUrl, "urn:content-classes:appointment", itemBody, etag, noneMatch);
return internalCreateOrUpdateEvent(folderPath, itemName, "urn:content-classes:appointment", itemBody, etag, noneMatch);
} else if (itemBody.startsWith("BEGIN:VCARD")) {
return internalCreateOrUpdateContact(messageUrl, "urn:content-classes:person", itemBody, etag, noneMatch);
return createOrUpdateContact(folderPath, itemName, itemBody, etag, noneMatch);
} else {
throw new IOException(BundleMessage.format("EXCEPTION_INVALID_MESSAGE_CONTENT", itemBody));
}
}
protected abstract ItemResult internalCreateOrUpdateContact(String messageUrl, String contentClass, String icsBody, String etag, String noneMatch) throws IOException;
protected ItemResult createOrUpdateContact(String folderPath, String itemName, String itemBody, String etag, String noneMatch) throws IOException {
// parse VCARD body to build contact property map
Map<String, String> properties = new HashMap<String, String>();
properties.put("outlookmessageclass", "IPM.Contact");
protected abstract ItemResult internalCreateOrUpdateEvent(String messageUrl, String contentClass, String icsBody, String etag, String noneMatch) throws IOException;
VCardReader reader = new VCardReader(new StringReader(itemBody));
VCardReader.Property property;
while ((property = reader.readProperty()) != null) {
if ("FN".equals(property.getKey())) {
properties.put("cn", property.getValue());
properties.put("subject", property.getValue());
properties.put("fileas", property.getValue());
} else if ("N".equals(property.getKey())) {
String[] values = property.getValues();
if (values.length > 0) {
properties.put("sn", values[0]);
}
if (values.length > 1) {
properties.put("givenName", values[1]);
}
} else if ("TEL".equals(property.getKey())) {
if (property.hasParam("TYPE", "cell")) {
properties.put("mobile", property.getValue());
}
if (property.hasParam("TYPE", "work")) {
properties.put("telephoneNumber", property.getValue());
}
if (property.hasParam("TYPE", "home")) {
properties.put("homePhone", property.getValue());
}
}
}
return internalCreateOrUpdateContact(folderPath, itemName, properties, etag, noneMatch);
}
protected abstract ItemResult internalCreateOrUpdateContact(String folderPath, String itemName, Map<String, String> properties, String etag, String noneMatch) throws IOException;
protected abstract ItemResult internalCreateOrUpdateEvent(String folderPath, String itemName, String contentClass, String icsBody, String etag, String noneMatch) throws IOException;
/**
* Get current Exchange alias name from login name
@ -2897,7 +2949,7 @@ public abstract class ExchangeSession {
public static final Set<String> CONTACT_ATTRIBUTES = new HashSet<String>();
static {
CONTACT_ATTRIBUTES.add("uid");
CONTACT_ATTRIBUTES.add("imapUid");
CONTACT_ATTRIBUTES.add("extensionattribute1");
CONTACT_ATTRIBUTES.add("extensionattribute2");
CONTACT_ATTRIBUTES.add("extensionattribute3");
@ -2938,7 +2990,7 @@ public abstract class ExchangeSession {
CONTACT_ATTRIBUTES.add("street");
CONTACT_ATTRIBUTES.add("telephoneNumber");
CONTACT_ATTRIBUTES.add("title");
CONTACT_ATTRIBUTES.add("textdescription");
CONTACT_ATTRIBUTES.add("description");
CONTACT_ATTRIBUTES.add("im");
CONTACT_ATTRIBUTES.add("middlename");
CONTACT_ATTRIBUTES.add("lastmodified");

View File

@ -0,0 +1,143 @@
/*
* DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
* Copyright (C) 2010 Mickael Guessant
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package davmail.exchange;
import java.io.IOException;
import java.io.Reader;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* VCARD reader.
*/
public class VCardReader extends ICSBufferedReader {
/**
* VCard property
*/
public class Property {
protected String key;
protected Map<String, Set<String>> params;
protected String value;
/**
* Property key, without optional parameters (e.g. TEL).
*
* @return key
*/
public String getKey() {
return key;
}
/**
* Property value.
*
* @return value
*/
public String getValue() {
return value;
}
/**
* Property values, split on ;.
*
* @return values
*/
public String[] getValues() {
// TODO: handle protected characters
return value.split(";");
}
public boolean hasParam(String paramName, String paramValue) {
return params.containsKey(paramName) && params.get(paramName).contains(paramValue);
}
protected void addParam(String paramName, Set<String> paramValues) {
if (params == null) {
params = new HashMap<String, Set<String>>();
}
params.put(paramName, paramValues);
}
}
/**
* Create a VCARD reader on the provided reader
*
* @param in input reader
* @throws IOException on error
*/
public VCardReader(Reader in) throws IOException {
super(in);
}
protected static enum State {
KEY, PARAM_NAME, PARAM_VALUE, VALUE
}
public Property readProperty() throws IOException {
Property property = null;
String line = readLine();
if (line != null && !"END:VCARD".equals(line)) {
property = new Property();
State state = State.KEY;
String paramName = null;
Set<String> paramValues = null;
int startIndex = 0;
for (int i = 0; i < line.length(); i++) {
char currentChar = line.charAt(i);
if (state == State.KEY) {
if (currentChar == ':') {
property.key = line.substring(startIndex, i);
state = State.VALUE;
startIndex = i + 1;
} else if (currentChar == ';') {
property.key = line.substring(startIndex, i);
state = State.PARAM_NAME;
startIndex = i + 1;
}
} else if (state == State.PARAM_NAME) {
if (currentChar == '=') {
paramName = line.substring(startIndex, i);
state = State.PARAM_VALUE;
paramValues = new HashSet<String>();
startIndex = i + 1;
}
} else if (state == State.PARAM_VALUE) {
if (currentChar == ':') {
paramValues.add(line.substring(startIndex, i));
property.addParam(paramName, paramValues);
state = State.VALUE;
startIndex = i + 1;
} else if (currentChar == ';') {
paramValues.add(line.substring(startIndex, i));
property.addParam(paramName, paramValues);
state = State.PARAM_NAME;
startIndex = i + 1;
} else if (currentChar == ',') {
paramValues.add(line.substring(startIndex, i));
startIndex = i + 1;
}
}
}
property.value = line.substring(startIndex);
}
return property;
}
}

View File

@ -425,7 +425,7 @@ public class DavExchangeSession extends ExchangeSession {
//noinspection VariableNotUsedInsideIf
if (field.cast != null) {
buffer.append("CAST (\"");
} else if (!isIntValue) {
} else if (!isIntValue && !field.isIntValue()) {
buffer.append('\'');
}
if (Operator.Like == operator) {
@ -437,7 +437,7 @@ public class DavExchangeSession extends ExchangeSession {
}
if (field.cast != null) {
buffer.append("\" as '").append(field.cast).append("')");
} else if (!isIntValue) {
} else if (!isIntValue && !field.isIntValue()) {
buffer.append('\'');
}
}
@ -561,7 +561,7 @@ public class DavExchangeSession extends ExchangeSession {
* @throws URIException on error
*/
public Contact(MultiStatusResponse multiStatusResponse) throws URIException {
href = URIUtil.decode(multiStatusResponse.getHref());
setHref(URIUtil.decode(multiStatusResponse.getHref()));
DavPropertySet properties = multiStatusResponse.getProperties(HttpStatus.SC_OK);
permanentUrl = getPropertyIfExists(properties, "permanenturl");
etag = getPropertyIfExists(properties, "etag");
@ -577,8 +577,8 @@ public class DavExchangeSession extends ExchangeSession {
/**
* @inheritDoc
*/
public Contact(String messageUrl, String contentClass, String itemBody, String etag, String noneMatch) {
super(messageUrl, contentClass, itemBody, etag, noneMatch);
public Contact(String folderPath, String itemName, Map<String, String> properties, String etag, String noneMatch) {
super(folderPath, itemName, properties, etag, noneMatch);
}
protected List<DavConstants> buildProperties() throws IOException {
@ -626,7 +626,7 @@ public class DavExchangeSession extends ExchangeSession {
*/
public ItemResult createOrUpdate() throws IOException {
int status = 0;
PropPatchMethod propPatchMethod = new PropPatchMethod(URIUtil.encodePath(href), buildProperties());
PropPatchMethod propPatchMethod = new PropPatchMethod(URIUtil.encodePath(getHref()), buildProperties());
propPatchMethod.setRequestHeader("Translate", "f");
if (etag != null) {
propPatchMethod.setRequestHeader("If-Match", etag);
@ -643,9 +643,9 @@ public class DavExchangeSession extends ExchangeSession {
}
if (status == HttpStatus.SC_CREATED) {
LOGGER.debug("Created contact " + href);
LOGGER.debug("Created contact " + getHref());
} else {
LOGGER.debug("Updated contact " + href);
LOGGER.debug("Updated contact " + getHref());
}
} else {
LOGGER.warn("Unable to create or update contact " + status + ' ' + propPatchMethod.getStatusLine());
@ -663,7 +663,7 @@ public class DavExchangeSession extends ExchangeSession {
}
itemResult.status = status;
// need to retrieve new etag
HeadMethod headMethod = new HeadMethod(URIUtil.encodePath(href));
HeadMethod headMethod = new HeadMethod(URIUtil.encodePath(getHref()));
try {
httpClient.executeMethod(headMethod);
if (headMethod.getResponseHeader("ETag") != null) {
@ -690,7 +690,7 @@ public class DavExchangeSession extends ExchangeSession {
* @throws URIException on error
*/
public Event(MultiStatusResponse multiStatusResponse) throws URIException {
href = URIUtil.decode(multiStatusResponse.getHref());
setHref(URIUtil.decode(multiStatusResponse.getHref()));
DavPropertySet properties = multiStatusResponse.getProperties(HttpStatus.SC_OK);
permanentUrl = getPropertyIfExists(properties, "permanenturl");
etag = getPropertyIfExists(properties, "etag");
@ -701,8 +701,8 @@ public class DavExchangeSession extends ExchangeSession {
/**
* @inheritDoc
*/
public Event(String messageUrl, String contentClass, String itemBody, String etag, String noneMatch) {
super(messageUrl, contentClass, itemBody, etag, noneMatch);
public Event(String folderPath, String itemName, String contentClass, String itemBody, String etag, String noneMatch) {
super(folderPath, itemName, contentClass, itemBody, etag, noneMatch);
}
/**
@ -803,7 +803,7 @@ public class DavExchangeSession extends ExchangeSession {
*/
@Override
protected ItemResult createOrUpdate(byte[] messageContent) throws IOException {
PutMethod putmethod = new PutMethod(URIUtil.encodePath(href));
PutMethod putmethod = new PutMethod(URIUtil.encodePath(getHref()));
putmethod.setRequestHeader("Translate", "f");
putmethod.setRequestHeader("Overwrite", "f");
if (etag != null) {
@ -819,9 +819,9 @@ public class DavExchangeSession extends ExchangeSession {
status = httpClient.executeMethod(putmethod);
if (status == HttpURLConnection.HTTP_OK) {
if (etag != null) {
LOGGER.debug("Updated event " + href);
LOGGER.debug("Updated event " + getHref());
} else {
LOGGER.warn("Overwritten event " + href);
LOGGER.warn("Overwritten event " + getHref());
}
} else if (status != HttpURLConnection.HTTP_CREATED) {
LOGGER.warn("Unable to create or update message " + status + ' ' + putmethod.getStatusLine());
@ -847,13 +847,13 @@ public class DavExchangeSession extends ExchangeSession {
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);
PropPatchMethod propPatchMethod = new PropPatchMethod(URIUtil.encodePath(getHref()), 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);
Item newItem = getItem(getHref());
itemResult.etag = newItem.etag;
}
}
@ -1201,12 +1201,12 @@ public class DavExchangeSession extends ExchangeSession {
@Override
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;
String itemName = UUID.randomUUID().toString() + ".EML";
int status = internalCreateOrUpdateEvent(draftsUrl, itemName, "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);
MoveMethod method = new MoveMethod(URIUtil.encodePath(draftsUrl + '/' + itemName), URIUtil.encodePath(sendmsgUrl), true);
status = DavGatewayHttpClientFacade.executeHttpMethod(httpClient, method);
if (status != HttpStatus.SC_OK) {
throw DavGatewayHttpClientFacade.buildHttpException(method);
@ -1259,8 +1259,8 @@ public class DavExchangeSession extends ExchangeSession {
}
@Override
public ItemResult internalCreateOrUpdateEvent(String messageUrl, String contentClass, String icsBody, String etag, String noneMatch) throws IOException {
return new Event(messageUrl, contentClass, icsBody, etag, noneMatch).createOrUpdate();
public ItemResult internalCreateOrUpdateEvent(String folderPath, String itemName, String contentClass, String icsBody, String etag, String noneMatch) throws IOException {
return new Event(folderPath, itemName, contentClass, icsBody, etag, noneMatch).createOrUpdate();
}
/**
@ -1373,8 +1373,8 @@ public class DavExchangeSession extends ExchangeSession {
}
@Override
protected ItemResult internalCreateOrUpdateContact(String messageUrl, String contentClass, String icsBody, String etag, String noneMatch) throws IOException {
return new Contact(messageUrl, contentClass, icsBody, etag, noneMatch).createOrUpdate();
protected ItemResult internalCreateOrUpdateContact(String folderPath, String itemName, Map<String, String> properties, String etag, String noneMatch) throws IOException {
return new Contact(folderPath, itemName, properties, etag, noneMatch).createOrUpdate();
}
protected List<DavConstants> buildProperties(Map<String, String> properties) {

View File

@ -658,12 +658,12 @@ public class EwsExchangeSession extends ExchangeSession {
}
@Override
protected ItemResult internalCreateOrUpdateContact(String messageUrl, String contentClass, String icsBody, String etag, String noneMatch) throws IOException {
protected ItemResult internalCreateOrUpdateContact(String folderPath, String itemName, Map<String, String> properties, 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 {
protected ItemResult internalCreateOrUpdateEvent(String folderPath, String itemName, String contentClass, String icsBody, String etag, String noneMatch) throws IOException {
throw new UnsupportedOperationException();
}