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 03e535d828072a29425a0d653ec766314e3e9343
Author: RĂ©mi Kowalski <[email protected]>
AuthorDate: Fri Sep 18 11:35:55 2020 +0200

    JAMES-3381 email/query implements limit
---
 .../contract/EmailQueryMethodContract.scala        | 267 ++++++++++++++++++---
 .../james/jmap/json/EmailQuerySerializer.scala     |   6 +-
 .../james/jmap/json/MailboxQuerySerializer.scala   |   3 +-
 .../org/apache/james/jmap/mail/EmailQuery.scala    |  31 +--
 .../org/apache/james/jmap/mail/MailboxQuery.scala  |   5 +-
 .../james/jmap/method/EmailQueryMethod.scala       |  18 +-
 .../james/jmap/method/MailboxQueryMethod.scala     |   2 +-
 .../scala/org/apache/james/jmap/model/Query.scala  |  28 ++-
 8 files changed, 282 insertions(+), 78 deletions(-)

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 7792c9a..8381c45 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
@@ -96,10 +96,7 @@ trait EmailQueryMethodContract {
     val otherMailboxPath = MailboxPath.forUser(BOB, "other")
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
     val requestDate = Date.from(ZonedDateTime.now().minusDays(1).toInstant)
-    val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
-      .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
-        AppendCommand.builder().withInternalDate(requestDate).build(message))
-      .getMessageId
+    val messageId1: MessageId = sendMessageToBobInbox(server, message, 
requestDate)
 
     val messageId2: MessageId = server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, otherMailboxPath, 
AppendCommand.from(message))
@@ -223,20 +220,11 @@ trait EmailQueryMethodContract {
     val otherMailboxPath = MailboxPath.forUser(BOB, "other")
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
     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 messageId1: MessageId = sendMessageToBobInbox(server, message, 
requestDateMessage1)
     val requestDateMessage2 = 
Date.from(ZonedDateTime.now().minusDays(1).plusHours(1).toInstant)
-    val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
-      .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
-        
AppendCommand.builder().withInternalDate(requestDateMessage2).build(message))
-      .getMessageId
+    val messageId2 = sendMessageToBobInbox(server, message, 
requestDateMessage2)
     val requestDateMessage3 = 
Date.from(ZonedDateTime.now().minusDays(1).plusHours(2).toInstant)
-    val messageId3 = server.getProbe(classOf[MailboxProbeImpl])
-      .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
-        
AppendCommand.builder().withInternalDate(requestDateMessage3).build(message))
-      .getMessageId
+    val messageId3 = sendMessageToBobInbox(server, message, 
requestDateMessage3)
 
     val request =
       s"""{
@@ -782,10 +770,7 @@ trait EmailQueryMethodContract {
     val otherMailboxPath = MailboxPath.forUser(BOB, "other")
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
     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 messageId1: MessageId = sendMessageToBobInbox(server, message, 
requestDateMessage1)
     val messageId2: MessageId = server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, otherMailboxPath, 
AppendCommand.from(message))
       .getMessageId
@@ -940,9 +925,7 @@ trait EmailQueryMethodContract {
       .build
     
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
     val requestDate = ZonedDateTime.now().minusDays(1)
-    val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
-      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.builder().withInternalDate(Date.from(requestDate.toInstant)).build(message))
-      .getMessageId
+    val messageId1 = sendMessageToBobInbox(server, message, 
Date.from(requestDate.toInstant))
 
 
     val otherMailboxPath = MailboxPath.forUser(BOB, "other")
@@ -994,9 +977,7 @@ trait EmailQueryMethodContract {
       .build
     
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
     val requestDate = ZonedDateTime.now().minusDays(1)
-    val messageId1 = server.getProbe(classOf[MailboxProbeImpl])
-      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.builder().withInternalDate(Date.from(requestDate.toInstant)).build(message))
-      .getMessageId
+    val messageId1 = sendMessageToBobInbox(server, message, 
Date.from(requestDate.toInstant))
 
     val otherMailboxPath = MailboxPath.forUser(BOB, "other")
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
@@ -1048,13 +1029,11 @@ trait EmailQueryMethodContract {
       .build
     
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
     val receivedDateMessage1 = ZonedDateTime.now().minusDays(1)
-    server.getProbe(classOf[MailboxProbeImpl])
-      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.builder().withInternalDate(Date.from(receivedDateMessage1.toInstant)).build(message))
-      .getMessageId
+    sendMessageToBobInbox(server, message, 
Date.from(receivedDateMessage1.toInstant))
 
     val otherMailboxPath = MailboxPath.forUser(BOB, "other")
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
-    val receivedDateMessage2 = ZonedDateTime.now().minusDays(1).plusHours(2)
+    val receivedDateMessage2 = receivedDateMessage1.plusHours(2)
     val messageId2 = server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, otherMailboxPath, 
AppendCommand.builder().withInternalDate(Date.from(receivedDateMessage2.toInstant)).build(message))
       .getMessageId
@@ -1103,9 +1082,7 @@ trait EmailQueryMethodContract {
       .build
     
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
     val receivedDateMessage1 = ZonedDateTime.now().minusDays(1)
-    server.getProbe(classOf[MailboxProbeImpl])
-      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.builder().withInternalDate(Date.from(receivedDateMessage1.toInstant)).build(message))
-      .getMessageId
+    sendMessageToBobInbox(server, message, 
Date.from(receivedDateMessage1.toInstant))
 
     val otherMailboxPath = MailboxPath.forUser(BOB, "other")
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
@@ -1148,6 +1125,230 @@ trait EmailQueryMethodContract {
     }
   }
 
+  @Test
+  def shouldLimitResultByTheLimitProvidedByTheClient(server: 
GuiceJamesServer): Unit = {
+    val message: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+    
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    val otherMailboxPath = MailboxPath.forUser(BOB, "other")
+    val requestDate = Date.from(ZonedDateTime.now().minusDays(1).toInstant)
+    sendMessageToBobInbox(server, message, Date.from(requestDate.toInstant))
+
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
+    val messageId2: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, otherMailboxPath, 
AppendCommand.from(message))
+      .getMessageId
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "limit": 1
+         |    },
+         |    "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).isEqualTo(
+        s"""{
+           |    "sessionState": "75128aab4b1b",
+           |    "methodResponses": [[
+           |            "Email/query",
+           |            {
+           |                "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |                "queryState": "${generateQueryState(messageId2)}",
+           |                "canCalculateChanges": false,
+           |                "position": 0,
+           |                "ids": ["${messageId2.serialize()}"]
+           |            },
+           |            "c1"
+           |        ]]
+           |}""".stripMargin)
+    }
+  }
+
+  @Test
+  def shouldReturnAnIllegalArgumentExceptionIfTheLimitIsNegative(server: 
GuiceJamesServer): Unit = {
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "limit": -1
+         |    },
+         |    "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).isEqualTo(
+        s"""{
+           |    "sessionState": "75128aab4b1b",
+           |    "methodResponses": [
+           |        [
+           |            "error",
+           |            {
+           |                "type": "invalidArguments",
+           |                "description": "The limit can not be negative. -1 
was provided."
+           |            },
+           |            "c1"
+           |        ]
+           |    ]
+           |}""".stripMargin)
+    }
+  }
+
+  @Test
+  def theLimitshouldBeEnforcedByTheServerIfNoLimitProvidedByTheClient(server: 
GuiceJamesServer): Unit = {
+    val message: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+    
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+
+    val allMessages = (0 to 300).toList.foldLeft(List[MessageId](), 
ZonedDateTime.now().minusYears(1))((acc, _) => {
+      val (messageList, date) = acc
+      val dateForNewMessage = date.plusDays(1)
+      val messageId = sendMessageToBobInbox(server, message, 
Date.from(dateForNewMessage.toInstant))
+      (messageId :: messageList, dateForNewMessage)
+    })
+
+    val expectedMessages = allMessages._1.take(256)
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6"
+         |    },
+         |    "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("[" + expectedMessages.map(message => 
s""""${message.serialize()}"""").mkString(", ") + "]")
+
+      assertThatJson(response)
+        .inPath("$.methodResponses[0][1].limit")
+        .isEqualTo("256")
+    }
+  }
+
+  @Test
+  def 
theLimitshouldBeEnforcedByTheServerIfAGreaterLimitProvidedByTheClient(server: 
GuiceJamesServer): Unit = {
+    val message: Message = Message.Builder
+      .of
+      .setSubject("test")
+      .setBody("testmail", StandardCharsets.UTF_8)
+      .build
+    
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+
+    val allMessages = (0 to 300).toList.foldLeft(List[MessageId](), 
ZonedDateTime.now().minusYears(1))((acc, _) => {
+      val (messageList, date) = acc
+      val dateForNewMessage = date.plusDays(1)
+      val messageId = sendMessageToBobInbox(server, message, 
Date.from(dateForNewMessage.toInstant))
+      (messageId :: messageList, dateForNewMessage)
+    })
+
+    val expectedMessages = allMessages._1.take(256)
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "limit": 2000
+         |    },
+         |    "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("[" + expectedMessages.map(message => 
s""""${message.serialize()}"""").mkString(", ") + "]")
+
+      assertThatJson(response)
+        .inPath("$.methodResponses[0][1].limit")
+        .isEqualTo("256")
+    }
+  }
+
+  private def sendMessageToBobInbox(server: GuiceJamesServer, message: 
Message, requestDate: Date) = {
+    server.getProbe(classOf[MailboxProbeImpl])
+      .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
+        AppendCommand.builder().withInternalDate(requestDate).build(message))
+      .getMessageId
+  }
+
   @ParameterizedTest
   @MethodSource(value = Array("jmapSystemKeywords"))
   def 
listMailsBySystemKeywordShouldReturnOnlyMailsWithThisSystemKeyword(keywordFlag: 
Flags, keywordName: String, server: GuiceJamesServer): Unit = {
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 8251f96..a4609a6 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,8 @@
 package org.apache.james.jmap.json
 
 import javax.inject.Inject
-import org.apache.james.jmap.mail.{CanCalculateChanges, Collation, Comparator, 
EmailQueryRequest, EmailQueryResponse, FilterCondition, IsAscending, Limit, 
Position, QueryState, ReceivedAtSortProperty, SortProperty}
-import org.apache.james.jmap.model.{AccountId, Keyword}
+import org.apache.james.jmap.mail.{Collation, Comparator, EmailQueryRequest, 
EmailQueryResponse, FilterCondition, IsAscending, ReceivedAtSortProperty, 
SortProperty}
+import org.apache.james.jmap.model.{AccountId, CanCalculateChanges, Keyword, 
LimitUnparsed, Position, QueryState}
 import org.apache.james.mailbox.model.{MailboxId, MessageId}
 import play.api.libs.json._
 
@@ -49,11 +49,11 @@ class EmailQuerySerializer @Inject()(mailboxIdFactory: 
MailboxId.Factory) {
     case _ => JsError("Expecting keywords to be represented by a JsString")
   }
   private implicit val filterConditionReads: Reads[FilterCondition] = 
Json.reads[FilterCondition]
+  private implicit val limitUnparsedReads: Reads[LimitUnparsed] = 
Json.valueReads[LimitUnparsed]
   private implicit val CanCalculateChangesFormat: Format[CanCalculateChanges] 
= Json.valueFormat[CanCalculateChanges]
 
   private implicit val queryStateWrites: Writes[QueryState] = 
Json.valueWrites[QueryState]
   private implicit val positionFormat: Format[Position] = 
Json.valueFormat[Position]
-  private implicit val limitFormat: Format[Limit] = Json.valueFormat[Limit]
   private implicit val messageIdWrites: Writes[MessageId] = id => 
JsString(id.serialize())
 
   private implicit val sortPropertyReads: Reads[SortProperty] = {
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/MailboxQuerySerializer.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/MailboxQuerySerializer.scala
index f8580c9..e5bc83b 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/MailboxQuerySerializer.scala
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/MailboxQuerySerializer.scala
@@ -30,7 +30,7 @@ import scala.language.implicitConversions
 
 object MailboxQuerySerializer {
   private implicit val accountIdWrites: Format[AccountId] = 
Json.valueFormat[AccountId]
-  private implicit val canCalculateChangeWrites: Writes[CanCalculateChange] = 
Json.valueWrites[CanCalculateChange]
+  private implicit val canCalculateChangeWrites: Writes[CanCalculateChanges] = 
Json.valueWrites[CanCalculateChanges]
 
   private implicit val mailboxIdWrites: Writes[MailboxId] = mailboxId => 
JsString(mailboxId.serialize)
 
@@ -44,7 +44,6 @@ object MailboxQuerySerializer {
   private implicit val emailQueryRequestReads: Reads[MailboxQueryRequest] = 
Json.reads[MailboxQueryRequest]
   private implicit val queryStateWrites: Writes[QueryState] = 
Json.valueWrites[QueryState]
   private implicit val positionFormat: Format[Position] = 
Json.valueFormat[Position]
-  private implicit val limitFormat: Format[Limit] = Json.valueFormat[Limit]
 
   private implicit def mailboxQueryResponseWrites: 
OWrites[MailboxQueryResponse] = Json.writes[MailboxQueryResponse]
 
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 f5b282e..8f23532 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
@@ -18,11 +18,9 @@
  ****************************************************************/
 
 package org.apache.james.jmap.mail
-
-import org.apache.james.jmap.model.{Keyword, UTCDate}
-import com.google.common.hash.Hashing
+import org.apache.james.jmap.model.{AccountId, CanCalculateChanges, Keyword, 
LimitUnparsed, Position, QueryState, UTCDate}
 import org.apache.james.jmap.mail.IsAscending.{ASCENDING, DESCENDING}
-import org.apache.james.jmap.model.AccountId
+import org.apache.james.jmap.model.Limit.Limit
 import org.apache.james.mailbox.model.SearchQuery.Sort.Order.{NATURAL, REVERSE}
 import org.apache.james.mailbox.model.SearchQuery.Sort.SortClause
 import org.apache.james.mailbox.model.{MailboxId, MessageId, SearchQuery}
@@ -34,17 +32,7 @@ case class FilterCondition(inMailbox: Option[MailboxId],
                            hasKeyword: Option[Keyword],
                            notKeyword: Option[Keyword])
 
-case class EmailQueryRequest(accountId: AccountId, filter: 
Option[FilterCondition], comparator: Option[Set[Comparator]])
-
-case class Position(value: Int) extends AnyVal
-object Position{
-  val zero: Position = Position(0)
-}
-case class Limit(value: Long) extends AnyVal
-object Limit {
-  val default: Limit = Limit(256L)
-}
-case class QueryState(value: String) extends AnyVal
+case class EmailQueryRequest(accountId: AccountId, limit: 
Option[LimitUnparsed], filter: Option[FilterCondition], comparator: 
Option[Set[Comparator]])
 
 sealed trait SortProperty {
   def toSortClause: SortClause
@@ -77,19 +65,6 @@ case class Comparator(property: SortProperty,
   def toSort: SearchQuery.Sort = new SearchQuery.Sort(property.toSortClause, 
isAscending.getOrElse(ASCENDING).toSortOrder)
 }
 
-object QueryState {
-  def forIds(ids: Seq[MessageId]): QueryState = QueryState(
-    Hashing.murmur3_32()
-      .hashUnencodedChars(ids.map(_.serialize()).mkString(" "))
-      .toString)
-}
-
-object CanCalculateChanges {
-  val CANT: CanCalculateChanges = CanCalculateChanges(false)
-}
-
-case class CanCalculateChanges(value: Boolean) extends AnyVal
-
 case class EmailQueryResponse(accountId: AccountId,
                               queryState: QueryState,
                               canCalculateChanges: CanCalculateChanges,
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxQuery.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxQuery.scala
index 56a7e62..6f9df68 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxQuery.scala
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxQuery.scala
@@ -19,7 +19,8 @@
 
 package org.apache.james.jmap.mail
 
-import org.apache.james.jmap.model.{AccountId, CanCalculateChange, Limit, 
Position, QueryState}
+import org.apache.james.jmap.model.Limit.Limit
+import org.apache.james.jmap.model.{AccountId, CanCalculateChanges, Position, 
QueryState}
 import org.apache.james.mailbox.Role
 import org.apache.james.mailbox.model.MailboxId
 
@@ -29,7 +30,7 @@ case class MailboxFilter(role: Role)
 
 case class MailboxQueryResponse(accountId: AccountId,
                               queryState: QueryState,
-                              canCalculateChanges: CanCalculateChange,
+                              canCalculateChanges: CanCalculateChanges,
                               ids: Seq[MailboxId],
                               position: Position,
                               limit: Option[Limit])
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
index ab5ddb8..7437260 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailQueryMethod.scala
@@ -21,11 +21,12 @@ package org.apache.james.jmap.method
 import eu.timepit.refined.auto._
 import javax.inject.Inject
 import org.apache.james.jmap.json.{EmailQuerySerializer, ResponseSerializer}
-import org.apache.james.jmap.mail.{CanCalculateChanges, Comparator, 
EmailQueryRequest, EmailQueryResponse, Limit, Position, QueryState}
+import org.apache.james.jmap.mail.{Comparator, EmailQueryRequest, 
EmailQueryResponse}
 import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
 import org.apache.james.jmap.model.DefaultCapabilities.{CORE_CAPABILITY, 
MAIL_CAPABILITY}
 import org.apache.james.jmap.model.Invocation.{Arguments, MethodName}
-import org.apache.james.jmap.model.{Capabilities, ErrorCode, Invocation}
+import org.apache.james.jmap.model.{CanCalculateChanges, Capabilities, 
ErrorCode, Invocation, Limit, Position, QueryState}
+import org.apache.james.jmap.model.Limit.Limit
 import org.apache.james.jmap.routes.ProcessingContext
 import org.apache.james.jmap.utils.search.MailboxFilter.QueryFilter
 import org.apache.james.jmap.utils.search.MailboxFilter
@@ -58,16 +59,21 @@ class EmailQueryMethod @Inject() (serializer: 
EmailQuerySerializer,
 
   private def processRequest(mailboxSession: MailboxSession, invocation: 
Invocation, request: EmailQueryRequest): SMono[Invocation] = {
     val searchQuery: MultimailboxesSearchQuery = 
searchQueryFromRequest(request)
+    for {
+      limitToUse <- Limit.validateRequestLimit(request.limit)
+      response <- executeQuery(mailboxSession, request, searchQuery, 
limitToUse)
+    } yield Invocation(methodName = methodName, arguments = 
Arguments(serializer.serialize(response)), methodCallId = 
invocation.methodCallId)
+  }
 
-    SFlux.fromPublisher(mailboxManager.search(searchQuery, mailboxSession, 
Limit.default.value))
+  private def executeQuery(mailboxSession: MailboxSession, request: 
EmailQueryRequest, searchQuery: MultimailboxesSearchQuery, limitToUse: Limit): 
SMono[EmailQueryResponse] = {
+    SFlux.fromPublisher(mailboxManager.search(searchQuery, mailboxSession, 
limitToUse))
       .collectSeq()
       .map(ids => EmailQueryResponse(accountId = request.accountId,
         queryState = QueryState.forIds(ids),
-        canCalculateChanges = CanCalculateChanges.CANT,
+        canCalculateChanges = CanCalculateChanges.CANNOT,
         ids = ids,
         position = Position.zero,
-        limit = Some(Limit.default)))
-      .map(response => Invocation(methodName = methodName, arguments = 
Arguments(serializer.serialize(response)), methodCallId = 
invocation.methodCallId))
+        limit = Some(limitToUse).filterNot(used => 
request.limit.map(_.value).contains(used.value))))
   }
 
   private def searchQueryFromRequest(request: EmailQueryRequest): 
MultimailboxesSearchQuery = {
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxQueryMethod.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxQueryMethod.scala
index a85b657..46e9417 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxQueryMethod.scala
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxQueryMethod.scala
@@ -59,7 +59,7 @@ class MailboxQueryMethod @Inject()(systemMailboxesProvider: 
SystemMailboxesProvi
       .collectSeq()
       .map(ids => MailboxQueryResponse(accountId = request.accountId,
         queryState = QueryState.forMailboxIds(ids),
-        canCalculateChanges = CanCalculateChange(false),
+        canCalculateChanges = CanCalculateChanges(false),
         ids = ids,
         position = Position.zero,
         limit = Some(Limit.default)))
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Query.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Query.scala
index 24135dd..624e3e0 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Query.scala
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Query.scala
@@ -20,15 +20,33 @@
 package org.apache.james.jmap.model
 
 import com.google.common.hash.Hashing
+import eu.timepit.refined.auto._
+import eu.timepit.refined.api.Refined
+import eu.timepit.refined.numeric.Positive
+import eu.timepit.refined.refineV
 import org.apache.james.mailbox.model.{MailboxId, MessageId}
+import reactor.core.scala.publisher.SMono
 
 case class Position(value: Int) extends AnyVal
 object Position{
   val zero: Position = Position(0)
 }
-case class Limit(value: Long) extends AnyVal
+
+case class LimitUnparsed(value: Long) extends AnyVal
+
 object Limit {
-  val default: Limit = Limit(256L)
+  type Limit = Long Refined Positive
+  val default: Limit = 256L
+
+  def validateRequestLimit(requestLimit: Option[LimitUnparsed]): SMono[Limit] 
= {
+    val refinedLimit : Option[Either[String, Limit]] =  requestLimit.map(limit 
=> refineV[Positive](limit.value))
+
+    refinedLimit match {
+      case Some(Left(_))  =>  SMono.raiseError(new 
IllegalArgumentException(s"The limit can not be negative. 
${requestLimit.map(_.value).getOrElse("")} was provided."))
+      case Some(Right(limit)) if limit.value < default.value => 
SMono.just(limit)
+      case _ => SMono.just(default)
+    }
+  }
 }
 
 case class QueryState(value: String) extends AnyVal
@@ -45,4 +63,8 @@ object QueryState {
       .toString)
 }
 
-case class CanCalculateChange(value: Boolean) extends AnyVal
+case class CanCalculateChanges(value: Boolean) extends AnyVal
+
+object CanCalculateChanges {
+  val CANNOT: CanCalculateChanges = CanCalculateChanges(false)
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to