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/23 07:53:11 UTC

[james-project] branch master updated (8a4afcf -> 01991fd)

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 8a4afcf  [CHANGELOG] Document recent work
     new 3ae6ff6  JAMES-3177 extract applicable flags update
     new d06faa6  JAMES-3177 group applicableflags with update status
     new ab05880  JAMES-3177 turn updateApplicableFlags into a static method for tests
     new a5e1332  JAMES-3177 port SelectedMailboxImplTest to junit5
     new cbd08be  JAMES-3177 cover updateApplicableFlags with tests
     new bf4ad97  JAMES-3177 refactor to encapsulate applicable flag logic
     new db67bdf  JAMES-3177 make use of a persistent datastructure to avoid most locking in UidMsnConverter
     new eb806e3  JAMES-3177 extraction of method handling events
     new 0747959  JAMES-3177 use vavr pattern matching to simplify the event handling code
     new cd8e790  JAMES-3177 use latest stable vavr library
     new 27d307c  JAMES-3177 Enhance SelectedMailboxImpl applicable flags related tests
     new dc5ecc7  JAMES-3177 Applicable flags updates needs to be thread safe
     new ffab77e  JAMES-3313 Write a Deduplicating blobStore
     new 82206dc  JAMES-3313 Move GC class to a separate class
     new 62d934c  JAMES-3313 MemoryBlobStore should use DeduplicatingBlobStore
     new c18ddb0  JAMES-3313 CassandraBlobStore should use DeDuplicatingBlobStore
     new 20812f2  JAMES-3313 Drop support for Hybrid BlobStore
     new 01991fd  JAMES-3312: Integration test for SessionRoutes

The 18 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:
 CHANGELOG.md                                       |  10 +-
 .../destination/conf/blob.properties               |  11 +-
 .../destination/conf/blob.properties               |  11 +-
 .../mail/CassandraAnnotationMapperTest.java        |   6 +-
 .../CassandraMailboxMapperConcurrencyTest.java     |   2 +
 .../mail/CassandraMailboxMapperGenericTest.java    |   2 +
 .../cassandra/mail/CassandraMessageDAOTest.java    |   5 +-
 .../mailbox/cassandra/mail/utils/GuiceUtils.java   |  22 +-
 .../quota/CassandraPerUserMaxQuotaManagerTest.java |   6 +-
 .../james/vault/DeletedMessageVaultHookTest.java   |   5 +-
 .../blob/BlobStoreDeletedMessageVaultTest.java     |   5 +-
 pom.xml                                            |  12 +-
 protocols/imap/pom.xml                             |   4 +
 .../imap/processor/base/SelectedMailboxImpl.java   | 228 ++++++---
 .../james/imap/processor/base/UidMsnConverter.java |  83 ++--
 .../processor/base/SelectedMailboxImplTest.java    | 180 ++++++-
 .../imap/processor/base/UidMsnConverterTest.java   |  23 +-
 server/blob/blob-cassandra/pom.xml                 |   4 +
 .../james/blob/cassandra/CassandraBlobStore.java   | 137 ------
 .../blob/cassandra/CassandraBlobStoreFactory.java} |  49 +-
 .../blob/cassandra/CassandraDumbBlobStore.java     |   4 +-
 .../blob/cassandra/CassandraBlobStoreTest.java     |   2 +-
 .../blob/cassandra/cache/CachedBlobStoreTest.java  |   4 +-
 server/blob/blob-deduplicating/pom.xml             |  44 +-
 .../deduplication/DeDuplicationBlobStore.scala     | 100 ++++
 .../file/LocalFileBlobExportMechanismTest.java     |   5 +-
 .../doc/gc-properties.adoc                         |   0
 .../blob/{blob-deduplicating => blob-gc}/pom.xml   |  10 +-
 .../james/server/blob/deduplication/GC.scala       |   0
 .../server/blob/deduplication/GCJsonReporter.scala |   0
 .../src/test/resources/gcReport.json               |   0
 .../blob/deduplication/GCJsonReporterTest.scala    |   2 -
 .../blob/deduplication/GCPropertiesTest.scala      |   0
 .../james/server/blob/deduplication/State.scala    |   0
 server/blob/blob-memory/pom.xml                    |   4 +
 .../apache/james/blob/memory/MemoryBlobStore.java  | 116 -----
 .../james/blob/memory/MemoryBlobStoreFactory.java} |  25 +-
 .../james/blob/memory/MemoryBlobStoreTest.java     |   2 +-
 server/blob/blob-union/pom.xml                     |  75 ---
 .../apache/james/blob/union/HybridBlobStore.java   | 239 ---------
 .../james/blob/union/HybridBlobStoreTest.java      | 537 ---------------------
 .../james/blob/mail/MimeMessageStoreTest.java      |   5 +-
 server/blob/pom.xml                                |   2 +-
 .../org/apache/james/modules/BlobMemoryModule.java |  17 +-
 .../CassandraBlobStoreDependenciesModule.java      |   7 +-
 .../modules/mailbox/CassandraBlobStoreModule.java  |  11 +-
 .../guice/cassandra-rabbitmq-guice/pom.xml         |   4 -
 .../modules/blobstore/BlobStoreChoosingModule.java |  23 -
 .../modules/blobstore/BlobStoreConfiguration.java  |   7 +-
 .../modules/blobstore/BlobStoreModulesChooser.java |  54 +--
 .../blobstore/BlobStoreConfigurationTest.java      |  33 +-
 .../blobstore/BlobStoreModulesChooserTest.java     |  64 ---
 .../james/jmap/rfc8621/RFC8621MethodsModule.java   |   5 +-
 .../cassandra/CassandraMailRepositoryTest.java     |   5 +-
 ...aMailRepositoryWithFakeImplementationsTest.java |   7 +-
 ...nTest.java => DistributedSessionRouteTest.java} |   4 +-
 .../jmap/rfc8621/contract/EchoMethodContract.scala |   4 +-
 .../rfc8621/contract/SessionRoutesContract.scala}  | 132 ++---
 ...MethodTest.java => MemorySessionRouteTest.java} |   4 +-
 .../org/apache/james/jmap/http/SessionRoutes.scala |   5 +-
 .../apache/james/jmap/http/SessionRoutesTest.scala |  62 +--
 .../scala/org/apache/james/jmap/json/Fixture.scala |  63 ++-
 .../routes/DeletedMessagesVaultRoutesTest.java     |   8 +-
 .../webadmin/service/ExportServiceTestSystem.java  |   5 +-
 .../RabbitMQMailQueueConfigurationChangeTest.java  |   7 +-
 .../queue/rabbitmq/RabbitMQMailQueueTest.java      |   6 +-
 src/site/xdoc/server/config-blobstore.xml          |   9 -
 .../linshare/LinshareBlobExportMechanismTest.java  |   8 +-
 68 files changed, 732 insertions(+), 1803 deletions(-)
 delete mode 100644 server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraBlobStore.java
 copy server/blob/blob-cassandra/src/{test/java/org/apache/james/blob/cassandra/CassandraDumbBlobStoreTest.java => main/java/org/apache/james/blob/cassandra/CassandraBlobStoreFactory.java} (55%)
 create mode 100644 server/blob/blob-deduplicating/src/main/scala/org/apache/james/server/blob/deduplication/DeDuplicationBlobStore.scala
 rename server/blob/{blob-deduplicating => blob-gc}/doc/gc-properties.adoc (100%)
 copy server/blob/{blob-deduplicating => blob-gc}/pom.xml (92%)
 rename server/blob/{blob-deduplicating => blob-gc}/src/main/scala/org/apache/james/server/blob/deduplication/GC.scala (100%)
 rename server/blob/{blob-deduplicating => blob-gc}/src/main/scala/org/apache/james/server/blob/deduplication/GCJsonReporter.scala (100%)
 rename server/blob/{blob-deduplicating => blob-gc}/src/test/resources/gcReport.json (100%)
 rename server/blob/{blob-deduplicating => blob-gc}/src/test/scala/org/apache/james/server/blob/deduplication/GCJsonReporterTest.scala (99%)
 rename server/blob/{blob-deduplicating => blob-gc}/src/test/scala/org/apache/james/server/blob/deduplication/GCPropertiesTest.scala (100%)
 rename server/blob/{blob-deduplicating => blob-gc}/src/test/scala/org/apache/james/server/blob/deduplication/State.scala (100%)
 delete mode 100644 server/blob/blob-memory/src/main/java/org/apache/james/blob/memory/MemoryBlobStore.java
 copy server/{container/guice/blob-memory-guice/src/main/java/org/apache/james/modules/BlobMemoryModule.java => blob/blob-memory/src/main/java/org/apache/james/blob/memory/MemoryBlobStoreFactory.java} (70%)
 delete mode 100644 server/blob/blob-union/pom.xml
 delete mode 100644 server/blob/blob-union/src/main/java/org/apache/james/blob/union/HybridBlobStore.java
 delete mode 100644 server/blob/blob-union/src/test/java/org/apache/james/blob/union/HybridBlobStoreTest.java
 copy server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/{DistributedAuthenticationTest.java => DistributedSessionRouteTest.java} (94%)
 copy server/protocols/{jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala => jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/SessionRoutesContract.scala} (64%)
 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 => MemorySessionRouteTest.java} (93%)


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


[james-project] 04/18: JAMES-3177 port SelectedMailboxImplTest to junit5

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 a5e1332f07c700cbfe06d76ecaaabbaa60e77e80
Author: Matthieu Baechler <ma...@apache.org>
AuthorDate: Tue Jun 16 15:42:25 2020 +0200

    JAMES-3177 port SelectedMailboxImplTest to junit5
---
 .../processor/base/SelectedMailboxImplTest.java    | 42 +++++++++++-----------
 1 file changed, 21 insertions(+), 21 deletions(-)

diff --git a/protocols/imap/src/test/java/org/apache/james/imap/processor/base/SelectedMailboxImplTest.java b/protocols/imap/src/test/java/org/apache/james/imap/processor/base/SelectedMailboxImplTest.java
index eb07d08..de8b3ef 100644
--- a/protocols/imap/src/test/java/org/apache/james/imap/processor/base/SelectedMailboxImplTest.java
+++ b/protocols/imap/src/test/java/org/apache/james/imap/processor/base/SelectedMailboxImplTest.java
@@ -57,10 +57,10 @@ import org.apache.james.mailbox.model.UpdatedFlags;
 import org.apache.james.mailbox.store.event.EventFactory;
 import org.apache.james.mailbox.store.mail.model.DefaultMessageId;
 import org.apache.james.util.concurrent.NamedThreadFactory;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
 import org.mockito.stubbing.Answer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -68,7 +68,7 @@ import org.slf4j.LoggerFactory;
 import reactor.core.publisher.Mono;
 
 
-public class SelectedMailboxImplTest {
+class SelectedMailboxImplTest {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(SelectedMailboxImplTest.class);
     private static final MessageUid EMITTED_EVENT_UID = MessageUid.of(5);
@@ -86,8 +86,8 @@ public class SelectedMailboxImplTest {
     private EventBus eventBus;
     private MailboxIdRegistrationKey mailboxIdRegistrationKey;
 
-    @Before
-    public void setUp() throws Exception {
+    @BeforeEach
+    void setUp() throws Exception {
         ThreadFactory threadFactory = NamedThreadFactory.withClassName(getClass());
         executorService = Executors.newFixedThreadPool(1, threadFactory);
         mailboxPath = MailboxPath.inbox(Username.of("tellier@linagora.com"));
@@ -113,13 +113,13 @@ public class SelectedMailboxImplTest {
         when(mailbox.getMailboxId()).thenReturn(mailboxId);
     }
 
-    @After
-    public void tearDown() {
+    @AfterEach
+    void tearDown() {
         executorService.shutdownNow();
     }
 
     @Test
-    public void concurrentEventShouldNotSkipAddedEventsEmittedDuringInitialisation() throws Exception {
+    void concurrentEventShouldNotSkipAddedEventsEmittedDuringInitialisation() throws Exception {
         AtomicInteger successCount = new AtomicInteger(0);
         doAnswer(generateEmitEventAnswer(successCount))
             .when(eventBus)
@@ -133,9 +133,9 @@ public class SelectedMailboxImplTest {
         assertThat(selectedMailbox.getLastUid().get()).isEqualTo(EMITTED_EVENT_UID);
     }
 
-    @Ignore("JAMES-3177 SelectedMailboxImpl is not thread safe")
+    @Disabled("JAMES-3177 SelectedMailboxImpl is not thread safe")
     @Test
-    public void customFlagsEventShouldNotFailWhenConcurrentWithCreation() throws Exception {
+    void customFlagsEventShouldNotFailWhenConcurrentWithCreation() throws Exception {
         AtomicInteger successCount = new AtomicInteger(0);
         doAnswer(generateEmitCustomFlagEventAnswer(successCount))
             .when(eventBus)
@@ -146,9 +146,9 @@ public class SelectedMailboxImplTest {
         assertThat(successCount.get()).isEqualTo(1);
     }
 
-    @Ignore("JAMES-3177 SelectedMailboxImpl is not thread safe")
+    @Disabled("JAMES-3177 SelectedMailboxImpl is not thread safe")
     @Test
-    public void applicableFlagsShouldBeWellUpdatedWhenConcurrentWithCreation() throws Exception {
+    void applicableFlagsShouldBeWellUpdatedWhenConcurrentWithCreation() throws Exception {
         AtomicInteger successCount = new AtomicInteger(0);
         doAnswer(generateEmitCustomFlagEventAnswer(successCount))
             .when(eventBus)
@@ -160,7 +160,7 @@ public class SelectedMailboxImplTest {
     }
 
     @Test
-    public void concurrentEventShouldBeProcessedSuccessfullyDuringInitialisation() throws Exception {
+    void concurrentEventShouldBeProcessedSuccessfullyDuringInitialisation() throws Exception {
         AtomicInteger successCount = new AtomicInteger(0);
         doAnswer(generateEmitEventAnswer(successCount))
             .when(eventBus)
@@ -177,22 +177,22 @@ public class SelectedMailboxImplTest {
             .isEqualTo(1);
     }
 
-    private Answer<Stream<MessageUid>> delayedSearchAnswer() {
+    Answer<Stream<MessageUid>> delayedSearchAnswer() {
         return invocation -> {
             Thread.sleep(1000);
             return Stream.of(MessageUid.of(1), MessageUid.of(3));
         };
     }
 
-    private Answer<Mono<Registration>> generateEmitEventAnswer(AtomicInteger success) {
+    Answer<Mono<Registration>> generateEmitEventAnswer(AtomicInteger success) {
         return generateEmitEventAnswer(event(), success);
     }
 
-    private Answer<Mono<Registration>> generateEmitCustomFlagEventAnswer(AtomicInteger success) {
+    Answer<Mono<Registration>> generateEmitCustomFlagEventAnswer(AtomicInteger success) {
         return generateEmitEventAnswer(customFlagEvent(), success);
     }
 
-    private Answer<Mono<Registration>> generateEmitEventAnswer(Event event, AtomicInteger success) {
+    Answer<Mono<Registration>> generateEmitEventAnswer(Event event, AtomicInteger success) {
         return invocation -> {
             Object[] args = invocation.getArguments();
             MailboxListener mailboxListener = (MailboxListener) args[0];
@@ -208,7 +208,7 @@ public class SelectedMailboxImplTest {
         };
     }
 
-    private Event event() {
+    Event event() {
         return EventFactory.added()
             .randomEventId()
             .mailboxSession(MailboxSessionUtil.create(Username.of("user")))
@@ -217,7 +217,7 @@ public class SelectedMailboxImplTest {
             .build();
     }
 
-    private Event customFlagEvent() {
+    Event customFlagEvent() {
         return EventFactory.flagsUpdated()
             .randomEventId()
             .mailboxSession(MailboxSessionUtil.create(Username.of("user")))


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


[james-project] 18/18: JAMES-3312: Integration test for SessionRoutes

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 01991fdca16d9ef8c8597478f1dd7ddb3ddbe199
Author: duc91 <vd...@linagora.com>
AuthorDate: Wed Jul 22 10:43:08 2020 +0700

    JAMES-3312: Integration test for SessionRoutes
---
 .../james/jmap/rfc8621/RFC8621MethodsModule.java   |   5 +-
 .../distributed/DistributedSessionRouteTest.java   |  52 ++++++++
 .../jmap/rfc8621/contract/EchoMethodContract.scala |   4 +-
 .../rfc8621/contract/SessionRoutesContract.scala}  | 132 ++++++++-------------
 .../rfc8621/memory/MemorySessionRouteTest.java     |  38 ++++++
 .../org/apache/james/jmap/http/SessionRoutes.scala |   5 +-
 .../apache/james/jmap/http/SessionRoutesTest.scala |  62 +---------
 .../scala/org/apache/james/jmap/json/Fixture.scala |  63 +++++++++-
 8 files changed, 209 insertions(+), 152 deletions(-)

diff --git a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
index 5cd2117..4c856da 100644
--- a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
+++ b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
@@ -24,6 +24,7 @@ import org.apache.james.jmap.JMAPRoutesHandler;
 import org.apache.james.jmap.Version;
 import org.apache.james.jmap.http.Authenticator;
 import org.apache.james.jmap.http.BasicAuthenticationStrategy;
+import org.apache.james.jmap.http.SessionRoutes;
 import org.apache.james.jmap.http.rfc8621.InjectionKeys;
 import org.apache.james.jmap.json.Serializer;
 import org.apache.james.jmap.jwt.JWTAuthenticationStrategy;
@@ -53,8 +54,8 @@ public class RFC8621MethodsModule extends AbstractModule {
     }
 
     @ProvidesIntoSet
-    JMAPRoutesHandler routesHandler(JMAPApiRoutes jmapApiRoutes) {
-        return new JMAPRoutesHandler(Version.RFC8621, jmapApiRoutes);
+    JMAPRoutesHandler routesHandler(SessionRoutes sessionRoutes, JMAPApiRoutes jmapApiRoutes) {
+        return new JMAPRoutesHandler(Version.RFC8621, jmapApiRoutes, sessionRoutes);
     }
 
     @Provides
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
new file mode 100644
index 0000000..ef34f84
--- /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/DistributedSessionRouteTest.java
@@ -0,0 +1,52 @@
+/****************************************************************
+ * 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.SearchConfiguration;
+import org.apache.james.jmap.rfc8621.contract.SessionRoutesContract;
+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 DistributedSessionRouteTest implements SessionRoutesContract {
+    @RegisterExtension
+    static JamesServerExtension testExtension = new JamesServerBuilder<CassandraRabbitMQJamesConfiguration>(tmpDir ->
+        CassandraRabbitMQJamesConfiguration.builder()
+            .workingDirectory(tmpDir)
+            .configurationFromClasspath()
+            .blobStore(BlobStoreConfiguration.objectStorage().disableCache())
+            .searchConfiguration(SearchConfiguration.elasticSearch())
+            .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/EchoMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EchoMethodContract.scala
index 509106d..2306bc7 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EchoMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EchoMethodContract.scala
@@ -102,7 +102,7 @@ trait EchoMethodContract {
         .body(ECHO_REQUEST_OBJECT)
       .when()
         .post()
-      .then
+      .`then`
         .statusCode(SC_OK)
         .contentType(JSON)
       .extract()
@@ -119,7 +119,7 @@ trait EchoMethodContract {
         .body(REQUEST_OBJECT_WITH_UNSUPPORTED_METHOD)
       .when()
         .post()
-      .then
+      .`then`
         .statusCode(SC_OK)
         .contentType(JSON)
       .extract()
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/SessionRoutesContract.scala
similarity index 64%
copy from server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
copy to server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/SessionRoutesContract.scala
index dad6b10..2155401 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/SessionRoutesContract.scala
@@ -16,91 +16,24 @@
  * specific language governing permissions and limitations      *
  * under the License.                                           *
  * ***************************************************************/
-
-package org.apache.james.jmap.http
-
-import java.nio.charset.StandardCharsets
+package org.apache.james.jmap.rfc8621.contract
 
 import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
-import io.restassured.RestAssured
-import io.restassured.builder.RequestSpecBuilder
-import io.restassured.config.EncoderConfig.encoderConfig
-import io.restassured.config.RestAssuredConfig.newConfig
-import io.restassured.http.ContentType
-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.Serializer
-import org.apache.james.mailbox.MailboxSession
-import org.apache.james.mailbox.model.TestId
-import org.mockito.ArgumentMatchers.any
-import org.mockito.Mockito._
-import org.scalatest.BeforeAndAfter
-import org.scalatest.flatspec.AnyFlatSpec
-import org.scalatest.matchers.should.Matchers
-import play.api.libs.json.Json
-import reactor.core.publisher.Mono
-
-import scala.jdk.CollectionConverters._
-
-object SessionRoutesTest {
-  private val JMAP_SESSION = "/jmap/session"
-  private val TEST_CONFIGURATION = JMAPConfiguration.builder.enable.randomPort.build
-  private val BOB = Username.of("bob@james.org")
-}
-
-class SessionRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
-
-  var jmapServer: JMAPServer = _
+import io.restassured.RestAssured._
+import io.restassured.http.ContentType.JSON
+import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
+import org.apache.http.HttpStatus.SC_OK
+import org.apache.james.GuiceJamesServer
+import org.apache.james.jmap.http.UserCredential
+import org.apache.james.jmap.rfc8621.contract.Fixture._
+import org.apache.james.jmap.rfc8621.contract.SessionRoutesContract.session_object_json_expected
+import org.apache.james.jmap.rfc8621.contract.tags.CategoryTags
+import org.apache.james.utils.DataProbeImpl
+import org.junit.jupiter.api.{BeforeEach, Tag, Test}
+import play.api.libs.json.{JsValue, Json}
 
-  before {
-    val mockedSession = mock(classOf[MailboxSession])
-    when(mockedSession.getUser)
-      .thenReturn(BOB)
-
-    val mockedAuthFilter = mock(classOf[Authenticator])
-    when(mockedAuthFilter.authenticate(any()))
-      .thenReturn(Mono.just(mockedSession))
-
-    val sessionRoutes = new SessionRoutes(
-      serializer = new Serializer(new TestId.Factory),
-      sessionSupplier = new SessionSupplier(),
-      authenticator = mockedAuthFilter)
-    jmapServer = new JMAPServer(
-      TEST_CONFIGURATION,
-      Set(new JMAPRoutesHandler(Version.RFC8621, sessionRoutes)).asJava,
-      new VersionParser(Set(Version.RFC8621).asJava))
-    jmapServer.start()
-
-    RestAssured.requestSpecification = new RequestSpecBuilder()
-      .setContentType(ContentType.JSON)
-      .addHeader(ACCEPT.toString, s"application/json; jmapVersion=${Version.RFC8621.asString}")
-      .setConfig(newConfig.encoderConfig(encoderConfig.defaultContentCharset(StandardCharsets.UTF_8)))
-      .setPort(jmapServer.getPort.getValue)
-      .setBasePath(SessionRoutesTest.JMAP_SESSION)
-      .build()
-  }
-
-  after {
-    jmapServer.stop()
-  }
-
-  "get" should "return OK status" in {
-    RestAssured.when()
-      .get
-    .then
-      .statusCode(HttpStatus.SC_OK)
-      .contentType(ContentType.JSON)
-  }
-
-  "get" should "return correct session" in {
-    val sessionJson = RestAssured.`with`()
-        .get
-      .thenReturn
-        .getBody
-        .asString()
-    val expectedJson = """{
+object SessionRoutesContract {
+  private val expected_session_object = """{
                          |  "capabilities" : {
                          |    "urn:ietf:params:jmap:core" : {
                          |      "maxSizeUpload" : 10000000,
@@ -159,7 +92,38 @@ class SessionRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
                          |  "eventSourceUrl" : "http://this-url-is-hardcoded.org/eventSource",
                          |  "state" : "000001"
                          |}""".stripMargin
+  private val session_object_json_expected: JsValue = Json.parse(expected_session_object)
+}
+
+trait SessionRoutesContract {
 
-    Json.parse(sessionJson) should equal(Json.parse(expectedJson))
+  @BeforeEach
+  def setUp(server: GuiceJamesServer): Unit = {
+    server.getProbe(classOf[DataProbeImpl])
+      .fluent
+      .addDomain(DOMAIN.asString)
+      .addUser(BOB.asString, BOB_PASSWORD)
+      .addUser(ANDRE.asString, ANDRE_PASSWORD)
+
+    requestSpecification = baseRequestSpecBuilder(server)
+        .setAuth(authScheme(UserCredential(BOB, BOB_PASSWORD)))
+      .build
   }
-}
+
+  @Test
+  @Tag(CategoryTags.BASIC_FEATURE)
+  def getShouldReturnCorrectSession() {
+    val sessionJson: String = `given`()
+      .when()
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .get("/session")
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+      .extract()
+        .body()
+        .asString()
+
+    assertThatJson(Json.parse(sessionJson)).isEqualTo(session_object_json_expected)
+  }
+}
\ No newline at end of file
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/MemorySessionRouteTest.java b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemorySessionRouteTest.java
new file mode 100644
index 0000000..b4de0c7
--- /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/MemorySessionRouteTest.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.SessionRoutesContract;
+import org.apache.james.modules.TestJMAPServerModule;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class MemorySessionRouteTest implements SessionRoutesContract {
+    @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/http/SessionRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala
index a7efeab..047e8da 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala
@@ -42,12 +42,11 @@ import reactor.core.scheduler.Schedulers
 import reactor.netty.http.server.HttpServerResponse
 
 object SessionRoutes {
-  private val JMAP_SESSION = "/jmap/session"
+  private val JMAP_SESSION: String = "/jmap/session"
   private val LOGGER = LoggerFactory.getLogger(classOf[SessionRoutes])
 }
 
-@Inject
-class SessionRoutes(@Named(InjectionKeys.RFC_8621) val authenticator: Authenticator,
+class SessionRoutes @Inject() (@Named(InjectionKeys.RFC_8621) val authenticator: Authenticator,
                     val serializer: Serializer,
                     val sessionSupplier: SessionSupplier = new SessionSupplier()) extends JMAPRoutes {
 
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 dad6b10..0501f8b 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,6 +31,7 @@ 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
@@ -100,66 +101,7 @@ 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
-                         |    }
-                         |  },
-                         |  "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
-                         |        }
-                         |      }
-                         |    }
-                         |  },
-                         |  "primaryAccounts" : {
-                         |    "urn:ietf:params:jmap:core" : "0fe275bf13ff761407c17f64b1dfae2f4b3186feea223d7267b79f873a105401",
-                         |    "urn:ietf:params:jmap:mail" : "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(Json.parse(expectedJson))
+    Json.parse(sessionJson) should equal(expected_session_object_json)
   }
 }
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/Fixture.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/Fixture.scala
index e249535..278d247 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/Fixture.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/Fixture.scala
@@ -24,7 +24,7 @@ import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
 import org.apache.james.jmap.model.Id.Id
 import org.apache.james.jmap.model.Invocation.{Arguments, MethodCallId, MethodName}
 import org.apache.james.jmap.model.{ClientId, CreatedIds, Invocation, ResponseObject, ServerId}
-import play.api.libs.json.Json
+import play.api.libs.json.{JsValue, Json}
 
 object Fixture {
   val id: Id = "aHR0cHM6Ly93d3cuYmFzZTY0ZW5jb2RlLm9yZy8"
@@ -47,4 +47,65 @@ object Fixture {
   val responseObject1: ResponseObject = ResponseObject(ResponseObject.SESSION_STATE, Seq(invocation1))
   val responseObject2: ResponseObject = ResponseObject(ResponseObject.SESSION_STATE, Seq(invocation2))
   val responseObjectWithUnsupportedMethod: ResponseObject = ResponseObject(ResponseObject.SESSION_STATE, Seq(invocation1, unsupportedInvocation))
+
+  val expected_session_object: String = """{
+                         |  "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
+                         |    }
+                         |  },
+                         |  "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
+                         |        }
+                         |      }
+                         |    }
+                         |  },
+                         |  "primaryAccounts" : {
+                         |    "urn:ietf:params:jmap:core" : "0fe275bf13ff761407c17f64b1dfae2f4b3186feea223d7267b79f873a105401",
+                         |    "urn:ietf:params:jmap:mail" : "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
+  val expected_session_object_json: JsValue = Json.parse(expected_session_object)
 }


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


[james-project] 02/18: JAMES-3177 group applicableflags with update status

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 d06faa6746002d4249c09f5adf71c4bca4773c81
Author: Matthieu Baechler <ma...@apache.org>
AuthorDate: Tue Jun 16 15:36:16 2020 +0200

    JAMES-3177 group applicableflags with update status
    
    	This does not change how the class behaves but it will help
    	for testing as updateApplicableFlags will become a pure function
---
 .../imap/processor/base/SelectedMailboxImpl.java   | 59 ++++++++++++++++++----
 1 file changed, 48 insertions(+), 11 deletions(-)

diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java
index 535e8a5..3f73abc 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java
@@ -59,6 +59,39 @@ import reactor.core.scheduler.Schedulers;
  * Default implementation of {@link SelectedMailbox}
  */
 public class SelectedMailboxImpl implements SelectedMailbox, MailboxListener {
+
+
+    private static class ApplicableFlags {
+        static ApplicableFlags from(Flags flags) {
+            boolean updated = false;
+            return new ApplicableFlags(flags, updated);
+        }
+
+        private final Flags flags;
+        private final boolean updated;
+
+        private ApplicableFlags(Flags flags, boolean updated) {
+            this.flags = flags;
+            this.updated = updated;
+        }
+
+        public ApplicableFlags ackUpdates() {
+            return new ApplicableFlags(flags, false);
+        }
+
+        public Flags flags() {
+            return new Flags(flags);
+        }
+
+        public boolean updated() {
+            return updated;
+        }
+
+        public ApplicableFlags update(boolean applicableFlagsChanged) {
+            return new ApplicableFlags(flags, true);
+        }
+    }
+
     private final Registration registration;
     private final MailboxManager mailboxManager;
     private final MailboxId mailboxId;
@@ -75,8 +108,7 @@ public class SelectedMailboxImpl implements SelectedMailbox, MailboxListener {
     private boolean isDeletedByOtherSession = false;
     private boolean sizeChanged = false;
     private boolean silentFlagChanges = false;
-    private final Flags applicableFlags;
-    private boolean applicableFlagsChanged;
+    private ApplicableFlags applicableFlags;
 
     public SelectedMailboxImpl(MailboxManager mailboxManager, EventBus eventBus, ImapSession session, MessageManager messageManager) throws MailboxException {
         this.session = session;
@@ -96,7 +128,7 @@ public class SelectedMailboxImpl implements SelectedMailbox, MailboxListener {
             .subscribeOn(Schedulers.elastic())
             .block();
 
-        applicableFlags = messageManager.getApplicableFlags(mailboxSession);
+        applicableFlags = ApplicableFlags.from(messageManager.getApplicableFlags(mailboxSession));
         try (Stream<MessageUid> stream = messageManager.search(SearchQuery.of(SearchQuery.all()), mailboxSession)) {
             uidMsnConverter.addAll(stream.collect(Guavate.toImmutableList()));
         }
@@ -185,7 +217,7 @@ public class SelectedMailboxImpl implements SelectedMailbox, MailboxListener {
         sizeChanged = false;
         flagUpdateUids.clear();
         isDeletedByOtherSession = false;
-        applicableFlagsChanged = false;
+        applicableFlags = applicableFlags.ackUpdates();
     }
 
     @Override
@@ -290,19 +322,19 @@ public class SelectedMailboxImpl implements SelectedMailbox, MailboxListener {
 
     @Override
     public synchronized Flags getApplicableFlags() {
-        return new Flags(applicableFlags);
+        return applicableFlags.flags();
     }
 
     
     @Override
     public synchronized boolean hasNewApplicableFlags() {
-        return applicableFlagsChanged;
+        return applicableFlags.updated();
     }
 
     
     @Override
     public synchronized void resetNewApplicableFlags() {
-        applicableFlagsChanged = false;
+        applicableFlags = applicableFlags.ackUpdates();
     }
 
     
@@ -381,22 +413,27 @@ public class SelectedMailboxImpl implements SelectedMailbox, MailboxListener {
     }
 
     private void updateApplicableFlags(FlagsUpdated messageEvent) {
-        int size = applicableFlags.getUserFlags().length;
+        Flags updatedFlags = applicableFlags.flags();
+        int size = updatedFlags.getUserFlags().length;
         FlagsUpdated updatedF = messageEvent;
         List<UpdatedFlags> flags = updatedF.getUpdatedFlags();
 
         for (UpdatedFlags flag : flags) {
-            applicableFlags.add(flag.getNewFlags());
+            updatedFlags.add(flag.getNewFlags());
 
         }
 
         // \RECENT is not a applicable flag in imap so remove it
         // from the list
-        applicableFlags.remove(Flag.RECENT);
+        updatedFlags.remove(Flag.RECENT);
 
-        if (size < applicableFlags.getUserFlags().length) {
+        boolean applicableFlagsChanged;
+        if (size < updatedFlags.getUserFlags().length) {
             applicableFlagsChanged = true;
+        } else {
+            applicableFlagsChanged = false;
         }
+        applicableFlags = ApplicableFlags.from(updatedFlags).update(applicableFlagsChanged);
     }
 
     @Override


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


[james-project] 03/18: JAMES-3177 turn updateApplicableFlags into a static method for tests

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 ab05880c4da73240110862fad75237d566c19887
Author: Matthieu Baechler <ma...@apache.org>
AuthorDate: Tue Jun 16 15:39:39 2020 +0200

    JAMES-3177 turn updateApplicableFlags into a static method for tests
---
 .../james/imap/processor/base/SelectedMailboxImpl.java      | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java
index 3f73abc..88f1ece 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java
@@ -51,6 +51,7 @@ import org.apache.james.mailbox.model.SearchQuery;
 import org.apache.james.mailbox.model.UpdatedFlags;
 
 import com.github.steveash.guavate.Guavate;
+import com.google.common.annotations.VisibleForTesting;
 
 import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Schedulers;
@@ -61,7 +62,8 @@ import reactor.core.scheduler.Schedulers;
 public class SelectedMailboxImpl implements SelectedMailbox, MailboxListener {
 
 
-    private static class ApplicableFlags {
+    @VisibleForTesting
+    static class ApplicableFlags {
         static ApplicableFlags from(Flags flags) {
             boolean updated = false;
             return new ApplicableFlags(flags, updated);
@@ -88,7 +90,7 @@ public class SelectedMailboxImpl implements SelectedMailbox, MailboxListener {
         }
 
         public ApplicableFlags update(boolean applicableFlagsChanged) {
-            return new ApplicableFlags(flags, true);
+            return new ApplicableFlags(flags, applicableFlagsChanged);
         }
     }
 
@@ -398,7 +400,7 @@ public class SelectedMailboxImpl implements SelectedMailbox, MailboxListener {
                         }
                     }
 
-                    updateApplicableFlags((FlagsUpdated) messageEvent);
+                    applicableFlags = updateApplicableFlags(applicableFlags, (FlagsUpdated) messageEvent);
 
                 } else if (messageEvent instanceof Expunged) {
                     expungedUids.addAll(messageEvent.getUids());
@@ -412,7 +414,8 @@ public class SelectedMailboxImpl implements SelectedMailbox, MailboxListener {
         }
     }
 
-    private void updateApplicableFlags(FlagsUpdated messageEvent) {
+    @VisibleForTesting
+    static ApplicableFlags updateApplicableFlags(ApplicableFlags applicableFlags, FlagsUpdated messageEvent) {
         Flags updatedFlags = applicableFlags.flags();
         int size = updatedFlags.getUserFlags().length;
         FlagsUpdated updatedF = messageEvent;
@@ -433,7 +436,7 @@ public class SelectedMailboxImpl implements SelectedMailbox, MailboxListener {
         } else {
             applicableFlagsChanged = false;
         }
-        applicableFlags = ApplicableFlags.from(updatedFlags).update(applicableFlagsChanged);
+        return ApplicableFlags.from(updatedFlags).update(applicableFlagsChanged);
     }
 
     @Override


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


[james-project] 09/18: JAMES-3177 use vavr pattern matching to simplify the event handling code

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 0747959b68159b53a84f444ae4f0d53972bff3d0
Author: Matthieu Baechler <ma...@apache.org>
AuthorDate: Tue Jun 16 18:07:25 2020 +0200

    JAMES-3177 use vavr pattern matching to simplify the event handling code
---
 .../imap/processor/base/SelectedMailboxImpl.java   | 58 ++++++++++++----------
 1 file changed, 31 insertions(+), 27 deletions(-)

diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java
index f7180a2..91fe286 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java
@@ -19,6 +19,11 @@
 
 package org.apache.james.imap.processor.base;
 
+import static io.vavr.API.$;
+import static io.vavr.API.Case;
+import static io.vavr.API.Match;
+import static io.vavr.Predicates.instanceOf;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -63,6 +68,8 @@ import reactor.core.scheduler.Schedulers;
 public class SelectedMailboxImpl implements SelectedMailbox, MailboxListener {
 
 
+    private static final Void VOID = null;
+
     @VisibleForTesting
     static class ApplicableFlags {
         static ApplicableFlags from(Flags flags) {
@@ -362,37 +369,35 @@ public class SelectedMailboxImpl implements SelectedMailbox, MailboxListener {
     private void mailboxEvent(MailboxEvent mailboxEvent) {
         // Check if the event was for the mailbox we are observing
         if (mailboxEvent.getMailboxId().equals(getMailboxId())) {
-            MailboxSession.SessionId eventSessionId = mailboxEvent.getSessionId();
-            if (mailboxEvent instanceof MessageEvent) {
-                final MessageEvent messageEvent = (MessageEvent) mailboxEvent;
-                if (messageEvent instanceof Added) {
-                    final Collection<MessageUid> uids = ((Added) mailboxEvent).getUids();
-                    handleAddition(uids);
-                } else if (messageEvent instanceof FlagsUpdated) {
-                    FlagsUpdated updated = (FlagsUpdated) messageEvent;
-                    handleFlagsUpdates(mailboxEvent, eventSessionId, (FlagsUpdated) messageEvent, updated);
-                } else if (messageEvent instanceof Expunged) {
-                    handleMailboxExpunge(messageEvent);
-                }
-            } else if (mailboxEvent instanceof MailboxDeletion) {
-                handleMailboxDeletion(eventSessionId);
-            }
+            Match(mailboxEvent).of(
+                Case($(instanceOf(Added.class)),
+                    this::handleAddition),
+                Case($(instanceOf(FlagsUpdated.class)),
+                    this::handleFlagsUpdates),
+                Case($(instanceOf(Expunged.class)),
+                    this::handleMailboxExpunge),
+                Case($(instanceOf(MailboxDeletion.class)),
+                    this::handleMailboxDeletion),
+                Case($(), VOID)
+            );
         }
     }
 
-    private void handleMailboxDeletion(MailboxSession.SessionId eventSessionId) {
-        if (eventSessionId != sessionId) {
+    private Void handleMailboxDeletion(MailboxDeletion mailboxDeletion) {
+        if (mailboxDeletion.getSessionId() != sessionId) {
             isDeletedByOtherSession = true;
         }
+        return VOID;
     }
 
-    private void handleMailboxExpunge(MessageEvent messageEvent) {
+    private Void handleMailboxExpunge(MessageEvent messageEvent) {
         expungedUids.addAll(messageEvent.getUids());
+        return VOID;
     }
 
-    private void handleFlagsUpdates(MailboxEvent mailboxEvent, MailboxSession.SessionId eventSessionId, FlagsUpdated messageEvent, FlagsUpdated updated) {
+    private Void handleFlagsUpdates(FlagsUpdated updated) {
         List<UpdatedFlags> uFlags = updated.getUpdatedFlags();
-        if (sessionId != eventSessionId || !silentFlagChanges) {
+        if (sessionId != updated.getSessionId() || !silentFlagChanges) {
 
             for (UpdatedFlags u : uFlags) {
                 if (interestingFlags(u)) {
@@ -414,28 +419,27 @@ public class SelectedMailboxImpl implements SelectedMailbox, MailboxListener {
                 while (flags.hasNext()) {
                     if (Flag.RECENT.equals(flags.next())) {
                         MailboxId id = sm.getMailboxId();
-                        if (id != null && id.equals(mailboxEvent.getMailboxId())) {
+                        if (id != null && id.equals(updated.getMailboxId())) {
                             sm.addRecent(u.getUid());
                         }
                     }
                 }
-
-
             }
         }
-
-        applicableFlags = updateApplicableFlags(applicableFlags, messageEvent);
+        applicableFlags = updateApplicableFlags(applicableFlags, updated);
+        return VOID;
     }
 
-    private void handleAddition(Collection<MessageUid> uids) {
+    private Void handleAddition(Added added) {
         sizeChanged = true;
         SelectedMailbox sm = session.getSelected();
-        for (MessageUid uid : uids) {
+        for (MessageUid uid : added.getUids()) {
             uidMsnConverter.addUid(uid);
             if (sm != null) {
                 sm.addRecent(uid);
             }
         }
+        return VOID;
     }
 
     @VisibleForTesting


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


[james-project] 16/18: JAMES-3313 CassandraBlobStore should use DeDuplicatingBlobStore

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 c18ddb062a90ca51c8823931512fe4ac6e780811
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Jul 17 17:28:02 2020 +0700

    JAMES-3313 CassandraBlobStore should use DeDuplicatingBlobStore
---
 .../mail/CassandraAnnotationMapperTest.java        |   6 +-
 .../CassandraMailboxMapperConcurrencyTest.java     |   2 +
 .../mail/CassandraMailboxMapperGenericTest.java    |   2 +
 .../cassandra/mail/CassandraMessageDAOTest.java    |   5 +-
 .../mailbox/cassandra/mail/utils/GuiceUtils.java   |  22 +---
 .../quota/CassandraPerUserMaxQuotaManagerTest.java |   6 +-
 server/blob/blob-cassandra/pom.xml                 |   4 +
 .../james/blob/cassandra/CassandraBlobStore.java   | 137 ---------------------
 .../blob/cassandra/CassandraBlobStoreFactory.java} |  32 +++--
 .../blob/cassandra/CassandraDumbBlobStore.java     |   4 +-
 .../blob/cassandra/CassandraBlobStoreTest.java     |   2 +-
 .../blob/cassandra/cache/CachedBlobStoreTest.java  |   4 +-
 .../CassandraBlobStoreDependenciesModule.java      |   7 +-
 .../modules/mailbox/CassandraBlobStoreModule.java  |  11 +-
 .../modules/blobstore/BlobStoreModulesChooser.java |   5 +-
 .../cassandra/CassandraMailRepositoryTest.java     |   5 +-
 ...aMailRepositoryWithFakeImplementationsTest.java |   7 +-
 .../RabbitMQMailQueueConfigurationChangeTest.java  |   7 +-
 .../queue/rabbitmq/RabbitMQMailQueueTest.java      |   6 +-
 19 files changed, 80 insertions(+), 194 deletions(-)

diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraAnnotationMapperTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraAnnotationMapperTest.java
index deb914c..93d5c8a 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraAnnotationMapperTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraAnnotationMapperTest.java
@@ -20,6 +20,8 @@
 package org.apache.james.mailbox.cassandra.mail;
 
 import org.apache.james.backends.cassandra.CassandraClusterExtension;
+import org.apache.james.backends.cassandra.components.CassandraModule;
+import org.apache.james.blob.cassandra.CassandraBlobModule;
 import org.apache.james.mailbox.cassandra.ids.CassandraId;
 import org.apache.james.mailbox.cassandra.mail.utils.GuiceUtils;
 import org.apache.james.mailbox.cassandra.modules.CassandraAnnotationModule;
@@ -31,7 +33,9 @@ import org.junit.jupiter.api.extension.RegisterExtension;
 class CassandraAnnotationMapperTest extends AnnotationMapperTest {
 
     @RegisterExtension
-    static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(CassandraAnnotationModule.MODULE);
+    static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(CassandraModule.aggregateModules(
+        CassandraBlobModule.MODULE,
+        CassandraAnnotationModule.MODULE));
 
     @Override
     protected AnnotationMapper createAnnotationMapper() {
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperConcurrencyTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperConcurrencyTest.java
index 313fdc2..70fc8b0 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperConcurrencyTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperConcurrencyTest.java
@@ -28,6 +28,7 @@ import org.apache.james.backends.cassandra.CassandraCluster;
 import org.apache.james.backends.cassandra.CassandraClusterExtension;
 import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
+import org.apache.james.blob.cassandra.CassandraBlobModule;
 import org.apache.james.core.Username;
 import org.apache.james.mailbox.cassandra.mail.utils.GuiceUtils;
 import org.apache.james.mailbox.cassandra.modules.CassandraAclModule;
@@ -50,6 +51,7 @@ class CassandraMailboxMapperConcurrencyTest {
     @RegisterExtension
     static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(
         CassandraModule.aggregateModules(
+            CassandraBlobModule.MODULE,
             CassandraSchemaVersionModule.MODULE,
             CassandraMailboxModule.MODULE,
             CassandraAclModule.MODULE));
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperGenericTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperGenericTest.java
index 87c6fc3..673db45 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperGenericTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperGenericTest.java
@@ -24,6 +24,7 @@ import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionDAO;
 import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
 import org.apache.james.backends.cassandra.versions.SchemaVersion;
+import org.apache.james.blob.cassandra.CassandraBlobModule;
 import org.apache.james.mailbox.cassandra.ids.CassandraId;
 import org.apache.james.mailbox.cassandra.mail.utils.GuiceUtils;
 import org.apache.james.mailbox.cassandra.modules.CassandraAclModule;
@@ -38,6 +39,7 @@ import org.junit.jupiter.api.extension.RegisterExtension;
 
 class CassandraMailboxMapperGenericTest {
     private static final CassandraModule MODULES = CassandraModule.aggregateModules(
+        CassandraBlobModule.MODULE,
         CassandraSchemaVersionModule.MODULE,
         CassandraAclModule.MODULE,
         CassandraMailboxModule.MODULE,
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOTest.java
index 06223e0..a58595c 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOTest.java
@@ -34,9 +34,10 @@ import org.apache.james.backends.cassandra.CassandraCluster;
 import org.apache.james.backends.cassandra.CassandraClusterExtension;
 import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
+import org.apache.james.blob.api.BlobStore;
 import org.apache.james.blob.api.HashBlobId;
 import org.apache.james.blob.cassandra.CassandraBlobModule;
-import org.apache.james.blob.cassandra.CassandraBlobStore;
+import org.apache.james.blob.cassandra.CassandraBlobStoreFactory;
 import org.apache.james.mailbox.MessageUid;
 import org.apache.james.mailbox.ModSeq;
 import org.apache.james.mailbox.cassandra.ids.CassandraId;
@@ -85,7 +86,7 @@ class CassandraMessageDAOTest {
     void setUp(CassandraCluster cassandra) {
         messageIdFactory = new CassandraMessageId.Factory();
         messageId = messageIdFactory.generate();
-        CassandraBlobStore blobStore = CassandraBlobStore.forTesting(cassandra.getConf());
+        BlobStore blobStore = CassandraBlobStoreFactory.forTesting(cassandra.getConf());
         HashBlobId.Factory blobIdFactory = new HashBlobId.Factory();
         testee = new CassandraMessageDAO(
             cassandra.getConf(),
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/GuiceUtils.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/GuiceUtils.java
index 02fc9dc..204438c 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/GuiceUtils.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/GuiceUtils.java
@@ -19,18 +19,14 @@
 
 package org.apache.james.mailbox.cassandra.mail.utils;
 
-import static com.google.inject.Scopes.SINGLETON;
-
 import org.apache.james.backends.cassandra.CassandraCluster;
 import org.apache.james.backends.cassandra.init.CassandraTypesProvider;
 import org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration;
 import org.apache.james.backends.cassandra.init.configuration.CassandraConsistenciesConfiguration;
 import org.apache.james.blob.api.BlobId;
 import org.apache.james.blob.api.BlobStore;
-import org.apache.james.blob.api.BucketName;
 import org.apache.james.blob.api.HashBlobId;
-import org.apache.james.blob.cassandra.CassandraBlobStore;
-import org.apache.james.blob.cassandra.CassandraDumbBlobStore;
+import org.apache.james.blob.cassandra.CassandraBlobStoreFactory;
 import org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
 import org.apache.james.mailbox.model.MessageId;
 
@@ -38,7 +34,6 @@ import com.datastax.driver.core.Session;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Module;
-import com.google.inject.name.Names;
 import com.google.inject.util.Modules;
 
 public class GuiceUtils {
@@ -53,15 +48,6 @@ public class GuiceUtils {
 
     public static Injector testInjector(Session session, CassandraTypesProvider typesProvider,
                                         CassandraMessageId.Factory messageIdFactory,
-                                        CassandraConfiguration configuration, Module... guiceModules) {
-        return Guice.createInjector(
-            Modules.combine(
-                Modules.combine(commonModules(session, typesProvider, messageIdFactory, configuration)),
-                Modules.combine(guiceModules)));
-    }
-
-    public static Injector testInjector(Session session, CassandraTypesProvider typesProvider,
-                                        CassandraMessageId.Factory messageIdFactory,
                                         CassandraConfiguration configuration) {
         return Guice.createInjector(
             commonModules(session, typesProvider, messageIdFactory, configuration));
@@ -73,11 +59,7 @@ public class GuiceUtils {
         return Modules.combine(
             binder -> binder.bind(MessageId.Factory.class).toInstance(messageIdFactory),
             binder -> binder.bind(BlobId.Factory.class).toInstance(new HashBlobId.Factory()),
-            binder -> binder.bind(BlobStore.class).to(CassandraBlobStore.class),
-            binder -> binder.bind(CassandraDumbBlobStore.class).in(SINGLETON),
-            binder -> binder.bind(BucketName.class)
-                .annotatedWith(Names.named(CassandraDumbBlobStore.DEFAULT_BUCKET))
-                .toInstance(BucketName.DEFAULT),
+            binder -> binder.bind(BlobStore.class).toInstance(CassandraBlobStoreFactory.forTesting(session)),
             binder -> binder.bind(Session.class).toInstance(session),
             binder -> binder.bind(CassandraTypesProvider.class).toInstance(typesProvider),
             binder -> binder.bind(CassandraConfiguration.class).toInstance(configuration),
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManagerTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManagerTest.java
index c4db9d5..b1117a0 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManagerTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/quota/CassandraPerUserMaxQuotaManagerTest.java
@@ -20,6 +20,8 @@
 package org.apache.james.mailbox.cassandra.quota;
 
 import org.apache.james.backends.cassandra.CassandraClusterExtension;
+import org.apache.james.backends.cassandra.components.CassandraModule;
+import org.apache.james.blob.cassandra.CassandraBlobModule;
 import org.apache.james.mailbox.cassandra.mail.utils.GuiceUtils;
 import org.apache.james.mailbox.cassandra.modules.CassandraQuotaModule;
 import org.apache.james.mailbox.quota.MaxQuotaManager;
@@ -29,7 +31,9 @@ import org.junit.jupiter.api.extension.RegisterExtension;
 class CassandraPerUserMaxQuotaManagerTest extends GenericMaxQuotaManagerTest {
 
     @RegisterExtension
-    static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(CassandraQuotaModule.MODULE);
+    static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(CassandraModule.aggregateModules(
+        CassandraBlobModule.MODULE,
+        CassandraQuotaModule.MODULE));
 
     @Override
     protected MaxQuotaManager provideMaxQuotaManager() {
diff --git a/server/blob/blob-cassandra/pom.xml b/server/blob/blob-cassandra/pom.xml
index a59db28..733596a 100644
--- a/server/blob/blob-cassandra/pom.xml
+++ b/server/blob/blob-cassandra/pom.xml
@@ -55,6 +55,10 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>blob-deduplicating</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>metrics-tests</artifactId>
             <scope>test</scope>
         </dependency>
diff --git a/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraBlobStore.java b/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraBlobStore.java
deleted file mode 100644
index b59139a..0000000
--- a/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraBlobStore.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/****************************************************************
- * 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.blob.cassandra;
-
-import java.io.InputStream;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration;
-import org.apache.james.blob.api.BlobId;
-import org.apache.james.blob.api.BlobStore;
-import org.apache.james.blob.api.BucketName;
-import org.apache.james.blob.api.HashBlobId;
-
-import com.datastax.driver.core.Session;
-import com.github.fge.lambdas.Throwing;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-import com.google.common.hash.Hashing;
-import com.google.common.hash.HashingInputStream;
-import com.google.common.io.FileBackedOutputStream;
-
-import reactor.core.publisher.Mono;
-import reactor.util.function.Tuples;
-
-public class CassandraBlobStore implements BlobStore {
-
-    public static final boolean LAZY_RESOURCE_CLEANUP = false;
-    public static final int FILE_THRESHOLD = 10000;
-    private final HashBlobId.Factory blobIdFactory;
-    private final BucketName defaultBucketName;
-    private final CassandraDumbBlobStore dumbBlobStore;
-
-    @Inject
-    CassandraBlobStore(HashBlobId.Factory blobIdFactory,
-                       @Named(CassandraDumbBlobStore.DEFAULT_BUCKET) BucketName defaultBucketName,
-                       CassandraDumbBlobStore dumbBlobStore) {
-
-        this.blobIdFactory = blobIdFactory;
-        this.defaultBucketName = defaultBucketName;
-        this.dumbBlobStore = dumbBlobStore;
-    }
-
-    @VisibleForTesting
-    public static CassandraBlobStore forTesting(Session session) {
-        HashBlobId.Factory blobIdFactory = new HashBlobId.Factory();
-        CassandraBucketDAO bucketDAO = new CassandraBucketDAO(blobIdFactory, session);
-        CassandraDefaultBucketDAO defaultBucketDAO = new CassandraDefaultBucketDAO(session);
-        return new CassandraBlobStore(
-            blobIdFactory,
-            BucketName.DEFAULT,
-            new CassandraDumbBlobStore(defaultBucketDAO, bucketDAO, CassandraConfiguration.DEFAULT_CONFIGURATION, BucketName.DEFAULT));
-    }
-
-    @Override
-    public Mono<BlobId> save(BucketName bucketName, byte[] data, StoragePolicy storagePolicy) {
-        Preconditions.checkNotNull(bucketName);
-        Preconditions.checkNotNull(data);
-
-        BlobId blobId = blobIdFactory.forPayload(data);
-
-        return dumbBlobStore.save(bucketName, blobId, data)
-            .then(Mono.just(blobId));
-    }
-
-    @Override
-    public Mono<BlobId> save(BucketName bucketName, InputStream data, StoragePolicy storagePolicy) {
-        Preconditions.checkNotNull(bucketName);
-        Preconditions.checkNotNull(data);
-        HashingInputStream hashingInputStream = new HashingInputStream(Hashing.sha256(), data);
-        return Mono.using(
-            () -> new FileBackedOutputStream(FILE_THRESHOLD),
-            fileBackedOutputStream -> saveAndGenerateBlobId(bucketName, hashingInputStream, fileBackedOutputStream),
-            Throwing.consumer(FileBackedOutputStream::reset).sneakyThrow(),
-            LAZY_RESOURCE_CLEANUP);
-    }
-
-    private Mono<BlobId> saveAndGenerateBlobId(BucketName bucketName, HashingInputStream hashingInputStream, FileBackedOutputStream fileBackedOutputStream) {
-        return Mono.fromCallable(() -> {
-            IOUtils.copy(hashingInputStream, fileBackedOutputStream);
-            return Tuples.of(blobIdFactory.from(hashingInputStream.hash().toString()), fileBackedOutputStream.asByteSource());
-        })
-            .flatMap(tuple -> dumbBlobStore.save(bucketName, tuple.getT1(), tuple.getT2()).thenReturn(tuple.getT1()));
-    }
-
-    @Override
-    public Mono<byte[]> readBytes(BucketName bucketName, BlobId blobId) {
-        Preconditions.checkNotNull(bucketName);
-        return dumbBlobStore.readBytes(bucketName, blobId);
-    }
-
-    @Override
-    public InputStream read(BucketName bucketName, BlobId blobId) {
-        Preconditions.checkNotNull(bucketName);
-        return dumbBlobStore.read(bucketName, blobId);
-    }
-
-    @Override
-    public Mono<Void> deleteBucket(BucketName bucketName) {
-        Preconditions.checkNotNull(bucketName);
-
-        return dumbBlobStore.deleteBucket(bucketName);
-    }
-
-    @Override
-    public BucketName getDefaultBucketName() {
-        return defaultBucketName;
-    }
-
-    @Override
-    public Mono<Void> delete(BucketName bucketName, BlobId blobId) {
-        Preconditions.checkNotNull(bucketName);
-        Preconditions.checkNotNull(blobId);
-
-        return dumbBlobStore.delete(bucketName, blobId);
-    }
-
-}
diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraBlobStoreModule.java b/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraBlobStoreFactory.java
similarity index 51%
copy from server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraBlobStoreModule.java
copy to server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraBlobStoreFactory.java
index b4b3dca..cc51047 100644
--- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraBlobStoreModule.java
+++ b/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraBlobStoreFactory.java
@@ -17,20 +17,30 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.modules.mailbox;
+package org.apache.james.blob.cassandra;
 
+import org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration;
 import org.apache.james.blob.api.BlobStore;
-import org.apache.james.blob.api.MetricableBlobStore;
-import org.apache.james.blob.cassandra.CassandraBlobStore;
+import org.apache.james.blob.api.BucketName;
+import org.apache.james.blob.api.HashBlobId;
+import org.apache.james.server.blob.deduplication.DeDuplicationBlobStore;
 
-import com.google.inject.AbstractModule;
-import com.google.inject.name.Names;
+import com.datastax.driver.core.Session;
 
-public class CassandraBlobStoreModule extends AbstractModule {
-    @Override
-    protected void configure() {
-        bind(BlobStore.class)
-            .annotatedWith(Names.named(MetricableBlobStore.BLOB_STORE_IMPLEMENTATION))
-            .to(CassandraBlobStore.class);
+public class CassandraBlobStoreFactory {
+    public static BlobStore forTesting(HashBlobId.Factory blobIdFactory,
+                       BucketName defaultBucketName,
+                       CassandraDumbBlobStore dumbBlobStore) {
+        return new DeDuplicationBlobStore(dumbBlobStore, defaultBucketName, blobIdFactory);
+    }
+
+    public static BlobStore forTesting(Session session) {
+        HashBlobId.Factory blobIdFactory = new HashBlobId.Factory();
+        CassandraBucketDAO bucketDAO = new CassandraBucketDAO(blobIdFactory, session);
+        CassandraDefaultBucketDAO defaultBucketDAO = new CassandraDefaultBucketDAO(session);
+        return forTesting(
+            blobIdFactory,
+            BucketName.DEFAULT,
+            new CassandraDumbBlobStore(defaultBucketDAO, bucketDAO, CassandraConfiguration.DEFAULT_CONFIGURATION, BucketName.DEFAULT));
     }
 }
diff --git a/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraDumbBlobStore.java b/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraDumbBlobStore.java
index b6b7ace..69dbf41 100644
--- a/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraDumbBlobStore.java
+++ b/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/CassandraDumbBlobStore.java
@@ -46,8 +46,6 @@ import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
 public class CassandraDumbBlobStore implements DumbBlobStore {
-
-    public static final String DEFAULT_BUCKET = "cassandraDefault";
     public static final boolean LAZY = false;
 
     private final CassandraDefaultBucketDAO defaultBucketDAO;
@@ -61,7 +59,7 @@ public class CassandraDumbBlobStore implements DumbBlobStore {
     public CassandraDumbBlobStore(CassandraDefaultBucketDAO defaultBucketDAO,
                                   CassandraBucketDAO bucketDAO,
                                   CassandraConfiguration cassandraConfiguration,
-                                  @Named(DEFAULT_BUCKET) BucketName defaultBucket) {
+                                  @Named("defaultBucket") BucketName defaultBucket) {
         this.defaultBucketDAO = defaultBucketDAO;
         this.bucketDAO = bucketDAO;
         this.configuration = cassandraConfiguration;
diff --git a/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/CassandraBlobStoreTest.java b/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/CassandraBlobStoreTest.java
index f924cfe..cf43ad5 100644
--- a/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/CassandraBlobStoreTest.java
+++ b/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/CassandraBlobStoreTest.java
@@ -71,7 +71,7 @@ public class CassandraBlobStoreTest implements MetricableBlobStoreContract {
             .build();
         testee = new MetricableBlobStore(
             metricsTestExtension.getMetricFactory(),
-            new CassandraBlobStore(
+            CassandraBlobStoreFactory.forTesting(
                 blobIdFactory,
                 BucketName.DEFAULT,
                 new CassandraDumbBlobStore(defaultBucketDAO, bucketDAO, cassandraConfiguration, BucketName.DEFAULT)));
diff --git a/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/cache/CachedBlobStoreTest.java b/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/cache/CachedBlobStoreTest.java
index c96175c..34ed1be 100644
--- a/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/cache/CachedBlobStoreTest.java
+++ b/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/cache/CachedBlobStoreTest.java
@@ -45,7 +45,7 @@ import org.apache.james.blob.api.HashBlobId;
 import org.apache.james.blob.api.ObjectNotFoundException;
 import org.apache.james.blob.api.TestBlobId;
 import org.apache.james.blob.cassandra.CassandraBlobModule;
-import org.apache.james.blob.cassandra.CassandraBlobStore;
+import org.apache.james.blob.cassandra.CassandraBlobStoreFactory;
 import org.apache.james.metrics.tests.RecordingMetricFactory;
 import org.assertj.core.api.SoftAssertions;
 import org.junit.jupiter.api.BeforeEach;
@@ -74,7 +74,7 @@ public class CachedBlobStoreTest implements BlobStoreContract {
 
     @BeforeEach
     void setUp(CassandraCluster cassandra) {
-        backend = CassandraBlobStore.forTesting(cassandra.getConf());
+        backend = CassandraBlobStoreFactory.forTesting(cassandra.getConf());
         CassandraCacheConfiguration cacheConfig = new CassandraCacheConfiguration.Builder()
             .sizeThresholdInBytes(EIGHT_KILOBYTES.length + 1)
             .timeOut(Duration.ofSeconds(60))
diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraBlobStoreDependenciesModule.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraBlobStoreDependenciesModule.java
index ea5b1a7..22dfc63 100644
--- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraBlobStoreDependenciesModule.java
+++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraBlobStoreDependenciesModule.java
@@ -22,9 +22,10 @@ package org.apache.james.modules.mailbox;
 import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.blob.api.BucketName;
 import org.apache.james.blob.cassandra.CassandraBlobModule;
-import org.apache.james.blob.cassandra.CassandraBlobStore;
+import org.apache.james.blob.cassandra.CassandraBlobStoreFactory;
 import org.apache.james.blob.cassandra.CassandraDefaultBucketDAO;
 import org.apache.james.blob.cassandra.CassandraDumbBlobStore;
+import org.apache.james.server.blob.deduplication.DeDuplicationBlobStore;
 
 import com.google.inject.AbstractModule;
 import com.google.inject.Scopes;
@@ -35,11 +36,11 @@ public class CassandraBlobStoreDependenciesModule extends AbstractModule {
     @Override
     protected void configure() {
         bind(CassandraDefaultBucketDAO.class).in(Scopes.SINGLETON);
-        bind(CassandraBlobStore.class).in(Scopes.SINGLETON);
+        bind(CassandraBlobStoreFactory.class).in(Scopes.SINGLETON);
         bind(CassandraDumbBlobStore.class).in(Scopes.SINGLETON);
 
         bind(BucketName.class)
-            .annotatedWith(Names.named(CassandraDumbBlobStore.DEFAULT_BUCKET))
+            .annotatedWith(Names.named(DeDuplicationBlobStore.DEFAULT_BUCKET()))
             .toInstance(BucketName.DEFAULT);
 
         Multibinder<CassandraModule> cassandraDataDefinitions = Multibinder.newSetBinder(binder(), CassandraModule.class);
diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraBlobStoreModule.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraBlobStoreModule.java
index b4b3dca..23f4981 100644
--- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraBlobStoreModule.java
+++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraBlobStoreModule.java
@@ -20,17 +20,24 @@
 package org.apache.james.modules.mailbox;
 
 import org.apache.james.blob.api.BlobStore;
+import org.apache.james.blob.api.DumbBlobStore;
 import org.apache.james.blob.api.MetricableBlobStore;
-import org.apache.james.blob.cassandra.CassandraBlobStore;
+import org.apache.james.blob.cassandra.CassandraDumbBlobStore;
+import org.apache.james.server.blob.deduplication.DeDuplicationBlobStore;
 
 import com.google.inject.AbstractModule;
+import com.google.inject.Scopes;
 import com.google.inject.name.Names;
 
 public class CassandraBlobStoreModule extends AbstractModule {
     @Override
     protected void configure() {
+        bind(CassandraDumbBlobStore.class).in(Scopes.SINGLETON);
+        bind(DeDuplicationBlobStore.class).in(Scopes.SINGLETON);
+
+        bind(DumbBlobStore.class).to(CassandraDumbBlobStore.class);
         bind(BlobStore.class)
             .annotatedWith(Names.named(MetricableBlobStore.BLOB_STORE_IMPLEMENTATION))
-            .to(CassandraBlobStore.class);
+            .to(DeDuplicationBlobStore.class);
     }
 }
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 763e66d..b38ea7a 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
@@ -49,9 +49,12 @@ public class BlobStoreModulesChooser {
         @Override
         protected void configure() {
             install(new CassandraBlobStoreDependenciesModule());
+
+            bind(DumbBlobStore.class).to(CassandraDumbBlobStore.class);
+
             bind(BlobStore.class)
                 .annotatedWith(Names.named(CachedBlobStore.BACKEND))
-                .to(CassandraBlobStore.class);
+                .to(DeDuplicationBlobStore.class);
         }
     }
 
diff --git a/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryTest.java b/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryTest.java
index f329125..0a3be14 100644
--- a/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryTest.java
+++ b/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryTest.java
@@ -24,9 +24,10 @@ import org.apache.james.backends.cassandra.CassandraClusterExtension;
 import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.backends.cassandra.utils.CassandraUtils;
 import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
+import org.apache.james.blob.api.BlobStore;
 import org.apache.james.blob.api.HashBlobId;
 import org.apache.james.blob.cassandra.CassandraBlobModule;
-import org.apache.james.blob.cassandra.CassandraBlobStore;
+import org.apache.james.blob.cassandra.CassandraBlobStoreFactory;
 import org.apache.james.blob.mail.MimeMessageStore;
 import org.apache.james.mailrepository.MailRepositoryContract;
 import org.apache.james.mailrepository.api.MailRepository;
@@ -57,7 +58,7 @@ class CassandraMailRepositoryTest implements MailRepositoryContract {
         CassandraMailRepositoryMailDaoAPI mailDAO = new MergingCassandraMailRepositoryMailDao(v1, v2);
         CassandraMailRepositoryKeysDAO keysDAO = new CassandraMailRepositoryKeysDAO(cassandra.getConf(), CassandraUtils.WITH_DEFAULT_CONFIGURATION);
         CassandraMailRepositoryCountDAO countDAO = new CassandraMailRepositoryCountDAO(cassandra.getConf());
-        CassandraBlobStore blobStore = CassandraBlobStore.forTesting(cassandra.getConf());
+        BlobStore blobStore = CassandraBlobStoreFactory.forTesting(cassandra.getConf());
 
         cassandraMailRepository = new CassandraMailRepository(URL,
             keysDAO, countDAO, mailDAO, MimeMessageStore.factory(blobStore).mimeMessageStore());
diff --git a/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryWithFakeImplementationsTest.java b/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryWithFakeImplementationsTest.java
index 203b594..648a6e9 100644
--- a/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryWithFakeImplementationsTest.java
+++ b/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryWithFakeImplementationsTest.java
@@ -33,11 +33,12 @@ import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.backends.cassandra.utils.CassandraUtils;
 import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
 import org.apache.james.blob.api.BlobId;
+import org.apache.james.blob.api.BlobStore;
 import org.apache.james.blob.api.HashBlobId;
 import org.apache.james.blob.api.Store;
 import org.apache.james.blob.cassandra.BlobTables;
 import org.apache.james.blob.cassandra.CassandraBlobModule;
-import org.apache.james.blob.cassandra.CassandraBlobStore;
+import org.apache.james.blob.cassandra.CassandraBlobStoreFactory;
 import org.apache.james.blob.mail.MimeMessagePartsId;
 import org.apache.james.blob.mail.MimeMessageStore;
 import org.apache.james.core.builder.MimeMessageBuilder;
@@ -127,7 +128,7 @@ class CassandraMailRepositoryWithFakeImplementationsTest {
             FailingMailDAO mailDAO = new FailingMailDAO();
             keysDAO = new CassandraMailRepositoryKeysDAO(cassandra.getConf(), CassandraUtils.WITH_DEFAULT_CONFIGURATION);
             CassandraMailRepositoryCountDAO countDAO = new CassandraMailRepositoryCountDAO(cassandra.getConf());
-            CassandraBlobStore blobStore = CassandraBlobStore.forTesting(cassandra.getConf());
+            BlobStore blobStore = CassandraBlobStoreFactory.forTesting(cassandra.getConf());
 
             cassandraMailRepository = new CassandraMailRepository(URL,
                     keysDAO, countDAO, mailDAO, MimeMessageStore.factory(blobStore).mimeMessageStore());
@@ -212,7 +213,7 @@ class CassandraMailRepositoryWithFakeImplementationsTest {
             CassandraMailRepositoryMailDaoAPI mailDAO = new CassandraMailRepositoryMailDAO(cassandra.getConf(), BLOB_ID_FACTORY, cassandra.getTypesProvider());
             FailingKeysDAO keysDAO = new FailingKeysDAO(cassandra.getConf(), CassandraUtils.WITH_DEFAULT_CONFIGURATION);
             countDAO = new CassandraMailRepositoryCountDAO(cassandra.getConf());
-            CassandraBlobStore blobStore = CassandraBlobStore.forTesting(cassandra.getConf());
+            BlobStore blobStore = CassandraBlobStoreFactory.forTesting(cassandra.getConf());
 
             cassandraMailRepository = new CassandraMailRepository(URL,
                     keysDAO, countDAO, mailDAO, MimeMessageStore.factory(blobStore).mimeMessageStore());
diff --git a/server/queue/queue-rabbitmq/src/test/java/org/apache/james/queue/rabbitmq/RabbitMQMailQueueConfigurationChangeTest.java b/server/queue/queue-rabbitmq/src/test/java/org/apache/james/queue/rabbitmq/RabbitMQMailQueueConfigurationChangeTest.java
index 74bc055..a863ef8 100644
--- a/server/queue/queue-rabbitmq/src/test/java/org/apache/james/queue/rabbitmq/RabbitMQMailQueueConfigurationChangeTest.java
+++ b/server/queue/queue-rabbitmq/src/test/java/org/apache/james/queue/rabbitmq/RabbitMQMailQueueConfigurationChangeTest.java
@@ -36,9 +36,10 @@ import org.apache.james.backends.cassandra.CassandraClusterExtension;
 import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
 import org.apache.james.backends.rabbitmq.RabbitMQExtension;
+import org.apache.james.blob.api.BlobStore;
 import org.apache.james.blob.api.HashBlobId;
 import org.apache.james.blob.cassandra.CassandraBlobModule;
-import org.apache.james.blob.cassandra.CassandraBlobStore;
+import org.apache.james.blob.cassandra.CassandraBlobStoreFactory;
 import org.apache.james.blob.mail.MimeMessageStore;
 import org.apache.james.eventsourcing.eventstore.cassandra.CassandraEventStoreModule;
 import org.apache.james.metrics.api.NoopGaugeRegistry;
@@ -93,8 +94,8 @@ class RabbitMQMailQueueConfigurationChangeTest {
 
     @BeforeEach
     void setup(CassandraCluster cassandra) throws Exception {
-        CassandraBlobStore blobsDAO = CassandraBlobStore.forTesting(cassandra.getConf());
-        mimeMessageStoreFactory = MimeMessageStore.factory(blobsDAO);
+        BlobStore blobStore = CassandraBlobStoreFactory.forTesting(cassandra.getConf());
+        mimeMessageStoreFactory = MimeMessageStore.factory(blobStore);
         clock = new UpdatableTickingClock(IN_SLICE_1);
         mqManagementApi = new RabbitMQMailQueueManagement(rabbitMQExtension.managementAPI());
     }
diff --git a/server/queue/queue-rabbitmq/src/test/java/org/apache/james/queue/rabbitmq/RabbitMQMailQueueTest.java b/server/queue/queue-rabbitmq/src/test/java/org/apache/james/queue/rabbitmq/RabbitMQMailQueueTest.java
index 8808bfc..c6d0691 100644
--- a/server/queue/queue-rabbitmq/src/test/java/org/apache/james/queue/rabbitmq/RabbitMQMailQueueTest.java
+++ b/server/queue/queue-rabbitmq/src/test/java/org/apache/james/queue/rabbitmq/RabbitMQMailQueueTest.java
@@ -46,9 +46,10 @@ import org.apache.james.backends.cassandra.CassandraClusterExtension;
 import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
 import org.apache.james.backends.rabbitmq.RabbitMQExtension;
+import org.apache.james.blob.api.BlobStore;
 import org.apache.james.blob.api.HashBlobId;
 import org.apache.james.blob.cassandra.CassandraBlobModule;
-import org.apache.james.blob.cassandra.CassandraBlobStore;
+import org.apache.james.blob.cassandra.CassandraBlobStoreFactory;
 import org.apache.james.blob.mail.MimeMessageStore;
 import org.apache.james.eventsourcing.eventstore.cassandra.CassandraEventStoreModule;
 import org.apache.james.metrics.api.Gauge;
@@ -76,6 +77,7 @@ import org.junit.jupiter.api.extension.RegisterExtension;
 import org.mockito.ArgumentCaptor;
 
 import com.github.fge.lambdas.Throwing;
+
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Schedulers;
@@ -700,7 +702,7 @@ class RabbitMQMailQueueTest {
     private void setUp(CassandraCluster cassandra,
                        MailQueueMetricExtension.MailQueueMetricTestSystem metricTestSystem,
                        RabbitMQMailQueueConfiguration configuration) throws Exception {
-        CassandraBlobStore blobStore = CassandraBlobStore.forTesting(cassandra.getConf());
+        BlobStore blobStore = CassandraBlobStoreFactory.forTesting(cassandra.getConf());
         MimeMessageStore.Factory mimeMessageStoreFactory = MimeMessageStore.factory(blobStore);
         clock = new UpdatableTickingClock(IN_SLICE_1);
 


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


[james-project] 12/18: JAMES-3177 Applicable flags updates needs to be thread safe

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 dc5ecc7e0dc1ae95063ccb82eb38b2338729d34a
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Jul 22 14:08:56 2020 +0700

    JAMES-3177 Applicable flags updates needs to be thread safe
---
 .../imap/processor/base/SelectedMailboxImpl.java   | 23 +++++++++++++++-------
 .../processor/base/SelectedMailboxImplTest.java    |  3 ---
 2 files changed, 16 insertions(+), 10 deletions(-)

diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java
index 91fe286..1c8d8db 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java
@@ -122,12 +122,13 @@ public class SelectedMailboxImpl implements SelectedMailbox, MailboxListener {
     private final Set<MessageUid> flagUpdateUids = new TreeSet<>();
     private final Flags.Flag uninterestingFlag = Flags.Flag.RECENT;
     private final Set<MessageUid> expungedUids = new TreeSet<>();
+    private final Object applicableFlagsLock = new Object();
 
     private boolean recentUidRemoved = false;
     private boolean isDeletedByOtherSession = false;
     private boolean sizeChanged = false;
     private boolean silentFlagChanges = false;
-    private ApplicableFlags applicableFlags;
+    private ApplicableFlags applicableFlags = ApplicableFlags.from(new Flags());
 
     public SelectedMailboxImpl(MailboxManager mailboxManager, EventBus eventBus, ImapSession session, MessageManager messageManager) throws MailboxException {
         this.session = session;
@@ -147,7 +148,9 @@ public class SelectedMailboxImpl implements SelectedMailbox, MailboxListener {
             .subscribeOn(Schedulers.elastic())
             .block();
 
-        applicableFlags = ApplicableFlags.from(messageManager.getApplicableFlags(mailboxSession));
+        synchronized (applicableFlagsLock) {
+            applicableFlags = applicableFlags.updateWithNewFlags(messageManager.getApplicableFlags(mailboxSession));
+        }
         try (Stream<MessageUid> stream = messageManager.search(SearchQuery.of(SearchQuery.all()), mailboxSession)) {
             uidMsnConverter.addAll(stream.collect(Guavate.toImmutableList()));
         }
@@ -236,7 +239,9 @@ public class SelectedMailboxImpl implements SelectedMailbox, MailboxListener {
         sizeChanged = false;
         flagUpdateUids.clear();
         isDeletedByOtherSession = false;
-        applicableFlags = applicableFlags.ackUpdates();
+        synchronized (applicableFlagsLock) {
+            applicableFlags = applicableFlags.ackUpdates();
+        }
     }
 
     @Override
@@ -340,20 +345,22 @@ public class SelectedMailboxImpl implements SelectedMailbox, MailboxListener {
     }
 
     @Override
-    public synchronized Flags getApplicableFlags() {
+    public Flags getApplicableFlags() {
         return applicableFlags.flags();
     }
 
     
     @Override
-    public synchronized boolean hasNewApplicableFlags() {
+    public boolean hasNewApplicableFlags() {
         return applicableFlags.updated();
     }
 
     
     @Override
     public synchronized void resetNewApplicableFlags() {
-        applicableFlags = applicableFlags.ackUpdates();
+        synchronized (applicableFlagsLock) {
+            applicableFlags = applicableFlags.ackUpdates();
+        }
     }
 
     
@@ -426,7 +433,9 @@ public class SelectedMailboxImpl implements SelectedMailbox, MailboxListener {
                 }
             }
         }
-        applicableFlags = updateApplicableFlags(applicableFlags, updated);
+        synchronized (applicableFlagsLock) {
+            applicableFlags = updateApplicableFlags(applicableFlags, updated);
+        }
         return VOID;
     }
 
diff --git a/protocols/imap/src/test/java/org/apache/james/imap/processor/base/SelectedMailboxImplTest.java b/protocols/imap/src/test/java/org/apache/james/imap/processor/base/SelectedMailboxImplTest.java
index 679add3..02fd802 100644
--- a/protocols/imap/src/test/java/org/apache/james/imap/processor/base/SelectedMailboxImplTest.java
+++ b/protocols/imap/src/test/java/org/apache/james/imap/processor/base/SelectedMailboxImplTest.java
@@ -66,7 +66,6 @@ import org.apache.james.mailbox.store.mail.model.DefaultMessageId;
 import org.apache.james.util.concurrent.NamedThreadFactory;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.mockito.stubbing.Answer;
@@ -145,7 +144,6 @@ class SelectedMailboxImplTest {
         assertThat(selectedMailbox.getLastUid().get()).isEqualTo(EMITTED_EVENT_UID);
     }
 
-    @Disabled("JAMES-3177 SelectedMailboxImpl is not thread safe")
     @Test
     void customFlagsEventShouldNotFailWhenConcurrentWithCreation() throws Exception {
         AtomicInteger successCount = new AtomicInteger(0);
@@ -158,7 +156,6 @@ class SelectedMailboxImplTest {
         assertThat(successCount.get()).isEqualTo(1);
     }
 
-    @Disabled("JAMES-3177 SelectedMailboxImpl is not thread safe")
     @Test
     void applicableFlagsShouldBeWellUpdatedWhenConcurrentWithCreation() throws Exception {
         AtomicInteger successCount = new AtomicInteger(0);


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


[james-project] 11/18: JAMES-3177 Enhance SelectedMailboxImpl applicable flags related tests

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 27d307c4daef44b494f0d1427054505ebadd2dde
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Jul 22 14:01:12 2020 +0700

    JAMES-3177 Enhance SelectedMailboxImpl applicable flags related tests
    
     - Review test naming
     - Use a nested class
---
 .../processor/base/SelectedMailboxImplTest.java    | 136 +++++++++++----------
 1 file changed, 70 insertions(+), 66 deletions(-)

diff --git a/protocols/imap/src/test/java/org/apache/james/imap/processor/base/SelectedMailboxImplTest.java b/protocols/imap/src/test/java/org/apache/james/imap/processor/base/SelectedMailboxImplTest.java
index f3bc59c..679add3 100644
--- a/protocols/imap/src/test/java/org/apache/james/imap/processor/base/SelectedMailboxImplTest.java
+++ b/protocols/imap/src/test/java/org/apache/james/imap/processor/base/SelectedMailboxImplTest.java
@@ -67,6 +67,7 @@ import org.apache.james.util.concurrent.NamedThreadFactory;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.mockito.stubbing.Answer;
 import org.slf4j.Logger;
@@ -242,77 +243,80 @@ class SelectedMailboxImplTest {
             .build();
     }
 
-    @Test
-    void updateApplicableFlagsShouldNotUpdateWhenEmptyFlagsUpdate() {
-        ApplicableFlags applicableFlags = ApplicableFlags.from(flagsBuilder().add(SEEN).build());
-        MailboxListener.FlagsUpdated flagsUpdated = flagsUpdated(updatedFlags().noOldFlag().noNewFlag());
-        ApplicableFlags actual = SelectedMailboxImpl.updateApplicableFlags(applicableFlags, flagsUpdated);
-        assertThat(actual).satisfies(ap -> {
-            assertThat(ap.updated()).isFalse();
-            assertThat(ap.flags()).isEqualTo(flagsBuilder().add(SEEN).build());
-        });
-    }
+    @Nested
+    class ApplicableFlagsTests {
+        @Test
+        void updateApplicableFlagsShouldNotUpdateWhenEmptyFlagsUpdate() {
+            ApplicableFlags applicableFlags = ApplicableFlags.from(flagsBuilder().add(SEEN).build());
+            MailboxListener.FlagsUpdated flagsUpdated = flagsUpdated(updatedFlags().noOldFlag().noNewFlag());
+            ApplicableFlags actual = SelectedMailboxImpl.updateApplicableFlags(applicableFlags, flagsUpdated);
+            assertThat(actual).satisfies(ap -> {
+                assertThat(ap.updated()).isFalse();
+                assertThat(ap.flags()).isEqualTo(flagsBuilder().add(SEEN).build());
+            });
+        }
 
-    @Test
-    void updateApplicableFlagsShouldNotUpdateWhenNewFlag() {
-        ApplicableFlags applicableFlags = ApplicableFlags.from(flagsBuilder().add(SEEN).build());
-        MailboxListener.FlagsUpdated flagsUpdated =
-            flagsUpdated(updatedFlags().noOldFlag().newFlags(flags -> flags.add(ANSWERED)));
-        ApplicableFlags actual = SelectedMailboxImpl.updateApplicableFlags(applicableFlags, flagsUpdated);
-        assertThat(actual).satisfies(ap -> {
-            assertThat(ap.updated()).isFalse();
-            assertThat(ap.flags()).isEqualTo(flagsBuilder().add(SEEN).add(ANSWERED).build());
-        });
-    }
+        @Test
+        void updateApplicableFlagsShouldNotUpdateWhenNewFlag() {
+            ApplicableFlags applicableFlags = ApplicableFlags.from(flagsBuilder().add(SEEN).build());
+            MailboxListener.FlagsUpdated flagsUpdated =
+                flagsUpdated(updatedFlags().noOldFlag().newFlags(flags -> flags.add(ANSWERED)));
+            ApplicableFlags actual = SelectedMailboxImpl.updateApplicableFlags(applicableFlags, flagsUpdated);
+            assertThat(actual).satisfies(ap -> {
+                assertThat(ap.updated()).isFalse();
+                assertThat(ap.flags()).isEqualTo(flagsBuilder().add(SEEN).add(ANSWERED).build());
+            });
+        }
 
-    @Test
-    void updateApplicableFlagsShouldNotUpdateWhenSeveralUpdatedFlagsNewFlag() {
-        ApplicableFlags applicableFlags = ApplicableFlags.from(flagsBuilder().add(SEEN).build());
-        MailboxListener.FlagsUpdated flagsUpdated =
-            flagsUpdated(
-                updatedFlags().noOldFlag().newFlags(flags -> flags.add(ANSWERED)),
-                updatedFlags().noOldFlag().newFlags(flags -> flags.add(FLAGGED)));
-        ApplicableFlags actual = SelectedMailboxImpl.updateApplicableFlags(applicableFlags, flagsUpdated);
-        assertThat(actual).satisfies(ap -> {
-            assertThat(ap.updated()).isFalse();
-            assertThat(ap.flags()).isEqualTo(flagsBuilder().add(SEEN).add(ANSWERED).add(FLAGGED).build());
-        });
-    }
+        @Test
+        void updateApplicableFlagsShouldNotUpdateWhenSeveralUpdatedFlagsNewFlag() {
+            ApplicableFlags applicableFlags = ApplicableFlags.from(flagsBuilder().add(SEEN).build());
+            MailboxListener.FlagsUpdated flagsUpdated =
+                flagsUpdated(
+                    updatedFlags().noOldFlag().newFlags(flags -> flags.add(ANSWERED)),
+                    updatedFlags().noOldFlag().newFlags(flags -> flags.add(FLAGGED)));
+            ApplicableFlags actual = SelectedMailboxImpl.updateApplicableFlags(applicableFlags, flagsUpdated);
+            assertThat(actual).satisfies(ap -> {
+                assertThat(ap.updated()).isFalse();
+                assertThat(ap.flags()).isEqualTo(flagsBuilder().add(SEEN).add(ANSWERED).add(FLAGGED).build());
+            });
+        }
 
-    @Test
-    void updateApplicableFlagsShouldNotUpdateWhenOldFlagRemoved() {
-        ApplicableFlags applicableFlags = ApplicableFlags.from(flagsBuilder().add(SEEN).build());
-        MailboxListener.FlagsUpdated flagsUpdated =
-            flagsUpdated(updatedFlags().oldFlags(flags -> flags.add(SEEN)).noNewFlag());
-        ApplicableFlags actual = SelectedMailboxImpl.updateApplicableFlags(applicableFlags, flagsUpdated);
-        assertThat(actual).satisfies(ap -> {
-            assertThat(ap.updated()).isFalse();
-            assertThat(ap.flags()).isEqualTo(flagsBuilder().add(SEEN).build());
-        });
-    }
+        @Test
+        void updateApplicableFlagsShouldNotUpdateWhenOldFlagRemoved() {
+            ApplicableFlags applicableFlags = ApplicableFlags.from(flagsBuilder().add(SEEN).build());
+            MailboxListener.FlagsUpdated flagsUpdated =
+                flagsUpdated(updatedFlags().oldFlags(flags -> flags.add(SEEN)).noNewFlag());
+            ApplicableFlags actual = SelectedMailboxImpl.updateApplicableFlags(applicableFlags, flagsUpdated);
+            assertThat(actual).satisfies(ap -> {
+                assertThat(ap.updated()).isFalse();
+                assertThat(ap.flags()).isEqualTo(flagsBuilder().add(SEEN).build());
+            });
+        }
 
-    @Test
-    void updateApplicableFlagsShouldNotIncludeRecent() {
-        ApplicableFlags applicableFlags = ApplicableFlags.from(flagsBuilder().add(SEEN).build());
-        MailboxListener.FlagsUpdated flagsUpdated =
-            flagsUpdated(updatedFlags().noOldFlag().newFlags(flags -> flags.add(RECENT)));
-        ApplicableFlags actual = SelectedMailboxImpl.updateApplicableFlags(applicableFlags, flagsUpdated);
-        assertThat(actual).satisfies(ap -> {
-            assertThat(ap.updated()).isFalse();
-            assertThat(ap.flags()).isEqualTo(flagsBuilder().add(SEEN).build());
-        });
-    }
+        @Test
+        void updateApplicableFlagsShouldNotIncludeRecent() {
+            ApplicableFlags applicableFlags = ApplicableFlags.from(flagsBuilder().add(SEEN).build());
+            MailboxListener.FlagsUpdated flagsUpdated =
+                flagsUpdated(updatedFlags().noOldFlag().newFlags(flags -> flags.add(RECENT)));
+            ApplicableFlags actual = SelectedMailboxImpl.updateApplicableFlags(applicableFlags, flagsUpdated);
+            assertThat(actual).satisfies(ap -> {
+                assertThat(ap.updated()).isFalse();
+                assertThat(ap.flags()).isEqualTo(flagsBuilder().add(SEEN).build());
+            });
+        }
 
-    @Test
-    void updateApplicableFlagsShouldNotUpdateWhenNewUserFlag() {
-        ApplicableFlags applicableFlags = ApplicableFlags.from(flagsBuilder().add(SEEN).build());
-        MailboxListener.FlagsUpdated flagsUpdated =
-            flagsUpdated(updatedFlags().noOldFlag().newFlags(flags -> flags.add("Foo")));
-        ApplicableFlags actual = SelectedMailboxImpl.updateApplicableFlags(applicableFlags, flagsUpdated);
-        assertThat(actual).satisfies(ap -> {
-            assertThat(ap.updated()).isTrue();
-            assertThat(ap.flags()).isEqualTo(flagsBuilder().add(SEEN).add("Foo").build());
-        });
+        @Test
+        void updateApplicableFlagsShouldUpdateWhenNewUserFlag() {
+            ApplicableFlags applicableFlags = ApplicableFlags.from(flagsBuilder().add(SEEN).build());
+            MailboxListener.FlagsUpdated flagsUpdated =
+                flagsUpdated(updatedFlags().noOldFlag().newFlags(flags -> flags.add("Foo")));
+            ApplicableFlags actual = SelectedMailboxImpl.updateApplicableFlags(applicableFlags, flagsUpdated);
+            assertThat(actual).satisfies(ap -> {
+                assertThat(ap.updated()).isTrue();
+                assertThat(ap.flags()).isEqualTo(flagsBuilder().add(SEEN).add("Foo").build());
+            });
+        }
     }
 
     private static FlagsBuilder flagsBuilder() {


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


[james-project] 08/18: JAMES-3177 extraction of method handling events

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 eb806e3d6069749c291e2eb61d996fd21ac9eccb
Author: Matthieu Baechler <ma...@apache.org>
AuthorDate: Tue Jun 16 17:48:30 2020 +0200

    JAMES-3177 extraction of method handling events
---
 .../imap/processor/base/SelectedMailboxImpl.java   | 108 ++++++++++++---------
 1 file changed, 61 insertions(+), 47 deletions(-)

diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java
index 6600b84..f7180a2 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java
@@ -366,60 +366,74 @@ public class SelectedMailboxImpl implements SelectedMailbox, MailboxListener {
             if (mailboxEvent instanceof MessageEvent) {
                 final MessageEvent messageEvent = (MessageEvent) mailboxEvent;
                 if (messageEvent instanceof Added) {
-                    sizeChanged = true;
                     final Collection<MessageUid> uids = ((Added) mailboxEvent).getUids();
-                    SelectedMailbox sm = session.getSelected();
-                    for (MessageUid uid : uids) {
-                        uidMsnConverter.addUid(uid);
-                        if (sm != null) {
-                            sm.addRecent(uid);
-                        }
-                    }
+                    handleAddition(uids);
                 } else if (messageEvent instanceof FlagsUpdated) {
                     FlagsUpdated updated = (FlagsUpdated) messageEvent;
-                    List<UpdatedFlags> uFlags = updated.getUpdatedFlags();
-                    if (sessionId != eventSessionId || !silentFlagChanges) {
-   
-                        for (UpdatedFlags u : uFlags) {
-                            if (interestingFlags(u)) {
-                                flagUpdateUids.add(u.getUid());
-                            }
-                        }
-                    }
-   
-                    SelectedMailbox sm = session.getSelected();
-                    if (sm != null) {
-                        // We need to add the UID of the message to the recent
-                        // list if we receive an flag update which contains a
-                        // \RECENT flag
-                        // See IMAP-287
-                        List<UpdatedFlags> uflags = updated.getUpdatedFlags();
-                        for (UpdatedFlags u : uflags) {
-                            Iterator<Flag> flags = u.systemFlagIterator();
-   
-                            while (flags.hasNext()) {
-                                if (Flag.RECENT.equals(flags.next())) {
-                                    MailboxId id = sm.getMailboxId();
-                                    if (id != null && id.equals(mailboxEvent.getMailboxId())) {
-                                        sm.addRecent(u.getUid());
-                                    }
-                                }
-                            }
-   
-   
-                        }
-                    }
-
-                    applicableFlags = updateApplicableFlags(applicableFlags, (FlagsUpdated) messageEvent);
-
+                    handleFlagsUpdates(mailboxEvent, eventSessionId, (FlagsUpdated) messageEvent, updated);
                 } else if (messageEvent instanceof Expunged) {
-                    expungedUids.addAll(messageEvent.getUids());
-                    
+                    handleMailboxExpunge(messageEvent);
                 }
             } else if (mailboxEvent instanceof MailboxDeletion) {
-                if (eventSessionId != sessionId) {
-                    isDeletedByOtherSession = true;
+                handleMailboxDeletion(eventSessionId);
+            }
+        }
+    }
+
+    private void handleMailboxDeletion(MailboxSession.SessionId eventSessionId) {
+        if (eventSessionId != sessionId) {
+            isDeletedByOtherSession = true;
+        }
+    }
+
+    private void handleMailboxExpunge(MessageEvent messageEvent) {
+        expungedUids.addAll(messageEvent.getUids());
+    }
+
+    private void handleFlagsUpdates(MailboxEvent mailboxEvent, MailboxSession.SessionId eventSessionId, FlagsUpdated messageEvent, FlagsUpdated updated) {
+        List<UpdatedFlags> uFlags = updated.getUpdatedFlags();
+        if (sessionId != eventSessionId || !silentFlagChanges) {
+
+            for (UpdatedFlags u : uFlags) {
+                if (interestingFlags(u)) {
+                    flagUpdateUids.add(u.getUid());
+                }
+            }
+        }
+
+        SelectedMailbox sm = session.getSelected();
+        if (sm != null) {
+            // We need to add the UID of the message to the recent
+            // list if we receive an flag update which contains a
+            // \RECENT flag
+            // See IMAP-287
+            List<UpdatedFlags> uflags = updated.getUpdatedFlags();
+            for (UpdatedFlags u : uflags) {
+                Iterator<Flag> flags = u.systemFlagIterator();
+
+                while (flags.hasNext()) {
+                    if (Flag.RECENT.equals(flags.next())) {
+                        MailboxId id = sm.getMailboxId();
+                        if (id != null && id.equals(mailboxEvent.getMailboxId())) {
+                            sm.addRecent(u.getUid());
+                        }
+                    }
                 }
+
+
+            }
+        }
+
+        applicableFlags = updateApplicableFlags(applicableFlags, messageEvent);
+    }
+
+    private void handleAddition(Collection<MessageUid> uids) {
+        sizeChanged = true;
+        SelectedMailbox sm = session.getSelected();
+        for (MessageUid uid : uids) {
+            uidMsnConverter.addUid(uid);
+            if (sm != null) {
+                sm.addRecent(uid);
             }
         }
     }


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


[james-project] 01/18: JAMES-3177 extract applicable flags update

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 3ae6ff652fdbea4b08b3ce9a4639b5ff8ef3452a
Author: Matthieu Baechler <ma...@apache.org>
AuthorDate: Tue Jun 16 15:23:07 2020 +0200

    JAMES-3177 extract applicable flags update
---
 .../imap/processor/base/SelectedMailboxImpl.java   | 41 ++++++++++++----------
 1 file changed, 22 insertions(+), 19 deletions(-)

diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java
index 2c07c2a..535e8a5 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java
@@ -365,25 +365,9 @@ public class SelectedMailboxImpl implements SelectedMailbox, MailboxListener {
    
                         }
                     }
-                    
-                    int size = applicableFlags.getUserFlags().length;
-                    FlagsUpdated updatedF = (FlagsUpdated) messageEvent;
-                    List<UpdatedFlags> flags = updatedF.getUpdatedFlags();
-   
-                    for (UpdatedFlags flag : flags) {
-                        applicableFlags.add(flag.getNewFlags());
-   
-                    }
-   
-                    // \RECENT is not a applicable flag in imap so remove it
-                    // from the list
-                    applicableFlags.remove(Flags.Flag.RECENT);
-   
-                    if (size < applicableFlags.getUserFlags().length) {
-                        applicableFlagsChanged = true;
-                    }
-                    
-                    
+
+                    updateApplicableFlags((FlagsUpdated) messageEvent);
+
                 } else if (messageEvent instanceof Expunged) {
                     expungedUids.addAll(messageEvent.getUids());
                     
@@ -396,6 +380,25 @@ public class SelectedMailboxImpl implements SelectedMailbox, MailboxListener {
         }
     }
 
+    private void updateApplicableFlags(FlagsUpdated messageEvent) {
+        int size = applicableFlags.getUserFlags().length;
+        FlagsUpdated updatedF = messageEvent;
+        List<UpdatedFlags> flags = updatedF.getUpdatedFlags();
+
+        for (UpdatedFlags flag : flags) {
+            applicableFlags.add(flag.getNewFlags());
+
+        }
+
+        // \RECENT is not a applicable flag in imap so remove it
+        // from the list
+        applicableFlags.remove(Flag.RECENT);
+
+        if (size < applicableFlags.getUserFlags().length) {
+            applicableFlagsChanged = true;
+        }
+    }
+
     @Override
     public synchronized NullableMessageSequenceNumber msn(MessageUid uid) {
         return uidMsnConverter.getMsn(uid);


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


[james-project] 17/18: JAMES-3313 Drop support for Hybrid 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 20812f2e335242783da4dce13ced7b907e80d9d1
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Jul 17 17:24:01 2020 +0700

    JAMES-3313 Drop support for Hybrid BlobStore
---
 CHANGELOG.md                                       |  10 +-
 .../destination/conf/blob.properties               |  11 +-
 .../destination/conf/blob.properties               |  11 +-
 pom.xml                                            |   5 -
 server/blob/blob-union/pom.xml                     |  75 ---
 .../apache/james/blob/union/HybridBlobStore.java   | 239 ---------
 .../james/blob/union/HybridBlobStoreTest.java      | 536 ---------------------
 server/blob/pom.xml                                |   1 -
 .../guice/cassandra-rabbitmq-guice/pom.xml         |   4 -
 .../modules/blobstore/BlobStoreChoosingModule.java |  23 -
 .../modules/blobstore/BlobStoreConfiguration.java  |   7 +-
 .../modules/blobstore/BlobStoreModulesChooser.java |  49 +-
 .../blobstore/BlobStoreConfigurationTest.java      |  33 +-
 .../blobstore/BlobStoreModulesChooserTest.java     |  64 ---
 src/site/xdoc/server/config-blobstore.xml          |   9 -
 15 files changed, 14 insertions(+), 1063 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f5c8690..31f9e86 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -71,11 +71,6 @@ of tasks being currently executed.
 - JAMES-3305 Avoid crashes upon deserialization issues when consuming RabbitMQ messages, leverage dead-letter feature
 - JAMES-3212 JMAP Handle subcrible/unsubcrible child's folder when update mailbox
 
-### Deprecated
-- HybridBlobStore. This will be removed after 3.6.0 release. Introduced to fasten small blob access, its usage could be 
-compared to a cache, but with a sub-optimal implementation (no eviction, default replication factor, no  circuit breaking).
-Use BlobStore cache instead.
-
 ### Removed
 - Classes marked as deprecated whose removal was planned after 3.4.0 release (See JAMES-2703). This includes:
   - SieveDefaultRepository. Please use SieveFileRepository instead.
@@ -90,7 +85,10 @@ This parameter could cause body content alteration leading to DKIM invalid DKIM
 Thanks to Sergey B. for the report. 
 More details about the property is at [java mail doc](https://javaee.github.io/javamail/docs/api/com/sun/mail/smtp/package-summary.html)
  - JAMES-3122 LogEnabled API in Spring product had been removed for Log4J2 adoption for Java 9+ runtime compatibility. 
- 
+ - HybridBlobStore. This will be removed after 3.6.0 release. Introduced to fasten small blob access, its usage could be
+ compared to a cache, but with a sub-optimal implementation (no eviction, default replication factor, no  circuit breaking).
+ Use BlobStore cache instead.
+
 ### Third party softwares
  - The distributed James server product (relying on Guice, Cassandra, ElasticSearch, RabbitMQ and optionally Swift) now needs at least RabbitMQ 3.8.1.
  - Tika prior 1.24 is subject to multiple CVEs. We recommend the upgrade.
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 4fa1dcd..40aee03 100644
--- a/dockerfiles/run/guice/cassandra-rabbitmq-ldap/destination/conf/blob.properties
+++ b/dockerfiles/run/guice/cassandra-rabbitmq-ldap/destination/conf/blob.properties
@@ -2,18 +2,9 @@
 # Read https://james.apache.org/server/config-blobstore.html for further details
 
 # Choose your BlobStore implementation
-# Mandatory, allowed values are: cassandra, objectstorage, hybrid (deprecated)
-# hybrid is using both objectstorage for unfrequently read or big blobs & cassandra for small, often read blobs
-# Deprecated. Use CachedBlobStore instead. Introduced to fasten small blob access, its usage could be compared
-# to a cache, but with a sub-optimal implementation (no eviction, default replication factor, no  circuit breaking).
+# Mandatory, allowed values are: cassandra, objectstorage
 implementation=objectstorage
 
-# ========================================= Hybrid BlobStore ======================================
-# hybrid is using both objectstorage for unfrequently read or big blobs & cassandra for small, often read blobs
-# Size threshold for considering a blob as 'big', causing it to be saved in the low cost blobStore
-# Optional, defaults to 32768 bytes (32KB), must be positive
-hybrid.size.threshold=32768
-
 # ========================================= 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 da0945f..8c98913 100644
--- a/dockerfiles/run/guice/cassandra-rabbitmq/destination/conf/blob.properties
+++ b/dockerfiles/run/guice/cassandra-rabbitmq/destination/conf/blob.properties
@@ -2,10 +2,7 @@
 # Read https://james.apache.org/server/config-blobstore.html for further details
 
 # Choose your BlobStore implementation
-# Mandatory, allowed values are: cassandra, objectstorage, hybrid (deprecated)
-# hybrid is using both objectstorage for unfrequently read or big blobs & cassandra for small, often read blobs
-# Deprecated. Use CachedBlobStore instead. Introduced to fasten small blob access, its usage could be compared
-# to a cache, but with a sub-optimal implementation (no eviction, default replication factor, no  circuit breaking).
+# Mandatory, allowed values are: cassandra, objectstorage
 implementation=objectstorage
 
 # ========================================= Cassandra BlobStore Cache ======================================
@@ -30,12 +27,6 @@ cache.enable=false
 # Units: bytes, Kib, MiB, GiB, TiB
 # cache.sizeThresholdInBytes=8 KiB
 
-# ========================================= Hybrid BlobStore ======================================
-# hybrid is using both objectstorage for unfrequently read or big blobs & cassandra for small, often read blobs
-# Size threshold for considering a blob as 'big', causing it to be saved in the low cost blobStore
-# Optional, defaults to 32768 bytes (32KB), must be positive
-hybrid.size.threshold=32768
-
 # ============================================== ObjectStorage ============================================
 
 # ========================================= ObjectStorage Codec ======================================
diff --git a/pom.xml b/pom.xml
index 8881160..fbdf065 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1196,11 +1196,6 @@
                 <version>${project.version}</version>
                 <type>test-jar</type>
             </dependency>
-            <dependency>
-                <groupId>${james.groupId}</groupId>
-                <artifactId>blob-union</artifactId>
-                <version>${project.version}</version>
-            </dependency>
              <dependency>
                 <groupId>${james.groupId}</groupId>
                 <artifactId>event-sourcing-core</artifactId>
diff --git a/server/blob/blob-union/pom.xml b/server/blob/blob-union/pom.xml
deleted file mode 100644
index bcc1d4b..0000000
--- a/server/blob/blob-union/pom.xml
+++ /dev/null
@@ -1,75 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-    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.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-    <modelVersion>4.0.0</modelVersion>
-
-    <parent>
-        <artifactId>james-server-blob</artifactId>
-        <groupId>org.apache.james</groupId>
-        <version>3.6.0-SNAPSHOT</version>
-        <relativePath>../pom.xml</relativePath>
-    </parent>
-
-    <artifactId>blob-union</artifactId>
-    <packaging>jar</packaging>
-
-    <name>Apache James :: Server :: Blob :: Union Blob Storage</name>
-    <description>
-        An implementation of BlobStore which relies on a current and a legacy BlobStore by order for reading and writing
-        blobs with fallback mechanism.
-    </description>
-
-    <dependencies>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
-            <artifactId>blob-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
-            <artifactId>blob-api</artifactId>
-            <type>test-jar</type>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
-            <artifactId>blob-memory</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
-            <artifactId>james-server-testing</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
-            <artifactId>james-server-util</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
-            <artifactId>testing-base</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.commons</groupId>
-            <artifactId>commons-configuration2</artifactId>
-        </dependency>
-    </dependencies>
-    
-</project>
diff --git a/server/blob/blob-union/src/main/java/org/apache/james/blob/union/HybridBlobStore.java b/server/blob/blob-union/src/main/java/org/apache/james/blob/union/HybridBlobStore.java
deleted file mode 100644
index 6dd06ad..0000000
--- a/server/blob/blob-union/src/main/java/org/apache/james/blob/union/HybridBlobStore.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/****************************************************************
- * 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.blob.union;
-
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Objects;
-import java.util.Optional;
-
-import org.apache.james.blob.api.BlobId;
-import org.apache.james.blob.api.BlobStore;
-import org.apache.james.blob.api.BucketName;
-import org.apache.james.blob.api.ObjectNotFoundException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.base.MoreObjects;
-import com.google.common.base.Preconditions;
-
-import reactor.core.publisher.Mono;
-
-/**
- * Will be removed in future release (3.6.0).
- * Prefer using CachedBlobStore.
- *
- * Introduced to fasten small blob access, its usage could be compared to a cache, but with a sub-optimal
- * implementation (no eviction, default replication factor, no  circuit breaking).
- */
-@Deprecated
-public class HybridBlobStore implements BlobStore {
-    @FunctionalInterface
-    public interface RequireLowCost {
-        RequireHighPerformance lowCost(BlobStore blobStore);
-    }
-
-    @FunctionalInterface
-    public interface RequireHighPerformance {
-        RequireConfiguration highPerformance(BlobStore blobStore);
-    }
-
-    @FunctionalInterface
-    public interface RequireConfiguration {
-        Builder configuration(Configuration configuration);
-    }
-
-    public static class Builder {
-        private final BlobStore lowCostBlobStore;
-        private final BlobStore highPerformanceBlobStore;
-        private final Configuration configuration;
-
-        Builder(BlobStore lowCostBlobStore, BlobStore highPerformanceBlobStore, Configuration configuration) {
-            this.lowCostBlobStore = lowCostBlobStore;
-            this.highPerformanceBlobStore = highPerformanceBlobStore;
-            this.configuration = configuration;
-        }
-
-        public HybridBlobStore build() {
-            return new HybridBlobStore(
-                lowCostBlobStore,
-                highPerformanceBlobStore,
-                configuration);
-        }
-    }
-
-    public static class Configuration {
-        public static final int DEFAULT_SIZE_THRESHOLD = 32 * 1024;
-        public static final Configuration DEFAULT = new Configuration(DEFAULT_SIZE_THRESHOLD);
-        private static final String PROPERTY_NAME = "hybrid.size.threshold";
-
-        public static Configuration from(org.apache.commons.configuration2.Configuration propertiesConfiguration) {
-            return new Configuration(Optional.ofNullable(propertiesConfiguration.getInteger(PROPERTY_NAME, null))
-                .orElse(DEFAULT_SIZE_THRESHOLD));
-        }
-
-        private final int sizeThreshold;
-
-        public Configuration(int sizeThreshold) {
-            Preconditions.checkArgument(sizeThreshold >= 0, "'" + PROPERTY_NAME + "' needs to be positive");
-
-            this.sizeThreshold = sizeThreshold;
-        }
-
-        public int getSizeThreshold() {
-            return sizeThreshold;
-        }
-
-        @Override
-        public final boolean equals(Object o) {
-            if (o instanceof Configuration) {
-                Configuration that = (Configuration) o;
-
-                return Objects.equals(this.sizeThreshold, that.sizeThreshold);
-            }
-            return false;
-        }
-
-        @Override
-        public final int hashCode() {
-            return Objects.hash(sizeThreshold);
-        }
-    }
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(HybridBlobStore.class);
-
-    public static RequireLowCost builder() {
-        return lowCost -> highPerformance -> configuration -> new Builder(lowCost, highPerformance, configuration);
-    }
-
-    private final BlobStore lowCostBlobStore;
-    private final BlobStore highPerformanceBlobStore;
-    private final Configuration configuration;
-
-    private HybridBlobStore(BlobStore lowCostBlobStore, BlobStore highPerformanceBlobStore, Configuration configuration) {
-        this.lowCostBlobStore = lowCostBlobStore;
-        this.highPerformanceBlobStore = highPerformanceBlobStore;
-        this.configuration = configuration;
-    }
-
-    @Override
-    public Mono<BlobId> save(BucketName bucketName, byte[] data, StoragePolicy storagePolicy) {
-        return selectBlobStore(storagePolicy, Mono.just(data.length > configuration.getSizeThreshold()))
-            .flatMap(blobStore -> Mono.from(blobStore.save(bucketName, data, storagePolicy)));
-    }
-
-    @Override
-    public Mono<BlobId> save(BucketName bucketName, InputStream data, StoragePolicy storagePolicy) {
-        Preconditions.checkNotNull(data);
-
-        BufferedInputStream bufferedInputStream = new BufferedInputStream(data, configuration.getSizeThreshold() + 1);
-        return selectBlobStore(storagePolicy, Mono.fromCallable(() -> isItABigStream(bufferedInputStream)))
-            .flatMap(blobStore -> Mono.from(blobStore.save(bucketName, bufferedInputStream, storagePolicy)));
-    }
-
-    private Mono<BlobStore> selectBlobStore(StoragePolicy storagePolicy, Mono<Boolean> largeData) {
-        switch (storagePolicy) {
-            case LOW_COST:
-                return Mono.just(lowCostBlobStore);
-            case SIZE_BASED:
-                return largeData.map(isLarge -> {
-                    if (isLarge) {
-                        return lowCostBlobStore;
-                    }
-                    return highPerformanceBlobStore;
-                });
-            case HIGH_PERFORMANCE:
-                return Mono.just(highPerformanceBlobStore);
-            default:
-                throw new RuntimeException("Unknown storage policy: " + storagePolicy);
-        }
-    }
-
-    private boolean isItABigStream(InputStream bufferedData) throws IOException {
-        bufferedData.mark(0);
-        bufferedData.skip(configuration.getSizeThreshold());
-        boolean isItABigStream = bufferedData.read() != -1;
-        bufferedData.reset();
-        return isItABigStream;
-    }
-
-    @Override
-    public BucketName getDefaultBucketName() {
-        Preconditions.checkState(
-            lowCostBlobStore.getDefaultBucketName()
-                .equals(highPerformanceBlobStore.getDefaultBucketName()),
-            "lowCostBlobStore and highPerformanceBlobStore doen't have same defaultBucketName which could lead to " +
-                "unexpected result when interact with other APIs");
-
-        return lowCostBlobStore.getDefaultBucketName();
-    }
-
-    @Override
-    public Mono<byte[]> readBytes(BucketName bucketName, BlobId blobId) {
-        return Mono.defer(() -> Mono.from(highPerformanceBlobStore.readBytes(bucketName, blobId)))
-            .onErrorResume(this::logAndReturnEmpty)
-            .switchIfEmpty(Mono.defer(() -> Mono.from(lowCostBlobStore.readBytes(bucketName, blobId))));
-    }
-
-    @Override
-    public InputStream read(BucketName bucketName, BlobId blobId) {
-        try {
-            return highPerformanceBlobStore.read(bucketName, blobId);
-        } catch (ObjectNotFoundException e) {
-            return lowCostBlobStore.read(bucketName, blobId);
-        } catch (Exception e) {
-            LOGGER.error("Error reading {} {} in {}, falling back to {}", bucketName, blobId, highPerformanceBlobStore, lowCostBlobStore);
-            return lowCostBlobStore.read(bucketName, blobId);
-        }
-    }
-
-    @Override
-    public Mono<Void> deleteBucket(BucketName bucketName) {
-        return Mono.defer(() -> Mono.from(lowCostBlobStore.deleteBucket(bucketName)))
-            .and(highPerformanceBlobStore.deleteBucket(bucketName))
-            .onErrorResume(this::logDeleteFailureAndReturnEmpty);
-    }
-
-    @Override
-    public Mono<Void> delete(BucketName bucketName, BlobId blobId) {
-        return Mono.defer(() -> Mono.from(lowCostBlobStore.delete(bucketName, blobId)))
-            .and(highPerformanceBlobStore.delete(bucketName, blobId))
-            .onErrorResume(this::logDeleteFailureAndReturnEmpty);
-    }
-
-    private <T> Mono<T> logAndReturnEmpty(Throwable throwable) {
-        LOGGER.error("error happens from current blob store, fall back to lowCost blob store", throwable);
-        return Mono.empty();
-    }
-
-    private <T> Mono<T> logDeleteFailureAndReturnEmpty(Throwable throwable) {
-        LOGGER.error("Cannot delete from either lowCost or highPerformance blob store", throwable);
-        return Mono.empty();
-    }
-
-    @Override
-    public String toString() {
-        return MoreObjects.toStringHelper(this)
-            .add("lowCostBlobStore", lowCostBlobStore)
-            .add("highPerformanceBlobStore", highPerformanceBlobStore)
-            .toString();
-    }
-}
diff --git a/server/blob/blob-union/src/test/java/org/apache/james/blob/union/HybridBlobStoreTest.java b/server/blob/blob-union/src/test/java/org/apache/james/blob/union/HybridBlobStoreTest.java
deleted file mode 100644
index ee36d52..0000000
--- a/server/blob/blob-union/src/test/java/org/apache/james/blob/union/HybridBlobStoreTest.java
+++ /dev/null
@@ -1,536 +0,0 @@
-/****************************************************************
- * 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.blob.union;
-
-import static org.apache.james.blob.api.BlobStore.StoragePolicy.HIGH_PERFORMANCE;
-import static org.apache.james.blob.api.BlobStore.StoragePolicy.LOW_COST;
-import static org.apache.james.blob.api.BlobStore.StoragePolicy.SIZE_BASED;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatCode;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-
-import org.apache.james.blob.api.BlobId;
-import org.apache.james.blob.api.BlobStore;
-import org.apache.james.blob.api.BlobStoreContract;
-import org.apache.james.blob.api.BucketName;
-import org.apache.james.blob.api.HashBlobId;
-import org.apache.james.blob.api.ObjectNotFoundException;
-import org.apache.james.blob.api.ObjectStoreException;
-import org.apache.james.blob.memory.MemoryBlobStoreFactory;
-import org.assertj.core.api.SoftAssertions;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-
-import com.github.fge.lambdas.Throwing;
-import com.google.common.base.MoreObjects;
-
-import nl.jqno.equalsverifier.EqualsVerifier;
-import reactor.core.publisher.Mono;
-
-class HybridBlobStoreTest implements BlobStoreContract {
-
-    private static class FailingBlobStore implements BlobStore {
-        @Override
-        public Mono<BlobId> save(BucketName bucketName, InputStream data, StoragePolicy storagePolicy) {
-            return Mono.error(new RuntimeException("broken everywhere"));
-        }
-
-        @Override
-        public Mono<BlobId> save(BucketName bucketName, byte[] data, StoragePolicy storagePolicy) {
-            return Mono.error(new RuntimeException("broken everywhere"));
-        }
-
-        @Override
-        public Mono<BlobId> save(BucketName bucketName, String data, StoragePolicy storagePolicy) {
-            return Mono.error(new RuntimeException("broken everywhere"));
-        }
-
-        @Override
-        public BucketName getDefaultBucketName() {
-            return BucketName.DEFAULT;
-        }
-
-        @Override
-        public Mono<byte[]> readBytes(BucketName bucketName, BlobId blobId) {
-            return Mono.error(new RuntimeException("broken everywhere"));
-        }
-
-        @Override
-        public InputStream read(BucketName bucketName, BlobId blobId) {
-            throw new RuntimeException("broken everywhere");
-        }
-
-        @Override
-        public Mono<Void> deleteBucket(BucketName bucketName) {
-            return Mono.error(new RuntimeException("broken everywhere"));
-        }
-
-        @Override
-        public Mono<Void> delete(BucketName bucketName, BlobId blobId) {
-            return Mono.error(new RuntimeException("broken everywhere"));
-        }
-
-        @Override
-        public String toString() {
-            return MoreObjects.toStringHelper(this)
-                .toString();
-        }
-    }
-
-    private static class ThrowingBlobStore implements BlobStore {
-
-        @Override
-        public Mono<BlobId> save(BucketName bucketName, byte[] data, StoragePolicy storagePolicy) {
-            throw new RuntimeException("broken everywhere");
-        }
-
-        @Override
-        public Mono<BlobId> save(BucketName bucketName, String data, StoragePolicy storagePolicy) {
-            throw new RuntimeException("broken everywhere");
-        }
-
-        @Override
-        public BucketName getDefaultBucketName() {
-            return BucketName.DEFAULT;
-        }
-
-        @Override
-        public Mono<BlobId> save(BucketName bucketName, InputStream data, StoragePolicy storagePolicy) {
-            throw new RuntimeException("broken everywhere");
-        }
-
-        @Override
-        public Mono<byte[]> readBytes(BucketName bucketName, BlobId blobId) {
-            throw new RuntimeException("broken everywhere");
-        }
-
-        @Override
-        public InputStream read(BucketName bucketName, BlobId blobId) {
-            throw new RuntimeException("broken everywhere");
-        }
-
-        @Override
-        public Mono<Void> deleteBucket(BucketName bucketName) {
-            return Mono.error(new RuntimeException("broken everywhere"));
-        }
-
-        @Override
-        public Mono<Void> delete(BucketName bucketName, BlobId blobId) {
-            return Mono.error(new RuntimeException("broken everywhere"));
-        }
-
-        @Override
-        public String toString() {
-            return MoreObjects.toStringHelper(this)
-                .toString();
-        }
-    }
-
-    private static final HashBlobId.Factory BLOB_ID_FACTORY = new HashBlobId.Factory();
-    private static final String STRING_CONTENT = "blob content";
-    private static final byte [] BLOB_CONTENT = STRING_CONTENT.getBytes();
-
-    private BlobStore lowCostBlobStore;
-    private BlobStore highPerformanceBlobStore;
-    private HybridBlobStore hybridBlobStore;
-
-    @BeforeEach
-    void setup() {
-        lowCostBlobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY);
-        highPerformanceBlobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY);
-        hybridBlobStore = HybridBlobStore.builder()
-            .lowCost(lowCostBlobStore)
-            .highPerformance(highPerformanceBlobStore)
-            .configuration(HybridBlobStore.Configuration.DEFAULT)
-            .build();
-    }
-
-    @Override
-    public BlobStore testee() {
-        return hybridBlobStore;
-    }
-
-    @Override
-    public BlobId.Factory blobIdFactory() {
-        return BLOB_ID_FACTORY;
-    }
-
-    @Nested
-    class StoragePolicyTests {
-        @Test
-        void saveShouldRelyOnLowCostWhenLowCost() {
-            BlobId blobId = hybridBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST).block();
-
-            SoftAssertions.assertSoftly(softly -> {
-                softly.assertThat(lowCostBlobStore.read(BucketName.DEFAULT, blobId))
-                    .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
-                softly.assertThatThrownBy(() -> highPerformanceBlobStore.read(BucketName.DEFAULT, blobId))
-                    .isInstanceOf(ObjectNotFoundException.class);
-            });
-        }
-
-        @Test
-        void saveShouldRelyOnPerformingWhenPerforming() {
-            BlobId blobId = hybridBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, HIGH_PERFORMANCE).block();
-
-            SoftAssertions.assertSoftly(softly -> {
-                softly.assertThat(highPerformanceBlobStore.read(BucketName.DEFAULT, blobId))
-                    .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
-                softly.assertThatThrownBy(() -> lowCostBlobStore.read(BucketName.DEFAULT, blobId))
-                    .isInstanceOf(ObjectNotFoundException.class);
-            });
-        }
-
-        @Test
-        void saveShouldRelyOnPerformingWhenSizeBasedAndSmall() {
-            BlobId blobId = hybridBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, SIZE_BASED).block();
-
-            SoftAssertions.assertSoftly(softly -> {
-                softly.assertThat(highPerformanceBlobStore.read(BucketName.DEFAULT, blobId))
-                    .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
-                softly.assertThatThrownBy(() -> lowCostBlobStore.read(BucketName.DEFAULT, blobId))
-                    .isInstanceOf(ObjectNotFoundException.class);
-            });
-        }
-
-        @Test
-        void saveShouldRelyOnLowCostWhenSizeBasedAndBig() {
-            BlobId blobId = hybridBlobStore.save(BucketName.DEFAULT, TWELVE_MEGABYTES, SIZE_BASED).block();
-
-            SoftAssertions.assertSoftly(softly -> {
-                softly.assertThat(lowCostBlobStore.read(BucketName.DEFAULT, blobId))
-                    .satisfies(Throwing.consumer(inputStream -> assertThat(inputStream.read()).isGreaterThan(0)));
-                softly.assertThatThrownBy(() -> highPerformanceBlobStore.read(BucketName.DEFAULT, blobId))
-                    .isInstanceOf(ObjectNotFoundException.class);
-            });
-        }
-
-        @Test
-        void saveInputStreamShouldRelyOnLowCostWhenLowCost() {
-            BlobId blobId = hybridBlobStore.save(BucketName.DEFAULT, new ByteArrayInputStream(BLOB_CONTENT), LOW_COST).block();
-
-            SoftAssertions.assertSoftly(softly -> {
-                softly.assertThat(lowCostBlobStore.read(BucketName.DEFAULT, blobId))
-                    .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
-                softly.assertThatThrownBy(() -> highPerformanceBlobStore.read(BucketName.DEFAULT, blobId))
-                    .isInstanceOf(ObjectNotFoundException.class);
-            });
-        }
-
-        @Test
-        void saveInputStreamShouldRelyOnPerformingWhenPerforming() {
-            BlobId blobId = hybridBlobStore.save(BucketName.DEFAULT, new ByteArrayInputStream(BLOB_CONTENT), HIGH_PERFORMANCE).block();
-
-            SoftAssertions.assertSoftly(softly -> {
-                softly.assertThat(highPerformanceBlobStore.read(BucketName.DEFAULT, blobId))
-                    .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
-                softly.assertThatThrownBy(() -> lowCostBlobStore.read(BucketName.DEFAULT, blobId))
-                    .isInstanceOf(ObjectNotFoundException.class);
-            });
-        }
-
-        @Test
-        void saveInputStreamShouldRelyOnPerformingWhenSizeBasedAndSmall() {
-            BlobId blobId = hybridBlobStore.save(BucketName.DEFAULT, new ByteArrayInputStream(BLOB_CONTENT), SIZE_BASED).block();
-
-            SoftAssertions.assertSoftly(softly -> {
-                softly.assertThat(highPerformanceBlobStore.read(BucketName.DEFAULT, blobId))
-                    .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
-                softly.assertThatThrownBy(() -> lowCostBlobStore.read(BucketName.DEFAULT, blobId))
-                    .isInstanceOf(ObjectNotFoundException.class);
-            });
-        }
-
-        @Test
-        void saveInputStreamShouldRelyOnLowCostWhenSizeBasedAndBig() {
-            BlobId blobId = hybridBlobStore.save(BucketName.DEFAULT, new ByteArrayInputStream(TWELVE_MEGABYTES), SIZE_BASED).block();
-
-            SoftAssertions.assertSoftly(softly -> {
-                softly.assertThat(lowCostBlobStore.read(BucketName.DEFAULT, blobId))
-                    .satisfies(Throwing.consumer(inputStream -> assertThat(inputStream.read()).isGreaterThan(0)));
-                softly.assertThatThrownBy(() -> highPerformanceBlobStore.read(BucketName.DEFAULT, blobId))
-                    .isInstanceOf(ObjectNotFoundException.class);
-            });
-        }
-    }
-
-    @Nested
-    class LowCostSaveThrowsExceptionDirectly {
-        @Test
-        void saveShouldFailWhenException() {
-            BlobStore highPerformanceBlobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY);
-            HybridBlobStore hybridBlobStore = HybridBlobStore.builder()
-                .lowCost(new ThrowingBlobStore())
-                .highPerformance(highPerformanceBlobStore)
-                .configuration(HybridBlobStore.Configuration.DEFAULT)
-                .build();
-
-            assertThatThrownBy(() -> hybridBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block())
-                .isInstanceOf(RuntimeException.class);
-        }
-
-        @Test
-        void saveInputStreamShouldFailWhenException() {
-            BlobStore highPerformanceBlobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY);
-            HybridBlobStore hybridBlobStore = HybridBlobStore.builder()
-                .lowCost(new ThrowingBlobStore())
-                .highPerformance(highPerformanceBlobStore)
-                .configuration(HybridBlobStore.Configuration.DEFAULT)
-                .build();
-
-            assertThatThrownBy(() -> hybridBlobStore.save(hybridBlobStore.getDefaultBucketName(), new ByteArrayInputStream(BLOB_CONTENT), LOW_COST).block())
-                .isInstanceOf(RuntimeException.class);
-        }
-    }
-
-    @Nested
-    class LowCostSaveCompletesExceptionally {
-
-        @Test
-        void saveShouldFailWhenLowCostCompletedExceptionally() {
-            BlobStore highPerformanceBlobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY);
-            HybridBlobStore hybridBlobStore = HybridBlobStore.builder()
-                .lowCost(new FailingBlobStore())
-                .highPerformance(highPerformanceBlobStore)
-                .configuration(HybridBlobStore.Configuration.DEFAULT)
-                .build();
-
-            assertThatThrownBy(() -> hybridBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block())
-                .isInstanceOf(RuntimeException.class);
-        }
-
-        @Test
-        void saveInputStreamShouldFallBackToPerformingWhenLowCostCompletedExceptionally() {
-            BlobStore highPerformanceBlobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY);
-            HybridBlobStore hybridBlobStore = HybridBlobStore.builder()
-                .lowCost(new FailingBlobStore())
-                .highPerformance(highPerformanceBlobStore)
-                .configuration(HybridBlobStore.Configuration.DEFAULT)
-                .build();
-
-            assertThatThrownBy(() -> hybridBlobStore.save(hybridBlobStore.getDefaultBucketName(), new ByteArrayInputStream(BLOB_CONTENT), LOW_COST).block())
-                .isInstanceOf(RuntimeException.class);
-        }
-
-    }
-
-    @Nested
-    class LowCostReadThrowsExceptionDirectly {
-
-        @Test
-        void readShouldReturnFallbackToPerformingWhenLowCostGotException() {
-            BlobStore highPerformanceBlobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY);
-            HybridBlobStore hybridBlobStore = HybridBlobStore.builder()
-                .lowCost(new ThrowingBlobStore())
-                .highPerformance(highPerformanceBlobStore)
-                .configuration(HybridBlobStore.Configuration.DEFAULT)
-                .build();
-            BlobId blobId = Mono.from(highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST)).block();
-
-            assertThat(hybridBlobStore.read(hybridBlobStore.getDefaultBucketName(), blobId))
-                .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
-        }
-
-        @Test
-        void readBytesShouldReturnFallbackToPerformingWhenLowCostGotException() {
-            BlobStore highPerformanceBlobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY);
-
-            HybridBlobStore hybridBlobStore = HybridBlobStore.builder()
-                .lowCost(new ThrowingBlobStore())
-                .highPerformance(highPerformanceBlobStore)
-                .configuration(HybridBlobStore.Configuration.DEFAULT)
-                .build();
-            BlobId blobId = Mono.from(highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST)).block();
-
-            assertThat(hybridBlobStore.readBytes(hybridBlobStore.getDefaultBucketName(), blobId).block())
-                .isEqualTo(BLOB_CONTENT);
-        }
-
-    }
-
-    @Nested
-    class LowCostReadCompletesExceptionally {
-
-        @Test
-        void readShouldReturnFallbackToPerformingWhenLowCostCompletedExceptionally() {
-            BlobStore highPerformanceBlobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY);
-            HybridBlobStore hybridBlobStore = HybridBlobStore.builder()
-                .lowCost(new FailingBlobStore())
-                .highPerformance(highPerformanceBlobStore)
-                .configuration(HybridBlobStore.Configuration.DEFAULT)
-                .build();
-            BlobId blobId = Mono.from(highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST)).block();
-
-            assertThat(hybridBlobStore.read(hybridBlobStore.getDefaultBucketName(), blobId))
-                .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
-        }
-
-        @Test
-        void readBytesShouldReturnFallbackToPerformingWhenLowCostCompletedExceptionally() {
-            BlobStore highPerformanceBlobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY);
-            HybridBlobStore hybridBlobStore = HybridBlobStore.builder()
-                .lowCost(new FailingBlobStore())
-                .highPerformance(highPerformanceBlobStore)
-                .configuration(HybridBlobStore.Configuration.DEFAULT)
-                .build();
-            BlobId blobId = Mono.from(highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST)).block();
-
-            assertThat(hybridBlobStore.readBytes(hybridBlobStore.getDefaultBucketName(), blobId).block())
-                .isEqualTo(BLOB_CONTENT);
-        }
-    }
-
-    @Test
-    void readShouldReturnFromLowCostWhenAvailable() {
-        BlobId blobId = Mono.from(lowCostBlobStore.save(lowCostBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST)).block();
-
-        assertThat(hybridBlobStore.read(hybridBlobStore.getDefaultBucketName(), blobId))
-            .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
-    }
-
-    @Test
-    void readShouldReturnFromPerformingWhenLowCostNotAvailable() {
-        BlobId blobId = Mono.from(highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST)).block();
-
-        assertThat(hybridBlobStore.read(hybridBlobStore.getDefaultBucketName(), blobId))
-            .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
-    }
-
-    @Test
-    void readBytesShouldReturnFromLowCostWhenAvailable() {
-        BlobId blobId = Mono.from(lowCostBlobStore.save(lowCostBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST)).block();
-
-        assertThat(hybridBlobStore.readBytes(lowCostBlobStore.getDefaultBucketName(), blobId).block())
-            .isEqualTo(BLOB_CONTENT);
-    }
-
-    @Test
-    void readBytesShouldReturnFromPerformingWhenLowCostNotAvailable() {
-        BlobId blobId = Mono.from(highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST)).block();
-
-        assertThat(hybridBlobStore.readBytes(hybridBlobStore.getDefaultBucketName(), blobId).block())
-            .isEqualTo(BLOB_CONTENT);
-    }
-
-    @Test
-    void deleteBucketShouldDeleteBothLowCostAndPerformingBuckets() {
-        BlobId blobId1 = Mono.from(highPerformanceBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST)).block();
-        BlobId blobId2 = Mono.from(lowCostBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST)).block();
-
-        hybridBlobStore.deleteBucket(BucketName.DEFAULT).block();
-
-        assertThatThrownBy(() -> Mono.from(highPerformanceBlobStore.readBytes(BucketName.DEFAULT, blobId1)).block())
-            .isInstanceOf(ObjectStoreException.class);
-        assertThatThrownBy(() -> Mono.from(lowCostBlobStore.readBytes(BucketName.DEFAULT, blobId2)).block())
-            .isInstanceOf(ObjectStoreException.class);
-    }
-
-    @Test
-    void deleteBucketShouldDeleteLowCostBucketEvenWhenPerformingDoesNotExist() {
-        BlobId blobId = Mono.from(lowCostBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST)).block();
-
-        hybridBlobStore.deleteBucket(BucketName.DEFAULT).block();
-
-        assertThatThrownBy(() -> Mono.from(lowCostBlobStore.readBytes(BucketName.DEFAULT, blobId)).block())
-            .isInstanceOf(ObjectStoreException.class);
-    }
-
-    @Test
-    void deleteBucketShouldDeletePerformingBucketEvenWhenLowCostDoesNotExist() {
-        BlobId blobId = Mono.from(highPerformanceBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST)).block();
-
-        hybridBlobStore.deleteBucket(BucketName.DEFAULT).block();
-
-        assertThatThrownBy(() -> Mono.from(highPerformanceBlobStore.readBytes(BucketName.DEFAULT, blobId)).block())
-            .isInstanceOf(ObjectStoreException.class);
-    }
-
-    @Test
-    void deleteBucketShouldNotThrowWhenLowCostAndPerformingBucketsDoNotExist() {
-        assertThatCode(() -> hybridBlobStore.deleteBucket(BucketName.DEFAULT).block())
-            .doesNotThrowAnyException();
-    }
-
-    @Test
-    void getDefaultBucketNameShouldThrowWhenBlobStoreDontShareTheSameDefaultBucketName() {
-        lowCostBlobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY, BucketName.of("lowCost"));
-        highPerformanceBlobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY, BucketName.of("highPerformance"));
-        hybridBlobStore = HybridBlobStore.builder()
-            .lowCost(lowCostBlobStore)
-            .highPerformance(highPerformanceBlobStore)
-            .configuration(HybridBlobStore.Configuration.DEFAULT)
-            .build();
-
-        assertThatThrownBy(() -> hybridBlobStore.getDefaultBucketName())
-            .isInstanceOf(IllegalStateException.class);
-    }
-
-    @Test
-    void deleteShouldDeleteBothLowCostAndPerformingBlob() {
-        BlobId blobId1 = hybridBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST).block();
-        BlobId blobId2 = hybridBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, HIGH_PERFORMANCE).block();
-
-        hybridBlobStore.delete(BucketName.DEFAULT, blobId1).block();
-
-        assertThatThrownBy(() -> Mono.from(highPerformanceBlobStore.readBytes(BucketName.DEFAULT, blobId1)).block())
-            .isInstanceOf(ObjectStoreException.class);
-        assertThatThrownBy(() -> Mono.from(lowCostBlobStore.readBytes(BucketName.DEFAULT, blobId2)).block())
-            .isInstanceOf(ObjectStoreException.class);
-    }
-
-    @Test
-    void deleteShouldDeleteLowCostBlobEvenWhenPerformingDoesNotExist() {
-        BlobId blobId = Mono.from(lowCostBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST)).block();
-
-        hybridBlobStore.delete(BucketName.DEFAULT, blobId).block();
-
-        assertThatThrownBy(() -> Mono.from(lowCostBlobStore.readBytes(BucketName.DEFAULT, blobId)).block())
-            .isInstanceOf(ObjectStoreException.class);
-    }
-
-    @Test
-    void deleteShouldDeletePerformingBlobEvenWhenLowCostDoesNotExist() {
-        BlobId blobId = Mono.from(highPerformanceBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST)).block();
-
-        hybridBlobStore.delete(BucketName.DEFAULT, blobId).block();
-
-        assertThatThrownBy(() -> Mono.from(highPerformanceBlobStore.readBytes(BucketName.DEFAULT, blobId)).block())
-            .isInstanceOf(ObjectStoreException.class);
-    }
-
-    @Test
-    void deleteShouldNotThrowWhenLowCostAndPerformingBlobsDoNotExist() {
-        assertThatCode(() -> hybridBlobStore.delete(BucketName.DEFAULT, blobIdFactory().randomId()).block())
-            .doesNotThrowAnyException();
-    }
-
-    @Nested
-    class ConfigurationTest {
-        @Test
-        void shouldMatchBeanContract() {
-            EqualsVerifier.forClass(HybridBlobStore.Configuration.class)
-                .verify();
-        }
-    }
-}
diff --git a/server/blob/pom.xml b/server/blob/pom.xml
index f62e027..a4d6742 100644
--- a/server/blob/pom.xml
+++ b/server/blob/pom.xml
@@ -42,7 +42,6 @@
         <module>blob-gc</module>
         <module>blob-memory</module>
         <module>blob-objectstorage</module>
-        <module>blob-union</module>
 
         <module>mail-store</module>
     </modules>
diff --git a/server/container/guice/cassandra-rabbitmq-guice/pom.xml b/server/container/guice/cassandra-rabbitmq-guice/pom.xml
index 6edd29c..c0fefec 100644
--- a/server/container/guice/cassandra-rabbitmq-guice/pom.xml
+++ b/server/container/guice/cassandra-rabbitmq-guice/pom.xml
@@ -128,10 +128,6 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
-            <artifactId>blob-union</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
             <artifactId>james-server-cassandra-guice</artifactId>
         </dependency>
         <dependency>
diff --git a/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreChoosingModule.java b/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreChoosingModule.java
index 5b30d21..99e9ea2 100644
--- a/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreChoosingModule.java
+++ b/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreChoosingModule.java
@@ -19,22 +19,11 @@
 
 package org.apache.james.modules.blobstore;
 
-import java.io.FileNotFoundException;
-
-import javax.inject.Singleton;
-
-import org.apache.commons.configuration2.Configuration;
-import org.apache.commons.configuration2.ex.ConfigurationException;
 import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.blob.cassandra.CassandraBlobModule;
-import org.apache.james.blob.union.HybridBlobStore;
-import org.apache.james.modules.mailbox.ConfigurationComponent;
 import org.apache.james.modules.objectstorage.ObjectStorageDependenciesModule;
-import org.apache.james.utils.PropertiesProvider;
 
-import com.google.common.annotations.VisibleForTesting;
 import com.google.inject.AbstractModule;
-import com.google.inject.Provides;
 import com.google.inject.multibindings.Multibinder;
 
 public class BlobStoreChoosingModule extends AbstractModule {
@@ -45,16 +34,4 @@ public class BlobStoreChoosingModule extends AbstractModule {
         Multibinder<CassandraModule> cassandraDataDefinitions = Multibinder.newSetBinder(binder(), CassandraModule.class);
         cassandraDataDefinitions.addBinding().toInstance(CassandraBlobModule.MODULE);
     }
-
-    @Provides
-    @Singleton
-    @VisibleForTesting
-    HybridBlobStore.Configuration providesHybridBlobStoreConfiguration(PropertiesProvider propertiesProvider) {
-        try {
-            Configuration configuration = propertiesProvider.getConfigurations(ConfigurationComponent.NAMES);
-            return HybridBlobStore.Configuration.from(configuration);
-        } catch (FileNotFoundException | ConfigurationException e) {
-            return HybridBlobStore.Configuration.DEFAULT;
-        }
-    }
 }
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 c6044fa..789813b 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
@@ -57,8 +57,7 @@ public class BlobStoreConfiguration {
 
     public enum BlobStoreImplName {
         CASSANDRA("cassandra"),
-        OBJECTSTORAGE("objectstorage"),
-        HYBRID("hybrid");
+        OBJECTSTORAGE("objectstorage");
 
         static String supportedImplNames() {
             return Stream.of(BlobStoreImplName.values())
@@ -127,10 +126,6 @@ public class BlobStoreConfiguration {
         return new CacheChoice(BlobStoreImplName.OBJECTSTORAGE);
     }
 
-    public static BlobStoreConfiguration hybrid() {
-        return new BlobStoreConfiguration(BlobStoreImplName.HYBRID, !CACHE_ENABLED);
-    }
-
     private final BlobStoreImplName implementation;
     private final boolean cacheEnabled;
 
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 b38ea7a..d790968 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
@@ -19,29 +19,21 @@
 
 package org.apache.james.modules.blobstore;
 
-import java.io.FileNotFoundException;
 import java.util.List;
 
-import javax.inject.Named;
-import javax.inject.Singleton;
-
-import org.apache.commons.configuration2.Configuration;
-import org.apache.commons.configuration2.ex.ConfigurationException;
 import org.apache.james.blob.api.BlobStore;
-import org.apache.james.blob.cassandra.CassandraBlobStore;
+import org.apache.james.blob.api.DumbBlobStore;
+import org.apache.james.blob.cassandra.CassandraDumbBlobStore;
 import org.apache.james.blob.cassandra.cache.CachedBlobStore;
 import org.apache.james.blob.objectstorage.ObjectStorageBlobStore;
-import org.apache.james.blob.union.HybridBlobStore;
 import org.apache.james.modules.mailbox.CassandraBlobStoreDependenciesModule;
-import org.apache.james.modules.mailbox.ConfigurationComponent;
 import org.apache.james.modules.objectstorage.ObjectStorageDependenciesModule;
-import org.apache.james.utils.PropertiesProvider;
+import org.apache.james.server.blob.deduplication.DeDuplicationBlobStore;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 import com.google.inject.AbstractModule;
 import com.google.inject.Module;
-import com.google.inject.Provides;
 import com.google.inject.name.Names;
 
 public class BlobStoreModulesChooser {
@@ -68,39 +60,6 @@ public class BlobStoreModulesChooser {
         }
     }
 
-    static class HybridDeclarationModule extends AbstractModule {
-        @Override
-        protected void configure() {
-            install(new ObjectStorageDependenciesModule());
-            install(new CassandraBlobStoreDependenciesModule());
-        }
-
-        @Provides
-        @Singleton
-        @VisibleForTesting
-        HybridBlobStore.Configuration providesHybridBlobStoreConfiguration(PropertiesProvider propertiesProvider) {
-            try {
-                Configuration configuration = propertiesProvider.getConfigurations(ConfigurationComponent.NAMES);
-                return HybridBlobStore.Configuration.from(configuration);
-            } catch (FileNotFoundException | ConfigurationException e) {
-                return HybridBlobStore.Configuration.DEFAULT;
-            }
-        }
-
-        @Provides
-        @Named(CachedBlobStore.BACKEND)
-        @Singleton
-        BlobStore providesHybridBlobStore(HybridBlobStore.Configuration hybridBlobStoreConfiguration,
-                                          CassandraBlobStore cassandraBlobStore,
-                                          ObjectStorageBlobStore objectStorageBlobStore) {
-            return HybridBlobStore.builder()
-                .lowCost(objectStorageBlobStore)
-                .highPerformance(cassandraBlobStore)
-                .configuration(hybridBlobStoreConfiguration)
-                .build();
-        }
-    }
-
     @VisibleForTesting
     public static List<Module> chooseModules(BlobStoreConfiguration choosingConfiguration) {
         switch (choosingConfiguration.getImplementation()) {
@@ -108,8 +67,6 @@ public class BlobStoreModulesChooser {
                 return ImmutableList.of(new CassandraDeclarationModule());
             case OBJECTSTORAGE:
                 return ImmutableList.of(new ObjectStorageDeclarationModule());
-            case HYBRID:
-                return ImmutableList.of(new HybridDeclarationModule());
             default:
                 throw new RuntimeException("Unsuported blobStore implementation " + choosingConfiguration.getImplementation());
         }
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 a1ae0c7..e870fb7 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
@@ -34,7 +34,6 @@ class BlobStoreConfigurationTest {
 
     private static final String OBJECT_STORAGE = "objectstorage";
     private static final String CASSANDRA = "cassandra";
-    private static final String HYBRID = "hybrid";
 
     @Test
     void shouldMatchBeanContract() {
@@ -101,18 +100,6 @@ class BlobStoreConfigurationTest {
     }
 
     @Test
-    void provideChoosingConfigurationShouldReturnHybridConfigurationWhenConfigurationImplIsHybrid() throws Exception {
-        PropertiesConfiguration configuration = new PropertiesConfiguration();
-        configuration.addProperty("implementation", BlobStoreConfiguration.BlobStoreImplName.HYBRID.getName());
-        FakePropertiesProvider propertyProvider = FakePropertiesProvider.builder()
-            .register(ConfigurationComponent.NAME, configuration)
-            .build();
-
-        assertThat(parse(propertyProvider))
-            .isEqualTo(BlobStoreConfiguration.hybrid());
-    }
-
-    @Test
     void provideChoosingConfigurationShouldReturnCassandraFactoryWhenConfigurationImplIsCassandra() throws Exception {
         PropertiesConfiguration configuration = new PropertiesConfiguration();
         configuration.addProperty("implementation", BlobStoreConfiguration.BlobStoreImplName.CASSANDRA.getName());
@@ -131,7 +118,7 @@ class BlobStoreConfigurationTest {
 
         assertThatThrownBy(() -> BlobStoreConfiguration.from(configuration))
             .isInstanceOf(IllegalStateException.class)
-            .hasMessage("implementation property is missing please use one of supported values in: cassandra, objectstorage, hybrid");
+            .hasMessage("implementation property is missing please use one of supported values in: cassandra, objectstorage");
     }
 
     @Test
@@ -141,7 +128,7 @@ class BlobStoreConfigurationTest {
 
         assertThatThrownBy(() -> BlobStoreConfiguration.from(configuration))
             .isInstanceOf(IllegalStateException.class)
-            .hasMessage("implementation property is missing please use one of supported values in: cassandra, objectstorage, hybrid");
+            .hasMessage("implementation property is missing please use one of supported values in: cassandra, objectstorage");
     }
 
     @Test
@@ -151,7 +138,7 @@ class BlobStoreConfigurationTest {
 
         assertThatThrownBy(() -> BlobStoreConfiguration.from(configuration))
             .isInstanceOf(IllegalStateException.class)
-            .hasMessage("implementation property is missing please use one of supported values in: cassandra, objectstorage, hybrid");
+            .hasMessage("implementation property is missing please use one of supported values in: cassandra, objectstorage");
     }
 
     @Test
@@ -161,7 +148,7 @@ class BlobStoreConfigurationTest {
 
         assertThatThrownBy(() -> BlobStoreConfiguration.from(configuration))
             .isInstanceOf(IllegalArgumentException.class)
-            .hasMessage("un_supported is not a valid name of BlobStores, please use one of supported values in: cassandra, objectstorage, hybrid");
+            .hasMessage("un_supported is not a valid name of BlobStores, please use one of supported values in: cassandra, objectstorage");
     }
 
     @Test
@@ -177,18 +164,6 @@ class BlobStoreConfigurationTest {
     }
 
     @Test
-    void fromShouldReturnConfigurationWhenBlobStoreImplIsUnion() {
-        PropertiesConfiguration configuration = new PropertiesConfiguration();
-        configuration.addProperty("implementation", HYBRID);
-
-        assertThat(
-            BlobStoreConfiguration.from(configuration)
-                .getImplementation()
-                .getName())
-            .isEqualTo(HYBRID);
-    }
-
-    @Test
     void fromShouldReturnConfigurationWhenBlobStoreImplIsObjectStorage() {
         PropertiesConfiguration configuration = new PropertiesConfiguration();
         configuration.addProperty("implementation", OBJECT_STORAGE);
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 2b77046..da99509 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
@@ -19,68 +19,11 @@
 
 package org.apache.james.modules.blobstore;
 
-import static org.apache.james.modules.blobstore.BlobStoreModulesChooser.HybridDeclarationModule;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
-import org.apache.commons.configuration2.PropertiesConfiguration;
-import org.apache.james.FakePropertiesProvider;
-import org.apache.james.blob.union.HybridBlobStore;
-import org.apache.james.modules.mailbox.ConfigurationComponent;
 import org.junit.jupiter.api.Test;
 
 class BlobStoreModulesChooserTest {
-    @Test
-    void providesHybridBlobStoreConfigurationShouldThrowWhenNegative() {
-        HybridDeclarationModule module = new HybridDeclarationModule();
-        PropertiesConfiguration configuration = new PropertiesConfiguration();
-        configuration.addProperty("hybrid.size.threshold", -1);
-        FakePropertiesProvider propertyProvider = FakePropertiesProvider.builder()
-            .register(ConfigurationComponent.NAME, configuration)
-            .build();
-
-        assertThatThrownBy(() -> module.providesHybridBlobStoreConfiguration(propertyProvider))
-            .isInstanceOf(IllegalArgumentException.class);
-    }
-
-    @Test
-    void providesHybridBlobStoreConfigurationShouldNotThrowWhenZero() {
-        HybridDeclarationModule module = new HybridDeclarationModule();
-        PropertiesConfiguration configuration = new PropertiesConfiguration();
-        configuration.addProperty("hybrid.size.threshold", 0);
-        FakePropertiesProvider propertyProvider = FakePropertiesProvider.builder()
-            .register(ConfigurationComponent.NAME, configuration)
-            .build();
-
-        assertThat(module.providesHybridBlobStoreConfiguration(propertyProvider))
-            .isEqualTo(new HybridBlobStore.Configuration(0));
-    }
-
-    @Test
-    void providesHybridBlobStoreConfigurationShouldReturnConfiguration() {
-        HybridDeclarationModule module = new HybridDeclarationModule();
-        PropertiesConfiguration configuration = new PropertiesConfiguration();
-        configuration.addProperty("hybrid.size.threshold", 36);
-        FakePropertiesProvider propertyProvider = FakePropertiesProvider.builder()
-            .register(ConfigurationComponent.NAME, configuration)
-            .build();
-
-        assertThat(module.providesHybridBlobStoreConfiguration(propertyProvider))
-            .isEqualTo(new HybridBlobStore.Configuration(36));
-    }
-
-    @Test
-    void providesHybridBlobStoreConfigurationShouldReturnConfigurationWhenLegacyFile() {
-        HybridDeclarationModule module = new HybridDeclarationModule();
-        PropertiesConfiguration configuration = new PropertiesConfiguration();
-        configuration.addProperty("hybrid.size.threshold", 36);
-        FakePropertiesProvider propertyProvider = FakePropertiesProvider.builder()
-            .register(ConfigurationComponent.LEGACY, configuration)
-            .build();
-
-        assertThat(module.providesHybridBlobStoreConfiguration(propertyProvider))
-            .isEqualTo(new HybridBlobStore.Configuration(36));
-    }
 
     @Test
     void provideBlobStoreShouldReturnObjectStoreBlobStoreWhenObjectStoreConfigured() {
@@ -95,11 +38,4 @@ class BlobStoreModulesChooserTest {
             .first()
             .isInstanceOf(BlobStoreModulesChooser.CassandraDeclarationModule.class);
     }
-
-    @Test
-    void provideBlobStoreShouldReturnHybridBlobStoreWhenHybridConfigured() {
-        assertThat(BlobStoreModulesChooser.chooseModules(BlobStoreConfiguration.hybrid()))
-            .first()
-            .isInstanceOf(BlobStoreModulesChooser.HybridDeclarationModule.class);
-    }
 }
\ No newline at end of file
diff --git a/src/site/xdoc/server/config-blobstore.xml b/src/site/xdoc/server/config-blobstore.xml
index 389aee8..299d764 100644
--- a/src/site/xdoc/server/config-blobstore.xml
+++ b/src/site/xdoc/server/config-blobstore.xml
@@ -46,8 +46,6 @@
                 <dt><strong>implementation</strong></dt>
                 <dd>cassandra: use cassandra based BlobStore</dd>
                 <dd>objectstorage: use Swift/AWS S3 based BlobStore</dd>
-                <dd>hybrid (deprecated, use CachedBlobStore instead): Using both objectstorage for unfrequently read or big blobs &amp; cassandra for small, often read blobs.
-                    Introduced to fasten small blob access, its usage could be compared to a cache, but with a sub-optimal implementation (no eviction, default replication factor, no  circuit breaking).</dd>
             </dl>
 
 
@@ -79,13 +77,6 @@
                 </dl>
             </subsection>
 
-            <subsection name="Hybrid BlobStore size threshold (deprecated)">
-                <dl>
-                    <dt><strong>hybrid.size.threshold</strong></dt>
-                    <dd>DEFAULT: 32768 bytes (32KB), must be positive. Size threshold for considering a blob as 'big', causing it to be saved in the low cost blobStore.</dd>
-                </dl>
-            </subsection>
-
             <subsection name="ObjectStorage BlobStore Codec Configuration">
                 <dl>
                     <dt><strong>objectstorage.payload.codec</strong></dt>


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


[james-project] 15/18: JAMES-3313 MemoryBlobStore should use DeduplicatingBlobStore

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 62d934caf9fffe3463b2c5309e97ece8c51238c1
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Jul 17 16:41:01 2020 +0700

    JAMES-3313 MemoryBlobStore should use DeduplicatingBlobStore
---
 .../james/vault/DeletedMessageVaultHookTest.java   |   5 +-
 .../blob/BlobStoreDeletedMessageVaultTest.java     |   5 +-
 pom.xml                                            |   5 +
 .../file/LocalFileBlobExportMechanismTest.java     |   5 +-
 server/blob/blob-memory/pom.xml                    |   4 +
 .../apache/james/blob/memory/MemoryBlobStore.java  | 116 ---------------------
 .../james/blob/memory/MemoryBlobStoreFactory.java} |  25 ++---
 .../james/blob/memory/MemoryBlobStoreTest.java     |   2 +-
 .../james/blob/union/HybridBlobStoreTest.java      |  75 +++++++------
 .../james/blob/mail/MimeMessageStoreTest.java      |   5 +-
 .../org/apache/james/modules/BlobMemoryModule.java |  17 ++-
 .../routes/DeletedMessagesVaultRoutesTest.java     |   8 +-
 .../webadmin/service/ExportServiceTestSystem.java  |   5 +-
 .../linshare/LinshareBlobExportMechanismTest.java  |   8 +-
 14 files changed, 90 insertions(+), 195 deletions(-)

diff --git a/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageVaultHookTest.java b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageVaultHookTest.java
index 3b6fd29..766fdd9 100644
--- a/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageVaultHookTest.java
+++ b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageVaultHookTest.java
@@ -30,8 +30,7 @@ import java.time.ZoneOffset;
 import java.util.List;
 
 import org.apache.james.blob.api.HashBlobId;
-import org.apache.james.blob.memory.MemoryBlobStore;
-import org.apache.james.blob.memory.MemoryDumbBlobStore;
+import org.apache.james.blob.memory.MemoryBlobStoreFactory;
 import org.apache.james.core.MailAddress;
 import org.apache.james.core.MaybeSender;
 import org.apache.james.core.Username;
@@ -110,7 +109,7 @@ class DeletedMessageVaultHookTest {
     void setUp() throws Exception {
         clock = Clock.fixed(DELETION_DATE.toInstant(), ZoneOffset.UTC);
         messageVault = new BlobStoreDeletedMessageVault(new RecordingMetricFactory(), new MemoryDeletedMessageMetadataVault(),
-            new MemoryBlobStore(new HashBlobId.Factory(), new MemoryDumbBlobStore()), new BucketNameGenerator(clock), clock,
+            MemoryBlobStoreFactory.create(new HashBlobId.Factory()), new BucketNameGenerator(clock), clock,
             RetentionConfiguration.DEFAULT);
 
         DeletedMessageConverter deletedMessageConverter = new DeletedMessageConverter();
diff --git a/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/blob/BlobStoreDeletedMessageVaultTest.java b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/blob/BlobStoreDeletedMessageVaultTest.java
index c499529..a965a89 100644
--- a/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/blob/BlobStoreDeletedMessageVaultTest.java
+++ b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/blob/BlobStoreDeletedMessageVaultTest.java
@@ -40,8 +40,7 @@ import java.time.ZonedDateTime;
 
 import org.apache.james.blob.api.BucketName;
 import org.apache.james.blob.api.HashBlobId;
-import org.apache.james.blob.memory.MemoryBlobStore;
-import org.apache.james.blob.memory.MemoryDumbBlobStore;
+import org.apache.james.blob.memory.MemoryBlobStoreFactory;
 import org.apache.james.metrics.tests.RecordingMetricFactory;
 import org.apache.james.utils.UpdatableTickingClock;
 import org.apache.james.vault.DeletedMessageVault;
@@ -65,7 +64,7 @@ class BlobStoreDeletedMessageVaultTest implements DeletedMessageVaultContract, D
         clock = new UpdatableTickingClock(NOW.toInstant());
         metricFactory = new RecordingMetricFactory();
         messageVault = new BlobStoreDeletedMessageVault(metricFactory, new MemoryDeletedMessageMetadataVault(),
-            new MemoryBlobStore(new HashBlobId.Factory(), new MemoryDumbBlobStore()),
+            MemoryBlobStoreFactory.create(new HashBlobId.Factory()),
             new BucketNameGenerator(clock), clock, RetentionConfiguration.DEFAULT);
     }
 
diff --git a/pom.xml b/pom.xml
index 46add5d..8881160 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1128,6 +1128,11 @@
             </dependency>
             <dependency>
                 <groupId>${james.groupId}</groupId>
+                <artifactId>blob-deduplicating</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>${james.groupId}</groupId>
                 <artifactId>blob-export-api</artifactId>
                 <version>${project.version}</version>
             </dependency>
diff --git a/server/blob/blob-export-file/src/test/java/org/apache/james/blob/export/file/LocalFileBlobExportMechanismTest.java b/server/blob/blob-export-file/src/test/java/org/apache/james/blob/export/file/LocalFileBlobExportMechanismTest.java
index dc1262c..4eff0c4 100644
--- a/server/blob/blob-export-file/src/test/java/org/apache/james/blob/export/file/LocalFileBlobExportMechanismTest.java
+++ b/server/blob/blob-export-file/src/test/java/org/apache/james/blob/export/file/LocalFileBlobExportMechanismTest.java
@@ -41,8 +41,7 @@ import org.apache.james.blob.api.HashBlobId;
 import org.apache.james.blob.api.ObjectStoreException;
 import org.apache.james.blob.export.api.FileExtension;
 import org.apache.james.blob.export.file.LocalFileBlobExportMechanism.Configuration;
-import org.apache.james.blob.memory.MemoryBlobStore;
-import org.apache.james.blob.memory.MemoryDumbBlobStore;
+import org.apache.james.blob.memory.MemoryBlobStoreFactory;
 import org.apache.james.dnsservice.api.DNSService;
 import org.apache.james.filesystem.api.FileSystem;
 import org.apache.james.util.MimeMessageUtil;
@@ -68,7 +67,7 @@ class LocalFileBlobExportMechanismTest {
     @BeforeEach
     void setUp(FileSystem fileSystem) throws Exception {
         mailetContext = FakeMailContext.builder().postmaster(MailAddressFixture.POSTMASTER_AT_JAMES).build();
-        blobStore = new MemoryBlobStore(new HashBlobId.Factory(), new MemoryDumbBlobStore());
+        blobStore = MemoryBlobStoreFactory.create(new HashBlobId.Factory());
 
         InetAddress localHost = mock(InetAddress.class);
         when(localHost.getHostName()).thenReturn(JAMES_HOST);
diff --git a/server/blob/blob-memory/pom.xml b/server/blob/blob-memory/pom.xml
index 48c7bba..4224387 100644
--- a/server/blob/blob-memory/pom.xml
+++ b/server/blob/blob-memory/pom.xml
@@ -44,6 +44,10 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>blob-deduplicating</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>james-server-util</artifactId>
             <scope>test</scope>
         </dependency>
diff --git a/server/blob/blob-memory/src/main/java/org/apache/james/blob/memory/MemoryBlobStore.java b/server/blob/blob-memory/src/main/java/org/apache/james/blob/memory/MemoryBlobStore.java
deleted file mode 100644
index 6c5b67c..0000000
--- a/server/blob/blob-memory/src/main/java/org/apache/james/blob/memory/MemoryBlobStore.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/****************************************************************
- * 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.blob.memory;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-import javax.inject.Inject;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.james.blob.api.BlobId;
-import org.apache.james.blob.api.BlobStore;
-import org.apache.james.blob.api.BucketName;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-
-import reactor.core.publisher.Mono;
-
-public class MemoryBlobStore implements BlobStore {
-    private final BlobId.Factory factory;
-    private final BucketName defaultBucketName;
-    private final MemoryDumbBlobStore dumbBlobStore;
-
-    @Inject
-    public MemoryBlobStore(BlobId.Factory factory, MemoryDumbBlobStore dumbBlobStore) {
-        this(factory, BucketName.DEFAULT, dumbBlobStore);
-    }
-
-    @VisibleForTesting
-    public MemoryBlobStore(BlobId.Factory factory, BucketName defaultBucketName, MemoryDumbBlobStore dumbBlobStore) {
-        this.factory = factory;
-        this.defaultBucketName = defaultBucketName;
-        this.dumbBlobStore = dumbBlobStore;
-    }
-
-    @Override
-    public Mono<BlobId> save(BucketName bucketName, byte[] data, StoragePolicy storagePolicy) {
-        Preconditions.checkNotNull(bucketName);
-        Preconditions.checkNotNull(data);
-
-        BlobId blobId = factory.forPayload(data);
-
-        return dumbBlobStore.save(bucketName, blobId, data)
-            .then(Mono.just(blobId));
-    }
-
-    @Override
-    public Mono<BlobId> save(BucketName bucketName, InputStream data, StoragePolicy storagePolicy) {
-        Preconditions.checkNotNull(bucketName);
-        Preconditions.checkNotNull(data);
-        try {
-            byte[] bytes = IOUtils.toByteArray(data);
-            return save(bucketName, bytes, storagePolicy);
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    @Override
-    public Mono<byte[]> readBytes(BucketName bucketName, BlobId blobId) {
-        Preconditions.checkNotNull(bucketName);
-        return retrieveStoredValue(bucketName, blobId);
-    }
-
-    @Override
-    public InputStream read(BucketName bucketName, BlobId blobId) {
-        Preconditions.checkNotNull(bucketName);
-        return retrieveStoredValue(bucketName, blobId)
-            .map(ByteArrayInputStream::new)
-            .block();
-    }
-
-    @Override
-    public Mono<Void> deleteBucket(BucketName bucketName) {
-        Preconditions.checkNotNull(bucketName);
-
-        return dumbBlobStore.deleteBucket(bucketName);
-    }
-
-    private Mono<byte[]> retrieveStoredValue(BucketName bucketName, BlobId blobId) {
-        return dumbBlobStore.readBytes(bucketName, blobId);
-    }
-
-    @Override
-    public BucketName getDefaultBucketName() {
-        return defaultBucketName;
-    }
-
-    @Override
-    public Mono<Void> delete(BucketName bucketName, BlobId blobId) {
-        Preconditions.checkNotNull(bucketName);
-        Preconditions.checkNotNull(blobId);
-
-        return dumbBlobStore.delete(bucketName, blobId);
-    }
-
-}
diff --git a/server/container/guice/blob-memory-guice/src/main/java/org/apache/james/modules/BlobMemoryModule.java b/server/blob/blob-memory/src/main/java/org/apache/james/blob/memory/MemoryBlobStoreFactory.java
similarity index 70%
copy from server/container/guice/blob-memory-guice/src/main/java/org/apache/james/modules/BlobMemoryModule.java
copy to server/blob/blob-memory/src/main/java/org/apache/james/blob/memory/MemoryBlobStoreFactory.java
index a9ab646..5e7c7e2 100644
--- a/server/container/guice/blob-memory-guice/src/main/java/org/apache/james/modules/BlobMemoryModule.java
+++ b/server/blob/blob-memory/src/main/java/org/apache/james/blob/memory/MemoryBlobStoreFactory.java
@@ -17,24 +17,21 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.modules;
+package org.apache.james.blob.memory;
 
 import org.apache.james.blob.api.BlobId;
 import org.apache.james.blob.api.BlobStore;
-import org.apache.james.blob.api.HashBlobId;
-import org.apache.james.blob.memory.MemoryBlobStore;
+import org.apache.james.blob.api.BucketName;
+import org.apache.james.server.blob.deduplication.DeDuplicationBlobStore;
 
-import com.google.inject.AbstractModule;
-import com.google.inject.Scopes;
-
-public class BlobMemoryModule extends AbstractModule {
-
-    @Override
-    protected void configure() {
-        bind(HashBlobId.Factory.class).in(Scopes.SINGLETON);
-        bind(BlobId.Factory.class).to(HashBlobId.Factory.class);
+public class MemoryBlobStoreFactory {
+    public static BlobStore create(BlobId.Factory factory) {
+        return create(factory, BucketName.DEFAULT);
+    }
 
-        bind(MemoryBlobStore.class).in(Scopes.SINGLETON);
-        bind(BlobStore.class).to(MemoryBlobStore.class);
+    public static BlobStore create(BlobId.Factory factory, BucketName defaultBucketName) {
+        return new DeDuplicationBlobStore(
+            new MemoryDumbBlobStore(),
+            defaultBucketName, factory);
     }
 }
diff --git a/server/blob/blob-memory/src/test/java/org/apache/james/blob/memory/MemoryBlobStoreTest.java b/server/blob/blob-memory/src/test/java/org/apache/james/blob/memory/MemoryBlobStoreTest.java
index 9d7f052..80dba7e 100644
--- a/server/blob/blob-memory/src/test/java/org/apache/james/blob/memory/MemoryBlobStoreTest.java
+++ b/server/blob/blob-memory/src/test/java/org/apache/james/blob/memory/MemoryBlobStoreTest.java
@@ -33,7 +33,7 @@ public class MemoryBlobStoreTest implements MetricableBlobStoreContract {
 
     @BeforeEach
     void setUp() {
-        blobStore = new MetricableBlobStore(metricsTestExtension.getMetricFactory(), new MemoryBlobStore(BLOB_ID_FACTORY, new MemoryDumbBlobStore()));
+        blobStore = new MetricableBlobStore(metricsTestExtension.getMetricFactory(), MemoryBlobStoreFactory.create(BLOB_ID_FACTORY));
     }
 
     @Override
diff --git a/server/blob/blob-union/src/test/java/org/apache/james/blob/union/HybridBlobStoreTest.java b/server/blob/blob-union/src/test/java/org/apache/james/blob/union/HybridBlobStoreTest.java
index a17dc55..ee36d52 100644
--- a/server/blob/blob-union/src/test/java/org/apache/james/blob/union/HybridBlobStoreTest.java
+++ b/server/blob/blob-union/src/test/java/org/apache/james/blob/union/HybridBlobStoreTest.java
@@ -36,8 +36,7 @@ import org.apache.james.blob.api.BucketName;
 import org.apache.james.blob.api.HashBlobId;
 import org.apache.james.blob.api.ObjectNotFoundException;
 import org.apache.james.blob.api.ObjectStoreException;
-import org.apache.james.blob.memory.MemoryBlobStore;
-import org.apache.james.blob.memory.MemoryDumbBlobStore;
+import org.apache.james.blob.memory.MemoryBlobStoreFactory;
 import org.assertj.core.api.SoftAssertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
@@ -152,14 +151,14 @@ class HybridBlobStoreTest implements BlobStoreContract {
     private static final String STRING_CONTENT = "blob content";
     private static final byte [] BLOB_CONTENT = STRING_CONTENT.getBytes();
 
-    private MemoryBlobStore lowCostBlobStore;
-    private MemoryBlobStore highPerformanceBlobStore;
+    private BlobStore lowCostBlobStore;
+    private BlobStore highPerformanceBlobStore;
     private HybridBlobStore hybridBlobStore;
 
     @BeforeEach
     void setup() {
-        lowCostBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY, new MemoryDumbBlobStore());
-        highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY, new MemoryDumbBlobStore());
+        lowCostBlobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY);
+        highPerformanceBlobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY);
         hybridBlobStore = HybridBlobStore.builder()
             .lowCost(lowCostBlobStore)
             .highPerformance(highPerformanceBlobStore)
@@ -280,7 +279,7 @@ class HybridBlobStoreTest implements BlobStoreContract {
     class LowCostSaveThrowsExceptionDirectly {
         @Test
         void saveShouldFailWhenException() {
-            MemoryBlobStore highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY, new MemoryDumbBlobStore());
+            BlobStore highPerformanceBlobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY);
             HybridBlobStore hybridBlobStore = HybridBlobStore.builder()
                 .lowCost(new ThrowingBlobStore())
                 .highPerformance(highPerformanceBlobStore)
@@ -293,7 +292,7 @@ class HybridBlobStoreTest implements BlobStoreContract {
 
         @Test
         void saveInputStreamShouldFailWhenException() {
-            MemoryBlobStore highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY, new MemoryDumbBlobStore());
+            BlobStore highPerformanceBlobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY);
             HybridBlobStore hybridBlobStore = HybridBlobStore.builder()
                 .lowCost(new ThrowingBlobStore())
                 .highPerformance(highPerformanceBlobStore)
@@ -310,7 +309,7 @@ class HybridBlobStoreTest implements BlobStoreContract {
 
         @Test
         void saveShouldFailWhenLowCostCompletedExceptionally() {
-            MemoryBlobStore highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY, new MemoryDumbBlobStore());
+            BlobStore highPerformanceBlobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY);
             HybridBlobStore hybridBlobStore = HybridBlobStore.builder()
                 .lowCost(new FailingBlobStore())
                 .highPerformance(highPerformanceBlobStore)
@@ -323,7 +322,7 @@ class HybridBlobStoreTest implements BlobStoreContract {
 
         @Test
         void saveInputStreamShouldFallBackToPerformingWhenLowCostCompletedExceptionally() {
-            MemoryBlobStore highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY, new MemoryDumbBlobStore());
+            BlobStore highPerformanceBlobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY);
             HybridBlobStore hybridBlobStore = HybridBlobStore.builder()
                 .lowCost(new FailingBlobStore())
                 .highPerformance(highPerformanceBlobStore)
@@ -341,13 +340,13 @@ class HybridBlobStoreTest implements BlobStoreContract {
 
         @Test
         void readShouldReturnFallbackToPerformingWhenLowCostGotException() {
-            MemoryBlobStore highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY, new MemoryDumbBlobStore());
+            BlobStore highPerformanceBlobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY);
             HybridBlobStore hybridBlobStore = HybridBlobStore.builder()
                 .lowCost(new ThrowingBlobStore())
                 .highPerformance(highPerformanceBlobStore)
                 .configuration(HybridBlobStore.Configuration.DEFAULT)
                 .build();
-            BlobId blobId = highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block();
+            BlobId blobId = Mono.from(highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST)).block();
 
             assertThat(hybridBlobStore.read(hybridBlobStore.getDefaultBucketName(), blobId))
                 .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
@@ -355,14 +354,14 @@ class HybridBlobStoreTest implements BlobStoreContract {
 
         @Test
         void readBytesShouldReturnFallbackToPerformingWhenLowCostGotException() {
-            MemoryBlobStore highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY, new MemoryDumbBlobStore());
+            BlobStore highPerformanceBlobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY);
 
             HybridBlobStore hybridBlobStore = HybridBlobStore.builder()
                 .lowCost(new ThrowingBlobStore())
                 .highPerformance(highPerformanceBlobStore)
                 .configuration(HybridBlobStore.Configuration.DEFAULT)
                 .build();
-            BlobId blobId = highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block();
+            BlobId blobId = Mono.from(highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST)).block();
 
             assertThat(hybridBlobStore.readBytes(hybridBlobStore.getDefaultBucketName(), blobId).block())
                 .isEqualTo(BLOB_CONTENT);
@@ -375,13 +374,13 @@ class HybridBlobStoreTest implements BlobStoreContract {
 
         @Test
         void readShouldReturnFallbackToPerformingWhenLowCostCompletedExceptionally() {
-            MemoryBlobStore highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY, new MemoryDumbBlobStore());
+            BlobStore highPerformanceBlobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY);
             HybridBlobStore hybridBlobStore = HybridBlobStore.builder()
                 .lowCost(new FailingBlobStore())
                 .highPerformance(highPerformanceBlobStore)
                 .configuration(HybridBlobStore.Configuration.DEFAULT)
                 .build();
-            BlobId blobId = highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block();
+            BlobId blobId = Mono.from(highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST)).block();
 
             assertThat(hybridBlobStore.read(hybridBlobStore.getDefaultBucketName(), blobId))
                 .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
@@ -389,13 +388,13 @@ class HybridBlobStoreTest implements BlobStoreContract {
 
         @Test
         void readBytesShouldReturnFallbackToPerformingWhenLowCostCompletedExceptionally() {
-            MemoryBlobStore highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY, new MemoryDumbBlobStore());
+            BlobStore highPerformanceBlobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY);
             HybridBlobStore hybridBlobStore = HybridBlobStore.builder()
                 .lowCost(new FailingBlobStore())
                 .highPerformance(highPerformanceBlobStore)
                 .configuration(HybridBlobStore.Configuration.DEFAULT)
                 .build();
-            BlobId blobId = highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block();
+            BlobId blobId = Mono.from(highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST)).block();
 
             assertThat(hybridBlobStore.readBytes(hybridBlobStore.getDefaultBucketName(), blobId).block())
                 .isEqualTo(BLOB_CONTENT);
@@ -404,7 +403,7 @@ class HybridBlobStoreTest implements BlobStoreContract {
 
     @Test
     void readShouldReturnFromLowCostWhenAvailable() {
-        BlobId blobId = lowCostBlobStore.save(lowCostBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block();
+        BlobId blobId = Mono.from(lowCostBlobStore.save(lowCostBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST)).block();
 
         assertThat(hybridBlobStore.read(hybridBlobStore.getDefaultBucketName(), blobId))
             .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
@@ -412,7 +411,7 @@ class HybridBlobStoreTest implements BlobStoreContract {
 
     @Test
     void readShouldReturnFromPerformingWhenLowCostNotAvailable() {
-        BlobId blobId = highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block();
+        BlobId blobId = Mono.from(highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST)).block();
 
         assertThat(hybridBlobStore.read(hybridBlobStore.getDefaultBucketName(), blobId))
             .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT));
@@ -420,7 +419,7 @@ class HybridBlobStoreTest implements BlobStoreContract {
 
     @Test
     void readBytesShouldReturnFromLowCostWhenAvailable() {
-        BlobId blobId = lowCostBlobStore.save(lowCostBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block();
+        BlobId blobId = Mono.from(lowCostBlobStore.save(lowCostBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST)).block();
 
         assertThat(hybridBlobStore.readBytes(lowCostBlobStore.getDefaultBucketName(), blobId).block())
             .isEqualTo(BLOB_CONTENT);
@@ -428,7 +427,7 @@ class HybridBlobStoreTest implements BlobStoreContract {
 
     @Test
     void readBytesShouldReturnFromPerformingWhenLowCostNotAvailable() {
-        BlobId blobId = highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block();
+        BlobId blobId = Mono.from(highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST)).block();
 
         assertThat(hybridBlobStore.readBytes(hybridBlobStore.getDefaultBucketName(), blobId).block())
             .isEqualTo(BLOB_CONTENT);
@@ -436,34 +435,34 @@ class HybridBlobStoreTest implements BlobStoreContract {
 
     @Test
     void deleteBucketShouldDeleteBothLowCostAndPerformingBuckets() {
-        BlobId blobId1 = highPerformanceBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST).block();
-        BlobId blobId2 = lowCostBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST).block();
+        BlobId blobId1 = Mono.from(highPerformanceBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST)).block();
+        BlobId blobId2 = Mono.from(lowCostBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST)).block();
 
         hybridBlobStore.deleteBucket(BucketName.DEFAULT).block();
 
-        assertThatThrownBy(() -> highPerformanceBlobStore.readBytes(BucketName.DEFAULT, blobId1).block())
+        assertThatThrownBy(() -> Mono.from(highPerformanceBlobStore.readBytes(BucketName.DEFAULT, blobId1)).block())
             .isInstanceOf(ObjectStoreException.class);
-        assertThatThrownBy(() -> lowCostBlobStore.readBytes(BucketName.DEFAULT, blobId2).block())
+        assertThatThrownBy(() -> Mono.from(lowCostBlobStore.readBytes(BucketName.DEFAULT, blobId2)).block())
             .isInstanceOf(ObjectStoreException.class);
     }
 
     @Test
     void deleteBucketShouldDeleteLowCostBucketEvenWhenPerformingDoesNotExist() {
-        BlobId blobId = lowCostBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST).block();
+        BlobId blobId = Mono.from(lowCostBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST)).block();
 
         hybridBlobStore.deleteBucket(BucketName.DEFAULT).block();
 
-        assertThatThrownBy(() -> lowCostBlobStore.readBytes(BucketName.DEFAULT, blobId).block())
+        assertThatThrownBy(() -> Mono.from(lowCostBlobStore.readBytes(BucketName.DEFAULT, blobId)).block())
             .isInstanceOf(ObjectStoreException.class);
     }
 
     @Test
     void deleteBucketShouldDeletePerformingBucketEvenWhenLowCostDoesNotExist() {
-        BlobId blobId = highPerformanceBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST).block();
+        BlobId blobId = Mono.from(highPerformanceBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST)).block();
 
         hybridBlobStore.deleteBucket(BucketName.DEFAULT).block();
 
-        assertThatThrownBy(() -> highPerformanceBlobStore.readBytes(BucketName.DEFAULT, blobId).block())
+        assertThatThrownBy(() -> Mono.from(highPerformanceBlobStore.readBytes(BucketName.DEFAULT, blobId)).block())
             .isInstanceOf(ObjectStoreException.class);
     }
 
@@ -475,8 +474,8 @@ class HybridBlobStoreTest implements BlobStoreContract {
 
     @Test
     void getDefaultBucketNameShouldThrowWhenBlobStoreDontShareTheSameDefaultBucketName() {
-        lowCostBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY, BucketName.of("lowCost"), new MemoryDumbBlobStore());
-        highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY, BucketName.of("highPerformance"), new MemoryDumbBlobStore());
+        lowCostBlobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY, BucketName.of("lowCost"));
+        highPerformanceBlobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY, BucketName.of("highPerformance"));
         hybridBlobStore = HybridBlobStore.builder()
             .lowCost(lowCostBlobStore)
             .highPerformance(highPerformanceBlobStore)
@@ -494,29 +493,29 @@ class HybridBlobStoreTest implements BlobStoreContract {
 
         hybridBlobStore.delete(BucketName.DEFAULT, blobId1).block();
 
-        assertThatThrownBy(() -> highPerformanceBlobStore.readBytes(BucketName.DEFAULT, blobId1).block())
+        assertThatThrownBy(() -> Mono.from(highPerformanceBlobStore.readBytes(BucketName.DEFAULT, blobId1)).block())
             .isInstanceOf(ObjectStoreException.class);
-        assertThatThrownBy(() -> lowCostBlobStore.readBytes(BucketName.DEFAULT, blobId2).block())
+        assertThatThrownBy(() -> Mono.from(lowCostBlobStore.readBytes(BucketName.DEFAULT, blobId2)).block())
             .isInstanceOf(ObjectStoreException.class);
     }
 
     @Test
     void deleteShouldDeleteLowCostBlobEvenWhenPerformingDoesNotExist() {
-        BlobId blobId = lowCostBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST).block();
+        BlobId blobId = Mono.from(lowCostBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST)).block();
 
         hybridBlobStore.delete(BucketName.DEFAULT, blobId).block();
 
-        assertThatThrownBy(() -> lowCostBlobStore.readBytes(BucketName.DEFAULT, blobId).block())
+        assertThatThrownBy(() -> Mono.from(lowCostBlobStore.readBytes(BucketName.DEFAULT, blobId)).block())
             .isInstanceOf(ObjectStoreException.class);
     }
 
     @Test
     void deleteShouldDeletePerformingBlobEvenWhenLowCostDoesNotExist() {
-        BlobId blobId = highPerformanceBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST).block();
+        BlobId blobId = Mono.from(highPerformanceBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST)).block();
 
         hybridBlobStore.delete(BucketName.DEFAULT, blobId).block();
 
-        assertThatThrownBy(() -> highPerformanceBlobStore.readBytes(BucketName.DEFAULT, blobId).block())
+        assertThatThrownBy(() -> Mono.from(highPerformanceBlobStore.readBytes(BucketName.DEFAULT, blobId)).block())
             .isInstanceOf(ObjectStoreException.class);
     }
 
diff --git a/server/blob/mail-store/src/test/java/org/apache/james/blob/mail/MimeMessageStoreTest.java b/server/blob/mail-store/src/test/java/org/apache/james/blob/mail/MimeMessageStoreTest.java
index a7c0649..e1dda9a 100644
--- a/server/blob/mail-store/src/test/java/org/apache/james/blob/mail/MimeMessageStoreTest.java
+++ b/server/blob/mail-store/src/test/java/org/apache/james/blob/mail/MimeMessageStoreTest.java
@@ -30,8 +30,7 @@ import org.apache.james.blob.api.BlobId;
 import org.apache.james.blob.api.BlobStore;
 import org.apache.james.blob.api.HashBlobId;
 import org.apache.james.blob.api.Store;
-import org.apache.james.blob.memory.MemoryBlobStore;
-import org.apache.james.blob.memory.MemoryDumbBlobStore;
+import org.apache.james.blob.memory.MemoryBlobStoreFactory;
 import org.apache.james.core.builder.MimeMessageBuilder;
 import org.apache.james.util.MimeMessageUtil;
 import org.assertj.core.api.SoftAssertions;
@@ -48,7 +47,7 @@ class MimeMessageStoreTest {
 
     @BeforeEach
     void setUp() {
-        blobStore = new MemoryBlobStore(BLOB_ID_FACTORY, new MemoryDumbBlobStore());
+        blobStore = MemoryBlobStoreFactory.create(BLOB_ID_FACTORY);
         testee = MimeMessageStore.factory(blobStore).mimeMessageStore();
     }
 
diff --git a/server/container/guice/blob-memory-guice/src/main/java/org/apache/james/modules/BlobMemoryModule.java b/server/container/guice/blob-memory-guice/src/main/java/org/apache/james/modules/BlobMemoryModule.java
index a9ab646..6e912bc 100644
--- a/server/container/guice/blob-memory-guice/src/main/java/org/apache/james/modules/BlobMemoryModule.java
+++ b/server/container/guice/blob-memory-guice/src/main/java/org/apache/james/modules/BlobMemoryModule.java
@@ -21,11 +21,15 @@ package org.apache.james.modules;
 
 import org.apache.james.blob.api.BlobId;
 import org.apache.james.blob.api.BlobStore;
+import org.apache.james.blob.api.BucketName;
+import org.apache.james.blob.api.DumbBlobStore;
 import org.apache.james.blob.api.HashBlobId;
-import org.apache.james.blob.memory.MemoryBlobStore;
+import org.apache.james.blob.memory.MemoryDumbBlobStore;
+import org.apache.james.server.blob.deduplication.DeDuplicationBlobStore;
 
 import com.google.inject.AbstractModule;
 import com.google.inject.Scopes;
+import com.google.inject.name.Names;
 
 public class BlobMemoryModule extends AbstractModule {
 
@@ -34,7 +38,14 @@ public class BlobMemoryModule extends AbstractModule {
         bind(HashBlobId.Factory.class).in(Scopes.SINGLETON);
         bind(BlobId.Factory.class).to(HashBlobId.Factory.class);
 
-        bind(MemoryBlobStore.class).in(Scopes.SINGLETON);
-        bind(BlobStore.class).to(MemoryBlobStore.class);
+        bind(DeDuplicationBlobStore.class).in(Scopes.SINGLETON);
+        bind(BlobStore.class).to(DeDuplicationBlobStore.class);
+
+        bind(MemoryDumbBlobStore.class).in(Scopes.SINGLETON);
+        bind(DumbBlobStore.class).to(MemoryDumbBlobStore.class);
+
+        bind(BucketName.class)
+            .annotatedWith(Names.named(DeDuplicationBlobStore.DEFAULT_BUCKET()))
+            .toInstance(BucketName.DEFAULT);
     }
 }
diff --git a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutesTest.java b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutesTest.java
index a5ff748..43004e0 100644
--- a/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-mailbox-deleted-message-vault/src/test/java/org/apache/james/webadmin/vault/routes/DeletedMessagesVaultRoutesTest.java
@@ -72,11 +72,11 @@ import java.util.Optional;
 import java.util.stream.Stream;
 
 import org.apache.james.blob.api.BlobId;
+import org.apache.james.blob.api.BlobStore;
 import org.apache.james.blob.api.BucketName;
 import org.apache.james.blob.api.HashBlobId;
 import org.apache.james.blob.export.api.BlobExportMechanism;
-import org.apache.james.blob.memory.MemoryBlobStore;
-import org.apache.james.blob.memory.MemoryDumbBlobStore;
+import org.apache.james.blob.memory.MemoryBlobStoreFactory;
 import org.apache.james.core.Domain;
 import org.apache.james.core.MailAddress;
 import org.apache.james.core.MaybeSender;
@@ -166,7 +166,7 @@ class DeletedMessagesVaultRoutesTest {
     private InMemoryMailboxManager mailboxManager;
     private MemoryTaskManager taskManager;
     private NoopBlobExporting blobExporting;
-    private MemoryBlobStore blobStore;
+    private BlobStore blobStore;
     private DeletedMessageZipper zipper;
     private MemoryUsersRepository usersRepository;
     private ExportService exportService;
@@ -176,7 +176,7 @@ class DeletedMessagesVaultRoutesTest {
     @BeforeEach
     void beforeEach() throws Exception {
         blobIdFactory = new HashBlobId.Factory();
-        blobStore = spy(new MemoryBlobStore(blobIdFactory, new MemoryDumbBlobStore()));
+        blobStore = spy(MemoryBlobStoreFactory.create(blobIdFactory));
         clock = new UpdatableTickingClock(OLD_DELETION_DATE.toInstant());
         vault = spy(new BlobStoreDeletedMessageVault(new RecordingMetricFactory(), new MemoryDeletedMessageMetadataVault(),
             blobStore, new BucketNameGenerator(clock), clock,
diff --git a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/ExportServiceTestSystem.java b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/ExportServiceTestSystem.java
index 77049c9..389ffad 100644
--- a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/ExportServiceTestSystem.java
+++ b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/service/ExportServiceTestSystem.java
@@ -29,8 +29,7 @@ import org.apache.james.blob.api.BlobStore;
 import org.apache.james.blob.api.HashBlobId;
 import org.apache.james.blob.export.api.BlobExportMechanism;
 import org.apache.james.blob.export.file.LocalFileBlobExportMechanism;
-import org.apache.james.blob.memory.MemoryBlobStore;
-import org.apache.james.blob.memory.MemoryDumbBlobStore;
+import org.apache.james.blob.memory.MemoryBlobStoreFactory;
 import org.apache.james.core.Domain;
 import org.apache.james.core.Username;
 import org.apache.james.dnsservice.api.DNSService;
@@ -77,7 +76,7 @@ public class ExportServiceTestSystem {
 
         bobSession = mailboxManager.createSystemSession(BOB);
 
-        blobStore = Mockito.spy(new MemoryBlobStore(FACTORY, new MemoryDumbBlobStore()));
+        blobStore = Mockito.spy(MemoryBlobStoreFactory.create(FACTORY));
         mailetContext = FakeMailContext.builder().postmaster(MailAddressFixture.POSTMASTER_AT_JAMES).build();
         blobExport = new LocalFileBlobExportMechanism(mailetContext, blobStore, fileSystem, dnsService,
             LocalFileBlobExportMechanism.Configuration.DEFAULT_CONFIGURATION);
diff --git a/third-party/linshare/src/test/java/org/apache/james/linshare/LinshareBlobExportMechanismTest.java b/third-party/linshare/src/test/java/org/apache/james/linshare/LinshareBlobExportMechanismTest.java
index 5f5d03f..738d4b0 100644
--- a/third-party/linshare/src/test/java/org/apache/james/linshare/LinshareBlobExportMechanismTest.java
+++ b/third-party/linshare/src/test/java/org/apache/james/linshare/LinshareBlobExportMechanismTest.java
@@ -29,11 +29,11 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import java.nio.charset.StandardCharsets;
 
 import org.apache.james.blob.api.BlobId;
+import org.apache.james.blob.api.BlobStore;
 import org.apache.james.blob.api.HashBlobId;
 import org.apache.james.blob.export.api.BlobExportMechanism;
 import org.apache.james.blob.export.api.FileExtension;
-import org.apache.james.blob.memory.MemoryBlobStore;
-import org.apache.james.blob.memory.MemoryDumbBlobStore;
+import org.apache.james.blob.memory.MemoryBlobStoreFactory;
 import org.apache.james.core.MailAddress;
 import org.apache.james.linshare.client.Document;
 import org.junit.jupiter.api.BeforeEach;
@@ -50,7 +50,7 @@ class LinshareBlobExportMechanismTest {
     @RegisterExtension
     static LinshareExtension linshareExtension = new LinshareExtension();
 
-    private MemoryBlobStore blobStore;
+    private BlobStore blobStore;
     private LinshareBlobExportMechanism testee;
     private HashBlobId.Factory blobIdFactory;
     private LinshareAPIForUserTesting user2API;
@@ -58,7 +58,7 @@ class LinshareBlobExportMechanismTest {
     @BeforeEach
     void setUp() throws Exception {
         blobIdFactory = new HashBlobId.Factory();
-        blobStore = new MemoryBlobStore(blobIdFactory, new MemoryDumbBlobStore());
+        blobStore = MemoryBlobStoreFactory.create(blobIdFactory);
 
         testee = new LinshareBlobExportMechanism(
             linshareExtension.getDelegationAccountAPI(),


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


[james-project] 13/18: JAMES-3313 Write a Deduplicating 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 ffab77e50c5a3491533e1a6c710b4de6c83da598
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Jul 17 16:21:31 2020 +0700

    JAMES-3313 Write a Deduplicating blobStore
---
 server/blob/blob-deduplicating/pom.xml             |   4 +
 .../deduplication/DeDuplicationBlobStore.scala     | 100 +++++++++++++++++++++
 2 files changed, 104 insertions(+)

diff --git a/server/blob/blob-deduplicating/pom.xml b/server/blob/blob-deduplicating/pom.xml
index 7f531e9..ebc0006 100644
--- a/server/blob/blob-deduplicating/pom.xml
+++ b/server/blob/blob-deduplicating/pom.xml
@@ -71,6 +71,10 @@
             <artifactId>play-json_${scala.base}</artifactId>
         </dependency>
         <dependency>
+            <groupId>io.projectreactor</groupId>
+            <artifactId>reactor-scala-extensions_${scala.base}</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.scala-lang</groupId>
             <artifactId>scala-library</artifactId>
         </dependency>
diff --git a/server/blob/blob-deduplicating/src/main/scala/org/apache/james/server/blob/deduplication/DeDuplicationBlobStore.scala b/server/blob/blob-deduplicating/src/main/scala/org/apache/james/server/blob/deduplication/DeDuplicationBlobStore.scala
new file mode 100644
index 0000000..5bd663f
--- /dev/null
+++ b/server/blob/blob-deduplicating/src/main/scala/org/apache/james/server/blob/deduplication/DeDuplicationBlobStore.scala
@@ -0,0 +1,100 @@
+/*****************************************************************
+ * 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.server.blob.deduplication
+
+import java.io.InputStream
+
+import com.google.common.base.Preconditions
+import com.google.common.hash.{Hashing, HashingInputStream}
+import com.google.common.io.{ByteSource, FileBackedOutputStream}
+import javax.inject.{Inject, Named}
+import org.apache.commons.io.IOUtils
+import org.apache.james.blob.api.{BlobId, BlobStore, BucketName, DumbBlobStore}
+import org.reactivestreams.Publisher
+import reactor.core.publisher.Mono
+import reactor.core.scala.publisher.SMono
+import reactor.util.function.{Tuple2, Tuples}
+
+object DeDuplicationBlobStore {
+  val DEFAULT_BUCKET = "defaultBucket"
+  val LAZY_RESOURCE_CLEANUP = false
+  val FILE_THRESHOLD = 10000
+}
+
+class DeDuplicationBlobStore @Inject()(dumbBlobStore: DumbBlobStore,
+                                       @Named("defaultBucket") defaultBucketName: BucketName,
+                                       blobIdFactory: BlobId.Factory) extends BlobStore {
+
+  override def save(bucketName: BucketName, data: Array[Byte], storagePolicy: BlobStore.StoragePolicy): Publisher[BlobId] = {
+    Preconditions.checkNotNull(bucketName)
+    Preconditions.checkNotNull(data)
+
+    val blobId = blobIdFactory.forPayload(data)
+
+    SMono(dumbBlobStore.save(bucketName, blobId, data))
+      .`then`(SMono.just(blobId))
+  }
+
+  override def save(bucketName: BucketName, data: InputStream, storagePolicy: BlobStore.StoragePolicy): Publisher[BlobId] = {
+    Preconditions.checkNotNull(bucketName)
+    Preconditions.checkNotNull(data)
+    val hashingInputStream = new HashingInputStream(Hashing.sha256, data)
+    val sourceSupplier: FileBackedOutputStream => SMono[BlobId] = (fileBackedOutputStream: FileBackedOutputStream) => saveAndGenerateBlobId(bucketName, hashingInputStream, fileBackedOutputStream)
+    Mono.using(() => new FileBackedOutputStream(DeDuplicationBlobStore.FILE_THRESHOLD),
+      sourceSupplier,
+      (fileBackedOutputStream: FileBackedOutputStream) => fileBackedOutputStream.reset(),
+      DeDuplicationBlobStore.LAZY_RESOURCE_CLEANUP)
+  }
+
+  private def saveAndGenerateBlobId(bucketName: BucketName, hashingInputStream: HashingInputStream, fileBackedOutputStream: FileBackedOutputStream): SMono[BlobId] =
+    SMono.fromCallable(() => {
+      IOUtils.copy(hashingInputStream, fileBackedOutputStream)
+      Tuples.of(blobIdFactory.from(hashingInputStream.hash.toString), fileBackedOutputStream.asByteSource)
+    })
+      .flatMap((tuple: Tuple2[BlobId, ByteSource]) =>
+        SMono(dumbBlobStore.save(bucketName, tuple.getT1, tuple.getT2))
+          .`then`(SMono.just(tuple.getT1)))
+
+
+  override def readBytes(bucketName: BucketName, blobId: BlobId): Publisher[Array[Byte]] = {
+    Preconditions.checkNotNull(bucketName)
+
+    dumbBlobStore.readBytes(bucketName, blobId)
+  }
+
+  override def read(bucketName: BucketName, blobId: BlobId): InputStream = {
+    Preconditions.checkNotNull(bucketName)
+
+    dumbBlobStore.read(bucketName, blobId)
+  }
+
+  override def getDefaultBucketName: BucketName = defaultBucketName
+
+  override def deleteBucket(bucketName: BucketName): Publisher[Void] = {
+    dumbBlobStore.deleteBucket(bucketName)
+  }
+
+  override def delete(bucketName: BucketName, blobId: BlobId): Publisher[Void] = {
+    Preconditions.checkNotNull(bucketName)
+    Preconditions.checkNotNull(blobId)
+
+    dumbBlobStore.delete(bucketName, blobId)
+  }
+}


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


[james-project] 10/18: JAMES-3177 use latest stable vavr library

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 cd8e7902f5bfcbdf25da56a4bb236109b6199c5e
Author: Matthieu Baechler <ma...@apache.org>
AuthorDate: Tue Jun 16 18:07:55 2020 +0200

    JAMES-3177 use latest stable vavr library
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 85d33d8..46add5d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2209,7 +2209,7 @@
             <dependency>
                 <groupId>io.vavr</groupId>
                 <artifactId>vavr</artifactId>
-                <version>0.9.0</version>
+                <version>0.10.3</version>
             </dependency>
             <dependency>
                 <groupId>javax.activation</groupId>


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


[james-project] 14/18: JAMES-3313 Move GC class to a separate class

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 82206dcc2510595a4ab2571e47b0585c74f5dce3
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Jul 17 16:29:04 2020 +0700

    JAMES-3313 Move GC class to a separate class
    
    This avoids a circular dependency
---
 server/blob/blob-deduplicating/pom.xml             | 44 +---------------------
 .../doc/gc-properties.adoc                         |  0
 .../blob/{blob-deduplicating => blob-gc}/pom.xml   |  6 +--
 .../james/server/blob/deduplication/GC.scala       |  0
 .../server/blob/deduplication/GCJsonReporter.scala |  0
 .../src/test/resources/gcReport.json               |  0
 .../blob/deduplication/GCJsonReporterTest.scala    |  2 -
 .../blob/deduplication/GCPropertiesTest.scala      |  0
 .../james/server/blob/deduplication/State.scala    |  0
 server/blob/pom.xml                                |  1 +
 10 files changed, 5 insertions(+), 48 deletions(-)

diff --git a/server/blob/blob-deduplicating/pom.xml b/server/blob/blob-deduplicating/pom.xml
index ebc0006..b4d2740 100644
--- a/server/blob/blob-deduplicating/pom.xml
+++ b/server/blob/blob-deduplicating/pom.xml
@@ -32,8 +32,7 @@
 
     <name>Apache James :: Server :: Blob :: Deduplicating Blob Storage</name>
     <description>
-        An implementation of BlobStore which deduplicates the stored blobs and uses a garbage collector
-        to ensure their effective deletion.
+        An implementation of BlobStore which deduplicates the stored blobs.
     </description>
 
     <dependencies>
@@ -43,34 +42,9 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
-            <artifactId>blob-api</artifactId>
-            <type>test-jar</type>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
-            <artifactId>blob-memory</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
-            <artifactId>james-server-testing</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
             <artifactId>james-server-util</artifactId>
         </dependency>
         <dependency>
-            <groupId>${james.groupId}</groupId>
-            <artifactId>testing-base</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>com.typesafe.play</groupId>
-            <artifactId>play-json_${scala.base}</artifactId>
-        </dependency>
-        <dependency>
             <groupId>io.projectreactor</groupId>
             <artifactId>reactor-scala-extensions_${scala.base}</artifactId>
         </dependency>
@@ -82,22 +56,6 @@
             <groupId>org.scala-lang.modules</groupId>
             <artifactId>scala-java8-compat_${scala.base}</artifactId>
         </dependency>
-        <dependency>
-            <groupId>org.scalacheck</groupId>
-            <artifactId>scalacheck_${scala.base}</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.scalactic</groupId>
-            <artifactId>scalactic_${scala.base}</artifactId>
-            <version>3.1.1</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.scalatest</groupId>
-            <artifactId>scalatest_${scala.base}</artifactId>
-            <scope>test</scope>
-        </dependency>
     </dependencies>
 
     <build>
diff --git a/server/blob/blob-deduplicating/doc/gc-properties.adoc b/server/blob/blob-gc/doc/gc-properties.adoc
similarity index 100%
rename from server/blob/blob-deduplicating/doc/gc-properties.adoc
rename to server/blob/blob-gc/doc/gc-properties.adoc
diff --git a/server/blob/blob-deduplicating/pom.xml b/server/blob/blob-gc/pom.xml
similarity index 95%
copy from server/blob/blob-deduplicating/pom.xml
copy to server/blob/blob-gc/pom.xml
index ebc0006..44e3d1b 100644
--- a/server/blob/blob-deduplicating/pom.xml
+++ b/server/blob/blob-gc/pom.xml
@@ -27,12 +27,12 @@
         <relativePath>../pom.xml</relativePath>
     </parent>
 
-    <artifactId>blob-deduplicating</artifactId>
+    <artifactId>blob-gc</artifactId>
     <packaging>jar</packaging>
 
-    <name>Apache James :: Server :: Blob :: Deduplicating Blob Storage</name>
+    <name>Apache James :: Server :: Blob :: Deduplicating Blob Storage Garbage collection</name>
     <description>
-        An implementation of BlobStore which deduplicates the stored blobs and uses a garbage collector
+        Garbage collector for the deduplicating blob store
         to ensure their effective deletion.
     </description>
 
diff --git a/server/blob/blob-deduplicating/src/main/scala/org/apache/james/server/blob/deduplication/GC.scala b/server/blob/blob-gc/src/main/scala/org/apache/james/server/blob/deduplication/GC.scala
similarity index 100%
rename from server/blob/blob-deduplicating/src/main/scala/org/apache/james/server/blob/deduplication/GC.scala
rename to server/blob/blob-gc/src/main/scala/org/apache/james/server/blob/deduplication/GC.scala
diff --git a/server/blob/blob-deduplicating/src/main/scala/org/apache/james/server/blob/deduplication/GCJsonReporter.scala b/server/blob/blob-gc/src/main/scala/org/apache/james/server/blob/deduplication/GCJsonReporter.scala
similarity index 100%
rename from server/blob/blob-deduplicating/src/main/scala/org/apache/james/server/blob/deduplication/GCJsonReporter.scala
rename to server/blob/blob-gc/src/main/scala/org/apache/james/server/blob/deduplication/GCJsonReporter.scala
diff --git a/server/blob/blob-deduplicating/src/test/resources/gcReport.json b/server/blob/blob-gc/src/test/resources/gcReport.json
similarity index 100%
rename from server/blob/blob-deduplicating/src/test/resources/gcReport.json
rename to server/blob/blob-gc/src/test/resources/gcReport.json
diff --git a/server/blob/blob-deduplicating/src/test/scala/org/apache/james/server/blob/deduplication/GCJsonReporterTest.scala b/server/blob/blob-gc/src/test/scala/org/apache/james/server/blob/deduplication/GCJsonReporterTest.scala
similarity index 99%
rename from server/blob/blob-deduplicating/src/test/scala/org/apache/james/server/blob/deduplication/GCJsonReporterTest.scala
rename to server/blob/blob-gc/src/test/scala/org/apache/james/server/blob/deduplication/GCJsonReporterTest.scala
index 6988e00..742a34f 100644
--- a/server/blob/blob-deduplicating/src/test/scala/org/apache/james/server/blob/deduplication/GCJsonReporterTest.scala
+++ b/server/blob/blob-gc/src/test/scala/org/apache/james/server/blob/deduplication/GCJsonReporterTest.scala
@@ -178,8 +178,6 @@ class GCJsonReporterTest extends AnyWordSpec with Matchers {
             lastIteration = gcReportGenNPlus2.iteration,
             targetedGeneration = generationPlusOne.next(2))
 
-          import JsonReport._
-
           val actualJson = Json.toJson(GCJsonReporter.report(ReferenceEvent(reference) :: GCIterationEvent(gcReportGenNPlus2) :: DereferenceEvent(dereference) :: GCIterationEvent(gcReportGenNPlus3) :: Nil))
 
           actualJson should equal(Json.parse(ClassLoaderUtils.getSystemResourceAsString("gcReport.json")))
diff --git a/server/blob/blob-deduplicating/src/test/scala/org/apache/james/server/blob/deduplication/GCPropertiesTest.scala b/server/blob/blob-gc/src/test/scala/org/apache/james/server/blob/deduplication/GCPropertiesTest.scala
similarity index 100%
rename from server/blob/blob-deduplicating/src/test/scala/org/apache/james/server/blob/deduplication/GCPropertiesTest.scala
rename to server/blob/blob-gc/src/test/scala/org/apache/james/server/blob/deduplication/GCPropertiesTest.scala
diff --git a/server/blob/blob-deduplicating/src/test/scala/org/apache/james/server/blob/deduplication/State.scala b/server/blob/blob-gc/src/test/scala/org/apache/james/server/blob/deduplication/State.scala
similarity index 100%
rename from server/blob/blob-deduplicating/src/test/scala/org/apache/james/server/blob/deduplication/State.scala
rename to server/blob/blob-gc/src/test/scala/org/apache/james/server/blob/deduplication/State.scala
diff --git a/server/blob/pom.xml b/server/blob/pom.xml
index 9744471..f62e027 100644
--- a/server/blob/pom.xml
+++ b/server/blob/pom.xml
@@ -39,6 +39,7 @@
         <module>blob-common</module>
         <module>blob-export-api</module>
         <module>blob-export-file</module>
+        <module>blob-gc</module>
         <module>blob-memory</module>
         <module>blob-objectstorage</module>
         <module>blob-union</module>


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


[james-project] 05/18: JAMES-3177 cover updateApplicableFlags with tests

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 cbd08bed6520e68225a50c7656a240dc7570060d
Author: Matthieu Baechler <ma...@apache.org>
AuthorDate: Tue Jun 16 16:34:18 2020 +0200

    JAMES-3177 cover updateApplicableFlags with tests
---
 .../processor/base/SelectedMailboxImplTest.java    | 137 +++++++++++++++++++++
 1 file changed, 137 insertions(+)

diff --git a/protocols/imap/src/test/java/org/apache/james/imap/processor/base/SelectedMailboxImplTest.java b/protocols/imap/src/test/java/org/apache/james/imap/processor/base/SelectedMailboxImplTest.java
index de8b3ef..f3bc59c 100644
--- a/protocols/imap/src/test/java/org/apache/james/imap/processor/base/SelectedMailboxImplTest.java
+++ b/protocols/imap/src/test/java/org/apache/james/imap/processor/base/SelectedMailboxImplTest.java
@@ -19,6 +19,10 @@
 
 package org.apache.james.imap.processor.base;
 
+import static javax.mail.Flags.Flag.ANSWERED;
+import static javax.mail.Flags.Flag.FLAGGED;
+import static javax.mail.Flags.Flag.RECENT;
+import static javax.mail.Flags.Flag.SEEN;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -31,12 +35,15 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
 import java.util.stream.Stream;
 
 import javax.mail.Flags;
 
 import org.apache.james.core.Username;
 import org.apache.james.imap.encode.FakeImapSession;
+import org.apache.james.imap.processor.base.SelectedMailboxImpl.ApplicableFlags;
+import org.apache.james.mailbox.FlagsBuilder;
 import org.apache.james.mailbox.MailboxManager;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.MailboxSessionUtil;
@@ -65,6 +72,8 @@ import org.mockito.stubbing.Answer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.collect.ImmutableList;
+
 import reactor.core.publisher.Mono;
 
 
@@ -75,6 +84,8 @@ class SelectedMailboxImplTest {
     private static final ModSeq MOD_SEQ = ModSeq.of(12);
     private static final int SIZE = 38;
     private static final String CUSTOM_FLAG = "custom";
+    private static final Username BOB = Username.of("bob");
+    private static final MailboxSession.SessionId SESSION_ID = MailboxSession.SessionId.of(2);
 
     private ExecutorService executorService;
     private MailboxManager mailboxManager;
@@ -230,4 +241,130 @@ class SelectedMailboxImplTest {
                 .build())
             .build();
     }
+
+    @Test
+    void updateApplicableFlagsShouldNotUpdateWhenEmptyFlagsUpdate() {
+        ApplicableFlags applicableFlags = ApplicableFlags.from(flagsBuilder().add(SEEN).build());
+        MailboxListener.FlagsUpdated flagsUpdated = flagsUpdated(updatedFlags().noOldFlag().noNewFlag());
+        ApplicableFlags actual = SelectedMailboxImpl.updateApplicableFlags(applicableFlags, flagsUpdated);
+        assertThat(actual).satisfies(ap -> {
+            assertThat(ap.updated()).isFalse();
+            assertThat(ap.flags()).isEqualTo(flagsBuilder().add(SEEN).build());
+        });
+    }
+
+    @Test
+    void updateApplicableFlagsShouldNotUpdateWhenNewFlag() {
+        ApplicableFlags applicableFlags = ApplicableFlags.from(flagsBuilder().add(SEEN).build());
+        MailboxListener.FlagsUpdated flagsUpdated =
+            flagsUpdated(updatedFlags().noOldFlag().newFlags(flags -> flags.add(ANSWERED)));
+        ApplicableFlags actual = SelectedMailboxImpl.updateApplicableFlags(applicableFlags, flagsUpdated);
+        assertThat(actual).satisfies(ap -> {
+            assertThat(ap.updated()).isFalse();
+            assertThat(ap.flags()).isEqualTo(flagsBuilder().add(SEEN).add(ANSWERED).build());
+        });
+    }
+
+    @Test
+    void updateApplicableFlagsShouldNotUpdateWhenSeveralUpdatedFlagsNewFlag() {
+        ApplicableFlags applicableFlags = ApplicableFlags.from(flagsBuilder().add(SEEN).build());
+        MailboxListener.FlagsUpdated flagsUpdated =
+            flagsUpdated(
+                updatedFlags().noOldFlag().newFlags(flags -> flags.add(ANSWERED)),
+                updatedFlags().noOldFlag().newFlags(flags -> flags.add(FLAGGED)));
+        ApplicableFlags actual = SelectedMailboxImpl.updateApplicableFlags(applicableFlags, flagsUpdated);
+        assertThat(actual).satisfies(ap -> {
+            assertThat(ap.updated()).isFalse();
+            assertThat(ap.flags()).isEqualTo(flagsBuilder().add(SEEN).add(ANSWERED).add(FLAGGED).build());
+        });
+    }
+
+    @Test
+    void updateApplicableFlagsShouldNotUpdateWhenOldFlagRemoved() {
+        ApplicableFlags applicableFlags = ApplicableFlags.from(flagsBuilder().add(SEEN).build());
+        MailboxListener.FlagsUpdated flagsUpdated =
+            flagsUpdated(updatedFlags().oldFlags(flags -> flags.add(SEEN)).noNewFlag());
+        ApplicableFlags actual = SelectedMailboxImpl.updateApplicableFlags(applicableFlags, flagsUpdated);
+        assertThat(actual).satisfies(ap -> {
+            assertThat(ap.updated()).isFalse();
+            assertThat(ap.flags()).isEqualTo(flagsBuilder().add(SEEN).build());
+        });
+    }
+
+    @Test
+    void updateApplicableFlagsShouldNotIncludeRecent() {
+        ApplicableFlags applicableFlags = ApplicableFlags.from(flagsBuilder().add(SEEN).build());
+        MailboxListener.FlagsUpdated flagsUpdated =
+            flagsUpdated(updatedFlags().noOldFlag().newFlags(flags -> flags.add(RECENT)));
+        ApplicableFlags actual = SelectedMailboxImpl.updateApplicableFlags(applicableFlags, flagsUpdated);
+        assertThat(actual).satisfies(ap -> {
+            assertThat(ap.updated()).isFalse();
+            assertThat(ap.flags()).isEqualTo(flagsBuilder().add(SEEN).build());
+        });
+    }
+
+    @Test
+    void updateApplicableFlagsShouldNotUpdateWhenNewUserFlag() {
+        ApplicableFlags applicableFlags = ApplicableFlags.from(flagsBuilder().add(SEEN).build());
+        MailboxListener.FlagsUpdated flagsUpdated =
+            flagsUpdated(updatedFlags().noOldFlag().newFlags(flags -> flags.add("Foo")));
+        ApplicableFlags actual = SelectedMailboxImpl.updateApplicableFlags(applicableFlags, flagsUpdated);
+        assertThat(actual).satisfies(ap -> {
+            assertThat(ap.updated()).isTrue();
+            assertThat(ap.flags()).isEqualTo(flagsBuilder().add(SEEN).add("Foo").build());
+        });
+    }
+
+    private static FlagsBuilder flagsBuilder() {
+        return FlagsBuilder.builder();
+    }
+
+    private MailboxListener.FlagsUpdated flagsUpdated(UpdatedFlags... updatedFlags) {
+        return new MailboxListener.FlagsUpdated(
+            SESSION_ID,
+            BOB,
+            mailboxPath,
+            mailboxId,
+            ImmutableList.copyOf(updatedFlags),
+            Event.EventId.random());
+    }
+
+    interface RequireOldFlags {
+        RequireNewFlags oldFlags(Flags flags);
+
+        default RequireNewFlags noOldFlag() {
+            return oldFlags(new Flags());
+        }
+
+        default RequireNewFlags oldFlags(Consumer<FlagsBuilder> builder) {
+            FlagsBuilder internalBuilder = FlagsBuilder.builder();
+            builder.accept(internalBuilder);
+            return oldFlags(internalBuilder.build());
+        }
+    }
+
+    interface RequireNewFlags {
+        UpdatedFlags newFlags(Flags flags);
+
+        default UpdatedFlags noNewFlag() {
+            return newFlags(new Flags());
+        }
+
+        default UpdatedFlags newFlags(Consumer<FlagsBuilder> builder) {
+            FlagsBuilder internalBuilder = FlagsBuilder.builder();
+            builder.accept(internalBuilder);
+            return newFlags(internalBuilder.build());
+        }
+    }
+
+    static RequireOldFlags updatedFlags() {
+        return oldFlags -> newFlags ->
+            UpdatedFlags
+                .builder()
+                .modSeq(MOD_SEQ)
+                .uid(EMITTED_EVENT_UID)
+                .oldFlags(oldFlags)
+                .newFlags(newFlags)
+                .build();
+    }
 }


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


[james-project] 06/18: JAMES-3177 refactor to encapsulate applicable flag logic

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 bf4ad97b144be5e8c53cd43d46e188e549b9b18b
Author: Matthieu Baechler <ma...@apache.org>
AuthorDate: Tue Jun 16 17:02:49 2020 +0200

    JAMES-3177 refactor to encapsulate applicable flag logic
---
 .../imap/processor/base/SelectedMailboxImpl.java   | 44 +++++++++++-----------
 1 file changed, 21 insertions(+), 23 deletions(-)

diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java
index 88f1ece..6600b84 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java
@@ -34,6 +34,7 @@ import javax.mail.Flags.Flag;
 
 import org.apache.james.imap.api.process.ImapSession;
 import org.apache.james.imap.api.process.SelectedMailbox;
+import org.apache.james.mailbox.FlagsBuilder;
 import org.apache.james.mailbox.MailboxManager;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.MessageManager;
@@ -89,8 +90,17 @@ public class SelectedMailboxImpl implements SelectedMailbox, MailboxListener {
             return updated;
         }
 
-        public ApplicableFlags update(boolean applicableFlagsChanged) {
-            return new ApplicableFlags(flags, applicableFlagsChanged);
+        public ApplicableFlags updateWithNewFlags(Flags newFlags) {
+            Flags updatedFlags = flags();
+            int size = updatedFlags.getUserFlags().length;
+
+            updatedFlags.add(newFlags);
+            // \RECENT is not a applicable flag in imap so remove it
+            // from the list
+            updatedFlags.remove(Flag.RECENT);
+
+            boolean applicableFlagsChanged = size < updatedFlags.getUserFlags().length;
+            return new ApplicableFlags(updatedFlags, applicableFlagsChanged);
         }
     }
 
@@ -415,28 +425,16 @@ public class SelectedMailboxImpl implements SelectedMailbox, MailboxListener {
     }
 
     @VisibleForTesting
-    static ApplicableFlags updateApplicableFlags(ApplicableFlags applicableFlags, FlagsUpdated messageEvent) {
-        Flags updatedFlags = applicableFlags.flags();
-        int size = updatedFlags.getUserFlags().length;
-        FlagsUpdated updatedF = messageEvent;
-        List<UpdatedFlags> flags = updatedF.getUpdatedFlags();
-
-        for (UpdatedFlags flag : flags) {
-            updatedFlags.add(flag.getNewFlags());
-
-        }
-
-        // \RECENT is not a applicable flag in imap so remove it
-        // from the list
-        updatedFlags.remove(Flag.RECENT);
+    static ApplicableFlags updateApplicableFlags(ApplicableFlags applicableFlags, FlagsUpdated flagsUpdated) {
+        Flags updatedFlags = mergeAllNewFlags(flagsUpdated);
+        return applicableFlags.updateWithNewFlags(updatedFlags);
+    }
 
-        boolean applicableFlagsChanged;
-        if (size < updatedFlags.getUserFlags().length) {
-            applicableFlagsChanged = true;
-        } else {
-            applicableFlagsChanged = false;
-        }
-        return ApplicableFlags.from(updatedFlags).update(applicableFlagsChanged);
+    private static Flags mergeAllNewFlags(FlagsUpdated flagsUpdated) {
+        List<UpdatedFlags> flags = flagsUpdated.getUpdatedFlags();
+        FlagsBuilder builder = FlagsBuilder.builder();
+        flags.stream().map(UpdatedFlags::getNewFlags).forEach(builder::add);
+        return builder.build();
     }
 
     @Override


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


[james-project] 07/18: JAMES-3177 make use of a persistent datastructure to avoid most locking in UidMsnConverter

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 db67bdfc5623a09b88c4c8a9763a35174a420e86
Author: Matthieu Baechler <ma...@apache.org>
AuthorDate: Tue Jun 16 17:42:30 2020 +0200

    JAMES-3177 make use of a persistent datastructure to avoid most locking in UidMsnConverter
    
    	Leveraging vavr here helps a lot but the caller is not ready yet to handle
    	an immutable UidMsnConverter.
    
    	Next step is to ensure locking when calling mutation method on UidMsnConverter
    	and removing locks
---
 protocols/imap/pom.xml                             |  4 ++
 .../james/imap/processor/base/UidMsnConverter.java | 83 ++++++++--------------
 .../imap/processor/base/UidMsnConverterTest.java   | 23 +++---
 3 files changed, 47 insertions(+), 63 deletions(-)

diff --git a/protocols/imap/pom.xml b/protocols/imap/pom.xml
index b6930eb..a8dc191 100644
--- a/protocols/imap/pom.xml
+++ b/protocols/imap/pom.xml
@@ -91,6 +91,10 @@
             <artifactId>javax.mail</artifactId>
         </dependency>
         <dependency>
+            <groupId>io.vavr</groupId>
+            <artifactId>vavr</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
         </dependency>
diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/base/UidMsnConverter.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/base/UidMsnConverter.java
index fc7b114..1850db5 100644
--- a/protocols/imap/src/main/java/org/apache/james/imap/processor/base/UidMsnConverter.java
+++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/base/UidMsnConverter.java
@@ -19,97 +19,70 @@
 
 package org.apache.james.imap.processor.base;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
 import java.util.Optional;
-import java.util.TreeSet;
+import java.util.function.Function;
 
 import org.apache.james.mailbox.MessageUid;
 import org.apache.james.mailbox.NullableMessageSequenceNumber;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.Lists;
 
-public class UidMsnConverter {
+import io.vavr.collection.TreeSet;
 
-    public static final int FIRST_MSN = 1;
+public class UidMsnConverter {
 
-    @VisibleForTesting final ArrayList<MessageUid> uids;
+    @VisibleForTesting TreeSet<MessageUid> uids;
 
     public UidMsnConverter() {
-        this.uids = Lists.newArrayList();
+        this.uids = TreeSet.empty();
     }
 
-    public synchronized void addAll(List<MessageUid> addedUids) {
-        TreeSet<MessageUid> tmp = new TreeSet<>();
-        tmp.addAll(uids);
-        tmp.addAll(addedUids);
-        uids.clear();
-        uids.addAll(tmp);
+    public synchronized void addAll(java.util.List<MessageUid> addedUids) {
+        uids = uids.addAll(addedUids);
     }
 
-    public synchronized NullableMessageSequenceNumber getMsn(MessageUid uid) {
-        int position = Collections.binarySearch(uids, uid);
-        if (position < 0) {
-            return NullableMessageSequenceNumber.noMessage();
-        }
-        return NullableMessageSequenceNumber.of(position + 1);
+    public NullableMessageSequenceNumber getMsn(MessageUid uid) {
+        return uids
+            .zipWithIndex()
+            .toMap(Function.identity())
+            .get(uid)
+            .map(position -> NullableMessageSequenceNumber.of(position + 1))
+            .getOrElse(NullableMessageSequenceNumber.noMessage());
     }
 
-    public synchronized Optional<MessageUid> getUid(int msn) {
-        if (msn <= uids.size() && msn > 0) {
-            return Optional.of(uids.get(msn - 1));
-        }
-        return Optional.empty();
+    public Optional<MessageUid> getUid(int msn) {
+        return uids
+            .drop(msn - 1)
+            .headOption()
+            .toJavaOptional();
     }
 
-    public synchronized Optional<MessageUid> getLastUid() {
-        if (uids.isEmpty()) {
-            return Optional.empty();
-        }
-        return getUid(getLastMsn());
+    public Optional<MessageUid> getLastUid() {
+        return uids.lastOption().toJavaOptional();
     }
 
-    public synchronized Optional<MessageUid> getFirstUid() {
-        return getUid(FIRST_MSN);
+    public Optional<MessageUid> getFirstUid() {
+        return uids.headOption().toJavaOptional();
     }
 
-    public synchronized int getNumMessage() {
+    public int getNumMessage() {
         return uids.size();
     }
 
     public synchronized void remove(MessageUid uid) {
-        uids.remove(uid);
+        uids = uids.remove(uid);
     }
 
-    public synchronized boolean isEmpty() {
+    public boolean isEmpty() {
         return uids.isEmpty();
     }
 
     public synchronized void clear() {
-        uids.clear();
+        uids = TreeSet.empty();
     }
 
     public synchronized void addUid(MessageUid uid) {
-        if (uids.contains(uid)) {
-            return;
-        }
-        if (isLastUid(uid)) {
-            uids.add(uid);
-        } else {
-            uids.add(uid);
-            Collections.sort(uids);
-        }
+        uids = uids.add(uid);
     }
 
-    private boolean isLastUid(MessageUid uid) {
-        Optional<MessageUid> lastUid = getLastUid();
-        return !lastUid.isPresent() ||
-            lastUid.get().compareTo(uid) < 0;
-    }
-
-    private int getLastMsn() {
-        return getNumMessage();
-    }
 }
diff --git a/protocols/imap/src/test/java/org/apache/james/imap/processor/base/UidMsnConverterTest.java b/protocols/imap/src/test/java/org/apache/james/imap/processor/base/UidMsnConverterTest.java
index 438db40..78b8b3f 100644
--- a/protocols/imap/src/test/java/org/apache/james/imap/processor/base/UidMsnConverterTest.java
+++ b/protocols/imap/src/test/java/org/apache/james/imap/processor/base/UidMsnConverterTest.java
@@ -33,6 +33,8 @@ import org.junit.Test;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
+import io.vavr.Tuple;
+
 public class UidMsnConverterTest {
     private UidMsnConverter testee;
     private MessageUid messageUid1;
@@ -62,11 +64,20 @@ public class UidMsnConverterTest {
     }
 
     @Test
-    public void getUidShouldTheCorrespondingUidIfItExist() {
+    public void getUidShouldReturnEmptyIfOutOfRange() {
         testee.addUid(messageUid1);
+        testee.addUid(messageUid2);
 
-        assertThat(testee.getUid(1))
-            .contains(messageUid1);
+        assertThat(testee.getUid(50))
+            .isEmpty();
+    }
+
+    @Test
+    public void getUidShouldReturnTheCorrespondingUidIfItExist() {
+        testee.addAll(ImmutableList.of(messageUid1, messageUid2));
+
+        assertThat(testee.getUid(2))
+            .contains(messageUid2);
     }
 
     @Test
@@ -447,11 +458,7 @@ public class UidMsnConverterTest {
     }
 
     private Map<Integer, MessageUid> mapTesteeInternalDataToMsnByUid() {
-        ImmutableMap.Builder<Integer, MessageUid> result = ImmutableMap.builder();
-        for (int i = 0; i < testee.uids.size(); i++) {
-            result.put(i + 1, testee.uids.get(i));
-        }
-        return result.build();
+        return testee.uids.zipWithIndex().toMap(input -> Tuple.of(input._2 + 1, input._1)).toJavaMap();
     }
 
 }
\ No newline at end of file


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