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 <[email protected]> Authored: Mon Jul 18 14:37:53 2016 +0200 Committer: Antoine Duprat <[email protected]> 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 = "[email protected]"; + String to = "[email protected]"; + String cc = "[email protected]"; + String bcc = "[email protected]"; + 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 = "[email protected]"; + 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 = "[email protected]"; + 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 = "[email protected]"; + 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 = "[email protected]"; + 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 = "[email protected]"; + String to = "[email protected]"; + 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 = "[email protected]"; + String to = "[email protected]"; + 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 = "[email protected]"; + String to = "[email protected]"; + 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 = "[email protected]"; + String to = "[email protected]"; + 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 = "[email protected]"; + String to = "[email protected]"; + String cc = "[email protected]"; + 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: [email protected] For additional commands, e-mail: [email protected]
