This is an automated email from the ASF dual-hosted git repository. rcordier pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 06e33c574d0928efb18b9d2e8f0a4f15c03b182f Author: Rémi Kowalski <[email protected]> AuthorDate: Fri Sep 11 10:45:45 2020 +0200 JAMES-3375 filter message in a mailbox --- .../contract/EmailQueryMethodContract.scala | 119 ++++++++++++++++++++- .../rfc8621/memory/MemoryEmailQueryMethodTest.java | 9 -- .../james/jmap/json/EmailQuerySerializer.scala | 3 +- .../org/apache/james/jmap/mail/EmailQuery.scala | 4 +- .../james/jmap/method/EmailQueryMethod.scala | 19 +++- 5 files changed, 140 insertions(+), 14 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 3a9fd36..312388f 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 @@ -238,7 +238,7 @@ trait EmailQueryMethodContract { assertThatJson(response) .inPath("$.methodResponses[0][1].ids") - .isEqualTo(Json.stringify(Json.toJson(List(messageId3, messageId2, messageId1).map(_.serialize())))) + .isEqualTo(s"""["${messageId3.serialize()}", "${messageId2.serialize()}", "${messageId1.serialize()}"]""") } } @@ -301,6 +301,123 @@ trait EmailQueryMethodContract { } } + @Test + def shouldListMailsInASpecificUserMailboxes(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 otherMailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath) + server.getProbe(classOf[MailboxProbeImpl]) + .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.from(message)) + .getMessageId + 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", + | "filter": { + | "inMailbox": "${otherMailboxId.serialize()}" + | } + | }, + | "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"""["${messageId2.serialize()}"]""") + } + } + + @Test + def shouldReturnIllegalArgumentErrorForAnUnknownSpecificUserMailboxes(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 otherMailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath) + server.getProbe(classOf[MailboxProbeImpl]) + .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.from(message)) + .getMessageId + server.getProbe(classOf[MailboxProbeImpl]) + .appendMessage(BOB.asString, otherMailboxPath, AppendCommand.from(message)) + .getMessageId + + server.getProbe(classOf[MailboxProbeImpl]).deleteMailbox(otherMailboxPath.getNamespace, BOB.asString(), otherMailboxPath.getName) + + val request = + s"""{ + | "using": [ + | "urn:ietf:params:jmap:core", + | "urn:ietf:params:jmap:mail"], + | "methodCalls": [[ + | "Email/query", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "filter": { + | "inMailbox": "${otherMailboxId.serialize()}" + | } + | }, + | "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": "${otherMailboxId.serialize()} can not be found" + | }, + | "c1" + | ] + | ] + |}""".stripMargin) + } + } + private def generateQueryState(messages: MessageId*): String = { Hashing.murmur3_32() .hashUnencodedChars(messages.toList.map(_.serialize()).mkString(" ")) diff --git a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryEmailQueryMethodTest.java b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryEmailQueryMethodTest.java index 6d17a60..78b50d1 100644 --- a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryEmailQueryMethodTest.java +++ b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryEmailQueryMethodTest.java @@ -26,8 +26,6 @@ import org.apache.james.JamesServerBuilder; import org.apache.james.JamesServerExtension; import org.apache.james.jmap.rfc8621.contract.EmailQueryMethodContract; import org.apache.james.modules.TestJMAPServerModule; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; public class MemoryEmailQueryMethodTest implements EmailQueryMethodContract { @@ -38,11 +36,4 @@ public class MemoryEmailQueryMethodTest implements EmailQueryMethodContract { .overrideWith(new TestJMAPServerModule())) .build(); - @Disabled("Can't be done in memory because the SimpleMessageSearchIndex doesn't handle sorting") - @Test - public void shouldListMailsInAllUserMailboxes(GuiceJamesServer server) {} - - @Disabled("Can't be done in memory because the SimpleMessageSearchIndex doesn't handle sorting") - @Test - public void listMailsShouldBeSortedByDescendingOrderOfArrivalByDefault(GuiceJamesServer server) {} } 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 01e8afe..6c7cfb8 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.{EmailQueryRequest, EmailQueryResponse, Limit, Position, QueryState} +import org.apache.james.jmap.mail.{EmailQueryRequest, EmailQueryResponse, FilterCondition, Limit, Position, QueryState} import org.apache.james.jmap.model._ import org.apache.james.mailbox.model.{MailboxId, MessageId} import play.api.libs.json._ @@ -37,6 +37,7 @@ class EmailQuerySerializer @Inject()(mailboxIdFactory: MailboxId.Factory) { case _ => JsError() } + private implicit val filterConditionReads: Reads[FilterCondition] = Json.reads[FilterCondition] private implicit val emailQueryRequestReads: Reads[EmailQueryRequest] = Json.reads[EmailQueryRequest] private implicit val queryStateWrites: Writes[QueryState] = Json.valueWrites[QueryState] private implicit val positionFormat: Format[Position] = Json.valueFormat[Position] 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 ad15604..4ac82aa 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 @@ -23,7 +23,9 @@ import com.google.common.hash.Hashing import org.apache.james.jmap.model.AccountId import org.apache.james.mailbox.model.{MailboxId, MessageId} -case class EmailQueryRequest(accountId: AccountId, inMailbox: Option[MailboxId]) +case class FilterCondition(inMailbox: Option[MailboxId]) + +case class EmailQueryRequest(accountId: AccountId, filter: Option[FilterCondition]) case class Position(value: Int) extends AnyVal object Position{ 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 5ba6543..cdb370e 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 @@ -27,6 +27,7 @@ import org.apache.james.jmap.model.DefaultCapabilities.{CORE_CAPABILITY, MAIL_CA import org.apache.james.jmap.model.Invocation.{Arguments, MethodName} import org.apache.james.jmap.model._ import org.apache.james.jmap.routes.ProcessingContext +import org.apache.james.mailbox.exception.{MailboxNotFoundException} import org.apache.james.mailbox.model.SearchQuery.Sort.SortClause import org.apache.james.mailbox.model.{MultimailboxesSearchQuery, SearchQuery} import org.apache.james.mailbox.{MailboxManager, MailboxSession} @@ -47,12 +48,13 @@ class EmailQueryMethod @Inject() (serializer: EmailQuerySerializer, .flatMap(processRequest(mailboxSession, invocation, _)) .onErrorResume { 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) } .map(invocationResult => (invocationResult, processingContext))) private def processRequest(mailboxSession: MailboxSession, invocation: Invocation, request: EmailQueryRequest): SMono[Invocation] = { - val searchQuery = MultimailboxesSearchQuery.from(new SearchQuery.Builder().sorts(new SearchQuery.Sort(SortClause.Arrival, SearchQuery.Sort.Order.REVERSE)).build()).build() + val searchQuery = searchQueryFromRequest(request) SFlux.fromPublisher(mailboxManager.search(searchQuery, mailboxSession, Limit.default.value)) .collectSeq() .map(ids => EmailQueryResponse(accountId = request.accountId, @@ -64,10 +66,23 @@ class EmailQueryMethod @Inject() (serializer: EmailQuerySerializer, .map(response => Invocation(methodName = methodName, arguments = Arguments(serializer.serialize(response)), methodCallId = invocation.methodCallId)) } + private def searchQueryFromRequest(request: EmailQueryRequest): MultimailboxesSearchQuery = { + val query = new SearchQuery.Builder() + val defaultSort = new SearchQuery.Sort(SortClause.Arrival, SearchQuery.Sort.Order.REVERSE) + val querySorted = query.sorts(defaultSort) + + val multiMailboxQueryBuilder = MultimailboxesSearchQuery.from(querySorted.build()) + + val multiMailboxQueryBuilderWithInMailboxFilter = request.inMailbox match { + case Some(mailboxId) => multiMailboxQueryBuilder.inMailboxes(mailboxId) + case None => multiMailboxQueryBuilder + } + multiMailboxQueryBuilderWithInMailboxFilter.build() + } + private def asEmailQueryRequest(arguments: Arguments): SMono[EmailQueryRequest] = serializer.deserializeEmailQueryRequest(arguments.value) match { case JsSuccess(emailQueryRequest, _) => SMono.just(emailQueryRequest) case errors: JsError => SMono.raiseError(new IllegalArgumentException(ResponseSerializer.serialize(errors).toString)) } - } \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
