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]

Reply via email to