From dd2aa81d1e0914e6383723b2a4841d31f95b779a Mon Sep 17 00:00:00 2001 From: mguessan Date: Sat, 24 Jul 2010 09:45:32 +0000 Subject: [PATCH] Dav: new ExchangePropPatchMethod to handle custom exchange propertyupdate and invalid response tag names git-svn-id: http://svn.code.sf.net/p/davmail/code/trunk@1259 3d1905a2-6b24-0410-a738-b14d5a86fcbd --- .../exchange/dav/ExchangePropPatchMethod.java | 259 ++++++++++++++++++ src/java/davmail/exchange/dav/Field.java | 25 +- .../davmail/exchange/dav/PropertyType.java | 28 ++ .../davmail/exchange/dav/PropertyValue.java | 63 +++++ 4 files changed, 362 insertions(+), 13 deletions(-) create mode 100644 src/java/davmail/exchange/dav/ExchangePropPatchMethod.java create mode 100644 src/java/davmail/exchange/dav/PropertyType.java create mode 100644 src/java/davmail/exchange/dav/PropertyValue.java diff --git a/src/java/davmail/exchange/dav/ExchangePropPatchMethod.java b/src/java/davmail/exchange/dav/ExchangePropPatchMethod.java new file mode 100644 index 00000000..0a54f0ec --- /dev/null +++ b/src/java/davmail/exchange/dav/ExchangePropPatchMethod.java @@ -0,0 +1,259 @@ +/* + * 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.dav; + +import davmail.util.StringUtil; +import org.apache.commons.httpclient.*; +import org.apache.commons.httpclient.methods.EntityEnclosingMethod; +import org.apache.commons.httpclient.methods.RequestEntity; +import org.apache.jackrabbit.webdav.MultiStatusResponse; +import org.apache.jackrabbit.webdav.property.DefaultDavProperty; +import org.apache.jackrabbit.webdav.xml.Namespace; +import org.apache.log4j.Logger; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import java.io.*; +import java.util.*; + +/** + * Custom Exchange PROPPATCH method. + * Supports extended property update with type. + */ +public class ExchangePropPatchMethod extends EntityEnclosingMethod { + protected static final Logger logger = Logger.getLogger(ExchangePropPatchMethod.class); + + static final Namespace TYPE_NAMESPACE = Namespace.getNamespace("urn:schemas-microsoft-com:datatypes"); + final Set propertyValues; + + public ExchangePropPatchMethod(String path, Set propertyValues) { + super(path); + this.propertyValues = propertyValues; + setRequestEntity(new RequestEntity() { + byte[] content; + + public boolean isRepeatable() { + return true; + } + + public void writeRequest(OutputStream outputStream) throws IOException { + if (content == null) { + content = generateRequestContent(); + } + outputStream.write(content); + } + + public long getContentLength() { + if (content == null) { + content = generateRequestContent(); + } + return content.length; + } + + public String getContentType() { + return "text/xml;charset=UTF-8"; + } + }); + } + + public byte[] generateRequestContent() { + try { + // build namespace map + int currentChar = 'e'; + final Map nameSpaceMap = new HashMap(); + final Set setPropertyValues = new HashSet(); + final Set deletePropertyValues = new HashSet(); + for (PropertyValue propertyValue : propertyValues) { + // data type namespace + if (!nameSpaceMap.containsKey(TYPE_NAMESPACE) && propertyValue.getType() != null) { + nameSpaceMap.put(TYPE_NAMESPACE, currentChar++); + } + // property namespace + Namespace namespace = propertyValue.getNamespace(); + if (!nameSpaceMap.containsKey(namespace)) { + nameSpaceMap.put(namespace, currentChar++); + } + if (propertyValue.getValue() == null) { + deletePropertyValues.add(propertyValue); + } else { + setPropertyValues.add(propertyValue); + } + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + OutputStreamWriter writer = new OutputStreamWriter(baos, "UTF-8"); + writer.write(" mapEntry : nameSpaceMap.entrySet()) { + writer.write(" xmlns:"); + writer.write((char) mapEntry.getValue().intValue()); + writer.write("=\""); + writer.write(mapEntry.getKey().getURI()); + writer.write("\""); + } + writer.write(">"); + if (!setPropertyValues.isEmpty()) { + writer.write(""); + for (PropertyValue propertyValue : setPropertyValues) { + PropertyType propertyType = propertyValue.getType(); + char nameSpaceChar = (char) nameSpaceMap.get(propertyValue.getNamespace()).intValue(); + writer.write('<'); + writer.write(nameSpaceChar); + writer.write(':'); + writer.write(propertyValue.getName()); + if (propertyType != null) { + writer.write(' '); + writer.write(nameSpaceMap.get(TYPE_NAMESPACE)); + writer.write(":dt=\""); + writer.write(propertyType.toString().toLowerCase()); + writer.write("\""); + } + writer.write('>'); + writer.write(StringUtil.xmlEncode(propertyValue.getValue())); + writer.write("'); + } + writer.write(""); + } + if (!deletePropertyValues.isEmpty()) { + writer.write(""); + for (PropertyValue propertyValue : deletePropertyValues) { + char nameSpaceChar = (char) nameSpaceMap.get(propertyValue.getNamespace()).intValue(); + writer.write('<'); + writer.write(nameSpaceChar); + writer.write(':'); + writer.write(propertyValue.getName()); + writer.write("/>"); + } + writer.write(""); + } + writer.write(""); + writer.close(); + return baos.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + @Override + public String getName() { + return "PROPPATCH"; + } + + /** + * Build a new XMLInputFactory. + * + * @return XML input factory + */ + public static XMLInputFactory getXmlInputFactory() { + XMLInputFactory inputFactory = XMLInputFactory.newInstance(); + inputFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE); + inputFactory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, Boolean.TRUE); + return inputFactory; + } + + protected boolean isStartTag(XMLStreamReader reader, String tagLocalName) { + return (reader.getEventType() == XMLStreamConstants.START_ELEMENT) && (reader.getLocalName().equals(tagLocalName)); + } + + protected boolean isEndTag(XMLStreamReader reader, String tagLocalName) { + return (reader.getEventType() == XMLStreamConstants.END_ELEMENT) && (reader.getLocalName().equals(tagLocalName)); + } + + List responses; + + @Override + protected void processResponseBody(HttpState httpState, HttpConnection httpConnection) { + Header contentTypeHeader = getResponseHeader("Content-Type"); + if (contentTypeHeader != null && "text/xml".equals(contentTypeHeader.getValue())) { + responses = new ArrayList(); + XMLStreamReader reader; + try { + XMLInputFactory xmlInputFactory = getXmlInputFactory(); + reader = xmlInputFactory.createXMLStreamReader(new FilterInputStream(getResponseBodyAsStream()) { + @Override + public int read() throws IOException { + return in.read(); + } + + }); + while (reader.hasNext()) { + reader.next(); + if (isStartTag(reader, "response")) { + handleResponse(reader); + } + } + + } catch (IOException e) { + logger.error("Error while parsing soap response: " + e, e); + } catch (XMLStreamException e) { + logger.error("Error while parsing soap response: " + e, e); + } + } + } + + protected void handleResponse(XMLStreamReader reader) throws XMLStreamException { + MultiStatusResponse multiStatusResponse = null; + int currentStatus = 0; + while (reader.hasNext() && !isEndTag(reader, "response")) { + int event = reader.next(); + if (event == XMLStreamConstants.START_ELEMENT) { + String tagLocalName = reader.getLocalName(); + if ("href".equals(tagLocalName)) { + multiStatusResponse = new MultiStatusResponse(reader.getElementText(), ""); + } else if ("status".equals(tagLocalName)) { + if ("HTTP/1.1 200 OK".equals(reader.getElementText())) { + currentStatus = HttpStatus.SC_OK; + } + } else if (tagLocalName.equals("prop")) { + handleProperty(reader, currentStatus, multiStatusResponse); + } + } + } + + } + + protected void handleProperty(XMLStreamReader reader, int currentStatus, MultiStatusResponse multiStatusResponse) throws XMLStreamException { + while (reader.hasNext() && !isEndTag(reader, "prop")) { + try { + int event = reader.next(); + if (event == XMLStreamConstants.START_ELEMENT) { + String tagLocalName = reader.getLocalName(); + Namespace namespace = Namespace.getNamespace(reader.getNamespaceURI()); + multiStatusResponse.add(new DefaultDavProperty(tagLocalName, reader.getElementText(), namespace)); + } + } catch (XMLStreamException e) { + // ignore, exchange invalid response + logger.debug("Ignore invalid response tag name"); + } + } + } + + public List getResponses() throws HttpException { + if (responses == null) { + throw new HttpException(getStatusLine().toString()); + } + return responses; + } + +} diff --git a/src/java/davmail/exchange/dav/Field.java b/src/java/davmail/exchange/dav/Field.java index 548416e2..143d6379 100644 --- a/src/java/davmail/exchange/dav/Field.java +++ b/src/java/davmail/exchange/dav/Field.java @@ -69,17 +69,6 @@ public class Field { Namespace.getNamespace(SCHEMAS_MAPI_STRING.getURI() + '{' + distinguishedPropertySetMap.get(DistinguishedPropertySetType.InternetHeaders) + "}/"); - - /** - * Property type list from EWS - */ - @SuppressWarnings({"UnusedDeclaration"}) - protected static enum PropertyType { - ApplicationTime, ApplicationTimeArray, Binary, BinaryArray, Boolean, CLSID, CLSIDArray, Currency, CurrencyArray, - Double, DoubleArray, Error, Float, FloatArray, Integer, IntegerArray, Long, LongArray, Null, Object, - ObjectArray, Short, ShortArray, SystemTime, SystemTimeArray, String, StringArray - } - protected static final Map propertyTypeMap = new HashMap(); static { @@ -276,8 +265,14 @@ public class Field { createField(DAV, "ishidden"); + // attachment content + createField("attachDataBinary", 0x3701, PropertyType.Binary); + createField("attachmentContactPhoto", 0x7FFF, PropertyType.Boolean); // PR_ATTACHMENT_CONTACTPHOTO createField("renderingPosition", 0x370B, PropertyType.Integer);// PR_RENDERING_POSITION + //createField("attachFilename", 0x3704, PropertyType.String); // PR_ATTACH_FILENAME + createField("attachExtension", 0x3703, PropertyType.String); // PR_ATTACH_FILENAME + } protected static String toHexString(int propertyTag) { @@ -313,7 +308,11 @@ public class Field { // Common namespace expects hex names name = "0x" + toHexString(propertyTag); } - updateAlias = "_x0030_x" + toHexString(propertyTag); + if ("haspicture".equals(alias)) { + updateAlias = "_x0030_x" + toHexString(propertyTag); + } else { + updateAlias = "_x0030_x" + toHexString(propertyTag); + } Field field = new Field(alias, Namespace.getNamespace(SCHEMAS_MAPI_ID.getURI() + '{' + distinguishedPropertySetMap.get(propertySetType) + "}/"), name, propertyType, responseAlias, null, updateAlias); fieldMap.put(field.alias, field); @@ -469,7 +468,7 @@ public class Field { } return new DefaultDavProperty(field.updatePropertyName, valueList); - } else if (field.isBooleanValue) { + } else if (field.isBooleanValue && !"haspicture".equals(alias)) { if ("true".equals(value)) { return new DefaultDavProperty(field.updatePropertyName, "1"); } else if ("false".equals(value)) { diff --git a/src/java/davmail/exchange/dav/PropertyType.java b/src/java/davmail/exchange/dav/PropertyType.java new file mode 100644 index 00000000..755936f8 --- /dev/null +++ b/src/java/davmail/exchange/dav/PropertyType.java @@ -0,0 +1,28 @@ +/* + * 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.dav; + +/** + * MAPI property types. + */ +public enum PropertyType { + ApplicationTime, ApplicationTimeArray, Binary, BinaryArray, Boolean, CLSID, CLSIDArray, Currency, CurrencyArray, + Double, DoubleArray, Error, Float, FloatArray, Integer, IntegerArray, Long, LongArray, Null, Object, + ObjectArray, Short, ShortArray, SystemTime, SystemTimeArray, String, StringArray +} diff --git a/src/java/davmail/exchange/dav/PropertyValue.java b/src/java/davmail/exchange/dav/PropertyValue.java new file mode 100644 index 00000000..0f73fa8c --- /dev/null +++ b/src/java/davmail/exchange/dav/PropertyValue.java @@ -0,0 +1,63 @@ +/* + * 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.dav; + +import org.apache.jackrabbit.webdav.xml.Namespace; + + +/** + * Property value. + */ +public class PropertyValue { + protected Namespace namespace; + protected String name; + protected String value; + protected PropertyType type; + + public PropertyValue(Namespace namespace, String name) { + this(namespace, name, null, null); + } + + public PropertyValue(Namespace namespace, String name, String value) { + this(namespace, name, value, null); + } + + public PropertyValue(Namespace namespace, String name, String value, PropertyType type) { + this.namespace = namespace; + this.name = name; + this.value = value; + this.type = type; + } + + public Namespace getNamespace() { + return namespace; + } + + public String getValue() { + return value; + } + + public PropertyType getType() { + return type; + } + + public String getName() { + return name; + } +}