diff --git a/src/java/davmail/exchange/ExchangeSession.java b/src/java/davmail/exchange/ExchangeSession.java index 3614001d..9c400c30 100644 --- a/src/java/davmail/exchange/ExchangeSession.java +++ b/src/java/davmail/exchange/ExchangeSession.java @@ -813,6 +813,42 @@ public abstract class ExchangeSession { return messages; } + protected enum Operator { + Or, And, Not, IsEqualTo + } + + protected abstract static class Condition { + public abstract void appendTo(StringBuilder buffer); + } + + protected abstract class AttributeCondition extends Condition { + protected String attributeName; + protected Operator operator; + protected String value; + + protected AttributeCondition(String attributeName, Operator operator, String value) { + this.attributeName = attributeName; + this.operator = operator; + this.value = value; + } + } + + protected abstract class MultiCondition extends Condition { + protected Operator operator; + protected Condition[] conditions; + + protected MultiCondition(Operator operator, Condition... condition) { + this.operator = operator; + conditions = condition; + } + } + + protected abstract Condition and(Condition... condition); + + protected abstract Condition or(Condition... condition); + + protected abstract AttributeCondition equals(String attributeName, String value); + /** * Search folders under given folder. * @@ -822,7 +858,8 @@ public abstract class ExchangeSession { * @throws IOException on error */ public List getSubFolders(String folderName, boolean recursive) throws IOException { - return getSubFolders(folderName, "(\"DAV:contentclass\"='urn:content-classes:mailfolder' OR \"DAV:contentclass\"='urn:content-classes:folder')", recursive); + // "(\"DAV:contentclass\"='urn:content-classes:mailfolder' OR \"DAV:contentclass\"='urn:content-classes:folder')" + return getSubFolders(folderName, equals("folderclass", "IPF.Note"), recursive); } /** @@ -834,19 +871,20 @@ public abstract class ExchangeSession { * @throws IOException on error */ public List getSubCalendarFolders(String folderName, boolean recursive) throws IOException { - return getSubFolders(folderName, "\"DAV:contentclass\"='urn:content-classes:calendarfolder'", recursive); + // "\"DAV:contentclass\"='urn:content-classes:calendarfolder'" + return getSubFolders(folderName, equals("folderclass", "IPF.Appointment"), recursive); } /** * Search folders under given folder matching filter. * * @param folderName Exchange folder name - * @param filter search filter + * @param condition search filter * @param recursive deep search if true * @return list of folders * @throws IOException on error */ - public abstract List getSubFolders(String folderName, String filter, boolean recursive) throws IOException; + public abstract List getSubFolders(String folderName, Condition condition, boolean recursive) throws IOException; /** * Delete oldest messages in trash. @@ -1159,9 +1197,9 @@ public abstract class ExchangeSession { public String folderPath; /** - * Folder content class. + * Folder class (PR_CONTAINER_CLASS). */ - public String contentClass; + public String folderClass; /** * Folder unread message count. */ @@ -1305,7 +1343,7 @@ public abstract class ExchangeSession { * @return true if this is a calendar folder */ public boolean isCalendar() { - return "urn:content-classes:calendarfolder".equals(contentClass); + return "IPF.Appointment".equals(folderClass); } /** @@ -1314,7 +1352,7 @@ public abstract class ExchangeSession { * @return true if this is a calendar folder */ public boolean isContact() { - return "urn:content-classes:contactfolder".equals(contentClass); + return "IPF.Contact".equals(folderClass); } /** diff --git a/src/java/davmail/exchange/dav/DavExchangeSession.java b/src/java/davmail/exchange/dav/DavExchangeSession.java index c2d4bc31..257bd77a 100644 --- a/src/java/davmail/exchange/dav/DavExchangeSession.java +++ b/src/java/davmail/exchange/dav/DavExchangeSession.java @@ -37,7 +37,9 @@ import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Webdav Exchange adapter. @@ -168,16 +170,79 @@ public class DavExchangeSession extends ExchangeSession { } } + protected class MultiCondition extends ExchangeSession.MultiCondition { + protected MultiCondition(Operator operator, Condition... condition) { + super(operator, condition); + } + + @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(' '); + } + condition.appendTo(buffer); + } + buffer.append(')'); + } + } + + static final Map attributeMap = new HashMap(); + + static { + attributeMap.put("folderclass", "http://schemas.microsoft.com/exchange/outlookfolderclass"); + attributeMap.put("contentclass", "DAV:contentclass"); + } + + static final Map operatorMap = new HashMap(); + + static { + operatorMap.put(Operator.IsEqualTo, "="); + } + + protected class AttributeCondition extends ExchangeSession.AttributeCondition { + protected AttributeCondition(String attributeName, Operator operator, String value) { + super(attributeName, operator, value); + } + + @Override + public void appendTo(StringBuilder buffer) { + buffer.append('"').append(attributeMap.get(attributeName)).append('"'); + buffer.append(operatorMap.get(operator)); + buffer.append('\'').append(value).append('\''); + } + } + + @Override + protected Condition and(Condition... condition) { + return new MultiCondition(Operator.And, condition); + } + + @Override + protected Condition or(Condition... condition) { + return new MultiCondition(Operator.Or, condition); + } + + @Override + protected AttributeCondition equals(String attributeName, String value) { + return new AttributeCondition(attributeName, Operator.IsEqualTo, value); + } + + protected Folder buildFolder(MultiStatusResponse entity) throws IOException { String href = URIUtil.decode(entity.getHref()); Folder folder = new Folder(); DavPropertySet properties = entity.getProperties(HttpStatus.SC_OK); - folder.contentClass = getPropertyIfExists(properties, "contentclass", DAV); + folder.folderClass = getPropertyIfExists(properties, "outlookfolderclass", SCHEMAS_EXCHANGE); folder.hasChildren = "1".equals(getPropertyIfExists(properties, "hassubs", DAV)); folder.noInferiors = "1".equals(getPropertyIfExists(properties, "nosubs", DAV)); folder.unreadCount = getIntPropertyIfExists(properties, "unreadcount", URN_SCHEMAS_HTTPMAIL); 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, "x30080040", SCHEMAS_MAPI_PROPTAG); // replace well known folder names if (href.startsWith(inboxUrl)) { @@ -230,15 +295,17 @@ public class DavExchangeSession extends ExchangeSession { * @inheritDoc */ @Override - public List getSubFolders(String folderName, String filter, boolean recursive) throws IOException { + public List getSubFolders(String folderName, Condition condition, boolean recursive) throws IOException { String mode = recursive ? "DEEP" : "SHALLOW"; List folders = new ArrayList(); StringBuilder searchRequest = new StringBuilder(); - searchRequest.append("Select \"DAV:nosubs\", \"DAV:hassubs\"," + + searchRequest.append("Select \"DAV:nosubs\", \"DAV:hassubs\", \"http://schemas.microsoft.com/exchange/outlookfolderclass\", " + + "\"http://schemas.microsoft.com/repl/contenttag\", \"http://schemas.microsoft.com/mapi/proptag/x30080040\", " + "\"urn:schemas:httpmail:unreadcount\" FROM Scope('").append(mode).append(" TRAVERSAL OF \"").append(getFolderPath(folderName)).append("\"')\n" + " WHERE \"DAV:ishidden\" = False AND \"DAV:isfolder\" = True \n"); - if (filter != null && filter.length() > 0) { - searchRequest.append(" AND ").append(filter); + if (condition != null) { + searchRequest.append(" AND "); + condition.appendTo(searchRequest); } MultiStatusResponse[] responses = DavGatewayHttpClientFacade.executeSearchMethod( httpClient, URIUtil.encodePath(getFolderPath(folderName)), searchRequest.toString()); @@ -248,5 +315,5 @@ public class DavExchangeSession extends ExchangeSession { } return folders; } - + } diff --git a/src/java/davmail/exchange/ews/EWSMethod.java b/src/java/davmail/exchange/ews/EWSMethod.java index c53ee44f..d99d6595 100644 --- a/src/java/davmail/exchange/ews/EWSMethod.java +++ b/src/java/davmail/exchange/ews/EWSMethod.java @@ -133,9 +133,11 @@ public abstract class EWSMethod extends PostMethod { baseShape.write(writer); if (additionalProperties != null) { writer.write(""); + StringBuilder buffer = new StringBuilder(); for (FieldURI fieldURI : additionalProperties) { - fieldURI.write(writer); + fieldURI.appendTo(buffer); } + writer.write(buffer.toString()); writer.write(""); } if (includeMimeContent) { @@ -194,7 +196,9 @@ public abstract class EWSMethod extends PostMethod { protected void writeRestriction(Writer writer) throws IOException { if (searchExpression != null) { writer.write(""); - searchExpression.write(writer); + StringBuilder buffer = new StringBuilder(); + searchExpression.appendTo(buffer); + writer.write(buffer.toString()); writer.write(""); } } @@ -281,6 +285,15 @@ public abstract class EWSMethod extends PostMethod { writer.write(type); writer.write(">"); } + + public int getInt(String key) { + int result = 0; + String value = get(key); + if (value != null && value.length() > 0) { + result = Integer.parseInt(value); + } + return result; + } } public List getResponseItems() { diff --git a/src/java/davmail/exchange/ews/EwsExchangeSession.java b/src/java/davmail/exchange/ews/EwsExchangeSession.java index cbaa44ba..4331d2f0 100644 --- a/src/java/davmail/exchange/ews/EwsExchangeSession.java +++ b/src/java/davmail/exchange/ews/EwsExchangeSession.java @@ -18,12 +18,20 @@ */ package davmail.exchange.ews; +import davmail.exception.DavMailAuthenticationException; import davmail.exception.DavMailException; import davmail.exchange.ExchangeSession; +import davmail.http.DavGatewayHttpClientFacade; +import davmail.util.StringUtil; import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.methods.HeadMethod; import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * EWS Exchange adapter. @@ -31,7 +39,7 @@ import java.util.List; */ public class EwsExchangeSession extends ExchangeSession { - protected class EwsFolder extends Folder { + protected class Folder extends ExchangeSession.Folder { public FolderId folderId; } @@ -45,39 +53,147 @@ public class EwsExchangeSession extends ExchangeSession { @Override protected void buildSessionInfo(HttpMethod method) throws DavMailException { // nothing to do, mailPath not used in EWS mode + // check EWS access + HttpMethod headMethod = new HeadMethod("/ews/services.wsdl"); + try { + headMethod = DavGatewayHttpClientFacade.executeFollowRedirects(httpClient, headMethod); + if (headMethod.getStatusCode() != HttpStatus.SC_OK) { + throw DavGatewayHttpClientFacade.buildHttpException(headMethod); + } + } catch (IOException e) { + LOGGER.error(e.getMessage()); + throw new DavMailAuthenticationException("EXCEPTION_EWS_NOT_AVAILABLE"); + } finally { + headMethod.releaseConnection(); + } + } + + protected class MultiCondition extends ExchangeSession.MultiCondition implements SearchExpression { + protected MultiCondition(Operator operator, Condition... condition) { + super(operator, condition); + } + + @Override + public void appendTo(StringBuilder buffer) { + buffer.append("'); + + for (Condition condition : conditions) { + condition.appendTo(buffer); + } + + buffer.append("'); + } + } + + static final Map attributeMap = new HashMap(); + + static { + attributeMap.put("folderclass", ExtendedFieldURI.PR_CONTAINER_CLASS); + } + + protected class AttributeCondition extends ExchangeSession.AttributeCondition implements SearchExpression { + protected AttributeCondition(String attributeName, Operator operator, String value) { + super(attributeName, operator, value); + } + + @Override + public void appendTo(StringBuilder buffer) { + buffer.append("'); + attributeMap.get(attributeName).appendTo(buffer); + + buffer.append(""); + + buffer.append("'); + } + } + + @Override + protected Condition and(Condition... condition) { + return new MultiCondition(Operator.And, condition); + } + + @Override + protected Condition or(Condition... condition) { + return new MultiCondition(Operator.Or, condition); + } + + @Override + protected AttributeCondition equals(String attributeName, String value) { + return new AttributeCondition(attributeName, Operator.IsEqualTo, value); + } + + protected Folder buildFolder(EWSMethod.Item item) { + Folder folder = new Folder(); + folder.folderId = new FolderId(item.get("FolderId")); + folder.etag = item.get(ExtendedFieldURI.PR_LAST_MODIFICATION_TIME.getPropertyTag()); + // TODO: implement ctag + folder.ctag = String.valueOf(System.currentTimeMillis()); + // TODO: implement contentClass, noInferiors + folder.unreadCount = item.getInt("UnreadCount"); + folder.hasChildren = item.getInt("ChildFolderCount") != 0; + // noInferiors not implemented + return folder; } /** * @inheritDoc */ @Override - public List getSubFolders(String folderName, String filter, boolean recursive) throws IOException { - // TODO - throw new UnsupportedOperationException(); + public List getSubFolders(String folderPath, Condition condition, boolean recursive) throws IOException { + + List folders = new ArrayList(); + appendSubFolders(folders, folderPath, getFolderId(folderPath), condition, recursive); + return folders; + } + + protected void appendSubFolders(List folders, + String parentFolderPath, FolderId parentFolderId, + Condition condition, boolean recursive) throws IOException { + FindFolderMethod findFolderMethod = new FindFolderMethod(FolderQueryTraversal.SHALLOW, BaseShape.ALL_PROPERTIES, parentFolderId); + findFolderMethod.setSearchExpression((SearchExpression) condition); + findFolderMethod.addAdditionalProperty(ExtendedFieldURI.PR_URL_COMP_NAME); + findFolderMethod.addAdditionalProperty(ExtendedFieldURI.PR_LAST_MODIFICATION_TIME); + try { + httpClient.executeMethod(findFolderMethod); + } finally { + findFolderMethod.releaseConnection(); + } + for (EWSMethod.Item item : findFolderMethod.getResponseItems()) { + Folder folder = buildFolder(item); + if (parentFolderPath.length() > 0) { + folder.folderPath = parentFolderPath + '/' + item.get(ExtendedFieldURI.PR_URL_COMP_NAME.getPropertyTag()); + } else { + folder.folderPath = item.get(ExtendedFieldURI.PR_URL_COMP_NAME.getPropertyTag()); + } + folders.add(folder); + if (recursive && folder.hasChildren) { + appendSubFolders(folders, folder.folderPath, folder.folderId, condition, recursive); + } + } } /** * @inheritDoc */ @Override - public Folder getFolder(String folderPath) throws IOException { + public ExchangeSession.Folder getFolder(String folderPath) throws IOException { GetFolderMethod getFolderMethod = new GetFolderMethod(BaseShape.ALL_PROPERTIES, getFolderId(folderPath)); getFolderMethod.addAdditionalProperty(ExtendedFieldURI.PR_URL_COMP_NAME); getFolderMethod.addAdditionalProperty(ExtendedFieldURI.PR_LAST_MODIFICATION_TIME); //getFolderMethod.addAdditionalProperty(new ExtendedFieldURI("0x65E2", ExtendedFieldURI.PropertyType.Binary)); //getFolderMethod.addAdditionalProperty(new ExtendedFieldURI("00062040-0000-0000-C000-000000000046", 0x8A23, ExtendedFieldURI.PropertyType.SystemTime)); - - httpClient.executeMethod(getFolderMethod); + try { + httpClient.executeMethod(getFolderMethod); + } finally { + getFolderMethod.releaseConnection(); + } EWSMethod.Item item = getFolderMethod.getResponseItem(); - EwsFolder folder = null; + Folder folder = null; if (item != null) { - folder = new EwsFolder(); - folder.folderId = new FolderId(item.get("FolderId")); - folder.folderName = folderPath; - folder.etag = item.get(ExtendedFieldURI.PR_LAST_MODIFICATION_TIME.getPropertyTag()); - // TODO: implement ctag - folder.ctag = String.valueOf(System.currentTimeMillis()); - // TODO: implement contentClass, unreadCount, hasChildren, noInferiors + folder = buildFolder(item); + folder.folderPath = folderPath; } return folder; } @@ -95,7 +211,7 @@ public class EwsExchangeSession extends ExchangeSession { currentFolderId = DistinguishedFolderId.DRAFTS; } else if ("Trash".equals(folderName)) { currentFolderId = DistinguishedFolderId.DELETEDITEMS; - } else { + } else if (folderName.length() > 0) { currentFolderId = getSubFolderByName(currentFolderId, folderName); } } @@ -110,9 +226,14 @@ public class EwsExchangeSession extends ExchangeSession { new TwoOperandExpression(TwoOperandExpression.Operator.IsEqualTo, ExtendedFieldURI.PR_URL_COMP_NAME, folderName) ); - httpClient.executeMethod(findFolderMethod); + try { + httpClient.executeMethod(findFolderMethod); + } finally { + findFolderMethod.releaseConnection(); + } EWSMethod.Item item = findFolderMethod.getResponseItem(); - return new FolderId(item.get("Id")); + // TODO: handle not found error + return new FolderId(item.get("FolderId")); } } diff --git a/src/java/davmail/exchange/ews/ExtendedFieldURI.java b/src/java/davmail/exchange/ews/ExtendedFieldURI.java index af4dd74a..7560a626 100644 --- a/src/java/davmail/exchange/ews/ExtendedFieldURI.java +++ b/src/java/davmail/exchange/ews/ExtendedFieldURI.java @@ -51,26 +51,18 @@ public class ExtendedFieldURI implements FieldURI { return propertyTag; } - public void write(Writer writer) throws IOException { - writer.write(""); + buffer.append("PropertyType=\"").append(propertyType.toString()).append("\"/>"); } public static final ExtendedFieldURI PR_INSTANCE_KEY = new ExtendedFieldURI("0x0FF6", PropertyType.Binary); @@ -80,7 +72,8 @@ public class ExtendedFieldURI implements FieldURI { public static final ExtendedFieldURI PR_FLAG_STATUS = new ExtendedFieldURI("0x1090", PropertyType.Integer); public static final ExtendedFieldURI PR_MESSAGE_FLAGS = new ExtendedFieldURI("0x0E07", PropertyType.Integer); public static final ExtendedFieldURI PR_ACTION_FLAG = new ExtendedFieldURI("0x1081", PropertyType.Integer); - public static final ExtendedFieldURI PR_URL_COMP_NAME = new ExtendedFieldURI("0x10F3", PropertyType.String); + public static final ExtendedFieldURI PR_URL_COMP_NAME = new ExtendedFieldURI("0x10f3", PropertyType.String); + public static final ExtendedFieldURI PR_CONTAINER_CLASS = new ExtendedFieldURI("0x3613", PropertyType.String); public static final ExtendedFieldURI PR_LAST_MODIFICATION_TIME = new ExtendedFieldURI("0x3008", PropertyType.SystemTime); diff --git a/src/java/davmail/exchange/ews/FieldURI.java b/src/java/davmail/exchange/ews/FieldURI.java index b5b39147..48326a30 100644 --- a/src/java/davmail/exchange/ews/FieldURI.java +++ b/src/java/davmail/exchange/ews/FieldURI.java @@ -26,6 +26,6 @@ import java.io.Writer; */ public interface FieldURI { - public void write(Writer writer) throws IOException; + public void appendTo(StringBuilder buffer); } diff --git a/src/java/davmail/exchange/ews/MultipleOperandBooleanExpression.java b/src/java/davmail/exchange/ews/MultipleOperandBooleanExpression.java index 6a0455cb..3971e5b0 100644 --- a/src/java/davmail/exchange/ews/MultipleOperandBooleanExpression.java +++ b/src/java/davmail/exchange/ews/MultipleOperandBooleanExpression.java @@ -37,17 +37,13 @@ public class MultipleOperandBooleanExpression implements SearchExpression { this.operator = operator; } - public void write(Writer writer) throws IOException { - writer.write(""); + public void appendTo(StringBuilder buffer) { + buffer.append("'); for (SearchExpression searchExpression : searchExpressions) { - searchExpression.write(writer); + searchExpression.appendTo(buffer); } - writer.write(""); + buffer.append("'); } } diff --git a/src/java/davmail/exchange/ews/SearchExpression.java b/src/java/davmail/exchange/ews/SearchExpression.java index 4dd8a260..d5b61430 100644 --- a/src/java/davmail/exchange/ews/SearchExpression.java +++ b/src/java/davmail/exchange/ews/SearchExpression.java @@ -25,11 +25,5 @@ import java.io.Writer; * EWS Search Expression. */ public interface SearchExpression { - /** - * Write XML content to writer. - * - * @param writer writer - * @throws IOException on error - */ - public void write(Writer writer) throws IOException; + public void appendTo(StringBuilder buffer); } diff --git a/src/java/davmail/exchange/ews/TwoOperandExpression.java b/src/java/davmail/exchange/ews/TwoOperandExpression.java index 6e346a78..1146f69e 100644 --- a/src/java/davmail/exchange/ews/TwoOperandExpression.java +++ b/src/java/davmail/exchange/ews/TwoOperandExpression.java @@ -18,6 +18,8 @@ */ package davmail.exchange.ews; +import davmail.util.StringUtil; + import java.io.IOException; import java.io.Writer; @@ -46,19 +48,15 @@ public class TwoOperandExpression implements SearchExpression { this.value = value; } - public void write(Writer writer) throws IOException { - writer.write(""); - fieldURI.write(writer); + public void appendTo(StringBuilder buffer) { + buffer.append("'); + fieldURI.appendTo(buffer); - writer.write(""); + buffer.append(""); - writer.write(""); + buffer.append("'); } } diff --git a/src/java/davmail/exchange/ews/UnindexedFieldURI.java b/src/java/davmail/exchange/ews/UnindexedFieldURI.java index e5d99a03..d7ef602d 100644 --- a/src/java/davmail/exchange/ews/UnindexedFieldURI.java +++ b/src/java/davmail/exchange/ews/UnindexedFieldURI.java @@ -31,12 +31,10 @@ public class UnindexedFieldURI implements FieldURI { this.fieldURI = fieldURI; } - public void write(Writer writer) throws IOException { - writer.write(""); + public void appendTo(StringBuilder buffer) { + buffer.append(""); } public static final UnindexedFieldURI DATE_TIME_SENT = new UnindexedFieldURI("item:DateTimeSent"); - + } diff --git a/src/java/davmailmessages.properties b/src/java/davmailmessages.properties index cec740a8..50df8346 100644 --- a/src/java/davmailmessages.properties +++ b/src/java/davmailmessages.properties @@ -257,3 +257,4 @@ UI_IMAP_AUTO_EXPUNGE=IMAP auto expunge: UI_IMAP_AUTO_EXPUNGE_HELP=Delete messages immediately on the server over IMAP UI_IMAP_IDLE_DELAY=IDLE folder monitor delay (IMAP): UI_IMAP_IDLE_DELAY_HELP=IMAP folder idle monitor delay in minutes, leave empty to disable IDLE support +EXCEPTION_EWS_NOT_AVAILABLE=EWS end point not available diff --git a/src/java/davmailmessages_fr.properties b/src/java/davmailmessages_fr.properties index a79417df..267d02a8 100644 --- a/src/java/davmailmessages_fr.properties +++ b/src/java/davmailmessages_fr.properties @@ -255,4 +255,5 @@ LOG_READ_CLIENT_AUTH_LOGIN=< AUTH LOGIN ******** UI_IMAP_IDLE_DELAY=Délai de surveillance dossier (IMAP) : UI_IMAP_IDLE_DELAY_HELP=Délai de surveillance du dossier IMAP en minutes, laisser vide pour désactiver le support IDLE UI_IMAP_AUTO_EXPUNGE=IMAP suppression immédiate : -UI_IMAP_AUTO_EXPUNGE_HELP=Supprimer immédiatement les messages du serveur via IMAP \ No newline at end of file +UI_IMAP_AUTO_EXPUNGE_HELP=Supprimer immédiatement les messages du serveur via IMAP +EXCEPTION_EWS_NOT_AVAILABLE=Point d''accès EWS non disponible