You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by bt...@apache.org on 2020/07/06 01:37:03 UTC

[james-project] 01/05: JAMES-3095 Get mailboxes by ids

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 <rc...@linagora.com>
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&bob@domain.tld": {
+         |                "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: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org