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 3fac80d4ca7c3bf74aab86fb5781d83770c42f78 Author: Rene Cordier <[email protected]> AuthorDate: Tue Sep 22 13:58:58 2020 +0700 JAMES-3384 allInThreadHaveKeyword and someInThreadHaveKeyword parameters are unsupported for sorting on Email/query --- .../contract/EmailQueryMethodContract.scala | 46 +++++++++++++++++++++- .../james/jmap/json/EmailQuerySerializer.scala | 4 +- .../org/apache/james/jmap/mail/EmailQuery.scala | 19 +++++++-- .../james/jmap/method/EmailQueryMethod.scala | 37 ++++++++++------- .../org/apache/james/jmap/model/Invocation.scala | 4 ++ 5 files changed, 91 insertions(+), 19 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 099cbda..eba54dd 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 @@ -51,7 +51,7 @@ import org.awaitility.Awaitility import org.awaitility.Duration.ONE_HUNDRED_MILLISECONDS import org.junit.jupiter.api.{BeforeEach, Test} import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.{Arguments, MethodSource} +import org.junit.jupiter.params.provider.{Arguments, MethodSource, ValueSource} import org.threeten.extra.Seconds object EmailQueryMethodContract { @@ -867,6 +867,50 @@ trait EmailQueryMethodContract { } } + @ParameterizedTest + @ValueSource(strings = Array( + "allInThreadHaveKeyword", + "someInThreadHaveKeyword" + )) + def listMailsShouldReturnUnsupportedSortWhenPropertyFieldInComparatorIsValidButUnsupported(unsupported: String): Unit = { + val request = + s"""{ + | "using": [ + | "urn:ietf:params:jmap:core", + | "urn:ietf:params:jmap:mail"], + | "methodCalls": [[ + | "Email/query", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "comparator": [{ + | "property":"$unsupported" + | }] + | }, + | "c1"]] + |}""".stripMargin + + 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]") + .isEqualTo(s""" + { + "type": "unsupportedSort", + "description": "The sort $unsupported is syntactically valid, but it includes a property the server does not support sorting on or a collation method it does not recognise." + } + """) + } + @Test def shouldReturnIllegalArgumentErrorForAnUnknownSpecificUserMailboxes(server: GuiceJamesServer): Unit = { val message: Message = Message.Builder 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 c8f2fb4..3fac550 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,7 +20,7 @@ package org.apache.james.jmap.json import javax.inject.Inject -import org.apache.james.jmap.mail.{Collation, Comparator, EmailQueryRequest, EmailQueryResponse, FilterCondition, HasAttachment, IsAscending, ReceivedAtSortProperty, SortProperty} +import org.apache.james.jmap.mail.{AllInThreadHaveKeywordSortProperty, Collation, Comparator, EmailQueryRequest, EmailQueryResponse, FilterCondition, HasAttachment, IsAscending, ReceivedAtSortProperty, SomeInThreadHaveKeywordSortProperty, SortProperty} import org.apache.james.jmap.model.{AccountId, CanCalculateChanges, Keyword, LimitUnparsed, PositionUnparsed, QueryState} import org.apache.james.mailbox.model.{MailboxId, MessageId} import play.api.libs.json._ @@ -59,6 +59,8 @@ class EmailQuerySerializer @Inject()(mailboxIdFactory: MailboxId.Factory) { private implicit val sortPropertyReads: Reads[SortProperty] = { case JsString("receivedAt") => JsSuccess(ReceivedAtSortProperty) + case JsString("allInThreadHaveKeyword") => JsSuccess(AllInThreadHaveKeywordSortProperty) + case JsString("someInThreadHaveKeyword") => JsSuccess(SomeInThreadHaveKeywordSortProperty) 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 1318819..e2ab4ea 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 @@ -27,6 +27,8 @@ 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} +case class UnsupportedSortException(unsupportedSort: String) extends UnsupportedOperationException + case class FilterCondition(inMailbox: Option[MailboxId], inMailboxOtherThan: Option[Seq[MailboxId]], before: Option[UTCDate], @@ -40,10 +42,18 @@ case class FilterCondition(inMailbox: Option[MailboxId], case class EmailQueryRequest(accountId: AccountId, position: Option[PositionUnparsed], limit: Option[LimitUnparsed], filter: Option[FilterCondition], comparator: Option[Set[Comparator]]) sealed trait SortProperty { - def toSortClause: SortClause + def toSortClause: Either[UnsupportedSortException, SortClause] } case object ReceivedAtSortProperty extends SortProperty { - override def toSortClause: SortClause = SortClause.Arrival + override def toSortClause: Either[UnsupportedSortException, SortClause] = scala.Right(SortClause.Arrival) +} + +case object AllInThreadHaveKeywordSortProperty extends SortProperty { + override def toSortClause: Either[UnsupportedSortException, SortClause] = Left(UnsupportedSortException("allInThreadHaveKeyword")) +} + +case object SomeInThreadHaveKeywordSortProperty extends SortProperty { + override def toSortClause: Either[UnsupportedSortException, SortClause] = Left(UnsupportedSortException("someInThreadHaveKeyword")) } object IsAscending { @@ -67,7 +77,10 @@ case class Collation(value: String) extends AnyVal case class Comparator(property: SortProperty, isAscending: Option[IsAscending], collation: Option[Collation]) { - def toSort: SearchQuery.Sort = new SearchQuery.Sort(property.toSortClause, isAscending.getOrElse(ASCENDING).toSortOrder) + def toSort: Either[UnsupportedSortException, SearchQuery.Sort] = + for { + sortClause <- property.toSortClause + } yield new SearchQuery.Sort(sortClause, isAscending.getOrElse(ASCENDING).toSortOrder) } case class EmailQueryResponse(accountId: AccountId, 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 3f76023..196b3f8 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 @@ -18,21 +18,22 @@ ****************************************************************/ package org.apache.james.jmap.method +import cats.implicits._ import eu.timepit.refined.auto._ import javax.inject.Inject import org.apache.james.jmap.json.{EmailQuerySerializer, ResponseSerializer} -import org.apache.james.jmap.mail.{Comparator, EmailQueryRequest, EmailQueryResponse} +import org.apache.james.jmap.mail.{Comparator, EmailQueryRequest, EmailQueryResponse, UnsupportedSortException} 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.{CanCalculateChanges, Capabilities, ErrorCode, Invocation, Limit, Position, QueryState} import org.apache.james.jmap.model.Limit.Limit import org.apache.james.jmap.model.Position.Position +import org.apache.james.jmap.model.{CanCalculateChanges, Capabilities, ErrorCode, Invocation, Limit, Position, QueryState} import org.apache.james.jmap.routes.ProcessingContext import org.apache.james.jmap.utils.search.MailboxFilter import org.apache.james.jmap.utils.search.MailboxFilter.QueryFilter import org.apache.james.mailbox.exception.MailboxNotFoundException -import org.apache.james.mailbox.model.{MultimailboxesSearchQuery, SearchQuery} +import org.apache.james.mailbox.model.MultimailboxesSearchQuery import org.apache.james.mailbox.{MailboxManager, MailboxSession} import org.apache.james.metrics.api.MetricFactory import org.reactivestreams.Publisher @@ -52,6 +53,10 @@ class EmailQueryMethod @Inject() (serializer: EmailQuerySerializer, asEmailQueryRequest(invocation.arguments) .flatMap(processRequest(mailboxSession, invocation, _)) .onErrorResume { + case e: UnsupportedSortException => SMono.just(Invocation.error( + ErrorCode.UnsupportedSort, + s"The sort ${e.unsupportedSort} is syntactically valid, but it includes a property the server does not support sorting on or a collation method it does not recognise.", + invocation.methodCallId)) case e: IllegalArgumentException => SMono.just(Invocation.error(ErrorCode.InvalidArguments, e.getMessage, invocation.methodCallId)) case e: MailboxNotFoundException => SMono.just(Invocation.error(ErrorCode.InvalidArguments, e.getMessage, invocation.methodCallId)) case e: Throwable => SMono.raiseError(e) @@ -59,12 +64,14 @@ class EmailQueryMethod @Inject() (serializer: EmailQuerySerializer, .map(invocationResult => (invocationResult, processingContext))) private def processRequest(mailboxSession: MailboxSession, invocation: Invocation, request: EmailQueryRequest): SMono[Invocation] = { - val searchQuery: MultimailboxesSearchQuery = searchQueryFromRequest(request) - for { - positionToUse <- Position.validateRequestPosition(request.position) - limitToUse <- Limit.validateRequestLimit(request.limit) - response <- executeQuery(mailboxSession, request, searchQuery, positionToUse, limitToUse) - } yield Invocation(methodName = methodName, arguments = Arguments(serializer.serialize(response)), methodCallId = invocation.methodCallId) + searchQueryFromRequest(request) match { + case Left(error) => SMono.raiseError(error) + case Right(searchQuery) => for { + positionToUse <- Position.validateRequestPosition(request.position) + limitToUse <- Limit.validateRequestLimit(request.limit) + response <- executeQuery(mailboxSession, request, searchQuery, positionToUse, limitToUse) + } yield Invocation(methodName = methodName, arguments = Arguments(serializer.serialize(response)), methodCallId = invocation.methodCallId) + } } private def executeQuery(mailboxSession: MailboxSession, request: EmailQueryRequest, searchQuery: MultimailboxesSearchQuery, position: Position, limitToUse: Limit): SMono[EmailQueryResponse] = { @@ -79,13 +86,15 @@ class EmailQueryMethod @Inject() (serializer: EmailQuerySerializer, limit = Some(limitToUse).filterNot(used => request.limit.map(_.value).contains(used.value)))) } - private def searchQueryFromRequest(request: EmailQueryRequest): MultimailboxesSearchQuery = { + private def searchQueryFromRequest(request: EmailQueryRequest): Either[UnsupportedSortException, MultimailboxesSearchQuery] = { val comparators: List[Comparator] = request.comparator.getOrElse(Set(Comparator.default)).toList - val sortedSearchQuery: SearchQuery = QueryFilter.buildQuery(request) - .sorts(comparators.map(_.toSort).asJava) - .build() - MailboxFilter.buildQuery(request, sortedSearchQuery) + comparators.map(_.toSort) + .sequence + .map(sorts => QueryFilter.buildQuery(request) + .sorts(sorts.asJava) + .build()) + .map(MailboxFilter.buildQuery(request, _)) } private def asEmailQueryRequest(arguments: Arguments): SMono[EmailQueryRequest] = diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Invocation.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Invocation.scala index 81b517d..6bfdd43 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Invocation.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Invocation.scala @@ -69,4 +69,8 @@ object ErrorCode { case object InvalidResultReference extends ErrorCode { override def code: String = "invalidResultReference" } + + case object UnsupportedSort extends ErrorCode { + override def code: String = "unsupportedSort" + } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
