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 7f0c2f51aefa3101b720bff9d4183e8c9896cfd4 Author: duc91 <[email protected]> AuthorDate: Mon Sep 21 13:44:48 2020 +0700 JAMES-3374: Email/query sort by sentAt --- .../store/search/SimpleMessageSearchIndexTest.java | 5 - .../store/search/SimpleMessageSearchIndex.java | 44 +-- .../search/comparator/SentDateComparator.java | 21 +- .../contract/EmailQueryMethodContract.scala | 368 ++++++++++++++++++--- .../james/jmap/json/EmailQuerySerializer.scala | 6 +- .../org/apache/james/jmap/mail/EmailQuery.scala | 4 + 6 files changed, 354 insertions(+), 94 deletions(-) diff --git a/mailbox/scanning-search/src/test/java/org/apache/james/mailbox/store/search/SimpleMessageSearchIndexTest.java b/mailbox/scanning-search/src/test/java/org/apache/james/mailbox/store/search/SimpleMessageSearchIndexTest.java index 40a9152..2124af0 100644 --- a/mailbox/scanning-search/src/test/java/org/apache/james/mailbox/store/search/SimpleMessageSearchIndexTest.java +++ b/mailbox/scanning-search/src/test/java/org/apache/james/mailbox/store/search/SimpleMessageSearchIndexTest.java @@ -147,11 +147,6 @@ class SimpleMessageSearchIndexTest extends AbstractMessageSearchIndexTest { @Disabled("JAMES-1799: ignoring failing test after generalizing ElasticSearch test suite to other mailbox search backends") @Override - public void sortOnSentDateShouldWork() { - } - - @Disabled("JAMES-1799: ignoring failing test after generalizing ElasticSearch test suite to other mailbox search backends") - @Override public void addressShouldReturnUidHavingRightRecipientWhenToIsSpecified() { } diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/SimpleMessageSearchIndex.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/SimpleMessageSearchIndex.java index a2fc487..90c7cfe 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/SimpleMessageSearchIndex.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/SimpleMessageSearchIndex.java @@ -21,7 +21,6 @@ package org.apache.james.mailbox.store.search; import static org.apache.james.mailbox.store.mail.AbstractMessageMapper.UNLIMITED; import java.util.Collection; -import java.util.Comparator; import java.util.EnumSet; import java.util.Iterator; import java.util.List; @@ -54,9 +53,10 @@ import org.apache.james.mailbox.store.mail.MessageMapper; import org.apache.james.mailbox.store.mail.MessageMapper.FetchType; import org.apache.james.mailbox.store.mail.MessageMapperFactory; import org.apache.james.mailbox.store.mail.model.MailboxMessage; +import org.apache.james.mailbox.store.search.comparator.CombinedComparator; +import org.apache.james.util.streams.Iterators; import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; import reactor.core.publisher.Flux; import reactor.core.scheduler.Schedulers; @@ -71,41 +71,6 @@ import reactor.core.scheduler.Schedulers; */ public class SimpleMessageSearchIndex implements MessageSearchIndex { - private static class SearchComparator implements Comparator<MailboxMessage> { - public static final int REVERSE = -1; - public static final int EQUALS = 0; - private final List<SearchQuery.Sort> sorts; - - public SearchComparator(SearchQuery query) { - sorts = query.getSorts(); - } - - @Override - public int compare(MailboxMessage message, MailboxMessage other) { - return sorts.stream() - .mapToInt(sort -> { - SearchQuery.Sort.SortClause sortClause = sort.getSortClause(); - int sortResult = compareWithSortClause(message, other, sortClause); - if (sort.isReverse()) { - return sortResult * REVERSE; - } - return sortResult; - }) - .filter(i -> i != 0) - .findFirst() - .orElse(EQUALS); - } - - private int compareWithSortClause(MailboxMessage message, MailboxMessage other, SearchQuery.Sort.SortClause sortClause) { - switch (sortClause) { - case Arrival: - return message.metaData().getInternalDate().compareTo(other.getInternalDate()); - default: - return message.compareTo(other); - } - } - } - private final MessageMapperFactory messageMapperFactory; private final MailboxMapperFactory mailboxMapperFactory; private final TextExtractor textExtractor; @@ -196,9 +161,8 @@ public class SimpleMessageSearchIndex implements MessageSearchIndex { private Flux<? extends SearchResult> searchResults(MailboxSession session, Flux<Mailbox> mailboxes, SearchQuery query) throws MailboxException { return mailboxes.concatMap(mailbox -> Flux.fromStream(getSearchResultStream(session, query, mailbox))) - .collectSortedList(new SearchComparator(query)) - .map(list -> ImmutableList.copyOf(new MessageSearches(list.iterator(), query, textExtractor, attachmentContentLoader, session).iterator())) - .flatMapIterable(list -> list) + .collectSortedList(CombinedComparator.create(query.getSorts())) + .flatMapMany(list -> Iterators.toFlux(new MessageSearches(list.iterator(), query, textExtractor, attachmentContentLoader, session).iterator())) .subscribeOn(Schedulers.elastic()); } diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/SentDateComparator.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/SentDateComparator.java index 07eff78..67bab53 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/SentDateComparator.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/comparator/SentDateComparator.java @@ -18,14 +18,19 @@ ****************************************************************/ package org.apache.james.mailbox.store.search.comparator; +import static org.apache.james.mime4j.codec.DecodeMonitor.SILENT; + import java.time.Instant; import java.time.ZonedDateTime; import java.util.Comparator; +import java.util.Date; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.james.mailbox.store.mail.model.MailboxMessage; +import org.apache.james.mime4j.field.DateTimeFieldLenientImpl; +import org.apache.james.mime4j.stream.RawField; import org.apache.james.util.date.ImapDateTimeFormatter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -72,15 +77,15 @@ public class SentDateComparator extends AbstractHeaderComparator { @Override public int compare(MailboxMessage o1, MailboxMessage o2) { - Instant date1 = getSentDate(o1); - Instant date2 = getSentDate(o2); - return date1.compareTo(date2); + return parseSentDate(o1).compareTo(parseSentDate(o2)); } - - private Instant getSentDate(MailboxMessage message) { - final String value = getHeaderValue("Date", message); - return toISODate(value) - .map(ZonedDateTime::toInstant) + + private Instant parseSentDate(MailboxMessage message) { + String value = getHeaderValue("Date", message); + RawField field = new RawField("Date", value); + return Optional.ofNullable(DateTimeFieldLenientImpl.PARSER.parse(field, SILENT) + .getDate()) + .map(Date::toInstant) .orElse(message.getInternalDate().toInstant()); } } diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala index 3b5a1f9..dcd7a7e 100644 --- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala +++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala @@ -754,7 +754,7 @@ trait EmailQueryMethodContract { | "canCalculateChanges": false, | "position": 0, | "limit": 256, - | "ids": ["${messageId2.serialize()}", "${messageId1.serialize()}"] + | "ids": ["${messageId2.serialize}", "${messageId1.serialize}"] | }, | "c1" | ]] @@ -816,7 +816,7 @@ trait EmailQueryMethodContract { | "canCalculateChanges": false, | "position": 0, | "limit": 256, - | "ids": ["${messageId1.serialize()}"] + | "ids": ["${messageId1.serialize}"] | }, | "c1" | ]] @@ -836,9 +836,9 @@ trait EmailQueryMethodContract { server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath) val requestDateMessage1 = Date.from(ZonedDateTime.now().minusDays(1).toInstant) val messageId1: MessageId = sendMessageToBobInbox(server, message, requestDateMessage1) - val requestDateMessage2 = Date.from(ZonedDateTime.now().minusDays(1).plusHours(1).toInstant) + val requestDateMessage2 = Date.from(ZonedDateTime.now().minusDays(2).toInstant) val messageId2 = sendMessageToBobInbox(server, message, requestDateMessage2) - val requestDateMessage3 = Date.from(ZonedDateTime.now().minusDays(1).plusHours(2).toInstant) + val requestDateMessage3 = Date.from(ZonedDateTime.now().minusDays(3).toInstant) val messageId3 = sendMessageToBobInbox(server, message, requestDateMessage3) val request = @@ -869,7 +869,297 @@ trait EmailQueryMethodContract { assertThatJson(response) .inPath("$.methodResponses[0][1].ids") - .isEqualTo(s"""["${messageId3.serialize()}", "${messageId2.serialize()}", "${messageId1.serialize()}"]""") + .isEqualTo(s"""["${messageId1.serialize}", "${messageId2.serialize}", "${messageId3.serialize}"]""") + } + } + + @Test + def listMailsShouldBeSortedByDescendingOrderOfSentAt(server: GuiceJamesServer): Unit = { + server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB)) + val otherMailboxPath = MailboxPath.forUser(BOB, "other") + server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath) + val message: Message = Message.Builder + .of + .setSubject("test") + .setBody("testmail", StandardCharsets.UTF_8) + .build + + val requestDateMessage1 = Date.from(ZonedDateTime.now().minusDays(1).toInstant) + val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl]) + .appendMessage(BOB.asString, MailboxPath.inbox(BOB), + AppendCommand.builder() + .withInternalDate(requestDateMessage1) + .build(message)) + .getMessageId + + val requestDateMessage2 = Date.from(ZonedDateTime.now().minusDays(2).toInstant) + val messageId2 = server.getProbe(classOf[MailboxProbeImpl]) + .appendMessage(BOB.asString, MailboxPath.inbox(BOB), + AppendCommand.builder() + .withInternalDate(requestDateMessage2) + .build(message)) + .getMessageId + + val requestDateMessage3 = Date.from(ZonedDateTime.now().minusDays(3).toInstant) + val messageId3 = server.getProbe(classOf[MailboxProbeImpl]) + .appendMessage(BOB.asString, MailboxPath.inbox(BOB), + AppendCommand.builder() + .withInternalDate(requestDateMessage3) + .build(message)) + .getMessageId + + val request = + s"""{ + | "using": [ + | "urn:ietf:params:jmap:core", + | "urn:ietf:params:jmap:mail"], + | "methodCalls": [[ + | "Email/query", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "comparator": [{ + | "property":"sentAt", + | "isAscending": false + | }] + | }, + | "c1"]] + |}""".stripMargin + + awaitAtMostTenSeconds.untilAsserted { () => + val response = `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(request) + .when + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response) + .inPath("$.methodResponses[0][1].ids") + .isEqualTo(s"""["${messageId1.serialize}", "${messageId2.serialize}", "${messageId3.serialize}"]""") + } + } + + @Test + def listMailsShouldBeSortedByAscendingOrderOfSentAt(server: GuiceJamesServer): Unit = { + server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB)) + val otherMailboxPath = MailboxPath.forUser(BOB, "other") + server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath) + + val requestDateMessage1 = Date.from(ZonedDateTime.now().minusDays(3).toInstant) + val message1: Message = Message.Builder + .of + .setSubject("test") + .setDate(requestDateMessage1) + .setBody("testmail", StandardCharsets.UTF_8) + .build + val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl]) + .appendMessage(BOB.asString, MailboxPath.inbox(BOB), + AppendCommand.builder().build(message1)) + .getMessageId + + val requestDateMessage2 = Date.from(ZonedDateTime.now().minusDays(2).toInstant) + val message2: Message = Message.Builder + .of + .setSubject("test") + .setDate(requestDateMessage2) + .setBody("testmail", StandardCharsets.UTF_8) + .build + val messageId2 = server.getProbe(classOf[MailboxProbeImpl]) + .appendMessage(BOB.asString, MailboxPath.inbox(BOB), + AppendCommand.builder().build(message2)) + .getMessageId + + val requestDateMessage3 = Date.from(ZonedDateTime.now().minusDays(1).toInstant) + val message3: Message = Message.Builder + .of + .setSubject("test") + .setDate(requestDateMessage3) + .setBody("testmail", StandardCharsets.UTF_8) + .build + val messageId3 = server.getProbe(classOf[MailboxProbeImpl]) + .appendMessage(BOB.asString, MailboxPath.inbox(BOB), + AppendCommand.builder().build(message3)) + .getMessageId + + val request = + s"""{ + | "using": [ + | "urn:ietf:params:jmap:core", + | "urn:ietf:params:jmap:mail"], + | "methodCalls": [[ + | "Email/query", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "comparator": [{ + | "property":"sentAt", + | "isAscending": true + | }] + | }, + | "c1"]] + |}""".stripMargin + + awaitAtMostTenSeconds.untilAsserted { () => + val response = `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(request) + .when + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response) + .inPath("$.methodResponses[0][1].ids") + .isEqualTo(s"""["${messageId1.serialize}", "${messageId2.serialize}", "${messageId3.serialize}"]""") + } + } + + @Test + def listMailsShouldBeSortedByAscendingOrderWhenSortingBySentAtWithoutOrdering(server: GuiceJamesServer): Unit = { + server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB)) + val otherMailboxPath = MailboxPath.forUser(BOB, "other") + server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath) + + val requestDateMessage1 = Date.from(ZonedDateTime.now().minusDays(3).toInstant) + val message1: Message = Message.Builder + .of + .setSubject("test") + .setDate(requestDateMessage1) + .setBody("testmail", StandardCharsets.UTF_8) + .build + val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl]) + .appendMessage(BOB.asString, MailboxPath.inbox(BOB), + AppendCommand.builder().build(message1)) + .getMessageId + + val requestDateMessage2 = Date.from(ZonedDateTime.now().minusDays(2).toInstant) + val message2: Message = Message.Builder + .of + .setSubject("test") + .setDate(requestDateMessage2) + .setBody("testmail", StandardCharsets.UTF_8) + .build + val messageId2 = server.getProbe(classOf[MailboxProbeImpl]) + .appendMessage(BOB.asString, MailboxPath.inbox(BOB), + AppendCommand.builder().build(message2)) + .getMessageId + + val requestDateMessage3 = Date.from(ZonedDateTime.now().minusDays(1).toInstant) + val message3: Message = Message.Builder + .of + .setSubject("test") + .setDate(requestDateMessage3) + .setBody("testmail", StandardCharsets.UTF_8) + .build + val messageId3 = server.getProbe(classOf[MailboxProbeImpl]) + .appendMessage(BOB.asString, MailboxPath.inbox(BOB), + AppendCommand.builder().build(message3)) + .getMessageId + + val request = + s"""{ + | "using": [ + | "urn:ietf:params:jmap:core", + | "urn:ietf:params:jmap:mail"], + | "methodCalls": [[ + | "Email/query", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "comparator": [{ + | "property":"sentAt" + | }] + | }, + | "c1"]] + |}""".stripMargin + + awaitAtMostTenSeconds.untilAsserted { () => + val response = `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(request) + .when + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response) + .inPath("$.methodResponses[0][1].ids") + .isEqualTo(s"""["${messageId1.serialize}", "${messageId2.serialize}", "${messageId3.serialize}"]""") + } + } + + @Test + def listMailsShouldBeSortedByAscendingOrderOfInternalDateByDefaultWhenNoDateInHeader(server: GuiceJamesServer): Unit = { + server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB)) + val otherMailboxPath = MailboxPath.forUser(BOB, "other") + server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath) + + val requestDateMessage1 = Date.from(ZonedDateTime.now().minusDays(2).toInstant) + val message: Message = Message.Builder + .of + .setSubject("test") + .setBody("testmail", StandardCharsets.UTF_8) + .build + val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl]) + .appendMessage(BOB.asString, MailboxPath.inbox(BOB), + AppendCommand.builder().withInternalDate(requestDateMessage1).build(message)) + .getMessageId + + val requestDateMessage2 = Date.from(ZonedDateTime.now().minusDays(1).toInstant) + val messageId2 = server.getProbe(classOf[MailboxProbeImpl]) + .appendMessage(BOB.asString, MailboxPath.inbox(BOB), + AppendCommand.builder().withInternalDate(requestDateMessage2).build(message)) + .getMessageId + + val requestDateMessage3 = Date.from(ZonedDateTime.now().toInstant) + val messageId3 = server.getProbe(classOf[MailboxProbeImpl]) + .appendMessage(BOB.asString, MailboxPath.inbox(BOB), + AppendCommand.builder().withInternalDate(requestDateMessage3).build(message)) + .getMessageId + + val request = + s"""{ + | "using": [ + | "urn:ietf:params:jmap:core", + | "urn:ietf:params:jmap:mail"], + | "methodCalls": [[ + | "Email/query", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "comparator": [{ + | "property":"sentAt" + | }] + | }, + | "c1"]] + |}""".stripMargin + + awaitAtMostTenSeconds.untilAsserted { () => + val response = `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(request) + .when + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response) + .inPath("$.methodResponses[0][1].ids") + .isEqualTo(s"""["${messageId1.serialize}", "${messageId2.serialize}", "${messageId3.serialize}"]""") } } @@ -986,7 +1276,7 @@ trait EmailQueryMethodContract { assertThatJson(response) .inPath("$.methodResponses[0][1].ids") - .isEqualTo(s"""["${messageId1.serialize()}", "${messageId2.serialize()}", "${messageId3.serialize()}"]""") + .isEqualTo(s"""["${messageId1.serialize}", "${messageId2.serialize}", "${messageId3.serialize}"]""") } } @@ -1045,7 +1335,7 @@ trait EmailQueryMethodContract { assertThatJson(response) .inPath("$.methodResponses[0][1].ids") - .isEqualTo(s"""["${messageId1.serialize()}", "${messageId2.serialize()}", "${messageId3.serialize()}"]""") + .isEqualTo(s"""["${messageId1.serialize}", "${messageId2.serialize}", "${messageId3.serialize}"]""") } } @@ -1104,7 +1394,7 @@ trait EmailQueryMethodContract { assertThatJson(response) .inPath("$.methodResponses[0][1].ids") - .isEqualTo(s"""["${messageId3.serialize()}", "${messageId2.serialize()}", "${messageId1.serialize()}"]""") + .isEqualTo(s"""["${messageId3.serialize}", "${messageId2.serialize}", "${messageId1.serialize}"]""") } } @@ -1123,7 +1413,7 @@ trait EmailQueryMethodContract { | { | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", | "filter": { - | "inMailbox": "${otherMailboxId.serialize()}" + | "inMailbox": "${otherMailboxId.serialize}" | }, | "comparator": [{ | "isAscending":true @@ -1183,7 +1473,7 @@ trait EmailQueryMethodContract { | { | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", | "filter": { - | "inMailbox": "${otherMailboxId.serialize()}" + | "inMailbox": "${otherMailboxId.serialize}" | } | }, | "c1"]] @@ -1204,7 +1494,7 @@ trait EmailQueryMethodContract { assertThatJson(response) .inPath("$.methodResponses[0][1].ids") - .isEqualTo(s"""["${messageId2.serialize()}"]""") + .isEqualTo(s"""["${messageId2.serialize}"]""") } } @@ -1223,7 +1513,7 @@ trait EmailQueryMethodContract { | { | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", | "filter": { - | "inMailbox": "${otherMailboxId.serialize()}" + | "inMailbox": "${otherMailboxId.serialize}" | }, | "comparator": [{ | "property":"unsupported", @@ -1560,7 +1850,7 @@ trait EmailQueryMethodContract { | { | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", | "filter": { - | "inMailbox": "${otherMailboxId.serialize()}" + | "inMailbox": "${otherMailboxId.serialize}" | } | }, | "c1"]] @@ -1587,7 +1877,7 @@ trait EmailQueryMethodContract { | "error", | { | "type": "invalidArguments", - | "description": "${otherMailboxId.serialize()} can not be found" + | "description": "${otherMailboxId.serialize} can not be found" | }, | "c1" | ] @@ -1647,8 +1937,8 @@ trait EmailQueryMethodContract { .inPath("$.methodResponses[0][1].ids") .isEqualTo( s"""[ - | "${id2.serialize()}", - | "${id3.serialize()}" + | "${id2.serialize}", + | "${id3.serialize}" |]""".stripMargin) } } @@ -1704,7 +1994,7 @@ trait EmailQueryMethodContract { .inPath("$.methodResponses[0][1].ids") .isEqualTo( s"""[ - | "${id1.serialize()}" + | "${id1.serialize}" |]""".stripMargin) } } @@ -1849,7 +2139,7 @@ trait EmailQueryMethodContract { | { | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", | "filter": { - | "inMailboxOtherThan": [ "${otherMailboxId.serialize()}" ] + | "inMailboxOtherThan": [ "${otherMailboxId.serialize}" ] | } | }, | "c1"]] @@ -1870,7 +2160,7 @@ trait EmailQueryMethodContract { assertThatJson(response) .inPath("$.methodResponses[0][1].ids") - .isEqualTo(s"""["${messageId1.serialize()}"]""") + .isEqualTo(s"""["${messageId1.serialize}"]""") } } @@ -1921,7 +2211,7 @@ trait EmailQueryMethodContract { assertThatJson(response) .inPath("$.methodResponses[0][1].ids") - .isEqualTo(s"""["${messageId2.serialize()}", "${messageId1.serialize()}"]""") + .isEqualTo(s"""["${messageId2.serialize}", "${messageId1.serialize}"]""") } } @@ -1952,8 +2242,8 @@ trait EmailQueryMethodContract { | { | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", | "filter": { - | "inMailbox": "${inbox.serialize()}", - | "inMailboxOtherThan": [ "${otherMailboxId.serialize()}" ] + | "inMailbox": "${inbox.serialize}", + | "inMailboxOtherThan": [ "${otherMailboxId.serialize}" ] | } | }, | "c1"]] @@ -1974,7 +2264,7 @@ trait EmailQueryMethodContract { assertThatJson(response) .inPath("$.methodResponses[0][1].ids") - .isEqualTo(s"""["${messageId1.serialize()}"]""") + .isEqualTo(s"""["${messageId1.serialize}"]""") } } @@ -2005,8 +2295,8 @@ trait EmailQueryMethodContract { | { | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", | "filter" : { - | "inMailbox": "${inbox.serialize()}", - | "inMailboxOtherThan": [ "${inbox.serialize()}" ] + | "inMailbox": "${inbox.serialize}", + | "inMailboxOtherThan": [ "${inbox.serialize}" ] | } | }, | "c1"]] @@ -2080,7 +2370,7 @@ trait EmailQueryMethodContract { assertThatJson(response) .inPath("$.methodResponses[0][1].ids") - .isEqualTo(s"""["${messageId1.serialize()}"]""") + .isEqualTo(s"""["${messageId1.serialize}"]""") } } @Test @@ -2131,7 +2421,7 @@ trait EmailQueryMethodContract { assertThatJson(response) .inPath("$.methodResponses[0][1].ids") - .isEqualTo(s"""["${messageId1.serialize()}"]""") + .isEqualTo(s"""["${messageId1.serialize}"]""") } } @@ -2184,7 +2474,7 @@ trait EmailQueryMethodContract { assertThatJson(response) .inPath("$.methodResponses[0][1].ids") - .isEqualTo(s"""["${messageId2.serialize()}"]""") + .isEqualTo(s"""["${messageId2.serialize}"]""") } } @@ -2236,7 +2526,7 @@ trait EmailQueryMethodContract { assertThatJson(response) .inPath("$.methodResponses[0][1].ids") - .isEqualTo(s"""["${messageId2.serialize()}"]""") + .isEqualTo(s"""["${messageId2.serialize}"]""") } } @@ -2294,7 +2584,7 @@ trait EmailQueryMethodContract { | "queryState": "${generateQueryState(messageId2)}", | "canCalculateChanges": false, | "position": 0, - | "ids": ["${messageId2.serialize()}"] + | "ids": ["${messageId2.serialize}"] | }, | "c1" | ]] @@ -2394,7 +2684,7 @@ trait EmailQueryMethodContract { assertThatJson(response) .inPath("$.methodResponses[0][1].ids") - .isEqualTo("[" + expectedMessages.map(message => s""""${message.serialize()}"""").mkString(", ") + "]") + .isEqualTo("[" + expectedMessages.map(message => s""""${message.serialize}"""").mkString(", ") + "]") assertThatJson(response) .inPath("$.methodResponses[0][1].limit") @@ -2449,7 +2739,7 @@ trait EmailQueryMethodContract { assertThatJson(response) .inPath("$.methodResponses[0][1].ids") - .isEqualTo("[" + expectedMessages.map(message => s""""${message.serialize()}"""").mkString(", ") + "]") + .isEqualTo("[" + expectedMessages.map(message => s""""${message.serialize}"""").mkString(", ") + "]") assertThatJson(response) .inPath("$.methodResponses[0][1].limit") @@ -2514,7 +2804,7 @@ trait EmailQueryMethodContract { | "position": 1, | "limit": 256, | "canCalculateChanges": false, - | "ids": ["${messageId1.serialize()}"] + | "ids": ["${messageId1.serialize}"] | }, | "c1" | ]] @@ -2579,7 +2869,7 @@ trait EmailQueryMethodContract { | "position": 0, | "limit": 256, | "canCalculateChanges": false, - | "ids": ["${messageId2.serialize()}", "${messageId1.serialize()}"] + | "ids": ["${messageId2.serialize}", "${messageId1.serialize}"] | }, | "c1" | ]] @@ -2759,7 +3049,7 @@ trait EmailQueryMethodContract { assertThatJson(response) .inPath("$.methodResponses[0][1].ids") - .isEqualTo(s"""["${messageId.serialize()}"]""") + .isEqualTo(s"""["${messageId.serialize}"]""") } } @@ -3043,7 +3333,7 @@ trait EmailQueryMethodContract { assertThatJson(response) .inPath("$.methodResponses[0][1].ids") - .isEqualTo(s"""["${messageId.serialize()}"]""") + .isEqualTo(s"""["${messageId.serialize}"]""") } } @@ -3094,7 +3384,7 @@ trait EmailQueryMethodContract { assertThatJson(response) .inPath("$.methodResponses[0][1].ids") - .isEqualTo(s"""["${messageWithoudFlagId.serialize()}"]""") + .isEqualTo(s"""["${messageWithoudFlagId.serialize}"]""") } } @@ -3144,7 +3434,7 @@ trait EmailQueryMethodContract { assertThatJson(response) .inPath("$.methodResponses[0][1].ids") - .isEqualTo(s"""["${messageWithoudFlagId.serialize()}"]""") + .isEqualTo(s"""["${messageWithoudFlagId.serialize}"]""") } } @@ -3157,7 +3447,7 @@ trait EmailQueryMethodContract { private def generateQueryState(messages: MessageId*): String = { Hashing.murmur3_32() - .hashUnencodedChars(messages.toList.map(_.serialize()).mkString(" ")) + .hashUnencodedChars(messages.toList.map(_.serialize).mkString(" ")) .toString } } diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala index e2dce1b..84b90af 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailQuerySerializer.scala @@ -20,8 +20,9 @@ package org.apache.james.jmap.json import javax.inject.Inject -import org.apache.james.jmap.mail.{AllInThreadHaveKeywordSortProperty, Anchor, AnchorOffset, Bcc, Body, Cc, CollapseThreads, Collation, Comparator, EmailQueryRequest, EmailQueryResponse, FilterCondition, From, FromSortProperty, HasAttachment, HasKeywordSortProperty, Header, IsAscending, ReceivedAtSortProperty, SizeSortProperty, SomeInThreadHaveKeywordSortProperty, SortProperty, Subject, SubjectSortProperty, Text, To, ToSortProperty} -import org.apache.james.jmap.model.{AccountId, CanCalculateChanges, Keyword, LimitUnparsed, PositionUnparsed, QueryState} +import org.apache.james.jmap.mail.{AllInThreadHaveKeywordSortProperty, Anchor, AnchorOffset, Bcc, Body, Cc, CollapseThreads, From, FromSortProperty, HasKeywordSortProperty, Header, SizeSortProperty, SomeInThreadHaveKeywordSortProperty, Subject, SubjectSortProperty, Text, To, ToSortProperty, HasAttachment, Collation, Comparator, EmailQueryRequest, EmailQueryResponse, FilterCondition, IsAscending, ReceivedAtSortProperty, SentAtSortProperty, SortProperty} +import org.apache.james.jmap.model.{CanCalculateChanges, LimitUnparsed, PositionUnparsed, QueryState} +import org.apache.james.jmap.model.{AccountId, Keyword} import org.apache.james.mailbox.model.{MailboxId, MessageId} import play.api.libs.json._ @@ -74,6 +75,7 @@ class EmailQuerySerializer @Inject()(mailboxIdFactory: MailboxId.Factory) { case JsString("to") => JsSuccess(ToSortProperty) case JsString("subject") => JsSuccess(SubjectSortProperty) case JsString("hasKeyword") => JsSuccess(HasKeywordSortProperty) + case JsString("sentAt") => JsSuccess(SentAtSortProperty) case JsString(others) => JsError(s"'$others' is not a supported sort property") case _ => JsError(s"Expecting a JsString to represent a sort property") } diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala index 6e5c308..e91530b 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala @@ -98,6 +98,10 @@ case object HasKeywordSortProperty extends SortProperty { override def toSortClause: Either[UnsupportedSortException, SortClause] = Left(UnsupportedSortException("hasKeyword")) } +case object SentAtSortProperty extends SortProperty { + override def toSortClause: Either[UnsupportedSortException, SortClause] = scala.Right(SortClause.SentDate) +} + object IsAscending { val DESCENDING: IsAscending = IsAscending(false) val ASCENDING: IsAscending = IsAscending(true) --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
