This is an automated email from the ASF dual-hosted git repository. btellier pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit f5d90c6bc735c4f5657beed27b8558d19fd7c136 Author: Benoit Tellier <[email protected]> AuthorDate: Mon Nov 16 17:28:19 2020 +0700 JAMES-3440 JMAP Draft should use EmailQueryView when enabled --- .../src/test/resources/listeners.xml | 4 ++ .../integration/GetMessageListMethodTest.java | 22 +++++++ .../src/test/resources/listeners.xml | 4 ++ .../src/test/resources/listeners.xml | 4 ++ .../jmap/draft/methods/GetMessageListMethod.java | 77 ++++++++++++++++++++-- .../org/apache/james/jmap/draft/model/Filter.java | 8 +++ .../james/jmap/draft/model/FilterCondition.java | 39 +++++++++++ .../src/test/resources/listeners.xml | 4 ++ .../src/test/resources/listeners.xml | 4 ++ 9 files changed, 159 insertions(+), 7 deletions(-) diff --git a/server/protocols/jmap-draft-integration-testing/cassandra-jmap-draft-integration-testing/src/test/resources/listeners.xml b/server/protocols/jmap-draft-integration-testing/cassandra-jmap-draft-integration-testing/src/test/resources/listeners.xml index ff2e517..1ff4055 100644 --- a/server/protocols/jmap-draft-integration-testing/cassandra-jmap-draft-integration-testing/src/test/resources/listeners.xml +++ b/server/protocols/jmap-draft-integration-testing/cassandra-jmap-draft-integration-testing/src/test/resources/listeners.xml @@ -46,4 +46,8 @@ <name>second</name> </configuration> </listener> + <listener> + <class>org.apache.james.jmap.event.PopulateEmailQueryViewListener</class> + <async>true</async> + </listener> </listeners> \ No newline at end of file diff --git a/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/GetMessageListMethodTest.java b/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/GetMessageListMethodTest.java index f130a7d..bfff531 100644 --- a/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/GetMessageListMethodTest.java +++ b/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/GetMessageListMethodTest.java @@ -1771,6 +1771,28 @@ public abstract class GetMessageListMethodTest { } @Test + public void getMessageListShouldSortMessagesWhenSortedByDateDescAndInMailbox() throws Exception { + MailboxId mailboxId = mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, ALICE.asString(), "mailbox"); + + LocalDate date = LocalDate.now(); + ComposedMessageId message1 = mailboxProbe.appendMessage(ALICE.asString(), ALICE_MAILBOX, + new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes()), convertToDate(date.plusDays(1)), false, new Flags()); + ComposedMessageId message2 = mailboxProbe.appendMessage(ALICE.asString(), ALICE_MAILBOX, + new ByteArrayInputStream("Subject: test2\r\n\r\ntestmail".getBytes()), convertToDate(date), false, new Flags()); + await(); + + given() + .header("Authorization", aliceAccessToken.asString()) + .body("[[\"getMessageList\", {\"filter\":{\"inMailboxes\":[\"" + mailboxId.serialize() + "\"]}, \"sort\":[\"date desc\"]}, \"#0\"]]") + .when() + .post("/jmap") + .then() + .statusCode(200) + .body(NAME, equalTo("messageList")) + .body(ARGUMENTS + ".messageIds", contains(message1.getMessageId().serialize(), message2.getMessageId().serialize())); + } + + @Test public void getMessageListShouldWorkWhenCollapseThreadIsFalse() { given() .header("Authorization", aliceAccessToken.asString()) diff --git a/server/protocols/jmap-draft-integration-testing/memory-jmap-draft-integration-testing/src/test/resources/listeners.xml b/server/protocols/jmap-draft-integration-testing/memory-jmap-draft-integration-testing/src/test/resources/listeners.xml index ae2e80a..a686755 100644 --- a/server/protocols/jmap-draft-integration-testing/memory-jmap-draft-integration-testing/src/test/resources/listeners.xml +++ b/server/protocols/jmap-draft-integration-testing/memory-jmap-draft-integration-testing/src/test/resources/listeners.xml @@ -46,4 +46,8 @@ <listener> <class>org.apache.james.mailbox.spamassassin.SpamAssassinListener</class> </listener> + <listener> + <class>org.apache.james.jmap.event.PopulateEmailQueryViewListener</class> + <async>true</async> + </listener> </listeners> \ No newline at end of file diff --git a/server/protocols/jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/resources/listeners.xml b/server/protocols/jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/resources/listeners.xml index cac2777..43c8b96 100644 --- a/server/protocols/jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/resources/listeners.xml +++ b/server/protocols/jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/resources/listeners.xml @@ -49,4 +49,8 @@ <listener> <class>org.apache.james.mailbox.spamassassin.SpamAssassinListener</class> </listener> + <listener> + <class>org.apache.james.jmap.event.PopulateEmailQueryViewListener</class> + <async>true</async> + </listener> </listeners> \ No newline at end of file diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/GetMessageListMethod.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/GetMessageListMethod.java index a14069c..5935b79 100644 --- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/GetMessageListMethod.java +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/GetMessageListMethod.java @@ -21,6 +21,7 @@ package org.apache.james.jmap.draft.methods; import static org.apache.james.util.ReactorUtils.context; +import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; import java.util.Set; @@ -31,6 +32,8 @@ import javax.inject.Inject; import javax.inject.Named; import org.apache.commons.lang3.NotImplementedException; +import org.apache.james.jmap.JMAPConfiguration; +import org.apache.james.jmap.api.projections.EmailQueryView; import org.apache.james.jmap.draft.model.Filter; import org.apache.james.jmap.draft.model.FilterCondition; import org.apache.james.jmap.draft.model.GetMessageListRequest; @@ -42,17 +45,19 @@ import org.apache.james.jmap.draft.utils.FilterToCriteria; import org.apache.james.jmap.draft.utils.SortConverter; import org.apache.james.mailbox.MailboxManager; import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxNotFoundException; import org.apache.james.mailbox.model.MailboxId; import org.apache.james.mailbox.model.MailboxId.Factory; import org.apache.james.mailbox.model.MultimailboxesSearchQuery; import org.apache.james.mailbox.model.SearchQuery; import org.apache.james.metrics.api.MetricFactory; import org.apache.james.util.MDCBuilder; +import org.apache.james.util.streams.Limit; import com.github.fge.lambdas.Throwing; import com.github.steveash.guavate.Guavate; -import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -71,17 +76,25 @@ public class GetMessageListMethod implements Method { private final long maximumLimit; private final GetMessagesMethod getMessagesMethod; private final Factory mailboxIdFactory; + private final EmailQueryView emailQueryView; + private final JMAPConfiguration configuration; private final MetricFactory metricFactory; @Inject - @VisibleForTesting public GetMessageListMethod(MailboxManager mailboxManager, - @Named(MAXIMUM_LIMIT) long maximumLimit, GetMessagesMethod getMessagesMethod, MailboxId.Factory mailboxIdFactory, - MetricFactory metricFactory) { + private GetMessageListMethod(MailboxManager mailboxManager, + @Named(MAXIMUM_LIMIT) long maximumLimit, + GetMessagesMethod getMessagesMethod, + Factory mailboxIdFactory, + EmailQueryView emailQueryView, + JMAPConfiguration configuration, + MetricFactory metricFactory) { this.mailboxManager = mailboxManager; this.maximumLimit = maximumLimit; this.getMessagesMethod = getMessagesMethod; this.mailboxIdFactory = mailboxIdFactory; + this.emailQueryView = emailQueryView; + this.configuration = configuration; this.metricFactory = metricFactory; } @@ -149,14 +162,64 @@ public class GetMessageListMethod implements Method { } private Mono<GetMessageListResponse> getMessageListResponse(GetMessageListRequest messageListRequest, MailboxSession mailboxSession) { + long position = messageListRequest.getPosition().map(Number::asLong).orElse(DEFAULT_POSITION); + long limit = position + messageListRequest.getLimit().map(Number::asLong).orElse(maximumLimit); + + if (isListingContentInMailboxQuery(messageListRequest)) { + Filter filter = messageListRequest.getFilter().get(); + FilterCondition condition = (FilterCondition) filter; + String mailboxIdAsString = condition.getInMailboxes().get().iterator().next(); + MailboxId mailboxId = mailboxIdFactory.fromString(mailboxIdAsString); + Limit aLimit = Limit.from(Math.toIntExact(limit)); + + return Mono.fromCallable(() -> mailboxManager.getMailbox(mailboxId, mailboxSession)) + .subscribeOn(Schedulers.elastic()) + .then(emailQueryView.listMailboxContent(mailboxId, aLimit) + .skip(position) + .take(limit) + .reduce(GetMessageListResponse.builder(), GetMessageListResponse.Builder::messageId) + .map(GetMessageListResponse.Builder::build)) + .onErrorResume(MailboxNotFoundException.class, e -> Mono.just(GetMessageListResponse.builder().build())); + } + if (isListingContentInMailboxAfterQuery(messageListRequest)) { + Filter filter = messageListRequest.getFilter().get(); + FilterCondition condition = (FilterCondition) filter; + String mailboxIdAsString = condition.getInMailboxes().get().iterator().next(); + MailboxId mailboxId = mailboxIdFactory.fromString(mailboxIdAsString); + ZonedDateTime after = condition.getAfter().get(); + Limit aLimit = Limit.from(Math.toIntExact(limit)); + + return Mono.fromCallable(() -> mailboxManager.getMailbox(mailboxId, mailboxSession)) + .subscribeOn(Schedulers.elastic()) + .then(emailQueryView.listMailboxContentSinceReceivedAt(mailboxId, after, aLimit) + .skip(position) + .take(limit) + .reduce(GetMessageListResponse.builder(), GetMessageListResponse.Builder::messageId) + .map(GetMessageListResponse.Builder::build)) + .onErrorResume(MailboxNotFoundException.class, e -> Mono.just(GetMessageListResponse.builder().build())); + } + return querySearchBackend(messageListRequest, position, limit, mailboxSession); + } + + private boolean isListingContentInMailboxQuery(GetMessageListRequest messageListRequest) { + return configuration.isEmailQueryViewEnabled() + && messageListRequest.getFilter().map(Filter::inMailboxFilterOnly).orElse(false) + && messageListRequest.getSort().equals(ImmutableList.of("date desc")); + } + + private boolean isListingContentInMailboxAfterQuery(GetMessageListRequest messageListRequest) { + return configuration.isEmailQueryViewEnabled() + && messageListRequest.getFilter().map(Filter::inMailboxAndAfterFiltersOnly).orElse(false) + && messageListRequest.getSort().equals(ImmutableList.of("date desc")); + } + + private Mono<GetMessageListResponse> querySearchBackend(GetMessageListRequest messageListRequest, long position, long limit, MailboxSession mailboxSession) { Mono<MultimailboxesSearchQuery> searchQuery = Mono.fromCallable(() -> convertToSearchQuery(messageListRequest)) .subscribeOn(Schedulers.parallel()); - Long positionValue = messageListRequest.getPosition().map(Number::asLong).orElse(DEFAULT_POSITION); - long limit = positionValue + messageListRequest.getLimit().map(Number::asLong).orElse(maximumLimit); return searchQuery .flatMapMany(Throwing.function(query -> mailboxManager.search(query, mailboxSession, limit))) - .skip(positionValue) + .skip(position) .reduce(GetMessageListResponse.builder(), GetMessageListResponse.Builder::messageId) .map(GetMessageListResponse.Builder::build); } diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/Filter.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/Filter.java index 0f26d7c..d562634 100644 --- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/Filter.java +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/Filter.java @@ -39,6 +39,14 @@ public interface Filter { String prettyPrint(String indentation); + default boolean inMailboxFilterOnly() { + return false; + } + + default boolean inMailboxAndAfterFiltersOnly() { + return false; + } + default List<FilterCondition> breadthFirstVisit() { return this.breadthFirstVisit(0) .collect(Guavate.toImmutableList()); diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/FilterCondition.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/FilterCondition.java index e61aba5..14d723e 100644 --- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/FilterCondition.java +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/FilterCondition.java @@ -279,6 +279,45 @@ public class FilterCondition implements Filter { this.attachmentFileName = attachmentFileName; } + @Override + public boolean inMailboxFilterOnly() { + return inMailboxes.filter(list -> list.size() == 1).isPresent() + && after.isEmpty() + && noOtherFiltersSet(); + } + + @Override + public boolean inMailboxAndAfterFiltersOnly() { + return inMailboxes.filter(list -> list.size() == 1).isPresent() + && after.isPresent() + && noOtherFiltersSet(); + } + + private boolean noOtherFiltersSet() { + return notInMailboxes.isEmpty() + && before.isEmpty() + && minSize.isEmpty() + && maxSize.isEmpty() + && isFlagged.isEmpty() + && isUnread.isEmpty() + && isAnswered.isEmpty() + && isDraft.isEmpty() + && isForwarded.isEmpty() + && hasAttachment.isEmpty() + && text.isEmpty() + && from.isEmpty() + && to.isEmpty() + && cc.isEmpty() + && bcc.isEmpty() + && subject.isEmpty() + && body.isEmpty() + && attachments.isEmpty() + && header.isEmpty() + && hasKeyword.isEmpty() + && notKeyword.isEmpty() + && attachmentFileName.isEmpty(); + } + public Optional<List<String>> getInMailboxes() { return inMailboxes; } diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/resources/listeners.xml b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/resources/listeners.xml index ff2e517..1ff4055 100644 --- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/resources/listeners.xml +++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/resources/listeners.xml @@ -46,4 +46,8 @@ <name>second</name> </configuration> </listener> + <listener> + <class>org.apache.james.jmap.event.PopulateEmailQueryViewListener</class> + <async>true</async> + </listener> </listeners> \ No newline at end of file diff --git a/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/resources/listeners.xml b/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/resources/listeners.xml index 59e3fec..a1a139d 100644 --- a/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/resources/listeners.xml +++ b/server/protocols/webadmin-integration-test/memory-webadmin-integration-test/src/test/resources/listeners.xml @@ -43,4 +43,8 @@ <name>second</name> </configuration> </listener> + <listener> + <class>org.apache.james.jmap.event.PopulateEmailQueryViewListener</class> + <async>true</async> + </listener> </listeners> \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
