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