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 3a0cb986fe3b0c9fb1492ca56d1a1b7c6bdaebde Author: Rene Cordier <[email protected]> AuthorDate: Tue Jun 23 16:43:44 2020 +0700 JAMES-3095 Get mailboxes by ids --- .../DistributedMailboxGetMethodTest.java | 9 + .../contract/MailboxGetMethodContract.scala | 212 +++++++++++++++++++-- .../rfc8621/memory/MemoryMailboxGetMethodTest.java | 9 + .../org/apache/james/jmap/mail/MailboxGet.scala | 4 +- .../james/jmap/method/MailboxGetMethod.scala | 33 +++- 5 files changed, 243 insertions(+), 24 deletions(-) diff --git a/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedMailboxGetMethodTest.java b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedMailboxGetMethodTest.java index d2db630..1e34353 100644 --- a/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedMailboxGetMethodTest.java +++ b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedMailboxGetMethodTest.java @@ -26,12 +26,16 @@ import org.apache.james.DockerElasticSearchExtension; import org.apache.james.JamesServerBuilder; import org.apache.james.JamesServerExtension; import org.apache.james.jmap.rfc8621.contract.MailboxGetMethodContract; +import org.apache.james.mailbox.cassandra.ids.CassandraId; +import org.apache.james.mailbox.model.MailboxId; import org.apache.james.modules.AwsS3BlobStoreExtension; import org.apache.james.modules.RabbitMQExtension; import org.apache.james.modules.TestJMAPServerModule; import org.apache.james.modules.blobstore.BlobStoreConfiguration; import org.junit.jupiter.api.extension.RegisterExtension; +import com.datastax.driver.core.utils.UUIDs; + public class DistributedMailboxGetMethodTest implements MailboxGetMethodContract { @RegisterExtension static JamesServerExtension testExtension = new JamesServerBuilder<CassandraRabbitMQJamesConfiguration>(tmpDir -> @@ -48,4 +52,9 @@ public class DistributedMailboxGetMethodTest implements MailboxGetMethodContract .overrideWith(new TestJMAPServerModule())) .build(); + @Override + public MailboxId randomMailboxId() { + return CassandraId.of(UUIDs.timeBased()); + } + } 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/MailboxGetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxGetMethodContract.scala index 0748914..1c1a027 100644 --- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxGetMethodContract.scala +++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxGetMethodContract.scala @@ -27,7 +27,7 @@ import io.restassured.RestAssured._ import io.restassured.http.ContentType.JSON import javax.mail.Flags import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson -import org.apache.http.HttpStatus.{SC_INTERNAL_SERVER_ERROR, SC_OK} +import org.apache.http.HttpStatus.SC_OK import org.apache.james.GuiceJamesServer import org.apache.james.core.quota.{QuotaCountLimit, QuotaSizeLimit} import org.apache.james.jmap.http.UserCredential @@ -70,6 +70,8 @@ object MailboxGetMethodContract { trait MailboxGetMethodContract { import MailboxGetMethodContract._ + def randomMailboxId: MailboxId + @BeforeEach def setUp(server: GuiceJamesServer): Unit = { server.getProbe(classOf[DataProbeImpl]) @@ -450,27 +452,207 @@ trait MailboxGetMethodContract { } @Test - def getMailboxesByIdsShouldNotBeImplementedYet(server: GuiceJamesServer): Unit = { - val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl]) + def getMailboxesByIdsShouldReturnCorrespondingMailbox(server: GuiceJamesServer): Unit = { + val mailboxId: String = server.getProbe(classOf[MailboxProbeImpl]) .createMailbox(MailboxPath.forUser(BOB, "custom")) + .serialize + + val response: String = `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(s"""{ + | "using": [ + | "urn:ietf:params:jmap:core", + | "urn:ietf:params:jmap:mail"], + | "methodCalls": [[ + | "Mailbox/get", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "ids": ["${mailboxId}"] + | }, + | "c1"]] + |}""".stripMargin) + .when + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response).isEqualTo( + s"""{ + | "sessionState": "75128aab4b1b", + | "methodResponses": [[ + | "Mailbox/get", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "state": "000001", + | "list": [ + | { + | "id": "${mailboxId}", + | "name": "custom", + | "sortOrder": 1000, + | "totalEmails": 0, + | "unreadEmails": 0, + | "totalThreads": 0, + | "unreadThreads": 0, + | "myRights": { + | "mayReadItems": true, + | "mayAddItems": true, + | "mayRemoveItems": true, + | "maySetSeen": true, + | "maySetKeywords": true, + | "mayCreateChild": true, + | "mayRename": true, + | "mayDelete": true, + | "maySubmit": true + | }, + | "isSubscribed": false, + | "namespace": "Personal", + | "rights": {}, + | "quotas": { + | "#private&[email protected]": { + | "Storage": { "used": 0}, + | "Message": {"used": 0} + | } + | } + | } + | ], + | "notFound": [] + | }, + | "c1"]] + |}""".stripMargin) + } + + @Test + def getMailboxesByIdsShouldReturnOnlyRequestedMailbox(server: GuiceJamesServer): Unit = { + val mailboxName: String = "custom" + val mailboxId: String = server.getProbe(classOf[MailboxProbeImpl]) + .createMailbox(MailboxPath.forUser(BOB, "custom")) + .serialize + server.getProbe(classOf[MailboxProbeImpl]) + .createMailbox(MailboxPath.forUser(BOB, "othercustom")) `given` .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) .body(s"""{ - | "using": [ - | "urn:ietf:params:jmap:core", - | "urn:ietf:params:jmap:mail"], - | "methodCalls": [[ - | "Mailbox/get", - | { - | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", - | "ids": ["${mailboxId}"] - | }, - | "c1"]] - |}""".stripMargin) + | "using": [ + | "urn:ietf:params:jmap:core", + | "urn:ietf:params:jmap:mail"], + | "methodCalls": [[ + | "Mailbox/get", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "ids": ["${mailboxId}"] + | }, + | "c1"]] + |}""".stripMargin) .when .post .`then` - .statusCode(SC_INTERNAL_SERVER_ERROR) + .statusCode(SC_OK) + .body(s"$ARGUMENTS.list", hasSize(1)) + .body(s"$FIRST_MAILBOX.id", equalTo(mailboxId)) + .body(s"$FIRST_MAILBOX.name", equalTo(mailboxName)) + } + + @Test + def getMailboxesByIdsShouldReturnBothFoundAndNotFound(server: GuiceJamesServer): Unit = { + val mailboxName: String = "custom" + val mailboxId: String = server.getProbe(classOf[MailboxProbeImpl]) + .createMailbox(MailboxPath.forUser(BOB, "custom")) + .serialize + val randomId = randomMailboxId.serialize() + server.getProbe(classOf[MailboxProbeImpl]) + .createMailbox(MailboxPath.forUser(BOB, "othercustom")) + + `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(s"""{ + | "using": [ + | "urn:ietf:params:jmap:core", + | "urn:ietf:params:jmap:mail"], + | "methodCalls": [[ + | "Mailbox/get", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "ids": ["$mailboxId", "$randomId"] + | }, + | "c1"]] + |}""".stripMargin) + .when + .post + .`then` + .statusCode(SC_OK) + .body(s"$ARGUMENTS.list", hasSize(1)) + .body(s"$FIRST_MAILBOX.id", equalTo(mailboxId)) + .body(s"$FIRST_MAILBOX.name", equalTo(mailboxName)) + .body(s"$ARGUMENTS.notFound", hasSize(1)) + .body(s"$ARGUMENTS.notFound", contains(randomId)) + } + + @Test + def getMailboxesByIdsShouldReturnNotFoundWhenMailboxDoesNotExist(): Unit = { + val randomId = randomMailboxId.serialize() + + `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body( + s"""{ + | "using": [ + | "urn:ietf:params:jmap:core", + | "urn:ietf:params:jmap:mail"], + | "methodCalls": [[ + | "Mailbox/get", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "ids": ["$randomId"] + | }, + | "c1"]] + |}""".stripMargin) + .when + .post + .`then` + .statusCode(SC_OK) + .body(s"$ARGUMENTS.list", empty()) + .body(s"$ARGUMENTS.notFound", hasSize(1)) + .body(s"$ARGUMENTS.notFound", contains(randomId)) + } + + @Test + def getMailboxesByIdsShouldReturnMailboxesInSorteredOrder(server: GuiceJamesServer): Unit = { + val mailboxId1: String = server.getProbe(classOf[MailboxProbeImpl]) + .createMailbox(MailboxPath.forUser(BOB, DefaultMailboxes.TRASH)) + .serialize + val mailboxId2: String = server.getProbe(classOf[MailboxProbeImpl]) + .createMailbox(MailboxPath.forUser(BOB, DefaultMailboxes.INBOX)) + .serialize + + `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(s"""{ + | "using": [ + | "urn:ietf:params:jmap:core", + | "urn:ietf:params:jmap:mail"], + | "methodCalls": [[ + | "Mailbox/get", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "ids": ["$mailboxId1", "$mailboxId2"] + | }, + | "c1"]] + |}""".stripMargin) + .when + .post + .`then` + .statusCode(SC_OK) + .body(s"$ARGUMENTS.list", hasSize(2)) + .body(s"$FIRST_MAILBOX.id", equalTo(mailboxId2)) + .body(s"$FIRST_MAILBOX.name", equalTo(DefaultMailboxes.INBOX)) + .body(s"$FIRST_MAILBOX.sortOrder", equalTo(10)) + .body(s"$SECOND_MAILBOX.id", equalTo(mailboxId1)) + .body(s"$SECOND_MAILBOX.name", equalTo(DefaultMailboxes.TRASH)) + .body(s"$SECOND_MAILBOX.sortOrder", equalTo(60)) } } 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/MemoryMailboxGetMethodTest.java b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryMailboxGetMethodTest.java index 70db27f..aa1f9bd 100644 --- a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryMailboxGetMethodTest.java +++ b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryMailboxGetMethodTest.java @@ -21,10 +21,14 @@ package org.apache.james.jmap.rfc8621.memory; import static org.apache.james.MemoryJamesServerMain.IN_MEMORY_SERVER_AGGREGATE_MODULE; +import java.util.concurrent.ThreadLocalRandom; + import org.apache.james.GuiceJamesServer; import org.apache.james.JamesServerBuilder; import org.apache.james.JamesServerExtension; import org.apache.james.jmap.rfc8621.contract.MailboxGetMethodContract; +import org.apache.james.mailbox.inmemory.InMemoryId; +import org.apache.james.mailbox.model.MailboxId; import org.apache.james.modules.TestJMAPServerModule; import org.junit.jupiter.api.extension.RegisterExtension; @@ -35,4 +39,9 @@ public class MemoryMailboxGetMethodTest implements MailboxGetMethodContract { .combineWith(IN_MEMORY_SERVER_AGGREGATE_MODULE) .overrideWith(new TestJMAPServerModule())) .build(); + + @Override + public MailboxId randomMailboxId() { + return InMemoryId.of(ThreadLocalRandom.current().nextInt(100000) + 100); + } } diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxGet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxGet.scala index f38a3a1..a4400ee 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxGet.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxGet.scala @@ -32,7 +32,9 @@ case class MailboxGetRequest(accountId: AccountId, ids: Option[Ids], properties: Option[Properties]) -case class NotFound(value: List[MailboxId]) +case class NotFound(value: List[MailboxId]) { + def merge(other: NotFound): NotFound = NotFound(this.value ++ other.value) +} case class MailboxGetResponse(accountId: AccountId, state: State, diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxGetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxGetMethod.scala index a37cb19..6eaea11 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxGetMethod.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxGetMethod.scala @@ -27,8 +27,8 @@ import org.apache.james.jmap.model.Invocation.{Arguments, MethodName} import org.apache.james.jmap.model.State.INSTANCE import org.apache.james.jmap.model.{Invocation, MailboxFactory} import org.apache.james.jmap.utils.quotas.{QuotaLoader, QuotaLoaderWithPreloadedDefaultFactory} -import org.apache.james.mailbox.model.MailboxMetaData import org.apache.james.mailbox.model.search.MailboxQuery +import org.apache.james.mailbox.model.{MailboxId, MailboxMetaData} import org.apache.james.mailbox.{MailboxManager, MailboxSession} import org.apache.james.metrics.api.MetricFactory import org.reactivestreams.Publisher @@ -43,17 +43,25 @@ class MailboxGetMethod @Inject() (serializer: Serializer, metricFactory: MetricFactory) extends Method { override val methodName: MethodName = MethodName("Mailbox/get") + object MailboxGetResults { + def found(mailbox: Mailbox): MailboxGetResults = MailboxGetResults(List(mailbox), NotFound(Nil)) + def notFound(mailboxId: MailboxId): MailboxGetResults = MailboxGetResults(Nil, NotFound(List(mailboxId))) + } + + case class MailboxGetResults(mailboxes: List[Mailbox], notFound: NotFound) { + def merge(other: MailboxGetResults): MailboxGetResults = MailboxGetResults(this.mailboxes ++ other.mailboxes, this.notFound.merge(other.notFound)) + } + override def process(invocation: Invocation, mailboxSession: MailboxSession): Publisher[Invocation] = { metricFactory.decoratePublisherWithTimerMetricLogP99(JMAP_RFC8621_PREFIX + methodName.value, asMailboxGetRequest(invocation.arguments) .flatMap(mailboxGetRequest => getMailboxes(mailboxGetRequest, mailboxSession) - .collectSeq() - .map(_.sortBy(_.sortOrder)) + .reduce(MailboxGetResults(Nil, NotFound(Nil)), (result1: MailboxGetResults, result2: MailboxGetResults) => result1.merge(result2)) .map(mailboxes => MailboxGetResponse( accountId = mailboxGetRequest.accountId, state = INSTANCE, - list = mailboxes.toList, - notFound = NotFound(Nil))) + list = mailboxes.mailboxes.sortBy(_.sortOrder), + notFound = mailboxes.notFound)) .map(mailboxGetResponse => Invocation( methodName = methodName, arguments = Arguments(serializer.serialize(mailboxGetResponse).as[JsObject]), @@ -67,9 +75,18 @@ class MailboxGetMethod @Inject() (serializer: Serializer, } } - private def getMailboxes(mailboxGetRequest: MailboxGetRequest, mailboxSession: MailboxSession): SFlux[Mailbox] = mailboxGetRequest.ids match { - case None => getAllMailboxes(mailboxSession) - case _ => SFlux.raiseError(new NotImplementedError("Getting mailboxes by Ids is not supported yet")) + private def getMailboxes(mailboxGetRequest: MailboxGetRequest, mailboxSession: MailboxSession): SFlux[MailboxGetResults] = mailboxGetRequest.ids match { + case None => getAllMailboxes(mailboxSession).map(MailboxGetResults.found) + case Some(ids) => + getAllMailboxes(mailboxSession) + .collectSeq() + .flatMapMany(mailboxes => SFlux.merge(Seq( + SFlux.fromIterable(mailboxes) + .filter(mailbox => ids.value.contains(mailbox.id)) + .map(MailboxGetResults.found), + SFlux.fromIterable(ids.value) + .filter(id => !mailboxes.map(_.id).contains(id)) + .map(MailboxGetResults.notFound)))) } private def getAllMailboxes(mailboxSession: MailboxSession): SFlux[Mailbox] = { --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
