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 e4395c2d3622b58690082d8580e6366395924956 Author: Raphael Ouazana <[email protected]> AuthorDate: Fri Sep 11 11:37:34 2020 +0200 JAMES-3375 implements inMailboxOtherThan filter --- .../contract/EmailQueryMethodContract.scala | 211 +++++++++++++++++++++ .../org/apache/james/jmap/mail/EmailQuery.scala | 2 +- .../james/jmap/method/EmailQueryMethod.scala | 9 +- .../james/jmap/utils/search/MailboxFilter.scala | 52 +++++ 4 files changed, 266 insertions(+), 8 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 312388f..21a3aad 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 @@ -418,6 +418,217 @@ trait EmailQueryMethodContract { } } + @Test + def shouldListMailsNotInASpecificUserMailboxes(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) + val messageId1: MessageId = 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 + + val request = + s"""{ + | "using": [ + | "urn:ietf:params:jmap:core", + | "urn:ietf:params:jmap:mail"], + | "methodCalls": [[ + | "Email/query", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "filter": { + | "inMailboxOtherThan": [ "${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"""["${messageId1.serialize()}"]""") + } + } + + @Test + def shouldListMailsNotInZeroMailboxes(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") + server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath) + val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl]) + .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.from(message)) + .getMessageId + Thread.sleep(1000) + 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": { + | "inMailboxOtherThan": [ ] + | } + | }, + | "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()}", "${messageId1.serialize()}"]""") + } + } + + @Test + def listMailsInAFirstMailboxAndNotSomeOtherMailboxShouldReturnMailsInFirstMailbox(server: GuiceJamesServer): Unit = { + val message: Message = Message.Builder + .of + .setSubject("test") + .setBody("testmail", StandardCharsets.UTF_8) + .build + val inbox = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB)) + val otherMailboxPath = MailboxPath.forUser(BOB, "other") + val otherMailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath) + val messageId1: MessageId = 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 + + val request = + s"""{ + | "using": [ + | "urn:ietf:params:jmap:core", + | "urn:ietf:params:jmap:mail"], + | "methodCalls": [[ + | "Email/query", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "filter": { + | "inMailbox": "${inbox.serialize()}", + | "inMailboxOtherThan": [ "${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"""["${messageId1.serialize()}"]""") + } + } + + @Test + def listMailsInAFirstMailboxAndNotInTheSameMailboxShouldReturnEmptyResult(server: GuiceJamesServer): Unit = { + val message: Message = Message.Builder + .of + .setSubject("test") + .setBody("testmail", StandardCharsets.UTF_8) + .build + val inbox = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB)) + val otherMailboxPath = MailboxPath.forUser(BOB, "other") + 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 + + val request = + s"""{ + | "using": [ + | "urn:ietf:params:jmap:core", + | "urn:ietf:params:jmap:mail"], + | "methodCalls": [[ + | "Email/query", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "filter" : { + | "inMailbox": "${inbox.serialize()}", + | "inMailboxOtherThan": [ "${inbox.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("[]") + } + } + private def generateQueryState(messages: MessageId*): String = { Hashing.murmur3_32() .hashUnencodedChars(messages.toList.map(_.serialize()).mkString(" ")) 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 4ac82aa..22c09d0 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,7 @@ import com.google.common.hash.Hashing import org.apache.james.jmap.model.AccountId import org.apache.james.mailbox.model.{MailboxId, MessageId} -case class FilterCondition(inMailbox: Option[MailboxId]) +case class FilterCondition(inMailbox: Option[MailboxId], inMailboxOtherThan: Option[Seq[MailboxId]]) case class EmailQueryRequest(accountId: AccountId, filter: Option[FilterCondition]) 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 cdb370e..18d391a 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 @@ -28,6 +28,7 @@ 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.jmap.utils.search.MailboxFilter import org.apache.james.mailbox.model.SearchQuery.Sort.SortClause import org.apache.james.mailbox.model.{MultimailboxesSearchQuery, SearchQuery} import org.apache.james.mailbox.{MailboxManager, MailboxSession} @@ -71,13 +72,7 @@ class EmailQueryMethod @Inject() (serializer: EmailQuerySerializer, 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() + MailboxFilter.buildQuery(request, querySorted.build()) } private def asEmailQueryRequest(arguments: Arguments): SMono[EmailQueryRequest] = diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/utils/search/MailboxFilter.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/utils/search/MailboxFilter.scala new file mode 100644 index 0000000..c673e5b --- /dev/null +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/utils/search/MailboxFilter.scala @@ -0,0 +1,52 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.jmap.utils.search + +import org.apache.james.jmap.mail.EmailQueryRequest +import org.apache.james.mailbox.model.{MultimailboxesSearchQuery, SearchQuery} + +import scala.collection.JavaConverters.seqAsJavaListConverter + + +sealed trait MailboxFilter { + def toQuery(builder: MultimailboxesSearchQuery.Builder, request: EmailQueryRequest): MultimailboxesSearchQuery.Builder +} + +case object InMailboxFilter extends MailboxFilter { + override def toQuery(builder: MultimailboxesSearchQuery.Builder, request: EmailQueryRequest): MultimailboxesSearchQuery.Builder = request.filter.flatMap(_.inMailbox) match { + case Some(mailboxId) => builder.inMailboxes(mailboxId) + case None => builder + } +} + +case object NotInMailboxFilter extends MailboxFilter { + override def toQuery(builder: MultimailboxesSearchQuery.Builder, request: EmailQueryRequest): MultimailboxesSearchQuery.Builder = request.filter.flatMap(_.inMailboxOtherThan) match { + case Some(mailboxIds) => builder.notInMailboxes(mailboxIds.asJava) + case None => builder + } +} + +object MailboxFilter { + def buildQuery(request: EmailQueryRequest, searchQuery: SearchQuery) = { + val multiMailboxQueryBuilder = MultimailboxesSearchQuery.from(searchQuery) + + List(InMailboxFilter, NotInMailboxFilter).foldLeft(multiMailboxQueryBuilder)((builder, filter) => filter.toQuery(builder, request)) + .build() + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
