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/03/23 07:37:01 UTC

[james-project] 01/02: JAMES-3316 Allow custom properties payload for custom capabilities in JMAP 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 a29d8c1d32ba9a81adbed4420edfc4f055d6e8dc
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Mar 19 10:38:20 2021 +0700

    JAMES-3316 Allow custom properties payload for custom capabilities in JMAP session
    
    Currently, extra capability needed to have empty properties as the JSON generation
    of the session capability payload was not modular.
    
    We add an indirection level (the capability properties becomes JSON aware) in order to
    permit that.
    
    Session in a conveniant mean to convey information regarding JMAP extensions thus
    customizing its payload is critical.
---
 .../rfc8621/contract/CustomMethodContract.scala    |  9 ++++--
 .../org/apache/james/jmap/core/Capability.scala    | 34 ++++++++++++++++-----
 .../james/jmap/json/ResponseSerializer.scala       | 35 ++++------------------
 3 files changed, 38 insertions(+), 40 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/CustomMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/CustomMethodContract.scala
index f2f367d..b399781 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/CustomMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/CustomMethodContract.scala
@@ -40,6 +40,7 @@ import org.apache.james.mailbox.MailboxSession
 import org.apache.james.utils.DataProbeImpl
 import org.junit.jupiter.api.{BeforeEach, Test}
 import org.reactivestreams.Publisher
+import play.api.libs.json.{JsObject, Json}
 import reactor.core.scala.publisher.SMono
 
 object CustomMethodContract {
@@ -75,7 +76,7 @@ object CustomMethodContract {
       |      "url": "http://domain.com/jmap/ws"
       |    },
       |    "urn:apache:james:params:jmap:mail:quota": {},
-      |    "$CUSTOM": {},
+      |    "$CUSTOM": {"custom": "property"},
       |    "urn:apache:james:params:jmap:mail:shares": {},
       |    "urn:ietf:params:jmap:vacationresponse":{}
       |  },
@@ -113,7 +114,7 @@ object CustomMethodContract {
       |        },
       |        "urn:apache:james:params:jmap:mail:quota": {},
       |        "urn:apache:james:params:jmap:mail:shares": {},
-      |        "$CUSTOM": {},
+      |        "$CUSTOM": {"custom": "property"},
       |        "urn:ietf:params:jmap:vacationresponse":{}
       |      }
       |    }
@@ -137,7 +138,9 @@ object CustomMethodContract {
       |}""".stripMargin
 }
 
-case class CustomCapabilityProperties() extends CapabilityProperties
+case class CustomCapabilityProperties() extends CapabilityProperties {
+  override def jsonify(): JsObject = Json.obj(("custom", "property"))
+}
 
 case class CustomCapability(properties: CustomCapabilityProperties = CustomCapabilityProperties(), identifier: CapabilityIdentifier = CUSTOM) extends 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 d3582bd..a73ad1a 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
@@ -30,7 +30,9 @@ import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, EM
 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}
+import org.apache.james.jmap.json.ResponseSerializer
 import org.apache.james.util.Size
+import play.api.libs.json.{JsObject, Json}
 
 import scala.util.{Failure, Success, Try}
 
@@ -48,7 +50,9 @@ object CapabilityIdentifier {
   val JAMES_SHARES: CapabilityIdentifier = "urn:apache:james:params:jmap:mail:shares"
 }
 
-trait CapabilityProperties
+trait CapabilityProperties {
+  def jsonify(): JsObject
+}
 
 trait Capability {
   def identifier(): CapabilityIdentifier
@@ -86,10 +90,14 @@ final case class CoreCapabilityProperties(maxSizeUpload: MaxSizeUpload,
                                           maxCallsInRequest: MaxCallsInRequest,
                                           maxObjectsInGet: MaxObjectsInGet,
                                           maxObjectsInSet: MaxObjectsInSet,
-                                          collationAlgorithms: List[CollationAlgorithm]) extends CapabilityProperties
+                                          collationAlgorithms: List[CollationAlgorithm]) extends CapabilityProperties {
+  override def jsonify(): JsObject = ResponseSerializer.coreCapabilityWrites.writes(this)
+}
 
 final case class WebSocketCapabilityProperties(supportsPush: SupportsPush,
-                                               url: URL) extends CapabilityProperties
+                                               url: URL) extends CapabilityProperties {
+  override def jsonify(): JsObject = ResponseSerializer.webSocketPropertiesWrites.writes(this)
+}
 
 final case class SupportsPush(value: Boolean) extends AnyVal
 final case class MaxDelayedSend(value: Int) extends AnyVal
@@ -100,7 +108,9 @@ final case class SubmissionCapability(identifier: CapabilityIdentifier = EMAIL_S
                                       properties: SubmissionProperties = SubmissionProperties()) extends Capability
 
 final case class SubmissionProperties(maxDelayedSend: MaxDelayedSend = MaxDelayedSend(0),
-                                      submissionExtensions: Map[EhloName, List[EhloArgs]] = Map()) extends CapabilityProperties
+                                      submissionExtensions: Map[EhloName, List[EhloArgs]] = Map()) extends CapabilityProperties {
+  override def jsonify(): JsObject = ResponseSerializer.submissionPropertiesWrites.writes(this)
+}
 
 object MailCapability {
   type EmailQuerySortOption = String Refined NonEmpty
@@ -120,19 +130,27 @@ final case class MailCapabilityProperties(maxMailboxesPerEmail: MaxMailboxesPerE
                                           maxSizeMailboxName: MaxSizeMailboxName,
                                           maxSizeAttachmentsPerEmail: MaxSizeAttachmentsPerEmail,
                                           emailQuerySortOptions: List[EmailQuerySortOption],
-                                          mayCreateTopLevelMailbox: MayCreateTopLevelMailbox) extends CapabilityProperties
+                                          mayCreateTopLevelMailbox: MayCreateTopLevelMailbox) extends CapabilityProperties {
+  override def jsonify(): JsObject = ResponseSerializer.mailCapabilityWrites.writes(this)
+}
 
-final case class QuotaCapabilityProperties() extends CapabilityProperties
+final case class QuotaCapabilityProperties() extends CapabilityProperties {
+  override def jsonify(): JsObject = Json.obj()
+}
 
 final case class QuotaCapability(properties: QuotaCapabilityProperties = QuotaCapabilityProperties(),
                                  identifier: CapabilityIdentifier = JAMES_QUOTA) extends Capability
 
-final case class SharesCapabilityProperties() extends CapabilityProperties
+final case class SharesCapabilityProperties() extends CapabilityProperties {
+  override def jsonify(): JsObject = Json.obj()
+}
 
 final case class SharesCapability(properties: SharesCapabilityProperties = SharesCapabilityProperties(),
                                   identifier: CapabilityIdentifier = JAMES_SHARES) extends Capability
 
-final case class VacationResponseCapabilityProperties() extends CapabilityProperties
+final case class VacationResponseCapabilityProperties() extends CapabilityProperties {
+  override def jsonify(): JsObject = Json.obj()
+}
 
 final case class VacationResponseCapability(properties: VacationResponseCapabilityProperties = VacationResponseCapabilityProperties(),
                                             identifier: CapabilityIdentifier = JMAP_VACATION_RESPONSE) extends Capability
\ No newline at end of file
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 89d4305..0c40307 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
@@ -88,42 +88,19 @@ object ResponseSerializer {
 
   private implicit val usernameWrites: Writes[Username] = username => JsString(username.asString)
   private implicit val urlWrites: Writes[URL] = url => JsString(url.toString)
-  private implicit val coreCapabilityWrites: Writes[CoreCapabilityProperties] = Json.writes[CoreCapabilityProperties]
-  private implicit val mailCapabilityWrites: Writes[MailCapabilityProperties] = Json.writes[MailCapabilityProperties]
+  val coreCapabilityWrites: OWrites[CoreCapabilityProperties] = Json.writes[CoreCapabilityProperties]
+  val mailCapabilityWrites: OWrites[MailCapabilityProperties] = Json.writes[MailCapabilityProperties]
   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())
+  val submissionPropertiesWrites: OWrites[SubmissionProperties] = Json.writes[SubmissionProperties]
+  val webSocketPropertiesWrites: OWrites[WebSocketCapabilityProperties] = Json.writes[WebSocketCapabilityProperties]
 
   private implicit val setCapabilityWrites: Writes[Set[_ <: Capability]] =
     (set: Set[_ <: Capability]) => {
-      set.foldLeft(JsObject.empty)((jsObject, capability) => {
-        capability match {
-          case capability: CoreCapability =>
-            jsObject.+(capability.identifier.value, coreCapabilityWrites.writes(capability.properties))
-          case capability: MailCapability =>
-            jsObject.+(capability.identifier.value, mailCapabilityWrites.writes(capability.properties))
-          case capability: QuotaCapability =>
-            jsObject.+(capability.identifier.value, quotaCapabilityWrites.writes(capability.properties))
-          case capability: SharesCapability =>
-            jsObject.+(capability.identifier.value, sharesCapabilityWrites.writes(capability.properties))
-          case capability: VacationResponseCapability =>
-            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.+(capability.identifier.value, JsObject(Map[String, JsValue]()))
-        }
-      })
+      set.foldLeft(JsObject.empty)((jsObject, capability) =>
+        jsObject.+(capability.identifier.value, capability.properties.jsonify))
     }
 
   private implicit val capabilitiesWrites: Writes[Capabilities] = capabilities => setCapabilityWrites.writes(capabilities.capabilities)

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