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 bt...@apache.org on 2017/08/25 08:32:40 UTC

[02/13] james-project git commit: JAMES-2110 GetMessageList should allow filtering by keywords

JAMES-2110 GetMessageList should allow filtering by keywords


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

Branch: refs/heads/master
Commit: a6f1c1059d20dd179e9f5feb2e3374c38869cfed
Parents: 92365a2
Author: quynhn <qn...@linagora.com>
Authored: Tue Aug 15 16:45:26 2017 +0700
Committer: Raphael Ouazana <ra...@linagora.com>
Committed: Thu Aug 24 15:47:27 2017 +0200

----------------------------------------------------------------------
 .../james/jmap/model/FilterCondition.java       | 48 ++++++++++--
 .../james/jmap/utils/FilterToSearchQuery.java   | 27 ++++++-
 .../james/jmap/model/FilterConditionTest.java   | 77 +++++++++++++++++++-
 .../jmap/utils/FilterToSearchQueryTest.java     | 52 +++++++++++++
 4 files changed, 196 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/a6f1c105/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 63663c5..334667d 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
@@ -29,6 +29,7 @@ import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.MoreObjects.ToStringHelper;
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 
 @JsonDeserialize(builder = FilterCondition.Builder.class)
@@ -60,10 +61,14 @@ public class FilterCondition implements Filter {
         private String subject;
         private String body;
         private Header header;
+        private Optional<String> hasKeyword;
+        private Optional<String> notKeyword;
 
         private Builder() {
             inMailboxes = Optional.empty();
             notInMailboxes = Optional.empty();
+            hasKeyword = Optional.empty();
+            notKeyword = Optional.empty();
         }
 
         public Builder inMailboxes(String... inMailboxes) {
@@ -88,6 +93,18 @@ public class FilterCondition implements Filter {
             return this;
         }
 
+        @JsonDeserialize
+        public Builder hasKeyword(Optional<String> hasKeyword) {
+            this.hasKeyword = hasKeyword;
+            return this;
+        }
+
+        @JsonDeserialize
+        public Builder notKeyword(Optional<String> notKeyword) {
+            this.notKeyword = notKeyword;
+            return this;
+        }
+
         public Builder before(ZonedDateTime before) {
             this.before = before;
             return this;
@@ -174,9 +191,12 @@ public class FilterCondition implements Filter {
         }
 
         public FilterCondition build() {
+            Preconditions.checkArgument(!hasKeyword.isPresent() || (new Keyword(hasKeyword.get()) != null), "hasKeyword is not valid");
+            Preconditions.checkArgument(!notKeyword.isPresent() || (new Keyword(notKeyword.get()) != null), "notKeyword is not valid");
             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), Optional.ofNullable(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), hasKeyword, notKeyword);
         }
     }
 
@@ -199,10 +219,13 @@ public class FilterCondition implements Filter {
     private final Optional<String> subject;
     private final Optional<String> body;
     private final Optional<Header> header;
+    private final Optional<String> hasKeyword;
+    private final Optional<String> notKeyword;
 
     @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<Header> header) {
+                                       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<Header> header, Optional<String> hasKeyword, Optional<String> notKeyword) {
 
         this.inMailboxes = inMailboxes;
         this.notInMailboxes = notInMailboxes;
@@ -223,6 +246,8 @@ public class FilterCondition implements Filter {
         this.subject = subject;
         this.body = body;
         this.header = header;
+        this.hasKeyword = hasKeyword;
+        this.notKeyword = notKeyword;
     }
 
     public Optional<List<String>> getInMailboxes() {
@@ -301,6 +326,14 @@ public class FilterCondition implements Filter {
         return header;
     }
 
+    public Optional<String> getHasKeyword() {
+        return hasKeyword;
+    }
+
+    public Optional<String> getNotKeyword() {
+        return notKeyword;
+    }
+
     @Override
     public final boolean equals(Object obj) {
         if (obj instanceof FilterCondition) {
@@ -323,14 +356,17 @@ public class FilterCondition implements Filter {
                 && Objects.equals(this.bcc, other.bcc)
                 && Objects.equals(this.subject, other.subject)
                 && Objects.equals(this.body, other.body)
-                && Objects.equals(this.header, other.header);
+                && Objects.equals(this.header, other.header)
+                && Objects.equals(this.hasKeyword, other.hasKeyword)
+                && Objects.equals(this.notKeyword, other.notKeyword);
         }
         return false;
     }
 
     @Override
     public final int hashCode() {
-        return Objects.hash(inMailboxes, notInMailboxes, before, after, minSize, maxSize, isFlagged, isUnread, isAnswered, isDraft, hasAttachment, text, from, to, cc, bcc, subject, body, header);
+        return Objects.hash(inMailboxes, notInMailboxes, before, after, minSize, maxSize, isFlagged, isUnread, isAnswered, isDraft, hasAttachment,
+                text, from, to, cc, bcc, subject, body, header, hasKeyword, notKeyword);
     }
 
     @Override
@@ -355,6 +391,8 @@ public class FilterCondition implements Filter {
         subject.ifPresent(x -> helper.add("subject", x));
         body.ifPresent(x -> helper.add("body", x));
         header.ifPresent(x -> helper.add("header", x));
+        hasKeyword.ifPresent(x -> helper.add("hasKeyword", x));
+        notKeyword.ifPresent(x -> helper.add("notKeyword", x));
         return helper.toString();
     }
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/a6f1c105/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
index 95799e4..f619357 100644
--- 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
@@ -20,12 +20,13 @@
 package org.apache.james.jmap.utils;
 
 import java.util.Date;
-
+import java.util.Optional;
 import javax.mail.Flags.Flag;
 
 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.Keyword;
 import org.apache.james.mailbox.model.SearchQuery;
 import org.apache.james.mailbox.model.SearchQuery.AddressType;
 import org.apache.james.mailbox.model.SearchQuery.Criterion;
@@ -75,9 +76,33 @@ public class FilterToSearchQuery {
         filter.getMaxSize().ifPresent(maxSize -> searchQuery.andCriteria(SearchQuery.sizeLessThan(maxSize)));
         filter.getMinSize().ifPresent(minSize -> searchQuery.andCriteria(SearchQuery.sizeGreaterThan(minSize)));
         filter.getHasAttachment().ifPresent(hasAttachment -> searchQuery.andCriteria(SearchQuery.hasAttachment(hasAttachment)));
+        filter.getHasKeyword().ifPresent(hasKeyword -> {
+            keywordQuery(hasKeyword, true).ifPresent(hasKeywordCriterion
+                -> searchQuery.andCriteria(hasKeywordCriterion));
+        });
+        filter.getNotKeyword().ifPresent(notKeyword -> {
+            keywordQuery(notKeyword, false).ifPresent(notKeywordCriterion
+                -> searchQuery.andCriteria(notKeywordCriterion));
+        });
+
         return searchQuery;
     }
 
+    private Optional<Criterion> keywordQuery(String stringKeyword, boolean isSet) {
+        Keyword keyword = new Keyword(stringKeyword);
+        if (keyword.isExposedImapKeyword()) {
+            return Optional.of(getFlagCriterion(keyword, isSet));
+        }
+
+        return Optional.empty();
+    }
+
+    private Criterion getFlagCriterion(Keyword keyword, boolean isSet) {
+        return keyword.asSystemFlag()
+            .map(flag -> SearchQuery.flagSet(flag, isSet))
+            .orElse(SearchQuery.flagSet(keyword.getFlagName(), isSet));
+    }
+
     private Criterion convertOperator(FilterOperator filter) {
         switch (filter.getOperator()) {
         case AND:

http://git-wip-us.apache.org/repos/asf/james-project/blob/a6f1c105/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 5a518f0..5efaecc 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
@@ -23,15 +23,22 @@ import static org.assertj.core.api.Assertions.assertThat;
 
 import java.time.ZonedDateTime;
 import java.util.Optional;
+import java.util.Set;
 
+import org.junit.Rule;
 import org.junit.Test;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 
 import nl.jqno.equalsverifier.EqualsVerifier;
+import org.junit.rules.ExpectedException;
 
 public class FilterConditionTest {
 
+    @Rule
+    public ExpectedException expectedException = ExpectedException.none();
+
     @Test
     public void buildShouldWorkWhenNoInMailboxes() {
         FilterCondition filterCondition = FilterCondition.builder().build();
@@ -89,9 +96,13 @@ public class FilterConditionTest {
         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<String> hasKeyword = Optional.of("$Draft");
+        Optional<String> notKeyword = Optional.of("$Flagged");
+
+        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));
+                Optional.of(to), Optional.of(cc), Optional.of(bcc), Optional.of(subject), Optional.of(body), Optional.of(header),
+                hasKeyword, notKeyword);
 
         FilterCondition filterCondition = FilterCondition.builder()
                 .inMailboxes(Optional.of(ImmutableList.of("1")))
@@ -113,6 +124,8 @@ public class FilterConditionTest {
                 .subject(subject)
                 .body(body)
                 .header(header)
+                .hasKeyword(hasKeyword)
+                .notKeyword(notKeyword)
                 .build();
 
         assertThat(filterCondition).isEqualToComparingFieldByField(expectedFilterCondition);
@@ -122,4 +135,64 @@ public class FilterConditionTest {
     public void shouldRespectJavaBeanContract() {
         EqualsVerifier.forClass(FilterCondition.class).verify();
     }
+
+    @Test
+    public void buildShouldBuildFilterConditionWithHasKeywordWhenGivenHasKeyword() {
+        String hasKeyword = "$Draft";
+
+        FilterCondition filterCondition = FilterCondition.builder()
+            .hasKeyword(Optional.of(hasKeyword))
+            .build();
+
+        assertThat(filterCondition.getHasKeyword().get())
+            .isEqualTo(hasKeyword);
+    }
+
+    @Test
+    public void buildShouldBuildFilterConditionWithoutHasKeywordWhenDoNotGivenHasKeyword() {
+        FilterCondition filterCondition = FilterCondition.builder()
+            .hasKeyword(Optional.empty())
+            .build();
+
+        assertThat(filterCondition.getHasKeyword().isPresent())
+            .isFalse();
+    }
+
+    @Test
+    public void buildShouldThrowWhenGivenInvalidKeywordAsHasKeyword() {
+        expectedException.expect(IllegalArgumentException.class);
+
+        FilterCondition.builder()
+            .hasKeyword(Optional.of("$Draft%"))
+            .build();
+    }
+
+    @Test
+    public void buildShouldBuildFilterConditionWithNotKeywordWhenGivenNotKeyword() {
+        String notKeyword = "$Draft";
+
+        FilterCondition filterCondition = FilterCondition.builder()
+            .notKeyword(Optional.of(notKeyword))
+            .build();
+        assertThat(filterCondition.getNotKeyword().get()).isEqualTo(notKeyword);
+    }
+
+    @Test
+    public void buildShouldBuildFilterConditionWithoutNotKeywordWhenDoNotGivenNotKeyword() {
+        FilterCondition filterCondition = FilterCondition.builder()
+            .notKeyword(Optional.empty())
+            .build();
+
+        assertThat(filterCondition.getNotKeyword().isPresent())
+            .isFalse();
+    }
+
+    @Test
+    public void buildShouldThrowWhenGivenInvalidKeywordAsNotKeyword() {
+        expectedException.expect(IllegalArgumentException.class);
+
+        FilterCondition.builder()
+            .notKeyword(Optional.of("$Draft%"))
+            .build();
+    }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/a6f1c105/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
index eb05436..aca9757 100644
--- 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
@@ -24,6 +24,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import java.time.ZonedDateTime;
 import java.util.Date;
+import java.util.Optional;
 
 import javax.mail.Flags.Flag;
 
@@ -31,14 +32,17 @@ 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.jmap.model.Keyword;
 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;
+import com.google.common.collect.ImmutableSet;
 
 public class FilterToSearchQueryTest {
+    private final static String FORWARDED = "forwarded";
 
     @Test
     public void filterConditionShouldThrowWhenUnknownFilter() {
@@ -476,4 +480,52 @@ public class FilterToSearchQueryTest {
 
         assertThat(searchQuery).isEqualTo(expectedSearchQuery);
     }
+
+    @Test
+    public void filterConditionShouldMapWhenHasKeyword() {
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.flagIsSet(Flag.FLAGGED));
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(FilterCondition.builder()
+                .hasKeyword(Optional.of("$Flagged"))
+                .build());
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+
+    @Test
+    public void filterConditionShouldMapWhenHasKeywordWithUserFlag() {
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.flagIsSet(FORWARDED));
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(FilterCondition.builder()
+                .hasKeyword(Optional.of(FORWARDED))
+                .build());
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+
+    @Test
+    public void filterConditionShouldMapWhenNotKeyword() {
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.flagIsUnSet(Flag.FLAGGED));
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(FilterCondition.builder()
+            .notKeyword(Optional.of("$Flagged"))
+            .build());
+
+        assertThat(searchQuery).isEqualTo(expectedSearchQuery);
+    }
+
+    @Test
+    public void filterConditionShouldMapWhenNotKeywordWithUserFlag() {
+        SearchQuery expectedSearchQuery = new SearchQuery();
+        expectedSearchQuery.andCriteria(SearchQuery.flagIsUnSet(FORWARDED));
+
+        SearchQuery searchQuery = new FilterToSearchQuery().convert(FilterCondition.builder()
+                .notKeyword(Optional.of(FORWARDED))
+                .build());
+
+        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