EWS: implement folder handling, including the new MoveFolderMethod

git-svn-id: http://svn.code.sf.net/p/davmail/code/trunk@1111 3d1905a2-6b24-0410-a738-b14d5a86fcbd
This commit is contained in:
mguessan 2010-06-29 14:15:50 +00:00
parent d961b647fa
commit 342b5d4e12
14 changed files with 385 additions and 39 deletions

View File

@ -24,7 +24,7 @@ package davmail.exchange.ews;
public class DistinguishedFolderId extends FolderId {
private DistinguishedFolderId(String value) {
super("t:DistinguishedFolderId", value);
super("t:DistinguishedFolderId", value, null);
}
public static final DistinguishedFolderId CALENDAR = new DistinguishedFolderId("calendar");

View File

@ -0,0 +1,35 @@
/*
* 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.ews;
import java.io.IOException;
/**
* EWS Exception
*/
public class EWSException extends IOException {
/**
* Create EWS Exception with detailed error message
*
* @param message error message
*/
public EWSException(String message) {
super(message);
}
}

View File

@ -18,10 +18,10 @@
*/
package davmail.exchange.ews;
import com.wutka.dtd.DTDOutput;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpConnection;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpState;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
@ -44,11 +44,14 @@ public abstract class EWSMethod extends PostMethod {
protected BaseShape baseShape;
protected boolean includeMimeContent;
protected FolderId folderId;
protected FolderId toFolderId;
protected FolderId parentFolderId;
protected ItemId itemId;
protected Set<FieldURI> additionalProperties;
protected Disposal deleteType;
protected Set<FieldUpdate> updates;
protected final String itemType;
protected final String methodName;
protected final String responseCollectionName;
@ -163,9 +166,21 @@ public abstract class EWSMethod extends PostMethod {
protected void writeFolderId(Writer writer) throws IOException {
if (folderId != null) {
writer.write("<m:FolderIds>");
if (updates == null) {
writer.write("<m:FolderIds>");
}
folderId.write(writer);
writer.write("</m:FolderIds>");
if (updates == null) {
writer.write("</m:FolderIds>");
}
}
}
protected void writeToFolderId(Writer writer) throws IOException {
if (toFolderId != null) {
writer.write("<m:ToFolderId>");
toFolderId.write(writer);
writer.write("</m:ToFolderId>");
}
}
@ -207,6 +222,47 @@ public abstract class EWSMethod extends PostMethod {
}
}
protected void startChanges(Writer writer) throws IOException {
if (updates != null) {
writer.write("<m:");
writer.write(itemType);
writer.write("Changes>");
writer.write("<t:");
writer.write(itemType);
writer.write("Change>");
}
}
protected void writeUpdates(Writer writer) throws IOException {
if (updates != null) {
writer.write("<t:Updates>");
for (FieldUpdate fieldUpdate : updates) {
writer.write("<t:Set");
writer.write(itemType);
writer.write("Field>");
fieldUpdate.write(itemType, writer);
writer.write("</t:Set");
writer.write(itemType);
writer.write("Field>");
}
writer.write("</t:Updates>");
}
}
protected void endChanges(Writer writer) throws IOException {
if (updates != null) {
writer.write("</t:");
writer.write(itemType);
writer.write("Change>");
writer.write("</m:");
writer.write(itemType);
writer.write("Changes>");
}
}
protected byte[] generateSoapEnvelope() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
@ -216,7 +272,7 @@ public abstract class EWSMethod extends PostMethod {
"xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\">" +
"<soap:Header>" +
"<t:RequestServerVersion Version=\"Exchange2007_SP1\"/>" +
"</soap:Header>"+
"</soap:Header>" +
"<soap:Body>");
writer.write("<m:");
writer.write(methodName);
@ -241,12 +297,16 @@ public abstract class EWSMethod extends PostMethod {
}
protected void writeSoapBody(Writer writer) throws IOException {
startChanges(writer);
writeShape(writer);
writeRestriction(writer);
writeItemId(writer);
writeParentFolderId(writer);
writeToFolderId(writer);
writeFolderId(writer);
writeItem(writer);
writeUpdates(writer);
endChanges(writer);
}
/**
@ -313,11 +373,19 @@ public abstract class EWSMethod extends PostMethod {
}
public List<Item> getResponseItems() {
public void checkSuccess() throws EWSException {
if (errorDetail != null) {
throw new EWSException(errorDetail);
}
}
public List<Item> getResponseItems() throws EWSException {
checkSuccess();
return responseItems;
}
public Item getResponseItem() {
public Item getResponseItem() throws EWSException {
checkSuccess();
if (responseItems != null && responseItems.size() == 1) {
return responseItems.get(0);
} else {
@ -325,7 +393,8 @@ public abstract class EWSMethod extends PostMethod {
}
}
public byte[] getMimeContent() {
public byte[] getMimeContent() throws EWSException {
checkSuccess();
return mimeContent;
}
@ -381,6 +450,8 @@ public abstract class EWSMethod extends PostMethod {
} else {
if (tagLocalName.endsWith("Id")) {
value = getAttributeValue(reader, "Id");
// get change key
item.put("ChangeKey", getAttributeValue(reader, "ChangeKey"));
}
if (value == null) {
value = getTagContent(reader);

View File

@ -45,6 +45,22 @@ public class EwsExchangeSession extends ExchangeSession {
public FolderId folderId;
}
protected class FolderPath {
protected String parentPath;
protected String folderName;
protected FolderPath(String folderPath) {
int slashIndex = folderPath.lastIndexOf('/');
if (slashIndex < 0) {
parentPath = "";
folderName = folderPath;
} else {
parentPath = folderPath.substring(0, slashIndex);
folderName = folderPath.substring(slashIndex + 1);
}
}
}
/**
* @inheritDoc
*/
@ -317,7 +333,8 @@ public class EwsExchangeSession extends ExchangeSession {
protected Folder buildFolder(EWSMethod.Item item) {
Folder folder = new Folder();
folder.folderId = new FolderId(item.get("FolderId"));
folder.folderId = new FolderId(item.get("FolderId"), item.get("ChangeKey"));
folder.displayName = item.get(ExtendedFieldURI.PR_URL_COMP_NAME.getPropertyTag());
folder.folderClass = item.get(ExtendedFieldURI.PR_CONTAINER_CLASS.getPropertyTag());
folder.etag = item.get(ExtendedFieldURI.PR_LAST_MODIFICATION_TIME.getPropertyTag());
folder.ctag = item.get(ExtendedFieldURI.PR_LOCAL_COMMIT_TIME_MAX.getPropertyTag());
@ -342,11 +359,7 @@ public class EwsExchangeSession extends ExchangeSession {
Condition condition, boolean recursive) throws IOException {
FindFolderMethod findFolderMethod = new FindFolderMethod(FolderQueryTraversal.SHALLOW,
BaseShape.ID_ONLY, parentFolderId, FOLDER_PROPERTIES, (SearchExpression) condition);
try {
httpClient.executeMethod(findFolderMethod);
} finally {
findFolderMethod.releaseConnection();
}
executeMethod(findFolderMethod);
for (EWSMethod.Item item : findFolderMethod.getResponseItems()) {
Folder folder = buildFolder(item);
if (parentFolderPath.length() > 0) {
@ -369,11 +382,7 @@ public class EwsExchangeSession extends ExchangeSession {
@Override
public EwsExchangeSession.Folder getFolder(String folderPath) throws IOException {
GetFolderMethod getFolderMethod = new GetFolderMethod(BaseShape.ID_ONLY, getFolderId(folderPath), FOLDER_PROPERTIES);
try {
httpClient.executeMethod(getFolderMethod);
} finally {
getFolderMethod.releaseConnection();
}
executeMethod(getFolderMethod);
EWSMethod.Item item = getFolderMethod.getResponseItem();
Folder folder = null;
if (item != null) {
@ -387,16 +396,28 @@ public class EwsExchangeSession extends ExchangeSession {
* @inheritDoc
*/
@Override
public void createFolder(String folderName, String folderClass) throws IOException {
throw new UnsupportedOperationException();
public void createFolder(String folderPath, String folderClass) throws IOException {
FolderPath path = new FolderPath(folderPath);
EWSMethod.Item folder = new EWSMethod.Item();
folder.type = "Folder";
folder.put("DisplayName", path.folderName);
folder.put("FolderClass", folderClass);
CreateFolderMethod createFolderMethod = new CreateFolderMethod(getFolderId(path.parentPath), folder);
executeMethod(createFolderMethod);
}
/**
* @inheritDoc
*/
@Override
public void deleteFolder(String folderName) throws IOException {
throw new UnsupportedOperationException();
public void deleteFolder(String folderPath) throws IOException {
FolderId folderId = getFolderIdIfExists(folderPath);
if (folderId != null) {
DeleteFolderMethod deleteFolderMethod = new DeleteFolderMethod(folderId);
executeMethod(deleteFolderMethod);
} else {
LOGGER.debug("Folder "+folderPath+" not found");
}
}
/**
@ -411,8 +432,24 @@ public class EwsExchangeSession extends ExchangeSession {
* @inheritDoc
*/
@Override
public void moveFolder(String folderName, String targetName) throws IOException {
throw new UnsupportedOperationException();
public void moveFolder(String folderPath, String targetFolderPath) throws IOException {
FolderPath path = new FolderPath(folderPath);
FolderPath targetPath = new FolderPath(targetFolderPath);
FolderId folderId = getFolderId(folderPath);
FolderId toFolderId = getFolderId(targetPath.parentPath);
toFolderId.changeKey = null;
// move folder
if (!path.parentPath.equals(targetPath.parentPath)) {
MoveFolderMethod moveFolderMethod = new MoveFolderMethod(folderId, toFolderId);
executeMethod(moveFolderMethod);
}
// rename folder
if (!path.folderName.equals(targetPath.folderName)) {
Set<FieldUpdate> updates = new HashSet<FieldUpdate>();
updates.add(new FieldUpdate(UnindexedFieldURI.FOLDER_DISPLAYNAME, targetPath.folderName));
UpdateFolderMethod updateFolderMethod = new UpdateFolderMethod(folderId, updates);
executeMethod(updateFolderMethod);
}
}
/**
@ -470,6 +507,14 @@ public class EwsExchangeSession extends ExchangeSession {
private FolderId getFolderId(String folderPath) throws IOException {
FolderId folderId = getFolderIdIfExists(folderPath);
if (folderId == null) {
throw new DavMailException("EXCEPTION_FOLDER_NOT_FOUND", folderPath);
}
return folderId;
}
private FolderId getFolderIdIfExists(String folderPath) throws IOException {
String[] folderNames;
FolderId currentFolderId;
if (folderPath.startsWith(PUBLIC_ROOT)) {
@ -506,12 +551,16 @@ public class EwsExchangeSession extends ExchangeSession {
for (String folderName : folderNames) {
if (folderName.length() > 0) {
currentFolderId = getSubFolderByName(currentFolderId, folderName);
if (currentFolderId == null) {
break;
}
}
}
return currentFolderId;
}
protected FolderId getSubFolderByName(FolderId parentFolderId, String folderName) throws IOException {
FolderId folderId = null;
FindFolderMethod findFolderMethod = new FindFolderMethod(
FolderQueryTraversal.SHALLOW,
BaseShape.ID_ONLY,
@ -520,16 +569,23 @@ public class EwsExchangeSession extends ExchangeSession {
new TwoOperandExpression(TwoOperandExpression.Operator.IsEqualTo,
ExtendedFieldURI.PR_URL_COMP_NAME, folderName)
);
try {
httpClient.executeMethod(findFolderMethod);
} finally {
findFolderMethod.releaseConnection();
}
executeMethod(findFolderMethod);
EWSMethod.Item item = findFolderMethod.getResponseItem();
if (item == null) {
throw new DavMailException("EXCEPTION_FOLDER_NOT_FOUND", folderName);
if (item != null) {
folderId = new FolderId(item.get("FolderId"), item.get("ChangeKey"));
}
return new FolderId(item.get("FolderId"));
return folderId;
}
protected int executeMethod(EWSMethod ewsMethod) throws IOException {
int status;
try {
status = httpClient.executeMethod(ewsMethod);
ewsMethod.checkSuccess();
} finally {
ewsMethod.releaseConnection();
}
return status;
}
}

View File

@ -85,6 +85,22 @@ public class ExtendedFieldURI implements FieldURI {
}
}
public void appendValue(StringBuilder buffer, String itemType, String value) {
appendTo(buffer);
buffer.append("<t:");
buffer.append(itemType);
buffer.append('>');
buffer.append("<t:ExtendedProperty>");
appendTo(buffer);
buffer.append("<t:Value>");
buffer.append(value);
buffer.append("</t:Value>");
buffer.append("</t:ExtendedProperty>");
buffer.append("</t:");
buffer.append(itemType);
buffer.append('>');
}
public static final ExtendedFieldURI PR_INSTANCE_KEY = new ExtendedFieldURI(0xff6, PropertyType.Binary);
public static final ExtendedFieldURI PR_MESSAGE_SIZE = new ExtendedFieldURI(0xe08, PropertyType.Integer);
public static final ExtendedFieldURI PR_INTERNET_ARTICLE_NUMBER = new ExtendedFieldURI(0xe23, PropertyType.Integer);

View File

@ -26,6 +26,16 @@ import java.io.Writer;
*/
public interface FieldURI {
/**
* Append field to buffer
* @param buffer current buffer
*/
public void appendTo(StringBuilder buffer);
/**
* Append updated field value to buffer
* @param buffer current buffer
*/
public void appendValue(StringBuilder buffer, String itemType, String value);
}

View File

@ -0,0 +1,41 @@
/*
* 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.ews;
import java.io.IOException;
import java.io.Writer;
/**
* Field update
*/
public class FieldUpdate {
FieldURI fieldURI;
String value;
public FieldUpdate(FieldURI fieldURI, String value) {
this.fieldURI = fieldURI;
this.value = value;
}
public void write(String itemType, Writer writer) throws IOException {
StringBuilder buffer = new StringBuilder();
fieldURI.appendValue(buffer, itemType, value);
writer.write(buffer.toString());
}
}

View File

@ -25,9 +25,11 @@ import java.io.Writer;
* Folder Id.
*/
public class FolderId extends Option {
protected String changeKey;
protected FolderId(String name, String value) {
protected FolderId(String name, String value, String changeKey) {
super(name, value);
this.changeKey = changeKey;
}
/**
@ -35,8 +37,9 @@ public class FolderId extends Option {
*
* @param value id value
*/
public FolderId(String value) {
public FolderId(String value, String changeKey) {
super("t:FolderId", value);
this.changeKey = changeKey;
}
/**
@ -48,6 +51,10 @@ public class FolderId extends Option {
writer.write(name);
writer.write(" Id=\"");
writer.write(value);
if (changeKey != null) {
writer.write("\" ChangeKey=\"");
writer.write(changeKey);
}
writer.write("\"/>");
}

View File

@ -35,4 +35,21 @@ public class IndexedFieldURI implements FieldURI {
buffer.append("\" FieldIndex=\"").append(fieldIndex);
buffer.append("\"/>");
}
public void appendValue(StringBuilder buffer, String itemType, String value) {
appendTo(buffer);
buffer.append("<t:");
buffer.append(itemType);
buffer.append('>');
buffer.append("<t:");
buffer.append(fieldIndex);
buffer.append('>');
buffer.append(value);
buffer.append("</t:");
buffer.append(fieldIndex);
buffer.append('>');
buffer.append("</t:");
buffer.append(itemType);
buffer.append('>');
}
}

View File

@ -0,0 +1,30 @@
/*
* 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.ews;
/**
* Create Folder method.
*/
public class MoveFolderMethod extends EWSMethod {
public MoveFolderMethod(FolderId folderId, FolderId toFolderId) {
super("Folder", "MoveFolder");
this.folderId = folderId;
this.toFolderId = toFolderId;
}
}

View File

@ -25,16 +25,41 @@ import java.io.Writer;
* Unindexed Field URI
*/
public class UnindexedFieldURI implements FieldURI {
protected String fieldURI;
protected final String fieldURI;
protected final String fieldName;
public UnindexedFieldURI(String fieldURI) {
this.fieldURI = fieldURI;
int colonIndex = fieldURI.indexOf(':');
if (colonIndex < 0) {
fieldName = fieldURI;
} else {
fieldName = fieldURI.substring(colonIndex+1);
}
}
public void appendTo(StringBuilder buffer) {
buffer.append("<t:FieldURI FieldURI=\"").append(fieldURI).append("\"/>");
}
public void appendValue(StringBuilder buffer, String itemType, String value) {
appendTo(buffer);
buffer.append("<t:");
buffer.append(itemType);
buffer.append('>');
buffer.append("<t:");
buffer.append(fieldName);
buffer.append('>');
buffer.append(value);
buffer.append("</t:");
buffer.append(fieldName);
buffer.append('>');
buffer.append("</t:");
buffer.append(itemType);
buffer.append('>');
}
public static final UnindexedFieldURI DATE_TIME_SENT = new UnindexedFieldURI("item:DateTimeSent");
public static final UnindexedFieldURI FOLDER_DISPLAYNAME = new UnindexedFieldURI("folder:DisplayName");
}

View File

@ -0,0 +1,32 @@
/*
* 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.ews;
import java.util.Set;
/**
* Create Folder method.
*/
public class UpdateFolderMethod extends EWSMethod {
public UpdateFolderMethod(FolderId folderId, Set<FieldUpdate> updates) {
super("Folder", "UpdateFolder");
this.folderId = folderId;
this.updates = updates;
}
}

View File

@ -27,6 +27,7 @@ import java.io.IOException;
import davmail.Settings;
import davmail.http.DavGatewaySSLProtocolSocketFactory;
import org.apache.log4j.Level;
/**
* Exchange session test case.
@ -59,13 +60,15 @@ public class AbstractExchangeSessionTestCase extends TestCase {
Settings.setProperty("davmail.password", password);
}
//Settings.setProperty("davmail.enableEws", "true");
DavGatewaySSLProtocolSocketFactory.register();
// force server mode
Settings.setProperty("davmail.server", "true");
// enable WIRE debug log
//Settings.setLoggingLevel("httpclient.wire", Level.DEBUG);
// enable EWS support
//Settings.setProperty("davmail.enableEws", "true");
// open session, get username and password from davmail.properties
// Note: those properties should *not* exist in normal production mode,

View File

@ -55,9 +55,12 @@ public class TestExchangeSessionFolder extends AbstractExchangeSessionTestCase {
}
public void testMoveFolder() throws IOException {
session.deleteFolder("target");
session.deleteFolder("tomove");
session.createMessageFolder("tomove");
session.moveFolder("tomove", "test/moved");
session.deleteFolder("test/moved");
session.createMessageFolder("target");
session.moveFolder("tomove", "target/moved");
session.deleteFolder("target");
}
public void testDeleteFolder() throws IOException {