You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2021/02/03 02:51:11 UTC

[james-project] 05/12: JAMES-3491 Advertise the JMAP websocket capability in the session

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 647e2e8843b83dc21ec1908a599ee71f45458e84
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Jan 28 12:29:50 2021 +0700

    JAMES-3491 Advertise the JMAP websocket capability in the session
---
 .../jmap/rfc8621/contract/SessionRoutesContract.scala   |  9 +++++++++
 .../scala/org/apache/james/jmap/core/Capabilities.scala | 17 +++++++++++------
 .../scala/org/apache/james/jmap/core/Capability.scala   | 11 ++++++++++-
 .../james/jmap/core/JmapRfc8621Configuration.scala      |  1 +
 .../org/apache/james/jmap/json/ResponseSerializer.scala |  5 +++++
 .../org/apache/james/jmap/routes/SessionSupplier.scala  |  8 +++-----
 .../james/jmap/json/MailboxGetSerializationTest.scala   |  7 +++++--
 .../apache/james/jmap/routes/JMAPApiRoutesTest.scala    |  4 ++--
 .../apache/james/jmap/routes/SessionRoutesTest.scala    |  9 +++++++++
 9 files changed, 55 insertions(+), 16 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/SessionRoutesContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/SessionRoutesContract.scala
index 9ba8df0..7e1abc8 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/SessionRoutesContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/SessionRoutesContract.scala
@@ -63,6 +63,10 @@ object SessionRoutesContract {
                          |      "emailQuerySortOptions" : ["receivedAt", "sentAt"],
                          |      "mayCreateTopLevelMailbox" : true
                          |    },
+                         |    "urn:ietf:params:jmap:websocket": {
+                         |      "supportsPush": false,
+                         |      "url": "http://domain.com/jmap/ws"
+                         |    },
                          |    "urn:apache:james:params:jmap:mail:quota": {},
                          |    "urn:apache:james:params:jmap:mail:shares": {},
                          |    "urn:ietf:params:jmap:vacationresponse":{}
@@ -77,6 +81,10 @@ object SessionRoutesContract {
                          |          "maxDelayedSend": 0,
                          |          "submissionExtensions": []
                          |        },
+                         |        "urn:ietf:params:jmap:websocket": {
+                         |            "supportsPush": false,
+                         |            "url": "http://domain.com/jmap/ws"
+                         |        },
                          |        "urn:ietf:params:jmap:core" : {
                          |          "maxSizeUpload" : 20971520,
                          |          "maxConcurrentUpload" : 4,
@@ -103,6 +111,7 @@ object SessionRoutesContract {
                          |  },
                          |  "primaryAccounts" : {
                          |    "urn:ietf:params:jmap:submission": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+                         |    "urn:ietf:params:jmap:websocket": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
                          |    "urn:ietf:params:jmap:core" : "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
                          |    "urn:ietf:params:jmap:mail" : "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
                          |    "urn:apache:james:params:jmap:mail:quota": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Capabilities.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Capabilities.scala
index af4ad66..689c9ca 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Capabilities.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Capabilities.scala
@@ -18,8 +18,10 @@
  ****************************************************************/
 package org.apache.james.jmap.core
 
+import java.net.URL
+
 import eu.timepit.refined.auto._
-import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, EMAIL_SUBMISSION, JAMES_QUOTA, JAMES_SHARES, JMAP_CORE, JMAP_MAIL, JMAP_VACATION_RESPONSE}
+import org.apache.james.jmap.core.CapabilityIdentifier.CapabilityIdentifier
 
 object DefaultCapabilities {
   def coreCapability(maxUploadSize: MaxSizeUpload) = CoreCapability(
@@ -32,6 +34,10 @@ object DefaultCapabilities {
       MaxObjectsInGet(500L),
       MaxObjectsInSet(500L),
       collationAlgorithms = List("i;unicode-casemap")))
+
+  def webSocketCapability(url: URL) = WebSocketCapability(
+    properties = WebSocketCapabilityProperties(SupportsPush(false), url))
+
   val MAIL_CAPABILITY = MailCapability(
     properties = MailCapabilityProperties(
       MaxMailboxesPerEmail(Some(10_000_000L)),
@@ -45,15 +51,14 @@ object DefaultCapabilities {
   val VACATION_RESPONSE_CAPABILITY = VacationResponseCapability()
   val SUBMISSION_CAPABILITY = SubmissionCapability()
 
-  val SUPPORTED_CAPABILITY_IDENTIFIERS: Set[CapabilityIdentifier] =
-    Set(JMAP_CORE, JMAP_MAIL, JMAP_VACATION_RESPONSE, JAMES_SHARES, JAMES_QUOTA, EMAIL_SUBMISSION)
-
-  def supported(maxUploadSize: MaxSizeUpload): Capabilities = Capabilities(coreCapability(maxUploadSize),
+  def supported(configuration: JmapRfc8621Configuration): Capabilities = Capabilities(
+    coreCapability(configuration.maxUploadSize),
     MAIL_CAPABILITY,
     QUOTA_CAPABILITY,
     SHARES_CAPABILITY,
     VACATION_RESPONSE_CAPABILITY,
-    SUBMISSION_CAPABILITY)
+    SUBMISSION_CAPABILITY,
+    webSocketCapability(configuration.webSocketUrl))
 }
 
 case class Capabilities(capabilities: Capability*) {
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Capability.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Capability.scala
index 94b0c50..d3582bd 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Capability.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Capability.scala
@@ -19,12 +19,14 @@
 
 package org.apache.james.jmap.core
 
+import java.net.URL
+
 import eu.timepit.refined
 import eu.timepit.refined.api.Refined
 import eu.timepit.refined.auto._
 import eu.timepit.refined.collection.NonEmpty
 import eu.timepit.refined.string.Uri
-import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, EMAIL_SUBMISSION, JAMES_QUOTA, JAMES_SHARES, JMAP_CORE, JMAP_MAIL, JMAP_VACATION_RESPONSE}
+import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, EMAIL_SUBMISSION, JAMES_QUOTA, JAMES_SHARES, JMAP_CORE, JMAP_MAIL, JMAP_VACATION_RESPONSE, JMAP_WEBSOCKET}
 import org.apache.james.jmap.core.CoreCapabilityProperties.CollationAlgorithm
 import org.apache.james.jmap.core.MailCapability.EmailQuerySortOption
 import org.apache.james.jmap.core.UnsignedInt.{UnsignedInt, UnsignedIntConstraint}
@@ -41,6 +43,7 @@ object CapabilityIdentifier {
   val JMAP_MAIL: CapabilityIdentifier = "urn:ietf:params:jmap:mail"
   val JMAP_VACATION_RESPONSE: CapabilityIdentifier = "urn:ietf:params:jmap:vacationresponse"
   val EMAIL_SUBMISSION: CapabilityIdentifier = "urn:ietf:params:jmap:submission"
+  val JMAP_WEBSOCKET: CapabilityIdentifier = "urn:ietf:params:jmap:websocket"
   val JAMES_QUOTA: CapabilityIdentifier = "urn:apache:james:params:jmap:mail:quota"
   val JAMES_SHARES: CapabilityIdentifier = "urn:apache:james:params:jmap:mail:shares"
 }
@@ -55,6 +58,8 @@ trait Capability {
 final case class CoreCapability(properties: CoreCapabilityProperties,
                                 identifier: CapabilityIdentifier = JMAP_CORE) extends Capability
 
+case class WebSocketCapability(properties: WebSocketCapabilityProperties, identifier: CapabilityIdentifier = JMAP_WEBSOCKET) extends Capability
+
 object MaxSizeUpload {
   def of(size: Size): Try[MaxSizeUpload] = refined.refineV[UnsignedIntConstraint](size.asBytes()) match {
     case Right(value) => Success(MaxSizeUpload(value))
@@ -83,6 +88,10 @@ final case class CoreCapabilityProperties(maxSizeUpload: MaxSizeUpload,
                                           maxObjectsInSet: MaxObjectsInSet,
                                           collationAlgorithms: List[CollationAlgorithm]) extends CapabilityProperties
 
+final case class WebSocketCapabilityProperties(supportsPush: SupportsPush,
+                                               url: URL) extends CapabilityProperties
+
+final case class SupportsPush(value: Boolean) extends AnyVal
 final case class MaxDelayedSend(value: Int) extends AnyVal
 final case class EhloName(value: String) extends AnyVal
 final case class EhloArgs(value: String) extends AnyVal
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/JmapRfc8621Configuration.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/JmapRfc8621Configuration.scala
index 9ce796a..43f8420 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/JmapRfc8621Configuration.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/JmapRfc8621Configuration.scala
@@ -48,4 +48,5 @@ case class JmapRfc8621Configuration(urlPrefixString: String, maxUploadSize: MaxS
   val downloadUrl: URL = new URL(urlPrefixString + "/download/{accountId}/{blobId}/?type={type}&name={name}")
   val uploadUrl: URL = new URL(s"$urlPrefixString/upload/{accountId}")
   val eventSourceUrl: URL = new URL(s"$urlPrefixString/eventSource")
+  val webSocketUrl: URL = new URL(s"$urlPrefixString/jmap/ws")
 }
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/ResponseSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/ResponseSerializer.scala
index 02ca667..f9fcee5 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/ResponseSerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/ResponseSerializer.scala
@@ -91,11 +91,14 @@ object ResponseSerializer {
   private implicit val maxDelayedSendWrites: Writes[MaxDelayedSend] = Json.valueWrites[MaxDelayedSend]
   private implicit val ehloNameWrites: Writes[EhloName] = Json.valueWrites[EhloName]
   private implicit val ehloArgsWrites: Writes[EhloArgs] = Json.valueWrites[EhloArgs]
+  private implicit val supportsPushWrites: Writes[SupportsPush] = Json.valueWrites[SupportsPush]
   private implicit val submissionPropertiesWrites: Writes[SubmissionProperties] = Json.writes[SubmissionProperties]
+  private implicit val webSocketPropertiesWrites: Writes[WebSocketCapabilityProperties] = Json.writes[WebSocketCapabilityProperties]
   private implicit val quotaCapabilityWrites: Writes[QuotaCapabilityProperties] = OWrites[QuotaCapabilityProperties](_ => Json.obj())
   private implicit val sharesCapabilityWrites: Writes[SharesCapabilityProperties] = OWrites[SharesCapabilityProperties](_ => Json.obj())
   private implicit val vacationResponseCapabilityWrites: Writes[VacationResponseCapabilityProperties] = OWrites[VacationResponseCapabilityProperties](_ => Json.obj())
   private implicit val submissionCapabilityWrites: Writes[SubmissionCapability] = OWrites[SubmissionCapability](_ => Json.obj())
+  private implicit val webSocketCapabilityWrites: Writes[WebSocketCapability] = OWrites[WebSocketCapability](_ => Json.obj())
 
   private implicit val setCapabilityWrites: Writes[Set[_ <: Capability]] =
     (set: Set[_ <: Capability]) => {
@@ -113,6 +116,8 @@ object ResponseSerializer {
             jsObject.+(capability.identifier.value, vacationResponseCapabilityWrites.writes(capability.properties))
           case capability: SubmissionCapability =>
             jsObject.+(capability.identifier.value, submissionPropertiesWrites.writes(capability.properties))
+          case capability: WebSocketCapability =>
+            jsObject.+(capability.identifier.value, webSocketPropertiesWrites.writes(capability.properties))
           case _ => jsObject
         }
       })
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/SessionSupplier.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/SessionSupplier.scala
index c9d631c..bb9270f 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/SessionSupplier.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/SessionSupplier.scala
@@ -25,12 +25,10 @@ import org.apache.james.jmap.core.CapabilityIdentifier.CapabilityIdentifier
 import org.apache.james.jmap.core.{Account, AccountId, DefaultCapabilities, IsPersonal, IsReadOnly, JmapRfc8621Configuration, Session}
 
 class SessionSupplier @Inject() (val configuration: JmapRfc8621Configuration) {
-  private val maxSizeUpload = configuration.maxUploadSize
-
   def generate(username: Username): Either[IllegalArgumentException, Session] =
     accounts(username)
       .map(account => Session(
-        DefaultCapabilities.supported(maxSizeUpload),
+        DefaultCapabilities.supported(configuration),
         List(account),
         primaryAccounts(account.accountId),
         username,
@@ -40,10 +38,10 @@ class SessionSupplier @Inject() (val configuration: JmapRfc8621Configuration) {
         eventSourceUrl = configuration.eventSourceUrl))
 
   private def accounts(username: Username): Either[IllegalArgumentException, Account] =
-    Account.from(username, IsPersonal(true), IsReadOnly(false), DefaultCapabilities.supported(maxSizeUpload).toSet)
+    Account.from(username, IsPersonal(true), IsReadOnly(false), DefaultCapabilities.supported(configuration).toSet)
 
   private def primaryAccounts(accountId: AccountId): Map[CapabilityIdentifier, AccountId] =
-    DefaultCapabilities.supported(maxSizeUpload).toSet
+    DefaultCapabilities.supported(configuration).toSet
       .map(capability => (capability.identifier(), accountId))
       .toMap
 }
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxGetSerializationTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxGetSerializationTest.scala
index 5eb339a..62c4e92 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxGetSerializationTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxGetSerializationTest.scala
@@ -21,8 +21,9 @@ package org.apache.james.jmap.json
 
 import eu.timepit.refined.auto._
 import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
+import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, EMAIL_SUBMISSION, JAMES_QUOTA, JAMES_SHARES, JMAP_CORE, JMAP_MAIL, JMAP_VACATION_RESPONSE}
 import org.apache.james.jmap.core.State.INSTANCE
-import org.apache.james.jmap.core.{AccountId, DefaultCapabilities, Properties}
+import org.apache.james.jmap.core.{AccountId, Properties}
 import org.apache.james.jmap.json.Fixture._
 import org.apache.james.jmap.json.MailboxGetSerializationTest._
 import org.apache.james.jmap.json.MailboxSerializationTest.MAILBOX
@@ -144,6 +145,8 @@ class MailboxGetSerializationTest extends AnyWordSpec with Matchers {
 
   "Serialize MailboxGetResponse" should {
     "succeed" in {
+      val supportedCapabilityIdentifiers: Set[CapabilityIdentifier] =
+        Set(JMAP_CORE, JMAP_MAIL, JMAP_VACATION_RESPONSE, JAMES_SHARES, JAMES_QUOTA, EMAIL_SUBMISSION)
       val actualValue: MailboxGetResponse = MailboxGetResponse(
         accountId = ACCOUNT_ID,
         state = INSTANCE,
@@ -197,7 +200,7 @@ class MailboxGetSerializationTest extends AnyWordSpec with Matchers {
           |}
           |""".stripMargin
 
-      assertThatJson(Json.stringify(SERIALIZER.serialize(actualValue, Mailbox.allProperties, DefaultCapabilities.SUPPORTED_CAPABILITY_IDENTIFIERS))).isEqualTo(expectedJson)
+      assertThatJson(Json.stringify(SERIALIZER.serialize(actualValue, Mailbox.allProperties, supportedCapabilityIdentifiers))).isEqualTo(expectedJson)
     }
   }
 }
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
index 61faf1b..ab43abe 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
@@ -78,7 +78,7 @@ object JMAPApiRoutesTest {
   private val userProvisionner: UserProvisioning = new UserProvisioning(usersRepository, new RecordingMetricFactory)
   private val JMAP_METHODS: Set[Method] = Set(new CoreEchoMethod)
 
-  private val JMAP_API_ROUTE: JMAPApiRoutes = new JMAPApiRoutes(AUTHENTICATOR, userProvisionner, new JMAPApi(JMAP_METHODS, DefaultCapabilities.supported(JmapRfc8621Configuration.UPLOAD_LIMIT_30_MB).capabilities.toSet))
+  private val JMAP_API_ROUTE: JMAPApiRoutes = new JMAPApiRoutes(AUTHENTICATOR, userProvisionner, new JMAPApi(JMAP_METHODS, DefaultCapabilities.supported(JmapRfc8621Configuration("http://127.0.0.1")).capabilities.toSet))
   private val ROUTES_HANDLER: ImmutableSet[JMAPRoutesHandler] = ImmutableSet.of(new JMAPRoutesHandler(Version.RFC8621, JMAP_API_ROUTE))
 
   private val userBase64String: String = Base64.getEncoder.encodeToString("user1:password".getBytes(StandardCharsets.UTF_8))
@@ -442,7 +442,7 @@ class JMAPApiRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
     when(mockCoreEchoMethod.requiredCapabilities).thenReturn(Set(JMAP_CORE))
 
     val methods: Set[Method] = Set(mockCoreEchoMethod)
-    val apiRoute: JMAPApiRoutes = new JMAPApiRoutes(AUTHENTICATOR, userProvisionner, new JMAPApi(methods, DefaultCapabilities.supported(JmapRfc8621Configuration.UPLOAD_LIMIT_30_MB).capabilities.toSet))
+    val apiRoute: JMAPApiRoutes = new JMAPApiRoutes(AUTHENTICATOR, userProvisionner, new JMAPApi(methods, DefaultCapabilities.supported(JmapRfc8621Configuration("http://127.0.0.1")).capabilities.toSet))
     val routesHandler: ImmutableSet[JMAPRoutesHandler] = ImmutableSet.of(new JMAPRoutesHandler(Version.RFC8621, apiRoute))
 
     val versionParser: VersionParser = new VersionParser(SUPPORTED_VERSIONS, JMAPConfiguration.DEFAULT)
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/SessionRoutesTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/SessionRoutesTest.scala
index 2f21d81..9e903e2 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/SessionRoutesTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/SessionRoutesTest.scala
@@ -142,6 +142,10 @@ class SessionRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
                          |      "emailQuerySortOptions" : ["receivedAt", "sentAt"],
                          |      "mayCreateTopLevelMailbox" : true
                          |    },
+                         |    "urn:ietf:params:jmap:websocket": {
+                         |      "supportsPush": false,
+                         |      "url": "http://localhost/jmap/ws"
+                         |    },
                          |    "urn:apache:james:params:jmap:mail:quota": {},
                          |    "urn:apache:james:params:jmap:mail:shares": {},
                          |    "urn:ietf:params:jmap:vacationresponse":{}
@@ -156,6 +160,10 @@ class SessionRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
                          |          "maxDelayedSend": 0,
                          |          "submissionExtensions": []
                          |        },
+                         |        "urn:ietf:params:jmap:websocket": {
+                         |            "supportsPush": false,
+                         |            "url": "http://localhost/jmap/ws"
+                         |        },
                          |        "urn:ietf:params:jmap:core" : {
                          |          "maxSizeUpload" : 31457280,
                          |          "maxConcurrentUpload" : 4,
@@ -182,6 +190,7 @@ class SessionRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
                          |  },
                          |  "primaryAccounts" : {
                          |    "urn:ietf:params:jmap:submission": "0fe275bf13ff761407c17f64b1dfae2f4b3186feea223d7267b79f873a105401",
+                         |    "urn:ietf:params:jmap:websocket": "0fe275bf13ff761407c17f64b1dfae2f4b3186feea223d7267b79f873a105401",
                          |    "urn:ietf:params:jmap:core" : "0fe275bf13ff761407c17f64b1dfae2f4b3186feea223d7267b79f873a105401",
                          |    "urn:ietf:params:jmap:mail" : "0fe275bf13ff761407c17f64b1dfae2f4b3186feea223d7267b79f873a105401",
                          |    "urn:apache:james:params:jmap:mail:quota": "0fe275bf13ff761407c17f64b1dfae2f4b3186feea223d7267b79f873a105401",


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org