You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by ro...@apache.org on 2016/07/20 10:37:54 UTC

[2/2] james-project git commit: JAMES-1801 Convert JMAP filter to SearchQuery (all but inMailboxes, notInMailboxes & hasAttachment)

JAMES-1801 Convert JMAP filter to SearchQuery (all but inMailboxes, notInMailboxes & hasAttachment)


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/ce1fcea4
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/ce1fcea4
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/ce1fcea4

Branch: refs/heads/master
Commit: ce1fcea4f0604e869d4eba313faa78d181edbeb3
Parents: a17b49a
Author: Antoine Duprat <ad...@linagora.com>
Authored: Mon Jul 18 14:37:53 2016 +0200
Committer: Antoine Duprat <ad...@linagora.com>
Committed: Wed Jul 20 11:44:48 2016 +0200

----------------------------------------------------------------------
 .../apache/james/mailbox/model/SearchQuery.java |  24 +-
 .../integration/GetMessageListMethodTest.java   |  37 +-
 server/protocols/jmap/pom.xml                   |   5 +
 .../james/jmap/model/FilterCondition.java       |  88 ++--
 .../org/apache/james/jmap/model/Header.java     |  76 ++++
 .../james/jmap/utils/FilterToSearchQuery.java   | 105 +++++
 .../james/jmap/model/FilterConditionTest.java   | 127 ++----
 .../org/apache/james/jmap/model/HeaderTest.java |  71 ++++
 .../jmap/utils/FilterToSearchQueryTest.java     | 415 +++++++++++++++++++
 9 files changed, 815 insertions(+), 133 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/ce1fcea4/mailbox/api/src/main/java/org/apache/james/mailbox/model/SearchQuery.java
----------------------------------------------------------------------
diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/model/SearchQuery.java b/mailbox/api/src/main/java/org/apache/james/mailbox/model/SearchQuery.java
index b0efb1a..d815d9d 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/model/SearchQuery.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/model/SearchQuery.java
@@ -471,6 +471,17 @@ public class SearchQuery implements Serializable {
     }
 
     /**
+     * Creates a filter composing the listed criteria.
+     * 
+     * @param criteria
+     *            <code>List</code> of {@link Criterion}
+     * @return <code>Criterion</code>, not null
+     */
+    public static final Criterion or(List<Criterion> criteria) {
+        return new ConjunctionCriterion(Conjunction.OR, criteria);
+    }
+
+    /**
      * Creates a filter composing the two different criteria.
      * 
      * @param one
@@ -511,6 +522,17 @@ public class SearchQuery implements Serializable {
     }
 
     /**
+     * Creates a filter composing the listed criteria.
+     * 
+     * @param criteria
+     *            <code>List</code> of {@link Criterion}
+     * @return <code>Criterion</code>, not null
+     */
+    public static final Criterion not(List<Criterion> criteria) {
+        return new ConjunctionCriterion(Conjunction.NOR, criteria);
+    }
+
+    /**
      * Creates a filter on the given flag.
      * 
      * @param flag
@@ -611,7 +633,7 @@ public class SearchQuery implements Serializable {
 
     private final List<Criterion> criterias = new ArrayList<Criterion>();
 
-    private List<Sort> sorts = new ArrayList<SearchQuery.Sort>(Arrays.asList(new Sort(Sort.SortClause.Uid, false)));
+    private List<Sort> sorts = new ArrayList<Sort>(Arrays.asList(new Sort(Sort.SortClause.Uid, false)));
 
     public void andCriteria(Criterion crit) {
         criterias.add(crit);

http://git-wip-us.apache.org/repos/asf/james-project/blob/ce1fcea4/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/GetMessageListMethodTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/GetMessageListMethodTest.java b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/GetMessageListMethodTest.java
index 161010f..60408ab 100644
--- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/GetMessageListMethodTest.java
+++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/GetMessageListMethodTest.java
@@ -69,11 +69,11 @@ public abstract class GetMessageListMethodTest {
         jmapServer = createJmapServer();
         jmapServer.start();
         RestAssured.requestSpecification = new RequestSpecBuilder()
-        		.setContentType(ContentType.JSON)
-        		.setAccept(ContentType.JSON)
-        		.setConfig(newConfig().encoderConfig(encoderConfig().defaultContentCharset(Charsets.UTF_8)))
-        		.setPort(jmapServer.getJmapPort())
-        		.build();
+                .setContentType(ContentType.JSON)
+                .setAccept(ContentType.JSON)
+                .setConfig(newConfig().encoderConfig(encoderConfig().defaultContentCharset(Charsets.UTF_8)))
+                .setPort(jmapServer.getJmapPort())
+                .build();
 
         this.domain = "domain.tld";
         this.username = "username@" + domain;
@@ -89,7 +89,7 @@ public abstract class GetMessageListMethodTest {
     }
     
     @Test
-    public void getMessageListShouldErrorInvalidArgumentsWhenRequestIsInvalid() throws Exception {
+    public void getMessageListShouldReturnErrorInvalidArgumentsWhenRequestIsInvalid() throws Exception {
         given()
             .header("Authorization", accessToken.serialize())
             .body("[[\"getMessageList\", {\"filter\": true}, \"#0\"]]")
@@ -102,6 +102,31 @@ public abstract class GetMessageListMethodTest {
     }
 
     @Test
+    public void getMessageListShouldReturnErrorInvalidArgumentsWhenHeaderIsInvalid() throws Exception {
+        given()
+            .header("Authorization", accessToken.serialize())
+            .body("[[\"getMessageList\", {\"filter\":{\"header\":[\"132\", \"456\", \"789\"]}}, \"#0\"]]")
+        .when()
+            .post("/jmap")
+        .then()
+            .statusCode(200)
+            .body(NAME, equalTo("error"))
+            .body(ARGUMENTS + ".type", equalTo("invalidArguments"));
+    }
+
+    @Test
+    public void getMessageListShouldNotFailWhenHeaderIsValid() throws Exception {
+        given()
+            .header("Authorization", accessToken.serialize())
+            .body("[[\"getMessageList\", {\"filter\":{\"header\":[\"132\", \"456\"]}}, \"#0\"]]")
+        .when()
+            .post("/jmap")
+        .then()
+            .statusCode(200)
+            .body(NAME, equalTo("messageList"));
+    }
+
+    @Test
     public void getMessageListShouldReturnAllMessagesWhenSingleMailboxNoParameters() throws Exception {
         jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox");
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/ce1fcea4/server/protocols/jmap/pom.xml
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/pom.xml b/server/protocols/jmap/pom.xml
index 39a0e85..3e73bac 100644
--- a/server/protocols/jmap/pom.xml
+++ b/server/protocols/jmap/pom.xml
@@ -254,6 +254,11 @@
                     <artifactId>throwing-lambdas</artifactId>
                 </dependency>
                 <dependency>
+                    <groupId>com.github.steveash.guavate</groupId>
+                    <artifactId>guavate</artifactId>
+                    <version>1.0.0</version>
+                </dependency>
+                <dependency>
                     <groupId>com.googlecode.thread-weaver</groupId>
                     <artifactId>threadweaver</artifactId>
                     <version>0.2</version>

http://git-wip-us.apache.org/repos/asf/james-project/blob/ce1fcea4/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/FilterCondition.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/FilterCondition.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/FilterCondition.java
index de5ea81..63663c5 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/FilterCondition.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/FilterCondition.java
@@ -19,13 +19,11 @@
 
 package org.apache.james.jmap.model;
 
-import java.util.Date;
+import java.time.ZonedDateTime;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 
-import org.apache.commons.lang.NotImplementedException;
-
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
 import com.google.common.annotations.VisibleForTesting;
@@ -45,8 +43,8 @@ public class FilterCondition implements Filter {
 
         private Optional<List<String>> inMailboxes;
         private Optional<List<String>> notInMailboxes;
-        private Date before;
-        private Date after;
+        private ZonedDateTime before;
+        private ZonedDateTime after;
         private Integer minSize;
         private Integer maxSize;
         private Boolean isFlagged;
@@ -61,12 +59,11 @@ public class FilterCondition implements Filter {
         private String bcc;
         private String subject;
         private String body;
-        private Optional<List<String>> header;
+        private Header header;
 
         private Builder() {
             inMailboxes = Optional.empty();
             notInMailboxes = Optional.empty();
-            header = Optional.empty();
         }
 
         public Builder inMailboxes(String... inMailboxes) {
@@ -91,85 +88,102 @@ public class FilterCondition implements Filter {
             return this;
         }
 
-        public Builder before(Date before) {
-            throw new NotImplementedException();
+        public Builder before(ZonedDateTime before) {
+            this.before = before;
+            return this;
         }
 
-        public Builder after(Date after) {
-            throw new NotImplementedException();
+        public Builder after(ZonedDateTime after) {
+            this.after = after;
+            return this;
         }
 
         public Builder minSize(int minSize) {
-            throw new NotImplementedException();
+            this.minSize = minSize;
+            return this;
         }
 
         public Builder maxSize(int maxSize) {
-            throw new NotImplementedException();
+            this.maxSize = maxSize;
+            return this;
         }
 
-        public Builder isFlagged(boolean isFlagger) {
-            throw new NotImplementedException();
+        public Builder isFlagged(boolean isFlagged) {
+            this.isFlagged = isFlagged;
+            return this;
         }
 
         public Builder isUnread(boolean isUnread) {
-            throw new NotImplementedException();
+            this.isUnread = isUnread;
+            return this;
         }
 
         public Builder isAnswered(boolean isAnswered) {
-            throw new NotImplementedException();
+            this.isAnswered = isAnswered;
+            return this;
         }
 
         public Builder isDraft(boolean isDraft) {
-            throw new NotImplementedException();
+            this.isDraft = isDraft;
+            return this;
         }
 
         public Builder hasAttachment(boolean hasAttachment) {
-            throw new NotImplementedException();
+            this.hasAttachment = hasAttachment;
+            return this;
         }
 
         public Builder text(String text) {
-            throw new NotImplementedException();
+            this.text = text;
+            return this;
         }
 
         public Builder from(String from) {
-            throw new NotImplementedException();
+            this.from = from;
+            return this;
         }
 
         public Builder to(String to) {
-            throw new NotImplementedException();
+            this.to = to;
+            return this;
         }
 
         public Builder cc(String cc) {
-            throw new NotImplementedException();
+            this.cc = cc;
+            return this;
         }
 
         public Builder bcc(String bcc) {
-            throw new NotImplementedException();
+            this.bcc = bcc;
+            return this;
         }
 
         public Builder subject(String subject) {
-            throw new NotImplementedException();
+            this.subject = subject;
+            return this;
         }
 
         public Builder body(String body) {
-            throw new NotImplementedException();
+            this.body = body;
+            return this;
         }
 
-        public Builder header(List<String> body) {
-            throw new NotImplementedException();
+        public Builder header(Header header) {
+            this.header = header;
+            return this;
         }
 
         public FilterCondition build() {
             return new FilterCondition(inMailboxes, notInMailboxes, Optional.ofNullable(before), Optional.ofNullable(after), Optional.ofNullable(minSize), Optional.ofNullable(maxSize),
                     Optional.ofNullable(isFlagged), Optional.ofNullable(isUnread), Optional.ofNullable(isAnswered), Optional.ofNullable(isDraft), Optional.ofNullable(hasAttachment),
-                    Optional.ofNullable(text), Optional.ofNullable(from), Optional.ofNullable(to), Optional.ofNullable(cc), Optional.ofNullable(bcc), Optional.ofNullable(subject), Optional.ofNullable(body), header);
+                    Optional.ofNullable(text), Optional.ofNullable(from), Optional.ofNullable(to), Optional.ofNullable(cc), Optional.ofNullable(bcc), Optional.ofNullable(subject), Optional.ofNullable(body), Optional.ofNullable(header));
         }
     }
 
     private final Optional<List<String>> inMailboxes;
     private final Optional<List<String>> notInMailboxes;
-    private final Optional<Date> before;
-    private final Optional<Date> after;
+    private final Optional<ZonedDateTime> before;
+    private final Optional<ZonedDateTime> after;
     private final Optional<Integer> minSize;
     private final Optional<Integer> maxSize;
     private final Optional<Boolean> isFlagged;
@@ -184,11 +198,11 @@ public class FilterCondition implements Filter {
     private final Optional<String> bcc;
     private final Optional<String> subject;
     private final Optional<String> body;
-    private final Optional<List<String>> header;
+    private final Optional<Header> header;
 
-    @VisibleForTesting FilterCondition(Optional<List<String>> inMailboxes, Optional<List<String>> notInMailboxes, Optional<Date> before, Optional<Date> after, Optional<Integer> minSize, Optional<Integer> maxSize,
+    @VisibleForTesting FilterCondition(Optional<List<String>> inMailboxes, Optional<List<String>> notInMailboxes, Optional<ZonedDateTime> before, Optional<ZonedDateTime> after, Optional<Integer> minSize, Optional<Integer> maxSize,
             Optional<Boolean> isFlagged, Optional<Boolean> isUnread, Optional<Boolean> isAnswered, Optional<Boolean> isDraft, Optional<Boolean> hasAttachment,
-            Optional<String> text, Optional<String> from, Optional<String> to, Optional<String> cc, Optional<String> bcc, Optional<String> subject, Optional<String> body, Optional<List<String>> header) {
+            Optional<String> text, Optional<String> from, Optional<String> to, Optional<String> cc, Optional<String> bcc, Optional<String> subject, Optional<String> body, Optional<Header> header) {
 
         this.inMailboxes = inMailboxes;
         this.notInMailboxes = notInMailboxes;
@@ -219,11 +233,11 @@ public class FilterCondition implements Filter {
         return notInMailboxes;
     }
 
-    public Optional<Date> getBefore() {
+    public Optional<ZonedDateTime> getBefore() {
         return before;
     }
 
-    public Optional<Date> getAfter() {
+    public Optional<ZonedDateTime> getAfter() {
         return after;
     }
 
@@ -283,7 +297,7 @@ public class FilterCondition implements Filter {
         return body;
     }
 
-    public Optional<List<String>> getHeader() {
+    public Optional<Header> getHeader() {
         return header;
     }
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/ce1fcea4/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Header.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Header.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Header.java
new file mode 100644
index 0000000..2d2608c
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Header.java
@@ -0,0 +1,76 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jmap.model;
+
+import java.util.List;
+import java.util.Optional;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+
+public class Header {
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @JsonCreator
+    public static Header from(List<String> header) {
+        return builder().header(header).build();
+    }
+
+    public static class Builder {
+        
+        private String name;
+        private Optional<String> value;
+
+        public Builder header(List<String> header) {
+            Preconditions.checkNotNull(header);
+            Preconditions.checkArgument(header.size() > 0, "'header' should contains at least one element");
+            Preconditions.checkArgument(header.size() < 3, "'header' should contains lesser than three elements");
+            this.name = header.get(0);
+            this.value = Optional.ofNullable(Iterables.get(header, 1, null));
+            return this;
+        }
+
+        public Header build() {
+            Preconditions.checkState(!Strings.isNullOrEmpty(name), "'name' is mandatory");
+            return new Header(name, value);
+        }
+    }
+
+    private final String name;
+    private final Optional<String> value;
+
+    private Header(String name, Optional<String> value) {
+        this.name = name;
+        this.value = value;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Optional<String> getValue() {
+        return value;
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/ce1fcea4/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/FilterToSearchQuery.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/FilterToSearchQuery.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/FilterToSearchQuery.java
new file mode 100644
index 0000000..db7b485
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/FilterToSearchQuery.java
@@ -0,0 +1,105 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jmap.utils;
+
+import java.util.Date;
+
+import javax.mail.Flags.Flag;
+
+import org.apache.commons.lang.NotImplementedException;
+import org.apache.james.jmap.model.Filter;
+import org.apache.james.jmap.model.FilterCondition;
+import org.apache.james.jmap.model.FilterOperator;
+import org.apache.james.mailbox.model.SearchQuery;
+import org.apache.james.mailbox.model.SearchQuery.AddressType;
+import org.apache.james.mailbox.model.SearchQuery.Criterion;
+import org.apache.james.mailbox.model.SearchQuery.DateResolution;
+
+import com.github.fge.lambdas.Throwing;
+import com.github.steveash.guavate.Guavate;
+import com.google.common.collect.ImmutableList;
+
+public class FilterToSearchQuery {
+
+    public SearchQuery convert(Filter filter) {
+        if (filter instanceof FilterCondition) {
+            return convertCondition((FilterCondition) filter);
+        }
+        if (filter instanceof FilterOperator) {
+            SearchQuery searchQuery = new SearchQuery();
+            searchQuery.andCriteria(convertOperator((FilterOperator) filter));
+            return searchQuery;
+        }
+        throw new RuntimeException("Unknown filter: " + filter.getClass());
+    }
+
+    private SearchQuery convertCondition(FilterCondition filter) {
+        SearchQuery searchQuery = new SearchQuery();
+        filter.getText().ifPresent(text -> {
+            searchQuery.andCriteria(
+                    SearchQuery.or(ImmutableList.of(
+                            SearchQuery.address(AddressType.From, text),
+                            SearchQuery.address(AddressType.To, text),
+                            SearchQuery.address(AddressType.Cc, text),
+                            SearchQuery.address(AddressType.Bcc, text),
+                            SearchQuery.headerContains("Subject", text),
+                            SearchQuery.bodyContains(text)))
+                    );
+        });
+        filter.getFrom().ifPresent(from -> searchQuery.andCriteria(SearchQuery.address(AddressType.From, from)));
+        filter.getTo().ifPresent(to -> searchQuery.andCriteria(SearchQuery.address(AddressType.To, to)));
+        filter.getCc().ifPresent(cc -> searchQuery.andCriteria(SearchQuery.address(AddressType.Cc, cc)));
+        filter.getBcc().ifPresent(bcc -> searchQuery.andCriteria(SearchQuery.address(AddressType.Bcc, bcc)));
+        filter.getSubject().ifPresent(subject -> searchQuery.andCriteria(SearchQuery.headerContains("Subject", subject)));
+        filter.getBody().ifPresent(body ->  searchQuery.andCriteria(SearchQuery.bodyContains(body)));
+        filter.getAfter().ifPresent(after -> searchQuery.andCriteria(SearchQuery.internalDateAfter(Date.from(after.toInstant()), DateResolution.Second)));
+        filter.getBefore().ifPresent(before -> searchQuery.andCriteria(SearchQuery.internalDateBefore(Date.from(before.toInstant()), DateResolution.Second)));
+        filter.getHasAttachment().ifPresent(Throwing.consumer(hasAttachment -> { throw new NotImplementedException(); } ));
+        filter.getHeader().ifPresent(header -> searchQuery.andCriteria(SearchQuery.headerContains(header.getName(), header.getValue().orElse(null))));
+        filter.getIsAnswered().ifPresent(isAnswered -> searchQuery.andCriteria(SearchQuery.flagIsSet(Flag.ANSWERED)));
+        filter.getIsDraft().ifPresent(isDraft -> searchQuery.andCriteria(SearchQuery.flagIsSet(Flag.DRAFT)));
+        filter.getIsFlagged().ifPresent(isFlagged -> searchQuery.andCriteria(SearchQuery.flagIsSet(Flag.FLAGGED)));
+        filter.getIsUnread().ifPresent(isUnread -> searchQuery.andCriteria(SearchQuery.flagIsUnSet(Flag.SEEN)));
+        filter.getMaxSize().ifPresent(maxSize -> searchQuery.andCriteria(SearchQuery.sizeLessThan(maxSize)));
+        filter.getMinSize().ifPresent(minSize -> searchQuery.andCriteria(SearchQuery.sizeGreaterThan(minSize)));
+        return searchQuery;
+    }
+
+    private Criterion convertOperator(FilterOperator filter) {
+        switch (filter.getOperator()) {
+        case AND:
+            return SearchQuery.and(convertCriterias(filter));
+   
+        case OR:
+            return SearchQuery.or(convertCriterias(filter));
+   
+        case NOT:
+            return SearchQuery.not(convertCriterias(filter));
+        }
+        throw new RuntimeException("Unknown operator");
+    }
+
+    private ImmutableList<Criterion> convertCriterias(FilterOperator filter) {
+        return filter.getConditions().stream()
+            .map(this::convert)
+            .flatMap(sq -> sq.getCriterias().stream())
+            .collect(Guavate.toImmutableList());
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/ce1fcea4/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/FilterConditionTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/FilterConditionTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/FilterConditionTest.java
index a4707e5..5a518f0 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/FilterConditionTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/FilterConditionTest.java
@@ -21,9 +21,9 @@ package org.apache.james.jmap.model;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
+import java.time.ZonedDateTime;
 import java.util.Optional;
 
-import org.apache.commons.lang.NotImplementedException;
 import org.junit.Test;
 
 import com.google.common.collect.ImmutableList;
@@ -69,101 +69,50 @@ public class FilterConditionTest {
                 .build();
         assertThat(filterCondition.getNotInMailboxes()).contains(ImmutableList.of("1", "2"));
     }
-    
-    @Test(expected=NotImplementedException.class)
-    public void builderShouldThrowWhenBefore() {
-        FilterCondition.builder().before(null);
-    }
-
-    @Test(expected=NotImplementedException.class)
-    public void builderShouldThrowWhenAfter() {
-        FilterCondition.builder().after(null);
-    }
-
-    @Test(expected=NotImplementedException.class)
-    public void builderShouldThrowWhenMinSize() {
-        FilterCondition.builder().minSize(0);
-    }
-
-    @Test(expected=NotImplementedException.class)
-    public void builderShouldThrowWhenMaxSize() {
-        FilterCondition.builder().maxSize(0);
-    }
-
-    @Test(expected=NotImplementedException.class)
-    public void builderShouldThrowWhenIsFlagged() {
-        FilterCondition.builder().isFlagged(false);
-    }
-
-    @Test(expected=NotImplementedException.class)
-    public void builderShouldThrowWhenIsUnread() {
-        FilterCondition.builder().isUnread(false);
-    }
-
-    @Test(expected=NotImplementedException.class)
-    public void builderShouldThrowWhenIsAnswered() {
-        FilterCondition.builder().isAnswered(false);
-    }
-
-    @Test(expected=NotImplementedException.class)
-    public void builderShouldThrowWhenIsDraft() {
-        FilterCondition.builder().isDraft(false);
-    }
-
-    @Test(expected=NotImplementedException.class)
-    public void builderShouldThrowWhenHasAttachment() {
-        FilterCondition.builder().hasAttachment(false);
-    }
-
-    @Test(expected=NotImplementedException.class)
-    public void builderShouldThrowWhenText() {
-        FilterCondition.builder().text(null);
-    }
-
-    @Test(expected=NotImplementedException.class)
-    public void builderShouldThrowWhenFrom() {
-        FilterCondition.builder().from(null);
-    }
-
-    @Test(expected=NotImplementedException.class)
-    public void builderShouldThrowWhenTo() {
-        FilterCondition.builder().to(null);
-    }
-
-    @Test(expected=NotImplementedException.class)
-    public void builderShouldThrowWhenCc() {
-        FilterCondition.builder().cc(null);
-    }
-
-    @Test(expected=NotImplementedException.class)
-    public void builderShouldThrowWhenBcc() {
-        FilterCondition.builder().bcc(null);
-    }
-
-    @Test(expected=NotImplementedException.class)
-    public void builderShouldThrowWhenSubject() {
-        FilterCondition.builder().subject(null);
-    }
-
-    @Test(expected=NotImplementedException.class)
-    public void builderShouldThrowWhenBody() {
-        FilterCondition.builder().body(null);
-    }
-
-    @Test(expected=NotImplementedException.class)
-    public void builderShouldThrowWhenHeader() {
-        FilterCondition.builder().header(ImmutableList.of());
-    }
 
     @Test
     public void buildShouldWork() {
-        FilterCondition expectedFilterCondition = new FilterCondition(Optional.of(ImmutableList.of("1")), Optional.of(ImmutableList.of("2")), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), 
-                Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), 
-                Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty());
+        ZonedDateTime before = ZonedDateTime.parse("2016-07-19T14:30:00Z");
+        ZonedDateTime after = ZonedDateTime.parse("2016-07-19T14:31:00Z");
+        int minSize = 4;
+        int maxSize = 123;
+        boolean isFlagged = true;
+        boolean isUnread = true;
+        boolean isAnswered = true;
+        boolean isDraft = true;
+        boolean hasAttachment = true;
+        String text = "text";
+        String from = "sender@james.org";
+        String to = "recipient@james.org";
+        String cc = "copy@james.org";
+        String bcc = "blindcopy@james.org";
+        String subject = "subject";
+        String body = "body";
+        Header header = Header.from(ImmutableList.of("name", "value"));
+        FilterCondition expectedFilterCondition = new FilterCondition(Optional.of(ImmutableList.of("1")), Optional.of(ImmutableList.of("2")), Optional.of(before), Optional.of(after), Optional.of(minSize), Optional.of(maxSize), 
+                Optional.of(isFlagged), Optional.of(isUnread), Optional.of(isAnswered), Optional.of(isDraft), Optional.of(hasAttachment), Optional.of(text), Optional.of(from), 
+                Optional.of(to), Optional.of(cc), Optional.of(bcc), Optional.of(subject), Optional.of(body), Optional.of(header));
 
         FilterCondition filterCondition = FilterCondition.builder()
                 .inMailboxes(Optional.of(ImmutableList.of("1")))
                 .notInMailboxes("2")
+                .before(before)
+                .after(after)
+                .minSize(minSize)
+                .maxSize(maxSize)
+                .isFlagged(isFlagged)
+                .isUnread(isUnread)
+                .isAnswered(isAnswered)
+                .isDraft(isDraft)
+                .hasAttachment(hasAttachment)
+                .text(text)
+                .from(from)
+                .to(to)
+                .cc(cc)
+                .bcc(bcc)
+                .subject(subject)
+                .body(body)
+                .header(header)
                 .build();
 
         assertThat(filterCondition).isEqualToComparingFieldByField(expectedFilterCondition);

http://git-wip-us.apache.org/repos/asf/james-project/blob/ce1fcea4/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/HeaderTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/HeaderTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/HeaderTest.java
new file mode 100644
index 0000000..9d5d09b
--- /dev/null
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/HeaderTest.java
@@ -0,0 +1,71 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jmap.model;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.util.Optional;
+
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class HeaderTest {
+
+    @Test
+    public void builderShouldThrowWhenHeaderIsNull() {
+        assertThatThrownBy(() -> Header.builder().header(null)).isInstanceOf(NullPointerException.class);
+    }
+
+    @Test
+    public void builderShouldThrowWhenHeaderIsEmpty() {
+        assertThatThrownBy(() -> Header.builder().header(ImmutableList.of())).isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void builderShouldThrowWhenHeaderHasMoreThanTwoElements() {
+        assertThatThrownBy(() -> Header.builder().header(ImmutableList.of("1", "2", "3"))).isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void buildShouldThrowWhenNameIsNotGiven() {
+        assertThatThrownBy(() -> Header.builder().build()).isInstanceOf(IllegalStateException.class);
+    }
+
+    @Test
+    public void buildShouldSetNameWhenGiven() {
+        String expectedName = "name";
+        Header header = Header.builder().header(ImmutableList.of(expectedName)).build();
+
+        assertThat(header.getName()).isEqualTo(expectedName);
+        assertThat(header.getValue()).isEmpty();
+    }
+
+    @Test
+    public void buildShouldSetValueWhenGiven() {
+        String expectedName = "name";
+        String expectedValue = "value";
+        Header header = Header.builder().header(ImmutableList.of(expectedName, expectedValue)).build();
+
+        assertThat(header.getName()).isEqualTo(expectedName);
+        assertThat(header.getValue()).isEqualTo(Optional.of(expectedValue));
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/ce1fcea4/server/protocols/jmap/src/test/java/org/apache/james/jmap/utils/FilterToSearchQueryTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/utils/FilterToSearchQueryTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/utils/FilterToSearchQueryTest.java
new file mode 100644
index 0000000..41cfb39
--- /dev/null
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/utils/FilterToSearchQueryTest.java
@@ -0,0 +1,415 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jmap.utils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.time.ZonedDateTime;
+import java.util.Date;
+
+import javax.mail.Flags.Flag;
+
+import org.apache.commons.lang.NotImplementedException;
+import org.apache.james.jmap.model.Filter;
+import org.apache.james.jmap.model.FilterCondition;
+import org.apache.james.jmap.model.FilterOperator;
+import org.apache.james.jmap.model.Header;
+import org.apache.james.mailbox.model.SearchQuery;
+import org.apache.james.mailbox.model.SearchQuery.AddressType;
+import org.apache.james.mailbox.model.SearchQuery.DateResolution;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class FilterToSearchQueryTest {
+
+    @Test
+    public void filterConditionShouldThrowWhenUnknownFilter() {
+        Filter myFilter = (indentation -> null);
+        assertThatThrownBy(() -> new FilterToSearchQuery().convert(myFilter))
+            .isInstanceOf(RuntimeException.class)
+            .hasMessage("Unknown filter: " + myFilter.getClass());
+    }
+
+    @Test
+    public void filterConditionShouldMapEmptyWhenEmptyFilter() {
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(FilterCondition.builder()
+                .build());
+
+        assertThat(searchQuery).isEqualTo(new SearchQuery());
+    }
+
+    @Test
+    public void filterConditionShouldMapWhenFrom() {
+        String from = "sender@james.org";
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.address(AddressType.From, from));
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(FilterCondition.builder()
+                .from(from)
+                .build());
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+
+    @Test
+    public void filterConditionShouldMapWhenTo() {
+        String to = "recipient@james.org";
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.address(AddressType.To, to));
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(FilterCondition.builder()
+                .to(to)
+                .build());
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+
+    @Test
+    public void filterConditionShouldMapWhenCc() {
+        String cc = "copy@james.org";
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.address(AddressType.Cc, cc));
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(FilterCondition.builder()
+                .cc(cc)
+                .build());
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+
+    @Test
+    public void filterConditionShouldMapWhenBcc() {
+        String bcc = "blindcopy@james.org";
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.address(AddressType.Bcc, bcc));
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(FilterCondition.builder()
+                .bcc(bcc)
+                .build());
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+
+    @Test
+    public void filterConditionShouldMapWhenSubject() {
+        String subject = "subject";
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.headerContains("Subject", subject));
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(FilterCondition.builder()
+                .subject(subject)
+                .build());
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+
+    @Test
+    public void filterConditionShouldMapWhenBody() {
+        String body = "body";
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.bodyContains(body));
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(FilterCondition.builder()
+                .body(body)
+                .build());
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+
+    @Test
+    public void filterConditionShouldMapWhenText() {
+        String text = "text";
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.or(ImmutableList.of(
+                SearchQuery.address(AddressType.From, text),
+                SearchQuery.address(AddressType.To, text),
+                SearchQuery.address(AddressType.Cc, text),
+                SearchQuery.address(AddressType.Bcc, text),
+                SearchQuery.headerContains("Subject", text),
+                SearchQuery.bodyContains(text))));
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(FilterCondition.builder()
+                .text(text)
+                .build());
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+
+    @Test
+    public void filterConditionShouldMapWhenAfter() {
+        ZonedDateTime after = ZonedDateTime.now();
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.internalDateAfter(Date.from(after.toInstant()), DateResolution.Second));
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(FilterCondition.builder()
+                .after(after)
+                .build());
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+
+    @Test
+    public void filterConditionShouldMapWhenBefore() {
+        ZonedDateTime before = ZonedDateTime.now();
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.internalDateBefore(Date.from(before.toInstant()), DateResolution.Second));
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(FilterCondition.builder()
+                .before(before)
+                .build());
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+
+    @Test
+    public void filterConditionShouldThrowWhenHasAttachment() {
+        assertThatThrownBy(() -> new FilterToSearchQuery().convert(FilterCondition.builder()
+                .hasAttachment(true)
+                .build()))
+            .isInstanceOf(NotImplementedException.class);
+    }
+
+    @Test
+    public void filterConditionShouldMapWhenIsAnswered() {
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.flagIsSet(Flag.ANSWERED));
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(FilterCondition.builder()
+                .isAnswered(true)
+                .build());
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+
+    @Test
+    public void filterConditionShouldMapWhenIsDraft() {
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.flagIsSet(Flag.DRAFT));
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(FilterCondition.builder()
+                .isDraft(true)
+                .build());
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+
+    @Test
+    public void filterConditionShouldMapWhenIsFlagged() {
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.flagIsSet(Flag.FLAGGED));
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(FilterCondition.builder()
+                .isFlagged(true)
+                .build());
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+
+    @Test
+    public void filterConditionShouldMapWhenIsUnread() {
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.flagIsUnSet(Flag.SEEN));
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(FilterCondition.builder()
+                .isUnread(true)
+                .build());
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+
+    @Test
+    public void filterConditionShouldMapWhenMaxSize() {
+        int maxSize = 123;
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.sizeLessThan(maxSize));
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(FilterCondition.builder()
+                .maxSize(maxSize)
+                .build());
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+
+    @Test
+    public void filterConditionShouldMapWhenMinSize() {
+        int minSize = 4;
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.sizeGreaterThan(minSize));
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(FilterCondition.builder()
+                .minSize(minSize)
+                .build());
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+
+    @Test
+    public void filterConditionShouldMapWhenHeaderWithOneElement() {
+        String headerName = "name";
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.headerExists(headerName));
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(FilterCondition.builder()
+                .header(Header.from(ImmutableList.of(headerName)))
+                .build());
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+
+    @Test
+    public void filterConditionShouldMapWhenHeaderWithTwoElements() {
+        String headerName = "name";
+        String headerValue = "value";
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.headerContains(headerName, headerValue));
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(FilterCondition.builder()
+                .header(Header.from(ImmutableList.of(headerName, headerValue)))
+                .build());
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+
+    @Test
+    public void filterConditionShouldMapTwoConditions() {
+        String from = "sender@james.org";
+        String to = "recipient@james.org";
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.and(ImmutableList.of(
+                SearchQuery.address(AddressType.From, from),
+                SearchQuery.address(AddressType.To, to))));
+
+        Filter filter = FilterOperator.and(
+                FilterCondition.builder()
+                    .from(from)
+                    .build(),
+                FilterCondition.builder()
+                    .to(to)
+                    .build());
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(filter);
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+
+    @Test
+    public void filterConditionShouldMapWhenAndOperator() {
+        String from = "sender@james.org";
+        String to = "recipient@james.org";
+        String subject = "subject";
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.and(ImmutableList.of(
+                SearchQuery.address(AddressType.From, from),
+                SearchQuery.address(AddressType.To, to),
+                SearchQuery.headerContains("Subject", subject))));
+
+        Filter complexFilter = FilterOperator.and(
+                FilterCondition.builder()
+                    .from(from)
+                    .to(to)
+                    .subject(subject)
+                    .build());
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(complexFilter);
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+
+    @Test
+    public void filterConditionShouldMapWhenOrOperator() {
+        String from = "sender@james.org";
+        String to = "recipient@james.org";
+        String subject = "subject";
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.or(ImmutableList.of(
+                SearchQuery.address(AddressType.From, from),
+                SearchQuery.address(AddressType.To, to),
+                SearchQuery.headerContains("Subject", subject))));
+
+        Filter complexFilter = FilterOperator.or(
+                FilterCondition.builder()
+                    .from(from)
+                    .to(to)
+                    .subject(subject)
+                    .build());
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(complexFilter);
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+
+    @Test
+    public void filterConditionShouldMapWhenNotOperator() {
+        String from = "sender@james.org";
+        String to = "recipient@james.org";
+        String subject = "subject";
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.not(ImmutableList.of(
+                SearchQuery.address(AddressType.From, from),
+                SearchQuery.address(AddressType.To, to),
+                SearchQuery.headerContains("Subject", subject))));
+
+        Filter complexFilter = FilterOperator.not(
+                FilterCondition.builder()
+                    .from(from)
+                    .to(to)
+                    .subject(subject)
+                    .build());
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(complexFilter);
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+
+    @Test
+    public void filterConditionShouldMapWhenComplexFilterTree() {
+        String from = "sender@james.org";
+        String to = "recipient@james.org";
+        String cc = "copy@james.org";
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.and(ImmutableList.of(
+                SearchQuery.address(AddressType.From, from),
+                SearchQuery.or(ImmutableList.of(
+                        SearchQuery.not(SearchQuery.address(AddressType.To, to)),
+                        SearchQuery.address(AddressType.Cc, cc))
+                        )
+                )));
+
+        Filter complexFilter = FilterOperator.and(
+                FilterCondition.builder()
+                    .from(from)
+                    .build(),
+                FilterOperator.or(
+                    FilterOperator.not(
+                        FilterCondition.builder()
+                            .to(to)
+                            .build()),
+                    FilterCondition.builder()
+                        .cc(cc)
+                        .build()
+                ));
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(complexFilter);
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org