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]
