1
0
mirror of https://github.com/moparisthebest/davmail synced 2024-12-13 11:12:22 -05:00

EWS: refactor search to use classes instead of String filters

git-svn-id: http://svn.code.sf.net/p/davmail/code/trunk@1078 3d1905a2-6b24-0410-a738-b14d5a86fcbd
This commit is contained in:
mguessan 2010-06-07 21:17:07 +00:00
parent 2275c3fb15
commit ec2e5acb4b
12 changed files with 303 additions and 83 deletions

View File

@ -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<Folder> 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<Folder> 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<Folder> getSubFolders(String folderName, String filter, boolean recursive) throws IOException;
public abstract List<Folder> 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);
}
/**

View File

@ -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<String, String> attributeMap = new HashMap<String, String>();
static {
attributeMap.put("folderclass", "http://schemas.microsoft.com/exchange/outlookfolderclass");
attributeMap.put("contentclass", "DAV:contentclass");
}
static final Map<Operator, String> operatorMap = new HashMap<Operator, String>();
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<Folder> getSubFolders(String folderName, String filter, boolean recursive) throws IOException {
public List<Folder> getSubFolders(String folderName, Condition condition, boolean recursive) throws IOException {
String mode = recursive ? "DEEP" : "SHALLOW";
List<Folder> folders = new ArrayList<Folder>();
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;
}
}

View File

@ -133,9 +133,11 @@ public abstract class EWSMethod extends PostMethod {
baseShape.write(writer);
if (additionalProperties != null) {
writer.write("<t:AdditionalProperties>");
StringBuilder buffer = new StringBuilder();
for (FieldURI fieldURI : additionalProperties) {
fieldURI.write(writer);
fieldURI.appendTo(buffer);
}
writer.write(buffer.toString());
writer.write("</t:AdditionalProperties>");
}
if (includeMimeContent) {
@ -194,7 +196,9 @@ public abstract class EWSMethod extends PostMethod {
protected void writeRestriction(Writer writer) throws IOException {
if (searchExpression != null) {
writer.write("<m:Restriction>");
searchExpression.write(writer);
StringBuilder buffer = new StringBuilder();
searchExpression.appendTo(buffer);
writer.write(buffer.toString());
writer.write("</m:Restriction>");
}
}
@ -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<Item> getResponseItems() {

View File

@ -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("<t:").append(operator.toString()).append('>');
for (Condition condition : conditions) {
condition.appendTo(buffer);
}
buffer.append("</t:").append(operator.toString()).append('>');
}
}
static final Map<String, FieldURI> attributeMap = new HashMap<String, FieldURI>();
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("<t:").append(operator.toString()).append('>');
attributeMap.get(attributeName).appendTo(buffer);
buffer.append("<t:FieldURIOrConstant><t:Constant Value=\"");
buffer.append(StringUtil.xmlEncode(value));
buffer.append("\"/></t:FieldURIOrConstant>");
buffer.append("</t:").append(operator.toString()).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<Folder> getSubFolders(String folderName, String filter, boolean recursive) throws IOException {
// TODO
throw new UnsupportedOperationException();
public List<ExchangeSession.Folder> getSubFolders(String folderPath, Condition condition, boolean recursive) throws IOException {
List<ExchangeSession.Folder> folders = new ArrayList<ExchangeSession.Folder>();
appendSubFolders(folders, folderPath, getFolderId(folderPath), condition, recursive);
return folders;
}
protected void appendSubFolders(List<ExchangeSession.Folder> 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"));
}
}

View File

@ -51,26 +51,18 @@ public class ExtendedFieldURI implements FieldURI {
return propertyTag;
}
public void write(Writer writer) throws IOException {
writer.write("<t:ExtendedFieldURI ");
public void appendTo(StringBuilder buffer) {
buffer.append("<t:ExtendedFieldURI ");
if (propertyTag != null) {
writer.write("PropertyTag=\"");
writer.write(propertyTag);
writer.write("\" ");
buffer.append("PropertyTag=\"").append(propertyTag).append("\" ");
}
if (propertySetId != null) {
writer.write("PropertySetId=\"");
writer.write(propertySetId);
writer.write("\" ");
buffer.append("PropertySetId=\"").append(propertySetId).append("\" ");
}
if (propertyId != 0) {
writer.write("PropertyId=\"");
writer.write(String.valueOf(propertyId));
writer.write("\" ");
buffer.append("PropertyId=\"").append(String.valueOf(propertyId)).append("\" ");
}
writer.write("PropertyType=\"");
writer.write(propertyType.toString());
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);

View File

@ -26,6 +26,6 @@ import java.io.Writer;
*/
public interface FieldURI {
public void write(Writer writer) throws IOException;
public void appendTo(StringBuilder buffer);
}

View File

@ -37,17 +37,13 @@ public class MultipleOperandBooleanExpression implements SearchExpression {
this.operator = operator;
}
public void write(Writer writer) throws IOException {
writer.write("<t:");
writer.write(operator.toString());
writer.write(">");
public void appendTo(StringBuilder buffer) {
buffer.append("<t:").append(operator.toString()).append('>');
for (SearchExpression searchExpression : searchExpressions) {
searchExpression.write(writer);
searchExpression.appendTo(buffer);
}
writer.write("</t:");
writer.write(operator.toString());
writer.write(">");
buffer.append("</t:").append(operator.toString()).append('>');
}
}

View File

@ -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);
}

View File

@ -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("<t:");
writer.write(operator.toString());
writer.write(">");
fieldURI.write(writer);
public void appendTo(StringBuilder buffer) {
buffer.append("<t:").append(operator.toString()).append('>');
fieldURI.appendTo(buffer);
writer.write("<t:FieldURIOrConstant><t:Constant Value=\"");
writer.write(value);
writer.write("\"/></t:FieldURIOrConstant>");
buffer.append("<t:FieldURIOrConstant><t:Constant Value=\"");
buffer.append(StringUtil.xmlEncode(value));
buffer.append("\"/></t:FieldURIOrConstant>");
writer.write("</t:");
writer.write(operator.toString());
writer.write(">");
buffer.append("</t:").append(operator.toString()).append('>');
}
}

View File

@ -31,12 +31,10 @@ public class UnindexedFieldURI implements FieldURI {
this.fieldURI = fieldURI;
}
public void write(Writer writer) throws IOException {
writer.write("<t:FieldURI FieldURI=\"");
writer.write(fieldURI);
writer.write("\"/>");
public void appendTo(StringBuilder buffer) {
buffer.append("<t:FieldURI FieldURI=\"").append(fieldURI).append("\"/>");
}
public static final UnindexedFieldURI DATE_TIME_SENT = new UnindexedFieldURI("item:DateTimeSent");
}

View File

@ -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

View File

@ -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
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