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/30 04:15:13 UTC

[james-project] branch master updated (2de38df -> 89fb5f4)

This is an automated email from the ASF dual-hosted git repository.

btellier pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git.


    from 2de38df  JAMES-3157 Fix support for bench profiles
     new 62b20ec  JAMES-2892 Request level error handling
     new 6867c94  JAMES-2892 use the same default capabilities everywhere
     new e1ae434  JAMES-3093 Port UserProvisioner from draft to jmap-rfc-8621
     new 98f9f18  JAMES-3093 Port MailboxProvisioner from draft to jmap-rfc-8621
     new d2c314e  JAMES-3093 User and mailboxes provisioning for JMAP-RFC-8621
     new 029fc19  JAMES-3093 Refactoring MailboxGetMethodContract with mailboxes provisioning
     new 986002b  JAMES-3317 add deduplication propertie for blobstore in configuration
     new d6ac26f  JAMES-3317 use Storage strategy from BlobStoreConfiguration when instantiating the blobstore
     new 03addb0  JAMES-3348 Add missing Sieve persistent class declarations
     new ab1a174  [REFACTORING] Default methods for GuiceModuleTestRule
     new 039bdea  [REFACTORING] Cleanup Text extractor overrides
     new 89fb5f4  [REFACTORING] Override cleanup for RabbitMQAwsS3Stepdefs

The 12 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../destination/conf/blob.properties               |   9 +-
 .../destination/conf/blob.properties               |   7 +
 .../destination/conf/blob.properties               |   7 +
 .../cassandra/destination/conf/blob.properties     |   9 +-
 .../src/main/resources/META-INF/persistence.xml    |   2 +
 server/container/cli-integration/pom.xml           |   6 -
 .../objectstorage/aws/s3/DockerAwsS3TestRule.java  |   4 -
 .../objectstorage/swift/DockerSwiftTestRule.java   |   4 -
 server/container/guice/cassandra-guice/pom.xml     |   6 -
 .../AuthenticatedCassandraJamesServerTest.java     |   3 -
 .../org/apache/james/CassandraJamesServerTest.java |   3 -
 .../apache/james/CassandraJmapJamesServerTest.java |   3 -
 .../james/DefaultCassandraJamesServerTest.java     |   3 -
 .../java/org/apache/james/DockerCassandraRule.java |   4 -
 .../test/java/org/apache/james/DockerLdapRule.java |   4 -
 .../guice/cassandra-rabbitmq-guice/pom.xml         |   6 -
 .../modules/blobstore/BlobStoreConfiguration.java  |  87 +++-
 .../modules/blobstore/BlobStoreModulesChooser.java |  45 +-
 .../james/CassandraRabbitMQAwsS3JmapTestRule.java  |   5 +-
 .../james/CassandraRabbitMQJamesServerFixture.java |   5 +-
 .../james/CassandraRabbitMQSwiftJmapTestRule.java  |   5 +-
 .../java/org/apache/james/WithCacheExtension.java  |   5 +-
 .../org/apache/james/WithCassandraBlobStore.java   |   5 +-
 .../apache/james/WithScanningSearchExtension.java  |   5 +-
 .../apache/james/modules/DockerRabbitMQRule.java   |   4 -
 .../BlobStoreCacheModulesChooserTest.java          |  10 +-
 .../blobstore/BlobStoreConfigurationTest.java      |  63 ++-
 .../blobstore/BlobStoreModulesChooserTest.java     |  14 +-
 .../CassandraRabbitMQLdapJmapJamesServerTest.java  |  15 +-
 .../java/org/apache/james/GuiceModuleTestRule.java |   8 +-
 .../org/apache/james/TempFilesystemTestRule.java   |  12 -
 .../src/main/resources/META-INF/persistence.xml    |   2 +
 server/container/guice/memory-guice/pom.xml        |   6 -
 .../org/apache/james/MemoryJamesServerTest.java    |   3 -
 .../java/org/apache/james/MemoryJmapTestRule.java  |   6 -
 .../memory/MemoryGetMessageListMethodTest.java     |   5 +-
 .../rabbitmq/RabbitMQAwsS3SendMDNMethodTest.java   |   5 +-
 .../RabbitMQAwsS3SpamAssassinContractTest.java     |   5 +-
 .../cucumber/awss3/RabbitMQAwsS3Stepdefs.java      |  11 +-
 .../distributed/DistributedAuthenticationTest.java |   5 +-
 .../distributed/DistributedEchoMethodTest.java     |   5 +-
 .../DistributedMailboxGetMethodTest.java           |   5 +-
 ...dTest.java => DistributedProvisioningTest.java} |  12 +-
 .../distributed/DistributedSessionRouteTest.java   |   2 +-
 .../james/jmap/rfc8621/contract/Fixture.scala      |  14 +
 .../contract/MailboxGetMethodContract.scala        | 577 +++++++++------------
 .../rfc8621/contract/ProvisioningContract.scala    |  83 +++
 ...MethodTest.java => MemoryProvisioningTest.java} |   4 +-
 .../james/jmap/http/MailboxesProvisioner.scala     |  81 +++
 .../apache/james/jmap/http/UserProvisioning.scala  |  64 +++
 .../org/apache/james/jmap/json/Serializer.scala    |  16 +-
 .../org/apache/james/jmap/model/Capabilities.scala |  13 +-
 .../org/apache/james/jmap/model/Capability.scala   |  16 +-
 .../apache/james/jmap/model/ProblemDetails.scala   |  23 +-
 .../james/jmap/model/RequestLevelErrorType.scala   |  13 +-
 .../org/apache/james/jmap/model/StatusCode.scala   |  10 +-
 .../apache/james/jmap/routes/JMAPApiRoutes.scala   |  81 ++-
 .../james/jmap/http/MailboxesProvisionerTest.scala | 103 ++++
 .../apache/james/jmap/http/SessionRoutesTest.scala |  68 ++-
 .../james/jmap/http/UserProvisioningTest.scala     | 103 ++++
 .../james/jmap/json/SessionSerializationTest.scala |   6 +-
 .../james/jmap/routes/JMAPApiRoutesTest.scala      |  96 +++-
 .../rabbitmq/ConsistencyTasksIntegrationTest.java  |   5 +-
 .../rabbitmq/FixingGhostMailboxTest.java           |   5 +-
 .../rabbitmq/RabbitMQAuthorizedEndpointsTest.java  |   5 +-
 .../RabbitMQEventDeadLettersIntegrationTest.java   |   5 +-
 ...stViewProjectionHealthCheckIntegrationTest.java |   5 +-
 .../rabbitmq/RabbitMQForwardIntegrationTest.java   |   5 +-
 .../rabbitmq/RabbitMQJmapExtension.java            |   5 +-
 .../rabbitmq/RabbitMQJwtFilterIntegrationTest.java |   5 +-
 ...RabbitMQReindexingWithEventDeadLettersTest.java |   5 +-
 .../RabbitMQWebAdminServerIntegrationTest.java     |   5 +-
 ...dminServerTaskSerializationIntegrationTest.java |   5 +-
 ...RabbitMQDeletedMessageVaultIntegrationTest.java |   5 +-
 ...LinshareBlobExportMechanismIntegrationTest.java |   8 +-
 .../webadmin-integration-test-common/pom.xml       |  10 -
 src/site/xdoc/server/config-blobstore.xml          |   9 +
 77 files changed, 1325 insertions(+), 573 deletions(-)
 copy server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/{DistributedEchoMethodTest.java => DistributedProvisioningTest.java} (88%)
 create mode 100644 server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/ProvisioningContract.scala
 copy server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/{MemoryEchoMethodTest.java => MemoryProvisioningTest.java} (93%)
 create mode 100644 server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/MailboxesProvisioner.scala
 create mode 100644 server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/UserProvisioning.scala
 copy mailbox/api/src/main/java/org/apache/james/mailbox/Authorizator.java => server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/ProblemDetails.scala (73%)
 copy backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/configuration/InjectionNames.java => server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/RequestLevelErrorType.scala (68%)
 copy core/src/main/java/org/apache/james/core/healthcheck/HealthCheck.java => server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/StatusCode.scala (86%)
 create mode 100644 server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/MailboxesProvisionerTest.scala
 create mode 100644 server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/UserProvisioningTest.scala


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


[james-project] 02/12: JAMES-2892 use the same default capabilities everywhere

Posted by bt...@apache.org.
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 6867c94624466b4ed217be3d267b60456ad9fad7
Author: Raphael Ouazana <ra...@linagora.com>
AuthorDate: Thu Jul 23 17:13:29 2020 +0200

    JAMES-2892 use the same default capabilities everywhere
---
 .../org/apache/james/jmap/json/Serializer.scala    | 12 +++-
 .../org/apache/james/jmap/model/Capabilities.scala | 13 ++++-
 .../org/apache/james/jmap/model/Capability.scala   | 18 +++++-
 .../apache/james/jmap/routes/JMAPApiRoutes.scala   |  2 +-
 .../apache/james/jmap/http/SessionRoutesTest.scala | 68 +++++++++++++++++++++-
 .../james/jmap/json/SessionSerializationTest.scala |  6 +-
 6 files changed, 106 insertions(+), 13 deletions(-)

diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala
index d9e234a..614c6fa 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala
@@ -93,9 +93,13 @@ class Serializer @Inject() (mailboxIdFactory: MailboxId.Factory) {
   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]
+  private implicit val quotaCapabilityWrites: Writes[QuotaCapabilityProperties] = OWrites[QuotaCapabilityProperties](_ => Json.obj())
+  private implicit val sharesCapabilityWrites: Writes[SharesCapabilityProperties] = OWrites[SharesCapabilityProperties](_ => Json.obj())
 
   private implicit def setCapabilityWrites(implicit corePropertiesWriter: Writes[CoreCapabilityProperties],
-                                   mailCapabilityWrites: Writes[MailCapabilityProperties]): Writes[Set[_ <: Capability]] =
+                                   mailCapabilityWrites: Writes[MailCapabilityProperties],
+                                   quotaCapabilityWrites: Writes[QuotaCapabilityProperties],
+                                   sharesCapabilityWrites: Writes[SharesCapabilityProperties]): Writes[Set[_ <: Capability]] =
     (set: Set[_ <: Capability]) => {
       set.foldLeft(JsObject.empty)((jsObject, capability) => {
         capability match {
@@ -103,12 +107,16 @@ class Serializer @Inject() (mailboxIdFactory: MailboxId.Factory) {
             jsObject.+(capability.identifier.value, corePropertiesWriter.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 _ => jsObject
         }
       })
     }
 
-  private implicit val capabilitiesWrites: Writes[Capabilities] = capabilities => setCapabilityWrites.writes(Set(capabilities.coreCapability, capabilities.mailCapability))
+  private implicit val capabilitiesWrites: Writes[Capabilities] = capabilities => setCapabilityWrites.writes(capabilities.toSet)
 
   private implicit val accountIdWrites: Format[AccountId] = Json.valueFormat[AccountId]
   private implicit def identifierMapWrite[Any](implicit idWriter: Writes[AccountId]): Writes[Map[CapabilityIdentifier, Any]] =
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Capabilities.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Capabilities.scala
index ac0a7d4..9fe910f 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Capabilities.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Capabilities.scala
@@ -19,6 +19,7 @@
 package org.apache.james.jmap.model
 
 import eu.timepit.refined.auto._
+import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
 
 object DefaultCapabilities {
   val CORE_CAPABILITY = CoreCapability(
@@ -43,9 +44,15 @@ object DefaultCapabilities {
     )
   )
 
-  val SUPPORTED = Capabilities(CORE_CAPABILITY, MAIL_CAPABILITY)
+  val QUOTA_CAPABILITY = QuotaCapability()
+
+  val SHARES_CAPABILITY = SharesCapability()
+
+  val SUPPORTED = Capabilities(CORE_CAPABILITY, MAIL_CAPABILITY, QUOTA_CAPABILITY, SHARES_CAPABILITY)
 }
 
-case class Capabilities(coreCapability: CoreCapability, mailCapability: MailCapability) {
-  def toSet : Set[Capability] = Set(coreCapability, mailCapability)
+case class Capabilities(capabilities: Capability*) {
+  def toSet : Set[Capability] = capabilities.toSet
+
+  def ids : Set[CapabilityIdentifier] = toSet.map(_.identifier())
 }
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Capability.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Capability.scala
index 8eaaefb..1ade76e 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Capability.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Capability.scala
@@ -23,7 +23,7 @@ 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.model.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_MAIL}
+import org.apache.james.jmap.model.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_MAIL, JAMES_QUOTA, JAMES_SHARES}
 import org.apache.james.jmap.model.CoreCapabilityProperties.CollationAlgorithm
 import org.apache.james.jmap.model.MailCapability.EmailQuerySortOption
 import org.apache.james.jmap.model.UnsignedInt.UnsignedInt
@@ -34,8 +34,6 @@ object CapabilityIdentifier {
   val JMAP_MAIL: CapabilityIdentifier = "urn:ietf:params:jmap:mail"
   val JAMES_QUOTA: CapabilityIdentifier = "urn:apache:james:params:jmap:mail:quota"
   val JAMES_SHARES: CapabilityIdentifier = "urn:apache:james:params:jmap:mail:shares"
-
-  val SUPPORTED_CAPABILITIES: Set[CapabilityIdentifier] = Set(JMAP_CORE, JMAP_MAIL, JAMES_QUOTA, JAMES_SHARES)
 }
 
 sealed trait CapabilityProperties
@@ -91,3 +89,17 @@ final case class MailCapabilityProperties(maxMailboxesPerEmail: MaxMailboxesPerE
                                           mayCreateTopLevelMailbox: MayCreateTopLevelMailbox) extends CapabilityProperties {
 }
 
+final case class QuotaCapabilityProperties() extends CapabilityProperties {
+}
+
+final case class QuotaCapability(properties: QuotaCapabilityProperties = QuotaCapabilityProperties(),
+                                 identifier: CapabilityIdentifier = JAMES_QUOTA) extends Capability {
+}
+
+final case class SharesCapabilityProperties() extends CapabilityProperties {
+}
+
+final case class SharesCapability(properties: SharesCapabilityProperties = SharesCapabilityProperties(),
+                                  identifier: CapabilityIdentifier = JAMES_SHARES) extends Capability {
+}
+
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
index 45dfda5..320c540 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
@@ -104,7 +104,7 @@ class JMAPApiRoutes (val authenticator: Authenticator,
   private def process(requestObject: RequestObject,
                       httpServerResponse: HttpServerResponse,
                       mailboxSession: MailboxSession): SMono[Void] = {
-    val unsupportedCapabilities = requestObject.using.toSet -- CapabilityIdentifier.SUPPORTED_CAPABILITIES
+    val unsupportedCapabilities = requestObject.using.toSet -- DefaultCapabilities.SUPPORTED.ids
 
     if (unsupportedCapabilities.nonEmpty) {
       SMono.raiseError(UnsupportedCapabilitiesException(unsupportedCapabilities))
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
index 0501f8b..47e89ee 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
@@ -31,7 +31,6 @@ import org.apache.http.HttpStatus
 import org.apache.james.core.Username
 import org.apache.james.jmap._
 import org.apache.james.jmap.http.SessionRoutesTest.{BOB, TEST_CONFIGURATION}
-import org.apache.james.jmap.json.Fixture.expected_session_object_json
 import org.apache.james.jmap.json.Serializer
 import org.apache.james.mailbox.MailboxSession
 import org.apache.james.mailbox.model.TestId
@@ -101,7 +100,72 @@ class SessionRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
       .thenReturn
         .getBody
         .asString()
+    val expectedJson = """{
+                         |  "capabilities" : {
+                         |    "urn:ietf:params:jmap:core" : {
+                         |      "maxSizeUpload" : 10000000,
+                         |      "maxConcurrentUpload" : 4,
+                         |      "maxSizeRequest" : 10000000,
+                         |      "maxConcurrentRequests" : 4,
+                         |      "maxCallsInRequest" : 16,
+                         |      "maxObjectsInGet" : 500,
+                         |      "maxObjectsInSet" : 500,
+                         |      "collationAlgorithms" : [ "i;unicode-casemap" ]
+                         |    },
+                         |    "urn:ietf:params:jmap:mail" : {
+                         |      "maxMailboxesPerEmail" : 10000000,
+                         |      "maxMailboxDepth" : null,
+                         |      "maxSizeMailboxName" : 200,
+                         |      "maxSizeAttachmentsPerEmail" : 20000000,
+                         |      "emailQuerySortOptions" : [ "receivedAt", "cc", "from", "to", "subject", "size", "sentAt", "hasKeyword", "uid", "Id" ],
+                         |      "mayCreateTopLevelMailbox" : true
+                         |    },
+                         |    "urn:apache:james:params:jmap:mail:quota": {},
+                         |    "urn:apache:james:params:jmap:mail:shares": {}
+                         |  },
+                         |  "accounts" : {
+                         |    "0fe275bf13ff761407c17f64b1dfae2f4b3186feea223d7267b79f873a105401" : {
+                         |      "name" : "bob@james.org",
+                         |      "isPersonal" : true,
+                         |      "isReadOnly" : false,
+                         |      "accountCapabilities" : {
+                         |        "urn:ietf:params:jmap:core" : {
+                         |          "maxSizeUpload" : 10000000,
+                         |          "maxConcurrentUpload" : 4,
+                         |          "maxSizeRequest" : 10000000,
+                         |          "maxConcurrentRequests" : 4,
+                         |          "maxCallsInRequest" : 16,
+                         |          "maxObjectsInGet" : 500,
+                         |          "maxObjectsInSet" : 500,
+                         |          "collationAlgorithms" : [ "i;unicode-casemap" ]
+                         |        },
+                         |        "urn:ietf:params:jmap:mail" : {
+                         |          "maxMailboxesPerEmail" : 10000000,
+                         |          "maxMailboxDepth" : null,
+                         |          "maxSizeMailboxName" : 200,
+                         |          "maxSizeAttachmentsPerEmail" : 20000000,
+                         |          "emailQuerySortOptions" : [ "receivedAt", "cc", "from", "to", "subject", "size", "sentAt", "hasKeyword", "uid", "Id" ],
+                         |          "mayCreateTopLevelMailbox" : true
+                         |        },
+                         |        "urn:apache:james:params:jmap:mail:quota": {},
+                         |        "urn:apache:james:params:jmap:mail:shares": {}
+                         |      }
+                         |    }
+                         |  },
+                         |  "primaryAccounts" : {
+                         |    "urn:ietf:params:jmap:core" : "0fe275bf13ff761407c17f64b1dfae2f4b3186feea223d7267b79f873a105401",
+                         |    "urn:ietf:params:jmap:mail" : "0fe275bf13ff761407c17f64b1dfae2f4b3186feea223d7267b79f873a105401",
+                         |    "urn:apache:james:params:jmap:mail:quota": "0fe275bf13ff761407c17f64b1dfae2f4b3186feea223d7267b79f873a105401",
+                         |    "urn:apache:james:params:jmap:mail:shares": "0fe275bf13ff761407c17f64b1dfae2f4b3186feea223d7267b79f873a105401"
+                         |  },
+                         |  "username" : "bob@james.org",
+                         |  "apiUrl" : "http://this-url-is-hardcoded.org/jmap",
+                         |  "downloadUrl" : "http://this-url-is-hardcoded.org/download",
+                         |  "uploadUrl" : "http://this-url-is-hardcoded.org/upload",
+                         |  "eventSourceUrl" : "http://this-url-is-hardcoded.org/eventSource",
+                         |  "state" : "000001"
+                         |}""".stripMargin
 
-    Json.parse(sessionJson) should equal(expected_session_object_json)
+    Json.parse(sessionJson) should equal(Json.parse(expectedJson))
   }
 }
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/SessionSerializationTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/SessionSerializationTest.scala
index 5a418d8..6daaa45 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/SessionSerializationTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/SessionSerializationTest.scala
@@ -79,7 +79,7 @@ object SessionSerializationTest {
     emailQuerySortOptions = EMAIL_QUERY_SORT_OPTIONS,
     mayCreateTopLevelMailbox = MAY_CREATE_TOP_LEVEL_MAILBOX))
 
-  private val CAPABILITIES = Capabilities(CORE_CAPABILITY, MAIL_CAPABILITY)
+  private val CAPABILITIES = Capabilities(CORE_CAPABILITY, MAIL_CAPABILITY, QuotaCapability(), SharesCapability())
 
   private val IS_PERSONAL : IsPersonal = IsPersonal(true)
   private val IS_NOT_PERSONAL : IsPersonal = IsPersonal(false)
@@ -148,7 +148,9 @@ class SessionSerializationTest extends AnyWordSpec with Matchers {
           |      "maxSizeAttachmentsPerEmail": 890099,
           |      "emailQuerySortOptions": ["size"],
           |      "mayCreateTopLevelMailbox": true
-          |    }
+          |    },
+          |    "urn:apache:james:params:jmap:mail:quota":{},
+          |    "urn:apache:james:params:jmap:mail:shares":{}
           |  },
           |  "accounts": {
           |    "807a5306ccb4527af7790a0f9b48a776514bdbfba064e355461a76bcffbf2c90": {


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


[james-project] 11/12: [REFACTORING] Cleanup Text extractor overrides

Posted by bt...@apache.org.
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 039bdeab526af6d5849fa3ee29688988afda0ad4
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Jul 29 11:39:02 2020 +0700

    [REFACTORING] Cleanup Text extractor overrides
    
    Mosts are not needed and allows guice wiring simplifications and dependency cleanups
---
 server/container/cli-integration/pom.xml                       |  6 ------
 server/container/guice/cassandra-guice/pom.xml                 |  6 ------
 .../apache/james/AuthenticatedCassandraJamesServerTest.java    |  3 ---
 .../test/java/org/apache/james/CassandraJamesServerTest.java   |  3 ---
 .../java/org/apache/james/DefaultCassandraJamesServerTest.java |  3 ---
 server/container/guice/cassandra-rabbitmq-guice/pom.xml        |  6 ------
 server/container/guice/memory-guice/pom.xml                    |  6 ------
 .../src/test/java/org/apache/james/MemoryJamesServerTest.java  |  3 ---
 .../src/test/java/org/apache/james/MemoryJmapTestRule.java     |  6 ------
 .../james/jmap/memory/MemoryGetMessageListMethodTest.java      |  5 +----
 .../RabbitMQLinshareBlobExportMechanismIntegrationTest.java    |  3 ---
 .../webadmin-integration-test-common/pom.xml                   | 10 ----------
 12 files changed, 1 insertion(+), 59 deletions(-)

diff --git a/server/container/cli-integration/pom.xml b/server/container/cli-integration/pom.xml
index 8084fda..ccb62bf 100644
--- a/server/container/cli-integration/pom.xml
+++ b/server/container/cli-integration/pom.xml
@@ -33,12 +33,6 @@
     <dependencies>
         <dependency>
             <groupId>${james.groupId}</groupId>
-            <artifactId>apache-james-mailbox-scanning-search</artifactId>
-            <type>test-jar</type>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
             <artifactId>james-server-cli</artifactId>
             <scope>test</scope>
         </dependency>
diff --git a/server/container/guice/cassandra-guice/pom.xml b/server/container/guice/cassandra-guice/pom.xml
index 01c7a62..7413af1 100644
--- a/server/container/guice/cassandra-guice/pom.xml
+++ b/server/container/guice/cassandra-guice/pom.xml
@@ -81,12 +81,6 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
-            <artifactId>apache-james-mailbox-scanning-search</artifactId>
-            <type>test-jar</type>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
             <artifactId>james-server-mailbox-plugin-deleted-messages-vault-guice</artifactId>
         </dependency>
         <dependency>
diff --git a/server/container/guice/cassandra-guice/src/test/java/org/apache/james/AuthenticatedCassandraJamesServerTest.java b/server/container/guice/cassandra-guice/src/test/java/org/apache/james/AuthenticatedCassandraJamesServerTest.java
index 58a6b35..79a8049 100644
--- a/server/container/guice/cassandra-guice/src/test/java/org/apache/james/AuthenticatedCassandraJamesServerTest.java
+++ b/server/container/guice/cassandra-guice/src/test/java/org/apache/james/AuthenticatedCassandraJamesServerTest.java
@@ -24,8 +24,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import org.apache.james.backends.cassandra.DockerCassandra;
 import org.apache.james.backends.cassandra.init.configuration.ClusterConfiguration;
-import org.apache.james.mailbox.extractor.TextExtractor;
-import org.apache.james.mailbox.store.search.PDFTextExtractor;
 import org.apache.james.modules.TestJMAPServerModule;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
@@ -89,7 +87,6 @@ class AuthenticatedCassandraJamesServerTest {
             .extension(cassandraExtension)
             .disableAutoStart()
             .server(configuration -> CassandraJamesServerMain.createServer(configuration)
-                .overrideWith(binder -> binder.bind(TextExtractor.class).to(PDFTextExtractor.class))
                 .overrideWith(new TestJMAPServerModule())
                 .overrideWith(DOMAIN_LIST_CONFIGURATION_MODULE))
             .overrideServerModule(binder -> binder.bind(ClusterConfiguration.class)
diff --git a/server/container/guice/cassandra-guice/src/test/java/org/apache/james/CassandraJamesServerTest.java b/server/container/guice/cassandra-guice/src/test/java/org/apache/james/CassandraJamesServerTest.java
index 699ca01..d614d17 100644
--- a/server/container/guice/cassandra-guice/src/test/java/org/apache/james/CassandraJamesServerTest.java
+++ b/server/container/guice/cassandra-guice/src/test/java/org/apache/james/CassandraJamesServerTest.java
@@ -21,8 +21,6 @@ package org.apache.james;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
-import org.apache.james.mailbox.extractor.TextExtractor;
-import org.apache.james.mailbox.store.search.PDFTextExtractor;
 import org.apache.james.modules.ConfigurationProbe;
 import org.apache.james.modules.TestJMAPServerModule;
 import org.junit.jupiter.api.Test;
@@ -34,7 +32,6 @@ class CassandraJamesServerTest implements JamesServerContract {
         .extension(new DockerElasticSearchExtension())
         .extension(new CassandraExtension())
         .server(configuration -> CassandraJamesServerMain.createServer(configuration)
-            .overrideWith(binder -> binder.bind(TextExtractor.class).to(PDFTextExtractor.class))
             .overrideWith(new TestJMAPServerModule())
             .overrideWith(DOMAIN_LIST_CONFIGURATION_MODULE))
         .build();
diff --git a/server/container/guice/cassandra-guice/src/test/java/org/apache/james/DefaultCassandraJamesServerTest.java b/server/container/guice/cassandra-guice/src/test/java/org/apache/james/DefaultCassandraJamesServerTest.java
index 86b6de5..229654e 100644
--- a/server/container/guice/cassandra-guice/src/test/java/org/apache/james/DefaultCassandraJamesServerTest.java
+++ b/server/container/guice/cassandra-guice/src/test/java/org/apache/james/DefaultCassandraJamesServerTest.java
@@ -22,8 +22,6 @@ package org.apache.james;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
-import org.apache.james.mailbox.extractor.TextExtractor;
-import org.apache.james.mailbox.store.search.PDFTextExtractor;
 import org.apache.james.modules.TestJMAPServerModule;
 import org.apache.james.server.core.configuration.ConfigurationProvider;
 import org.apache.james.utils.FailingPropertiesProvider;
@@ -37,7 +35,6 @@ class DefaultCassandraJamesServerTest {
         .extension(new DockerElasticSearchExtension())
         .extension(new CassandraExtension())
         .server(configuration -> CassandraJamesServerMain.createServer(configuration)
-            .overrideWith(binder -> binder.bind(TextExtractor.class).to(PDFTextExtractor.class))
             .overrideWith(new TestJMAPServerModule())
             .overrideWith(binder -> binder.bind(PropertiesProvider.class).to(FailingPropertiesProvider.class))
             .overrideWith(binder -> binder.bind(ConfigurationProvider.class).toInstance((s, l) -> new BaseHierarchicalConfiguration())))
diff --git a/server/container/guice/cassandra-rabbitmq-guice/pom.xml b/server/container/guice/cassandra-rabbitmq-guice/pom.xml
index c0fefec..a4c6c5f 100644
--- a/server/container/guice/cassandra-rabbitmq-guice/pom.xml
+++ b/server/container/guice/cassandra-rabbitmq-guice/pom.xml
@@ -75,12 +75,6 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
-            <artifactId>apache-james-mailbox-scanning-search</artifactId>
-            <type>test-jar</type>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
             <artifactId>apache-james-mailbox-tika</artifactId>
             <type>test-jar</type>
             <scope>test</scope>
diff --git a/server/container/guice/memory-guice/pom.xml b/server/container/guice/memory-guice/pom.xml
index ebca896..3acfd83 100644
--- a/server/container/guice/memory-guice/pom.xml
+++ b/server/container/guice/memory-guice/pom.xml
@@ -51,12 +51,6 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
-            <artifactId>apache-james-mailbox-scanning-search</artifactId>
-            <type>test-jar</type>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
             <artifactId>apache-james-mailbox-quota-search-scanning</artifactId>
         </dependency>
         <dependency>
diff --git a/server/container/guice/memory-guice/src/test/java/org/apache/james/MemoryJamesServerTest.java b/server/container/guice/memory-guice/src/test/java/org/apache/james/MemoryJamesServerTest.java
index 2da10d6..0dde933 100644
--- a/server/container/guice/memory-guice/src/test/java/org/apache/james/MemoryJamesServerTest.java
+++ b/server/container/guice/memory-guice/src/test/java/org/apache/james/MemoryJamesServerTest.java
@@ -19,8 +19,6 @@
 
 package org.apache.james;
 
-import org.apache.james.mailbox.extractor.TextExtractor;
-import org.apache.james.mailbox.store.search.PDFTextExtractor;
 import org.apache.james.modules.TestJMAPServerModule;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
@@ -29,7 +27,6 @@ class MemoryJamesServerTest implements JamesServerContract {
     static JamesServerExtension jamesServerExtension = new JamesServerBuilder<>(JamesServerBuilder.defaultConfigurationProvider())
         .server(configuration -> MemoryJamesServerMain.createServer(configuration)
             .overrideWith(new TestJMAPServerModule())
-            .overrideWith(binder -> binder.bind(TextExtractor.class).to(PDFTextExtractor.class))
             .overrideWith(DOMAIN_LIST_CONFIGURATION_MODULE))
         .build();
 }
diff --git a/server/container/guice/memory-guice/src/test/java/org/apache/james/MemoryJmapTestRule.java b/server/container/guice/memory-guice/src/test/java/org/apache/james/MemoryJmapTestRule.java
index c1e2697..8e24907 100644
--- a/server/container/guice/memory-guice/src/test/java/org/apache/james/MemoryJmapTestRule.java
+++ b/server/container/guice/memory-guice/src/test/java/org/apache/james/MemoryJmapTestRule.java
@@ -21,10 +21,6 @@ package org.apache.james;
 
 import java.io.IOException;
 
-import org.apache.james.mailbox.extractor.TextExtractor;
-import org.apache.james.mailbox.store.search.MessageSearchIndex;
-import org.apache.james.mailbox.store.search.PDFTextExtractor;
-import org.apache.james.mailbox.store.search.SimpleMessageSearchIndex;
 import org.apache.james.modules.TestJMAPServerModule;
 import org.apache.james.server.core.configuration.Configuration;
 import org.junit.rules.TemporaryFolder;
@@ -45,8 +41,6 @@ public class MemoryJmapTestRule implements TestRule {
             .build();
         return MemoryJamesServerMain.createServer(configuration)
             .overrideWith(new TestJMAPServerModule())
-            .overrideWith(binder -> binder.bind(TextExtractor.class).to(PDFTextExtractor.class))
-            .overrideWith(binder -> binder.bind(MessageSearchIndex.class).to(SimpleMessageSearchIndex.class))
             .overrideWith(modules);
     }
 
diff --git a/server/protocols/jmap-draft-integration-testing/memory-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/memory/MemoryGetMessageListMethodTest.java b/server/protocols/jmap-draft-integration-testing/memory-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/memory/MemoryGetMessageListMethodTest.java
index f675373..fbb3055 100644
--- a/server/protocols/jmap-draft-integration-testing/memory-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/memory/MemoryGetMessageListMethodTest.java
+++ b/server/protocols/jmap-draft-integration-testing/memory-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/memory/MemoryGetMessageListMethodTest.java
@@ -27,9 +27,7 @@ import org.apache.james.GuiceJamesServer;
 import org.apache.james.MemoryJmapTestRule;
 import org.apache.james.jmap.draft.methods.integration.GetMessageListMethodTest;
 import org.apache.james.mailbox.extractor.TextExtractor;
-import org.apache.james.mailbox.store.search.MessageSearchIndex;
 import org.apache.james.mailbox.store.search.PDFTextExtractor;
-import org.apache.james.mailbox.store.search.SimpleMessageSearchIndex;
 import org.apache.james.modules.TestJMAPServerModule;
 import org.junit.Ignore;
 import org.junit.Rule;
@@ -43,8 +41,7 @@ public class MemoryGetMessageListMethodTest extends GetMessageListMethodTest {
     @Override
     protected GuiceJamesServer createJmapServer() throws IOException {
         return memoryJmap.jmapServer(TestJMAPServerModule.SearchModule.maximumMessages(LIMIT_TO_3_MESSAGES),
-            binder -> binder.bind(TextExtractor.class).to(PDFTextExtractor.class),
-            binder -> binder.bind(MessageSearchIndex.class).to(SimpleMessageSearchIndex.class));
+            binder -> binder.bind(TextExtractor.class).to(PDFTextExtractor.class));
     }
     
     @Override
diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/vault/RabbitMQLinshareBlobExportMechanismIntegrationTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/vault/RabbitMQLinshareBlobExportMechanismIntegrationTest.java
index 43136e1..bdd5521 100644
--- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/vault/RabbitMQLinshareBlobExportMechanismIntegrationTest.java
+++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/vault/RabbitMQLinshareBlobExportMechanismIntegrationTest.java
@@ -27,8 +27,6 @@ import org.apache.james.JamesServerBuilder;
 import org.apache.james.JamesServerExtension;
 import org.apache.james.SearchConfiguration;
 import org.apache.james.backends.rabbitmq.DockerRabbitMQSingleton;
-import org.apache.james.mailbox.extractor.TextExtractor;
-import org.apache.james.mailbox.store.search.PDFTextExtractor;
 import org.apache.james.modules.AwsS3BlobStoreExtension;
 import org.apache.james.modules.LinshareGuiceExtension;
 import org.apache.james.modules.RabbitMQExtension;
@@ -58,7 +56,6 @@ class RabbitMQLinshareBlobExportMechanismIntegrationTest extends LinshareBlobExp
         .extension(new AwsS3BlobStoreExtension())
         .extension(new LinshareGuiceExtension())
         .server(configuration -> CassandraRabbitMQJamesServerMain.createServer(configuration)
-            .overrideWith(binder -> binder.bind(TextExtractor.class).to(PDFTextExtractor.class))
             .overrideWith(new TestJMAPServerModule())
             .overrideWith(new TestRabbitMQModule(DockerRabbitMQSingleton.SINGLETON))
             .overrideWith(new WebadminIntegrationTestModule())
diff --git a/server/protocols/webadmin-integration-test/webadmin-integration-test-common/pom.xml b/server/protocols/webadmin-integration-test/webadmin-integration-test-common/pom.xml
index 43f31ca..36f1dfe 100644
--- a/server/protocols/webadmin-integration-test/webadmin-integration-test-common/pom.xml
+++ b/server/protocols/webadmin-integration-test/webadmin-integration-test-common/pom.xml
@@ -49,11 +49,6 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
-            <artifactId>apache-james-mailbox-scanning-search</artifactId>
-            <type>test-jar</type>
-        </dependency>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
             <artifactId>backup</artifactId>
             <type>test-jar</type>
         </dependency>
@@ -62,11 +57,6 @@
             <artifactId>blob-export-guice</artifactId>
             <type>test-jar</type>
         </dependency>
-        <!--<dependency>-->
-            <!--<groupId>${james.groupId}</groupId>-->
-            <!--<artifactId>james-server-cassandra-guice</artifactId>-->
-            <!--<type>test-jar</type>-->
-        <!--</dependency>-->
         <dependency>
             <groupId>${james.groupId}</groupId>
             <artifactId>james-server-guice-common</artifactId>


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


[james-project] 05/12: JAMES-3093 User and mailboxes provisioning for JMAP-RFC-8621

Posted by bt...@apache.org.
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 d2c314e5096058f04fadfbffee00732519d7949c
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Wed Jul 22 17:17:00 2020 +0700

    JAMES-3093 User and mailboxes provisioning for JMAP-RFC-8621
---
 .../distributed/DistributedProvisioningTest.java   | 53 ++++++++++++++
 .../james/jmap/rfc8621/contract/Fixture.scala      | 14 ++++
 .../contract/MailboxGetMethodContract.scala        | 14 ----
 .../rfc8621/contract/ProvisioningContract.scala    | 83 ++++++++++++++++++++++
 .../rfc8621/memory/MemoryProvisioningTest.java     | 38 ++++++++++
 .../apache/james/jmap/routes/JMAPApiRoutes.scala   | 16 +++--
 .../james/jmap/routes/JMAPApiRoutesTest.scala      | 14 ++--
 7 files changed, 209 insertions(+), 23 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/DistributedProvisioningTest.java b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedProvisioningTest.java
new file mode 100644
index 0000000..f86bad6
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedProvisioningTest.java
@@ -0,0 +1,53 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jmap.rfc8621.distributed;
+
+import org.apache.james.CassandraExtension;
+import org.apache.james.CassandraRabbitMQJamesConfiguration;
+import org.apache.james.CassandraRabbitMQJamesServerMain;
+import org.apache.james.DockerElasticSearchExtension;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.jmap.rfc8621.contract.ProvisioningContract;
+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;
+
+public class DistributedProvisioningTest implements ProvisioningContract {
+    @RegisterExtension
+    static JamesServerExtension testExtension = new JamesServerBuilder<CassandraRabbitMQJamesConfiguration>(tmpDir ->
+        CassandraRabbitMQJamesConfiguration.builder()
+            .workingDirectory(tmpDir)
+            .configurationFromClasspath()
+            .blobStore(BlobStoreConfiguration.builder()
+                .objectStorage()
+                .disableCache()
+                .deduplication())
+            .build())
+        .extension(new DockerElasticSearchExtension())
+        .extension(new CassandraExtension())
+        .extension(new RabbitMQExtension())
+        .extension(new AwsS3BlobStoreExtension())
+        .server(configuration -> CassandraRabbitMQJamesServerMain.createServer(configuration)
+            .overrideWith(new TestJMAPServerModule()))
+        .build();
+}
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/Fixture.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/Fixture.scala
index 799f8f9..7022a76 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/Fixture.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/Fixture.scala
@@ -134,4 +134,18 @@ object Fixture {
       "-yDYktd4WT8MYhqY7MgS-wR0vO9jZFv8ZCgd_MkKCvCO0HmMjP5iQPZ0kqGkgWUH7X123tfR38MfbCVAdPDba-K3MfkogV1xvDhlkPScFr_6MxE" +
       "xtedOK2JnQZn7t9sUzSrcyjWverm7gZkPptkIVoS8TsEeMMME5vFXe_nqkEG69q3kuBUm_33tbR5oNS0ZGZKlG9r41lHBjyf9J1xN4UYV8n866d" +
       "a7RPPCzshIWUtO0q9T2umWTnp-6OnOdBCkndrZmRR6pPxsD5YL0_77Wq8KT_5__fGA"
+
+  val GET_ALL_MAILBOXES_REQUEST: String =
+    """{
+      |  "using": [
+      |    "urn:ietf:params:jmap:core",
+      |    "urn:ietf:params:jmap:mail"],
+      |  "methodCalls": [[
+      |      "Mailbox/get",
+      |      {
+      |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+      |        "ids": null
+      |      },
+      |      "c1"]]
+      |}""".stripMargin
 }
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 6a255a2..3d1029f 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
@@ -52,20 +52,6 @@ object MailboxGetMethodContract {
   private val READ: String = Right.Read.asCharacter.toString
   private val ADMINISTER: String = Right.Administer.asCharacter.toString
 
-  private val GET_ALL_MAILBOXES_REQUEST: String =
-    """{
-    |  "using": [
-    |    "urn:ietf:params:jmap:core",
-    |    "urn:ietf:params:jmap:mail"],
-    |  "methodCalls": [[
-    |      "Mailbox/get",
-    |      {
-    |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-    |        "ids": null
-    |      },
-    |      "c1"]]
-    |}""".stripMargin
-
   private val GET_ALL_MAILBOXES_REQUEST_NULL_PROPERTIES: String =
     """{
     |  "using": [
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/ProvisioningContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/ProvisioningContract.scala
new file mode 100644
index 0000000..37a4091
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/ProvisioningContract.scala
@@ -0,0 +1,83 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jmap.rfc8621.contract
+
+import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
+import io.restassured.RestAssured.{`given`, _}
+import io.restassured.authentication.NoAuthScheme
+import io.restassured.http.Header
+import org.apache.http.HttpStatus.SC_OK
+import org.apache.james.GuiceJamesServer
+import org.apache.james.jmap.rfc8621.contract.Fixture._
+import org.apache.james.jmap.rfc8621.contract.tags.CategoryTags
+import org.apache.james.mailbox.DefaultMailboxes
+import org.apache.james.utils.DataProbeImpl
+import org.assertj.core.api.Assertions.assertThat
+import org.hamcrest.Matchers.{hasItems, hasSize}
+import org.junit.jupiter.api.{BeforeEach, Tag, Test}
+
+object ProvisioningContract {
+  private val ARGUMENTS: String = "methodResponses[0][1]"
+}
+
+trait ProvisioningContract {
+  import ProvisioningContract._
+
+  @BeforeEach
+  def setup(server: GuiceJamesServer): Unit = {
+    server.getProbe(classOf[DataProbeImpl])
+      .fluent
+      .addDomain(DOMAIN.asString)
+      .addUser(BOB.asString, BOB_PASSWORD)
+
+    requestSpecification = baseRequestSpecBuilder(server)
+      .setAuth(new NoAuthScheme())
+      .build
+  }
+
+  @Tag(CategoryTags.BASIC_FEATURE)
+  @Test
+  def provisionUserShouldAddMissingValidUser(server: GuiceJamesServer): Unit = {
+    `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .header(new Header(AUTHORIZATION_HEADER, s"Bearer $USER_TOKEN"))
+      .body(GET_ALL_MAILBOXES_REQUEST)
+    .when
+      .post
+
+    assertThat(server.getProbe(classOf[DataProbeImpl]).listUsers())
+      .contains(USER.asString())
+  }
+
+  @Tag(CategoryTags.BASIC_FEATURE)
+  @Test
+  def provisionMailboxesShouldCreateMissingMailboxes(): Unit = {
+    `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .header(BOB_BASIC_AUTH_HEADER)
+      .body(GET_ALL_MAILBOXES_REQUEST)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .body(s"$ARGUMENTS.list", hasSize(6))
+      .body(s"$ARGUMENTS.list.name", hasItems(DefaultMailboxes.DEFAULT_MAILBOXES.toArray:_*))
+  }
+}
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/MemoryProvisioningTest.java b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryProvisioningTest.java
new file mode 100644
index 0000000..42382b4
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryProvisioningTest.java
@@ -0,0 +1,38 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jmap.rfc8621.memory;
+
+import static org.apache.james.MemoryJamesServerMain.IN_MEMORY_SERVER_AGGREGATE_MODULE;
+
+import org.apache.james.GuiceJamesServer;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.jmap.rfc8621.contract.ProvisioningContract;
+import org.apache.james.modules.TestJMAPServerModule;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class MemoryProvisioningTest implements ProvisioningContract {
+    @RegisterExtension
+    static JamesServerExtension testExtension = new JamesServerBuilder<>(JamesServerBuilder.defaultConfigurationProvider())
+        .server(configuration -> GuiceJamesServer.forConfiguration(configuration)
+            .combineWith(IN_MEMORY_SERVER_AGGREGATE_MODULE)
+            .overrideWith(new TestJMAPServerModule()))
+        .build();
+}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
index 320c540..e39d96d 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
@@ -33,8 +33,8 @@ import org.apache.http.HttpStatus.SC_BAD_REQUEST
 import org.apache.james.jmap.HttpConstants.JSON_CONTENT_TYPE
 import org.apache.james.jmap.JMAPUrls.JMAP
 import org.apache.james.jmap.exceptions.UnauthorizedException
-import org.apache.james.jmap.http.Authenticator
 import org.apache.james.jmap.http.rfc8621.InjectionKeys
+import org.apache.james.jmap.http.{Authenticator, MailboxesProvisioner, UserProvisioning}
 import org.apache.james.jmap.json.Serializer
 import org.apache.james.jmap.method.Method
 import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
@@ -57,6 +57,8 @@ object JMAPApiRoutes {
 
 class JMAPApiRoutes (val authenticator: Authenticator,
                      serializer: Serializer,
+                     userProvisioner: UserProvisioning,
+                     mailboxesProvisioner: MailboxesProvisioner,
                      methods: Set[Method]) extends JMAPRoutes {
 
   private val methodsByName: Map[MethodName, Method] = methods.map(method => method.methodName -> method).toMap
@@ -64,8 +66,10 @@ class JMAPApiRoutes (val authenticator: Authenticator,
   @Inject
   def this(@Named(InjectionKeys.RFC_8621) authenticator: Authenticator,
            serializer: Serializer,
+           userProvisioner: UserProvisioning,
+           mailboxesProvisioner: MailboxesProvisioner,
            javaMethods: java.util.Set[Method]) {
-    this(authenticator, serializer, javaMethods.asScala.toSet)
+    this(authenticator, serializer, userProvisioner, mailboxesProvisioner, javaMethods.asScala.toSet)
   }
 
   override def routes(): stream.Stream[JMAPRoute] = Stream.of(
@@ -80,8 +84,12 @@ class JMAPApiRoutes (val authenticator: Authenticator,
 
   private def post(httpServerRequest: HttpServerRequest, httpServerResponse: HttpServerResponse): Mono[Void] =
     SMono(authenticator.authenticate(httpServerRequest))
-      .flatMap((mailboxSession: MailboxSession) => this.requestAsJsonStream(httpServerRequest)
-        .flatMap(requestObject => this.process(requestObject, httpServerResponse, mailboxSession)))
+      .flatMap((mailboxSession: MailboxSession) => SFlux.merge(Seq(
+          userProvisioner.provisionUser(mailboxSession),
+          mailboxesProvisioner.createMailboxesIfNeeded(mailboxSession)))
+        .`then`
+        .`then`(this.requestAsJsonStream(httpServerRequest)
+          .flatMap(requestObject => this.process(requestObject, httpServerResponse, mailboxSession))))
       .onErrorResume(throwable => handleError(throwable, httpServerResponse))
       .subscribeOn(Schedulers.elastic)
       .asJava()
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 05e4029..ccec3b8 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
@@ -35,15 +35,15 @@ import org.apache.james.dnsservice.api.DNSService
 import org.apache.james.domainlist.memory.MemoryDomainList
 import org.apache.james.jmap.JMAPUrls.JMAP
 import org.apache.james.jmap._
-import org.apache.james.jmap.http.{Authenticator, BasicAuthenticationStrategy}
+import org.apache.james.jmap.http.{Authenticator, BasicAuthenticationStrategy, MailboxesProvisioner, UserProvisioning}
 import org.apache.james.jmap.json.Serializer
 import org.apache.james.jmap.method.{CoreEchoMethod, Method}
 import org.apache.james.jmap.model.RequestLevelErrorType
 import org.apache.james.jmap.routes.JMAPApiRoutesTest._
-import org.apache.james.mailbox.MailboxManager
 import org.apache.james.mailbox.extension.PreDeletionHook
-import org.apache.james.mailbox.inmemory.MemoryMailboxManagerProvider
+import org.apache.james.mailbox.inmemory.{InMemoryMailboxManager, MemoryMailboxManagerProvider}
 import org.apache.james.mailbox.model.TestId
+import org.apache.james.mailbox.store.StoreSubscriptionManager
 import org.apache.james.metrics.tests.RecordingMetricFactory
 import org.apache.james.user.memory.MemoryUsersRepository
 import org.hamcrest.Matchers.equalTo
@@ -67,13 +67,17 @@ object JMAPApiRoutesTest {
   private val usersRepository = MemoryUsersRepository.withoutVirtualHosting(domainList)
   usersRepository.addUser(Username.of("user1"), "password")
 
-  private val mailboxManager: MailboxManager = MemoryMailboxManagerProvider.provideMailboxManager(empty_set)
+  private val mailboxManager: InMemoryMailboxManager = MemoryMailboxManagerProvider.provideMailboxManager(empty_set)
   private val authenticationStrategy: BasicAuthenticationStrategy = new BasicAuthenticationStrategy(usersRepository, mailboxManager)
   private val AUTHENTICATOR: Authenticator = Authenticator.of(new RecordingMetricFactory, authenticationStrategy)
 
+  private val userProvisionner: UserProvisioning = new UserProvisioning(usersRepository, new RecordingMetricFactory)
+  private val subscriptionManager: StoreSubscriptionManager = new StoreSubscriptionManager(mailboxManager.getMapperFactory)
+  private val mailboxesProvisioner: MailboxesProvisioner = new MailboxesProvisioner(mailboxManager, subscriptionManager, new RecordingMetricFactory)
+
   private val JMAP_METHODS: Set[Method] = Set(new CoreEchoMethod)
 
-  private val JMAP_API_ROUTE: JMAPApiRoutes = new JMAPApiRoutes(AUTHENTICATOR, SERIALIZER, JMAP_METHODS)
+  private val JMAP_API_ROUTE: JMAPApiRoutes = new JMAPApiRoutes(AUTHENTICATOR, SERIALIZER, userProvisionner, mailboxesProvisioner, JMAP_METHODS)
   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))


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


[james-project] 08/12: JAMES-3317 use Storage strategy from BlobStoreConfiguration when instantiating the blobstore

Posted by bt...@apache.org.
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 d6ac26ff738d4bef849fc4511a13ae50a6c6a71b
Author: RĂ©mi Kowalski <rk...@linagora.com>
AuthorDate: Wed Jul 22 16:05:29 2020 +0200

    JAMES-3317 use Storage strategy from BlobStoreConfiguration when instantiating the blobstore
---
 .../modules/blobstore/BlobStoreModulesChooser.java | 45 +++++++++++++++++-----
 src/site/xdoc/server/config-blobstore.xml          |  9 +++++
 2 files changed, 44 insertions(+), 10 deletions(-)

diff --git a/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreModulesChooser.java b/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreModulesChooser.java
index c0cdcc2..323f6c5 100644
--- a/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreModulesChooser.java
+++ b/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreModulesChooser.java
@@ -29,6 +29,8 @@ import org.apache.james.blob.objectstorage.ObjectStorageBlobStore;
 import org.apache.james.modules.mailbox.CassandraBlobStoreDependenciesModule;
 import org.apache.james.modules.objectstorage.ObjectStorageDependenciesModule;
 import org.apache.james.server.blob.deduplication.DeDuplicationBlobStore;
+import org.apache.james.server.blob.deduplication.PassThroughBlobStore;
+import org.apache.james.server.blob.deduplication.StorageStrategy;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
@@ -37,20 +39,16 @@ import com.google.inject.Module;
 import com.google.inject.name.Names;
 
 public class BlobStoreModulesChooser {
-    static class CassandraDeclarationModule extends AbstractModule {
+    static class CassandraDumbBlobStoreDeclarationModule extends AbstractModule {
         @Override
         protected void configure() {
             install(new CassandraBlobStoreDependenciesModule());
 
             bind(DumbBlobStore.class).to(CassandraDumbBlobStore.class);
-
-            bind(BlobStore.class)
-                .annotatedWith(Names.named(CachedBlobStore.BACKEND))
-                .to(DeDuplicationBlobStore.class);
         }
     }
 
-    static class ObjectStorageDeclarationModule extends AbstractModule {
+    static class ObjectStorageDumdBlobStoreDeclarationModule extends AbstractModule {
         @Override
         protected void configure() {
             install(new ObjectStorageDependenciesModule());
@@ -63,13 +61,40 @@ public class BlobStoreModulesChooser {
 
     @VisibleForTesting
     public static List<Module> chooseModules(BlobStoreConfiguration choosingConfiguration) {
-        switch (choosingConfiguration.getImplementation()) {
+        ImmutableList.Builder<Module> moduleBuilder = ImmutableList.<Module>builder().add(
+            chooseDumBlobStoreModule(choosingConfiguration.getImplementation()));
+
+        //TODO JAMES-3028 add the storage policy module for all implementation and unbind the ObjectStorageBlobStore
+        if (choosingConfiguration.getImplementation() == BlobStoreConfiguration.BlobStoreImplName.CASSANDRA) {
+            moduleBuilder.add(
+                chooseStoragePolicyModule(choosingConfiguration.storageStrategy()));
+        }
+        return moduleBuilder.build();
+    }
+
+    public static Module chooseDumBlobStoreModule(BlobStoreConfiguration.BlobStoreImplName implementation) {
+        switch (implementation) {
             case CASSANDRA:
-                return ImmutableList.of(new CassandraDeclarationModule());
+                return new CassandraDumbBlobStoreDeclarationModule();
             case OBJECTSTORAGE:
-                return ImmutableList.of(new ObjectStorageDeclarationModule());
+                return new ObjectStorageDumdBlobStoreDeclarationModule();
+            default:
+                throw new RuntimeException("Unsupported blobStore implementation " + implementation);
+        }
+    }
+
+    private static Module chooseStoragePolicyModule(StorageStrategy storageStrategy) {
+        switch (storageStrategy) {
+            case DEDUPLICATION:
+                return binder -> binder.bind(BlobStore.class)
+                    .annotatedWith(Names.named(CachedBlobStore.BACKEND))
+                    .to(DeDuplicationBlobStore.class);
+            case PASSTHROUGH:
+                return binder -> binder.bind(BlobStore.class)
+                    .annotatedWith(Names.named(CachedBlobStore.BACKEND))
+                    .to(PassThroughBlobStore.class);
             default:
-                throw new RuntimeException("Unsuported blobStore implementation " + choosingConfiguration.getImplementation());
+                throw new RuntimeException("Unknown storage strategy " + storageStrategy.name());
         }
     }
 }
diff --git a/src/site/xdoc/server/config-blobstore.xml b/src/site/xdoc/server/config-blobstore.xml
index 299d764..a6f1a1b 100644
--- a/src/site/xdoc/server/config-blobstore.xml
+++ b/src/site/xdoc/server/config-blobstore.xml
@@ -46,6 +46,15 @@
                 <dt><strong>implementation</strong></dt>
                 <dd>cassandra: use cassandra based BlobStore</dd>
                 <dd>objectstorage: use Swift/AWS S3 based BlobStore</dd>
+
+                <dt><strong>deduplication/enable</strong></dt>
+                <dd>Mandatory. Supported value: true and false.</dd>
+                <dd>If you choose to enable deduplication, the mails with the same content will be stored only once.</dd>
+                <dd>Warning: Once this feature is enabled, there is no turning back as turning it off will lead to the deletion of all</dd>
+                <dd>the mails sharing the same content once one is deleted.</dd>
+                <dd>This feature also requires a garbage collector mechanism to effectively drop blobs, which is not implemented yet.</dd>
+                <dd>Consequently, all the requested deletions will not be performed, meaning that blobstore will only grow.</dd>
+                <dd>Upgrade note: If you are upgrading from James 3.5 or older, the deduplication was enabled.</dd>
             </dl>
 
 


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


[james-project] 03/12: JAMES-3093 Port UserProvisioner from draft to jmap-rfc-8621

Posted by bt...@apache.org.
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 e1ae434403a20d977a4f1e46968975c6323fde14
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Tue Jul 21 16:56:03 2020 +0700

    JAMES-3093 Port UserProvisioner from draft to jmap-rfc-8621
---
 .../apache/james/jmap/http/UserProvisioning.scala  |  64 +++++++++++++
 .../james/jmap/http/UserProvisioningTest.scala     | 103 +++++++++++++++++++++
 2 files changed, 167 insertions(+)

diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/UserProvisioning.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/UserProvisioning.scala
new file mode 100644
index 0000000..95895e5
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/UserProvisioning.scala
@@ -0,0 +1,64 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jmap.http
+
+import java.util.UUID
+
+import javax.inject.Inject
+import org.apache.james.core.Username
+import org.apache.james.mailbox.MailboxSession
+import org.apache.james.metrics.api.MetricFactory
+import org.apache.james.metrics.api.TimeMetric.ExecutionResult.DEFAULT_100_MS_THRESHOLD
+import org.apache.james.user.api.{AlreadyExistInUsersRepositoryException, UsersRepository, UsersRepositoryException}
+import reactor.core.scala.publisher.SMono
+
+class UserProvisioning @Inject() (usersRepository: UsersRepository, metricFactory: MetricFactory) {
+
+  def provisionUser(session: MailboxSession): SMono[Unit] =
+    if (session != null && !usersRepository.isReadOnly) {
+      SMono.fromCallable(() => createAccountIfNeeded(session))
+        .`then`
+    } else {
+      SMono.empty
+    }
+
+  private def createAccountIfNeeded(session: MailboxSession): Unit = {
+    val timeMetric = metricFactory.timer("JMAP-RFC-8621-user-provisioning")
+    try {
+      val username = session.getUser
+      if (needsAccountCreation(username)) {
+        createAccount(username)
+      }
+    } catch {
+      case exception: AlreadyExistInUsersRepositoryException => // Ignore
+      case exception: UsersRepositoryException => throw new RuntimeException(exception)
+    } finally {
+      timeMetric.stopAndPublish.logWhenExceedP99(DEFAULT_100_MS_THRESHOLD)
+    }
+  }
+
+  @throws[UsersRepositoryException]
+  private def createAccount(username: Username): Unit = usersRepository.addUser(username, generatePassword)
+
+  @throws[UsersRepositoryException]
+  private def needsAccountCreation(username: Username): Boolean = !usersRepository.contains(username)
+
+  private def generatePassword: String = UUID.randomUUID.toString
+}
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/UserProvisioningTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/UserProvisioningTest.scala
new file mode 100644
index 0000000..ff799a0
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/UserProvisioningTest.scala
@@ -0,0 +1,103 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jmap.http
+
+import java.time.Duration
+
+import org.apache.james.core.Username
+import org.apache.james.domainlist.api.DomainList
+import org.apache.james.mailbox.{MailboxSession, MailboxSessionUtil}
+import org.apache.james.metrics.tests.RecordingMetricFactory
+import org.apache.james.user.api.{UsersRepository, UsersRepositoryException}
+import org.apache.james.user.memory.MemoryUsersRepository
+import org.apache.james.util.concurrency.ConcurrentTestRunner
+import org.assertj.core.api.Assertions.{assertThat, assertThatThrownBy}
+import org.junit.jupiter.api.{BeforeEach, Test}
+import org.mockito.Mockito.{mock, verify, verifyNoMoreInteractions, when}
+
+object UserProvisioningTest {
+  private val USERNAME: Username = Username.of("username")
+  private val USERNAME_WITH_DOMAIN: Username = Username.of("username@james.org")
+  private val NO_DOMAIN_LIST: DomainList = null
+}
+
+class UserProvisioningTest {
+  import UserProvisioningTest._
+
+  var testee: UserProvisioning = _
+  var usersRepository: MemoryUsersRepository = _
+
+  @BeforeEach
+  def setup(): Unit = {
+    usersRepository = MemoryUsersRepository.withoutVirtualHosting(NO_DOMAIN_LIST)
+    testee = new UserProvisioning(usersRepository, new RecordingMetricFactory)
+  }
+
+  @Test
+  def filterShouldDoNothingOnNullSession(): Unit = {
+    testee.provisionUser(null).block()
+
+    assertThat(usersRepository.list).toIterable
+      .isEmpty
+  }
+
+  @Test
+  def filterShouldAddUsernameWhenNoVirtualHostingAndMailboxSessionContainsUsername(): Unit = {
+    usersRepository.setEnableVirtualHosting(false)
+    val mailboxSession: MailboxSession = MailboxSessionUtil.create(USERNAME)
+
+    testee.provisionUser(mailboxSession).block()
+
+    assertThat(usersRepository.list).toIterable
+      .contains(USERNAME)
+  }
+
+  @Test
+  def filterShouldFailOnInvalidVirtualHosting(): Unit = {
+    usersRepository.setEnableVirtualHosting(false)
+    val mailboxSession: MailboxSession = MailboxSessionUtil.create(USERNAME_WITH_DOMAIN)
+
+    assertThatThrownBy(() => testee.provisionUser(mailboxSession).block())
+      .hasCauseInstanceOf(classOf[UsersRepositoryException])
+  }
+
+  @Test
+  def filterShouldNotTryToAddUserWhenReadOnlyUsersRepository(): Unit = {
+    val usersRepository: UsersRepository = mock(classOf[UsersRepository])
+    when(usersRepository.isReadOnly).thenReturn(true)
+    testee = new UserProvisioning(usersRepository, new RecordingMetricFactory)
+
+    val mailboxSession: MailboxSession = MailboxSessionUtil.create(USERNAME_WITH_DOMAIN)
+    testee.provisionUser(mailboxSession).block()
+
+    verify(usersRepository).isReadOnly
+    verifyNoMoreInteractions(usersRepository)
+  }
+
+  @Test
+  def testConcurrentAccessToFilterShouldNotThrow(): Unit = {
+    val session: MailboxSession = MailboxSessionUtil.create(USERNAME)
+
+    ConcurrentTestRunner.builder
+      .operation((threadNumber: Int, step: Int) => testee.provisionUser(session))
+      .threadCount(2)
+      .runSuccessfullyWithin(Duration.ofMinutes(1))
+  }
+}


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


[james-project] 06/12: JAMES-3093 Refactoring MailboxGetMethodContract with mailboxes provisioning

Posted by bt...@apache.org.
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 029fc19ea6abafbd774537733859100721299c40
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Fri Jul 24 11:39:35 2020 +0700

    JAMES-3093 Refactoring MailboxGetMethodContract with mailboxes provisioning
---
 .../contract/MailboxGetMethodContract.scala        | 563 ++++++++++-----------
 1 file changed, 256 insertions(+), 307 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/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 3d1029f..19b2e37 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
@@ -43,6 +43,8 @@ import org.apache.james.utils.DataProbeImpl
 import org.hamcrest.Matchers._
 import org.junit.jupiter.api.{BeforeEach, Tag, Test}
 
+import scala.jdk.CollectionConverters._
+
 object MailboxGetMethodContract {
   private val ARGUMENTS: String = "methodResponses[0][1]"
   private val FIRST_MAILBOX: String = ARGUMENTS + ".list[0]"
@@ -51,143 +53,6 @@ object MailboxGetMethodContract {
   private val LOOKUP: String = Right.Lookup.asCharacter.toString
   private val READ: String = Right.Read.asCharacter.toString
   private val ADMINISTER: String = Right.Administer.asCharacter.toString
-
-  private val GET_ALL_MAILBOXES_REQUEST_NULL_PROPERTIES: String =
-    """{
-    |  "using": [
-    |    "urn:ietf:params:jmap:core",
-    |    "urn:ietf:params:jmap:mail"],
-    |  "methodCalls": [[
-    |      "Mailbox/get",
-    |      {
-    |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-    |        "properties": null,
-    |        "ids": null
-    |      },
-    |      "c1"]]
-    |}""".stripMargin
-
-  private val GET_ALL_MAILBOXES_REQUEST_EMPTY_PROPERTIES: String =
-    """{
-    |  "using": [
-    |    "urn:ietf:params:jmap:core",
-    |    "urn:ietf:params:jmap:mail"],
-    |  "methodCalls": [[
-    |      "Mailbox/get",
-    |      {
-    |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-    |        "properties": [],
-    |        "ids": null
-    |      },
-    |      "c1"]]
-    |}""".stripMargin
-
-  private val GET_ALL_MAILBOXES_REQUEST_NAME_AND_ID_PROPERTIES: String =
-    """{
-    |  "using": [
-    |    "urn:ietf:params:jmap:core",
-    |    "urn:ietf:params:jmap:mail"],
-    |  "methodCalls": [[
-    |      "Mailbox/get",
-    |      {
-    |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-    |        "properties": ["id", "name"],
-    |        "ids": null
-    |      },
-    |      "c1"]]
-    |}""".stripMargin
-
-  private val GET_ALL_MAILBOXES_REQUEST_NAME_PROPERTIES: String =
-    """{
-    |  "using": [
-    |    "urn:ietf:params:jmap:core",
-    |    "urn:ietf:params:jmap:mail"],
-    |  "methodCalls": [[
-    |      "Mailbox/get",
-    |      {
-    |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-    |        "properties": ["name"],
-    |        "ids": null
-    |      },
-    |      "c1"]]
-    |}""".stripMargin
-
-  private val GET_ALL_MAILBOXES_REQUEST_INVALID_PROPERTIES: String =
-    """{
-    |  "using": [
-    |    "urn:ietf:params:jmap:core",
-    |    "urn:ietf:params:jmap:mail"],
-    |  "methodCalls": [[
-    |      "Mailbox/get",
-    |      {
-    |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-    |        "properties": ["invalidProperty"],
-    |        "ids": null
-    |      },
-    |      "c1"]]
-    |}""".stripMargin
-
-  private val GET_ALL_MAILBOXES_REQUEST_WITH_QUOTA: String =
-    """{
-    |  "using": [
-    |    "urn:ietf:params:jmap:core",
-    |    "urn:ietf:params:jmap:mail",
-    |    "urn:apache:james:params:jmap:mail:quota"],
-    |  "methodCalls": [[
-    |      "Mailbox/get",
-    |      {
-    |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-    |        "ids": null
-    |      },
-    |      "c1"]]
-    |}""".stripMargin
-
-  private val GET_ALL_MAILBOXES_REQUEST_WITH_SHARES: String =
-    """{
-    |  "using": [
-    |    "urn:ietf:params:jmap:core",
-    |    "urn:ietf:params:jmap:mail",
-    |    "urn:apache:james:params:jmap:mail:shares"],
-    |  "methodCalls": [[
-    |      "Mailbox/get",
-    |      {
-    |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-    |        "ids": null
-    |      },
-    |      "c1"]]
-    |}""".stripMargin
-
-  private val GET_ALL_MAILBOXES_REQUEST_WITH_SHARES_WITH_ONLY_ID_NAME_AND_RIGHTS: String =
-    """{
-      |  "using": [
-      |    "urn:ietf:params:jmap:core",
-      |    "urn:ietf:params:jmap:mail",
-      |    "urn:apache:james:params:jmap:mail:shares"],
-      |  "methodCalls": [[
-      |      "Mailbox/get",
-      |      {
-      |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-      |        "properties": ["id", "name", "rights"],
-      |        "ids": null
-      |      },
-      |      "c1"]]
-      |}""".stripMargin
-
-  private val GET_ALL_MAILBOXES_REQUEST_WITH_BOTH: String =
-    """{
-    |  "using": [
-    |    "urn:ietf:params:jmap:core",
-    |    "urn:ietf:params:jmap:mail",
-    |    "urn:apache:james:params:jmap:mail:quota",
-    |    "urn:apache:james:params:jmap:mail:shares"],
-    |  "methodCalls": [[
-    |      "Mailbox/get",
-    |      {
-    |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-    |        "ids": null
-    |      },
-    |      "c1"]]
-    |}""".stripMargin
 }
 
 trait MailboxGetMethodContract {
@@ -209,61 +74,6 @@ trait MailboxGetMethodContract {
   }
 
   @Test
-  def getMailboxesShouldReturnExistingMailbox(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(GET_ALL_MAILBOXES_REQUEST)
-    .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
-         |        }
-         |      ],
-         |      "notFound": []
-         |    },
-         |    "c1"]]
-         |}""".stripMargin)
-  }
-
-  @Test
   def getMailboxesShouldIncludeRightsAndNamespaceIfSharesCapabilityIsUsed(server: GuiceJamesServer): Unit = {
     val mailboxId: String = server.getProbe(classOf[MailboxProbeImpl])
       .createMailbox(MailboxPath.forUser(BOB, "custom"))
@@ -271,7 +81,19 @@ trait MailboxGetMethodContract {
 
     val response: String = `given`
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(GET_ALL_MAILBOXES_REQUEST_WITH_SHARES)
+      .body(s"""{
+               |  "using": [
+               |    "urn:ietf:params:jmap:core",
+               |    "urn:ietf:params:jmap:mail",
+               |    "urn:apache:james:params:jmap:mail:shares"],
+               |  "methodCalls": [[
+               |    "Mailbox/get",
+               |    {
+               |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+               |      "ids": ["${mailboxId}"]
+               |    },
+               |    "c1"]]
+               |}""".stripMargin)
     .when
       .post
     .`then`
@@ -328,7 +150,19 @@ trait MailboxGetMethodContract {
 
     val response: String = `given`
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(GET_ALL_MAILBOXES_REQUEST_WITH_QUOTA)
+      .body(s"""{
+               |  "using": [
+               |    "urn:ietf:params:jmap:core",
+               |    "urn:ietf:params:jmap:mail",
+               |    "urn:apache:james:params:jmap:mail:quota"],
+               |  "methodCalls": [[
+               |    "Mailbox/get",
+               |    {
+               |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+               |      "ids": ["${mailboxId}"]
+               |    },
+               |    "c1"]]
+               |}""".stripMargin)
     .when
       .post
     .`then`
@@ -389,7 +223,20 @@ trait MailboxGetMethodContract {
 
     val response: String = `given`
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(GET_ALL_MAILBOXES_REQUEST_WITH_BOTH)
+      .body(s"""{
+               |  "using": [
+               |    "urn:ietf:params:jmap:core",
+               |    "urn:ietf:params:jmap:mail",
+               |    "urn:apache:james:params:jmap:mail:shares",
+               |    "urn:apache:james:params:jmap:mail:quota"],
+               |  "methodCalls": [[
+               |    "Mailbox/get",
+               |    {
+               |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+               |      "ids": ["${mailboxId}"]
+               |    },
+               |    "c1"]]
+               |}""".stripMargin)
     .when
       .post
     .`then`
@@ -445,61 +292,6 @@ trait MailboxGetMethodContract {
   }
 
   @Test
-  def getMailboxesShouldReturnAllPropertiesWhenNotSupplied(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(GET_ALL_MAILBOXES_REQUEST)
-    .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
-         |        }
-         |      ],
-         |      "notFound": []
-         |    },
-         |    "c1"]]
-         |}""".stripMargin)
-  }
-
-  @Test
   def getMailboxesShouldReturnAllPropertiesWhenNull(server: GuiceJamesServer): Unit = {
     val mailboxId: String = server.getProbe(classOf[MailboxProbeImpl])
       .createMailbox(MailboxPath.forUser(BOB, "custom"))
@@ -507,7 +299,19 @@ trait MailboxGetMethodContract {
 
     val response: String = `given`
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(GET_ALL_MAILBOXES_REQUEST_NULL_PROPERTIES)
+      .body(s"""{
+               |  "using": [
+               |    "urn:ietf:params:jmap:core",
+               |    "urn:ietf:params:jmap:mail"],
+               |  "methodCalls": [[
+               |    "Mailbox/get",
+               |    {
+               |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+               |      "properties": null,
+               |      "ids": ["${mailboxId}"]
+               |    },
+               |    "c1"]]
+               |}""".stripMargin)
     .when
       .post
     .`then`
@@ -562,7 +366,19 @@ trait MailboxGetMethodContract {
 
     val response: String = `given`
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(GET_ALL_MAILBOXES_REQUEST_EMPTY_PROPERTIES)
+      .body(s"""{
+               |  "using": [
+               |    "urn:ietf:params:jmap:core",
+               |    "urn:ietf:params:jmap:mail"],
+               |  "methodCalls": [[
+               |    "Mailbox/get",
+               |    {
+               |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+               |      "properties": [],
+               |      "ids": ["${mailboxId}"]
+               |    },
+               |    "c1"]]
+               |}""".stripMargin)
     .when
       .post
     .`then`
@@ -590,6 +406,7 @@ trait MailboxGetMethodContract {
          |    "c1"]]
          |}""".stripMargin)
   }
+
   @Test
   def getMailboxesShouldReturnOnlyNameAndIdWhenPropertiesRequested(server: GuiceJamesServer): Unit = {
     val mailboxId: String = server.getProbe(classOf[MailboxProbeImpl])
@@ -598,7 +415,19 @@ trait MailboxGetMethodContract {
 
     val response: String = `given`
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(GET_ALL_MAILBOXES_REQUEST_NAME_AND_ID_PROPERTIES)
+      .body(s"""{
+               |  "using": [
+               |    "urn:ietf:params:jmap:core",
+               |    "urn:ietf:params:jmap:mail"],
+               |  "methodCalls": [[
+               |    "Mailbox/get",
+               |    {
+               |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+               |      "properties": ["id", "name"],
+               |      "ids": ["${mailboxId}"]
+               |    },
+               |    "c1"]]
+               |}""".stripMargin)
     .when
       .post
     .`then`
@@ -636,7 +465,19 @@ trait MailboxGetMethodContract {
 
     val response: String = `given`
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(GET_ALL_MAILBOXES_REQUEST_NAME_PROPERTIES)
+      .body(s"""{
+               |  "using": [
+               |    "urn:ietf:params:jmap:core",
+               |    "urn:ietf:params:jmap:mail"],
+               |  "methodCalls": [[
+               |    "Mailbox/get",
+               |    {
+               |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+               |      "properties": ["name"],
+               |      "ids": ["${mailboxId}"]
+               |    },
+               |    "c1"]]
+               |}""".stripMargin)
     .when
       .post
     .`then`
@@ -674,7 +515,20 @@ trait MailboxGetMethodContract {
 
     val response: String = `given`
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(GET_ALL_MAILBOXES_REQUEST_WITH_SHARES_WITH_ONLY_ID_NAME_AND_RIGHTS)
+      .body(s"""{
+               |  "using": [
+               |    "urn:ietf:params:jmap:core",
+               |    "urn:ietf:params:jmap:mail",
+               |    "urn:apache:james:params:jmap:mail:shares"],
+               |  "methodCalls": [[
+               |    "Mailbox/get",
+               |    {
+               |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+               |      "properties": ["id", "name", "rights"],
+               |      "ids": ["${mailboxId}"]
+               |    },
+               |    "c1"]]
+               |}""".stripMargin)
     .when
       .post
     .`then`
@@ -707,12 +561,25 @@ trait MailboxGetMethodContract {
 
   @Test
   def getMailboxesShouldReturnInvalidArgumentsErrorWhenInvalidProperty(server: GuiceJamesServer): Unit = {
-    server.getProbe(classOf[MailboxProbeImpl])
+    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(GET_ALL_MAILBOXES_REQUEST_INVALID_PROPERTIES)
+      .body(s"""{
+              |  "using": [
+              |    "urn:ietf:params:jmap:core",
+              |    "urn:ietf:params:jmap:mail"],
+              |  "methodCalls": [[
+              |      "Mailbox/get",
+              |      {
+              |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+              |        "properties": ["invalidProperty"],
+              |        "ids": ["${mailboxId}"]
+              |      },
+              |      "c1"]]
+              |}""".stripMargin)
     .when
       .post
     .`then`
@@ -736,29 +603,13 @@ trait MailboxGetMethodContract {
 
   @Test
   @Tag(CategoryTags.BASIC_FEATURE)
-  def getMailboxesShouldReturnEmptyWhenNone(): Unit = {
-    `given`
-      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(GET_ALL_MAILBOXES_REQUEST)
-    .when
-      .post
-    .`then`
-      .statusCode(SC_OK)
-      .body(s"$ARGUMENTS.list", empty)
-  }
-
-  @Test
-  @Tag(CategoryTags.BASIC_FEATURE)
   def getMailboxesShouldReturnAllExistingMailboxes(server: GuiceJamesServer): Unit = {
-    val firstMailboxName: String = "custom"
-    val mailboxId1: String = server.getProbe(classOf[MailboxProbeImpl])
-      .createMailbox(MailboxPath.forUser(BOB, firstMailboxName))
+    val customMailbox: String = "custom"
+    server.getProbe(classOf[MailboxProbeImpl])
+      .createMailbox(MailboxPath.forUser(BOB, customMailbox))
       .serialize
 
-    val secondMailboxName: String = "othercustom"
-    val mailboxId2: String = server.getProbe(classOf[MailboxProbeImpl])
-      .createMailbox(MailboxPath.forUser(BOB, secondMailboxName))
-      .serialize
+    val expectedList = DefaultMailboxes.DEFAULT_MAILBOXES.asScala ++ List(customMailbox)
 
     `given`
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
@@ -767,21 +618,15 @@ trait MailboxGetMethodContract {
       .post
     .`then`
       .statusCode(SC_OK)
-      .body(s"$ARGUMENTS.list", hasSize(2))
-      .body(s"$FIRST_MAILBOX.id", equalTo(mailboxId1))
-      .body(s"$FIRST_MAILBOX.name", equalTo(firstMailboxName))
-      .body(s"$SECOND_MAILBOX.id", equalTo(mailboxId2))
-      .body(s"$SECOND_MAILBOX.name", equalTo(secondMailboxName))
+      .body(s"$ARGUMENTS.list", hasSize(7))
+      .body(s"$ARGUMENTS.list.name", hasItems(expectedList.toArray:_*))
   }
 
   @Test
   def getMailboxesShouldReturnOnlyMailboxesOfCurrentUser(server: GuiceJamesServer): Unit = {
-    val mailboxName: String = "custom"
-    val mailboxId: String = server.getProbe(classOf[MailboxProbeImpl])
-      .createMailbox(MailboxPath.forUser(BOB, "custom"))
-      .serialize
+    val andreMailbox: String = "andrecustom"
     server.getProbe(classOf[MailboxProbeImpl])
-      .createMailbox(MailboxPath.forUser(ANDRE, "andrecustom"))
+      .createMailbox(MailboxPath.forUser(ANDRE, andreMailbox))
 
     `given`
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
@@ -790,9 +635,9 @@ trait MailboxGetMethodContract {
       .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.list", hasSize(6))
+      .body(s"$ARGUMENTS.list.name", hasItems(DefaultMailboxes.DEFAULT_MAILBOXES.toArray:_*))
+      .body(s"$ARGUMENTS.list.name", not(hasItem(andreMailbox)))
   }
 
   @Test
@@ -800,7 +645,7 @@ trait MailboxGetMethodContract {
     val targetUser1: String = "touser1@" + DOMAIN.asString
     val targetUser2: String = "touser2@" + DOMAIN.asString
     val mailboxName: String = "myMailbox"
-    server.getProbe(classOf[MailboxProbeImpl])
+    val mailboxId: String = server.getProbe(classOf[MailboxProbeImpl])
       .createMailbox(MailboxPath.forUser(BOB, mailboxName))
       .serialize
 
@@ -811,7 +656,19 @@ trait MailboxGetMethodContract {
 
     `given`
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(GET_ALL_MAILBOXES_REQUEST_WITH_SHARES)
+      .body(s"""{
+               |  "using": [
+               |    "urn:ietf:params:jmap:core",
+               |    "urn:ietf:params:jmap:mail",
+               |    "urn:apache:james:params:jmap:mail:shares"],
+               |  "methodCalls": [[
+               |      "Mailbox/get",
+               |      {
+               |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+               |        "ids": ["${mailboxId}"]
+               |      },
+               |      "c1"]]
+               |}""".stripMargin)
     .when
       .post
     .`then`
@@ -827,7 +684,7 @@ trait MailboxGetMethodContract {
   def getMailboxesShouldReturnDelegatedNamespaceWhenSharedMailbox(server: GuiceJamesServer): Unit = {
     val sharedMailboxName = "AndreShared"
     val andreMailboxPath = MailboxPath.forUser(ANDRE, sharedMailboxName)
-    server.getProbe(classOf[MailboxProbeImpl])
+    val mailboxId: String = server.getProbe(classOf[MailboxProbeImpl])
       .createMailbox(andreMailboxPath)
       .serialize
 
@@ -836,7 +693,19 @@ trait MailboxGetMethodContract {
 
     `given`
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(GET_ALL_MAILBOXES_REQUEST_WITH_SHARES)
+      .body(s"""{
+               |  "using": [
+               |    "urn:ietf:params:jmap:core",
+               |    "urn:ietf:params:jmap:mail",
+               |    "urn:apache:james:params:jmap:mail:shares"],
+               |  "methodCalls": [[
+               |      "Mailbox/get",
+               |      {
+               |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+               |        "ids": ["${mailboxId}"]
+               |      },
+               |      "c1"]]
+               |}""".stripMargin)
     .when
       .post
     .`then`
@@ -853,7 +722,7 @@ trait MailboxGetMethodContract {
     val toUser1: String = "touser1@" + DOMAIN.asString
     val sharedMailboxName: String = "AndreShared"
     val andreMailboxPath: MailboxPath = MailboxPath.forUser(ANDRE, sharedMailboxName)
-    server.getProbe(classOf[MailboxProbeImpl])
+    val mailboxId: String = server.getProbe(classOf[MailboxProbeImpl])
       .createMailbox(andreMailboxPath)
       .serialize
 
@@ -864,7 +733,19 @@ trait MailboxGetMethodContract {
 
     `given`
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(GET_ALL_MAILBOXES_REQUEST_WITH_SHARES)
+      .body(s"""{
+               |  "using": [
+               |    "urn:ietf:params:jmap:core",
+               |    "urn:ietf:params:jmap:mail",
+               |    "urn:apache:james:params:jmap:mail:shares"],
+               |  "methodCalls": [[
+               |      "Mailbox/get",
+               |      {
+               |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+               |        "ids": ["${mailboxId}"]
+               |      },
+               |      "c1"]]
+               |}""".stripMargin)
     .when
       .post
     .`then`
@@ -880,7 +761,7 @@ trait MailboxGetMethodContract {
     val toUser1: String = "touser1@" + DOMAIN.asString
     val sharedMailboxName: String = "AndreShared"
     val andreMailboxPath: MailboxPath = MailboxPath.forUser(ANDRE, sharedMailboxName)
-    server.getProbe(classOf[MailboxProbeImpl])
+    val mailboxId: String = server.getProbe(classOf[MailboxProbeImpl])
       .createMailbox(andreMailboxPath)
       .serialize
 
@@ -891,7 +772,18 @@ trait MailboxGetMethodContract {
 
     `given`
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(GET_ALL_MAILBOXES_REQUEST)
+      .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`
@@ -913,7 +805,7 @@ trait MailboxGetMethodContract {
   @Tag(CategoryTags.BASIC_FEATURE)
   def getMailboxesShouldNotReturnInboxRoleToShareeWhenDelegatedInbox(server: GuiceJamesServer): Unit = {
     val andreMailboxPath = MailboxPath.forUser(ANDRE, DefaultMailboxes.INBOX)
-    server.getProbe(classOf[MailboxProbeImpl])
+    val mailboxId: String = server.getProbe(classOf[MailboxProbeImpl])
       .createMailbox(andreMailboxPath)
       .serialize
 
@@ -922,7 +814,18 @@ trait MailboxGetMethodContract {
 
     `given`
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(GET_ALL_MAILBOXES_REQUEST)
+      .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`
@@ -935,13 +838,24 @@ trait MailboxGetMethodContract {
 
   @Test
   def getMailboxesShouldReturnCorrectMailboxRole(server: GuiceJamesServer): Unit = {
-    server.getProbe(classOf[MailboxProbeImpl])
+    val mailboxId: String = server.getProbe(classOf[MailboxProbeImpl])
       .createMailbox(MailboxPath.forUser(BOB, DefaultMailboxes.INBOX))
       .serialize
 
     `given`
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(GET_ALL_MAILBOXES_REQUEST)
+      .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`
@@ -961,7 +875,7 @@ trait MailboxGetMethodContract {
       .setBody("testmail", StandardCharsets.UTF_8)
       .build
 
-    server.getProbe(classOf[MailboxProbeImpl])
+    val mailboxId: String = server.getProbe(classOf[MailboxProbeImpl])
       .createMailbox(MailboxPath.forUser(BOB, DefaultMailboxes.INBOX))
       .serialize
 
@@ -975,7 +889,19 @@ trait MailboxGetMethodContract {
 
     `given`
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(GET_ALL_MAILBOXES_REQUEST_WITH_QUOTA)
+      .body(s"""{
+               |  "using": [
+               |    "urn:ietf:params:jmap:core",
+               |    "urn:ietf:params:jmap:mail",
+               |    "urn:apache:james:params:jmap:mail:quota"],
+               |  "methodCalls": [[
+               |      "Mailbox/get",
+               |      {
+               |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+               |        "ids": ["${mailboxId}"]
+               |      },
+               |      "c1"]]
+               |}""".stripMargin)
     .when
       .post
     .`then`
@@ -995,13 +921,25 @@ trait MailboxGetMethodContract {
     server.getProbe(classOf[QuotaProbesImpl])
       .setGlobalMaxMessageCount(QuotaCountLimit.count(31))
 
-    server.getProbe(classOf[MailboxProbeImpl])
+    val mailboxId: String = server.getProbe(classOf[MailboxProbeImpl])
       .createMailbox(MailboxPath.forUser(BOB, DefaultMailboxes.INBOX))
       .serialize
 
     `given`
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(GET_ALL_MAILBOXES_REQUEST_WITH_QUOTA)
+      .body(s"""{
+               |  "using": [
+               |    "urn:ietf:params:jmap:core",
+               |    "urn:ietf:params:jmap:mail",
+               |    "urn:apache:james:params:jmap:mail:quota"],
+               |  "methodCalls": [[
+               |      "Mailbox/get",
+               |      {
+               |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+               |        "ids": ["${mailboxId}"]
+               |      },
+               |      "c1"]]
+               |}""".stripMargin)
     .when
       .post
     .`then`
@@ -1023,7 +961,18 @@ trait MailboxGetMethodContract {
 
     `given`
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(GET_ALL_MAILBOXES_REQUEST)
+      .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`


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


[james-project] 07/12: JAMES-3317 add deduplication propertie for blobstore in configuration

Posted by bt...@apache.org.
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 986002b275ee7c3dc689c85dcd17c5db9f8503e8
Author: RĂ©mi Kowalski <rk...@linagora.com>
AuthorDate: Wed Jul 22 15:51:50 2020 +0200

    JAMES-3317 add deduplication propertie for blobstore in configuration
---
 .../destination/conf/blob.properties               |  9 ++-
 .../destination/conf/blob.properties               |  7 ++
 .../destination/conf/blob.properties               |  7 ++
 .../cassandra/destination/conf/blob.properties     |  9 ++-
 .../modules/blobstore/BlobStoreConfiguration.java  | 87 +++++++++++++++++-----
 .../james/CassandraRabbitMQAwsS3JmapTestRule.java  |  5 +-
 .../james/CassandraRabbitMQJamesServerFixture.java |  5 +-
 .../james/CassandraRabbitMQSwiftJmapTestRule.java  |  5 +-
 .../java/org/apache/james/WithCacheExtension.java  |  5 +-
 .../org/apache/james/WithCassandraBlobStore.java   |  5 +-
 .../apache/james/WithScanningSearchExtension.java  |  5 +-
 .../BlobStoreCacheModulesChooserTest.java          | 10 ++-
 .../blobstore/BlobStoreConfigurationTest.java      | 63 ++++++++++++++--
 .../blobstore/BlobStoreModulesChooserTest.java     | 14 +++-
 .../CassandraRabbitMQLdapJmapJamesServerTest.java  | 15 +++-
 .../rabbitmq/RabbitMQAwsS3SendMDNMethodTest.java   |  5 +-
 .../RabbitMQAwsS3SpamAssassinContractTest.java     |  5 +-
 .../cucumber/awss3/RabbitMQAwsS3Stepdefs.java      |  5 +-
 .../distributed/DistributedAuthenticationTest.java |  5 +-
 .../distributed/DistributedEchoMethodTest.java     |  5 +-
 .../DistributedMailboxGetMethodTest.java           |  5 +-
 .../distributed/DistributedSessionRouteTest.java   |  2 +-
 .../rabbitmq/ConsistencyTasksIntegrationTest.java  |  5 +-
 .../rabbitmq/FixingGhostMailboxTest.java           |  5 +-
 .../rabbitmq/RabbitMQAuthorizedEndpointsTest.java  |  5 +-
 .../RabbitMQEventDeadLettersIntegrationTest.java   |  5 +-
 ...stViewProjectionHealthCheckIntegrationTest.java |  5 +-
 .../rabbitmq/RabbitMQForwardIntegrationTest.java   |  5 +-
 .../rabbitmq/RabbitMQJmapExtension.java            |  5 +-
 .../rabbitmq/RabbitMQJwtFilterIntegrationTest.java |  5 +-
 ...RabbitMQReindexingWithEventDeadLettersTest.java |  5 +-
 .../RabbitMQWebAdminServerIntegrationTest.java     |  5 +-
 ...dminServerTaskSerializationIntegrationTest.java |  5 +-
 ...RabbitMQDeletedMessageVaultIntegrationTest.java |  5 +-
 ...LinshareBlobExportMechanismIntegrationTest.java |  5 +-
 35 files changed, 287 insertions(+), 61 deletions(-)

diff --git a/dockerfiles/run/guice/cassandra-ldap/destination/conf/blob.properties b/dockerfiles/run/guice/cassandra-ldap/destination/conf/blob.properties
index 90c5135..2ec4035 100644
--- a/dockerfiles/run/guice/cassandra-ldap/destination/conf/blob.properties
+++ b/dockerfiles/run/guice/cassandra-ldap/destination/conf/blob.properties
@@ -6,6 +6,13 @@
 # Optional, default is localFile
 blob.export.implementation=localFile
 
+# ========================================= ObjectStorage deduplication ========================================
+# If you choose to enable deduplication, the mails with the same content will be stored only once.
+# Warning: Once this feature is enabled, there is no turning back as turning it off will lead to the deletion of all
+# the mails sharing the same content once one is deleted.
+# Mandatory, Allowed values are: true, false
+deduplication.enable=false
+
 # ======================================= Local File Blobs Exporting ========================================
 # Optional, directory to store exported blob, directory path follows James file system format
 # default is file://var/blobExporting
@@ -23,4 +30,4 @@ blob.export.localFile.directory=file://var/blobExporting
 # For Example: It will be formalized to 'Authorization: Basic {Credential of UUID/password}'
 
 # blob.export.linshare.technical.account.uuid=Technical_Account_UUID
-# blob.export.linshare.technical.account.password=password
\ No newline at end of file
+# blob.export.linshare.technical.account.password=password
diff --git a/dockerfiles/run/guice/cassandra-rabbitmq-ldap/destination/conf/blob.properties b/dockerfiles/run/guice/cassandra-rabbitmq-ldap/destination/conf/blob.properties
index 40aee03..6e5e8f8 100644
--- a/dockerfiles/run/guice/cassandra-rabbitmq-ldap/destination/conf/blob.properties
+++ b/dockerfiles/run/guice/cassandra-rabbitmq-ldap/destination/conf/blob.properties
@@ -5,6 +5,13 @@
 # Mandatory, allowed values are: cassandra, objectstorage
 implementation=objectstorage
 
+# ========================================= ObjectStorage deduplication ========================================
+# If you choose to enable deduplication, the mails with the same content will be stored only once.
+# Warning: Once this feature is enabled, there is no turning back as turning it off will lead to the deletion of all
+# the mails sharing the same content once one is deleted.
+# Mandatory, Allowed values are: true, false
+deduplication.enable=false
+
 # ========================================= Cassandra BlobStore Cache ======================================
 # A cassandra cache can be enabled to reduce latency when reading small blobs frequently
 # A dedicated keyspace with a replication factor of one is then used
diff --git a/dockerfiles/run/guice/cassandra-rabbitmq/destination/conf/blob.properties b/dockerfiles/run/guice/cassandra-rabbitmq/destination/conf/blob.properties
index 8c98913..c579d80 100644
--- a/dockerfiles/run/guice/cassandra-rabbitmq/destination/conf/blob.properties
+++ b/dockerfiles/run/guice/cassandra-rabbitmq/destination/conf/blob.properties
@@ -5,6 +5,13 @@
 # Mandatory, allowed values are: cassandra, objectstorage
 implementation=objectstorage
 
+# ========================================= ObjectStorage deduplication ========================================
+# If you choose to enable deduplication, the mails with the same content will be stored only once.
+# Warning: Once this feature is enabled, there is no turning back as turning it off will lead to the deletion of all
+# the mails sharing the same content once one is deleted.
+# Mandatory, Allowed values are: true, false
+deduplication.enable=false
+
 # ========================================= Cassandra BlobStore Cache ======================================
 # A cassandra cache can be enabled to reduce latency when reading small blobs frequently
 # A dedicated keyspace with a replication factor of one is then used
diff --git a/dockerfiles/run/guice/cassandra/destination/conf/blob.properties b/dockerfiles/run/guice/cassandra/destination/conf/blob.properties
index 90c5135..2ec4035 100644
--- a/dockerfiles/run/guice/cassandra/destination/conf/blob.properties
+++ b/dockerfiles/run/guice/cassandra/destination/conf/blob.properties
@@ -6,6 +6,13 @@
 # Optional, default is localFile
 blob.export.implementation=localFile
 
+# ========================================= ObjectStorage deduplication ========================================
+# If you choose to enable deduplication, the mails with the same content will be stored only once.
+# Warning: Once this feature is enabled, there is no turning back as turning it off will lead to the deletion of all
+# the mails sharing the same content once one is deleted.
+# Mandatory, Allowed values are: true, false
+deduplication.enable=false
+
 # ======================================= Local File Blobs Exporting ========================================
 # Optional, directory to store exported blob, directory path follows James file system format
 # default is file://var/blobExporting
@@ -23,4 +30,4 @@ blob.export.localFile.directory=file://var/blobExporting
 # For Example: It will be formalized to 'Authorization: Basic {Credential of UUID/password}'
 
 # blob.export.linshare.technical.account.uuid=Technical_Account_UUID
-# blob.export.linshare.technical.account.password=password
\ No newline at end of file
+# blob.export.linshare.technical.account.password=password
diff --git a/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreConfiguration.java b/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreConfiguration.java
index 789813b..7507794 100644
--- a/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreConfiguration.java
+++ b/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreConfiguration.java
@@ -29,32 +29,63 @@ import org.apache.commons.configuration2.Configuration;
 import org.apache.commons.configuration2.ex.ConfigurationException;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.james.modules.mailbox.ConfigurationComponent;
+import org.apache.james.server.blob.deduplication.StorageStrategy;
 import org.apache.james.server.core.filesystem.FileSystemImpl;
 import org.apache.james.utils.PropertiesProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.MoreObjects;
 
+import io.vavr.control.Try;
+
 public class BlobStoreConfiguration {
     private static final Logger LOGGER = LoggerFactory.getLogger(BlobStoreConfiguration.class);
 
-    public static class CacheChoice {
-        private final  BlobStoreImplName implementation;
+    @FunctionalInterface
+    public interface RequireImplementation {
+        RequireCache implementation(BlobStoreImplName implementation);
+
+        default RequireCache cassandra() {
+            return implementation(BlobStoreImplName.CASSANDRA);
+        }
+
+        default RequireCache objectStorage() {
+            return implementation(BlobStoreImplName.OBJECTSTORAGE);
+        }
+    }
+
+    @FunctionalInterface
+    public interface RequireCache {
+        RequireStoringStrategy enableCache(boolean enable);
+
+        default RequireStoringStrategy enableCache() {
+            return enableCache(CACHE_ENABLED);
+        }
 
-        private CacheChoice(BlobStoreImplName implementation) {
-            this.implementation = implementation;
+        default RequireStoringStrategy disableCache() {
+            return enableCache(!CACHE_ENABLED);
         }
+    }
+
+    @FunctionalInterface
+    public interface RequireStoringStrategy {
+        BlobStoreConfiguration strategy(StorageStrategy storageStrategy);
 
-        public BlobStoreConfiguration enableCache() {
-            return new BlobStoreConfiguration(implementation, CACHE_ENABLED);
+        default BlobStoreConfiguration passthrough() {
+            return strategy(StorageStrategy.PASSTHROUGH);
         }
 
-        public BlobStoreConfiguration disableCache() {
-            return new BlobStoreConfiguration(implementation, !CACHE_ENABLED);
+        default BlobStoreConfiguration deduplication() {
+            return strategy(StorageStrategy.DEDUPLICATION);
         }
     }
 
+    public static RequireImplementation builder() {
+        return implementation -> enableCache -> storageStrategy -> new BlobStoreConfiguration(implementation, enableCache, storageStrategy);
+    }
+
     public enum BlobStoreImplName {
         CASSANDRA("cassandra"),
         OBJECTSTORAGE("objectstorage");
@@ -87,6 +118,7 @@ public class BlobStoreConfiguration {
     static final String BLOBSTORE_IMPLEMENTATION_PROPERTY = "implementation";
     static final String CACHE_ENABLE_PROPERTY = "cache.enable";
     static final boolean CACHE_ENABLED = true;
+    static final String DEDUPLICATION_ENABLE_PROPERTY = "deduplication.enable";
 
     public static BlobStoreConfiguration parse(org.apache.james.server.core.configuration.Configuration configuration) throws ConfigurationException {
         PropertiesProvider propertiesProvider = new PropertiesProvider(new FileSystemImpl(configuration.directories()),
@@ -101,7 +133,10 @@ public class BlobStoreConfiguration {
             return BlobStoreConfiguration.from(configuration);
         } catch (FileNotFoundException e) {
             LOGGER.warn("Could not find " + ConfigurationComponent.NAME + " configuration file, using cassandra blobstore as the default");
-            return BlobStoreConfiguration.cassandra();
+            return BlobStoreConfiguration.builder()
+                    .cassandra()
+                    .disableCache()
+                    .passthrough();
         }
     }
 
@@ -114,30 +149,44 @@ public class BlobStoreConfiguration {
                 "supported values in: %s", BLOBSTORE_IMPLEMENTATION_PROPERTY, BlobStoreImplName.supportedImplNames())));
 
         boolean cacheEnabled = configuration.getBoolean(CACHE_ENABLE_PROPERTY, false);
-
-        return new BlobStoreConfiguration(blobStoreImplName, cacheEnabled);
+        boolean deduplicationEnabled = Try.ofCallable(() -> configuration.getBoolean(DEDUPLICATION_ENABLE_PROPERTY))
+                .getOrElseThrow(() -> new IllegalStateException("deduplication.enable property is missing please use one of the supported values in: true, false\n" +
+                        "If you choose to enable deduplication, the mails with the same content will be stored only once.\n" +
+                        "Warning: Once this feature is enabled, there is no turning back as turning it off will lead to the deletion of all\n" +
+                        "the mails sharing the same content once one is deleted.\n" +
+                        "Upgrade note: If you are upgrading from James 3.5 or older, the deduplication was enabled."));
+
+        if (deduplicationEnabled) {
+            return new BlobStoreConfiguration(blobStoreImplName, cacheEnabled, StorageStrategy.DEDUPLICATION);
+        } else {
+            return new BlobStoreConfiguration(blobStoreImplName, cacheEnabled, StorageStrategy.PASSTHROUGH);
+        }
     }
 
+    @VisibleForTesting
     public static BlobStoreConfiguration cassandra() {
-        return new BlobStoreConfiguration(BlobStoreImplName.CASSANDRA, !CACHE_ENABLED);
+        return new BlobStoreConfiguration(BlobStoreImplName.CASSANDRA, !CACHE_ENABLED, StorageStrategy.PASSTHROUGH);
     }
 
-    public static CacheChoice objectStorage() {
-        return new CacheChoice(BlobStoreImplName.OBJECTSTORAGE);
-    }
 
     private final BlobStoreImplName implementation;
     private final boolean cacheEnabled;
+    private final StorageStrategy storageStrategy;
 
-    BlobStoreConfiguration(BlobStoreImplName implementation, boolean cacheEnabled) {
+    BlobStoreConfiguration(BlobStoreImplName implementation, boolean cacheEnabled, StorageStrategy storageStrategy) {
         this.implementation = implementation;
         this.cacheEnabled = cacheEnabled;
+        this.storageStrategy = storageStrategy;
     }
 
     public boolean cacheEnabled() {
         return cacheEnabled;
     }
 
+    public StorageStrategy storageStrategy() {
+        return storageStrategy;
+    }
+
     BlobStoreImplName getImplementation() {
         return implementation;
     }
@@ -148,14 +197,15 @@ public class BlobStoreConfiguration {
             BlobStoreConfiguration that = (BlobStoreConfiguration) o;
 
             return Objects.equals(this.implementation, that.implementation)
-                && Objects.equals(this.cacheEnabled, that.cacheEnabled);
+                && Objects.equals(this.cacheEnabled, that.cacheEnabled)
+                && Objects.equals(this.storageStrategy, that.storageStrategy);
         }
         return false;
     }
 
     @Override
     public final int hashCode() {
-        return Objects.hash(implementation, cacheEnabled);
+        return Objects.hash(implementation, cacheEnabled, storageStrategy);
     }
 
     @Override
@@ -163,6 +213,7 @@ public class BlobStoreConfiguration {
         return MoreObjects.toStringHelper(this)
             .add("implementation", implementation)
             .add("cacheEnabled", cacheEnabled)
+            .add("storageStrategy", storageStrategy.name())
             .toString();
     }
 }
diff --git a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/CassandraRabbitMQAwsS3JmapTestRule.java b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/CassandraRabbitMQAwsS3JmapTestRule.java
index 0fe2807..9986b37 100644
--- a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/CassandraRabbitMQAwsS3JmapTestRule.java
+++ b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/CassandraRabbitMQAwsS3JmapTestRule.java
@@ -62,7 +62,10 @@ public class CassandraRabbitMQAwsS3JmapTestRule implements TestRule {
         CassandraRabbitMQJamesConfiguration configuration = CassandraRabbitMQJamesConfiguration.builder()
             .workingDirectory(temporaryFolder.newFolder())
             .configurationFromClasspath()
-            .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+            .blobStore(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
             .searchConfiguration(SearchConfiguration.elasticSearch())
             .build();
 
diff --git a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/CassandraRabbitMQJamesServerFixture.java b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/CassandraRabbitMQJamesServerFixture.java
index 264faac..b9878db 100644
--- a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/CassandraRabbitMQJamesServerFixture.java
+++ b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/CassandraRabbitMQJamesServerFixture.java
@@ -40,7 +40,10 @@ public class CassandraRabbitMQJamesServerFixture {
             CassandraRabbitMQJamesConfiguration.builder()
                 .workingDirectory(tmpDir)
                 .configurationFromClasspath()
-                .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+                .blobStore(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
                 .searchConfiguration(SearchConfiguration.elasticSearch())
                 .build())
             .extension(new DockerElasticSearchExtension())
diff --git a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/CassandraRabbitMQSwiftJmapTestRule.java b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/CassandraRabbitMQSwiftJmapTestRule.java
index ae2891a..c359987 100644
--- a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/CassandraRabbitMQSwiftJmapTestRule.java
+++ b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/CassandraRabbitMQSwiftJmapTestRule.java
@@ -61,7 +61,10 @@ public class CassandraRabbitMQSwiftJmapTestRule implements TestRule {
         CassandraRabbitMQJamesConfiguration configuration = CassandraRabbitMQJamesConfiguration.builder()
             .workingDirectory(temporaryFolder.newFolder())
             .configurationFromClasspath()
-            .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+            .blobStore(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
             .searchConfiguration(SearchConfiguration.elasticSearch())
             .build();
 
diff --git a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/WithCacheExtension.java b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/WithCacheExtension.java
index 0df288c..a971e3e 100644
--- a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/WithCacheExtension.java
+++ b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/WithCacheExtension.java
@@ -42,7 +42,10 @@ public class WithCacheExtension implements BeforeAllCallback, AfterAllCallback,
             CassandraRabbitMQJamesConfiguration.builder()
                 .workingDirectory(tmpDir)
                 .configurationFromClasspath()
-                .blobStore(BlobStoreConfiguration.objectStorage().enableCache())
+                .blobStore(BlobStoreConfiguration.builder()
+                        .objectStorage()
+                        .enableCache()
+                        .deduplication())
                 .searchConfiguration(SearchConfiguration.elasticSearch())
                 .build())
             .extension(new DockerElasticSearchExtension())
diff --git a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/WithCassandraBlobStore.java b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/WithCassandraBlobStore.java
index 36f9889..98dd3a6 100644
--- a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/WithCassandraBlobStore.java
+++ b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/WithCassandraBlobStore.java
@@ -41,7 +41,10 @@ public class WithCassandraBlobStore implements BeforeAllCallback, AfterAllCallba
             CassandraRabbitMQJamesConfiguration.builder()
                 .workingDirectory(tmpDir)
                 .configurationFromClasspath()
-                .blobStore(BlobStoreConfiguration.cassandra())
+                .blobStore(BlobStoreConfiguration.builder()
+                        .cassandra()
+                        .disableCache()
+                        .passthrough())
                 .searchConfiguration(SearchConfiguration.elasticSearch())
                 .build())
             .extension(new DockerElasticSearchExtension())
diff --git a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/WithScanningSearchExtension.java b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/WithScanningSearchExtension.java
index 01088b7..ddeaabe 100644
--- a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/WithScanningSearchExtension.java
+++ b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/WithScanningSearchExtension.java
@@ -42,7 +42,10 @@ public class WithScanningSearchExtension implements BeforeAllCallback, AfterAllC
             CassandraRabbitMQJamesConfiguration.builder()
                 .workingDirectory(tmpDir)
                 .configurationFromClasspath()
-                .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+                .blobStore(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
                 .searchConfiguration(SearchConfiguration.scanning())
                 .build())
             .extension(new CassandraExtension())
diff --git a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreCacheModulesChooserTest.java b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreCacheModulesChooserTest.java
index 5cb8c50..03fc807 100644
--- a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreCacheModulesChooserTest.java
+++ b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreCacheModulesChooserTest.java
@@ -27,7 +27,10 @@ import org.junit.jupiter.api.Test;
 class BlobStoreCacheModulesChooserTest {
     @Test
     void chooseModulesShouldReturnCacheDisabledModuleWhenCacheDisabled() {
-        assertThat(BlobStoreCacheModulesChooser.chooseModules(BlobStoreConfiguration.objectStorage().disableCache()))
+        assertThat(BlobStoreCacheModulesChooser.chooseModules(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication()))
             .hasSize(1)
             .first()
             .isInstanceOf(BlobStoreCacheModulesChooser.CacheDisabledModule.class);
@@ -35,7 +38,10 @@ class BlobStoreCacheModulesChooserTest {
 
     @Test
     void chooseModulesShouldReturnCacheEnabledAndCassandraCacheModulesWhenCacheEnabled() {
-        assertThat(BlobStoreCacheModulesChooser.chooseModules(BlobStoreConfiguration.objectStorage().enableCache()))
+        assertThat(BlobStoreCacheModulesChooser.chooseModules(BlobStoreConfiguration.builder()
+                .objectStorage()
+                .enableCache()
+                .deduplication()))
             .hasSize(2)
             .allSatisfy(module ->
                 assertThat(module).isOfAnyClassIn(
diff --git a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreConfigurationTest.java b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreConfigurationTest.java
index e870fb7..731d6a7 100644
--- a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreConfigurationTest.java
+++ b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreConfigurationTest.java
@@ -26,6 +26,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import org.apache.commons.configuration2.PropertiesConfiguration;
 import org.apache.james.FakePropertiesProvider;
 import org.apache.james.modules.mailbox.ConfigurationComponent;
+import org.apache.james.server.blob.deduplication.StorageStrategy;
 import org.junit.jupiter.api.Test;
 
 import nl.jqno.equalsverifier.EqualsVerifier;
@@ -84,34 +85,44 @@ class BlobStoreConfigurationTest {
             .build();
 
         assertThat(parse(propertyProvider))
-            .isEqualTo(BlobStoreConfiguration.cassandra());
+            .isEqualTo(BlobStoreConfiguration.builder()
+                    .cassandra()
+                    .disableCache()
+                    .passthrough());
     }
 
     @Test
     void provideChoosingConfigurationShouldReturnObjectStorageFactoryWhenConfigurationImplIsObjectStorage() throws Exception {
         PropertiesConfiguration configuration = new PropertiesConfiguration();
         configuration.addProperty("implementation", BlobStoreConfiguration.BlobStoreImplName.OBJECTSTORAGE.getName());
+        configuration.addProperty("deduplication.enable", "true");
         FakePropertiesProvider propertyProvider = FakePropertiesProvider.builder()
             .register(ConfigurationComponent.NAME, configuration)
             .build();
 
         assertThat(parse(propertyProvider))
-            .isEqualTo(BlobStoreConfiguration.objectStorage().disableCache());
+            .isEqualTo(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication());
     }
 
     @Test
     void provideChoosingConfigurationShouldReturnCassandraFactoryWhenConfigurationImplIsCassandra() throws Exception {
         PropertiesConfiguration configuration = new PropertiesConfiguration();
         configuration.addProperty("implementation", BlobStoreConfiguration.BlobStoreImplName.CASSANDRA.getName());
+        configuration.addProperty("deduplication.enable", "false");
         FakePropertiesProvider propertyProvider = FakePropertiesProvider.builder()
             .register(ConfigurationComponent.NAME, configuration)
             .build();
 
         assertThat(parse(propertyProvider))
-            .isEqualTo(BlobStoreConfiguration.cassandra());
+            .isEqualTo(BlobStoreConfiguration.builder()
+                    .cassandra()
+                    .disableCache()
+                    .passthrough());
     }
 
-
     @Test
     void fromShouldThrowWhenBlobStoreImplIsMissing() {
         PropertiesConfiguration configuration = new PropertiesConfiguration();
@@ -155,6 +166,7 @@ class BlobStoreConfigurationTest {
     void fromShouldReturnConfigurationWhenBlobStoreImplIsCassandra() {
         PropertiesConfiguration configuration = new PropertiesConfiguration();
         configuration.addProperty("implementation", CASSANDRA);
+        configuration.addProperty("deduplication.enable", "false");
 
         assertThat(
             BlobStoreConfiguration.from(configuration)
@@ -167,7 +179,7 @@ class BlobStoreConfigurationTest {
     void fromShouldReturnConfigurationWhenBlobStoreImplIsObjectStorage() {
         PropertiesConfiguration configuration = new PropertiesConfiguration();
         configuration.addProperty("implementation", OBJECT_STORAGE);
-
+        configuration.addProperty("deduplication.enable", "true");
         assertThat(
             BlobStoreConfiguration.from(configuration)
                 .getImplementation()
@@ -179,6 +191,7 @@ class BlobStoreConfigurationTest {
     void fromShouldReturnConfigurationWhenBlobStoreImplIsSupportedAndCaseInsensitive() {
         PropertiesConfiguration configuration = new PropertiesConfiguration();
         configuration.addProperty("implementation", "OBjecTStorAGE");
+        configuration.addProperty("deduplication.enable", "true");
 
         assertThat(
             BlobStoreConfiguration.from(configuration)
@@ -191,6 +204,7 @@ class BlobStoreConfigurationTest {
     void fromShouldReturnConfigurationWhenBlobStoreImplIsSupportedAndHasExtraSpaces() {
         PropertiesConfiguration configuration = new PropertiesConfiguration();
         configuration.addProperty("implementation", " cassandra ");
+        configuration.addProperty("deduplication.enable", "false");
 
         assertThat(
             BlobStoreConfiguration.from(configuration)
@@ -204,6 +218,7 @@ class BlobStoreConfigurationTest {
         PropertiesConfiguration configuration = new PropertiesConfiguration();
         configuration.addProperty("implementation", BlobStoreConfiguration.BlobStoreImplName.OBJECTSTORAGE.getName());
         configuration.addProperty("cache.enable", true);
+        configuration.addProperty("deduplication.enable", "true");
 
         assertThat(BlobStoreConfiguration.from(configuration).cacheEnabled())
             .isTrue();
@@ -214,6 +229,7 @@ class BlobStoreConfigurationTest {
         PropertiesConfiguration configuration = new PropertiesConfiguration();
         configuration.addProperty("implementation", BlobStoreConfiguration.BlobStoreImplName.OBJECTSTORAGE.getName());
         configuration.addProperty("cache.enable", false);
+        configuration.addProperty("deduplication.enable", "true");
 
         assertThat(BlobStoreConfiguration.from(configuration).cacheEnabled())
             .isFalse();
@@ -223,8 +239,43 @@ class BlobStoreConfigurationTest {
     void cacheEnabledShouldDefaultToFalse() {
         PropertiesConfiguration configuration = new PropertiesConfiguration();
         configuration.addProperty("implementation", BlobStoreConfiguration.BlobStoreImplName.OBJECTSTORAGE.getName());
+        configuration.addProperty("deduplication.enable", "true");
 
         assertThat(BlobStoreConfiguration.from(configuration).cacheEnabled())
             .isFalse();
     }
-}
\ No newline at end of file
+
+    @Test
+    void storageStrategyShouldBePassthroughWhenDeduplicationDisabled() {
+        PropertiesConfiguration configuration = new PropertiesConfiguration();
+        configuration.addProperty("implementation", BlobStoreConfiguration.BlobStoreImplName.OBJECTSTORAGE.getName());
+        configuration.addProperty("deduplication.enable", "false");
+
+        assertThat(BlobStoreConfiguration.from(configuration).storageStrategy())
+            .isEqualTo(StorageStrategy.PASSTHROUGH);
+    }
+
+    @Test
+    void storageStrategyShouldBeDeduplicationWhenDeduplicationEnabled() {
+        PropertiesConfiguration configuration = new PropertiesConfiguration();
+        configuration.addProperty("implementation", BlobStoreConfiguration.BlobStoreImplName.OBJECTSTORAGE.getName());
+        configuration.addProperty("deduplication.enable", "true");
+
+        assertThat(BlobStoreConfiguration.from(configuration).storageStrategy())
+                .isEqualTo(StorageStrategy.DEDUPLICATION);
+    }
+
+    @Test
+    void buildingConfigurationShouldThrowWhenDeduplicationPropertieIsOmitted() {
+        PropertiesConfiguration configuration = new PropertiesConfiguration();
+        configuration.addProperty("implementation", BlobStoreConfiguration.BlobStoreImplName.OBJECTSTORAGE.getName());
+
+        assertThatThrownBy(() -> BlobStoreConfiguration.from(configuration)).isInstanceOf(IllegalStateException.class)
+                .hasMessage("deduplication.enable property is missing please use one of the supported values in: true, false\n" +
+         "If you choose to enable deduplication, the mails with the same content will be stored only once.\n" +
+         "Warning: Once this feature is enabled, there is no turning back as turning it off will lead to the deletion of all\n" +
+         "the mails sharing the same content once one is deleted.\n" +
+        "Upgrade note: If you are upgrading from James 3.5 or older, the deduplication was enabled.");
+    }
+
+}
diff --git a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreModulesChooserTest.java b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreModulesChooserTest.java
index da99509..65735b5 100644
--- a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreModulesChooserTest.java
+++ b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreModulesChooserTest.java
@@ -27,15 +27,21 @@ class BlobStoreModulesChooserTest {
 
     @Test
     void provideBlobStoreShouldReturnObjectStoreBlobStoreWhenObjectStoreConfigured() {
-        assertThat(BlobStoreModulesChooser.chooseModules(BlobStoreConfiguration.objectStorage().disableCache()))
+        assertThat(BlobStoreModulesChooser.chooseModules(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication()))
             .first()
-            .isInstanceOf(BlobStoreModulesChooser.ObjectStorageDeclarationModule.class);
+            .isInstanceOf(BlobStoreModulesChooser.ObjectStorageDumdBlobStoreDeclarationModule.class);
     }
 
     @Test
     void provideBlobStoreShouldReturnCassandraBlobStoreWhenCassandraConfigured() {
-        assertThat(BlobStoreModulesChooser.chooseModules(BlobStoreConfiguration.cassandra()))
+        assertThat(BlobStoreModulesChooser.chooseModules(BlobStoreConfiguration.builder()
+                .cassandra()
+                .disableCache()
+                .passthrough()))
             .first()
-            .isInstanceOf(BlobStoreModulesChooser.CassandraDeclarationModule.class);
+            .isInstanceOf(BlobStoreModulesChooser.CassandraDumbBlobStoreDeclarationModule.class);
     }
 }
\ No newline at end of file
diff --git a/server/container/guice/cassandra-rabbitmq-ldap-guice/src/test/java/org/apache/james/CassandraRabbitMQLdapJmapJamesServerTest.java b/server/container/guice/cassandra-rabbitmq-ldap-guice/src/test/java/org/apache/james/CassandraRabbitMQLdapJmapJamesServerTest.java
index ae150ca..91c8934 100644
--- a/server/container/guice/cassandra-rabbitmq-ldap-guice/src/test/java/org/apache/james/CassandraRabbitMQLdapJmapJamesServerTest.java
+++ b/server/container/guice/cassandra-rabbitmq-ldap-guice/src/test/java/org/apache/james/CassandraRabbitMQLdapJmapJamesServerTest.java
@@ -57,7 +57,10 @@ class CassandraRabbitMQLdapJmapJamesServerTest {
     @TestInstance(TestInstance.Lifecycle.PER_CLASS)
     class WithSwift implements ContractSuite {
         @RegisterExtension
-        JamesServerExtension testExtension = baseJamesServerExtensionBuilder(BlobStoreConfiguration.objectStorage().disableCache())
+        JamesServerExtension testExtension = baseJamesServerExtensionBuilder(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
             .extension(new SwiftBlobStoreExtension())
             .build();
     }
@@ -66,7 +69,10 @@ class CassandraRabbitMQLdapJmapJamesServerTest {
     @TestInstance(TestInstance.Lifecycle.PER_CLASS)
     class WithAwsS3 implements ContractSuite {
         @RegisterExtension
-        JamesServerExtension testExtension = baseJamesServerExtensionBuilder(BlobStoreConfiguration.objectStorage().disableCache())
+        JamesServerExtension testExtension = baseJamesServerExtensionBuilder(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
             .extension(new AwsS3BlobStoreExtension())
             .build();
     }
@@ -75,7 +81,10 @@ class CassandraRabbitMQLdapJmapJamesServerTest {
     @TestInstance(TestInstance.Lifecycle.PER_CLASS)
     class WithoutSwiftOrAwsS3 implements ContractSuite {
         @RegisterExtension
-        JamesServerExtension testExtension = baseJamesServerExtensionBuilder(BlobStoreConfiguration.cassandra())
+        JamesServerExtension testExtension = baseJamesServerExtensionBuilder(BlobStoreConfiguration.builder()
+                .cassandra()
+                .disableCache()
+                .passthrough())
             .build();
     }
 
diff --git a/server/protocols/jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/RabbitMQAwsS3SendMDNMethodTest.java b/server/protocols/jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/RabbitMQAwsS3SendMDNMethodTest.java
index 93506b8..cecf617 100644
--- a/server/protocols/jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/RabbitMQAwsS3SendMDNMethodTest.java
+++ b/server/protocols/jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/RabbitMQAwsS3SendMDNMethodTest.java
@@ -42,7 +42,10 @@ public class RabbitMQAwsS3SendMDNMethodTest extends SendMDNMethodTest {
         CassandraRabbitMQJamesConfiguration.builder()
             .workingDirectory(tmpDir)
             .configurationFromClasspath()
-            .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+            .blobStore(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
             .searchConfiguration(SearchConfiguration.elasticSearch())
             .build())
         .extension(new DockerElasticSearchExtension())
diff --git a/server/protocols/jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/RabbitMQAwsS3SpamAssassinContractTest.java b/server/protocols/jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/RabbitMQAwsS3SpamAssassinContractTest.java
index b74ddc7..97a9793 100644
--- a/server/protocols/jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/RabbitMQAwsS3SpamAssassinContractTest.java
+++ b/server/protocols/jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/RabbitMQAwsS3SpamAssassinContractTest.java
@@ -41,7 +41,10 @@ class RabbitMQAwsS3SpamAssassinContractTest implements SpamAssassinContract {
         CassandraRabbitMQJamesConfiguration.builder()
             .workingDirectory(tmpDir)
             .configurationFromClasspath()
-            .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+            .blobStore(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
             .searchConfiguration(SearchConfiguration.elasticSearch())
             .build())
         .extension(new DockerElasticSearchExtension())
diff --git a/server/protocols/jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/cucumber/awss3/RabbitMQAwsS3Stepdefs.java b/server/protocols/jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/cucumber/awss3/RabbitMQAwsS3Stepdefs.java
index 19bdf2e..f9f60e9 100644
--- a/server/protocols/jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/cucumber/awss3/RabbitMQAwsS3Stepdefs.java
+++ b/server/protocols/jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/cucumber/awss3/RabbitMQAwsS3Stepdefs.java
@@ -81,7 +81,10 @@ public class RabbitMQAwsS3Stepdefs {
         CassandraRabbitMQJamesConfiguration configuration = CassandraRabbitMQJamesConfiguration.builder()
             .workingDirectory(temporaryFolder.newFolder())
             .configurationFromClasspath()
-            .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+            .blobStore(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
             .searchConfiguration(SearchConfiguration.elasticSearch())
             .build();
 
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/DistributedAuthenticationTest.java b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedAuthenticationTest.java
index 52f9764..219ff54 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedAuthenticationTest.java
+++ b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedAuthenticationTest.java
@@ -39,7 +39,10 @@ class DistributedAuthenticationTest implements AuthenticationContract {
         CassandraRabbitMQJamesConfiguration.builder()
             .workingDirectory(tmpDir)
             .configurationFromClasspath()
-            .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+            .blobStore(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
             .searchConfiguration(SearchConfiguration.elasticSearch())
             .build())
         .extension(new DockerElasticSearchExtension())
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/DistributedEchoMethodTest.java b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedEchoMethodTest.java
index 9827396..79ff634 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedEchoMethodTest.java
+++ b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedEchoMethodTest.java
@@ -40,7 +40,10 @@ public class DistributedEchoMethodTest implements EchoMethodContract {
         CassandraRabbitMQJamesConfiguration.builder()
             .workingDirectory(tmpDir)
             .configurationFromClasspath()
-            .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+            .blobStore(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
             .searchConfiguration(SearchConfiguration.elasticSearch())
             .build())
         .extension(new DockerElasticSearchExtension())
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 1e34353..2ccd522 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
@@ -42,7 +42,10 @@ public class DistributedMailboxGetMethodTest implements MailboxGetMethodContract
         CassandraRabbitMQJamesConfiguration.builder()
             .workingDirectory(tmpDir)
             .configurationFromClasspath()
-            .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+            .blobStore(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
             .build())
         .extension(new DockerElasticSearchExtension())
         .extension(new CassandraExtension())
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/DistributedSessionRouteTest.java b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedSessionRouteTest.java
index ef34f84..27d9664 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedSessionRouteTest.java
+++ b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedSessionRouteTest.java
@@ -39,7 +39,7 @@ public class DistributedSessionRouteTest implements SessionRoutesContract {
         CassandraRabbitMQJamesConfiguration.builder()
             .workingDirectory(tmpDir)
             .configurationFromClasspath()
-            .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+            .blobStore(BlobStoreConfiguration.builder().objectStorage().disableCache().passthrough())
             .searchConfiguration(SearchConfiguration.elasticSearch())
             .build())
         .extension(new DockerElasticSearchExtension())
diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/ConsistencyTasksIntegrationTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/ConsistencyTasksIntegrationTest.java
index 3a5c87a..bdcfa96 100644
--- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/ConsistencyTasksIntegrationTest.java
+++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/ConsistencyTasksIntegrationTest.java
@@ -128,7 +128,10 @@ class ConsistencyTasksIntegrationTest {
         CassandraRabbitMQJamesConfiguration.builder()
             .workingDirectory(tmpDir)
             .configurationFromClasspath()
-            .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+            .blobStore(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
             .searchConfiguration(SearchConfiguration.elasticSearch())
             .build())
         .extension(new DockerElasticSearchExtension())
diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/FixingGhostMailboxTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/FixingGhostMailboxTest.java
index cd59cd5..603e008 100644
--- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/FixingGhostMailboxTest.java
+++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/FixingGhostMailboxTest.java
@@ -116,7 +116,10 @@ class FixingGhostMailboxTest {
         CassandraRabbitMQJamesConfiguration.builder()
             .workingDirectory(tmpDir)
             .configurationFromClasspath()
-            .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+            .blobStore(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
             .searchConfiguration(SearchConfiguration.elasticSearch())
             .build())
         .extension(new DockerElasticSearchExtension())
diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQAuthorizedEndpointsTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQAuthorizedEndpointsTest.java
index afca267..bebfaa3 100644
--- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQAuthorizedEndpointsTest.java
+++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQAuthorizedEndpointsTest.java
@@ -44,7 +44,10 @@ class RabbitMQAuthorizedEndpointsTest extends AuthorizedEndpointsTest {
         CassandraRabbitMQJamesConfiguration.builder()
             .workingDirectory(tmpDir)
             .configurationFromClasspath()
-            .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+            .blobStore(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
             .searchConfiguration(SearchConfiguration.elasticSearch())
             .build())
         .extension(new DockerElasticSearchExtension())
diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQEventDeadLettersIntegrationTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQEventDeadLettersIntegrationTest.java
index 70ec14c..29ca1ab 100644
--- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQEventDeadLettersIntegrationTest.java
+++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQEventDeadLettersIntegrationTest.java
@@ -205,7 +205,10 @@ class RabbitMQEventDeadLettersIntegrationTest {
         CassandraRabbitMQJamesConfiguration.builder()
             .workingDirectory(tmpDir)
             .configurationFromClasspath()
-            .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+            .blobStore(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
             .searchConfiguration(SearchConfiguration.elasticSearch())
             .build())
         .extension(new DockerElasticSearchExtension())
diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQFastViewProjectionHealthCheckIntegrationTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQFastViewProjectionHealthCheckIntegrationTest.java
index 45416a9..3ecfa00 100644
--- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQFastViewProjectionHealthCheckIntegrationTest.java
+++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQFastViewProjectionHealthCheckIntegrationTest.java
@@ -40,7 +40,10 @@ class RabbitMQFastViewProjectionHealthCheckIntegrationTest extends FastViewProje
         CassandraRabbitMQJamesConfiguration.builder()
             .workingDirectory(tmpDir)
             .configurationFromClasspath()
-            .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+            .blobStore(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
             .searchConfiguration(SearchConfiguration.elasticSearch())
             .build())
         .extension(new DockerElasticSearchExtension())
diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQForwardIntegrationTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQForwardIntegrationTest.java
index e72de6d..cdf1848 100644
--- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQForwardIntegrationTest.java
+++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQForwardIntegrationTest.java
@@ -40,7 +40,10 @@ class RabbitMQForwardIntegrationTest extends ForwardIntegrationTest {
         CassandraRabbitMQJamesConfiguration.builder()
             .workingDirectory(tmpDir)
             .configurationFromClasspath()
-            .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+            .blobStore(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
             .searchConfiguration(SearchConfiguration.elasticSearch())
             .build())
         .extension(new DockerElasticSearchExtension())
diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQJmapExtension.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQJmapExtension.java
index da86d14..aaa763f 100644
--- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQJmapExtension.java
+++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQJmapExtension.java
@@ -150,7 +150,10 @@ public class RabbitMQJmapExtension implements BeforeAllCallback, AfterAllCallbac
         CassandraRabbitMQJamesConfiguration configuration = CassandraRabbitMQJamesConfiguration.builder()
             .workingDirectory(temporaryFolder.newFolder())
             .configurationFromClasspath()
-            .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+            .blobStore(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
             .searchConfiguration(SearchConfiguration.elasticSearch())
             .build();
 
diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQJwtFilterIntegrationTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQJwtFilterIntegrationTest.java
index fca871b..1a96a6a 100644
--- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQJwtFilterIntegrationTest.java
+++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQJwtFilterIntegrationTest.java
@@ -42,7 +42,10 @@ class RabbitMQJwtFilterIntegrationTest extends JwtFilterIntegrationTest {
         CassandraRabbitMQJamesConfiguration.builder()
             .workingDirectory(tmpDir)
             .configurationFromClasspath()
-            .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+            .blobStore(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
             .searchConfiguration(SearchConfiguration.elasticSearch())
             .build())
         .extension(new DockerElasticSearchExtension())
diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQReindexingWithEventDeadLettersTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQReindexingWithEventDeadLettersTest.java
index fd3168a..df2390a 100644
--- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQReindexingWithEventDeadLettersTest.java
+++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQReindexingWithEventDeadLettersTest.java
@@ -87,7 +87,10 @@ class RabbitMQReindexingWithEventDeadLettersTest {
         CassandraRabbitMQJamesConfiguration.builder()
             .workingDirectory(tmpDir)
             .configurationFromClasspath()
-            .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+            .blobStore(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
             .searchConfiguration(SearchConfiguration.elasticSearch())
             .build())
         .extension(dockerElasticSearch)
diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerIntegrationTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerIntegrationTest.java
index 7162cb3..5f8a1fa 100644
--- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerIntegrationTest.java
+++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerIntegrationTest.java
@@ -64,7 +64,10 @@ class RabbitMQWebAdminServerIntegrationTest extends WebAdminServerIntegrationTes
         CassandraRabbitMQJamesConfiguration.builder()
             .workingDirectory(tmpDir)
             .configurationFromClasspath()
-            .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+            .blobStore(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
             .searchConfiguration(SearchConfiguration.elasticSearch())
             .build())
         .extension(new DockerElasticSearchExtension())
diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerTaskSerializationIntegrationTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerTaskSerializationIntegrationTest.java
index 3d5e3ed..6ef4410 100644
--- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerTaskSerializationIntegrationTest.java
+++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerTaskSerializationIntegrationTest.java
@@ -103,7 +103,10 @@ class RabbitMQWebAdminServerTaskSerializationIntegrationTest {
         CassandraRabbitMQJamesConfiguration.builder()
             .workingDirectory(tmpDir)
             .configurationFromClasspath()
-            .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+            .blobStore(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
             .searchConfiguration(SearchConfiguration.elasticSearch())
             .build())
         .extension(new DockerElasticSearchExtension())
diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/vault/RabbitMQDeletedMessageVaultIntegrationTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/vault/RabbitMQDeletedMessageVaultIntegrationTest.java
index d4f94d9..df878ac 100644
--- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/vault/RabbitMQDeletedMessageVaultIntegrationTest.java
+++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/vault/RabbitMQDeletedMessageVaultIntegrationTest.java
@@ -49,7 +49,10 @@ class RabbitMQDeletedMessageVaultIntegrationTest extends DeletedMessageVaultInte
         CassandraRabbitMQJamesConfiguration.builder()
             .workingDirectory(tmpDir)
             .configurationFromClasspath()
-            .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+            .blobStore(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
             .searchConfiguration(SearchConfiguration.elasticSearch())
             .build())
         .extension(ES_EXTENSION)
diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/vault/RabbitMQLinshareBlobExportMechanismIntegrationTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/vault/RabbitMQLinshareBlobExportMechanismIntegrationTest.java
index ed82af5..43136e1 100644
--- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/vault/RabbitMQLinshareBlobExportMechanismIntegrationTest.java
+++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/vault/RabbitMQLinshareBlobExportMechanismIntegrationTest.java
@@ -46,7 +46,10 @@ class RabbitMQLinshareBlobExportMechanismIntegrationTest extends LinshareBlobExp
         CassandraRabbitMQJamesConfiguration.builder()
             .workingDirectory(tmpDir)
             .configurationFromClasspath()
-            .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+            .blobStore(BlobStoreConfiguration.builder()
+                    .objectStorage()
+                    .disableCache()
+                    .deduplication())
             .searchConfiguration(SearchConfiguration.elasticSearch())
             .build())
         .extension(new DockerElasticSearchExtension())


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


[james-project] 12/12: [REFACTORING] Override cleanup for RabbitMQAwsS3Stepdefs

Posted by bt...@apache.org.
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 89fb5f4df08da90b4ba0b321224e3be9f6b110a3
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Jul 29 11:40:01 2020 +0700

    [REFACTORING] Override cleanup for RabbitMQAwsS3Stepdefs
---
 .../test/java/org/apache/james/CassandraJmapJamesServerTest.java    | 3 ---
 .../james/jmap/rabbitmq/cucumber/awss3/RabbitMQAwsS3Stepdefs.java   | 6 ------
 2 files changed, 9 deletions(-)

diff --git a/server/container/guice/cassandra-guice/src/test/java/org/apache/james/CassandraJmapJamesServerTest.java b/server/container/guice/cassandra-guice/src/test/java/org/apache/james/CassandraJmapJamesServerTest.java
index 97efb6b..2abb9d5 100644
--- a/server/container/guice/cassandra-guice/src/test/java/org/apache/james/CassandraJmapJamesServerTest.java
+++ b/server/container/guice/cassandra-guice/src/test/java/org/apache/james/CassandraJmapJamesServerTest.java
@@ -20,8 +20,6 @@
 package org.apache.james;
 
 import org.apache.james.jmap.draft.JmapJamesServerContract;
-import org.apache.james.mailbox.extractor.TextExtractor;
-import org.apache.james.mailbox.store.search.PDFTextExtractor;
 import org.apache.james.modules.TestJMAPServerModule;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
@@ -31,7 +29,6 @@ class CassandraJmapJamesServerTest implements JmapJamesServerContract {
         .extension(new DockerElasticSearchExtension())
         .extension(new CassandraExtension())
         .server(configuration -> CassandraJamesServerMain.createServer(configuration)
-            .overrideWith(binder -> binder.bind(TextExtractor.class).to(PDFTextExtractor.class))
             .overrideWith(new TestJMAPServerModule())
             .overrideWith(DOMAIN_LIST_CONFIGURATION_MODULE))
         .build();
diff --git a/server/protocols/jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/cucumber/awss3/RabbitMQAwsS3Stepdefs.java b/server/protocols/jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/cucumber/awss3/RabbitMQAwsS3Stepdefs.java
index f9f60e9..e588f66 100644
--- a/server/protocols/jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/cucumber/awss3/RabbitMQAwsS3Stepdefs.java
+++ b/server/protocols/jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/cucumber/awss3/RabbitMQAwsS3Stepdefs.java
@@ -23,8 +23,6 @@ import java.util.Arrays;
 
 import javax.inject.Inject;
 
-import org.apache.activemq.store.PersistenceAdapter;
-import org.apache.activemq.store.memory.MemoryPersistenceAdapter;
 import org.apache.james.CassandraRabbitMQJamesConfiguration;
 import org.apache.james.CassandraRabbitMQJamesServerMain;
 import org.apache.james.CleanupTasksPerformer;
@@ -34,8 +32,6 @@ import org.apache.james.SearchConfiguration;
 import org.apache.james.jmap.draft.methods.integration.cucumber.ImapStepdefs;
 import org.apache.james.jmap.draft.methods.integration.cucumber.MainStepdefs;
 import org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
-import org.apache.james.mailbox.extractor.TextExtractor;
-import org.apache.james.mailbox.store.extractor.DefaultTextExtractor;
 import org.apache.james.modules.DockerRabbitMQRule;
 import org.apache.james.modules.TestDockerESMetricReporterModule;
 import org.apache.james.modules.TestJMAPServerModule;
@@ -95,8 +91,6 @@ public class RabbitMQAwsS3Stepdefs {
                 .overrideWith(swiftServer.getModule())
                 .overrideWith(elasticSearch.getModule())
                 .overrideWith(cassandraServer.getModule())
-                .overrideWith(binder -> binder.bind(TextExtractor.class).to(DefaultTextExtractor.class))
-                .overrideWith((binder) -> binder.bind(PersistenceAdapter.class).to(MemoryPersistenceAdapter.class))
                 .overrideWith(binder -> Multibinder.newSetBinder(binder, CleanupTasksPerformer.CleanupTask.class).addBinding().to(CassandraTruncateTableTask.class))
                 .overrideWith((binder -> binder.bind(CleanupTasksPerformer.class).asEagerSingleton()));
         mainStepdefs.awaitMethod = () -> elasticSearch.getDockerEs().flushIndices();


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


[james-project] 09/12: JAMES-3348 Add missing Sieve persistent class declarations

Posted by bt...@apache.org.
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 03addb01736738f54bf7c9e0bbb9c3ec43a37419
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Jul 28 16:30:14 2020 +0700

    JAMES-3348 Add missing Sieve persistent class declarations
---
 server/app/src/main/resources/META-INF/persistence.xml                  | 2 ++
 .../guice/jpa-guice/src/main/resources/META-INF/persistence.xml         | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/server/app/src/main/resources/META-INF/persistence.xml b/server/app/src/main/resources/META-INF/persistence.xml
index 6f68e20..405b5b5 100644
--- a/server/app/src/main/resources/META-INF/persistence.xml
+++ b/server/app/src/main/resources/META-INF/persistence.xml
@@ -36,6 +36,8 @@
         <class>org.apache.james.mailrepository.jpa.JPAUrl</class>
         <class>org.apache.james.user.jpa.model.JPAUser</class>
         <class>org.apache.james.rrt.jpa.model.JPARecipientRewrite</class>
+        <class>org.apache.james.sieve.jpa.model.JPASieveQuota</class>
+        <class>org.apache.james.sieve.jpa.model.JPASieveScript</class>
         <class>org.apache.james.mailbox.jpa.quota.model.MaxDomainMessageCount</class>
         <class>org.apache.james.mailbox.jpa.quota.model.MaxDomainStorage</class>
         <class>org.apache.james.mailbox.jpa.quota.model.MaxGlobalMessageCount</class>
diff --git a/server/container/guice/jpa-guice/src/main/resources/META-INF/persistence.xml b/server/container/guice/jpa-guice/src/main/resources/META-INF/persistence.xml
index 2b89b96..e847af4 100644
--- a/server/container/guice/jpa-guice/src/main/resources/META-INF/persistence.xml
+++ b/server/container/guice/jpa-guice/src/main/resources/META-INF/persistence.xml
@@ -35,6 +35,8 @@
         <class>org.apache.james.mailrepository.jpa.JPAUrl</class>
         <class>org.apache.james.user.jpa.model.JPAUser</class>
         <class>org.apache.james.rrt.jpa.model.JPARecipientRewrite</class>
+        <class>org.apache.james.sieve.jpa.model.JPASieveQuota</class>
+        <class>org.apache.james.sieve.jpa.model.JPASieveScript</class>
 
         <class>org.apache.james.mailbox.jpa.quota.model.MaxDomainMessageCount</class>
         <class>org.apache.james.mailbox.jpa.quota.model.MaxDomainStorage</class>


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


[james-project] 10/12: [REFACTORING] Default methods for GuiceModuleTestRule

Posted by bt...@apache.org.
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 ab1a174c690e85a8cb2e38faf89c9419cf3e36df
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Jul 28 16:58:51 2020 +0700

    [REFACTORING] Default methods for GuiceModuleTestRule
    
    This allows a quick audit of which class implements which methods
---
 .../modules/objectstorage/aws/s3/DockerAwsS3TestRule.java    |  4 ----
 .../modules/objectstorage/swift/DockerSwiftTestRule.java     |  4 ----
 .../src/test/java/org/apache/james/DockerCassandraRule.java  |  4 ----
 .../src/test/java/org/apache/james/DockerLdapRule.java       |  4 ----
 .../java/org/apache/james/modules/DockerRabbitMQRule.java    |  4 ----
 .../src/test/java/org/apache/james/GuiceModuleTestRule.java  |  8 ++++++--
 .../test/java/org/apache/james/TempFilesystemTestRule.java   | 12 ------------
 7 files changed, 6 insertions(+), 34 deletions(-)

diff --git a/server/container/guice/blob-objectstorage-guice/src/test/java/org/apache/james/modules/objectstorage/aws/s3/DockerAwsS3TestRule.java b/server/container/guice/blob-objectstorage-guice/src/test/java/org/apache/james/modules/objectstorage/aws/s3/DockerAwsS3TestRule.java
index 097de25..9690b46 100644
--- a/server/container/guice/blob-objectstorage-guice/src/test/java/org/apache/james/modules/objectstorage/aws/s3/DockerAwsS3TestRule.java
+++ b/server/container/guice/blob-objectstorage-guice/src/test/java/org/apache/james/modules/objectstorage/aws/s3/DockerAwsS3TestRule.java
@@ -82,10 +82,6 @@ public class DockerAwsS3TestRule implements GuiceModuleTestRule {
     }
 
     @Override
-    public void await() {
-    }
-
-    @Override
     public Module getModule() {
         BucketName defaultBucketName = BucketName.of(UUID.randomUUID().toString());
         AwsS3AuthConfiguration authConfiguration = AwsS3AuthConfiguration.builder()
diff --git a/server/container/guice/blob-objectstorage-guice/src/test/java/org/apache/james/modules/objectstorage/swift/DockerSwiftTestRule.java b/server/container/guice/blob-objectstorage-guice/src/test/java/org/apache/james/modules/objectstorage/swift/DockerSwiftTestRule.java
index bc3ffde..520d828 100644
--- a/server/container/guice/blob-objectstorage-guice/src/test/java/org/apache/james/modules/objectstorage/swift/DockerSwiftTestRule.java
+++ b/server/container/guice/blob-objectstorage-guice/src/test/java/org/apache/james/modules/objectstorage/swift/DockerSwiftTestRule.java
@@ -76,10 +76,6 @@ public class DockerSwiftTestRule implements GuiceModuleTestRule {
     }
 
     @Override
-    public void await() {
-    }
-
-    @Override
     public Module getModule() {
         SwiftKeystone2ObjectStorage.Configuration authConfiguration = SwiftKeystone2ObjectStorage.configBuilder()
             .credentials(Credentials.of("demo"))
diff --git a/server/container/guice/cassandra-guice/src/test/java/org/apache/james/DockerCassandraRule.java b/server/container/guice/cassandra-guice/src/test/java/org/apache/james/DockerCassandraRule.java
index 6ed7d39..27c5463 100644
--- a/server/container/guice/cassandra-guice/src/test/java/org/apache/james/DockerCassandraRule.java
+++ b/server/container/guice/cassandra-guice/src/test/java/org/apache/james/DockerCassandraRule.java
@@ -42,10 +42,6 @@ public class DockerCassandraRule implements GuiceModuleTestRule {
     }
 
     @Override
-    public void await() {
-    }
-
-    @Override
     public Module getModule() {
         return Modules.combine(binder -> binder.bind(ClusterConfiguration.class)
             .toInstance(DockerCassandra.configurationBuilder(cassandraContainer.getHost())
diff --git a/server/container/guice/cassandra-ldap-guice/src/test/java/org/apache/james/DockerLdapRule.java b/server/container/guice/cassandra-ldap-guice/src/test/java/org/apache/james/DockerLdapRule.java
index 4a397cb..0c96b67 100644
--- a/server/container/guice/cassandra-ldap-guice/src/test/java/org/apache/james/DockerLdapRule.java
+++ b/server/container/guice/cassandra-ldap-guice/src/test/java/org/apache/james/DockerLdapRule.java
@@ -36,10 +36,6 @@ public class DockerLdapRule implements GuiceModuleTestRule {
     }
 
     @Override
-    public void await() {
-    }
-
-    @Override
     public Statement apply(Statement statement, Description description) {
         return statement;
     }
diff --git a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/DockerRabbitMQRule.java b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/DockerRabbitMQRule.java
index b107e2c..7834db7 100644
--- a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/DockerRabbitMQRule.java
+++ b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/DockerRabbitMQRule.java
@@ -43,10 +43,6 @@ public class DockerRabbitMQRule implements GuiceModuleTestRule {
     }
 
     @Override
-    public void await() {
-    }
-
-    @Override
     public Module getModule() {
         return Modules.combine((binder) -> {
                 try {
diff --git a/server/container/guice/guice-common/src/test/java/org/apache/james/GuiceModuleTestRule.java b/server/container/guice/guice-common/src/test/java/org/apache/james/GuiceModuleTestRule.java
index ca586c4..098d597 100644
--- a/server/container/guice/guice-common/src/test/java/org/apache/james/GuiceModuleTestRule.java
+++ b/server/container/guice/guice-common/src/test/java/org/apache/james/GuiceModuleTestRule.java
@@ -24,7 +24,11 @@ import org.junit.rules.TestRule;
 import com.google.inject.Module;
 
 public interface GuiceModuleTestRule extends TestRule {
-    Module getModule();
+    default Module getModule() {
+        return binder -> { };
+    }
 
-    void await();
+    default void await() {
+
+    }
 }
\ No newline at end of file
diff --git a/server/container/guice/guice-common/src/test/java/org/apache/james/TempFilesystemTestRule.java b/server/container/guice/guice-common/src/test/java/org/apache/james/TempFilesystemTestRule.java
index 54d5c12..8ee7908 100644
--- a/server/container/guice/guice-common/src/test/java/org/apache/james/TempFilesystemTestRule.java
+++ b/server/container/guice/guice-common/src/test/java/org/apache/james/TempFilesystemTestRule.java
@@ -23,8 +23,6 @@ import org.junit.rules.TemporaryFolder;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
-import com.google.inject.Module;
-
 public class TempFilesystemTestRule implements GuiceModuleTestRule {
 
     private final TemporaryFolder temporaryFolder;
@@ -41,14 +39,4 @@ public class TempFilesystemTestRule implements GuiceModuleTestRule {
     public Statement apply(Statement base, Description description) {
         return temporaryFolder.apply(base, description);
     }
-
-    @Override
-    public Module getModule() {
-        return binder -> { };
-    }
-
-    @Override
-    public void await() {
-    }
-
 }


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


[james-project] 04/12: JAMES-3093 Port MailboxProvisioner from draft to jmap-rfc-8621

Posted by bt...@apache.org.
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 98f9f18c9e77e33ed6d62ebe8b189817166fcc68
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Tue Jul 21 17:21:04 2020 +0700

    JAMES-3093 Port MailboxProvisioner from draft to jmap-rfc-8621
---
 .../james/jmap/http/MailboxesProvisioner.scala     |  81 ++++++++++++++++
 .../james/jmap/http/MailboxesProvisionerTest.scala | 103 +++++++++++++++++++++
 2 files changed, 184 insertions(+)

diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/MailboxesProvisioner.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/MailboxesProvisioner.scala
new file mode 100644
index 0000000..2690ee5
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/MailboxesProvisioner.scala
@@ -0,0 +1,81 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jmap.http
+
+import java.util.Optional
+
+import javax.inject.Inject
+import org.apache.james.core.Username
+import org.apache.james.mailbox.exception.{MailboxException, MailboxExistsException}
+import org.apache.james.mailbox.model.{MailboxId, MailboxPath}
+import org.apache.james.mailbox.{DefaultMailboxes, MailboxManager, MailboxSession, SubscriptionManager}
+import org.apache.james.metrics.api.MetricFactory
+import org.slf4j.{Logger, LoggerFactory}
+import reactor.core.scala.publisher.{SFlux, SMono}
+import reactor.core.scheduler.Schedulers
+
+import scala.jdk.CollectionConverters._
+
+class MailboxesProvisioner @Inject() (mailboxManager: MailboxManager,
+                                      subscriptionManager: SubscriptionManager,
+                                      metricFactory: MetricFactory) {
+  private val LOGGER: Logger = LoggerFactory.getLogger(classOf[MailboxesProvisioner])
+
+  def createMailboxesIfNeeded(session: MailboxSession): SMono[Unit] =
+    metricFactory.decorateSupplierWithTimerMetric("JMAP-RFC-8621-mailboxes-provisioning", () =>
+      createDefaultMailboxes(session.getUser))
+
+
+  private def createDefaultMailboxes(username: Username): SMono[Unit] = {
+    val session: MailboxSession = mailboxManager.createSystemSession(username)
+
+    SFlux.fromIterable(DefaultMailboxes.DEFAULT_MAILBOXES.asScala)
+      .map(toMailboxPath(session))
+      .filterWhen((mailboxPath: MailboxPath) => mailboxDoesntExist(mailboxPath, session))
+      .concatMap((mailboxPath: MailboxPath) => SMono.fromCallable(() => createMailbox(mailboxPath, session))
+        .subscribeOn(Schedulers.elastic))
+      .`then`
+  }
+
+  private def mailboxDoesntExist(mailboxPath: MailboxPath, session: MailboxSession): SMono[Boolean] = {
+    try {
+      SMono(mailboxManager.mailboxExists(mailboxPath, session))
+        .map(exist => !exist)
+    } catch {
+      case exception: MailboxException => SMono.raiseError(exception)
+    }
+  }
+
+  private def toMailboxPath(session: MailboxSession): String => MailboxPath =
+    (mailbox: String) => MailboxPath.forUser(session.getUser, mailbox)
+
+  private def createMailbox(mailboxPath: MailboxPath, session: MailboxSession): Unit = {
+    try {
+      val mailboxId: Optional[MailboxId] = mailboxManager.createMailbox(mailboxPath, session)
+      if (mailboxId.isPresent) {
+        subscriptionManager.subscribe(session, mailboxPath.getName)
+      }
+      LOGGER.info("Provisioning {}. {} created.", mailboxPath, mailboxId)
+    } catch {
+      case e: MailboxExistsException => LOGGER.info("Mailbox {} have been created concurrently", mailboxPath)
+      case e: MailboxException => throw new RuntimeException(e)
+    }
+  }
+}
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/MailboxesProvisionerTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/MailboxesProvisionerTest.scala
new file mode 100644
index 0000000..cd21992
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/MailboxesProvisionerTest.scala
@@ -0,0 +1,103 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jmap.http
+
+import java.time.Duration
+import java.util.function.Predicate
+
+import com.github.fge.lambdas.Throwing
+import com.github.steveash.guavate.Guavate
+import org.apache.james.core.Username
+import org.apache.james.mailbox.inmemory.InMemoryMailboxManager
+import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources
+import org.apache.james.mailbox.model.MailboxPath
+import org.apache.james.mailbox.store.StoreSubscriptionManager
+import org.apache.james.mailbox.{DefaultMailboxes, MailboxSession, MailboxSessionUtil}
+import org.apache.james.metrics.tests.RecordingMetricFactory
+import org.apache.james.util.concurrency.ConcurrentTestRunner
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.{BeforeEach, Test}
+
+object MailboxesProvisionerTest {
+  private val USERNAME: Username = Username.of("username")
+}
+
+class MailboxesProvisionerTest {
+  import MailboxesProvisionerTest._
+
+  var testee: MailboxesProvisioner = _
+  var session: MailboxSession = _
+  var mailboxManager: InMemoryMailboxManager = _
+  var subscriptionManager: StoreSubscriptionManager = _
+
+  @BeforeEach
+  def setup(): Unit = {
+    session = MailboxSessionUtil.create(USERNAME)
+    mailboxManager = InMemoryIntegrationResources.defaultResources.getMailboxManager
+    subscriptionManager = new StoreSubscriptionManager(mailboxManager.getMapperFactory)
+    testee = new MailboxesProvisioner(mailboxManager, subscriptionManager, new RecordingMetricFactory)
+  }
+
+  @Test
+  def createMailboxesIfNeededShouldCreateSystemMailboxes(): Unit = {
+    testee.createMailboxesIfNeeded(session).block()
+
+    assertThat(mailboxManager.list(session))
+      .containsOnlyElementsOf(DefaultMailboxes.DEFAULT_MAILBOXES
+        .stream
+        .map((mailboxName: String) => MailboxPath.forUser(USERNAME, mailboxName))
+        .collect(Guavate.toImmutableList()))
+  }
+
+  @Test
+  def createMailboxesIfNeededShouldCreateSpamWhenOtherSystemMailboxesExist(): Unit = {
+    DefaultMailboxes.DEFAULT_MAILBOXES
+      .stream
+      .filter(Predicate.not(Predicate.isEqual(DefaultMailboxes.SPAM)))
+      .forEach(Throwing.consumer((mailbox: String) => mailboxManager.createMailbox(MailboxPath.forUser(USERNAME, mailbox), session)))
+
+    testee.createMailboxesIfNeeded(session).block()
+
+    assertThat(mailboxManager.list(session))
+      .contains(MailboxPath.forUser(USERNAME, DefaultMailboxes.SPAM))
+  }
+
+  @Test
+  def createMailboxesIfNeededShouldSubscribeMailboxes(): Unit = {
+    testee.createMailboxesIfNeeded(session).block()
+
+    assertThat(subscriptionManager.subscriptions(session))
+      .containsOnlyElementsOf(DefaultMailboxes.DEFAULT_MAILBOXES)
+  }
+
+  @Test
+  def createMailboxesIfNeededShouldNotGenerateExceptionsInConcurrentEnvironment(): Unit = {
+    ConcurrentTestRunner.builder
+      .operation((threadNumber: Int, step: Int) => testee.createMailboxesIfNeeded(session).block())
+      .threadCount(10)
+      .runSuccessfullyWithin(Duration.ofSeconds(10))
+
+    assertThat(mailboxManager.list(session))
+      .containsOnlyElementsOf(DefaultMailboxes.DEFAULT_MAILBOXES
+        .stream
+        .map((mailboxName: String) => MailboxPath.forUser(USERNAME, mailboxName))
+        .collect(Guavate.toImmutableList()))
+  }
+}


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


[james-project] 01/12: JAMES-2892 Request level error handling

Posted by bt...@apache.org.
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 62b20ec9676bf37223bc896eb608d20e233b7117
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Fri Jul 17 16:46:04 2020 +0700

    JAMES-2892 Request level error handling
---
 .../org/apache/james/jmap/json/Serializer.scala    |  4 ++
 .../org/apache/james/jmap/model/Capability.scala   |  2 +
 .../apache/james/jmap/model/ProblemDetails.scala   | 29 ++++++++
 .../james/jmap/model/RequestLevelErrorType.scala   | 31 ++++++++
 .../org/apache/james/jmap/model/StatusCode.scala   | 27 +++++++
 .../apache/james/jmap/routes/JMAPApiRoutes.scala   | 65 +++++++++++------
 .../james/jmap/routes/JMAPApiRoutesTest.scala      | 82 ++++++++++++++++++++--
 7 files changed, 212 insertions(+), 28 deletions(-)

diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala
index b31e0b5..d9e234a 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala
@@ -236,12 +236,16 @@ class Serializer @Inject() (mailboxIdFactory: MailboxId.Factory) {
 
   private implicit def jsErrorWrites: Writes[JsError] = Json.writes[JsError]
 
+  private implicit val problemDetailsWrites: Writes[ProblemDetails] = Json.writes[ProblemDetails]
+
   def serialize(session: Session): JsValue = Json.toJson(session)
 
   def serialize(requestObject: RequestObject): JsValue = Json.toJson(requestObject)
 
   def serialize(responseObject: ResponseObject): JsValue = Json.toJson(responseObject)
 
+  def serialize(problemDetails: ProblemDetails): JsValue = Json.toJson(problemDetails)
+
   def serialize(mailbox: Mailbox)(implicit mailboxWrites: Writes[Mailbox]): JsValue = Json.toJson(mailbox)
 
   def serialize(mailboxGetResponse: MailboxGetResponse)(implicit mailboxWrites: Writes[Mailbox]): JsValue = Json.toJson(mailboxGetResponse)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Capability.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Capability.scala
index 6fd841c..8eaaefb 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Capability.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Capability.scala
@@ -34,6 +34,8 @@ object CapabilityIdentifier {
   val JMAP_MAIL: CapabilityIdentifier = "urn:ietf:params:jmap:mail"
   val JAMES_QUOTA: CapabilityIdentifier = "urn:apache:james:params:jmap:mail:quota"
   val JAMES_SHARES: CapabilityIdentifier = "urn:apache:james:params:jmap:mail:shares"
+
+  val SUPPORTED_CAPABILITIES: Set[CapabilityIdentifier] = Set(JMAP_CORE, JMAP_MAIL, JAMES_QUOTA, JAMES_SHARES)
 }
 
 sealed trait CapabilityProperties
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/ProblemDetails.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/ProblemDetails.scala
new file mode 100644
index 0000000..f617c86
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/ProblemDetails.scala
@@ -0,0 +1,29 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+package org.apache.james.jmap.model
+
+import org.apache.james.jmap.model.RequestLevelErrorType.ErrorTypeIdentifier
+import org.apache.james.jmap.model.StatusCode.ErrorStatus
+
+/**
+ * Problem Details for HTTP APIs within the JMAP context
+ * https://tools.ietf.org/html/rfc7807
+ * see https://jmap.io/spec-core.html#errors
+ */
+case class ProblemDetails(`type`: ErrorTypeIdentifier, status: ErrorStatus, limit: Option[String], detail: String)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/RequestLevelErrorType.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/RequestLevelErrorType.scala
new file mode 100644
index 0000000..5013a73
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/RequestLevelErrorType.scala
@@ -0,0 +1,31 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+package org.apache.james.jmap.model
+
+import eu.timepit.refined.api.Refined
+import eu.timepit.refined.string.Uri
+import eu.timepit.refined.auto._
+
+object RequestLevelErrorType {
+  type ErrorTypeIdentifier = String Refined Uri
+  val UNKNOWN_CAPABILITY: ErrorTypeIdentifier = "urn:ietf:params:jmap:error:unknownCapability"
+  val NOT_JSON: ErrorTypeIdentifier = "urn:ietf:params:jmap:error:notJSON"
+  val NOT_REQUEST: ErrorTypeIdentifier = "urn:ietf:params:jmap:error:notRequest"
+  val LIMIT: ErrorTypeIdentifier = "urn:ietf:params:jmap:error:limit"
+}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/StatusCode.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/StatusCode.scala
new file mode 100644
index 0000000..4de45e7
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/StatusCode.scala
@@ -0,0 +1,27 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jmap.model
+
+import eu.timepit.refined.api.Refined
+import eu.timepit.refined.numeric.Interval.Closed
+
+object StatusCode {
+  type ErrorStatus = Int Refined Closed[100, 599]
+}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
index 0a89a16..45dfda5 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
@@ -23,6 +23,7 @@ import java.nio.charset.StandardCharsets
 import java.util.stream
 import java.util.stream.Stream
 
+import com.fasterxml.jackson.core.JsonParseException
 import eu.timepit.refined.auto._
 import io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE
 import io.netty.handler.codec.http.HttpMethod
@@ -38,7 +39,7 @@ import org.apache.james.jmap.json.Serializer
 import org.apache.james.jmap.method.Method
 import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
 import org.apache.james.jmap.model.Invocation.{Arguments, MethodName}
-import org.apache.james.jmap.model.{Invocation, RequestObject, ResponseObject}
+import org.apache.james.jmap.model._
 import org.apache.james.jmap.{Endpoint, JMAPRoute, JMAPRoutes}
 import org.apache.james.mailbox.MailboxSession
 import org.slf4j.{Logger, LoggerFactory}
@@ -97,26 +98,33 @@ class JMAPApiRoutes (val authenticator: Authenticator,
   private def parseRequestObject(inputStream: InputStream): SMono[RequestObject] =
     serializer.deserializeRequestObject(inputStream) match {
       case JsSuccess(requestObject, _) => SMono.just(requestObject)
-      case JsError(_) => SMono.raiseError(new IllegalArgumentException("Invalid RequestObject"))
+      case errors: JsError => SMono.raiseError(new IllegalArgumentException(serializer.serialize(errors).toString()))
     }
 
   private def process(requestObject: RequestObject,
                       httpServerResponse: HttpServerResponse,
-                      mailboxSession: MailboxSession): SMono[Void] =
-    requestObject
-      .methodCalls
-      .map(invocation => this.processMethodWithMatchName(requestObject.using.toSet, invocation, mailboxSession))
-      .foldLeft(SFlux.empty[Invocation]) { (flux: SFlux[Invocation], mono: SMono[Invocation]) => flux.mergeWith(mono) }
-      .collectSeq()
-      .flatMap((invocations: Seq[Invocation]) =>
-        SMono.fromPublisher(httpServerResponse.status(OK)
-          .header(CONTENT_TYPE, JSON_CONTENT_TYPE)
-          .sendString(
-            SMono.fromCallable(() =>
-              serializer.serialize(ResponseObject(ResponseObject.SESSION_STATE, invocations)).toString),
-            StandardCharsets.UTF_8
-          ).`then`())
-      )
+                      mailboxSession: MailboxSession): SMono[Void] = {
+    val unsupportedCapabilities = requestObject.using.toSet -- CapabilityIdentifier.SUPPORTED_CAPABILITIES
+
+    if (unsupportedCapabilities.nonEmpty) {
+      SMono.raiseError(UnsupportedCapabilitiesException(unsupportedCapabilities))
+    } else {
+      requestObject
+        .methodCalls
+        .map(invocation => this.processMethodWithMatchName(requestObject.using.toSet, invocation, mailboxSession))
+        .foldLeft(SFlux.empty[Invocation]) { (flux: SFlux[Invocation], mono: SMono[Invocation]) => flux.mergeWith(mono) }
+        .collectSeq()
+        .flatMap((invocations: Seq[Invocation]) =>
+          SMono.fromPublisher(httpServerResponse.status(OK)
+            .header(CONTENT_TYPE, JSON_CONTENT_TYPE)
+            .sendString(
+              SMono.fromCallable(() =>
+                serializer.serialize(ResponseObject(ResponseObject.SESSION_STATE, invocations)).toString),
+              StandardCharsets.UTF_8
+            ).`then`())
+        )
+    }
+  }
 
   private def processMethodWithMatchName(capabilities: Set[CapabilityIdentifier], invocation: Invocation, mailboxSession: MailboxSession): SMono[Invocation] =
     SMono.justOrEmpty(methodsByName.get(invocation.methodName))
@@ -127,11 +135,26 @@ class JMAPApiRoutes (val authenticator: Authenticator,
         invocation.methodCallId)))
 
   private def handleError(throwable: Throwable, httpServerResponse: HttpServerResponse): SMono[Void] = throwable match {
-    case exception: IllegalArgumentException => SMono.fromPublisher(httpServerResponse.status(SC_BAD_REQUEST)
-      .header(CONTENT_TYPE, JSON_CONTENT_TYPE)
-      .sendString(SMono.fromCallable(() => exception.getMessage), StandardCharsets.UTF_8)
-      .`then`)
+    case exception: IllegalArgumentException => respondDetails(httpServerResponse,
+      ProblemDetails(RequestLevelErrorType.NOT_REQUEST, SC_BAD_REQUEST, None,
+        s"The request was successfully parsed as JSON but did not match the type signature of the Request object: ${exception.getMessage}"))
     case exception: UnauthorizedException => SMono(handleAuthenticationFailure(httpServerResponse, JMAPApiRoutes.LOGGER, exception))
+    case exception: JsonParseException => respondDetails(httpServerResponse,
+      ProblemDetails(RequestLevelErrorType.NOT_JSON, SC_BAD_REQUEST, None,
+        s"The content type of the request was not application/json or the request did not parse as I-JSON: ${exception.getMessage}"))
+    case exception: UnsupportedCapabilitiesException => respondDetails(httpServerResponse,
+      ProblemDetails(RequestLevelErrorType.UNKNOWN_CAPABILITY,
+        SC_BAD_REQUEST, None,
+        s"The request used unsupported capabilities: ${exception.capabilities}"))
     case _ => SMono.fromPublisher(handleInternalError(httpServerResponse, throwable))
   }
+
+  private def respondDetails(httpServerResponse: HttpServerResponse, details: ProblemDetails): SMono[Void] =
+    SMono.fromPublisher(httpServerResponse.status(SC_BAD_REQUEST)
+      .header(CONTENT_TYPE, JSON_CONTENT_TYPE)
+      .sendString(SMono.fromCallable(() => serializer.serialize(details).toString),
+        StandardCharsets.UTF_8)
+      .`then`)
 }
+
+case class UnsupportedCapabilitiesException(capabilities: Set[CapabilityIdentifier]) extends RuntimeException
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 37ffb9a..05e4029 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
@@ -38,6 +38,7 @@ import org.apache.james.jmap._
 import org.apache.james.jmap.http.{Authenticator, BasicAuthenticationStrategy}
 import org.apache.james.jmap.json.Serializer
 import org.apache.james.jmap.method.{CoreEchoMethod, Method}
+import org.apache.james.jmap.model.RequestLevelErrorType
 import org.apache.james.jmap.routes.JMAPApiRoutesTest._
 import org.apache.james.mailbox.MailboxManager
 import org.apache.james.mailbox.extension.PreDeletionHook
@@ -45,6 +46,7 @@ import org.apache.james.mailbox.inmemory.MemoryMailboxManagerProvider
 import org.apache.james.mailbox.model.TestId
 import org.apache.james.metrics.tests.RecordingMetricFactory
 import org.apache.james.user.memory.MemoryUsersRepository
+import org.hamcrest.Matchers.equalTo
 import org.mockito.Mockito.mock
 import org.scalatest.BeforeAndAfter
 import org.scalatest.flatspec.AnyFlatSpec
@@ -164,6 +166,32 @@ object JMAPApiRoutesTest {
       |    }
       |}
       |""".stripMargin
+
+  private val NOT_JSON_REQUEST: String =
+    """
+      |{
+      |  "using": [ "urn:ietf:params:jmap:core"],
+      |  "methodCalls": {
+      |      "arg1": "arg1data",
+      |}
+      |""".stripMargin
+
+  private val UNKNOWN_CAPABILITY_REQUEST: String =
+    """
+      |{
+      |  "using": [ "urn:ietf:params:jmap:core1"],
+      |   "methodCalls": [
+      |    [
+      |      "Core/echo",
+      |      {
+      |        "arg1": "arg1data",
+      |        "arg2": "arg2data"
+      |      },
+      |      "c1"
+      |    ]
+      |  ]
+      |}
+      |""".stripMargin
 }
 
 class JMAPApiRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
@@ -199,7 +227,7 @@ class JMAPApiRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
         .headers(headers)
       .when()
         .get
-      .then
+      .`then`
         .statusCode(HttpStatus.SC_NOT_FOUND)
   }
 
@@ -214,7 +242,7 @@ class JMAPApiRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
         .headers(headers)
       .when()
         .post
-      .then
+      .`then`
         .statusCode(HttpStatus.SC_OK)
   }
 
@@ -230,7 +258,7 @@ class JMAPApiRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
         .body(REQUEST_OBJECT)
       .when()
         .post()
-      .then
+      .`then`
         .statusCode(HttpStatus.SC_OK)
         .contentType(ContentType.JSON)
       .extract()
@@ -253,7 +281,7 @@ class JMAPApiRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
         .body(REQUEST_OBJECT_WITH_UNSUPPORTED_METHOD)
       .when()
         .post()
-      .then
+      .`then`
         .statusCode(HttpStatus.SC_OK)
         .contentType(ContentType.JSON)
       .extract()
@@ -274,7 +302,7 @@ class JMAPApiRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
         .headers(headers)
       .when()
         .get
-      .then
+      .`then`
         .statusCode(HttpStatus.SC_NOT_FOUND)
   }
 
@@ -288,7 +316,7 @@ class JMAPApiRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
         .headers(headers)
       .when()
         .post
-      .then
+      .`then`
         .statusCode(HttpStatus.SC_NOT_FOUND)
   }
 
@@ -303,7 +331,47 @@ class JMAPApiRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
         .body(WRONG_OBJECT_REQUEST)
       .when()
         .post
-      .then
+      .`then`
+        .statusCode(HttpStatus.SC_BAD_REQUEST)
+        .body("status", equalTo(400))
+        .body("type", equalTo(RequestLevelErrorType.NOT_REQUEST.value))
+        .body("detail", equalTo("The request was successfully parsed as JSON but did not match the type signature of the Request object: {\"errors\":[{\"path\":\"obj.methodCalls\",\"messages\":[\"error.expected.jsarray\"]}]}"))
+  }
+
+  "RFC-8621 version, POST, with not json request body" should "return 400 status" in {
+    val headers: Headers = Headers.headers(
+      new Header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER),
+      new Header("Authorization", s"Basic ${userBase64String}")
+    )
+    RestAssured
+      .`given`()
+        .headers(headers)
+        .body(NOT_JSON_REQUEST)
+      .when()
+        .post
+      .`then`
+        .statusCode(HttpStatus.SC_BAD_REQUEST)
+        .body("status", equalTo(400))
+        .body("type", equalTo(RequestLevelErrorType.NOT_JSON.value))
+        .body("detail", equalTo("The content type of the request was not application/json or the request did not parse as I-JSON: Unexpected character ('}' (code 125)): was expecting double-quote to start field name\n " +
+          "at [Source: (reactor.netty.ByteBufMono$ReleasingInputStream); line: 6, column: 2]"))
+  }
+
+  "RFC-8621 version, POST, with unknown capability" should "return 400 status" in {
+    val headers: Headers = Headers.headers(
+      new Header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER),
+      new Header("Authorization", s"Basic ${userBase64String}")
+    )
+    RestAssured
+      .`given`()
+        .headers(headers)
+        .body(UNKNOWN_CAPABILITY_REQUEST)
+      .when()
+        .post
+      .`then`
         .statusCode(HttpStatus.SC_BAD_REQUEST)
+        .body("status", equalTo(400))
+        .body("type", equalTo(RequestLevelErrorType.UNKNOWN_CAPABILITY.value))
+        .body("detail", equalTo("The request used unsupported capabilities: Set(urn:ietf:params:jmap:core1)"))
   }
 }


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