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/04/29 01:14:34 UTC

[james-project] branch master updated (77e4f75 -> b4de77a)

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 77e4f75  JAMES-3145 document metrics logger
     new d563e4a  [Refactoring] Move MailboxACLJsonConverter from mailbox/store to mailbox/cassandra
     new 10e1a44  JAMES-3143 Add select all query for MessageId/ImapUid
     new e31d789  JAMES-3143 SolveMessageInconsistenciesService/Test
     new 01cb8d1  JAMES-3143 Add context & objects describing inconsistencies
     new 84374e5  JAMES-3143 SolveMessageInconsistenciesTask/DTO
     new 4db5a1f  JAMES-3143 Guice binding for task
     new 6ceec97  JAMES-3143 Exposes task in MessageRoutes
     new c638eff  JAMES-3143 Add Webadmin integration test
     new a7d33f7  JAMES-3143 Update webadmin documentation
     new e0ce72b  JAMES-3143 Update CHANGELOG
     new ab4e4e3  JAMES-3138 Task for recomputing current quotas
     new 1b0bc6e  JAMES-3138 Webadmin endpoint and guice bindings for recomputing current quotas
     new 5178d1a  JAMES-3138 Changelog entry
     new 251a73d  JAMES-3138 Documentation for Recomputing current quotas endpoint
     new 7f5a694  JAMES-3138 Task for recomputing current quotas
     new 6e59725  JAMES-3138 Integration tests for consistency tasks
     new 1157a7b  JAMES-3138 Use concatMap upon quota computation
     new 00160a4  JAMES-3138 CassandraCurrentQuotaManager: Improve Reactor code style
     new e73dfb8  JAMES-3138 InMemoryCurrentQuotaManager: Improve reactor code style
     new fc528ae  JAMES-3138 Cassandra test instumentation: inject failure after awaitOn
     new 9266b93  JAMES-3138 VarArg for Cassandra instrumentation scenari registration
     new d018a04  JAMES-3138 Add fix nothing integration tests
     new 12a5987  JAMES-3065 remove remaining usages of Reactor types in public api
     new e781322  JAMES-3065 s/HealthCheck::checkReactive/HealthCheck::check/
     new f5b2483  JAMES-3065 s/HealthCheck::checkReactive/HealthCheck::check/
     new 7cb8ba9  JAMES-3153 make use of RetryWhen for backoff on reactor
     new b4de77a  JAMES-3153 use reactor.Retry insteaf of reactor-extra version

The 27 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                                       |   4 +-
 .../cassandra/init/ResilientClusterProvider.java   |   4 +-
 .../cassandra/utils/CassandraHealthCheck.java      |   2 +-
 .../apache/james/backends/cassandra/Scenario.java  |  23 +-
 .../james/backends/cassandra/TestingSession.java   |   6 +-
 .../backends/cassandra/TestingSessionTest.java     |  31 +-
 .../cassandra/utils/CassandraHealthCheckTest.java  |   8 +-
 .../apache/james/backends/es/ClientProvider.java   |   4 +-
 .../backends/es/ElasticSearchHealthCheck.java      |   2 +-
 .../es/ElasticSearchHealthCheckConnectionTest.java |   4 +-
 .../rabbitmq/RabbitMQConnectionFactory.java        |   4 +-
 .../backends/rabbitmq/RabbitMQHealthCheck.java     |   2 +-
 .../rabbitmq/ReactorRabbitMQChannelPool.java       |   6 +-
 .../backends/rabbitmq/SimpleConnectionPool.java    |   4 +-
 .../backends/rabbitmq/RabbitMQHealthCheckTest.java |  28 +-
 core/pom.xml                                       |   4 +
 .../apache/james/core/healthcheck/HealthCheck.java |  10 +-
 mailbox/api/pom.xml                                |   4 +
 .../events/EventDeadLettersHealthCheck.java        |   2 +-
 .../mailbox/events/RetryBackoffConfiguration.java  |   1 -
 .../org/apache/james/mailbox/model/QuotaRoot.java  |   5 +
 .../EventDeadLettersHealthCheckContract.java       |  30 +-
 .../apache/james/mailbox/model/QuotaRootTest.java  |  24 +
 .../cassandra}/json/MailboxACLJsonConverter.java   |   2 +-
 .../mailbox/cassandra/mail/CassandraACLMapper.java |   2 +-
 .../cassandra/mail/CassandraMailboxMapper.java     |   7 +-
 .../cassandra/mail/CassandraMessageIdDAO.java      |  12 +
 .../cassandra/mail/CassandraMessageIdMapper.java   |   3 +-
 .../mail/CassandraMessageIdToImapUidDAO.java       |  11 +
 .../cassandra/mail/CassandraMessageMapper.java     |   3 +-
 .../cassandra/mail/CassandraModSeqProvider.java    |   4 +-
 .../cassandra/mail/CassandraUidProvider.java       |   4 +-
 .../mail/task/MessageInconsistenciesEntry.java     |  92 ++++
 .../task/SolveMessageInconsistenciesService.java   | 443 ++++++++++++++++
 .../mail/task/SolveMessageInconsistenciesTask.java | 143 ++++++
 ...nconsistenciesTaskAdditionalInformationDTO.java | 135 +++++
 ...ava => SolveMessageInconsistenciesTaskDTO.java} |  26 +-
 .../quota/CassandraCurrentQuotaManager.java        |  12 +-
 .../json/MailboxACLJsonConverterTest.java          |   2 +-
 .../cassandra/mail/CassandraACLMapperTest.java     |   6 +-
 .../cassandra/mail/CassandraMailboxDAOTest.java    |   2 +-
 .../SolveMessageInconsistenciesServiceTest.java    | 561 +++++++++++++++++++++
 ...essageInconsistenciesTaskSerializationTest.java |  78 +++
 ...ssageInconsistencies.additionalInformation.json |  28 +
 .../json/solveMessageInconsistencies.task.json     |   3 +
 .../mailbox/events/delivery/EventDelivery.java     |   6 +-
 .../james/mailbox/events/GroupRegistration.java    |   4 +-
 .../mailbox/events/KeyRegistrationHandler.java     |  10 +-
 .../quota/InMemoryCurrentQuotaManager.java         |  12 +-
 mailbox/tools/quota-recompute/pom.xml              |  20 +
 .../quota/task/RecomputeCurrentQuotasService.java  |   2 +-
 .../quota/task/RecomputeCurrentQuotasTask.java}    |  59 ++-
 ...eCurrentQuotasTaskAdditionalInformationDTO.java |  79 ++-
 .../quota/task/RecomputeCurrentQuotasTaskDTO.java} |  25 +-
 ...computeCurrentQuotasTaskSerializationTest.java} |  37 +-
 metrics/metrics-tests/pom.xml                      |   4 +
 pom.xml                                            |   2 +-
 server/blob/blob-objectstorage/pom.xml             |   4 -
 .../objectstorage/StreamCompatibleBlobPutter.java  |  19 +-
 .../blob/objectstorage/aws/AwsS3ObjectStorage.java |  11 +-
 .../org/apache/james/CassandraJamesServerMain.java |   2 +
 .../webadmin/InconsistencySolvingRoutesModule.java |  16 +
 .../guice/cassandra-rabbitmq-guice/pom.xml         |   4 +
 .../james/modules/TaskSerializationModule.java     |  26 +
 .../apache/james/GuiceLifecycleHealthCheck.java    |   2 +-
 .../org/apache/james/PeriodicalHealthChecks.java   |   2 +-
 .../apache/james/PeriodicalHealthChecksTest.java   |  16 +-
 .../java/org/apache/james/JPAJamesServerMain.java  |   2 +
 .../org/apache/james/modules/MailboxProbeImpl.java |   6 +
 ...=> InconsistencyQuotasSolvingRoutesModule.java} |  18 +-
 .../james/modules/server/MailboxRoutesModule.java  |   2 +
 .../james/modules/server/MessagesRoutesModule.java |   5 +
 server/data/data-jmap/pom.xml                      |   4 +
 .../MessageFastViewProjectionHealthCheck.java      |  20 +-
 .../MessageFastViewProjectionHealthCheckTest.java  |  26 +-
 .../james/jpa/healthcheck/JPAHealthCheck.java      |  24 +-
 .../james/jpa/healthcheck/JPAHealthCheckTest.java  |   4 +-
 .../transport/mailets/delivery/MailDispatcher.java |   3 +-
 .../rabbitmq/ConsistencyTasksIntegrationTest.java  | 446 ++++++++++++++++
 .../RabbitMQWebAdminServerIntegrationTest.java     |  33 ++
 ...dminServerTaskSerializationIntegrationTest.java |  22 +-
 ... SolveMessageInconsistenciesRequestToTask.java} |  13 +-
 .../james/webadmin/routes/HealthCheckRoutes.java   |  17 +-
 .../org/apache/james/webadmin/WebAdminUtils.java   |   3 +-
 .../webadmin/routes/HealthCheckRoutesTest.java     |   6 +-
 server/protocols/webadmin/webadmin-mailbox/pom.xml |   4 +
 .../james/webadmin/routes/MessagesRoutes.java      |  38 +-
 .../RecomputeCurrentQuotasRequestToTask.java}      |  12 +-
 .../james/webadmin/routes/UserQuotaRoutes.java     |  59 ++-
 .../james/webadmin/routes/MessageRoutesTest.java   |   4 +-
 .../routes/WebAdminQuotaSearchTestSystem.java      |  11 +-
 server/queue/queue-activemq/pom.xml                |   2 +-
 .../distributed/RabbitMQWorkQueue.java             |   8 +-
 src/site/markdown/server/manage-webadmin.md        | 105 +++-
 94 files changed, 2710 insertions(+), 340 deletions(-)
 rename mailbox/{store/src/main/java/org/apache/james/mailbox/store => cassandra/src/main/java/org/apache/james/mailbox/cassandra}/json/MailboxACLJsonConverter.java (98%)
 create mode 100644 mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/MessageInconsistenciesEntry.java
 create mode 100644 mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesService.java
 create mode 100644 mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesTask.java
 create mode 100644 mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesTaskAdditionalInformationDTO.java
 copy mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/{SolveMailboxInconsistenciesTaskDTO.java => SolveMessageInconsistenciesTaskDTO.java} (64%)
 rename mailbox/{store/src/test/java/org/apache/james/mailbox/store => cassandra/src/test/java/org/apache/james/mailbox/cassandra}/json/MailboxACLJsonConverterTest.java (99%)
 create mode 100644 mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesServiceTest.java
 create mode 100644 mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesTaskSerializationTest.java
 create mode 100644 mailbox/cassandra/src/test/resources/json/solveMessageInconsistencies.additionalInformation.json
 create mode 100644 mailbox/cassandra/src/test/resources/json/solveMessageInconsistencies.task.json
 copy mailbox/{cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersTask.java => tools/quota-recompute/src/main/java/org/apache/james/mailbox/quota/task/RecomputeCurrentQuotasTask.java} (58%)
 copy server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RecomputeUserFastViewTaskAdditionalInformationDTO.java => mailbox/tools/quota-recompute/src/main/java/org/apache/james/mailbox/quota/task/RecomputeCurrentQuotasTaskAdditionalInformationDTO.java (51%)
 copy mailbox/{cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersTaskDTO.java => tools/quota-recompute/src/main/java/org/apache/james/mailbox/quota/task/RecomputeCurrentQuotasTaskDTO.java} (63%)
 copy mailbox/{cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersTaskSerializationTest.java => tools/quota-recompute/src/test/java/org/apache/james/mailbox/quota/task/RecomputeCurrentQuotasTaskSerializationTest.java} (55%)
 copy server/container/guice/protocols/webadmin-mailbox/src/main/java/org/apache/james/modules/server/{MailboxesExportRoutesModule.java => InconsistencyQuotasSolvingRoutesModule.java} (73%)
 create mode 100644 server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/ConsistencyTasksIntegrationTest.java
 copy server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/routes/{RecomputeMailboxCountersRequestToTask.java => SolveMessageInconsistenciesRequestToTask.java} (74%)
 copy server/protocols/webadmin/{webadmin-cassandra/src/main/java/org/apache/james/webadmin/routes/RecomputeMailboxCountersRequestToTask.java => webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/RecomputeCurrentQuotasRequestToTask.java} (75%)


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


[james-project] 08/27: JAMES-3143 Add Webadmin integration test

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 c638effd63116e7cda98a7127469ae81f63412b6
Author: LanKhuat <kh...@gmail.com>
AuthorDate: Wed Apr 22 11:05:10 2020 +0700

    JAMES-3143 Add Webadmin integration test
---
 .../RabbitMQWebAdminServerIntegrationTest.java     | 33 ++++++++++++++++++++++
 1 file changed, 33 insertions(+)

diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerIntegrationTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerIntegrationTest.java
index 0f7b1e8..c107aa6 100644
--- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerIntegrationTest.java
+++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerIntegrationTest.java
@@ -199,6 +199,39 @@ class RabbitMQWebAdminServerIntegrationTest extends WebAdminServerIntegrationTes
     }
 
     @Test
+    void solveMessageInconsistenciesTasksShouldBeExposed() {
+        String taskId = with().post(UPGRADE_TO_LATEST_VERSION)
+            .jsonPath()
+            .get("taskId");
+
+        with()
+            .get("/tasks/" + taskId + "/await")
+        .then()
+            .body("status", is("completed"));
+
+        taskId = with()
+            .queryParam("task", "SolveInconsistencies")
+            .post("/messages")
+            .jsonPath()
+            .get("taskId");
+
+        given()
+            .basePath(TasksRoutes.BASE)
+        .when()
+            .get(taskId + "/await")
+        .then()
+            .body("status", is("completed"))
+            .body("type", is("solve-message-inconsistencies"))
+            .body("additionalInformation.processedImapUidEntries", is(0))
+            .body("additionalInformation.processedMessageIdEntries", is(0))
+            .body("additionalInformation.addedMessageIdEntries", is(0))
+            .body("additionalInformation.updatedMessageIdEntries", is(0))
+            .body("additionalInformation.removedMessageIdEntries", is(0))
+            .body("additionalInformation.fixedInconsistencies", hasSize(0))
+            .body("additionalInformation.errors", hasSize(0));
+    }
+
+    @Test
     void getSwaggerShouldContainDistributedEndpoints() {
         when()
             .get(SwaggerRoutes.SWAGGER_ENDPOINT)


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


[james-project] 10/27: JAMES-3143 Update CHANGELOG

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 e0ce72b109357de06b31637de0bf22d341f37387
Author: LanKhuat <kh...@gmail.com>
AuthorDate: Thu Apr 23 16:26:28 2020 +0700

    JAMES-3143 Update CHANGELOG
---
 CHANGELOG.md | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5f70328..6d1986f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,8 +15,9 @@ of tasks being currently executed.
 - JAMES-3062 EventDeadLettersHealthCheck
 - JAMES-3058 WebAdmin offline task to correct mailbox inconsistencies on top of Cassandra products
 - JAMES-3105 WebAdmin offline task to recompute mailbox counters on top of Cassandra products
-- JAMES-3072 Webadmin endpoint to export mailbox backup
+- JAMES-3072 WebAdmin endpoint to export mailbox backup
 - JAMES-3117 Add PeriodicalHealthChecks for periodical calling all health checks
+- JAMES-3143 WebAdmin endpoint to solve Cassandra message inconsistencies
 
 ### Changed
 - Switch to Java 11 for build and run


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


[james-project] 26/27: JAMES-3153 make use of RetryWhen for backoff on reactor

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 7cb8ba9e88c76718f62ec32b2ded8c1d35420569
Author: Matthieu Baechler <ma...@apache.org>
AuthorDate: Thu Apr 23 12:34:56 2020 +0200

    JAMES-3153 make use of RetryWhen for backoff on reactor
---
 .../backends/cassandra/init/ResilientClusterProvider.java      |  4 ++--
 .../main/java/org/apache/james/backends/es/ClientProvider.java |  4 ++--
 .../james/backends/rabbitmq/RabbitMQConnectionFactory.java     |  4 ++--
 .../james/backends/rabbitmq/ReactorRabbitMQChannelPool.java    |  6 +++---
 .../apache/james/backends/rabbitmq/SimpleConnectionPool.java   |  4 ++--
 .../apache/james/mailbox/events/RetryBackoffConfiguration.java |  1 -
 .../james/mailbox/cassandra/mail/CassandraMailboxMapper.java   |  7 ++++---
 .../james/mailbox/cassandra/mail/CassandraMessageIdMapper.java |  3 ++-
 .../james/mailbox/cassandra/mail/CassandraMessageMapper.java   |  3 ++-
 .../james/mailbox/cassandra/mail/CassandraModSeqProvider.java  |  4 ++--
 .../james/mailbox/cassandra/mail/CassandraUidProvider.java     |  4 ++--
 .../apache/james/mailbox/events/delivery/EventDelivery.java    |  6 ++----
 .../org/apache/james/mailbox/events/GroupRegistration.java     |  4 ++--
 .../apache/james/mailbox/events/KeyRegistrationHandler.java    | 10 +++++-----
 pom.xml                                                        |  2 +-
 .../james/transport/mailets/delivery/MailDispatcher.java       |  3 ++-
 .../src/test/java/org/apache/james/webadmin/WebAdminUtils.java |  3 ++-
 server/queue/queue-activemq/pom.xml                            |  2 +-
 .../task/eventsourcing/distributed/RabbitMQWorkQueue.java      |  8 ++++----
 19 files changed, 42 insertions(+), 40 deletions(-)

diff --git a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/ResilientClusterProvider.java b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/ResilientClusterProvider.java
index f91e68d..4eeb502 100644
--- a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/ResilientClusterProvider.java
+++ b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/ResilientClusterProvider.java
@@ -40,6 +40,7 @@ import com.google.common.collect.ImmutableList;
 
 import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Schedulers;
+import reactor.util.retry.Retry;
 
 @Singleton
 public class ResilientClusterProvider implements Provider<Cluster> {
@@ -51,10 +52,9 @@ public class ResilientClusterProvider implements Provider<Cluster> {
     @Inject
     ResilientClusterProvider(ClusterConfiguration configuration) {
         Duration waitDelay = Duration.ofMillis(configuration.getMinDelay());
-        Duration forever = Duration.ofMillis(Long.MAX_VALUE);
         cluster = Mono.fromCallable(getClusterRetryCallable(configuration))
             .doOnError(e -> LOGGER.warn("Error establishing Cassandra connection. Next retry scheduled in {} ms", waitDelay, e))
-            .retryBackoff(configuration.getMaxRetry(), waitDelay, forever, Schedulers.elastic())
+            .retryWhen(Retry.backoff(configuration.getMaxRetry(), waitDelay).scheduler(Schedulers.elastic()))
             .publishOn(Schedulers.elastic())
             .block();
     }
diff --git a/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ClientProvider.java b/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ClientProvider.java
index 247ed50..b73fbd3 100644
--- a/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ClientProvider.java
+++ b/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ClientProvider.java
@@ -56,6 +56,7 @@ import com.google.common.annotations.VisibleForTesting;
 
 import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Schedulers;
+import reactor.util.retry.Retry;
 
 public class ClientProvider implements Provider<ReactorElasticSearchClient> {
 
@@ -180,13 +181,12 @@ public class ClientProvider implements Provider<ReactorElasticSearchClient> {
 
     private RestHighLevelClient connect(ElasticSearchConfiguration configuration) {
         Duration waitDelay = Duration.ofMillis(configuration.getMinDelay());
-        Duration forever = Duration.ofMillis(Long.MAX_VALUE);
         boolean suppressLeadingZeroElements = true;
         boolean suppressTrailingZeroElements = true;
         return Mono.fromCallable(() -> connectToCluster(configuration))
             .doOnError(e -> LOGGER.warn("Error establishing ElasticSearch connection. Next retry scheduled in {}",
                 DurationFormatUtils.formatDurationWords(waitDelay.toMillis(), suppressLeadingZeroElements, suppressTrailingZeroElements), e))
-            .retryBackoff(configuration.getMaxRetries(), waitDelay, forever, Schedulers.elastic())
+            .retryWhen(Retry.backoff(configuration.getMaxRetries(), waitDelay).scheduler(Schedulers.elastic()))
             .publishOn(Schedulers.elastic())
             .block();
     }
diff --git a/backends-common/rabbitmq/src/main/java/org/apache/james/backends/rabbitmq/RabbitMQConnectionFactory.java b/backends-common/rabbitmq/src/main/java/org/apache/james/backends/rabbitmq/RabbitMQConnectionFactory.java
index a3086c7..36989f9 100644
--- a/backends-common/rabbitmq/src/main/java/org/apache/james/backends/rabbitmq/RabbitMQConnectionFactory.java
+++ b/backends-common/rabbitmq/src/main/java/org/apache/james/backends/rabbitmq/RabbitMQConnectionFactory.java
@@ -27,6 +27,7 @@ import com.rabbitmq.client.ConnectionFactory;
 
 import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Schedulers;
+import reactor.util.retry.Retry;
 
 public class RabbitMQConnectionFactory {
 
@@ -60,8 +61,7 @@ public class RabbitMQConnectionFactory {
     }
 
     Mono<Connection> connectionMono() {
-        Duration forever = Duration.ofMillis(Long.MAX_VALUE);
         return Mono.fromCallable(connectionFactory::newConnection)
-            .retryBackoff(configuration.getMaxRetries(), Duration.ofMillis(configuration.getMinDelayInMs()), forever, Schedulers.elastic());
+            .retryWhen(Retry.backoff(configuration.getMaxRetries(), Duration.ofMillis(configuration.getMinDelayInMs())).scheduler(Schedulers.elastic()));
     }
 }
diff --git a/backends-common/rabbitmq/src/main/java/org/apache/james/backends/rabbitmq/ReactorRabbitMQChannelPool.java b/backends-common/rabbitmq/src/main/java/org/apache/james/backends/rabbitmq/ReactorRabbitMQChannelPool.java
index 0c89212..f4703b4 100644
--- a/backends-common/rabbitmq/src/main/java/org/apache/james/backends/rabbitmq/ReactorRabbitMQChannelPool.java
+++ b/backends-common/rabbitmq/src/main/java/org/apache/james/backends/rabbitmq/ReactorRabbitMQChannelPool.java
@@ -49,6 +49,7 @@ import reactor.rabbitmq.Receiver;
 import reactor.rabbitmq.ReceiverOptions;
 import reactor.rabbitmq.Sender;
 import reactor.rabbitmq.SenderOptions;
+import reactor.util.retry.Retry;
 
 public class ReactorRabbitMQChannelPool implements ChannelPool, Startable {
 
@@ -82,7 +83,7 @@ public class ReactorRabbitMQChannelPool implements ChannelPool, Startable {
             return Mono.fromCallable(connection::openChannel)
                 .map(maybeChannel ->
                     maybeChannel.orElseThrow(() -> new RuntimeException("RabbitMQ reached to maximum opened channels, cannot get more channels")))
-                .retryBackoff(MAX_RETRIES, RETRY_FIRST_BACK_OFF, FOREVER, Schedulers.elastic())
+                .retryWhen(Retry.backoff(MAX_RETRIES, RETRY_FIRST_BACK_OFF).scheduler(Schedulers.elastic()))
                 .doOnError(throwable -> LOGGER.error("error when creating new channel", throwable));
         }
 
@@ -105,7 +106,6 @@ public class ReactorRabbitMQChannelPool implements ChannelPool, Startable {
     private static final int MAX_CHANNELS_NUMBER = 3;
     private static final int MAX_BORROW_RETRIES = 3;
     private static final Duration MIN_BORROW_DELAY = Duration.ofMillis(50);
-    private static final Duration FOREVER = Duration.ofMillis(Long.MAX_VALUE);
 
     private final Mono<Connection> connectionMono;
     private final GenericObjectPool<Channel> pool;
@@ -146,7 +146,7 @@ public class ReactorRabbitMQChannelPool implements ChannelPool, Startable {
     private Mono<Channel> borrow() {
         return tryBorrowFromPool()
             .doOnError(throwable -> LOGGER.warn("Cannot borrow channel", throwable))
-            .retryBackoff(MAX_BORROW_RETRIES, MIN_BORROW_DELAY, FOREVER, Schedulers.elastic())
+            .retryWhen(Retry.backoff(MAX_BORROW_RETRIES, MIN_BORROW_DELAY).scheduler(Schedulers.elastic()))
             .onErrorMap(this::propagateException)
             .subscribeOn(Schedulers.elastic())
             .doOnNext(borrowedChannels::add);
diff --git a/backends-common/rabbitmq/src/main/java/org/apache/james/backends/rabbitmq/SimpleConnectionPool.java b/backends-common/rabbitmq/src/main/java/org/apache/james/backends/rabbitmq/SimpleConnectionPool.java
index c8f282b..b174795 100644
--- a/backends-common/rabbitmq/src/main/java/org/apache/james/backends/rabbitmq/SimpleConnectionPool.java
+++ b/backends-common/rabbitmq/src/main/java/org/apache/james/backends/rabbitmq/SimpleConnectionPool.java
@@ -33,6 +33,7 @@ import com.rabbitmq.client.Connection;
 
 import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Schedulers;
+import reactor.util.retry.Retry;
 
 public class SimpleConnectionPool implements AutoCloseable {
     private final AtomicReference<Connection> connectionReference;
@@ -56,9 +57,8 @@ public class SimpleConnectionPool implements AutoCloseable {
     public Mono<Connection> getResilientConnection() {
         int numRetries = 10;
         Duration initialDelay = Duration.ofMillis(100);
-        Duration forever = Duration.ofMillis(Long.MAX_VALUE);
         return Mono.defer(this::getOpenConnection)
-            .retryBackoff(numRetries, initialDelay, forever, Schedulers.elastic());
+            .retryWhen(Retry.backoff(numRetries, initialDelay).scheduler(Schedulers.elastic()));
     }
 
     private Mono<Connection> getOpenConnection() {
diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/events/RetryBackoffConfiguration.java b/mailbox/api/src/main/java/org/apache/james/mailbox/events/RetryBackoffConfiguration.java
index 98bae12..f90a4d4 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/events/RetryBackoffConfiguration.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/events/RetryBackoffConfiguration.java
@@ -65,7 +65,6 @@ public class RetryBackoffConfiguration {
     static final double DEFAULT_JITTER_FACTOR = 0.5;
     static final int DEFAULT_MAX_RETRIES = 8;
     static final Duration DEFAULT_FIRST_BACKOFF = Duration.ofMillis(100);
-    static final Duration FOREVER = Duration.ofMillis(Long.MAX_VALUE);
     public static final RetryBackoffConfiguration DEFAULT = new RetryBackoffConfiguration(
         DEFAULT_MAX_RETRIES,
         DEFAULT_FIRST_BACKOFF,
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapper.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapper.java
index d408624..05d3339 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapper.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapper.java
@@ -49,6 +49,7 @@ import com.google.common.base.Preconditions;
 
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
+import reactor.util.retry.Retry;
 
 public class CassandraMailboxMapper implements MailboxMapper {
     private static final Logger LOGGER = LoggerFactory.getLogger(CassandraMailboxMapper.class);
@@ -89,7 +90,7 @@ public class CassandraMailboxMapper implements MailboxMapper {
         CassandraId mailboxId = (CassandraId) mailbox.getMailboxId();
         deletePath(mailbox)
             .thenEmpty(mailboxDAO.delete(mailboxId)
-                .retryBackoff(MAX_RETRY, MIN_RETRY_BACKOFF, MAX_RETRY_BACKOFF))
+                .retryWhen(Retry.backoff(MAX_RETRY, MIN_RETRY_BACKOFF).maxBackoff(MAX_RETRY_BACKOFF)))
             .block();
     }
 
@@ -247,12 +248,12 @@ public class CassandraMailboxMapper implements MailboxMapper {
 
     private Mono<Void> persistMailboxEntity(Mailbox cassandraMailbox) {
         return mailboxDAO.save(cassandraMailbox)
-            .retryBackoff(MAX_RETRY, MIN_RETRY_BACKOFF, MAX_RETRY_BACKOFF);
+            .retryWhen(Retry.backoff(MAX_RETRY, MIN_RETRY_BACKOFF).maxBackoff(MAX_RETRY_BACKOFF));
     }
 
     private Mono<Void> deletePreviousMailboxPathReference(MailboxPath mailboxPath) {
         return mailboxPathV2DAO.delete(mailboxPath)
-            .retryBackoff(MAX_RETRY, MIN_RETRY_BACKOFF, MAX_RETRY_BACKOFF);
+            .retryWhen(Retry.backoff(MAX_RETRY, MIN_RETRY_BACKOFF).maxBackoff(MAX_RETRY_BACKOFF));
     }
 
     @Override
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapper.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapper.java
index bee2732..25c473c 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapper.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapper.java
@@ -55,6 +55,7 @@ import reactor.core.publisher.Flux;
 import reactor.core.publisher.GroupedFlux;
 import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Schedulers;
+import reactor.util.retry.Retry;
 
 public class CassandraMessageIdMapper implements MessageIdMapper {
     private static final Logger LOGGER = LoggerFactory.getLogger(CassandraMessageIdMapper.class);
@@ -158,7 +159,7 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
         return imapUidDAO.insert(composedMessageIdWithMetaData)
             .thenEmpty(Flux.merge(
                 messageIdDAO.insert(composedMessageIdWithMetaData)
-                    .retryBackoff(MAX_RETRY, MIN_RETRY_BACKOFF, MAX_RETRY_BACKOFF),
+                    .retryWhen(Retry.backoff(MAX_RETRY, MIN_RETRY_BACKOFF).maxBackoff(MAX_RETRY_BACKOFF)),
                 indexTableHandler.updateIndexOnAdd(mailboxMessage, mailboxId))
             .then());
     }
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
index 97c10cd..f5b9d55 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
@@ -60,6 +60,7 @@ import com.google.common.collect.ImmutableList;
 
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
+import reactor.util.retry.Retry;
 
 public class CassandraMessageMapper implements MessageMapper {
     public static final Logger LOGGER = LoggerFactory.getLogger(CassandraMessageMapper.class);
@@ -389,7 +390,7 @@ public class CassandraMessageMapper implements MessageMapper {
         return imapUidDAO.insert(composedMessageIdWithMetaData)
             .then(Flux.merge(
                 messageIdDAO.insert(composedMessageIdWithMetaData)
-                    .retryBackoff(MAX_RETRY, MIN_RETRY_BACKOFF, MAX_RETRY_BACKOFF),
+                    .retryWhen(Retry.backoff(MAX_RETRY, MIN_RETRY_BACKOFF).maxBackoff(MAX_RETRY_BACKOFF)),
                 indexTableHandler.updateIndexOnAdd(message, mailboxId))
             .then());
     }
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraModSeqProvider.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraModSeqProvider.java
index 9088471..f3ee258 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraModSeqProvider.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraModSeqProvider.java
@@ -51,6 +51,7 @@ import com.datastax.driver.core.Session;
 
 import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Schedulers;
+import reactor.util.retry.Retry;
 
 public class CassandraModSeqProvider implements ModSeqProvider {
 
@@ -185,11 +186,10 @@ public class CassandraModSeqProvider implements ModSeqProvider {
     }
 
     private Mono<ModSeq> handleRetries(CassandraId mailboxId) {
-        Duration forever = Duration.ofMillis(Long.MAX_VALUE);
         Duration firstBackoff = Duration.ofMillis(10);
         return tryFindThenUpdateOnce(mailboxId)
             .single()
-            .retryBackoff(maxModSeqRetries, firstBackoff, forever, Schedulers.elastic());
+            .retryWhen(Retry.backoff(maxModSeqRetries, firstBackoff).scheduler(Schedulers.elastic()));
     }
 
     private Mono<ModSeq> tryFindThenUpdateOnce(CassandraId mailboxId) {
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraUidProvider.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraUidProvider.java
index aa957be..fea0d5e 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraUidProvider.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraUidProvider.java
@@ -49,6 +49,7 @@ import com.datastax.driver.core.Session;
 
 import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Schedulers;
+import reactor.util.retry.Retry;
 
 public class CassandraUidProvider implements UidProvider {
     private static final String CONDITION = "Condition";
@@ -105,13 +106,12 @@ public class CassandraUidProvider implements UidProvider {
         Mono<MessageUid> updateUid = findHighestUid(cassandraId)
             .flatMap(messageUid -> tryUpdateUid(cassandraId, messageUid));
 
-        Duration forever = Duration.ofMillis(Long.MAX_VALUE);
         Duration firstBackoff = Duration.ofMillis(10);
         return updateUid
             .switchIfEmpty(tryInsert(cassandraId))
             .switchIfEmpty(updateUid)
             .single()
-            .retryBackoff(maxUidRetries, firstBackoff, forever, Schedulers.elastic());
+            .retryWhen(Retry.backoff(maxUidRetries, firstBackoff).scheduler(Schedulers.elastic()));
     }
 
     @Override
diff --git a/mailbox/event/event-memory/src/main/java/org/apache/james/mailbox/events/delivery/EventDelivery.java b/mailbox/event/event-memory/src/main/java/org/apache/james/mailbox/events/delivery/EventDelivery.java
index af7fa00..e37be04 100644
--- a/mailbox/event/event-memory/src/main/java/org/apache/james/mailbox/events/delivery/EventDelivery.java
+++ b/mailbox/event/event-memory/src/main/java/org/apache/james/mailbox/events/delivery/EventDelivery.java
@@ -22,8 +22,6 @@ package org.apache.james.mailbox.events.delivery;
 import static org.apache.james.mailbox.events.delivery.EventDelivery.PermanentFailureHandler.NO_HANDLER;
 import static org.apache.james.mailbox.events.delivery.EventDelivery.Retryer.NO_RETRYER;
 
-import java.time.Duration;
-
 import org.apache.james.mailbox.events.Event;
 import org.apache.james.mailbox.events.EventDeadLetters;
 import org.apache.james.mailbox.events.Group;
@@ -34,6 +32,7 @@ import org.slf4j.LoggerFactory;
 
 import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Schedulers;
+import reactor.util.retry.Retry;
 
 public interface EventDelivery {
 
@@ -75,7 +74,6 @@ public interface EventDelivery {
             }
 
             private static final Logger LOGGER = LoggerFactory.getLogger(BackoffRetryer.class);
-            private static final Duration FOREVER = Duration.ofMillis(Long.MAX_VALUE);
 
             private final RetryBackoffConfiguration retryBackoff;
             private final MailboxListener mailboxListener;
@@ -88,7 +86,7 @@ public interface EventDelivery {
             @Override
             public Mono<Void> doRetry(Mono<Void> executionResult, Event event) {
                 return executionResult
-                    .retryBackoff(retryBackoff.getMaxRetries(), retryBackoff.getFirstBackoff(), FOREVER, retryBackoff.getJitterFactor(), Schedulers.elastic())
+                    .retryWhen(Retry.backoff(retryBackoff.getMaxRetries(), retryBackoff.getFirstBackoff()).jitter(retryBackoff.getJitterFactor()).scheduler(Schedulers.elastic()))
                     .doOnError(throwable -> LOGGER.error("listener {} exceeded maximum retry({}) to handle event {}",
                         mailboxListener.getClass().getCanonicalName(),
                         retryBackoff.getMaxRetries(),
diff --git a/mailbox/event/event-rabbitmq/src/main/java/org/apache/james/mailbox/events/GroupRegistration.java b/mailbox/event/event-rabbitmq/src/main/java/org/apache/james/mailbox/events/GroupRegistration.java
index 8d1b7aa..7555267 100644
--- a/mailbox/event/event-rabbitmq/src/main/java/org/apache/james/mailbox/events/GroupRegistration.java
+++ b/mailbox/event/event-rabbitmq/src/main/java/org/apache/james/mailbox/events/GroupRegistration.java
@@ -26,7 +26,6 @@ import static org.apache.james.backends.rabbitmq.Constants.EXCLUSIVE;
 import static org.apache.james.backends.rabbitmq.Constants.NO_ARGUMENTS;
 import static org.apache.james.mailbox.events.RabbitMQEventBus.MAILBOX_EVENT;
 import static org.apache.james.mailbox.events.RabbitMQEventBus.MAILBOX_EVENT_EXCHANGE_NAME;
-import static org.apache.james.mailbox.events.RetryBackoffConfiguration.FOREVER;
 
 import java.nio.charset.StandardCharsets;
 import java.util.Objects;
@@ -49,6 +48,7 @@ import reactor.rabbitmq.ConsumeOptions;
 import reactor.rabbitmq.QueueSpecification;
 import reactor.rabbitmq.Receiver;
 import reactor.rabbitmq.Sender;
+import reactor.util.retry.Retry;
 
 class GroupRegistration implements Registration {
     static class WorkQueueName {
@@ -109,7 +109,7 @@ class GroupRegistration implements Registration {
             .of(createGroupWorkQueue()
                 .then(retryHandler.createRetryExchange(queueName))
                 .then(Mono.fromCallable(() -> this.consumeWorkQueue()))
-                .retryBackoff(retryBackoff.getMaxRetries(), retryBackoff.getFirstBackoff(), FOREVER, retryBackoff.getJitterFactor(), Schedulers.elastic())
+                .retryWhen(Retry.backoff(retryBackoff.getMaxRetries(), retryBackoff.getFirstBackoff()).jitter(retryBackoff.getJitterFactor()).scheduler(Schedulers.elastic()))
                 .block());
         return this;
     }
diff --git a/mailbox/event/event-rabbitmq/src/main/java/org/apache/james/mailbox/events/KeyRegistrationHandler.java b/mailbox/event/event-rabbitmq/src/main/java/org/apache/james/mailbox/events/KeyRegistrationHandler.java
index c9767b8..98fac7f 100644
--- a/mailbox/event/event-rabbitmq/src/main/java/org/apache/james/mailbox/events/KeyRegistrationHandler.java
+++ b/mailbox/event/event-rabbitmq/src/main/java/org/apache/james/mailbox/events/KeyRegistrationHandler.java
@@ -23,7 +23,6 @@ import static org.apache.james.backends.rabbitmq.Constants.AUTO_DELETE;
 import static org.apache.james.backends.rabbitmq.Constants.DURABLE;
 import static org.apache.james.backends.rabbitmq.Constants.EXCLUSIVE;
 import static org.apache.james.mailbox.events.RabbitMQEventBus.EVENT_BUS_ID;
-import static org.apache.james.mailbox.events.RetryBackoffConfiguration.FOREVER;
 
 import java.nio.charset.StandardCharsets;
 import java.time.Duration;
@@ -52,6 +51,7 @@ import reactor.rabbitmq.ConsumeOptions;
 import reactor.rabbitmq.QueueSpecification;
 import reactor.rabbitmq.Receiver;
 import reactor.rabbitmq.Sender;
+import reactor.util.retry.Retry;
 
 class KeyRegistrationHandler {
     private static final Logger LOGGER = LoggerFactory.getLogger(KeyRegistrationHandler.class);
@@ -107,7 +107,7 @@ class KeyRegistrationHandler {
             .autoDelete(AUTO_DELETE)
             .arguments(QUEUE_ARGUMENTS))
             .map(AMQP.Queue.DeclareOk::getQueue)
-            .retryBackoff(retryBackoff.getMaxRetries(), retryBackoff.getFirstBackoff(), FOREVER, retryBackoff.getJitterFactor())
+            .retryWhen(Retry.backoff(retryBackoff.getMaxRetries(), retryBackoff.getFirstBackoff()).jitter(retryBackoff.getJitterFactor()))
             .doOnSuccess(queueName -> {
                 if (!registrationQueueInitialized.get()) {
                     registrationQueue.initialize(queueName);
@@ -122,7 +122,7 @@ class KeyRegistrationHandler {
             .ifPresent(Disposable::dispose);
         receiver.close();
         sender.delete(QueueSpecification.queue(registrationQueue.asString()))
-            .retryBackoff(retryBackoff.getMaxRetries(), retryBackoff.getFirstBackoff(), FOREVER, retryBackoff.getJitterFactor(), Schedulers.elastic())
+            .retryWhen(Retry.backoff(retryBackoff.getMaxRetries(), retryBackoff.getFirstBackoff()).jitter(retryBackoff.getJitterFactor()).scheduler(Schedulers.elastic()))
             .block();
     }
 
@@ -130,13 +130,13 @@ class KeyRegistrationHandler {
         LocalListenerRegistry.LocalRegistration registration = localListenerRegistry.addListener(key, listener);
         if (registration.isFirstListener()) {
             registrationBinder.bind(key)
-                .retryBackoff(retryBackoff.getMaxRetries(), retryBackoff.getFirstBackoff(), FOREVER, retryBackoff.getJitterFactor(), Schedulers.elastic())
+                .retryWhen(Retry.backoff(retryBackoff.getMaxRetries(), retryBackoff.getFirstBackoff()).jitter(retryBackoff.getJitterFactor()).scheduler(Schedulers.elastic()))
                 .block();
         }
         return new KeyRegistration(() -> {
             if (registration.unregister().lastListenerRemoved()) {
                 registrationBinder.unbind(key)
-                    .retryBackoff(retryBackoff.getMaxRetries(), retryBackoff.getFirstBackoff(), FOREVER, retryBackoff.getJitterFactor(), Schedulers.elastic())
+                    .retryWhen(Retry.backoff(retryBackoff.getMaxRetries(), retryBackoff.getFirstBackoff()).jitter(retryBackoff.getJitterFactor()).scheduler(Schedulers.elastic()))
                     .block();
             }
         });
diff --git a/pom.xml b/pom.xml
index fb0b2f0..7b747ff 100644
--- a/pom.xml
+++ b/pom.xml
@@ -659,7 +659,7 @@
             <dependency>
                 <groupId>io.projectreactor</groupId>
                 <artifactId>reactor-bom</artifactId>
-                <version>Dysprosium-RELEASE</version>
+                <version>Dysprosium-SR6</version>
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
diff --git a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/delivery/MailDispatcher.java b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/delivery/MailDispatcher.java
index 9fb41be..055a56e 100644
--- a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/delivery/MailDispatcher.java
+++ b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/delivery/MailDispatcher.java
@@ -45,6 +45,7 @@ import com.google.common.collect.ImmutableMap;
 import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Scheduler;
 import reactor.core.scheduler.Schedulers;
+import reactor.util.retry.Retry;
 
 public class MailDispatcher {
     private static final Logger LOGGER = LoggerFactory.getLogger(MailDispatcher.class);
@@ -156,7 +157,7 @@ public class MailDispatcher {
        return Mono.fromRunnable((ThrowingRunnable)() -> mailStore.storeMail(recipient, mail))
            .doOnError(error -> LOGGER.error("Error While storing mail.", error))
            .subscribeOn(scheduler)
-           .retryBackoff(RETRIES, FIRST_BACKOFF, MAX_BACKOFF, scheduler)
+           .retryWhen(Retry.backoff(RETRIES, FIRST_BACKOFF).maxBackoff(MAX_BACKOFF).scheduler(Schedulers.elastic()))
            .then();
     }
 
diff --git a/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/WebAdminUtils.java b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/WebAdminUtils.java
index 5b3d07b..60aeb11 100644
--- a/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/WebAdminUtils.java
+++ b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/WebAdminUtils.java
@@ -40,6 +40,7 @@ import io.restassured.config.RestAssuredConfig;
 import io.restassured.http.ContentType;
 import io.restassured.specification.RequestSpecification;
 import reactor.core.publisher.Mono;
+import reactor.util.retry.Retry;
 
 public class WebAdminUtils {
     private static class ConcurrentSafeWebAdminServer extends WebAdminServer {
@@ -54,7 +55,7 @@ public class WebAdminUtils {
         @Override
         public WebAdminServer start() {
             Mono.fromRunnable(super::start)
-                .retryBackoff(5, Duration.ofMillis(10))
+                .retryWhen(Retry.backoff(5, Duration.ofMillis(10)))
                 .block();
             return this;
         }
diff --git a/server/queue/queue-activemq/pom.xml b/server/queue/queue-activemq/pom.xml
index 9d4b6a7..c37da34 100644
--- a/server/queue/queue-activemq/pom.xml
+++ b/server/queue/queue-activemq/pom.xml
@@ -37,7 +37,7 @@
         <dependency>
             <groupId>io.netty</groupId>
             <artifactId>netty-transport</artifactId>
-            <version>4.1.39.Final</version>
+            <version>4.1.48.Final</version>
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
diff --git a/server/task/task-distributed/src/main/java/org/apache/james/task/eventsourcing/distributed/RabbitMQWorkQueue.java b/server/task/task-distributed/src/main/java/org/apache/james/task/eventsourcing/distributed/RabbitMQWorkQueue.java
index dbdce52..f1daf1f 100644
--- a/server/task/task-distributed/src/main/java/org/apache/james/task/eventsourcing/distributed/RabbitMQWorkQueue.java
+++ b/server/task/task-distributed/src/main/java/org/apache/james/task/eventsourcing/distributed/RabbitMQWorkQueue.java
@@ -56,6 +56,7 @@ import reactor.rabbitmq.OutboundMessage;
 import reactor.rabbitmq.QueueSpecification;
 import reactor.rabbitmq.Receiver;
 import reactor.rabbitmq.Sender;
+import reactor.util.retry.Retry;
 
 public class RabbitMQWorkQueue implements WorkQueue {
     private static final Logger LOGGER = LoggerFactory.getLogger(RabbitMQWorkQueue.class);
@@ -71,7 +72,6 @@ public class RabbitMQWorkQueue implements WorkQueue {
 
     public static final int NUM_RETRIES = 8;
     public static final Duration FIRST_BACKOFF = Duration.ofMillis(100);
-    public static final Duration FOREVER = Duration.ofMillis(Long.MAX_VALUE);
 
     private final TaskManagerWorker worker;
     private final JsonTaskSerializer taskSerializer;
@@ -106,13 +106,13 @@ public class RabbitMQWorkQueue implements WorkQueue {
     void declareQueue() {
         Mono<AMQP.Exchange.DeclareOk> declareExchange = sender
             .declareExchange(ExchangeSpecification.exchange(EXCHANGE_NAME))
-            .retryBackoff(NUM_RETRIES, FIRST_BACKOFF, FOREVER);
+            .retryWhen(Retry.backoff(NUM_RETRIES, FIRST_BACKOFF));
         Mono<AMQP.Queue.DeclareOk> declareQueue = sender
             .declare(QueueSpecification.queue(QUEUE_NAME).durable(true).arguments(Constants.WITH_SINGLE_ACTIVE_CONSUMER))
-            .retryBackoff(NUM_RETRIES, FIRST_BACKOFF, FOREVER);
+            .retryWhen(Retry.backoff(NUM_RETRIES, FIRST_BACKOFF));
         Mono<AMQP.Queue.BindOk> bindQueueToExchange = sender
             .bind(BindingSpecification.binding(EXCHANGE_NAME, ROUTING_KEY, QUEUE_NAME))
-            .retryBackoff(NUM_RETRIES, FIRST_BACKOFF, FOREVER);
+            .retryWhen(Retry.backoff(NUM_RETRIES, FIRST_BACKOFF));
 
         declareExchange
             .then(declareQueue)


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


[james-project] 25/27: JAMES-3065 s/HealthCheck::checkReactive/HealthCheck::check/

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 f5b2483c3895c648e15bff319eb53b406d071bb2
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Apr 24 09:53:39 2020 +0700

    JAMES-3065 s/HealthCheck::checkReactive/HealthCheck::check/
---
 .../james/backends/cassandra/utils/CassandraHealthCheck.java      | 2 +-
 .../james/backends/cassandra/utils/CassandraHealthCheckTest.java  | 8 ++++----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/utils/CassandraHealthCheck.java b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/utils/CassandraHealthCheck.java
index c502d57..07f575b 100644
--- a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/utils/CassandraHealthCheck.java
+++ b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/utils/CassandraHealthCheck.java
@@ -51,7 +51,7 @@ public class CassandraHealthCheck implements HealthCheck {
     }
 
     @Override
-    public Mono<Result> checkReactive() {
+    public Mono<Result> check() {
         // execute a simple query to check if cassandra is responding
         // idea from: https://stackoverflow.com/questions/10246287
         return queryExecutor.execute(SAMPLE_QUERY)
diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/utils/CassandraHealthCheckTest.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/utils/CassandraHealthCheckTest.java
index 1796033..dbe115e 100644
--- a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/utils/CassandraHealthCheckTest.java
+++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/utils/CassandraHealthCheckTest.java
@@ -49,7 +49,7 @@ class CassandraHealthCheckTest {
 
     @Test
     void checkShouldReturnHealthyWhenCassandraIsRunning() {
-        Result check = healthCheck.checkReactive().block();
+        Result check = healthCheck.check().block();
 
         assertThat(check.isHealthy()).isTrue();
     }
@@ -58,7 +58,7 @@ class CassandraHealthCheckTest {
     void checkShouldReturnUnhealthyWhenCassandraIsNotRunning(DockerCassandraExtension.DockerCassandra cassandraServer) {
         try {
             cassandraServer.getContainer().pause();
-            Result check = healthCheck.checkReactive().block();
+            Result check = healthCheck.check().block();
 
             assertThat(check.isUnHealthy()).isTrue();
         } finally {
@@ -72,12 +72,12 @@ class CassandraHealthCheckTest {
         try {
             cassandraServer.getContainer().pause();
 
-            healthCheck.checkReactive().block();
+            healthCheck.check().block();
         } finally {
             cassandraServer.getContainer().unpause();
         }
 
-        Result check = healthCheck.checkReactive().block();
+        Result check = healthCheck.check().block();
 
         assertThat(check.isHealthy()).isTrue();
     }


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


[james-project] 12/27: JAMES-3138 Webadmin endpoint and guice bindings for recomputing current quotas

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 1b0bc6e1d6caa93a7a155193ac00d1fd09809db2
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Wed Apr 22 11:01:21 2020 +0700

    JAMES-3138 Webadmin endpoint and guice bindings for recomputing current quotas
---
 .../org/apache/james/CassandraJamesServerMain.java |  2 +
 .../guice/cassandra-rabbitmq-guice/pom.xml         |  4 ++
 .../james/modules/TaskSerializationModule.java     | 13 +++++
 .../java/org/apache/james/JPAJamesServerMain.java  |  2 +
 .../InconsistencyQuotasSolvingRoutesModule.java    | 40 +++++++++++++++
 .../james/modules/server/MailboxRoutesModule.java  |  2 +
 ...dminServerTaskSerializationIntegrationTest.java | 22 +++++++-
 server/protocols/webadmin/webadmin-mailbox/pom.xml |  4 ++
 .../RecomputeCurrentQuotasRequestToTask.java       | 37 ++++++++++++++
 .../james/webadmin/routes/UserQuotaRoutes.java     | 59 +++++++++++++++++++++-
 .../routes/WebAdminQuotaSearchTestSystem.java      | 11 +++-
 11 files changed, 192 insertions(+), 4 deletions(-)

diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/CassandraJamesServerMain.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/CassandraJamesServerMain.java
index 9bc756c..a7fcc7d 100644
--- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/CassandraJamesServerMain.java
+++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/CassandraJamesServerMain.java
@@ -55,6 +55,7 @@ import org.apache.james.modules.server.DKIMMailetModule;
 import org.apache.james.modules.server.DLPRoutesModule;
 import org.apache.james.modules.server.DataRoutesModules;
 import org.apache.james.modules.server.ElasticSearchMetricReporterModule;
+import org.apache.james.modules.server.InconsistencyQuotasSolvingRoutesModule;
 import org.apache.james.modules.server.JMXServerModule;
 import org.apache.james.modules.server.JmapTasksModule;
 import org.apache.james.modules.server.MailQueueRoutesModule;
@@ -85,6 +86,7 @@ public class CassandraJamesServerMain implements JamesServerMain {
         new DataRoutesModules(),
         new DeletedMessageVaultRoutesModule(),
         new DLPRoutesModule(),
+        new InconsistencyQuotasSolvingRoutesModule(),
         new InconsistencySolvingRoutesModule(),
         new JmapTasksModule(),
         new MailboxesExportRoutesModule(),
diff --git a/server/container/guice/cassandra-rabbitmq-guice/pom.xml b/server/container/guice/cassandra-rabbitmq-guice/pom.xml
index 29fffab..ade5311 100644
--- a/server/container/guice/cassandra-rabbitmq-guice/pom.xml
+++ b/server/container/guice/cassandra-rabbitmq-guice/pom.xml
@@ -87,6 +87,10 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>apache-james-mailbox-tools-quota-recompute</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>blob-objectstorage</artifactId>
             <type>test-jar</type>
             <scope>test</scope>
diff --git a/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/TaskSerializationModule.java b/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/TaskSerializationModule.java
index ab6b867..0e8b86f 100644
--- a/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/TaskSerializationModule.java
+++ b/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/TaskSerializationModule.java
@@ -46,6 +46,9 @@ import org.apache.james.mailbox.cassandra.mail.task.SolveMessageInconsistenciesT
 import org.apache.james.mailbox.cassandra.mail.task.SolveMessageInconsistenciesTaskDTO;
 import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.mailbox.model.MessageId;
+import org.apache.james.mailbox.quota.task.RecomputeCurrentQuotasService;
+import org.apache.james.mailbox.quota.task.RecomputeCurrentQuotasTaskAdditionalInformationDTO;
+import org.apache.james.mailbox.quota.task.RecomputeCurrentQuotasTaskDTO;
 import org.apache.james.queue.api.MailQueueFactory;
 import org.apache.james.queue.api.ManageableMailQueue;
 import org.apache.james.rrt.cassandra.CassandraMappingsSourcesDAO;
@@ -279,6 +282,11 @@ public class TaskSerializationModule extends AbstractModule {
     }
 
     @ProvidesIntoSet
+    public TaskDTOModule<? extends Task, ? extends TaskDTO> recomputeCurrentQuotasTask(RecomputeCurrentQuotasService service) {
+        return RecomputeCurrentQuotasTaskDTO.module(service);
+    }
+
+    @ProvidesIntoSet
     public TaskDTOModule<? extends Task, ? extends TaskDTO> messageIdReindexingTask(MessageIdReIndexingTask.Factory factory) {
         return MessageIdReindexingTaskDTO.module(factory);
     }
@@ -399,6 +407,11 @@ public class TaskSerializationModule extends AbstractModule {
     }
 
     @ProvidesIntoSet
+    public AdditionalInformationDTOModule<? extends TaskExecutionDetails.AdditionalInformation, ? extends  AdditionalInformationDTO> recomputeCurrentQuotasAdditionalInformation() {
+        return RecomputeCurrentQuotasTaskAdditionalInformationDTO.MODULE;
+    }
+
+    @ProvidesIntoSet
     public AdditionalInformationDTOModule<? extends TaskExecutionDetails.AdditionalInformation, ? extends  AdditionalInformationDTO> messageIdReindexingAdditionalInformation(MessageId.Factory messageIdFactory) {
         return MessageIdReindexingTaskAdditionalInformationDTO.serializationModule(messageIdFactory);
     }
diff --git a/server/container/guice/jpa-guice/src/main/java/org/apache/james/JPAJamesServerMain.java b/server/container/guice/jpa-guice/src/main/java/org/apache/james/JPAJamesServerMain.java
index 45f6c3d..19ba467 100644
--- a/server/container/guice/jpa-guice/src/main/java/org/apache/james/JPAJamesServerMain.java
+++ b/server/container/guice/jpa-guice/src/main/java/org/apache/james/JPAJamesServerMain.java
@@ -36,6 +36,7 @@ import org.apache.james.modules.protocols.SMTPServerModule;
 import org.apache.james.modules.server.DataRoutesModules;
 import org.apache.james.modules.server.DefaultProcessorsConfigurationProviderModule;
 import org.apache.james.modules.server.ElasticSearchMetricReporterModule;
+import org.apache.james.modules.server.InconsistencyQuotasSolvingRoutesModule;
 import org.apache.james.modules.server.JMXServerModule;
 import org.apache.james.modules.server.MailQueueRoutesModule;
 import org.apache.james.modules.server.MailRepositoriesRoutesModule;
@@ -57,6 +58,7 @@ public class JPAJamesServerMain implements JamesServerMain {
     public static final Module WEBADMIN = Modules.combine(
         new WebAdminServerModule(),
         new DataRoutesModules(),
+        new InconsistencyQuotasSolvingRoutesModule(),
         new MailboxRoutesModule(),
         new MailQueueRoutesModule(),
         new MailRepositoriesRoutesModule(),
diff --git a/server/container/guice/protocols/webadmin-mailbox/src/main/java/org/apache/james/modules/server/InconsistencyQuotasSolvingRoutesModule.java b/server/container/guice/protocols/webadmin-mailbox/src/main/java/org/apache/james/modules/server/InconsistencyQuotasSolvingRoutesModule.java
new file mode 100644
index 0000000..a5a77c0
--- /dev/null
+++ b/server/container/guice/protocols/webadmin-mailbox/src/main/java/org/apache/james/modules/server/InconsistencyQuotasSolvingRoutesModule.java
@@ -0,0 +1,40 @@
+/****************************************************************
+ * 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.modules.server;
+
+import static org.apache.james.webadmin.routes.UserQuotaRoutes.USER_QUOTAS_OPERATIONS_INJECTION_KEY;
+
+import org.apache.james.webadmin.routes.UserQuotaRoutes;
+import org.apache.james.webadmin.tasks.TaskFromRequestRegistry;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.multibindings.Multibinder;
+import com.google.inject.name.Names;
+
+public class InconsistencyQuotasSolvingRoutesModule extends AbstractModule {
+
+    @Override
+    protected void configure() {
+        Multibinder.newSetBinder(binder(), TaskFromRequestRegistry.TaskRegistration.class, Names.named(USER_QUOTAS_OPERATIONS_INJECTION_KEY))
+            .addBinding()
+            .to(UserQuotaRoutes.RecomputeCurrentQuotasRequestToTask.class);
+    }
+
+}
diff --git a/server/container/guice/protocols/webadmin-mailbox/src/main/java/org/apache/james/modules/server/MailboxRoutesModule.java b/server/container/guice/protocols/webadmin-mailbox/src/main/java/org/apache/james/modules/server/MailboxRoutesModule.java
index 42a7c88..6da9e2c 100644
--- a/server/container/guice/protocols/webadmin-mailbox/src/main/java/org/apache/james/modules/server/MailboxRoutesModule.java
+++ b/server/container/guice/protocols/webadmin-mailbox/src/main/java/org/apache/james/modules/server/MailboxRoutesModule.java
@@ -23,6 +23,7 @@ import static org.apache.james.webadmin.routes.MailboxesRoutes.ALL_MAILBOXES_TAS
 import static org.apache.james.webadmin.routes.MailboxesRoutes.ONE_MAILBOX_TASKS;
 import static org.apache.james.webadmin.routes.MailboxesRoutes.ONE_MAIL_TASKS;
 import static org.apache.james.webadmin.routes.UserMailboxesRoutes.USER_MAILBOXES_OPERATIONS_INJECTION_KEY;
+import static org.apache.james.webadmin.routes.UserQuotaRoutes.USER_QUOTAS_OPERATIONS_INJECTION_KEY;
 
 import org.apache.james.webadmin.Routes;
 import org.apache.james.webadmin.jackson.QuotaModule;
@@ -58,5 +59,6 @@ public class MailboxRoutesModule extends AbstractModule {
         Multibinder.newSetBinder(binder(), TaskRegistration.class, Names.named(ALL_MAILBOXES_TASKS));
         Multibinder.newSetBinder(binder(), TaskRegistration.class, Names.named(ONE_MAILBOX_TASKS));
         Multibinder.newSetBinder(binder(), TaskRegistration.class, Names.named(ONE_MAIL_TASKS));
+        Multibinder.newSetBinder(binder(), TaskRegistration.class, Names.named(USER_QUOTAS_OPERATIONS_INJECTION_KEY));
     }
 }
diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerTaskSerializationIntegrationTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerTaskSerializationIntegrationTest.java
index fe77c70..124c4dd 100644
--- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerTaskSerializationIntegrationTest.java
+++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerTaskSerializationIntegrationTest.java
@@ -82,7 +82,6 @@ import org.apache.james.webadmin.routes.MailRepositoriesRoutes;
 import org.apache.james.webadmin.routes.TasksRoutes;
 import org.apache.james.webadmin.vault.routes.DeletedMessagesVaultRoutes;
 import org.apache.mailet.base.test.FakeMail;
-import org.assertj.core.api.Assertions;
 import org.eclipse.jetty.http.HttpStatus;
 import org.hamcrest.Matchers;
 import org.junit.jupiter.api.BeforeEach;
@@ -759,6 +758,27 @@ class RabbitMQWebAdminServerTaskSerializationIntegrationTest {
             .body("additionalInformation.processedMailboxes", is(0));
     }
 
+    @Test
+    void recomputeCurrentQuotasShouldComplete() {
+        String taskId = with()
+            .basePath("/quota/users")
+            .queryParam("task", "RecomputeCurrentQuotas")
+        .post()
+            .jsonPath()
+            .get("taskId");
+
+        given()
+            .basePath(TasksRoutes.BASE)
+        .when()
+            .get(taskId + "/await")
+        .then()
+            .body("status", is("completed"))
+            .body("taskId", is(taskId))
+            .body("type", is("recompute-current-quotas"))
+            .body("additionalInformation.processedQuotaRoots", is(0))
+            .body("additionalInformation.failedQuotaRoots", empty());
+    }
+
     private MailboxListener.MailboxAdded createMailboxAdded() {
         String uuid = "6e0dd59d-660e-4d9b-b22f-0354479f47b4";
         return EventFactory.mailboxAdded()
diff --git a/server/protocols/webadmin/webadmin-mailbox/pom.xml b/server/protocols/webadmin/webadmin-mailbox/pom.xml
index c5e95fb..44fd413 100644
--- a/server/protocols/webadmin/webadmin-mailbox/pom.xml
+++ b/server/protocols/webadmin/webadmin-mailbox/pom.xml
@@ -90,6 +90,10 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>apache-james-mailbox-tools-quota-recompute</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>apache-james-backends-es</artifactId>
             <type>test-jar</type>
             <scope>test</scope>
diff --git a/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/RecomputeCurrentQuotasRequestToTask.java b/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/RecomputeCurrentQuotasRequestToTask.java
new file mode 100644
index 0000000..a407c77
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/RecomputeCurrentQuotasRequestToTask.java
@@ -0,0 +1,37 @@
+/****************************************************************
+ * 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.webadmin.routes;
+
+import javax.inject.Inject;
+
+import org.apache.james.mailbox.quota.task.RecomputeCurrentQuotasService;
+import org.apache.james.mailbox.quota.task.RecomputeCurrentQuotasTask;
+import org.apache.james.webadmin.tasks.TaskFromRequestRegistry;
+import org.apache.james.webadmin.tasks.TaskRegistrationKey;
+
+public class RecomputeCurrentQuotasRequestToTask extends TaskFromRequestRegistry.TaskRegistration {
+    private static final TaskRegistrationKey REGISTRATION_KEY = TaskRegistrationKey.of("RecomputeCurrentQuotas");
+
+    @Inject
+    public RecomputeCurrentQuotasRequestToTask(RecomputeCurrentQuotasService service) {
+        super(REGISTRATION_KEY,
+            request -> new RecomputeCurrentQuotasTask(service));
+    }
+}
diff --git a/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/UserQuotaRoutes.java b/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/UserQuotaRoutes.java
index fda1b65..0f73118 100644
--- a/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/UserQuotaRoutes.java
+++ b/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/UserQuotaRoutes.java
@@ -19,6 +19,8 @@
 
 package org.apache.james.webadmin.routes;
 
+import static org.apache.james.webadmin.routes.MailboxesRoutes.TASK_PARAMETER;
+
 import java.io.UnsupportedEncodingException;
 import java.net.URLDecoder;
 import java.nio.charset.StandardCharsets;
@@ -27,8 +29,10 @@ import java.util.Set;
 import java.util.stream.Collectors;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
+import javax.ws.rs.POST;
 import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
@@ -37,10 +41,13 @@ import org.apache.james.core.Domain;
 import org.apache.james.core.Username;
 import org.apache.james.core.quota.QuotaCountLimit;
 import org.apache.james.core.quota.QuotaSizeLimit;
+import org.apache.james.mailbox.quota.task.RecomputeCurrentQuotasService;
+import org.apache.james.mailbox.quota.task.RecomputeCurrentQuotasTask;
 import org.apache.james.quota.search.Limit;
 import org.apache.james.quota.search.Offset;
 import org.apache.james.quota.search.QuotaBoundary;
 import org.apache.james.quota.search.QuotaQuery;
+import org.apache.james.task.TaskManager;
 import org.apache.james.user.api.UsersRepository;
 import org.apache.james.user.api.UsersRepositoryException;
 import org.apache.james.webadmin.Routes;
@@ -48,6 +55,9 @@ import org.apache.james.webadmin.dto.QuotaDTO;
 import org.apache.james.webadmin.dto.QuotaDetailsDTO;
 import org.apache.james.webadmin.dto.ValidatedQuotaDTO;
 import org.apache.james.webadmin.service.UserQuotaService;
+import org.apache.james.webadmin.tasks.TaskFromRequestRegistry;
+import org.apache.james.webadmin.tasks.TaskIdDto;
+import org.apache.james.webadmin.tasks.TaskRegistrationKey;
 import org.apache.james.webadmin.utils.ErrorResponder;
 import org.apache.james.webadmin.utils.ErrorResponder.ErrorType;
 import org.apache.james.webadmin.utils.JsonExtractException;
@@ -67,6 +77,7 @@ import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiResponse;
 import io.swagger.annotations.ApiResponses;
 import spark.Request;
+import spark.Route;
 import spark.Service;
 
 @Api(tags = "UserQuota")
@@ -74,6 +85,16 @@ import spark.Service;
 @Produces("application/json")
 public class UserQuotaRoutes implements Routes {
 
+    public static final String USER_QUOTAS_OPERATIONS_INJECTION_KEY = "userQuotasOperations";
+
+    public static class RecomputeCurrentQuotasRequestToTask extends TaskFromRequestRegistry.TaskRegistration {
+        @Inject
+        public RecomputeCurrentQuotasRequestToTask(RecomputeCurrentQuotasService service) {
+            super(RECOMPUTE_CURRENT_QUOTAS, request -> new RecomputeCurrentQuotasTask(service));
+        }
+    }
+
+    private static final TaskRegistrationKey RECOMPUTE_CURRENT_QUOTAS = TaskRegistrationKey.of("RecomputeCurrentQuotas");
     private static final String USER = "user";
     private static final String MIN_OCCUPATION_RATIO = "minOccupationRatio";
     private static final String MAX_OCCUPATION_RATIO = "maxOccupationRatio";
@@ -88,15 +109,24 @@ public class UserQuotaRoutes implements Routes {
     private final JsonTransformer jsonTransformer;
     private final JsonExtractor<QuotaDTO> jsonExtractor;
     private final QuotaDTOValidator quotaDTOValidator;
+    private final TaskManager taskManager;
+    private final Set<TaskFromRequestRegistry.TaskRegistration> usersQuotasTaskRegistration;
     private Service service;
 
     @Inject
-    public UserQuotaRoutes(UsersRepository usersRepository, UserQuotaService userQuotaService, JsonTransformer jsonTransformer, Set<JsonTransformerModule> modules) {
+    public UserQuotaRoutes(UsersRepository usersRepository,
+                           UserQuotaService userQuotaService,
+                           JsonTransformer jsonTransformer,
+                           Set<JsonTransformerModule> modules,
+                           TaskManager taskManager,
+                           @Named(USER_QUOTAS_OPERATIONS_INJECTION_KEY) Set<TaskFromRequestRegistry.TaskRegistration> usersQuotasTaskRegistration) {
         this.usersRepository = usersRepository;
         this.userQuotaService = userQuotaService;
         this.jsonTransformer = jsonTransformer;
         this.jsonExtractor = new JsonExtractor<>(QuotaDTO.class, modules.stream().map(JsonTransformerModule::asJacksonModule).collect(Collectors.toList()));
         this.quotaDTOValidator = new QuotaDTOValidator();
+        this.taskManager = taskManager;
+        this.usersQuotasTaskRegistration = usersQuotasTaskRegistration;
     }
 
     @Override
@@ -120,6 +150,33 @@ public class UserQuotaRoutes implements Routes {
         defineUpdateQuota();
 
         defineGetUsersQuota();
+        definePostUsersQuota();
+        definePostUsersQuota()
+            .ifPresent(route -> service.post(USERS_QUOTA_ENDPOINT, route, jsonTransformer));
+    }
+
+    @POST
+    @ApiOperation(value = "Recomputing current quotas of users")
+    @ApiImplicitParams({
+        @ApiImplicitParam(
+            required = true,
+            name = "task",
+            paramType = "query parameter",
+            dataType = "String",
+            defaultValue = "none",
+            example = "?task=RecomputeCurrentQuotas",
+            value = "Compulsory. Only supported value is `RecomputeCurrentQuotas`")
+    })
+    @ApiResponses(value = {
+        @ApiResponse(code = HttpStatus.CREATED_201, message = "Task is created", response = TaskIdDto.class),
+        @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500, message = "Internal server error - Something went bad on the server side."),
+        @ApiResponse(code = HttpStatus.BAD_REQUEST_400, message = "Bad request - details in the returned error message")
+    })
+    public Optional<Route> definePostUsersQuota() {
+        return TaskFromRequestRegistry.builder()
+            .parameterName(TASK_PARAMETER)
+            .registrations(usersQuotasTaskRegistration)
+            .buildAsRouteOptional(taskManager);
     }
 
     @PUT
diff --git a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/WebAdminQuotaSearchTestSystem.java b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/WebAdminQuotaSearchTestSystem.java
index c3e622a..898d5d6 100644
--- a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/WebAdminQuotaSearchTestSystem.java
+++ b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/WebAdminQuotaSearchTestSystem.java
@@ -20,6 +20,9 @@
 package org.apache.james.webadmin.routes;
 
 import org.apache.james.quota.search.QuotaSearchTestSystem;
+import org.apache.james.task.Hostname;
+import org.apache.james.task.MemoryTaskManager;
+import org.apache.james.task.TaskManager;
 import org.apache.james.webadmin.WebAdminServer;
 import org.apache.james.webadmin.WebAdminUtils;
 import org.apache.james.webadmin.jackson.QuotaModule;
@@ -47,9 +50,13 @@ public class WebAdminQuotaSearchTestSystem {
 
         QuotaModule quotaModule = new QuotaModule();
         JsonTransformer jsonTransformer = new JsonTransformer(quotaModule);
+        TaskManager taskManager = new MemoryTaskManager(new Hostname("foo"));
         UserQuotaRoutes userQuotaRoutes = new UserQuotaRoutes(quotaSearchTestSystem.getUsersRepository(),
-            userQuotaService, jsonTransformer,
-            ImmutableSet.of(quotaModule));
+            userQuotaService,
+            jsonTransformer,
+            ImmutableSet.of(quotaModule),
+            taskManager,
+            ImmutableSet.of());
         DomainQuotaRoutes domainQuotaRoutes = new DomainQuotaRoutes(
             quotaSearchTestSystem.getDomainList(),
             new DomainQuotaService(quotaSearchTestSystem.getMaxQuotaManager()),


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


[james-project] 01/27: [Refactoring] Move MailboxACLJsonConverter from mailbox/store to mailbox/cassandra

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 d563e4afa12afa7f844874ead5f854c1405c3977
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Apr 24 14:47:17 2020 +0700

    [Refactoring] Move MailboxACLJsonConverter from mailbox/store to mailbox/cassandra
    
    This is the only place where it is being used.
---
 .../apache/james/mailbox/cassandra}/json/MailboxACLJsonConverter.java   | 2 +-
 .../org/apache/james/mailbox/cassandra/mail/CassandraACLMapper.java     | 2 +-
 .../james/mailbox/cassandra}/json/MailboxACLJsonConverterTest.java      | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/MailboxACLJsonConverter.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/json/MailboxACLJsonConverter.java
similarity index 98%
rename from mailbox/store/src/main/java/org/apache/james/mailbox/store/json/MailboxACLJsonConverter.java
rename to mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/json/MailboxACLJsonConverter.java
index 8fdfc83..d6b650f 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/json/MailboxACLJsonConverter.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/json/MailboxACLJsonConverter.java
@@ -17,7 +17,7 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.mailbox.store.json;
+package org.apache.james.mailbox.cassandra.json;
 
 import java.io.IOException;
 
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraACLMapper.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraACLMapper.java
index eccabcd..43eeef8 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraACLMapper.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraACLMapper.java
@@ -35,12 +35,12 @@ import org.apache.james.backends.cassandra.init.configuration.CassandraConfigura
 import org.apache.james.backends.cassandra.utils.CassandraAsyncExecutor;
 import org.apache.james.mailbox.acl.ACLDiff;
 import org.apache.james.mailbox.cassandra.ids.CassandraId;
+import org.apache.james.mailbox.cassandra.json.MailboxACLJsonConverter;
 import org.apache.james.mailbox.cassandra.table.CassandraACLTable;
 import org.apache.james.mailbox.cassandra.table.CassandraMailboxTable;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.exception.UnsupportedRightException;
 import org.apache.james.mailbox.model.MailboxACL;
-import org.apache.james.mailbox.store.json.MailboxACLJsonConverter;
 import org.apache.james.util.FunctionalUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/json/MailboxACLJsonConverterTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/json/MailboxACLJsonConverterTest.java
similarity index 99%
rename from mailbox/store/src/test/java/org/apache/james/mailbox/store/json/MailboxACLJsonConverterTest.java
rename to mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/json/MailboxACLJsonConverterTest.java
index b15fed2..3da7d4b 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/json/MailboxACLJsonConverterTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/json/MailboxACLJsonConverterTest.java
@@ -17,7 +17,7 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.mailbox.store.json;
+package org.apache.james.mailbox.cassandra.json;
 
 import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
 import static org.apache.james.mailbox.model.MailboxACL.Right.CreateMailbox;


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


[james-project] 16/27: JAMES-3138 Integration tests for consistency tasks

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 6e597252351c0d5986f97be41ece6cd501a2b737
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Apr 24 20:12:05 2020 +0700

    JAMES-3138 Integration tests for consistency tasks
---
 .../james/backends/cassandra/TestingSession.java   |   2 +-
 .../org/apache/james/modules/MailboxProbeImpl.java |   6 +
 .../rabbitmq/ConsistencyTasksIntegrationTest.java  | 314 +++++++++++++++++++++
 3 files changed, 321 insertions(+), 1 deletion(-)

diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/TestingSession.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/TestingSession.java
index b229b46..cbb7ae7 100644
--- a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/TestingSession.java
+++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/TestingSession.java
@@ -35,7 +35,7 @@ public class TestingSession implements Session {
     private final Session delegate;
     private volatile Scenario scenario;
 
-    TestingSession(Session delegate) {
+    public TestingSession(Session delegate) {
         this.delegate = delegate;
         this.scenario = Scenario.NOTHING;
     }
diff --git a/server/container/guice/mailbox/src/main/java/org/apache/james/modules/MailboxProbeImpl.java b/server/container/guice/mailbox/src/main/java/org/apache/james/modules/MailboxProbeImpl.java
index 97e0c16..1d5936a 100644
--- a/server/container/guice/mailbox/src/main/java/org/apache/james/modules/MailboxProbeImpl.java
+++ b/server/container/guice/mailbox/src/main/java/org/apache/james/modules/MailboxProbeImpl.java
@@ -36,6 +36,7 @@ import org.apache.james.mailbox.SubscriptionManager;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.exception.MailboxNotFoundException;
 import org.apache.james.mailbox.model.ComposedMessageId;
+import org.apache.james.mailbox.model.MailboxCounters;
 import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.mailbox.model.MailboxMetaData;
 import org.apache.james.mailbox.model.MailboxPath;
@@ -146,6 +147,11 @@ public class MailboxProbeImpl implements GuiceProbe, MailboxProbe {
         }
     }
 
+    public MailboxCounters retrieveCounters(MailboxPath path) throws MailboxException {
+        MailboxSession systemSession = mailboxManager.createSystemSession(path.getUser());
+        return mailboxManager.getMailbox(path, systemSession).getMailboxCounters(systemSession);
+    }
+
     @Override
     public ComposedMessageId appendMessage(String username, MailboxPath mailboxPath, InputStream message, Date internalDate, boolean isRecent, Flags flags)
             throws MailboxException {
diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/ConsistencyTasksIntegrationTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/ConsistencyTasksIntegrationTest.java
new file mode 100644
index 0000000..f821ced
--- /dev/null
+++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/ConsistencyTasksIntegrationTest.java
@@ -0,0 +1,314 @@
+/****************************************************************
+ * 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.webadmin.integration.rabbitmq;
+
+import static io.restassured.RestAssured.when;
+import static io.restassured.RestAssured.with;
+import static org.apache.james.backends.cassandra.Scenario.Builder.awaitOn;
+import static org.apache.james.backends.cassandra.Scenario.Builder.fail;
+import static org.apache.james.jmap.JMAPTestingConstants.BOB;
+import static org.apache.james.jmap.JMAPTestingConstants.BOB_PASSWORD;
+import static org.apache.james.webadmin.Constants.SEPARATOR;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.CoreMatchers.hasItems;
+import static org.hamcrest.Matchers.is;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.Date;
+import java.util.Optional;
+
+import javax.mail.Flags;
+
+import org.apache.james.CassandraExtension;
+import org.apache.james.CassandraRabbitMQJamesServerMain;
+import org.apache.james.DockerElasticSearchExtension;
+import org.apache.james.GuiceJamesServer;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.backends.cassandra.Scenario;
+import org.apache.james.backends.cassandra.Scenario.Barrier;
+import org.apache.james.backends.cassandra.TestingSession;
+import org.apache.james.backends.cassandra.init.SessionWithInitializedTablesFactory;
+import org.apache.james.junit.categories.BasicFeature;
+import org.apache.james.mailbox.events.RetryBackoffConfiguration;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.model.QuotaRoot;
+import org.apache.james.modules.AwsS3BlobStoreExtension;
+import org.apache.james.modules.MailboxProbeImpl;
+import org.apache.james.modules.QuotaProbesImpl;
+import org.apache.james.modules.RabbitMQExtension;
+import org.apache.james.probe.DataProbe;
+import org.apache.james.utils.DataProbeImpl;
+import org.apache.james.utils.GuiceProbe;
+import org.apache.james.utils.WebAdminGuiceProbe;
+import org.apache.james.webadmin.WebAdminUtils;
+import org.apache.james.webadmin.integration.WebadminIntegrationTestModule;
+import org.apache.james.webadmin.routes.AliasRoutes;
+import org.apache.james.webadmin.routes.CassandraMappingsRoutes;
+import org.apache.james.webadmin.routes.TasksRoutes;
+import org.eclipse.jetty.http.HttpStatus;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.RepeatedTest;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.datastax.driver.core.Session;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Provides;
+import com.google.inject.Singleton;
+import com.google.inject.multibindings.Multibinder;
+
+import io.restassured.RestAssured;
+import io.restassured.http.ContentType;
+
+@Tag(BasicFeature.TAG)
+class ConsistencyTasksIntegrationTest {
+
+    private static class TestingSessionProbe implements GuiceProbe {
+        private final TestingSession testingSession;
+
+        @Inject
+        private TestingSessionProbe(TestingSession testingSession) {
+            this.testingSession = testingSession;
+        }
+
+        public TestingSession getTestingSession() {
+            return testingSession;
+        }
+    }
+
+    private static class TestingSessionModule extends AbstractModule {
+        @Override
+        protected void configure() {
+            Multibinder.newSetBinder(binder(), GuiceProbe.class)
+                .addBinding()
+                .to(TestingSessionProbe.class);
+
+            bind(Session.class).to(TestingSession.class);
+        }
+
+        @Provides
+        @Singleton
+        TestingSession provideSession(SessionWithInitializedTablesFactory factory) {
+            return new TestingSession(factory.get());
+        }
+    }
+
+    @RegisterExtension
+    static JamesServerExtension testExtension = new JamesServerBuilder()
+        .extension(new DockerElasticSearchExtension())
+        .extension(new CassandraExtension())
+        .extension(new AwsS3BlobStoreExtension())
+        .extension(new RabbitMQExtension())
+        .server(configuration -> GuiceJamesServer.forConfiguration(configuration)
+            .combineWith(CassandraRabbitMQJamesServerMain.MODULES)
+            .overrideWith(new WebadminIntegrationTestModule())
+            // Enforce a single eventBus retry. Required as Current Quotas are handled by the eventBus.
+            .overrideWith(binder -> binder.bind(RetryBackoffConfiguration.class)
+                .toInstance(RetryBackoffConfiguration.builder()
+                    .maxRetries(1)
+                    .firstBackoff(Duration.ofMillis(2))
+                    .jitterFactor(0.5)
+                    .build()))
+            .overrideWith(new TestingSessionModule()))
+        .build();
+
+    private static final String VERSION = "/cassandra/version";
+    private static final String UPGRADE_VERSION = VERSION + "/upgrade";
+    private static final String UPGRADE_TO_LATEST_VERSION = UPGRADE_VERSION + "/latest";
+    private static final String DOMAIN = "domain";
+    private static final String USERNAME = "username@" + DOMAIN;
+    private static final String ALIAS_1 = "alias1@" + DOMAIN;
+    private static final String ALIAS_2 = "alias2@" + DOMAIN;
+    private static final boolean IS_RECENT = true;
+
+    private DataProbe dataProbe;
+
+    @BeforeEach
+    void setUp(GuiceJamesServer guiceJamesServer) throws Exception {
+        dataProbe = guiceJamesServer.getProbe(DataProbeImpl.class);
+        dataProbe.addDomain(DOMAIN);
+        WebAdminGuiceProbe webAdminGuiceProbe = guiceJamesServer.getProbe(WebAdminGuiceProbe.class);
+
+        RestAssured.requestSpecification = WebAdminUtils.buildRequestSpecification(webAdminGuiceProbe.getWebAdminPort())
+            .build();
+    }
+
+    @Test
+    void shouldSolveCassandraMappingInconsistency(GuiceJamesServer server) {
+        server.getProbe(TestingSessionProbe.class)
+            .getTestingSession().registerScenario(fail()
+            .times(1)
+            .whenQueryStartsWith("INSERT INTO mappings_sources"));
+
+        with()
+            .put(AliasRoutes.ROOT_PATH + SEPARATOR + USERNAME + "/sources/" + ALIAS_1);
+        with()
+            .put(AliasRoutes.ROOT_PATH + SEPARATOR + USERNAME + "/sources/" + ALIAS_2);
+
+        String taskId = with()
+            .queryParam("action", "SolveInconsistencies")
+            .post(CassandraMappingsRoutes.ROOT_PATH)
+            .jsonPath()
+            .get("taskId");
+
+        with()
+            .basePath(TasksRoutes.BASE)
+            .get(taskId + "/await");
+
+        when()
+            .get(AliasRoutes.ROOT_PATH + SEPARATOR + USERNAME)
+        .then()
+            .contentType(ContentType.JSON)
+        .statusCode(HttpStatus.OK_200)
+            .body("source", hasItems(ALIAS_1, ALIAS_2));
+    }
+
+    @Test
+    void shouldSolveMailboxesInconsistency(GuiceJamesServer server) {
+        MailboxProbeImpl probe = server.getProbe(MailboxProbeImpl.class);
+
+        server.getProbe(TestingSessionProbe.class)
+            .getTestingSession().registerScenario(fail()
+            .times(6) // Insertion in the DAO is retried 5 times once it failed
+            .whenQueryStartsWith("INSERT INTO mailbox (id,name,uidvalidity,mailboxbase)"));
+
+        try {
+            probe.createMailbox(MailboxPath.inbox(BOB));
+        } catch (Exception e) {
+            // Failure is expected
+        }
+
+        // schema version 6 or higher required to run solve mailbox inconsistencies task
+        String taskId = with().post(UPGRADE_TO_LATEST_VERSION)
+            .jsonPath()
+            .get("taskId");
+
+        with()
+            .get("/tasks/" + taskId + "/await")
+        .then()
+            .body("status", is("completed"));
+
+        taskId = with()
+            .header("I-KNOW-WHAT-I-M-DOING", "ALL-SERVICES-ARE-OFFLINE")
+            .queryParam("task", "SolveInconsistencies")
+            .post("/mailboxes")
+            .jsonPath()
+            .get("taskId");
+
+        with()
+            .basePath(TasksRoutes.BASE)
+            .get(taskId + "/await");
+
+        // The mailbox is removed as it is not in the mailboxDAO source of truth.
+        assertThat(probe.listUserMailboxes(BOB.asString()))
+            .isEmpty();
+    }
+
+    @Test
+    void shouldRecomputeMailboxCounters(GuiceJamesServer server) throws MailboxException {
+        MailboxProbeImpl probe = server.getProbe(MailboxProbeImpl.class);
+        MailboxPath inbox = MailboxPath.inbox(BOB);
+        probe.createMailbox(inbox);
+
+        server.getProbe(TestingSessionProbe.class)
+            .getTestingSession().registerScenario(fail()
+            .times(1)
+            .whenQueryStartsWith("INSERT INTO messageCounter (nextUid,mailboxId)"));
+
+        try {
+            probe.appendMessage(BOB.asString(), inbox,
+                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags(Flags.Flag.SEEN));
+        } catch (Exception e) {
+            // Expected to fail
+        }
+
+        String taskId = with()
+            .basePath("/mailboxes")
+            .queryParam("task", "RecomputeMailboxCounters")
+            .post()
+            .jsonPath()
+            .get("taskId");
+
+        with()
+            .basePath(TasksRoutes.BASE)
+            .get(taskId + "/await");
+
+        assertThat(probe.retrieveCounters(inbox).getCount()).isEqualTo(1);
+    }
+
+    @Test
+    void shouldRecomputeQuotas(GuiceJamesServer server) throws Exception {
+        dataProbe.fluent()
+            .addDomain(BOB.getDomainPart().get().asString())
+            .addUser(BOB.asString(), BOB_PASSWORD);
+        MailboxProbeImpl probe = server.getProbe(MailboxProbeImpl.class);
+        MailboxPath inbox = MailboxPath.inbox(BOB);
+        probe.createMailbox(inbox);
+
+        Barrier barrier1 = new Barrier();
+        Barrier barrier2 = new Barrier();
+        String updatedQuotaQueryString = "UPDATE currentQuota SET messageCount=messageCount+?,storage=storage+? WHERE quotaRoot=?;";
+        server.getProbe(TestingSessionProbe.class)
+            .getTestingSession().registerScenario(Scenario.combine(
+                awaitOn(barrier1) // Event bus first execution
+                    .thenFail()
+                    .times(1)
+                    .whenQueryStartsWith(updatedQuotaQueryString),
+                awaitOn(barrier2) // scenari for event bus retry
+                    .thenFail()
+                    .times(1)
+                    .whenQueryStartsWith(updatedQuotaQueryString)));
+
+        probe.appendMessage(BOB.asString(), inbox,
+            new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(),
+            !IS_RECENT, new Flags(Flags.Flag.SEEN));
+
+        // Await first execution
+        barrier1.awaitCaller();
+        barrier1.releaseCaller();
+        // Await event bus retry
+        barrier2.awaitCaller();
+        barrier2.releaseCaller();
+
+        String taskId = with()
+            .basePath("/quota/users")
+            .queryParam("task", "RecomputeCurrentQuotas")
+            .post()
+            .jsonPath()
+            .get("taskId");
+
+        with()
+            .basePath(TasksRoutes.BASE)
+            .get(taskId + "/await");
+
+        QuotaProbesImpl quotaProbe = server.getProbe(QuotaProbesImpl.class);
+        assertThat(
+            quotaProbe.getMessageCountQuota(QuotaRoot.quotaRoot("#private&" + BOB.asString(), Optional.empty()))
+                .getUsed()
+                .asLong())
+            .isEqualTo(1);
+    }
+}
\ 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


[james-project] 13/27: JAMES-3138 Changelog entry

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 5178d1a1ced30548c7987497d1a65a34fda02b05
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Tue Apr 14 16:05:03 2020 +0700

    JAMES-3138 Changelog entry
---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6d1986f..a5d57c9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,7 @@ of tasks being currently executed.
 - JAMES-3072 WebAdmin endpoint to export mailbox backup
 - JAMES-3117 Add PeriodicalHealthChecks for periodical calling all health checks
 - JAMES-3143 WebAdmin endpoint to solve Cassandra message inconsistencies
+- JAMES-3138 Webadmin endpoint to recompute users current quotas on top of Guice products
 
 ### Changed
 - Switch to Java 11 for build and run


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


[james-project] 06/27: JAMES-3143 Guice binding for task

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 4db5a1f875c815383dde3eec2ad3c3ae5d782838
Author: LanKhuat <kh...@gmail.com>
AuthorDate: Wed Apr 22 11:01:44 2020 +0700

    JAMES-3143 Guice binding for task
---
 .../webadmin/InconsistencySolvingRoutesModule.java       | 16 ++++++++++++++++
 .../apache/james/modules/TaskSerializationModule.java    | 13 +++++++++++++
 .../james/modules/server/MessagesRoutesModule.java       |  2 ++
 3 files changed, 31 insertions(+)

diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/webadmin/InconsistencySolvingRoutesModule.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/webadmin/InconsistencySolvingRoutesModule.java
index baf4402..f5197e1 100644
--- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/webadmin/InconsistencySolvingRoutesModule.java
+++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/webadmin/InconsistencySolvingRoutesModule.java
@@ -21,11 +21,14 @@ package org.apache.james.modules.webadmin;
 
 import org.apache.james.mailbox.cassandra.mail.task.RecomputeMailboxCountersService;
 import org.apache.james.mailbox.cassandra.mail.task.SolveMailboxInconsistenciesService;
+import org.apache.james.mailbox.cassandra.mail.task.SolveMessageInconsistenciesService;
 import org.apache.james.webadmin.Routes;
 import org.apache.james.webadmin.routes.CassandraMappingsRoutes;
 import org.apache.james.webadmin.routes.MailboxesRoutes;
+import org.apache.james.webadmin.routes.MessagesRoutes;
 import org.apache.james.webadmin.routes.RecomputeMailboxCountersRequestToTask;
 import org.apache.james.webadmin.routes.SolveMailboxInconsistenciesRequestToTask;
+import org.apache.james.webadmin.routes.SolveMessageInconsistenciesRequestToTask;
 import org.apache.james.webadmin.service.CassandraMappingsService;
 import org.apache.james.webadmin.tasks.TaskFromRequestRegistry;
 
@@ -60,9 +63,22 @@ public class InconsistencySolvingRoutesModule extends AbstractModule {
         }
     }
 
+    public static class SolveMessageInconsistenciesModules extends AbstractModule {
+        @Override
+        protected void configure() {
+            bind(SolveMessageInconsistenciesService.class).in(Scopes.SINGLETON);
+
+            Multibinder<TaskFromRequestRegistry.TaskRegistration> multiBinder = Multibinder.newSetBinder(binder(),
+                TaskFromRequestRegistry.TaskRegistration.class, Names.named(MessagesRoutes.ALL_MESSAGES_TASKS));
+
+            multiBinder.addBinding().to(SolveMessageInconsistenciesRequestToTask.class);
+        }
+    }
+
     @Override
     protected void configure() {
         install(new SolveRRTInconsistenciesModules());
         install(new SolveMailboxInconsistenciesModules());
+        install(new SolveMessageInconsistenciesModules());
     }
 }
diff --git a/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/TaskSerializationModule.java b/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/TaskSerializationModule.java
index 2b03f57..ab6b867 100644
--- a/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/TaskSerializationModule.java
+++ b/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/TaskSerializationModule.java
@@ -41,6 +41,9 @@ import org.apache.james.mailbox.cassandra.mail.task.RecomputeMailboxCountersTask
 import org.apache.james.mailbox.cassandra.mail.task.SolveMailboxInconsistenciesService;
 import org.apache.james.mailbox.cassandra.mail.task.SolveMailboxInconsistenciesTaskAdditionalInformationDTO;
 import org.apache.james.mailbox.cassandra.mail.task.SolveMailboxInconsistenciesTaskDTO;
+import org.apache.james.mailbox.cassandra.mail.task.SolveMessageInconsistenciesService;
+import org.apache.james.mailbox.cassandra.mail.task.SolveMessageInconsistenciesTaskAdditionalInformationDTO;
+import org.apache.james.mailbox.cassandra.mail.task.SolveMessageInconsistenciesTaskDTO;
 import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.mailbox.model.MessageId;
 import org.apache.james.queue.api.MailQueueFactory;
@@ -296,6 +299,11 @@ public class TaskSerializationModule extends AbstractModule {
     }
 
     @ProvidesIntoSet
+    public TaskDTOModule<? extends Task, ? extends TaskDTO> solveMessageInconsistenciesTask(SolveMessageInconsistenciesService solveMessageInconsistenciesService) {
+        return SolveMessageInconsistenciesTaskDTO.module(solveMessageInconsistenciesService);
+    }
+
+    @ProvidesIntoSet
     public TaskDTOModule<? extends Task, ? extends TaskDTO> singleMailboxReindexingTask(SingleMailboxReindexingTask.Factory factory) {
         return SingleMailboxReindexingTaskDTO.module(factory);
     }
@@ -440,6 +448,11 @@ public class TaskSerializationModule extends AbstractModule {
         return MailboxesExportTaskAdditionalInformationDTO.SERIALIZATION_MODULE;
     }
 
+    @ProvidesIntoSet
+    public AdditionalInformationDTOModule<? extends TaskExecutionDetails.AdditionalInformation, ? extends  AdditionalInformationDTO> solveMessageInconsistenciesAdditionalInformation() {
+        return SolveMessageInconsistenciesTaskAdditionalInformationDTO.MODULE;
+    }
+
     @Named(EventNestedTypes.EVENT_NESTED_TYPES_INJECTION_NAME)
     @Provides
     public Set<DTOModule<?, ? extends org.apache.james.json.DTO>> eventNestedTypes(Set<AdditionalInformationDTOModule<? extends TaskExecutionDetails.AdditionalInformation, ? extends  AdditionalInformationDTO>> additionalInformationDTOModules,
diff --git a/server/container/guice/protocols/webadmin-mailbox/src/main/java/org/apache/james/modules/server/MessagesRoutesModule.java b/server/container/guice/protocols/webadmin-mailbox/src/main/java/org/apache/james/modules/server/MessagesRoutesModule.java
index 8af3a00..df23a93 100644
--- a/server/container/guice/protocols/webadmin-mailbox/src/main/java/org/apache/james/modules/server/MessagesRoutesModule.java
+++ b/server/container/guice/protocols/webadmin-mailbox/src/main/java/org/apache/james/modules/server/MessagesRoutesModule.java
@@ -30,5 +30,7 @@ public class MessagesRoutesModule extends AbstractModule {
     protected void configure() {
         Multibinder<Routes> routesMultibinder = Multibinder.newSetBinder(binder(), Routes.class);
         routesMultibinder.addBinding().to(MessagesRoutes.class);
+
+
     }
 }


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


[james-project] 19/27: JAMES-3138 InMemoryCurrentQuotaManager: Improve reactor code style

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 e73dfb8e9696c2e9b7975d756a16965a56426826
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Sat Apr 25 11:19:29 2020 +0700

    JAMES-3138 InMemoryCurrentQuotaManager: Improve reactor code style
    
     - get rid of redundant Mono::from
     - filter rather than nested if
---
 .../mailbox/inmemory/quota/InMemoryCurrentQuotaManager.java  | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/quota/InMemoryCurrentQuotaManager.java b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/quota/InMemoryCurrentQuotaManager.java
index 34a9be6..8521557 100644
--- a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/quota/InMemoryCurrentQuotaManager.java
+++ b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/quota/InMemoryCurrentQuotaManager.java
@@ -85,14 +85,10 @@ public class InMemoryCurrentQuotaManager implements CurrentQuotaManager {
 
     @Override
     public Mono<Void> setCurrentQuotas(QuotaOperation quotaOperation) {
-        return Mono.from(getCurrentQuotas(quotaOperation.quotaRoot()))
-            .flatMap(storedQuotas -> {
-                if (!storedQuotas.equals(CurrentQuotas.from(quotaOperation))) {
-                    return Mono.from(decrease(new QuotaOperation(quotaOperation.quotaRoot(), storedQuotas.count(), storedQuotas.size())))
-                        .then(Mono.from(increase(quotaOperation)));
-                }
-                return Mono.empty();
-            });
+        return getCurrentQuotas(quotaOperation.quotaRoot())
+            .filter(storedQuotas -> !storedQuotas.equals(CurrentQuotas.from(quotaOperation)))
+            .flatMap(storedQuotas -> decrease(new QuotaOperation(quotaOperation.quotaRoot(), storedQuotas.count(), storedQuotas.size()))
+                .then(increase(quotaOperation)));
     }
 
     private Mono<Void> updateQuota(QuotaRoot quotaRoot, UnaryOperator<CurrentQuotas> quotaFunction) {


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


[james-project] 11/27: JAMES-3138 Task for recomputing current quotas

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 ab4e4e3b1df2b4be221dfd233990582b0801a606
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Mon Apr 13 17:12:02 2020 +0700

    JAMES-3138 Task for recomputing current quotas
---
 .../org/apache/james/mailbox/model/QuotaRoot.java  |   5 +
 mailbox/tools/quota-recompute/pom.xml              |  20 ++++
 .../quota/task/RecomputeCurrentQuotasTask.java     | 101 +++++++++++++++++++++
 ...eCurrentQuotasTaskAdditionalInformationDTO.java |  87 ++++++++++++++++++
 .../quota/task/RecomputeCurrentQuotasTaskDTO.java  |  57 ++++++++++++
 ...ecomputeCurrentQuotasTaskSerializationTest.java |  61 +++++++++++++
 6 files changed, 331 insertions(+)

diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/model/QuotaRoot.java b/mailbox/api/src/main/java/org/apache/james/mailbox/model/QuotaRoot.java
index c7a825a..1ad8d20 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/model/QuotaRoot.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/model/QuotaRoot.java
@@ -73,4 +73,9 @@ public class QuotaRoot {
                 .add("domain", domain)
                 .toString();
     }
+
+    public String asString() {
+        return domain.map(domainValue -> value + "@" + domainValue.asString())
+            .orElse(value);
+    }
 }
diff --git a/mailbox/tools/quota-recompute/pom.xml b/mailbox/tools/quota-recompute/pom.xml
index 34c21a7..2c59165 100644
--- a/mailbox/tools/quota-recompute/pom.xml
+++ b/mailbox/tools/quota-recompute/pom.xml
@@ -38,13 +38,33 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>james-json</artifactId>
+            <scope>test</scope>
+            <type>test-jar</type>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>james-server-data-api</artifactId>
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-task-json</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>testing-base</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>net.javacrumbs.json-unit</groupId>
+            <artifactId>json-unit-assertj</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
 </project>
\ No newline at end of file
diff --git a/mailbox/tools/quota-recompute/src/main/java/org/apache/james/mailbox/quota/task/RecomputeCurrentQuotasTask.java b/mailbox/tools/quota-recompute/src/main/java/org/apache/james/mailbox/quota/task/RecomputeCurrentQuotasTask.java
new file mode 100644
index 0000000..050737b
--- /dev/null
+++ b/mailbox/tools/quota-recompute/src/main/java/org/apache/james/mailbox/quota/task/RecomputeCurrentQuotasTask.java
@@ -0,0 +1,101 @@
+/****************************************************************
+ * 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.mailbox.quota.task;
+
+import java.time.Clock;
+import java.time.Instant;
+import java.util.Optional;
+
+import org.apache.james.mailbox.model.QuotaRoot;
+import org.apache.james.mailbox.quota.task.RecomputeCurrentQuotasService.Context;
+import org.apache.james.mailbox.quota.task.RecomputeCurrentQuotasService.Context.Snapshot;
+import org.apache.james.task.Task;
+import org.apache.james.task.TaskExecutionDetails;
+import org.apache.james.task.TaskType;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.steveash.guavate.Guavate;
+import com.google.common.collect.ImmutableList;
+
+import reactor.core.scheduler.Schedulers;
+
+public class RecomputeCurrentQuotasTask implements Task {
+    static final TaskType RECOMPUTE_CURRENT_QUOTAS = TaskType.of("recompute-current-quotas");
+
+    public static class Details implements TaskExecutionDetails.AdditionalInformation {
+        private final Instant instant;
+        private final long processedQuotaRoots;
+        private final ImmutableList<String> failedQuotaRoots;
+
+        Details(Instant instant, long processedQuotaRoots, ImmutableList<String> failedQuotaRoots) {
+            this.instant = instant;
+            this.processedQuotaRoots = processedQuotaRoots;
+            this.failedQuotaRoots = failedQuotaRoots;
+        }
+
+        @Override
+        public Instant timestamp() {
+            return instant;
+        }
+
+        @JsonProperty("processedQuotaRoots")
+        long getProcessedQuotaRoots() {
+            return processedQuotaRoots;
+        }
+
+        @JsonProperty("failedQuotaRoots")
+        ImmutableList<String> getFailedQuotaRoots() {
+            return failedQuotaRoots;
+        }
+    }
+
+    private final RecomputeCurrentQuotasService service;
+
+    private Context context;
+
+    public RecomputeCurrentQuotasTask(RecomputeCurrentQuotasService service) {
+        this.service = service;
+        this.context = new Context();
+    }
+
+    @Override
+    public Task.Result run() {
+        return service.recomputeCurrentQuotas(context)
+            .subscribeOn(Schedulers.elastic())
+            .block();
+    }
+
+    @Override
+    public TaskType type() {
+        return RECOMPUTE_CURRENT_QUOTAS;
+    }
+
+    @Override
+    public Optional<TaskExecutionDetails.AdditionalInformation> details() {
+        Snapshot snapshot = context.snapshot();
+
+        return Optional.of(new Details(Clock.systemUTC().instant(),
+            snapshot.getProcessedQuotaRootCount(),
+            snapshot.getFailedQuotaRoots()
+                .stream()
+                .map(QuotaRoot::asString)
+                .collect(Guavate.toImmutableList())));
+    }
+}
diff --git a/mailbox/tools/quota-recompute/src/main/java/org/apache/james/mailbox/quota/task/RecomputeCurrentQuotasTaskAdditionalInformationDTO.java b/mailbox/tools/quota-recompute/src/main/java/org/apache/james/mailbox/quota/task/RecomputeCurrentQuotasTaskAdditionalInformationDTO.java
new file mode 100644
index 0000000..5bebcee
--- /dev/null
+++ b/mailbox/tools/quota-recompute/src/main/java/org/apache/james/mailbox/quota/task/RecomputeCurrentQuotasTaskAdditionalInformationDTO.java
@@ -0,0 +1,87 @@
+/****************************************************************
+ * 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.mailbox.quota.task;
+
+import java.time.Instant;
+
+import org.apache.james.json.DTOModule;
+import org.apache.james.server.task.json.dto.AdditionalInformationDTO;
+import org.apache.james.server.task.json.dto.AdditionalInformationDTOModule;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.ImmutableList;
+
+public class RecomputeCurrentQuotasTaskAdditionalInformationDTO implements AdditionalInformationDTO {
+    private static RecomputeCurrentQuotasTaskAdditionalInformationDTO fromDomainObject(RecomputeCurrentQuotasTask.Details details, String type) {
+        return new RecomputeCurrentQuotasTaskAdditionalInformationDTO(
+            type,
+            details.getProcessedQuotaRoots(),
+            details.getFailedQuotaRoots(),
+            details.timestamp());
+    }
+
+    public static final AdditionalInformationDTOModule<RecomputeCurrentQuotasTask.Details, RecomputeCurrentQuotasTaskAdditionalInformationDTO> MODULE =
+        DTOModule
+            .forDomainObject(RecomputeCurrentQuotasTask.Details.class)
+            .convertToDTO(RecomputeCurrentQuotasTaskAdditionalInformationDTO.class)
+            .toDomainObjectConverter(RecomputeCurrentQuotasTaskAdditionalInformationDTO::toDomainObject)
+            .toDTOConverter(RecomputeCurrentQuotasTaskAdditionalInformationDTO::fromDomainObject)
+            .typeName(RecomputeCurrentQuotasTask.RECOMPUTE_CURRENT_QUOTAS.asString())
+            .withFactory(AdditionalInformationDTOModule::new);
+
+    private final String type;
+    private final long processedQuotaRoots;
+    private final ImmutableList<String> failedQuotaRoots;
+    private final Instant timestamp;
+
+    public RecomputeCurrentQuotasTaskAdditionalInformationDTO(@JsonProperty("type") String type,
+                                                              @JsonProperty("processedQuotaRoots") long processedQuotaRoots,
+                                                              @JsonProperty("failedQuotaRoots") ImmutableList<String> failedQuotaRoots,
+                                                              @JsonProperty("timestamp") Instant timestamp) {
+        this.type = type;
+        this.processedQuotaRoots = processedQuotaRoots;
+        this.failedQuotaRoots = failedQuotaRoots;
+        this.timestamp = timestamp;
+    }
+
+    public long getProcessedQuotaRoots() {
+        return processedQuotaRoots;
+    }
+
+    public ImmutableList<String> getFailedQuotaRoots() {
+        return failedQuotaRoots;
+    }
+
+    @Override
+    public Instant getTimestamp() {
+        return timestamp;
+    }
+
+    @Override
+    public String getType() {
+        return type;
+    }
+
+    private RecomputeCurrentQuotasTask.Details toDomainObject() {
+        return new RecomputeCurrentQuotasTask.Details(timestamp,
+            processedQuotaRoots,
+            failedQuotaRoots);
+    }
+}
diff --git a/mailbox/tools/quota-recompute/src/main/java/org/apache/james/mailbox/quota/task/RecomputeCurrentQuotasTaskDTO.java b/mailbox/tools/quota-recompute/src/main/java/org/apache/james/mailbox/quota/task/RecomputeCurrentQuotasTaskDTO.java
new file mode 100644
index 0000000..33d5a4e
--- /dev/null
+++ b/mailbox/tools/quota-recompute/src/main/java/org/apache/james/mailbox/quota/task/RecomputeCurrentQuotasTaskDTO.java
@@ -0,0 +1,57 @@
+/****************************************************************
+ * 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.mailbox.quota.task;
+
+import org.apache.james.json.DTOModule;
+import org.apache.james.server.task.json.dto.TaskDTO;
+import org.apache.james.server.task.json.dto.TaskDTOModule;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class RecomputeCurrentQuotasTaskDTO implements TaskDTO {
+    private static RecomputeCurrentQuotasTaskDTO toDTO(RecomputeCurrentQuotasTask domainObject, String typeName) {
+        return new RecomputeCurrentQuotasTaskDTO(typeName);
+    }
+
+    public static TaskDTOModule<RecomputeCurrentQuotasTask, RecomputeCurrentQuotasTaskDTO> module(RecomputeCurrentQuotasService service) {
+        return DTOModule
+            .forDomainObject(RecomputeCurrentQuotasTask.class)
+            .convertToDTO(RecomputeCurrentQuotasTaskDTO.class)
+            .toDomainObjectConverter(dto -> dto.toDomainObject(service))
+            .toDTOConverter(RecomputeCurrentQuotasTaskDTO::toDTO)
+            .typeName(RecomputeCurrentQuotasTask.RECOMPUTE_CURRENT_QUOTAS.asString())
+            .withFactory(TaskDTOModule::new);
+    }
+
+    private final String type;
+
+    public RecomputeCurrentQuotasTaskDTO(@JsonProperty("type") String type) {
+        this.type = type;
+    }
+
+    private RecomputeCurrentQuotasTask toDomainObject(RecomputeCurrentQuotasService service) {
+        return new RecomputeCurrentQuotasTask(service);
+    }
+
+    @Override
+    public String getType() {
+        return type;
+    }
+}
diff --git a/mailbox/tools/quota-recompute/src/test/java/org/apache/james/mailbox/quota/task/RecomputeCurrentQuotasTaskSerializationTest.java b/mailbox/tools/quota-recompute/src/test/java/org/apache/james/mailbox/quota/task/RecomputeCurrentQuotasTaskSerializationTest.java
new file mode 100644
index 0000000..9eb7405
--- /dev/null
+++ b/mailbox/tools/quota-recompute/src/test/java/org/apache/james/mailbox/quota/task/RecomputeCurrentQuotasTaskSerializationTest.java
@@ -0,0 +1,61 @@
+/****************************************************************
+ * 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.mailbox.quota.task;
+
+import static org.mockito.Mockito.mock;
+
+import java.time.Instant;
+
+import org.apache.james.JsonSerializationVerifier;
+import org.junit.jupiter.api.Test;
+
+import com.google.common.collect.ImmutableList;
+
+class RecomputeCurrentQuotasTaskSerializationTest {
+    static final Instant TIMESTAMP = Instant.parse("2018-11-13T12:00:55Z");
+    static final String QUOTA_ROOT_AS_STRING = "bob@localhost";
+
+    static final RecomputeCurrentQuotasService SERVICE = mock(RecomputeCurrentQuotasService.class);
+    static final RecomputeCurrentQuotasTask TASK = new RecomputeCurrentQuotasTask(SERVICE);
+    static final String SERIALIZED_TASK = "{\"type\": \"recompute-current-quotas\"}";
+    static final RecomputeCurrentQuotasTask.Details DETAILS = new RecomputeCurrentQuotasTask.Details(TIMESTAMP, 12, ImmutableList.of(QUOTA_ROOT_AS_STRING));
+    static final String SERIALIZED_ADDITIONAL_INFORMATION = "{" +
+        "  \"type\":\"recompute-current-quotas\"," +
+        "  \"processedQuotaRoots\":12," +
+        "  \"failedQuotaRoots\":[\"bob@localhost\"]," +
+        "  \"timestamp\":\"2018-11-13T12:00:55Z\"" +
+        "}";
+
+    @Test
+    void taskShouldBeSerializable() throws Exception {
+        JsonSerializationVerifier.dtoModule(RecomputeCurrentQuotasTaskDTO.module(SERVICE))
+            .bean(TASK)
+            .json(SERIALIZED_TASK)
+            .verify();
+    }
+
+    @Test
+    void additionalInformationShouldBeSerializable() throws Exception {
+        JsonSerializationVerifier.dtoModule(RecomputeCurrentQuotasTaskAdditionalInformationDTO.MODULE)
+            .bean(DETAILS)
+            .json(SERIALIZED_ADDITIONAL_INFORMATION)
+            .verify();
+    }
+}


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


[james-project] 09/27: JAMES-3143 Update webadmin documentation

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 a7d33f77dda5a7f029379f81531fc719a08f0321
Author: LanKhuat <kh...@gmail.com>
AuthorDate: Wed Apr 22 18:42:22 2020 +0700

    JAMES-3143 Update webadmin documentation
---
 src/site/markdown/server/manage-webadmin.md | 76 ++++++++++++++++++++++++++++-
 1 file changed, 75 insertions(+), 1 deletion(-)

diff --git a/src/site/markdown/server/manage-webadmin.md b/src/site/markdown/server/manage-webadmin.md
index e88532e..7efda46 100644
--- a/src/site/markdown/server/manage-webadmin.md
+++ b/src/site/markdown/server/manage-webadmin.md
@@ -697,6 +697,81 @@ The scheduled task will have the following type `messageId-reindexing` and the f
 
 Warning: During the re-indexing, the result of search operations might be altered.
 
+### Fixing message inconsistencies
+
+This task is only available on top of Guice Cassandra products.
+
+```
+curl -XPOST /messages?task=SolveInconsistencies
+```
+
+Will schedule a task for fixing message inconsistencies created by the message denormalization process. 
+
+Messages are denormalized and stored in separated data tables in Cassandra, so they can be accessed 
+by their unique identifier or mailbox identifier & local mailbox identifier through different protocols. 
+
+Failure in the denormalization process will lead to inconsistencies, for example:
+
+```
+BOB receives a message
+The denormalization process fails
+BOB can read the message via JMAP
+BOB cannot read the message via IMAP
+
+BOB marks a message as SEEN
+The denormalization process fails
+The message is SEEN via JMAP
+The message is UNSEEN via IMAP
+```
+
+[More details about endpoints returning a task](#Endpoints_returning_a_task).
+
+Response codes:
+
+ - 201: Success. Corresponding task id is returned.
+ - 400: Error in the request. Details can be found in the reported error.
+
+The scheduled task will have the following type `solve-message-inconsistencies` and the following `additionalInformation`:
+
+```
+{
+  "type":"solve-message-inconsistencies",
+  "timestamp":"2007-12-03T10:15:30Z",
+  "processedImapUidEntries": 2,
+  "processedMessageIdEntries": 1,
+  "addedMessageIdEntries": 1,
+  "updatedMessageIdEntries": 0,
+  "removedMessageIdEntries": 1,
+  "fixedInconsistencies": [
+    {
+      "mailboxId": "551f0580-82fb-11ea-970e-f9c83d4cf8c2",
+      "messageId": "d2bee791-7e63-11ea-883c-95b84008f979",
+      "uid": 1
+    },
+    {
+      "mailboxId": "551f0580-82fb-11ea-970e-f9c83d4cf8c2",
+      "messageId": "d2bee792-7e63-11ea-883c-95b84008f979",
+      "uid": 2
+    }
+  ],
+  "errors": [
+    {
+      "mailboxId": "551f0580-82fb-11ea-970e-f9c83d4cf8c2",
+      "messageId": "ffffffff-7e63-11ea-883c-95b84008f979",
+      "uid": 3
+    }
+  ]
+}
+```
+
+User actions concurrent to the inconsistency fixing task could result in concurrency issues. New inconsistencies 
+could be created. 
+
+However the source of truth will not be impacted, hence rerunning the task will eventually fix all issues.
+
+This task could be run safely online and can be scheduled on a recurring basis outside of peak traffic 
+by an admin to ensure Cassandra message consistency.
+
 ## Administrating user mailboxes
 
  - [Creating a mailbox](#Creating_a_mailbox)
@@ -3554,4 +3629,3 @@ Response codes :
 
  - 201: the taskId of the created task
  - 400: Invalid action argument for performing operation on mappings data
-


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


[james-project] 18/27: JAMES-3138 CassandraCurrentQuotaManager: Improve Reactor code style

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 00160a4205d11e0e2a9f51fbbe6f0ed70614148f
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Sat Apr 25 11:15:58 2020 +0700

    JAMES-3138 CassandraCurrentQuotaManager: Improve Reactor code style
    
     - get rid of redundant Mono::from
     - filter rather than nested if
---
 .../cassandra/quota/CassandraCurrentQuotaManager.java        | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraCurrentQuotaManager.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraCurrentQuotaManager.java
index 92c4200..675b3e2 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraCurrentQuotaManager.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/quota/CassandraCurrentQuotaManager.java
@@ -116,13 +116,9 @@ public class CassandraCurrentQuotaManager implements CurrentQuotaManager {
 
     @Override
     public Mono<Void> setCurrentQuotas(QuotaOperation quotaOperation) {
-        return Mono.from(getCurrentQuotas(quotaOperation.quotaRoot()))
-            .flatMap(storedQuotas -> {
-                if (!storedQuotas.equals(CurrentQuotas.from(quotaOperation))) {
-                    return Mono.from(decrease(new QuotaOperation(quotaOperation.quotaRoot(), storedQuotas.count(), storedQuotas.size())))
-                        .then(Mono.from(increase(quotaOperation)));
-                }
-                return Mono.empty();
-            });
+        return getCurrentQuotas(quotaOperation.quotaRoot())
+            .filter(storedQuotas -> !storedQuotas.equals(CurrentQuotas.from(quotaOperation)))
+            .flatMap(storedQuotas -> decrease(new QuotaOperation(quotaOperation.quotaRoot(), storedQuotas.count(), storedQuotas.size()))
+                .then(increase(quotaOperation)));
     }
 }


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


[james-project] 22/27: JAMES-3138 Add fix nothing integration 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 d018a04bd0634dd4783dc9ec00724d1d1b20b34a
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Apr 28 07:48:11 2020 +0700

    JAMES-3138 Add fix nothing integration tests
---
 .../rabbitmq/ConsistencyTasksIntegrationTest.java  | 134 +++++++++++++++++++++
 1 file changed, 134 insertions(+)

diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/ConsistencyTasksIntegrationTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/ConsistencyTasksIntegrationTest.java
index bb69945..11faa4b 100644
--- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/ConsistencyTasksIntegrationTest.java
+++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/ConsistencyTasksIntegrationTest.java
@@ -50,6 +50,7 @@ import org.apache.james.backends.cassandra.init.SessionWithInitializedTablesFact
 import org.apache.james.junit.categories.BasicFeature;
 import org.apache.james.mailbox.events.RetryBackoffConfiguration;
 import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.model.MailboxConstants;
 import org.apache.james.mailbox.model.MailboxPath;
 import org.apache.james.mailbox.model.QuotaRoot;
 import org.apache.james.modules.AwsS3BlobStoreExtension;
@@ -309,4 +310,137 @@ class ConsistencyTasksIntegrationTest {
                 .asLong())
             .isEqualTo(1);
     }
+
+    @Test
+    void solveCassandraMappingInconsistencyShouldSolveNotingWhenNoInconsistencies() {
+        with()
+            .put(AliasRoutes.ROOT_PATH + SEPARATOR + USERNAME + "/sources/" + ALIAS_1);
+        with()
+            .put(AliasRoutes.ROOT_PATH + SEPARATOR + USERNAME + "/sources/" + ALIAS_2);
+
+        String taskId = with()
+            .queryParam("action", "SolveInconsistencies")
+            .post(CassandraMappingsRoutes.ROOT_PATH)
+            .jsonPath()
+            .get("taskId");
+
+        with()
+            .basePath(TasksRoutes.BASE)
+            .get(taskId + "/await");
+
+        when()
+            .get(AliasRoutes.ROOT_PATH + SEPARATOR + USERNAME)
+        .then()
+            .contentType(ContentType.JSON)
+        .statusCode(HttpStatus.OK_200)
+            .body("source", hasItems(ALIAS_1, ALIAS_2));
+    }
+
+    @Test
+    void solveMailboxesInconsistencyShouldSolveNotingWhenNoInconsistencies(GuiceJamesServer server) {
+        MailboxProbeImpl probe = server.getProbe(MailboxProbeImpl.class);
+
+        try {
+            probe.createMailbox(MailboxPath.inbox(BOB));
+        } catch (Exception e) {
+            // Failure is expected
+        }
+
+        // schema version 6 or higher required to run solve mailbox inconsistencies task
+        String taskId = with().post(UPGRADE_TO_LATEST_VERSION)
+            .jsonPath()
+            .get("taskId");
+
+        with()
+            .get("/tasks/" + taskId + "/await")
+        .then()
+            .body("status", is("completed"));
+
+        taskId = with()
+            .header("I-KNOW-WHAT-I-M-DOING", "ALL-SERVICES-ARE-OFFLINE")
+            .queryParam("task", "SolveInconsistencies")
+            .post("/mailboxes")
+            .jsonPath()
+            .get("taskId");
+
+        with()
+            .basePath(TasksRoutes.BASE)
+            .get(taskId + "/await");
+
+        // The mailbox is removed as it is not in the mailboxDAO source of truth.
+        assertThat(probe.listUserMailboxes(BOB.asString()))
+            .containsOnly(MailboxConstants.INBOX);
+    }
+
+    @Test
+    void recomputeMailboxCountersShouldSolveNotingWhenNoInconsistencies(GuiceJamesServer server) throws MailboxException {
+        MailboxProbeImpl probe = server.getProbe(MailboxProbeImpl.class);
+        MailboxPath inbox = MailboxPath.inbox(BOB);
+        probe.createMailbox(inbox);
+
+        try {
+            probe.appendMessage(BOB.asString(), inbox,
+                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags(Flags.Flag.SEEN));
+        } catch (Exception e) {
+            // Expected to fail
+        }
+
+        String taskId = with()
+            .basePath("/mailboxes")
+            .queryParam("task", "RecomputeMailboxCounters")
+            .post()
+            .jsonPath()
+            .get("taskId");
+
+        with()
+            .basePath(TasksRoutes.BASE)
+            .get(taskId + "/await");
+
+        assertThat(probe.retrieveCounters(inbox).getCount()).isEqualTo(1);
+    }
+
+    @Test
+    void recomputeQuotasShouldSolveNotingWhenNoInconsistencies(GuiceJamesServer server) throws Exception {
+        dataProbe.fluent()
+            .addDomain(BOB.getDomainPart().get().asString())
+            .addUser(BOB.asString(), BOB_PASSWORD);
+        MailboxProbeImpl probe = server.getProbe(MailboxProbeImpl.class);
+        MailboxPath inbox = MailboxPath.inbox(BOB);
+        probe.createMailbox(inbox);
+
+        Barrier barrier = new Barrier();
+        String updatedQuotaQueryString = "UPDATE currentQuota SET messageCount=messageCount+?,storage=storage+? WHERE quotaRoot=?;";
+        server.getProbe(TestingSessionProbe.class)
+            .getTestingSession().registerScenario(
+                awaitOn(barrier) // Event bus first execution
+                    .thenExecuteNormally()
+                    .times(1)
+                    .whenQueryStartsWith(updatedQuotaQueryString));
+
+        probe.appendMessage(BOB.asString(), inbox,
+            new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(),
+            !IS_RECENT, new Flags(Flags.Flag.SEEN));
+
+        // Await first execution
+        barrier.awaitCaller();
+        barrier.releaseCaller();
+
+        String taskId = with()
+            .basePath("/quota/users")
+            .queryParam("task", "RecomputeCurrentQuotas")
+            .post()
+            .jsonPath()
+            .get("taskId");
+
+        with()
+            .basePath(TasksRoutes.BASE)
+            .get(taskId + "/await");
+
+        QuotaProbesImpl quotaProbe = server.getProbe(QuotaProbesImpl.class);
+        assertThat(
+            quotaProbe.getMessageCountQuota(QuotaRoot.quotaRoot("#private&" + BOB.asString(), Optional.empty()))
+                .getUsed()
+                .asLong())
+            .isEqualTo(1);
+    }
 }
\ 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


[james-project] 20/27: JAMES-3138 Cassandra test instumentation: inject failure after awaitOn

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 fc528ae112a211339616095d87b85a3366d9eb54
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Sat Apr 25 11:52:28 2020 +0700

    JAMES-3138 Cassandra test instumentation: inject failure after awaitOn
    
    This enables synchronized failure injection. New behaviours can be
    written then injected at will!
---
 .../apache/james/backends/cassandra/Scenario.java  | 23 ++++++++++++++-----
 .../backends/cassandra/TestingSessionTest.java     | 26 ++++++++++++++++++++++
 .../cassandra/mail/CassandraACLMapperTest.java     |  6 +++--
 .../cassandra/mail/CassandraMailboxDAOTest.java    |  2 +-
 4 files changed, 49 insertions(+), 8 deletions(-)

diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/Scenario.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/Scenario.java
index 0d01c3e..f2f3682 100644
--- a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/Scenario.java
+++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/Scenario.java
@@ -41,10 +41,10 @@ public class Scenario {
 
         Behavior EXECUTE_NORMALLY = Session::executeAsync;
 
-        static Behavior awaitOn(Barrier barrier) {
+        static Behavior awaitOn(Barrier barrier, Behavior behavior) {
             return (session, statement) -> {
                 barrier.call();
-                return session.executeAsync(statement);
+                return behavior.execute(session, statement);
             };
         }
 
@@ -130,6 +130,19 @@ public class Scenario {
             }
         }
 
+        @FunctionalInterface
+        interface ComposeBehavior {
+            RequiresValidity then(Behavior behavior);
+
+            default RequiresValidity thenExecuteNormally() {
+                return then(Behavior.EXECUTE_NORMALLY);
+            }
+
+            default RequiresValidity thenFail() {
+                return then(Behavior.THROW);
+            }
+        }
+
         static RequiresValidity fail() {
             return validity -> statementPredicate -> new ExecutionHook(
                 statementPredicate,
@@ -144,10 +157,10 @@ public class Scenario {
                 validity);
         }
 
-        static RequiresValidity awaitOn(Barrier barrier) {
-            return validity -> statementPredicate -> new ExecutionHook(
+        static ComposeBehavior awaitOn(Barrier barrier) {
+            return behavior -> validity -> statementPredicate -> new ExecutionHook(
                 statementPredicate,
-                Behavior.awaitOn(barrier),
+                Behavior.awaitOn(barrier, behavior),
                 validity);
         }
     }
diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/TestingSessionTest.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/TestingSessionTest.java
index bb08907..5140e14 100644
--- a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/TestingSessionTest.java
+++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/TestingSessionTest.java
@@ -209,6 +209,7 @@ class TestingSessionTest {
         Barrier barrier = new Barrier();
         cassandra.getConf()
             .registerScenario(awaitOn(barrier)
+                .thenExecuteNormally()
                 .times(1)
                 .whenQueryStartsWith("INSERT INTO schemaVersion"));
 
@@ -229,6 +230,7 @@ class TestingSessionTest {
         Barrier barrier = new Barrier();
         cassandra.getConf()
             .registerScenario(awaitOn(barrier)
+                .thenExecuteNormally()
                 .times(1)
                 .whenQueryStartsWith("INSERT INTO schemaVersion"));
 
@@ -251,6 +253,7 @@ class TestingSessionTest {
         Barrier barrier = new Barrier();
         cassandra.getConf()
             .registerScenario(awaitOn(barrier)
+                .thenExecuteNormally()
                 .times(1)
                 .whenQueryStartsWith("INSERT INTO schemaVersion"));
 
@@ -264,4 +267,27 @@ class TestingSessionTest {
         assertThat(dao.getCurrentSchemaVersion().block())
             .contains(newVersion);
     }
+
+    @Test
+    void awaitOnShouldBeAbleToInjectFailure(CassandraCluster cassandra) throws Exception {
+        SchemaVersion originalSchemaVersion = new SchemaVersion(32);
+        SchemaVersion newVersion = new SchemaVersion(36);
+
+        dao.updateVersion(originalSchemaVersion).block();
+        Barrier barrier = new Barrier();
+        cassandra.getConf()
+            .registerScenario(awaitOn(barrier)
+                .thenFail()
+                .times(1)
+                .whenQueryStartsWith("INSERT INTO schemaVersion"));
+
+        Mono<Void> operation = dao.updateVersion(newVersion).cache();
+
+        operation.subscribeOn(Schedulers.elastic()).subscribe();
+        barrier.awaitCaller();
+        barrier.releaseCaller();
+
+        assertThatThrownBy(operation::block)
+            .isInstanceOf(RuntimeException.class);
+    }
 }
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraACLMapperTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraACLMapperTest.java
index cf5a4ca..d6b66e9 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraACLMapperTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraACLMapperTest.java
@@ -164,8 +164,9 @@ class CassandraACLMapperTest {
         Barrier barrier = new Barrier(2);
         cassandra.getConf()
             .registerScenario(awaitOn(barrier)
-                    .times(2)
-                    .whenQueryStartsWith("SELECT acl,version FROM acl WHERE id=:id;"));
+                .thenExecuteNormally()
+                .times(2)
+                .whenQueryStartsWith("SELECT acl,version FROM acl WHERE id=:id;"));
 
         MailboxACL.EntryKey keyBob = new MailboxACL.EntryKey("bob", MailboxACL.NameType.user, false);
         MailboxACL.Rfc4314Rights rights = new MailboxACL.Rfc4314Rights(MailboxACL.Right.Read);
@@ -191,6 +192,7 @@ class CassandraACLMapperTest {
         Barrier barrier = new Barrier(2);
         cassandra.getConf()
             .registerScenario(awaitOn(barrier)
+                .thenExecuteNormally()
                 .times(2)
                 .whenQueryStartsWith("SELECT acl,version FROM acl WHERE id=:id;"));
 
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxDAOTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxDAOTest.java
index 3fc20cf..e2d3b2f 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxDAOTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxDAOTest.java
@@ -30,7 +30,6 @@ import java.util.Optional;
 
 import org.apache.james.backends.cassandra.CassandraCluster;
 import org.apache.james.backends.cassandra.CassandraClusterExtension;
-import org.apache.james.backends.cassandra.Scenario;
 import org.apache.james.backends.cassandra.Scenario.Barrier;
 import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
@@ -136,6 +135,7 @@ class CassandraMailboxDAOTest {
 
         Barrier barrier = new Barrier(2);
         cassandra.getConf().registerScenario(awaitOn(barrier)
+            .thenExecuteNormally()
             .times(2)
             .whenQueryStartsWith("UPDATE mailbox SET"));
 


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


[james-project] 17/27: JAMES-3138 Use concatMap upon quota computation

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 1157a7b3e34fb837fb26d6019426b2d98b6503d8
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Sat Apr 25 11:13:43 2020 +0700

    JAMES-3138 Use concatMap upon quota computation
    
    This limit the parralelism of a background task, limiting its capacity
    to overwhelm James and affect live traffic.
---
 .../apache/james/mailbox/quota/task/RecomputeCurrentQuotasService.java  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mailbox/tools/quota-recompute/src/main/java/org/apache/james/mailbox/quota/task/RecomputeCurrentQuotasService.java b/mailbox/tools/quota-recompute/src/main/java/org/apache/james/mailbox/quota/task/RecomputeCurrentQuotasService.java
index 8508b8e..dbb5078 100644
--- a/mailbox/tools/quota-recompute/src/main/java/org/apache/james/mailbox/quota/task/RecomputeCurrentQuotasService.java
+++ b/mailbox/tools/quota-recompute/src/main/java/org/apache/james/mailbox/quota/task/RecomputeCurrentQuotasService.java
@@ -141,7 +141,7 @@ public class RecomputeCurrentQuotasService {
     public Mono<Task.Result> recomputeCurrentQuotas(Context context) {
         try {
             return Iterators.toFlux(usersRepository.list())
-                .flatMap(username -> recomputeUserCurrentQuotas(context, username))
+                .concatMap(username -> recomputeUserCurrentQuotas(context, username))
                 .reduce(Task.Result.COMPLETED, Task::combine);
         } catch (UsersRepositoryException e) {
             LOGGER.error("Error while accessing users from repository", e);


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


[james-project] 07/27: JAMES-3143 Exposes task in MessageRoutes

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 6ceec974c780faa1895da90c47ac9bd94e90bb15
Author: LanKhuat <kh...@gmail.com>
AuthorDate: Wed Apr 22 11:03:14 2020 +0700

    JAMES-3143 Exposes task in MessageRoutes
---
 .../james/modules/server/MessagesRoutesModule.java |  5 ++-
 .../james/webadmin/routes/MessagesRoutes.java      | 38 +++++++++++++++++++++-
 .../james/webadmin/routes/MessageRoutesTest.java   |  4 ++-
 3 files changed, 44 insertions(+), 3 deletions(-)

diff --git a/server/container/guice/protocols/webadmin-mailbox/src/main/java/org/apache/james/modules/server/MessagesRoutesModule.java b/server/container/guice/protocols/webadmin-mailbox/src/main/java/org/apache/james/modules/server/MessagesRoutesModule.java
index df23a93..5e0ec7a 100644
--- a/server/container/guice/protocols/webadmin-mailbox/src/main/java/org/apache/james/modules/server/MessagesRoutesModule.java
+++ b/server/container/guice/protocols/webadmin-mailbox/src/main/java/org/apache/james/modules/server/MessagesRoutesModule.java
@@ -19,11 +19,14 @@
 
 package org.apache.james.modules.server;
 
+import static org.apache.james.webadmin.tasks.TaskFromRequestRegistry.TaskRegistration;
+
 import org.apache.james.webadmin.Routes;
 import org.apache.james.webadmin.routes.MessagesRoutes;
 
 import com.google.inject.AbstractModule;
 import com.google.inject.multibindings.Multibinder;
+import com.google.inject.name.Names;
 
 public class MessagesRoutesModule extends AbstractModule {
     @Override
@@ -31,6 +34,6 @@ public class MessagesRoutesModule extends AbstractModule {
         Multibinder<Routes> routesMultibinder = Multibinder.newSetBinder(binder(), Routes.class);
         routesMultibinder.addBinding().to(MessagesRoutes.class);
 
-
+        Multibinder.newSetBinder(binder(), TaskRegistration.class, Names.named(MessagesRoutes.ALL_MESSAGES_TASKS));
     }
 }
diff --git a/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/MessagesRoutes.java b/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/MessagesRoutes.java
index 45d235f..da42211 100644
--- a/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/MessagesRoutes.java
+++ b/server/protocols/webadmin/webadmin-mailbox/src/main/java/org/apache/james/webadmin/routes/MessagesRoutes.java
@@ -21,7 +21,11 @@ package org.apache.james.webadmin.routes;
 
 import static org.apache.james.webadmin.routes.MailboxesRoutes.TASK_PARAMETER;
 
+import java.util.Optional;
+import java.util.Set;
+
 import javax.inject.Inject;
+import javax.inject.Named;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
@@ -58,13 +62,18 @@ public class MessagesRoutes implements Routes {
     private final MessageId.Factory messageIdFactory;
     private final MessageIdReIndexer reIndexer;
     private final JsonTransformer jsonTransformer;
+    private final Set<TaskFromRequestRegistry.TaskRegistration> allMessagesTaskRegistration;
+
+    public static final String ALL_MESSAGES_TASKS = "allMessagesTasks";
 
     @Inject
-    MessagesRoutes(TaskManager taskManager, MessageId.Factory messageIdFactory, MessageIdReIndexer reIndexer, JsonTransformer jsonTransformer) {
+    MessagesRoutes(TaskManager taskManager, MessageId.Factory messageIdFactory, MessageIdReIndexer reIndexer, JsonTransformer jsonTransformer,
+                   @Named(ALL_MESSAGES_TASKS) Set<TaskFromRequestRegistry.TaskRegistration> allMessagesTaskRegistration) {
         this.taskManager = taskManager;
         this.messageIdFactory = messageIdFactory;
         this.reIndexer = reIndexer;
         this.jsonTransformer = jsonTransformer;
+        this.allMessagesTaskRegistration = allMessagesTaskRegistration;
     }
 
     @Override
@@ -75,6 +84,8 @@ public class MessagesRoutes implements Routes {
     @Override
     public void define(Service service) {
         service.post(MESSAGE_PATH, reIndexMessage(), jsonTransformer);
+        allMessagesOperations()
+            .ifPresent(route -> service.post(BASE_PATH, route, jsonTransformer));
     }
 
     @POST
@@ -121,4 +132,29 @@ public class MessagesRoutes implements Routes {
                 .haltError();
         }
     }
+
+    @POST
+    @Path("/")
+    @ApiOperation(value = "Operation on messages")
+    @ApiImplicitParams({
+        @ApiImplicitParam(
+            required = true,
+            name = "task",
+            paramType = "query parameter",
+            dataType = "String",
+            defaultValue = "none",
+            example = "?task=SolveInconsistencies",
+            value = "Compulsory. Depends on the tasks handled by the product")
+    })
+    @ApiResponses(value = {
+        @ApiResponse(code = HttpStatus.CREATED_201, message = "Task is created", response = TaskIdDto.class),
+        @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500, message = "Internal server error - Something went bad on the server side."),
+        @ApiResponse(code = HttpStatus.BAD_REQUEST_400, message = "Bad request - details in the returned error message")
+    })
+    private Optional<Route> allMessagesOperations() {
+        return TaskFromRequestRegistry.builder()
+            .parameterName(TASK_PARAMETER)
+            .registrations(allMessagesTaskRegistration)
+            .buildAsRouteOptional(taskManager);
+    }
 }
diff --git a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/MessageRoutesTest.java b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/MessageRoutesTest.java
index 63c1a0a..02be730 100644
--- a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/MessageRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/MessageRoutesTest.java
@@ -55,6 +55,7 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentCaptor;
+import org.testcontainers.shaded.com.google.common.collect.ImmutableSet;
 
 import io.restassured.RestAssured;
 
@@ -83,7 +84,8 @@ class MessageRoutesTest {
                 new MessagesRoutes(taskManager,
                     new InMemoryMessageId.Factory(),
                     new MessageIdReIndexerImpl(reIndexerPerformer),
-                    jsonTransformer))
+                    jsonTransformer,
+                    ImmutableSet.of()))
             .start();
 
         RestAssured.requestSpecification = WebAdminUtils.buildRequestSpecification(webAdminServer).build();


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


[james-project] 02/27: JAMES-3143 Add select all query for MessageId/ImapUid

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 10e1a444e3a62ce506d7f349aa84f1a01344cd32
Author: LanKhuat <kh...@gmail.com>
AuthorDate: Tue Apr 14 10:23:47 2020 +0700

    JAMES-3143 Add select all query for MessageId/ImapUid
---
 .../james/mailbox/cassandra/mail/CassandraMessageIdDAO.java  | 12 ++++++++++++
 .../cassandra/mail/CassandraMessageIdToImapUidDAO.java       | 11 +++++++++++
 2 files changed, 23 insertions(+)

diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAO.java
index a6275e3..c2342b3 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAO.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAO.java
@@ -81,6 +81,7 @@ public class CassandraMessageIdDAO {
     private final PreparedStatement selectUidGte;
     private final PreparedStatement selectUidRange;
     private final PreparedStatement update;
+    private final PreparedStatement listStatement;
 
     @Inject
     public CassandraMessageIdDAO(Session session, CassandraMessageId.Factory messageIdFactory) {
@@ -93,6 +94,7 @@ public class CassandraMessageIdDAO {
         this.selectAllUids = prepareSelectAllUids(session);
         this.selectUidGte = prepareSelectUidGte(session);
         this.selectUidRange = prepareSelectUidRange(session);
+        this.listStatement = prepareList(session);
     }
 
     private PreparedStatement prepareDelete(Session session) {
@@ -146,6 +148,11 @@ public class CassandraMessageIdDAO {
                 .where(eq(MAILBOX_ID, bindMarker(MAILBOX_ID))));
     }
 
+    private PreparedStatement prepareList(Session session) {
+        return session.prepare(select(new String[] {MESSAGE_ID, MAILBOX_ID, IMAP_UID})
+            .from(TABLE_NAME));
+    }
+
     private PreparedStatement prepareSelectUidGte(Session session) {
         return session.prepare(select(FIELDS)
                 .from(TABLE_NAME)
@@ -224,6 +231,11 @@ public class CassandraMessageIdDAO {
             .map(this::fromRowToComposedMessageIdWithFlags);
     }
 
+    public Flux<ComposedMessageIdWithMetaData> retrieveAllMessages() {
+        return cassandraAsyncExecutor.executeRows(listStatement.bind())
+            .map(this::fromRowToComposedMessageIdWithFlags);
+    }
+
     private Flux<Row> retrieveRows(CassandraId mailboxId, MessageRange set) {
         switch (set.getType()) {
         case ALL:
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAO.java
index 0725bcc..83099ba 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAO.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAO.java
@@ -77,6 +77,7 @@ public class CassandraMessageIdToImapUidDAO {
     private final PreparedStatement update;
     private final PreparedStatement selectAll;
     private final PreparedStatement select;
+    private final PreparedStatement listStatement;
 
     @Inject
     public CassandraMessageIdToImapUidDAO(Session session, CassandraMessageId.Factory messageIdFactory) {
@@ -87,6 +88,7 @@ public class CassandraMessageIdToImapUidDAO {
         this.update = prepareUpdate(session);
         this.selectAll = prepareSelectAll(session);
         this.select = prepareSelect(session);
+        this.listStatement = prepareList(session);
     }
 
     private PreparedStatement prepareDelete(Session session) {
@@ -136,6 +138,10 @@ public class CassandraMessageIdToImapUidDAO {
                 .where(eq(MESSAGE_ID, bindMarker(MESSAGE_ID))));
     }
 
+    private PreparedStatement prepareList(Session session) {
+        return session.prepare(select(new String[] {MESSAGE_ID, MAILBOX_ID, IMAP_UID}).from(TABLE_NAME));
+    }
+
     private PreparedStatement prepareSelect(Session session) {
         return session.prepare(select(FIELDS)
                 .from(TABLE_NAME)
@@ -193,6 +199,11 @@ public class CassandraMessageIdToImapUidDAO {
                 .map(this::toComposedMessageIdWithMetadata);
     }
 
+    public Flux<ComposedMessageIdWithMetaData> retrieveAllMessages() {
+        return cassandraAsyncExecutor.executeRows(listStatement.bind())
+            .map(row -> toComposedMessageIdWithMetadata(row));
+    }
+
     private ComposedMessageIdWithMetaData toComposedMessageIdWithMetadata(Row row) {
         return ComposedMessageIdWithMetaData.builder()
                 .composedMessageId(new ComposedMessageId(


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


[james-project] 15/27: JAMES-3138 Task for recomputing current quotas

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 7f5a6944c169e9dd6205a53ca5c1c9e8f259e88c
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Fri Apr 24 11:05:54 2020 +0700

    JAMES-3138 Task for recomputing current quotas
---
 .../apache/james/mailbox/model/QuotaRootTest.java  | 24 ++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/mailbox/api/src/test/java/org/apache/james/mailbox/model/QuotaRootTest.java b/mailbox/api/src/test/java/org/apache/james/mailbox/model/QuotaRootTest.java
index b9b80fb..76dd31e 100644
--- a/mailbox/api/src/test/java/org/apache/james/mailbox/model/QuotaRootTest.java
+++ b/mailbox/api/src/test/java/org/apache/james/mailbox/model/QuotaRootTest.java
@@ -19,6 +19,11 @@
 
 package org.apache.james.mailbox.model;
 
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Optional;
+
+import org.apache.james.core.Domain;
 import org.junit.jupiter.api.Test;
 
 import nl.jqno.equalsverifier.EqualsVerifier;
@@ -28,4 +33,23 @@ class QuotaRootTest {
     void shouldMatchBeanContract() {
         EqualsVerifier.forClass(QuotaRoot.class).verify();
     }
+
+    @Test
+    void asStringShouldReturnValueWhenNoDomain() {
+        String value = "#private&bob";
+        QuotaRoot quotaRoot = QuotaRoot.quotaRoot(value, Optional.empty());
+
+        assertThat(quotaRoot.asString()).isEqualTo(value);
+    }
+
+    @Test
+    void asStringShouldReturnValueWithDomainWhenHasDomain() {
+        String value = "#private&bob";
+        Domain domain = Domain.of("apache.org");
+        QuotaRoot quotaRoot = QuotaRoot.quotaRoot(value, Optional.of(domain));
+
+        String expectedValue = "#private&bob@apache.org";
+
+        assertThat(quotaRoot.asString()).isEqualTo(expectedValue);
+    }
 }
\ 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


[james-project] 21/27: JAMES-3138 VarArg for Cassandra instrumentation scenari registration

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 9266b93a73e9aa2c862f681f3530c51fa37a8024
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Apr 28 07:40:44 2020 +0700

    JAMES-3138 VarArg for Cassandra instrumentation scenari registration
---
 .../java/org/apache/james/backends/cassandra/TestingSession.java    | 4 ++--
 .../org/apache/james/backends/cassandra/TestingSessionTest.java     | 5 ++---
 .../integration/rabbitmq/ConsistencyTasksIntegrationTest.java       | 6 ++----
 3 files changed, 6 insertions(+), 9 deletions(-)

diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/TestingSession.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/TestingSession.java
index cbb7ae7..c8a7a54 100644
--- a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/TestingSession.java
+++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/TestingSession.java
@@ -44,8 +44,8 @@ public class TestingSession implements Session {
         this.scenario = scenario;
     }
 
-    public void registerScenario(Scenario.ExecutionHook hook) {
-        this.scenario = Scenario.combine(hook);
+    public void registerScenario(Scenario.ExecutionHook... hooks) {
+        this.scenario = Scenario.combine(hooks);
     }
 
     @Override
diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/TestingSessionTest.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/TestingSessionTest.java
index 5140e14..6c8dfc5 100644
--- a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/TestingSessionTest.java
+++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/TestingSessionTest.java
@@ -23,7 +23,6 @@ import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
 import static org.apache.james.backends.cassandra.Scenario.Builder.awaitOn;
 import static org.apache.james.backends.cassandra.Scenario.Builder.executeNormally;
 import static org.apache.james.backends.cassandra.Scenario.Builder.fail;
-import static org.apache.james.backends.cassandra.Scenario.combine;
 import static org.apache.james.backends.cassandra.versions.table.CassandraSchemaVersionTable.TABLE_NAME;
 import static org.apache.james.backends.cassandra.versions.table.CassandraSchemaVersionTable.VALUE;
 import static org.assertj.core.api.Assertions.assertThat;
@@ -154,13 +153,13 @@ class TestingSessionTest {
     @Test
     void scenarioShouldDefiningSeveralHooks(CassandraCluster cassandra) {
         cassandra.getConf()
-            .registerScenario(combine(
+            .registerScenario(
                 executeNormally()
                     .times(1)
                     .whenQueryStartsWith("SELECT value FROM schemaVersion;"),
                 fail()
                     .times(1)
-                    .whenQueryStartsWith("SELECT value FROM schemaVersion;")));
+                    .whenQueryStartsWith("SELECT value FROM schemaVersion;"));
 
         SoftAssertions.assertSoftly(softly -> {
             assertThatCode(() -> dao.getCurrentSchemaVersion().block())
diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/ConsistencyTasksIntegrationTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/ConsistencyTasksIntegrationTest.java
index f821ced..bb69945 100644
--- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/ConsistencyTasksIntegrationTest.java
+++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/ConsistencyTasksIntegrationTest.java
@@ -44,7 +44,6 @@ import org.apache.james.DockerElasticSearchExtension;
 import org.apache.james.GuiceJamesServer;
 import org.apache.james.JamesServerBuilder;
 import org.apache.james.JamesServerExtension;
-import org.apache.james.backends.cassandra.Scenario;
 import org.apache.james.backends.cassandra.Scenario.Barrier;
 import org.apache.james.backends.cassandra.TestingSession;
 import org.apache.james.backends.cassandra.init.SessionWithInitializedTablesFactory;
@@ -68,7 +67,6 @@ import org.apache.james.webadmin.routes.CassandraMappingsRoutes;
 import org.apache.james.webadmin.routes.TasksRoutes;
 import org.eclipse.jetty.http.HttpStatus;
 import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.RepeatedTest;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
@@ -272,7 +270,7 @@ class ConsistencyTasksIntegrationTest {
         Barrier barrier2 = new Barrier();
         String updatedQuotaQueryString = "UPDATE currentQuota SET messageCount=messageCount+?,storage=storage+? WHERE quotaRoot=?;";
         server.getProbe(TestingSessionProbe.class)
-            .getTestingSession().registerScenario(Scenario.combine(
+            .getTestingSession().registerScenario(
                 awaitOn(barrier1) // Event bus first execution
                     .thenFail()
                     .times(1)
@@ -280,7 +278,7 @@ class ConsistencyTasksIntegrationTest {
                 awaitOn(barrier2) // scenari for event bus retry
                     .thenFail()
                     .times(1)
-                    .whenQueryStartsWith(updatedQuotaQueryString)));
+                    .whenQueryStartsWith(updatedQuotaQueryString));
 
         probe.appendMessage(BOB.asString(), inbox,
             new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(),


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


[james-project] 14/27: JAMES-3138 Documentation for Recomputing current quotas endpoint

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 251a73d1a7543e98610c3025528dfbe218d6013a
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Tue Apr 14 16:18:23 2020 +0700

    JAMES-3138 Documentation for Recomputing current quotas endpoint
---
 src/site/markdown/server/manage-webadmin.md | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/src/site/markdown/server/manage-webadmin.md b/src/site/markdown/server/manage-webadmin.md
index 7efda46..384d4f6 100644
--- a/src/site/markdown/server/manage-webadmin.md
+++ b/src/site/markdown/server/manage-webadmin.md
@@ -974,6 +974,7 @@ Response codes:
  - [Updating the quota size for a user](#Updating_the_quota_size_for_a_user)
  - [Deleting the quota size for a user](#Deleting_the_quota_size_for_a_user)
  - [Searching user by quota ratio](#Searching_user_by_quota_ratio)
+ - [Recomputing current quotas for users](#Recomputing_current_quotas_for_users)
 
 ### Getting the quota for a user
 
@@ -1226,6 +1227,34 @@ Response codes:
 
  - 200: List of users had successfully been returned.
  - 400: Validation issues with parameters
+ 
+### Recomputing current quotas for users
+
+This task is available on top of Cassandra & JPA products.
+
+```
+curl -XPOST /quota/users?task=RecomputeCurrentQuotas
+```
+
+Will recompute current quotas (count and size) for all users stored in James.
+
+James maintains per quota a projection for current quota count and size. As with any projection, it can 
+go out of sync, leading to inconsistent results being returned to the client.
+
+[More details about endpoints returning a task](#Endpoints_returning_a_task).
+
+The scheduled task will have the following type `recompute-current-quotas` and the following `additionalInformation`:
+
+```
+{
+  "type":"recompute-current-quotas",
+  "processedQuotaRoots": 3,
+  "failedQuotaRoots": ["#private&bob@localhost"]
+}
+```
+
+**WARNING**: this task do not take into account concurrent modifications upon a single current quota recomputation. 
+Rerunning the task will *eventually* provide the consistent result.
 
 ## Administrating quotas by domains
 


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


[james-project] 27/27: JAMES-3153 use reactor.Retry insteaf of reactor-extra version

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 b4de77a1e58708d380f8d7b669fd75f93970125c
Author: Matthieu Baechler <ma...@apache.org>
AuthorDate: Fri Apr 24 12:14:20 2020 +0200

    JAMES-3153 use reactor.Retry insteaf of reactor-extra version
---
 server/blob/blob-objectstorage/pom.xml                |  4 ----
 .../objectstorage/StreamCompatibleBlobPutter.java     | 19 +++++++++----------
 .../blob/objectstorage/aws/AwsS3ObjectStorage.java    | 11 +++++------
 3 files changed, 14 insertions(+), 20 deletions(-)

diff --git a/server/blob/blob-objectstorage/pom.xml b/server/blob/blob-objectstorage/pom.xml
index 4d2f297..5729d35 100644
--- a/server/blob/blob-objectstorage/pom.xml
+++ b/server/blob/blob-objectstorage/pom.xml
@@ -87,10 +87,6 @@
             <artifactId>guava</artifactId>
         </dependency>
         <dependency>
-            <groupId>io.projectreactor.addons</groupId>
-            <artifactId>reactor-extra</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.apache.jclouds.api</groupId>
             <artifactId>openstack-swift</artifactId>
             <version>${jclouds.version}</version>
diff --git a/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/StreamCompatibleBlobPutter.java b/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/StreamCompatibleBlobPutter.java
index 46d1d2e..35da9c8 100644
--- a/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/StreamCompatibleBlobPutter.java
+++ b/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/StreamCompatibleBlobPutter.java
@@ -34,7 +34,7 @@ import org.jclouds.http.HttpResponseException;
 
 import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Schedulers;
-import reactor.retry.Retry;
+import reactor.util.retry.Retry;
 
 public class StreamCompatibleBlobPutter implements BlobPutter {
 
@@ -54,15 +54,14 @@ public class StreamCompatibleBlobPutter implements BlobPutter {
     public Mono<Void> putDirectly(ObjectStorageBucketName bucketName, Blob blob) {
         return Mono.fromRunnable(() -> blobStore.putBlob(bucketName.asString(), blob))
             .publishOn(Schedulers.elastic())
-            .retryWhen(Retry.onlyIf(retryContext -> needToCreateBucket(retryContext.exception(), bucketName))
-                .exponentialBackoff(FIRST_BACK_OFF, FOREVER)
-                .withBackoffScheduler(Schedulers.elastic())
-                .retryMax(MAX_RETRIES)
-                .doOnRetry(retryContext -> blobStore.createContainerInLocation(DEFAULT_LOCATION, bucketName.asString())))
-            .retryWhen(Retry.onlyIf(RetryContext -> isPutMethod(RetryContext.exception()))
-                .withBackoffScheduler(Schedulers.elastic())
-                .exponentialBackoff(FIRST_BACK_OFF, FOREVER)
-                .retryMax(RETRY_ONE_LAST_TIME_ON_CONCURRENT_SAVING))
+            .retryWhen(Retry
+                .backoff(MAX_RETRIES, FIRST_BACK_OFF)
+                .filter(throwable -> needToCreateBucket(throwable, bucketName))
+                .doBeforeRetry(retryContext -> blobStore.createContainerInLocation(DEFAULT_LOCATION, bucketName.asString())))
+            .retryWhen(Retry
+                    .backoff(RETRY_ONE_LAST_TIME_ON_CONCURRENT_SAVING, FIRST_BACK_OFF)
+                    .filter(this::isPutMethod)
+                    .scheduler(Schedulers.elastic()))
             .then();
     }
 
diff --git a/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/aws/AwsS3ObjectStorage.java b/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/aws/AwsS3ObjectStorage.java
index fa58add..8cf22da 100644
--- a/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/aws/AwsS3ObjectStorage.java
+++ b/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/aws/AwsS3ObjectStorage.java
@@ -65,7 +65,7 @@ import com.google.inject.Module;
 
 import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Schedulers;
-import reactor.retry.Retry;
+import reactor.util.retry.Retry;
 
 public class AwsS3ObjectStorage {
 
@@ -180,11 +180,10 @@ public class AwsS3ObjectStorage {
             return Mono.<Void>fromRunnable(puttingAttempt)
                 .publishOn(Schedulers.elastic())
                 .retryWhen(Retry
-                    .<Void>onlyIf(retryContext -> needToCreateBucket(retryContext.exception()))
-                    .exponentialBackoff(FIRST_BACK_OFF, FOREVER)
-                    .withBackoffScheduler(Schedulers.elastic())
-                    .retryMax(MAX_RETRY_ON_EXCEPTION)
-                    .doOnRetry(retryContext -> s3Client.createBucket(bucketName.asString())));
+                    .backoff(MAX_RETRY_ON_EXCEPTION, FIRST_BACK_OFF)
+                    .filter(throwable -> needToCreateBucket(throwable))
+                    .doBeforeRetry(retryContext -> s3Client.createBucket(bucketName.asString()))
+                    .scheduler(Schedulers.elastic()));
         }
 
         private void uploadByFile(ObjectStorageBucketName bucketName, BlobId blobId, File file) throws InterruptedException {


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


[james-project] 04/27: JAMES-3143 Add context & objects describing inconsistencies

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 01cb8d1af106486bf60924ed1dfa622880cfe74f
Author: LanKhuat <kh...@gmail.com>
AuthorDate: Fri Apr 17 01:57:50 2020 +0700

    JAMES-3143 Add context & objects describing inconsistencies
---
 .../task/SolveMessageInconsistenciesService.java   | 446 +++++++++++++++++----
 .../SolveMessageInconsistenciesServiceTest.java    | 265 +++++++++---
 2 files changed, 593 insertions(+), 118 deletions(-)

diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesService.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesService.java
index f67ff73..8518f1e 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesService.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesService.java
@@ -19,7 +19,11 @@
 
 package org.apache.james.mailbox.cassandra.mail.task;
 
+import java.util.Collection;
+import java.util.Objects;
 import java.util.Optional;
+import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.concurrent.atomic.AtomicLong;
 
 import javax.inject.Inject;
 
@@ -27,105 +31,413 @@ import org.apache.james.mailbox.cassandra.ids.CassandraId;
 import org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
 import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdDAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdToImapUidDAO;
+import org.apache.james.mailbox.model.ComposedMessageId;
 import org.apache.james.mailbox.model.ComposedMessageIdWithMetaData;
 import org.apache.james.task.Task;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
 
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
 public class SolveMessageInconsistenciesService {
+
+    @FunctionalInterface
+    interface Inconsistency {
+        Mono<Task.Result> fix(Context context, CassandraMessageIdToImapUidDAO imapUidDAO, CassandraMessageIdDAO messageIdDAO);
+    }
+
+    private static Inconsistency NO_INCONSISTENCY = (context, imapUidDAO, messageIdDAO) -> Mono.just(Task.Result.COMPLETED);
+
+    private static class FailedToRetrieveRecord implements Inconsistency {
+        private final ComposedMessageIdWithMetaData message;
+
+        private FailedToRetrieveRecord(ComposedMessageIdWithMetaData message) {
+            this.message = message;
+        }
+
+        @Override
+        public Mono<Task.Result> fix(Context context, CassandraMessageIdToImapUidDAO imapUidDAO, CassandraMessageIdDAO messageIdDAO) {
+            context.addErrors(message.getComposedMessageId());
+            LOGGER.error("Failed to retrieve record: {}", message.getComposedMessageId());
+            return Mono.just(Task.Result.PARTIAL);
+        }
+    }
+
+    private static class OrphanImapUidEntry implements Inconsistency {
+        private final ComposedMessageIdWithMetaData message;
+
+        private OrphanImapUidEntry(ComposedMessageIdWithMetaData message) {
+            this.message = message;
+        }
+
+        @Override
+        public Mono<Task.Result> fix(Context context, CassandraMessageIdToImapUidDAO imapUidDAO, CassandraMessageIdDAO messageIdDAO) {
+            return messageIdDAO.insert(message)
+                .doOnSuccess(any -> notifySuccess(context))
+                .thenReturn(Task.Result.COMPLETED)
+                .onErrorResume(error -> {
+                    notifyFailure(context);
+                    return Mono.just(Task.Result.PARTIAL);
+                });
+        }
+
+        private void notifyFailure(Context context) {
+            context.addErrors(message.getComposedMessageId());
+            LOGGER.error("Failed to fix inconsistency for orphan message in ImapUid: {}", message.getComposedMessageId());
+        }
+
+        private void notifySuccess(Context context) {
+            LOGGER.info("Inconsistency fixed for orphan message in ImapUid: {}", message.getComposedMessageId());
+            context.incrementAddedMessageIdEntries();
+            context.addFixedInconsistency(message.getComposedMessageId());
+        }
+    }
+
+    private static class OutdatedMessageIdEntry implements Inconsistency {
+        private final ComposedMessageIdWithMetaData messageFromMessageId;
+        private final ComposedMessageIdWithMetaData messageFromImapUid;
+
+        private OutdatedMessageIdEntry(ComposedMessageIdWithMetaData message, ComposedMessageIdWithMetaData messageFromImapUid) {
+            this.messageFromMessageId = message;
+            this.messageFromImapUid = messageFromImapUid;
+        }
+
+        @Override
+        public Mono<Task.Result> fix(Context context, CassandraMessageIdToImapUidDAO imapUidDAO, CassandraMessageIdDAO messageIdDAO) {
+            return messageIdDAO.updateMetadata(messageFromImapUid)
+                .doOnSuccess(any -> notifySuccess(context))
+                .thenReturn(Task.Result.COMPLETED)
+                .onErrorResume(error -> {
+                    notifyFailure(context);
+                    return Mono.just(Task.Result.PARTIAL);
+                });
+        }
+
+        private void notifyFailure(Context context) {
+            context.addErrors(messageFromMessageId.getComposedMessageId());
+            LOGGER.error("Failed to fix inconsistency for outdated message in MessageId: {}", messageFromMessageId.getComposedMessageId());
+        }
+
+        private void notifySuccess(Context context) {
+            LOGGER.info("Inconsistency fixed for outdated message in MessageId: {}", messageFromMessageId.getComposedMessageId());
+            context.incrementUpdatedMessageIdEntries();
+            context.addFixedInconsistency(messageFromMessageId.getComposedMessageId());
+        }
+    }
+
+    private static class OrphanMessageIdEntry implements Inconsistency {
+        private final ComposedMessageIdWithMetaData message;
+
+        private OrphanMessageIdEntry(ComposedMessageIdWithMetaData message) {
+            this.message = message;
+        }
+
+        @Override
+        public Mono<Task.Result> fix(Context context, CassandraMessageIdToImapUidDAO imapUidDAO, CassandraMessageIdDAO messageIdDAO) {
+            return messageIdDAO.delete((CassandraId) message.getComposedMessageId().getMailboxId(), message.getComposedMessageId().getUid())
+                .doOnSuccess(any -> notifySuccess(context))
+                .thenReturn(Task.Result.COMPLETED)
+                .onErrorResume(error -> {
+                    notifyFailure(context);
+                    return Mono.just(Task.Result.PARTIAL);
+                });
+        }
+
+        private void notifyFailure(Context context) {
+            context.addErrors(message.getComposedMessageId());
+            LOGGER.error("Failed to fix inconsistency for orphan message in MessageId: {}", message.getComposedMessageId());
+        }
+
+        private void notifySuccess(Context context) {
+            LOGGER.info("Inconsistency fixed for orphan message in MessageId: {}", message.getComposedMessageId());
+            context.incrementRemovedMessageIdEntries();
+            context.addFixedInconsistency(message.getComposedMessageId());
+        }
+    }
+
+    static class Context {
+        static class Snapshot {
+            public static Builder builder() {
+                return new Builder();
+            }
+
+            static class Builder {
+                private Optional<Long> processedImapUidEntries;
+                private Optional<Long> processedMessageIdEntries;
+                private Optional<Long> addedMessageIdEntries;
+                private Optional<Long> updatedMessageIdEntries;
+                private Optional<Long> removedMessageIdEntries;
+                private ImmutableList.Builder<ComposedMessageId> fixedInconsistencies;
+                private ImmutableList.Builder<ComposedMessageId> errors;
+
+                Builder() {
+                    processedImapUidEntries = Optional.empty();
+                    processedMessageIdEntries = Optional.empty();
+                    addedMessageIdEntries = Optional.empty();
+                    updatedMessageIdEntries = Optional.empty();
+                    removedMessageIdEntries = Optional.empty();
+                    fixedInconsistencies = ImmutableList.builder();
+                    errors = ImmutableList.builder();
+                }
+
+                public Builder processedImapUidEntries(long count) {
+                    processedImapUidEntries = Optional.of(count);
+                    return this;
+                }
+
+                public Builder processedMessageIdEntries(long count) {
+                    processedMessageIdEntries = Optional.of(count);
+                    return this;
+                }
+
+                public Builder addedMessageIdEntries(long count) {
+                    addedMessageIdEntries = Optional.of(count);
+                    return this;
+                }
+
+                public Builder updatedMessageIdEntries(long count) {
+                    updatedMessageIdEntries = Optional.of(count);
+                    return this;
+                }
+
+                public Builder removedMessageIdEntries(long count) {
+                    removedMessageIdEntries = Optional.of(count);
+                    return this;
+                }
+
+                public Builder addFixedInconsistencies(ComposedMessageId composedMessageId) {
+                    fixedInconsistencies.add(composedMessageId);
+                    return this;
+                }
+
+                public Builder errors(ComposedMessageId composedMessageId) {
+                    errors.add(composedMessageId);
+                    return this;
+                }
+
+                public SolveMessageInconsistenciesService.Context.Snapshot build() {
+                    return new SolveMessageInconsistenciesService.Context.Snapshot(
+                        processedImapUidEntries.orElse(0L),
+                        processedMessageIdEntries.orElse(0L),
+                        addedMessageIdEntries.orElse(0L),
+                        updatedMessageIdEntries.orElse(0L),
+                        removedMessageIdEntries.orElse(0L),
+                        fixedInconsistencies.build(),
+                        errors.build());
+                }
+            }
+
+            private final long processedImapUidEntries;
+            private final long processedMessageIdEntries;
+            private final long addedMessageIdEntries;
+            private final long updatedMessageIdEntries;
+            private final long removedMessageIdEntries;
+            private final ImmutableList<ComposedMessageId> fixedInconsistencies;
+            private final ImmutableList<ComposedMessageId> errors;
+
+            private Snapshot(long processedImapUidEntries, long processedMessageIdEntries,
+                             long addedMessageIdEntries, long updatedMessageIdEntries,
+                             long removedMessageIdEntries,
+                             ImmutableList<ComposedMessageId> fixedInconsistencies,
+                             ImmutableList<ComposedMessageId> errors) {
+                this.processedImapUidEntries = processedImapUidEntries;
+                this.processedMessageIdEntries = processedMessageIdEntries;
+                this.addedMessageIdEntries = addedMessageIdEntries;
+                this.updatedMessageIdEntries = updatedMessageIdEntries;
+                this.removedMessageIdEntries = removedMessageIdEntries;
+                this.fixedInconsistencies = fixedInconsistencies;
+                this.errors = errors;
+            }
+
+            public long getProcessedImapUidEntries() {
+                return processedImapUidEntries;
+            }
+
+            public long getProcessedMessageIdEntries() {
+                return processedMessageIdEntries;
+            }
+
+            public long getAddedMessageIdEntries() {
+                return addedMessageIdEntries;
+            }
+
+            public long getUpdatedMessageIdEntries() {
+                return updatedMessageIdEntries;
+            }
+
+            public long getRemovedMessageIdEntries() {
+                return removedMessageIdEntries;
+            }
+
+            public ImmutableList<ComposedMessageId> getFixedInconsistencies() {
+                return fixedInconsistencies;
+            }
+
+            public ImmutableList<ComposedMessageId> getErrors() {
+                return errors;
+            }
+
+            @Override
+            public final boolean equals(Object o) {
+                if (o instanceof Snapshot) {
+                    Snapshot snapshot = (Snapshot) o;
+
+                    return Objects.equals(this.processedImapUidEntries, snapshot.processedImapUidEntries)
+                        && Objects.equals(this.processedMessageIdEntries, snapshot.processedMessageIdEntries)
+                        && Objects.equals(this.addedMessageIdEntries, snapshot.addedMessageIdEntries)
+                        && Objects.equals(this.updatedMessageIdEntries, snapshot.updatedMessageIdEntries)
+                        && Objects.equals(this.removedMessageIdEntries, snapshot.removedMessageIdEntries)
+                        && Objects.equals(this.errors, snapshot.errors)
+                        && Objects.equals(this.fixedInconsistencies, snapshot.fixedInconsistencies);
+                }
+                return false;
+            }
+
+            @Override
+            public final int hashCode() {
+                return Objects.hash(processedImapUidEntries, processedMessageIdEntries, addedMessageIdEntries, updatedMessageIdEntries, removedMessageIdEntries, fixedInconsistencies, errors);
+            }
+
+            @Override
+            public String toString() {
+                return MoreObjects.toStringHelper(this)
+                    .add("processedImapUidEntries", processedImapUidEntries)
+                    .add("processedMessageIdEntries", processedMessageIdEntries)
+                    .add("addedMessageIdEntries", addedMessageIdEntries)
+                    .add("updatedMessageIdEntries", updatedMessageIdEntries)
+                    .add("removedMessageIdEntries", removedMessageIdEntries)
+                    .add("fixedInconsistencies", fixedInconsistencies)
+                    .add("errors", errors)
+                    .toString();
+            }
+        }
+
+        private final AtomicLong processedImapUidEntries;
+        private final AtomicLong processedMessageIdEntries;
+        private final AtomicLong addedMessageIdEntries;
+        private final AtomicLong updatedMessageIdEntries;
+        private final AtomicLong removedMessageIdEntries;
+        private final ConcurrentLinkedDeque<ComposedMessageId> fixedInconsistencies;
+        private final ConcurrentLinkedDeque<ComposedMessageId> errors;
+
+        Context() {
+            this(new AtomicLong(), new AtomicLong(), new AtomicLong(), new AtomicLong(), new AtomicLong(), ImmutableList.of(), ImmutableList.of());
+        }
+
+        private Context(AtomicLong processedImapUidEntries, AtomicLong processedMessageIdEntries, AtomicLong addedMessageIdEntries,
+                        AtomicLong updatedMessageIdEntries, AtomicLong removedMessageIdEntries, Collection<ComposedMessageId> fixedInconsistencies,
+                        Collection<ComposedMessageId> errors) {
+            this.processedImapUidEntries = processedImapUidEntries;
+            this.processedMessageIdEntries = processedMessageIdEntries;
+            this.addedMessageIdEntries = addedMessageIdEntries;
+            this.updatedMessageIdEntries = updatedMessageIdEntries;
+            this.removedMessageIdEntries = removedMessageIdEntries;
+            this.fixedInconsistencies = new ConcurrentLinkedDeque<>(fixedInconsistencies);
+            this.errors = new ConcurrentLinkedDeque<>(errors);
+        }
+
+        void incrementProcessedImapUidEntries() {
+            processedImapUidEntries.incrementAndGet();
+        }
+
+        void incrementMessageIdEntries() {
+            processedMessageIdEntries.incrementAndGet();
+        }
+
+        void incrementAddedMessageIdEntries() {
+            addedMessageIdEntries.incrementAndGet();
+        }
+
+        void incrementUpdatedMessageIdEntries() {
+            updatedMessageIdEntries.incrementAndGet();
+        }
+
+        void incrementRemovedMessageIdEntries() {
+            removedMessageIdEntries.incrementAndGet();
+        }
+
+        void addFixedInconsistency(ComposedMessageId messageId) {
+            fixedInconsistencies.add(messageId);
+        }
+
+        void addErrors(ComposedMessageId messageId) {
+            errors.add(messageId);
+        }
+
+        Snapshot snapshot() {
+            return new Snapshot(
+                processedImapUidEntries.get(),
+                processedMessageIdEntries.get(),
+                addedMessageIdEntries.get(),
+                updatedMessageIdEntries.get(),
+                removedMessageIdEntries.get(),
+                ImmutableList.copyOf(fixedInconsistencies),
+                ImmutableList.copyOf(errors));
+        }
+    }
+
     public static final Logger LOGGER = LoggerFactory.getLogger(SolveMessageInconsistenciesService.class);
 
-    private final CassandraMessageIdToImapUidDAO idToImapUidDAO;
+    private final CassandraMessageIdToImapUidDAO messageIdToImapUidDAO;
     private final CassandraMessageIdDAO messageIdDAO;
 
     @Inject
-    SolveMessageInconsistenciesService(CassandraMessageIdToImapUidDAO idToImapUidDAO, CassandraMessageIdDAO messageIdDAO) {
-        this.idToImapUidDAO = idToImapUidDAO;
+    SolveMessageInconsistenciesService(CassandraMessageIdToImapUidDAO messageIdToImapUidDAO, CassandraMessageIdDAO messageIdDAO) {
+        this.messageIdToImapUidDAO = messageIdToImapUidDAO;
         this.messageIdDAO = messageIdDAO;
     }
 
-    Mono<Task.Result> fixMessageInconsistencies() {
+    Mono<Task.Result> fixMessageInconsistencies(Context context) {
         return Flux.concat(
-            fixMessageIdInconsistencies(),
-            fixImapUidInconsistencies())
+            fixInconsistenciesInMessageId(context),
+            fixInconsistenciesInImapUid(context))
             .reduce(Task.Result.COMPLETED, Task::combine);
     }
 
-    private Mono<Task.Result> fixMessageIdInconsistencies() {
-        return idToImapUidDAO.retrieveAllMessages()
-            .concatMap(this::fetchAndFixMessageId)
-            .reduce(Task.Result.COMPLETED, Task::combine);
+    private Flux<Task.Result> fixInconsistenciesInImapUid(Context context) {
+        return messageIdToImapUidDAO.retrieveAllMessages()
+            .doOnNext(any -> context.incrementProcessedImapUidEntries())
+            .concatMap(this::detectInconsistencyInImapUid)
+            .concatMap(inconsistency -> inconsistency.fix(context, messageIdToImapUidDAO, messageIdDAO));
     }
 
-    private Mono<Task.Result> fetchAndFixMessageId(ComposedMessageIdWithMetaData message) {
-        return idToImapUidDAO.retrieve((CassandraMessageId) message.getComposedMessageId().getMessageId(), Optional.of((CassandraId) message.getComposedMessageId().getMailboxId()))
-            .single()
-            .flatMap(upToDateMessage -> messageIdDAO.retrieve((CassandraId) upToDateMessage.getComposedMessageId().getMailboxId(), upToDateMessage.getComposedMessageId().getUid())
-                .flatMap(Mono::justOrEmpty)
-                .flatMap(fetchedFromMessageId -> fixWhenMessageFoundInMessageId(upToDateMessage, fetchedFromMessageId)))
-            .switchIfEmpty(fixWhenMessageNotFoundInMessageId(message));
+    private Mono<Inconsistency> detectInconsistencyInImapUid(ComposedMessageIdWithMetaData message) {
+        return messageIdToImapUidDAO.retrieve((CassandraMessageId) message.getComposedMessageId().getMessageId(), Optional.of((CassandraId) message.getComposedMessageId().getMailboxId()))
+            .next()
+            .flatMap(this::compareWithMessageIdRecord)
+            .onErrorResume(error -> Mono.just(new FailedToRetrieveRecord(message)));
     }
 
-    private Mono<Task.Result> fixWhenMessageFoundInMessageId(ComposedMessageIdWithMetaData messageFromImapUid, ComposedMessageIdWithMetaData messageFromMessageId) {
-        return Mono.fromCallable(() -> messageFromImapUid.equals(messageFromMessageId))
-            .flatMap(isEqual -> {
-                if (isEqual) {
-                    return Mono.just(Task.Result.COMPLETED);
+    private Mono<Inconsistency> compareWithMessageIdRecord(ComposedMessageIdWithMetaData upToDateMessageFromImapUid) {
+        return messageIdDAO.retrieve((CassandraId) upToDateMessageFromImapUid.getComposedMessageId().getMailboxId(), upToDateMessageFromImapUid.getComposedMessageId().getUid())
+            .flatMap(Mono::justOrEmpty)
+            .map(messageIdRecord -> {
+                if (messageIdRecord.equals(upToDateMessageFromImapUid)) {
+                    return NO_INCONSISTENCY;
                 }
-
-                return messageIdDAO.updateMetadata(messageFromImapUid)
-                    .then(Mono.just(Task.Result.COMPLETED))
-                    .onErrorResume(error -> {
-                        LOGGER.error("Error when fixing inconsistency for message: {}", messageFromImapUid, error);
-                        return Mono.just(Task.Result.PARTIAL);
-                    });
-            });
-    }
-
-    private Mono<Task.Result> fixWhenMessageNotFoundInMessageId(ComposedMessageIdWithMetaData message) {
-        return messageIdDAO.insert(message)
-            .then(Mono.just(Task.Result.COMPLETED))
-            .onErrorResume(error -> {
-                LOGGER.error("Error when fixing inconsistency for message: {}", message, error);
-                return Mono.just(Task.Result.PARTIAL);
-            });
+                return new OutdatedMessageIdEntry(messageIdRecord, upToDateMessageFromImapUid);
+            })
+            .switchIfEmpty(Mono.just(new OrphanImapUidEntry(upToDateMessageFromImapUid)));
     }
 
-    @VisibleForTesting
-    Mono<Task.Result> fixImapUidInconsistencies() {
+    private Flux<Task.Result> fixInconsistenciesInMessageId(Context context) {
         return messageIdDAO.retrieveAllMessages()
-            .concatMap(message -> process(message))
-            .reduce(Task.Result.COMPLETED, Task::combine);
+            .doOnNext(any -> context.incrementMessageIdEntries())
+            .concatMap(this::detectInconsistencyInMessageId)
+            .concatMap(inconsistency -> inconsistency.fix(context, messageIdToImapUidDAO, messageIdDAO));
     }
 
-    private Mono<Task.Result> process(ComposedMessageIdWithMetaData message) {
+    private Mono<Inconsistency> detectInconsistencyInMessageId(ComposedMessageIdWithMetaData message) {
         return messageIdDAO.retrieve((CassandraId) message.getComposedMessageId().getMailboxId(), message.getComposedMessageId().getUid())
             .flatMap(Mono::justOrEmpty)
-            .flatMap(this::fixWhenMessageFound)
-            .switchIfEmpty(Mono.just(Task.Result.COMPLETED));
-    }
-
-    private Mono<Task.Result> fixWhenMessageFound(ComposedMessageIdWithMetaData message) {
-        return idToImapUidDAO.retrieve((CassandraMessageId) message.getComposedMessageId().getMessageId(), Optional.of((CassandraId) message.getComposedMessageId().getMailboxId()))
-            .flatMap(uidRecord -> {
-                if (uidRecord.equals(message)) {
-                    return Mono.just(Task.Result.COMPLETED);
-                }
-
-                return messageIdDAO.updateMetadata(uidRecord)
-                    .then(Mono.just(Task.Result.COMPLETED));
-            })
-            .switchIfEmpty(messageIdDAO.delete((CassandraId) message.getComposedMessageId().getMailboxId(), message.getComposedMessageId().getUid())
-                .then(Mono.just(Task.Result.COMPLETED)))
-            .single()
-            .onErrorResume(error -> {
-                LOGGER.error("Error when fixing inconsistency for message {}", message, error);
-                return Mono.just(Task.Result.PARTIAL);
-            });
+            .flatMap(upToDateMessage -> messageIdToImapUidDAO.retrieve((CassandraMessageId) message.getComposedMessageId().getMessageId(), Optional.of((CassandraId) message.getComposedMessageId().getMailboxId()))
+                .map(uidRecord -> NO_INCONSISTENCY)
+                .switchIfEmpty(Mono.just(new OrphanMessageIdEntry(message)))
+                .next())
+            .onErrorResume(error -> Mono.just(new FailedToRetrieveRecord(message)));
     }
-}
+}
\ No newline at end of file
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesServiceTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesServiceTest.java
index 8bb845b..06b7054 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesServiceTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesServiceTest.java
@@ -21,7 +21,6 @@ package org.apache.james.mailbox.cassandra.mail.task;
 
 import static org.apache.james.backends.cassandra.Scenario.Builder.fail;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.doReturn;
 
 import java.util.Optional;
 
@@ -37,6 +36,7 @@ import org.apache.james.mailbox.cassandra.ids.CassandraId;
 import org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
 import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdDAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdToImapUidDAO;
+import org.apache.james.mailbox.cassandra.mail.task.SolveMessageInconsistenciesService.Context;
 import org.apache.james.mailbox.cassandra.modules.CassandraMessageModule;
 import org.apache.james.mailbox.model.ComposedMessageId;
 import org.apache.james.mailbox.model.ComposedMessageIdWithMetaData;
@@ -47,8 +47,6 @@ import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
-import reactor.core.publisher.Mono;
-
 public class SolveMessageInconsistenciesServiceTest {
 
     private static final CassandraId MAILBOX_ID = CassandraId.timeBased();
@@ -102,7 +100,7 @@ public class SolveMessageInconsistenciesServiceTest {
 
     @Test
     void fixMessageInconsistenciesShouldReturnCompletedWhenNoData() {
-        assertThat(testee.fixMessageInconsistencies().block())
+        assertThat(testee.fixMessageInconsistencies(new Context()).block())
             .isEqualTo(Task.Result.COMPLETED);
     }
 
@@ -111,13 +109,13 @@ public class SolveMessageInconsistenciesServiceTest {
         imapUidDAO.insert(MESSAGE_1).block();
         messageIdDAO.insert(MESSAGE_1).block();
 
-        assertThat(testee.fixMessageInconsistencies().block())
+        assertThat(testee.fixMessageInconsistencies(new Context()).block())
             .isEqualTo(Task.Result.COMPLETED);
     }
 
     @Test
     void fixMailboxInconsistenciesShouldNotAlterStateWhenEmpty() {
-        testee.fixMessageInconsistencies().block();
+        testee.fixMessageInconsistencies(new Context()).block();
 
         SoftAssertions.assertSoftly(softly -> {
             softly.assertThat(imapUidDAO.retrieveAllMessages().collectList().block()).isEmpty();
@@ -130,7 +128,7 @@ public class SolveMessageInconsistenciesServiceTest {
         imapUidDAO.insert(MESSAGE_1).block();
         messageIdDAO.insert(MESSAGE_1).block();
 
-        testee.fixMessageInconsistencies().block();
+        testee.fixMessageInconsistencies(new Context()).block();
 
         SoftAssertions.assertSoftly(softly -> {
             softly.assertThat(imapUidDAO.retrieveAllMessages().collectList().block())
@@ -147,7 +145,7 @@ public class SolveMessageInconsistenciesServiceTest {
         void fixMessageInconsistenciesShouldReturnCompletedWhenInconsistentData() {
             imapUidDAO.insert(MESSAGE_1).block();
 
-            assertThat(testee.fixMessageInconsistencies().block())
+            assertThat(testee.fixMessageInconsistencies(new Context()).block())
                 .isEqualTo(Task.Result.COMPLETED);
         }
 
@@ -155,7 +153,7 @@ public class SolveMessageInconsistenciesServiceTest {
         void fixMessageInconsistenciesShouldResolveInconsistentData() {
             imapUidDAO.insert(MESSAGE_1).block();
 
-            testee.fixMessageInconsistencies().block();
+            testee.fixMessageInconsistencies(new Context()).block();
 
             SoftAssertions.assertSoftly(softly -> {
                 softly.assertThat(imapUidDAO.retrieve(MESSAGE_ID_1, Optional.of(MAILBOX_ID)).collectList().block())
@@ -170,7 +168,7 @@ public class SolveMessageInconsistenciesServiceTest {
             imapUidDAO.insert(MESSAGE_1).block();
             messageIdDAO.insert(MESSAGE_1_WITH_SEEN_FLAG).block();
 
-            assertThat(testee.fixMessageInconsistencies().block())
+            assertThat(testee.fixMessageInconsistencies(new Context()).block())
                 .isEqualTo(Task.Result.COMPLETED);
         }
 
@@ -179,7 +177,7 @@ public class SolveMessageInconsistenciesServiceTest {
             imapUidDAO.insert(MESSAGE_1).block();
             messageIdDAO.insert(MESSAGE_1_WITH_SEEN_FLAG).block();
 
-            testee.fixMessageInconsistencies().block();
+            testee.fixMessageInconsistencies(new Context()).block();
 
             SoftAssertions.assertSoftly(softly -> {
                 softly.assertThat(imapUidDAO.retrieve(MESSAGE_ID_1, Optional.of(MAILBOX_ID)).collectList().block())
@@ -194,7 +192,7 @@ public class SolveMessageInconsistenciesServiceTest {
             imapUidDAO.insert(MESSAGE_1).block();
             messageIdDAO.insert(MESSAGE_1_WITH_MOD_SEQ_2).block();
 
-            assertThat(testee.fixMessageInconsistencies().block())
+            assertThat(testee.fixMessageInconsistencies(new Context()).block())
                 .isEqualTo(Task.Result.COMPLETED);
         }
 
@@ -203,7 +201,7 @@ public class SolveMessageInconsistenciesServiceTest {
             imapUidDAO.insert(MESSAGE_1).block();
             messageIdDAO.insert(MESSAGE_1_WITH_MOD_SEQ_2).block();
 
-            testee.fixMessageInconsistencies().block();
+            testee.fixMessageInconsistencies(new Context()).block();
 
             SoftAssertions.assertSoftly(softly -> {
                 softly.assertThat(imapUidDAO.retrieve(MESSAGE_ID_1, Optional.of(MAILBOX_ID)).collectList().block())
@@ -224,7 +222,7 @@ public class SolveMessageInconsistenciesServiceTest {
                         .forever()
                         .whenQueryStartsWith("INSERT INTO messageIdTable (mailboxId,uid,modSeq,messageId,flagAnswered,flagDeleted,flagDraft,flagFlagged,flagRecent,flagSeen,flagUser,userFlags) VALUES (:mailboxId,:uid,:modSeq,:messageId,:flagAnswered,:flagDeleted,:flagDraft,:flagFlagged,:flagRecent,:flagSeen,:flagUser,:userFlags)"));
 
-                assertThat(testee.fixMessageInconsistencies().block())
+                assertThat(testee.fixMessageInconsistencies(new Context()).block())
                     .isEqualTo(Task.Result.PARTIAL);
             }
 
@@ -238,7 +236,7 @@ public class SolveMessageInconsistenciesServiceTest {
                         .times(1)
                         .whenQueryStartsWith("INSERT INTO messageIdTable (mailboxId,uid,modSeq,messageId,flagAnswered,flagDeleted,flagDraft,flagFlagged,flagRecent,flagSeen,flagUser,userFlags) VALUES (:mailboxId,:uid,:modSeq,:messageId,:flagAnswered,:flagDeleted,:flagDraft,:flagFlagged,:flagRecent,:flagSeen,:flagUser,:userFlags)"));
 
-                assertThat(testee.fixMessageInconsistencies().block())
+                assertThat(testee.fixMessageInconsistencies(new Context()).block())
                     .isEqualTo(Task.Result.PARTIAL);
             }
 
@@ -252,7 +250,7 @@ public class SolveMessageInconsistenciesServiceTest {
                         .times(1)
                         .whenQueryStartsWith("INSERT INTO messageIdTable (mailboxId,uid,modSeq,messageId,flagAnswered,flagDeleted,flagDraft,flagFlagged,flagRecent,flagSeen,flagUser,userFlags) VALUES (:mailboxId,:uid,:modSeq,d2bee791-7e63-11ea-883c-95b84008f979,:flagAnswered,:flagDeleted,:flagDraft,:flagFlagged,:flagRecent,:flagSeen,:flagUser,:userFlags)"));
 
-                testee.fixMessageInconsistencies().block();
+                testee.fixMessageInconsistencies(new Context()).block();
 
                 SoftAssertions.assertSoftly(softly -> {
                     softly.assertThat(imapUidDAO.retrieve(MESSAGE_ID_2, Optional.of(MAILBOX_ID)).collectList().block())
@@ -261,6 +259,46 @@ public class SolveMessageInconsistenciesServiceTest {
                         .isEqualTo(MESSAGE_2);
                 });
             }
+
+            @Test
+            void fixMailboxInconsistenciesShouldUpdateContextWhenFailedToRetrieveImapUidRecord(CassandraCluster cassandra) {
+                Context context = new Context();
+
+                imapUidDAO.insert(MESSAGE_1).block();
+
+                cassandra.getConf()
+                    .registerScenario(fail()
+                        .times(1)
+                        .whenQueryStartsWith("SELECT messageId,mailboxId,uid,modSeq,flagAnswered,flagDeleted,flagDraft,flagFlagged,flagRecent,flagSeen,flagUser,userFlags FROM imapUidTable WHERE messageId=:messageId AND mailboxId=:mailboxId"));
+
+                testee.fixMessageInconsistencies(context).block();
+
+                assertThat(context.snapshot())
+                    .isEqualTo(Context.Snapshot.builder()
+                        .processedImapUidEntries(1)
+                        .errors(MESSAGE_1.getComposedMessageId())
+                        .build());
+            }
+
+            @Test
+            void fixMailboxInconsistenciesShouldUpdateContextWhenFailedToRetrieveMessageIdRecord(CassandraCluster cassandra) {
+                Context context = new Context();
+
+                imapUidDAO.insert(MESSAGE_1).block();
+
+                cassandra.getConf()
+                    .registerScenario(fail()
+                        .times(1)
+                        .whenQueryStartsWith("SELECT messageId,mailboxId,uid,modSeq,flagAnswered,flagDeleted,flagDraft,flagFlagged,flagRecent,flagSeen,flagUser,userFlags FROM messageIdTable WHERE mailboxId=:mailboxId AND uid=:uid"));
+
+                testee.fixMessageInconsistencies(context).block();
+
+                assertThat(context.snapshot())
+                    .isEqualTo(Context.Snapshot.builder()
+                        .processedImapUidEntries(1)
+                        .errors(MESSAGE_1.getComposedMessageId())
+                        .build());
+            }
         }
     }
 
@@ -271,7 +309,7 @@ public class SolveMessageInconsistenciesServiceTest {
         void fixMessageInconsistenciesShouldReturnCompletedWhenInconsistentData() {
             messageIdDAO.insert(MESSAGE_1).block();
 
-            assertThat(testee.fixMessageInconsistencies().block())
+            assertThat(testee.fixMessageInconsistencies(new Context()).block())
                 .isEqualTo(Task.Result.COMPLETED);
         }
 
@@ -279,7 +317,7 @@ public class SolveMessageInconsistenciesServiceTest {
         void fixMessageInconsistenciesShouldResolveInconsistentData() {
             messageIdDAO.insert(MESSAGE_1).block();
 
-            testee.fixMessageInconsistencies().block();
+            testee.fixMessageInconsistencies(new Context()).block();
 
             SoftAssertions.assertSoftly(softly -> {
                 softly.assertThat(imapUidDAO.retrieveAllMessages().collectList().block())
@@ -296,7 +334,7 @@ public class SolveMessageInconsistenciesServiceTest {
 
             imapUidDAO.insert(MESSAGE_1).block();
 
-            assertThat(testee.fixMessageInconsistencies().block())
+            assertThat(testee.fixMessageInconsistencies(new Context()).block())
                 .isEqualTo(Task.Result.COMPLETED);
         }
 
@@ -307,35 +345,7 @@ public class SolveMessageInconsistenciesServiceTest {
 
             imapUidDAO.insert(MESSAGE_1).block();
 
-            testee.fixMessageInconsistencies().block();
-
-            SoftAssertions.assertSoftly(softly -> {
-                softly.assertThat(imapUidDAO.retrieveAllMessages().collectList().block())
-                    .containsExactly(MESSAGE_1);
-                softly.assertThat(messageIdDAO.retrieveAllMessages().collectList().block())
-                    .containsExactly(MESSAGE_1);
-            });
-        }
-
-        @Test
-        void fixImapUidInconsistenciesShouldCompleteWhenInconsistent() {
-            messageIdDAO.insert(MESSAGE_1_WITH_MOD_SEQ_2).block();
-
-            imapUidDAO.insert(MESSAGE_1).block();
-
-            testee.fixMessageInconsistencies().block();
-
-            assertThat(testee.fixImapUidInconsistencies().block())
-                .isEqualTo(Task.Result.COMPLETED);
-        }
-
-        @Test
-        void fixImapUidInconsistenciesShouldResolveInconsistent() {
-            messageIdDAO.insert(MESSAGE_1_WITH_MOD_SEQ_2).block();
-
-            imapUidDAO.insert(MESSAGE_1).block();
-
-            testee.fixMessageInconsistencies().block();
+            testee.fixMessageInconsistencies(new Context()).block();
 
             SoftAssertions.assertSoftly(softly -> {
                 softly.assertThat(imapUidDAO.retrieveAllMessages().collectList().block())
@@ -356,7 +366,7 @@ public class SolveMessageInconsistenciesServiceTest {
                         .forever()
                         .whenQueryStartsWith("DELETE FROM messageIdTable WHERE mailboxId=:mailboxId AND uid=:uid"));
 
-                assertThat(testee.fixMessageInconsistencies().block())
+                assertThat(testee.fixMessageInconsistencies(new Context()).block())
                     .isEqualTo(Task.Result.PARTIAL);
             }
 
@@ -370,7 +380,7 @@ public class SolveMessageInconsistenciesServiceTest {
                         .times(1)
                         .whenQueryStartsWith("DELETE FROM messageIdTable WHERE mailboxId=:mailboxId AND uid=:uid"));
 
-                assertThat(testee.fixMessageInconsistencies().block())
+                assertThat(testee.fixMessageInconsistencies(new Context()).block())
                     .isEqualTo(Task.Result.PARTIAL);
             }
 
@@ -384,7 +394,7 @@ public class SolveMessageInconsistenciesServiceTest {
                         .times(1)
                         .whenQueryStartsWith("DELETE FROM messageIdTable WHERE mailboxId=:mailboxId AND uid=:uid;"));
 
-                testee.fixMessageInconsistencies().block();
+                testee.fixMessageInconsistencies(new Context()).block();
 
                 SoftAssertions.assertSoftly(softly -> {
                     softly.assertThat(imapUidDAO.retrieveAllMessages().collectList().block())
@@ -393,6 +403,159 @@ public class SolveMessageInconsistenciesServiceTest {
                         .containsExactly(MESSAGE_1);
                 });
             }
+
+            @Test
+            void fixMailboxInconsistenciesShouldUpdateContextWhenFailedToRetrieveMessageIdRecord(CassandraCluster cassandra) {
+                Context context = new Context();
+
+                messageIdDAO.insert(MESSAGE_1).block();
+
+                cassandra.getConf()
+                    .registerScenario(fail()
+                        .times(1)
+                        .whenQueryStartsWith("SELECT messageId,mailboxId,uid,modSeq,flagAnswered,flagDeleted,flagDraft,flagFlagged,flagRecent,flagSeen,flagUser,userFlags FROM messageIdTable WHERE mailboxId=:mailboxId AND uid=:uid"));
+
+                testee.fixMessageInconsistencies(context).block();
+
+                assertThat(context.snapshot())
+                    .isEqualTo(Context.Snapshot.builder()
+                        .processedMessageIdEntries(1)
+                        .errors(MESSAGE_1.getComposedMessageId())
+                        .build());
+            }
+
+            @Test
+            void fixMailboxInconsistenciesShouldUpdateContextWhenFailedToRetrieveImapUidRecord(CassandraCluster cassandra) {
+                Context context = new Context();
+
+                messageIdDAO.insert(MESSAGE_1).block();
+
+                cassandra.getConf()
+                    .registerScenario(fail()
+                        .times(1)
+                        .whenQueryStartsWith("SELECT messageId,mailboxId,uid,modSeq,flagAnswered,flagDeleted,flagDraft,flagFlagged,flagRecent,flagSeen,flagUser,userFlags FROM imapUidTable WHERE messageId=:messageId AND mailboxId=:mailboxId"));
+
+                testee.fixMessageInconsistencies(context).block();
+
+                assertThat(context.snapshot())
+                    .isEqualTo(Context.Snapshot.builder()
+                        .processedMessageIdEntries(1)
+                        .errors(MESSAGE_1.getComposedMessageId())
+                        .build());
+            }
         }
     }
+
+    @Test
+    void fixMailboxInconsistenciesShouldNotUpdateContextWhenNoData() {
+        Context context = new Context();
+
+        testee.fixMessageInconsistencies(context).block();
+
+        assertThat(context.snapshot()).isEqualToComparingFieldByFieldRecursively(new Context().snapshot());
+    }
+
+    @Test
+    void fixMessageInconsistenciesShouldUpdateContextWhenConsistentData() {
+        Context context = new Context();
+
+        imapUidDAO.insert(MESSAGE_1).block();
+        messageIdDAO.insert(MESSAGE_1).block();
+
+        testee.fixMessageInconsistencies(context).block();
+
+        assertThat(context.snapshot())
+            .isEqualTo(Context.Snapshot.builder()
+                .processedImapUidEntries(1)
+                .processedMessageIdEntries(1)
+                .build());
+    }
+
+    @Test
+    void fixMessageInconsistenciesShouldUpdateContextWhenOrphanImapUidMessage() {
+        Context context = new Context();
+
+        imapUidDAO.insert(MESSAGE_1).block();
+
+        testee.fixMessageInconsistencies(context).block();
+
+        assertThat(context.snapshot())
+            .isEqualTo(Context.Snapshot.builder()
+                .processedImapUidEntries(1)
+                .addedMessageIdEntries(1)
+                .addFixedInconsistencies(MESSAGE_1.getComposedMessageId())
+                .build());
+    }
+
+    @Test
+    void fixMailboxInconsistenciesShouldUpdateContextWhenInconsistentModSeq() {
+        Context context = new Context();
+
+        imapUidDAO.insert(MESSAGE_1).block();
+        messageIdDAO.insert(MESSAGE_1_WITH_MOD_SEQ_2).block();
+
+        testee.fixMessageInconsistencies(context).block();
+
+        assertThat(context.snapshot())
+            .isEqualTo(Context.Snapshot.builder()
+                .processedImapUidEntries(1)
+                .processedMessageIdEntries(1)
+                .updatedMessageIdEntries(1)
+                .addFixedInconsistencies(MESSAGE_1.getComposedMessageId())
+                .build());
+    }
+
+    @Test
+    void fixMailboxInconsistenciesShouldUpdateContextWhenInconsistentFlags() {
+        Context context = new Context();
+
+        imapUidDAO.insert(MESSAGE_1).block();
+        messageIdDAO.insert(MESSAGE_1_WITH_SEEN_FLAG).block();
+
+        testee.fixMessageInconsistencies(context).block();
+
+        assertThat(context.snapshot())
+            .isEqualTo(Context.Snapshot.builder()
+                .processedImapUidEntries(1)
+                .processedMessageIdEntries(1)
+                .updatedMessageIdEntries(1)
+                .addFixedInconsistencies(MESSAGE_1.getComposedMessageId())
+                .build());
+    }
+
+    @Test
+    void fixMailboxInconsistenciesShouldUpdateContextWhenOrphanMessageIdMessage() {
+        Context context = new Context();
+
+        messageIdDAO.insert(MESSAGE_1).block();
+
+        testee.fixMessageInconsistencies(context).block();
+
+        assertThat(context.snapshot())
+            .isEqualTo(Context.Snapshot.builder()
+                .processedMessageIdEntries(1)
+                .removedMessageIdEntries(1)
+                .addFixedInconsistencies(MESSAGE_1.getComposedMessageId())
+                .build());
+    }
+
+    @Test
+    void fixMailboxInconsistenciesShouldUpdateContextWhenDeleteError(CassandraCluster cassandra) {
+        Context context = new Context();
+
+        messageIdDAO.insert(MESSAGE_1).block();
+
+        cassandra.getConf()
+            .registerScenario(fail()
+                .times(1)
+                .whenQueryStartsWith("DELETE FROM messageIdTable WHERE mailboxId=:mailboxId AND uid=:uid;"));
+
+        testee.fixMessageInconsistencies(context).block();
+
+        assertThat(context.snapshot())
+            .isEqualTo(Context.Snapshot.builder()
+                .processedMessageIdEntries(1)
+                .errors(MESSAGE_1.getComposedMessageId())
+                .build());
+    }
 }


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


[james-project] 23/27: JAMES-3065 remove remaining usages of Reactor types in public api

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 12a5987235f60cbbe24c42a1e1411f12105ed441
Author: Matthieu Baechler <ma...@apache.org>
AuthorDate: Thu Apr 23 12:13:12 2020 +0200

    JAMES-3065 remove remaining usages of Reactor types in public api
---
 .../cassandra/utils/CassandraHealthCheckTest.java  |  8 +++---
 .../es/ElasticSearchHealthCheckConnectionTest.java |  4 +--
 .../backends/rabbitmq/RabbitMQHealthCheckTest.java | 28 ++++++++++----------
 core/pom.xml                                       |  4 +++
 .../apache/james/core/healthcheck/HealthCheck.java | 10 +-------
 mailbox/api/pom.xml                                |  4 +++
 .../EventDeadLettersHealthCheckContract.java       | 30 +++++++++++-----------
 metrics/metrics-tests/pom.xml                      |  4 +++
 .../apache/james/PeriodicalHealthChecksTest.java   |  2 +-
 server/data/data-jmap/pom.xml                      |  4 +++
 .../MessageFastViewProjectionHealthCheck.java      | 20 +++++++++------
 .../MessageFastViewProjectionHealthCheckTest.java  | 26 +++++++++----------
 .../james/jpa/healthcheck/JPAHealthCheck.java      | 24 +++++++++--------
 .../james/jpa/healthcheck/JPAHealthCheckTest.java  |  4 +--
 .../james/webadmin/routes/HealthCheckRoutes.java   | 17 ++++++------
 .../webadmin/routes/HealthCheckRoutesTest.java     |  6 +++--
 16 files changed, 107 insertions(+), 88 deletions(-)

diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/utils/CassandraHealthCheckTest.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/utils/CassandraHealthCheckTest.java
index c37f641..1796033 100644
--- a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/utils/CassandraHealthCheckTest.java
+++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/utils/CassandraHealthCheckTest.java
@@ -49,7 +49,7 @@ class CassandraHealthCheckTest {
 
     @Test
     void checkShouldReturnHealthyWhenCassandraIsRunning() {
-        Result check = healthCheck.check();
+        Result check = healthCheck.checkReactive().block();
 
         assertThat(check.isHealthy()).isTrue();
     }
@@ -58,7 +58,7 @@ class CassandraHealthCheckTest {
     void checkShouldReturnUnhealthyWhenCassandraIsNotRunning(DockerCassandraExtension.DockerCassandra cassandraServer) {
         try {
             cassandraServer.getContainer().pause();
-            Result check = healthCheck.check();
+            Result check = healthCheck.checkReactive().block();
 
             assertThat(check.isUnHealthy()).isTrue();
         } finally {
@@ -72,12 +72,12 @@ class CassandraHealthCheckTest {
         try {
             cassandraServer.getContainer().pause();
 
-            healthCheck.check();
+            healthCheck.checkReactive().block();
         } finally {
             cassandraServer.getContainer().unpause();
         }
 
-        Result check = healthCheck.check();
+        Result check = healthCheck.checkReactive().block();
 
         assertThat(check.isHealthy()).isTrue();
     }
diff --git a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchHealthCheckConnectionTest.java b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchHealthCheckConnectionTest.java
index 0f5ddd6..0dc6530 100644
--- a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchHealthCheckConnectionTest.java
+++ b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchHealthCheckConnectionTest.java
@@ -44,7 +44,7 @@ class ElasticSearchHealthCheckConnectionTest {
 
     @Test
     void checkShouldSucceedWhenElasticSearchIsRunning() {
-        assertThat(elasticSearchHealthCheck.check().isHealthy()).isTrue();
+        assertThat(elasticSearchHealthCheck.checkReactive().block().isHealthy()).isTrue();
     }
 
     @Test
@@ -53,7 +53,7 @@ class ElasticSearchHealthCheckConnectionTest {
         elasticSearch.getDockerElasticSearch().pause();
 
         try {
-            assertThat(elasticSearchHealthCheck.check().isUnHealthy()).isTrue();
+            assertThat(elasticSearchHealthCheck.checkReactive().block().isUnHealthy()).isTrue();
         } finally {
             elasticSearch.getDockerElasticSearch().unpause();
         }
diff --git a/backends-common/rabbitmq/src/test/java/org/apache/james/backends/rabbitmq/RabbitMQHealthCheckTest.java b/backends-common/rabbitmq/src/test/java/org/apache/james/backends/rabbitmq/RabbitMQHealthCheckTest.java
index 769741a..f205bc7 100644
--- a/backends-common/rabbitmq/src/test/java/org/apache/james/backends/rabbitmq/RabbitMQHealthCheckTest.java
+++ b/backends-common/rabbitmq/src/test/java/org/apache/james/backends/rabbitmq/RabbitMQHealthCheckTest.java
@@ -46,21 +46,21 @@ class RabbitMQHealthCheckTest {
 
     @Test
     void checkShouldReturnHealthyWhenRabbitMQIsRunning() {
-        Result check = healthCheck.check();
+        Result check = healthCheck.checkReactive().block();
 
         assertThat(check.isHealthy()).isTrue();
     }
 
     @Test
     void checkShouldReturnHealthyWhenCalledSeveralTime() {
-        healthCheck.check();
-        healthCheck.check();
-        healthCheck.check();
-        healthCheck.check();
-        healthCheck.check();
-        healthCheck.check();
-        healthCheck.check();
-        Result check = healthCheck.check();
+        healthCheck.checkReactive().block();
+        healthCheck.checkReactive().block();
+        healthCheck.checkReactive().block();
+        healthCheck.checkReactive().block();
+        healthCheck.checkReactive().block();
+        healthCheck.checkReactive().block();
+        healthCheck.checkReactive().block();
+        Result check = healthCheck.checkReactive().block();
 
         assertThat(check.isHealthy()).isTrue();
     }
@@ -69,7 +69,7 @@ class RabbitMQHealthCheckTest {
     void checkShouldReturnUnhealthyWhenRabbitMQIsNotRunning(DockerRabbitMQ rabbitMQ) throws Exception {
         rabbitMQ.stopApp();
 
-        Result check = healthCheck.check();
+        Result check = healthCheck.checkReactive().block();
 
         assertThat(check.isHealthy()).isFalse();
     }
@@ -77,21 +77,21 @@ class RabbitMQHealthCheckTest {
     @Test
     void checkShouldDetectWhenRabbitMQRecovered(DockerRabbitMQ rabbitMQ) throws Exception {
         rabbitMQ.stopApp();
-        healthCheck.check();
+        healthCheck.checkReactive().block();
 
         rabbitMQ.startApp();
 
-        Result check = healthCheck.check();
+        Result check = healthCheck.checkReactive().block();
         assertThat(check.isHealthy()).isTrue();
     }
 
     @Test
     void checkShouldDetectWhenRabbitMQFail(DockerRabbitMQ rabbitMQ) throws Exception {
-        healthCheck.check();
+        healthCheck.checkReactive().block();
 
         rabbitMQ.stopApp();
 
-        Result check = healthCheck.check();
+        Result check = healthCheck.checkReactive().block();
         assertThat(check.isHealthy()).isFalse();
     }
 }
\ No newline at end of file
diff --git a/core/pom.xml b/core/pom.xml
index 0127fea..48f4e33 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -66,6 +66,10 @@
             <artifactId>commons-io</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.reactivestreams</groupId>
+            <artifactId>reactive-streams</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
         </dependency>
diff --git a/core/src/main/java/org/apache/james/core/healthcheck/HealthCheck.java b/core/src/main/java/org/apache/james/core/healthcheck/HealthCheck.java
index effd3c6..4f0bd4b 100644
--- a/core/src/main/java/org/apache/james/core/healthcheck/HealthCheck.java
+++ b/core/src/main/java/org/apache/james/core/healthcheck/HealthCheck.java
@@ -20,16 +20,8 @@ package org.apache.james.core.healthcheck;
 
 import org.reactivestreams.Publisher;
 
-import reactor.core.publisher.Mono;
-
 public interface HealthCheck {
     ComponentName componentName();
 
-    default Result check() {
-        return Mono.from(checkReactive()).block();
-    }
-
-    default Publisher<Result> checkReactive() {
-        return Mono.fromCallable(this::check);
-    }
+    Publisher<Result> checkReactive();
 }
diff --git a/mailbox/api/pom.xml b/mailbox/api/pom.xml
index 7ce79e2..dc16b36 100644
--- a/mailbox/api/pom.xml
+++ b/mailbox/api/pom.xml
@@ -84,6 +84,10 @@
             <artifactId>commons-io</artifactId>
         </dependency>
         <dependency>
+            <groupId>io.projectreactor</groupId>
+            <artifactId>reactor-core</artifactId>
+        </dependency>
+        <dependency>
             <groupId>javax.inject</groupId>
             <artifactId>javax.inject</artifactId>
         </dependency>
diff --git a/mailbox/api/src/test/java/org/apache/james/mailbox/events/EventDeadLettersHealthCheckContract.java b/mailbox/api/src/test/java/org/apache/james/mailbox/events/EventDeadLettersHealthCheckContract.java
index 6d1c49c..73d4f39 100644
--- a/mailbox/api/src/test/java/org/apache/james/mailbox/events/EventDeadLettersHealthCheckContract.java
+++ b/mailbox/api/src/test/java/org/apache/james/mailbox/events/EventDeadLettersHealthCheckContract.java
@@ -57,8 +57,8 @@ interface EventDeadLettersHealthCheckContract {
 
     @Test
     default void checkShouldReturnHealthyWhenEventDeadLetterEmpty() {
-        assertThat(testee().check().isHealthy()).isTrue();
-        assertThat(testee().check())
+        assertThat(testee().checkReactive().block().isHealthy()).isTrue();
+        assertThat(testee().checkReactive().block())
             .isEqualTo(Result.healthy(COMPONENT_NAME));
     }
 
@@ -66,8 +66,8 @@ interface EventDeadLettersHealthCheckContract {
     default void checkShouldReturnDegradedWhenEventDeadLetterContainEvent() {
         eventDeadLetters().store(GROUP_A, EVENT_1).block();
 
-        assertThat(testee().check().isDegraded()).isTrue();
-        assertThat(testee().check())
+        assertThat(testee().checkReactive().block().isDegraded()).isTrue();
+        assertThat(testee().checkReactive().block())
             .isEqualTo(Result.degraded(COMPONENT_NAME, EXPECTED_DEGRADED_MESSAGE));
     }
 
@@ -76,8 +76,8 @@ interface EventDeadLettersHealthCheckContract {
         eventDeadLetters().store(GROUP_A, EVENT_1).block();
         eventDeadLetters().store(GROUP_B, EVENT_2).block();
 
-        assertThat(testee().check().isDegraded()).isTrue();
-        assertThat(testee().check())
+        assertThat(testee().checkReactive().block().isDegraded()).isTrue();
+        assertThat(testee().checkReactive().block())
             .isEqualTo(Result.degraded(COMPONENT_NAME, EXPECTED_DEGRADED_MESSAGE));
     }
 
@@ -86,15 +86,15 @@ interface EventDeadLettersHealthCheckContract {
         EventDeadLetters.InsertionId insertionId1 = eventDeadLetters().store(GROUP_A, EVENT_1).block();
         EventDeadLetters.InsertionId insertionId2 = eventDeadLetters().store(GROUP_B, EVENT_2).block();
 
-        assertThat(testee().check().isDegraded()).isTrue();
-        assertThat(testee().check())
+        assertThat(testee().checkReactive().block().isDegraded()).isTrue();
+        assertThat(testee().checkReactive().block())
             .isEqualTo(Result.degraded(COMPONENT_NAME, EXPECTED_DEGRADED_MESSAGE));
 
         eventDeadLetters().remove(GROUP_A, insertionId1).block();
         eventDeadLetters().remove(GROUP_B, insertionId2).block();
 
-        assertThat(testee().check().isHealthy()).isTrue();
-        assertThat(testee().check())
+        assertThat(testee().checkReactive().block().isHealthy()).isTrue();
+        assertThat(testee().checkReactive().block())
             .isEqualTo(Result.healthy(COMPONENT_NAME));
     }
 
@@ -103,14 +103,14 @@ interface EventDeadLettersHealthCheckContract {
         EventDeadLetters.InsertionId insertionId1 = eventDeadLetters().store(GROUP_A, EVENT_1).block();
         eventDeadLetters().store(GROUP_B, EVENT_2).block();
 
-        assertThat(testee().check().isDegraded()).isTrue();
-        assertThat(testee().check())
+        assertThat(testee().checkReactive().block().isDegraded()).isTrue();
+        assertThat(testee().checkReactive().block())
             .isEqualTo(Result.degraded(COMPONENT_NAME, EXPECTED_DEGRADED_MESSAGE));
 
         eventDeadLetters().remove(GROUP_A, insertionId1).block();
 
-        assertThat(testee().check().isDegraded()).isTrue();
-        assertThat(testee().check())
+        assertThat(testee().checkReactive().block().isDegraded()).isTrue();
+        assertThat(testee().checkReactive().block())
             .isEqualTo(Result.degraded(COMPONENT_NAME, EXPECTED_DEGRADED_MESSAGE));
     }
 
@@ -119,7 +119,7 @@ interface EventDeadLettersHealthCheckContract {
         Result actualResult;
         try {
             createErrorWhenDoingHealthCheck();
-            actualResult = testee().check();
+            actualResult = testee().checkReactive().block();
         } finally {
             resolveErrorWhenDoingHealthCheck();
         }
diff --git a/metrics/metrics-tests/pom.xml b/metrics/metrics-tests/pom.xml
index 69f0432..c8971d6 100644
--- a/metrics/metrics-tests/pom.xml
+++ b/metrics/metrics-tests/pom.xml
@@ -58,5 +58,9 @@
             <groupId>com.github.steveash.guavate</groupId>
             <artifactId>guavate</artifactId>
         </dependency>
+        <dependency>
+            <groupId>io.projectreactor</groupId>
+            <artifactId>reactor-core</artifactId>
+        </dependency>
     </dependencies>
 </project>
\ No newline at end of file
diff --git a/server/container/guice/guice-common/src/test/java/org/apache/james/PeriodicalHealthChecksTest.java b/server/container/guice/guice-common/src/test/java/org/apache/james/PeriodicalHealthChecksTest.java
index 6819ebe..8627197 100644
--- a/server/container/guice/guice-common/src/test/java/org/apache/james/PeriodicalHealthChecksTest.java
+++ b/server/container/guice/guice-common/src/test/java/org/apache/james/PeriodicalHealthChecksTest.java
@@ -95,7 +95,7 @@ public class PeriodicalHealthChecksTest {
 
     @Test
     void startShouldCallRemainingHealthChecksWhenAHealthCheckThrows() {
-        when(mockHealthCheck1.check()).thenThrow(new RuntimeException());
+        when(mockHealthCheck1.checkReactive()).thenReturn(Mono.error(RuntimeException::new));
 
         testee.start();
 
diff --git a/server/data/data-jmap/pom.xml b/server/data/data-jmap/pom.xml
index 2811569..e913193 100644
--- a/server/data/data-jmap/pom.xml
+++ b/server/data/data-jmap/pom.xml
@@ -85,6 +85,10 @@
             <artifactId>guava</artifactId>
         </dependency>
         <dependency>
+            <groupId>io.projectreactor</groupId>
+            <artifactId>reactor-core</artifactId>
+        </dependency>
+        <dependency>
             <groupId>javax.inject</groupId>
             <artifactId>javax.inject</artifactId>
         </dependency>
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/projections/MessageFastViewProjectionHealthCheck.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/projections/MessageFastViewProjectionHealthCheck.java
index de18851..61c5f79 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/projections/MessageFastViewProjectionHealthCheck.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/projections/MessageFastViewProjectionHealthCheck.java
@@ -30,6 +30,8 @@ import org.apache.james.core.healthcheck.Result;
 import org.apache.james.metrics.api.Metric;
 import org.apache.james.metrics.api.MetricFactory;
 
+import reactor.core.publisher.Mono;
+
 public class MessageFastViewProjectionHealthCheck implements HealthCheck {
 
     private static final ComponentName COMPONENT_NAME = new ComponentName("MessageFastViewProjection");
@@ -50,14 +52,16 @@ public class MessageFastViewProjectionHealthCheck implements HealthCheck {
     }
 
     @Override
-    public Result check() {
-        long hitCount = retrieveHitCountMetric.getCount();
-        long missCount = retrieveMissCountMetric.getCount();
-
-        if (missCount == 0) {
-            return Result.healthy(COMPONENT_NAME);
-        }
-        return check(hitCount, missCount);
+    public Mono<Result> checkReactive() {
+        return Mono.fromCallable(() -> retrieveMissCountMetric.getCount())
+            .flatMap(missCount -> {
+                if (missCount == 0) {
+                    return Mono.just(Result.healthy(COMPONENT_NAME));
+                } else {
+                    return Mono.fromCallable(() -> retrieveHitCountMetric.getCount())
+                        .map(hitCount -> check(hitCount, missCount));
+                }
+            });
     }
 
     private Result check(long hitCount, long missCount) {
diff --git a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/projections/MessageFastViewProjectionHealthCheckTest.java b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/projections/MessageFastViewProjectionHealthCheckTest.java
index b9888e7..f7ac3fa 100644
--- a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/projections/MessageFastViewProjectionHealthCheckTest.java
+++ b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/projections/MessageFastViewProjectionHealthCheckTest.java
@@ -62,7 +62,7 @@ class MessageFastViewProjectionHealthCheckTest {
 
         @Test
         void checkShouldReturnHealthyWhenNoRetrieveCalled() {
-            assertThat(testee.check())
+            assertThat(testee.checkReactive().block())
                 .isEqualTo(Result.healthy(COMPONENT_NAME));
         }
 
@@ -71,7 +71,7 @@ class MessageFastViewProjectionHealthCheckTest {
             missMetric.increment();
             missMetric.increment();
 
-            assertThat(testee.check())
+            assertThat(testee.checkReactive().block())
                 .isEqualTo(Result.degraded(COMPONENT_NAME, "retrieveMissCount percentage 100.0% (2/2) is higher than the threshold 10.0%"));
         }
 
@@ -80,7 +80,7 @@ class MessageFastViewProjectionHealthCheckTest {
             missMetric.increment();
             hitMetric.add(43);
 
-            assertThat(testee.check())
+            assertThat(testee.checkReactive().block())
                 .isEqualTo(Result.healthy(COMPONENT_NAME));
         }
 
@@ -89,7 +89,7 @@ class MessageFastViewProjectionHealthCheckTest {
             missMetric.increment();
             hitMetric.add(9);
 
-            assertThat(testee.check())
+            assertThat(testee.checkReactive().block())
                 .isEqualTo(Result.healthy(COMPONENT_NAME));
         }
 
@@ -98,7 +98,7 @@ class MessageFastViewProjectionHealthCheckTest {
             missMetric.increment();
             hitMetric.add(3);
 
-            assertThat(testee.check())
+            assertThat(testee.checkReactive().block())
                 .isEqualTo(Result.degraded(COMPONENT_NAME,
                     "retrieveMissCount percentage 25.0% (1/4) is higher than the threshold 10.0%"));
         }
@@ -107,11 +107,11 @@ class MessageFastViewProjectionHealthCheckTest {
         void checkShouldReturnHealthyAfterMoreHits() {
             missMetric.increment();
             hitMetric.increment();
-            Result resultWithLessHit = testee.check();
+            Result resultWithLessHit = testee.checkReactive().block();
 
             // more hits
             hitMetric.add(10);
-            Result resultWithMoreHit = testee.check();
+            Result resultWithMoreHit = testee.checkReactive().block();
 
             SoftAssertions.assertSoftly(softly -> {
                 softly.assertThat(resultWithLessHit)
@@ -126,11 +126,11 @@ class MessageFastViewProjectionHealthCheckTest {
         void checkShouldKeepBeingDegradedAfterNotEnoughOfHits() {
             missMetric.increment();
             hitMetric.increment();
-            Result resultWithLessHit = testee.check();
+            Result resultWithLessHit = testee.checkReactive().block();
 
             // more hits, but not enough
             hitMetric.add(3);
-            Result resultWithMoreHit = testee.check();
+            Result resultWithMoreHit = testee.checkReactive().block();
 
             SoftAssertions.assertSoftly(softly -> {
                 softly.assertThat(resultWithLessHit)
@@ -148,11 +148,11 @@ class MessageFastViewProjectionHealthCheckTest {
             // enough of hits
             hitMetric.add(10);
 
-            Result resultWithEnoughOfHits = testee.check();
+            Result resultWithEnoughOfHits = testee.checkReactive().block();
 
             // more miss
             missMetric.increment();
-            Result resultWithMoreMiss = testee.check();
+            Result resultWithMoreMiss = testee.checkReactive().block();
 
             SoftAssertions.assertSoftly(softly -> {
                 softly.assertThat(resultWithEnoughOfHits)
@@ -168,12 +168,12 @@ class MessageFastViewProjectionHealthCheckTest {
             missMetric.increment();
             // enough of hits
             hitMetric.add(10000);
-            Result resultWithEnoughOfHits = testee.check();
+            Result resultWithEnoughOfHits = testee.checkReactive().block();
 
             // more miss, but not enough
             IntStream.rangeClosed(1, 3)
                 .forEach(counter -> missMetric.increment());
-            Result resultWithMoreMiss = testee.check();
+            Result resultWithMoreMiss = testee.checkReactive().block();
 
             SoftAssertions.assertSoftly(softly -> {
                 softly.assertThat(resultWithEnoughOfHits)
diff --git a/server/data/data-jpa/src/main/java/org/apache/james/jpa/healthcheck/JPAHealthCheck.java b/server/data/data-jpa/src/main/java/org/apache/james/jpa/healthcheck/JPAHealthCheck.java
index 8cd1706..281626c 100644
--- a/server/data/data-jpa/src/main/java/org/apache/james/jpa/healthcheck/JPAHealthCheck.java
+++ b/server/data/data-jpa/src/main/java/org/apache/james/jpa/healthcheck/JPAHealthCheck.java
@@ -28,6 +28,8 @@ import org.apache.james.core.healthcheck.ComponentName;
 import org.apache.james.core.healthcheck.HealthCheck;
 import org.apache.james.core.healthcheck.Result;
 
+import reactor.core.publisher.Mono;
+
 public class JPAHealthCheck implements HealthCheck {
 
     private final EntityManagerFactory entityManagerFactory;
@@ -43,15 +45,17 @@ public class JPAHealthCheck implements HealthCheck {
     }
 
     @Override
-    public Result check() {
-        try {
-            if (entityManagerFactory.createEntityManager().isOpen()) {
-                return healthy(componentName());
-            }
-        } catch (IllegalStateException stateException) {
-            return unhealthy(componentName(), "EntityManagerFactory or EntityManager thrown an IllegalStateException, the connection is unhealthy", stateException);
-        }
-
-        return unhealthy(componentName(), "entityManager is not open");
+    public Mono<Result> checkReactive() {
+        return Mono.fromCallable(entityManagerFactory::createEntityManager)
+            .map(entityManager -> entityManager.isOpen())
+            .map(open -> {
+                if (open) {
+                    return healthy(componentName());
+                } else {
+                    return unhealthy(componentName(), "entityManager is not open");
+                }
+            })
+            .onErrorResume(IllegalStateException.class,
+                e -> Mono.just(unhealthy(componentName(), "EntityManagerFactory or EntityManager thrown an IllegalStateException, the connection is unhealthy", e)));
     }
 }
diff --git a/server/data/data-jpa/src/test/java/org/apache/james/jpa/healthcheck/JPAHealthCheckTest.java b/server/data/data-jpa/src/test/java/org/apache/james/jpa/healthcheck/JPAHealthCheckTest.java
index b749d96..a365abe 100644
--- a/server/data/data-jpa/src/test/java/org/apache/james/jpa/healthcheck/JPAHealthCheckTest.java
+++ b/server/data/data-jpa/src/test/java/org/apache/james/jpa/healthcheck/JPAHealthCheckTest.java
@@ -46,7 +46,7 @@ class JPAHealthCheckTest {
 
     @Test
     void testWhenActive() {
-        Result result = jpaHealthCheck.check();
+        Result result = jpaHealthCheck.checkReactive().block();
         ResultStatus healthy = ResultStatus.HEALTHY;
         assertThat(result.getStatus()).as("Result %s status should be %s", result.getStatus(), healthy)
                 .isEqualTo(healthy);
@@ -57,7 +57,7 @@ class JPAHealthCheckTest {
         jpaTestCluster.getEntityManagerFactory().close();
         Result result = Result.healthy(jpaHealthCheck.componentName());
         try {
-            result = jpaHealthCheck.check();
+            result = jpaHealthCheck.checkReactive().block();
         } catch (IllegalStateException e) {
             fail("The exception of the EMF was not handled property.ยช");
         }
diff --git a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/routes/HealthCheckRoutes.java b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/routes/HealthCheckRoutes.java
index 12051af..ce75afa 100644
--- a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/routes/HealthCheckRoutes.java
+++ b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/routes/HealthCheckRoutes.java
@@ -49,6 +49,8 @@ import io.swagger.annotations.ApiImplicitParams;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiResponse;
 import io.swagger.annotations.ApiResponses;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
 import spark.HaltException;
 import spark.Request;
 import spark.Response;
@@ -94,7 +96,7 @@ public class HealthCheckRoutes implements PublicRoutes {
             message = "Internal server error - When one check has failed.")
     })
     public Object validateHealthChecks(Request request, Response response) {
-        ImmutableList<Result> results = executeHealthChecks();
+        List<Result> results = executeHealthChecks().collectList().block();
         ResultStatus status = retrieveAggregationStatus(results);
         response.status(getCorrespondingStatusCode(status));
         return new HeathCheckAggregationExecutionResultDto(status, mapResultToDto(results));
@@ -119,8 +121,8 @@ public class HealthCheckRoutes implements PublicRoutes {
             .filter(c -> c.componentName().getName().equals(componentName))
             .findFirst()
             .orElseThrow(() -> throw404(componentName));
-        
-        Result result = healthCheck.check();
+
+        Result result = Mono.from(healthCheck.checkReactive()).block();
         logFailedCheck(result);
         response.status(getCorrespondingStatusCode(result.getStatus()));
         return new HealthCheckExecutionResultDto(result);
@@ -175,11 +177,10 @@ public class HealthCheckRoutes implements PublicRoutes {
         }
     }
 
-    private ImmutableList<Result> executeHealthChecks() {
-        return healthChecks.stream()
-            .map(HealthCheck::check)
-            .peek(this::logFailedCheck)
-            .collect(ImmutableList.toImmutableList());
+    private Flux<Result> executeHealthChecks() {
+        return Flux.fromIterable(healthChecks)
+            .flatMap(HealthCheck::checkReactive)
+            .doOnNext(this::logFailedCheck);
     }
 
     private ResultStatus retrieveAggregationStatus(List<Result> results) {
diff --git a/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/HealthCheckRoutesTest.java b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/HealthCheckRoutesTest.java
index 359724d..a394531 100644
--- a/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/HealthCheckRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/HealthCheckRoutesTest.java
@@ -41,9 +41,11 @@ import org.eclipse.jetty.http.HttpStatus;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.reactivestreams.Publisher;
 
 import io.restassured.RestAssured;
 import net.javacrumbs.jsonunit.core.Option;
+import reactor.core.publisher.Mono;
 
 public class HealthCheckRoutesTest {
 
@@ -65,8 +67,8 @@ public class HealthCheckRoutesTest {
             }
 
             @Override
-            public Result check() {
-                return result;
+            public Publisher<Result> checkReactive() {
+                return Mono.just(result);
             }
         };
     }


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


[james-project] 24/27: JAMES-3065 s/HealthCheck::checkReactive/HealthCheck::check/

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 e78132260994e0557b8add5e3883818f8d3ad7b5
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Apr 24 09:27:16 2020 +0700

    JAMES-3065 s/HealthCheck::checkReactive/HealthCheck::check/
---
 .../backends/es/ElasticSearchHealthCheck.java      |  2 +-
 .../es/ElasticSearchHealthCheckConnectionTest.java |  4 +--
 .../backends/rabbitmq/RabbitMQHealthCheck.java     |  2 +-
 .../backends/rabbitmq/RabbitMQHealthCheckTest.java | 28 ++++++++++----------
 .../apache/james/core/healthcheck/HealthCheck.java |  2 +-
 .../events/EventDeadLettersHealthCheck.java        |  2 +-
 .../EventDeadLettersHealthCheckContract.java       | 30 +++++++++++-----------
 .../apache/james/GuiceLifecycleHealthCheck.java    |  2 +-
 .../org/apache/james/PeriodicalHealthChecks.java   |  2 +-
 .../apache/james/PeriodicalHealthChecksTest.java   | 16 ++++++------
 .../MessageFastViewProjectionHealthCheck.java      |  2 +-
 .../MessageFastViewProjectionHealthCheckTest.java  | 26 +++++++++----------
 .../james/jpa/healthcheck/JPAHealthCheck.java      |  2 +-
 .../james/jpa/healthcheck/JPAHealthCheckTest.java  |  4 +--
 .../james/webadmin/routes/HealthCheckRoutes.java   |  4 +--
 .../webadmin/routes/HealthCheckRoutesTest.java     |  2 +-
 16 files changed, 65 insertions(+), 65 deletions(-)

diff --git a/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ElasticSearchHealthCheck.java b/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ElasticSearchHealthCheck.java
index 4dded30..1d3f92e 100644
--- a/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ElasticSearchHealthCheck.java
+++ b/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ElasticSearchHealthCheck.java
@@ -55,7 +55,7 @@ public class ElasticSearchHealthCheck implements HealthCheck {
     }
 
     @Override
-    public Mono<Result> checkReactive() {
+    public Mono<Result> check() {
         String[] indices = indexNames.stream()
             .map(IndexName::getValue)
             .toArray(String[]::new);
diff --git a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchHealthCheckConnectionTest.java b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchHealthCheckConnectionTest.java
index 0dc6530..70bfa3b 100644
--- a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchHealthCheckConnectionTest.java
+++ b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchHealthCheckConnectionTest.java
@@ -44,7 +44,7 @@ class ElasticSearchHealthCheckConnectionTest {
 
     @Test
     void checkShouldSucceedWhenElasticSearchIsRunning() {
-        assertThat(elasticSearchHealthCheck.checkReactive().block().isHealthy()).isTrue();
+        assertThat(elasticSearchHealthCheck.check().block().isHealthy()).isTrue();
     }
 
     @Test
@@ -53,7 +53,7 @@ class ElasticSearchHealthCheckConnectionTest {
         elasticSearch.getDockerElasticSearch().pause();
 
         try {
-            assertThat(elasticSearchHealthCheck.checkReactive().block().isUnHealthy()).isTrue();
+            assertThat(elasticSearchHealthCheck.check().block().isUnHealthy()).isTrue();
         } finally {
             elasticSearch.getDockerElasticSearch().unpause();
         }
diff --git a/backends-common/rabbitmq/src/main/java/org/apache/james/backends/rabbitmq/RabbitMQHealthCheck.java b/backends-common/rabbitmq/src/main/java/org/apache/james/backends/rabbitmq/RabbitMQHealthCheck.java
index 098dc26..fff049a 100644
--- a/backends-common/rabbitmq/src/main/java/org/apache/james/backends/rabbitmq/RabbitMQHealthCheck.java
+++ b/backends-common/rabbitmq/src/main/java/org/apache/james/backends/rabbitmq/RabbitMQHealthCheck.java
@@ -49,7 +49,7 @@ public class RabbitMQHealthCheck implements HealthCheck {
     }
 
     @Override
-    public Mono<Result> checkReactive() {
+    public Mono<Result> check() {
         try {
             return Flux.concat(connectionPool.tryConnection(),
                 rabbitChannelPoolImpl.tryChannel())
diff --git a/backends-common/rabbitmq/src/test/java/org/apache/james/backends/rabbitmq/RabbitMQHealthCheckTest.java b/backends-common/rabbitmq/src/test/java/org/apache/james/backends/rabbitmq/RabbitMQHealthCheckTest.java
index f205bc7..fa2eef4 100644
--- a/backends-common/rabbitmq/src/test/java/org/apache/james/backends/rabbitmq/RabbitMQHealthCheckTest.java
+++ b/backends-common/rabbitmq/src/test/java/org/apache/james/backends/rabbitmq/RabbitMQHealthCheckTest.java
@@ -46,21 +46,21 @@ class RabbitMQHealthCheckTest {
 
     @Test
     void checkShouldReturnHealthyWhenRabbitMQIsRunning() {
-        Result check = healthCheck.checkReactive().block();
+        Result check = healthCheck.check().block();
 
         assertThat(check.isHealthy()).isTrue();
     }
 
     @Test
     void checkShouldReturnHealthyWhenCalledSeveralTime() {
-        healthCheck.checkReactive().block();
-        healthCheck.checkReactive().block();
-        healthCheck.checkReactive().block();
-        healthCheck.checkReactive().block();
-        healthCheck.checkReactive().block();
-        healthCheck.checkReactive().block();
-        healthCheck.checkReactive().block();
-        Result check = healthCheck.checkReactive().block();
+        healthCheck.check().block();
+        healthCheck.check().block();
+        healthCheck.check().block();
+        healthCheck.check().block();
+        healthCheck.check().block();
+        healthCheck.check().block();
+        healthCheck.check().block();
+        Result check = healthCheck.check().block();
 
         assertThat(check.isHealthy()).isTrue();
     }
@@ -69,7 +69,7 @@ class RabbitMQHealthCheckTest {
     void checkShouldReturnUnhealthyWhenRabbitMQIsNotRunning(DockerRabbitMQ rabbitMQ) throws Exception {
         rabbitMQ.stopApp();
 
-        Result check = healthCheck.checkReactive().block();
+        Result check = healthCheck.check().block();
 
         assertThat(check.isHealthy()).isFalse();
     }
@@ -77,21 +77,21 @@ class RabbitMQHealthCheckTest {
     @Test
     void checkShouldDetectWhenRabbitMQRecovered(DockerRabbitMQ rabbitMQ) throws Exception {
         rabbitMQ.stopApp();
-        healthCheck.checkReactive().block();
+        healthCheck.check().block();
 
         rabbitMQ.startApp();
 
-        Result check = healthCheck.checkReactive().block();
+        Result check = healthCheck.check().block();
         assertThat(check.isHealthy()).isTrue();
     }
 
     @Test
     void checkShouldDetectWhenRabbitMQFail(DockerRabbitMQ rabbitMQ) throws Exception {
-        healthCheck.checkReactive().block();
+        healthCheck.check().block();
 
         rabbitMQ.stopApp();
 
-        Result check = healthCheck.checkReactive().block();
+        Result check = healthCheck.check().block();
         assertThat(check.isHealthy()).isFalse();
     }
 }
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/james/core/healthcheck/HealthCheck.java b/core/src/main/java/org/apache/james/core/healthcheck/HealthCheck.java
index 4f0bd4b..9fd5b0f 100644
--- a/core/src/main/java/org/apache/james/core/healthcheck/HealthCheck.java
+++ b/core/src/main/java/org/apache/james/core/healthcheck/HealthCheck.java
@@ -23,5 +23,5 @@ import org.reactivestreams.Publisher;
 public interface HealthCheck {
     ComponentName componentName();
 
-    Publisher<Result> checkReactive();
+    Publisher<Result> check();
 }
diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/events/EventDeadLettersHealthCheck.java b/mailbox/api/src/main/java/org/apache/james/mailbox/events/EventDeadLettersHealthCheck.java
index dfc8ce1..27d33d6 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/events/EventDeadLettersHealthCheck.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/events/EventDeadLettersHealthCheck.java
@@ -43,7 +43,7 @@ public class EventDeadLettersHealthCheck implements HealthCheck {
     }
 
     @Override
-    public Mono<Result> checkReactive() {
+    public Mono<Result> check() {
         return eventDeadLetters.containEvents()
             .map(containEvents -> {
                 if (containEvents) {
diff --git a/mailbox/api/src/test/java/org/apache/james/mailbox/events/EventDeadLettersHealthCheckContract.java b/mailbox/api/src/test/java/org/apache/james/mailbox/events/EventDeadLettersHealthCheckContract.java
index 73d4f39..7c16b7b 100644
--- a/mailbox/api/src/test/java/org/apache/james/mailbox/events/EventDeadLettersHealthCheckContract.java
+++ b/mailbox/api/src/test/java/org/apache/james/mailbox/events/EventDeadLettersHealthCheckContract.java
@@ -57,8 +57,8 @@ interface EventDeadLettersHealthCheckContract {
 
     @Test
     default void checkShouldReturnHealthyWhenEventDeadLetterEmpty() {
-        assertThat(testee().checkReactive().block().isHealthy()).isTrue();
-        assertThat(testee().checkReactive().block())
+        assertThat(testee().check().block().isHealthy()).isTrue();
+        assertThat(testee().check().block())
             .isEqualTo(Result.healthy(COMPONENT_NAME));
     }
 
@@ -66,8 +66,8 @@ interface EventDeadLettersHealthCheckContract {
     default void checkShouldReturnDegradedWhenEventDeadLetterContainEvent() {
         eventDeadLetters().store(GROUP_A, EVENT_1).block();
 
-        assertThat(testee().checkReactive().block().isDegraded()).isTrue();
-        assertThat(testee().checkReactive().block())
+        assertThat(testee().check().block().isDegraded()).isTrue();
+        assertThat(testee().check().block())
             .isEqualTo(Result.degraded(COMPONENT_NAME, EXPECTED_DEGRADED_MESSAGE));
     }
 
@@ -76,8 +76,8 @@ interface EventDeadLettersHealthCheckContract {
         eventDeadLetters().store(GROUP_A, EVENT_1).block();
         eventDeadLetters().store(GROUP_B, EVENT_2).block();
 
-        assertThat(testee().checkReactive().block().isDegraded()).isTrue();
-        assertThat(testee().checkReactive().block())
+        assertThat(testee().check().block().isDegraded()).isTrue();
+        assertThat(testee().check().block())
             .isEqualTo(Result.degraded(COMPONENT_NAME, EXPECTED_DEGRADED_MESSAGE));
     }
 
@@ -86,15 +86,15 @@ interface EventDeadLettersHealthCheckContract {
         EventDeadLetters.InsertionId insertionId1 = eventDeadLetters().store(GROUP_A, EVENT_1).block();
         EventDeadLetters.InsertionId insertionId2 = eventDeadLetters().store(GROUP_B, EVENT_2).block();
 
-        assertThat(testee().checkReactive().block().isDegraded()).isTrue();
-        assertThat(testee().checkReactive().block())
+        assertThat(testee().check().block().isDegraded()).isTrue();
+        assertThat(testee().check().block())
             .isEqualTo(Result.degraded(COMPONENT_NAME, EXPECTED_DEGRADED_MESSAGE));
 
         eventDeadLetters().remove(GROUP_A, insertionId1).block();
         eventDeadLetters().remove(GROUP_B, insertionId2).block();
 
-        assertThat(testee().checkReactive().block().isHealthy()).isTrue();
-        assertThat(testee().checkReactive().block())
+        assertThat(testee().check().block().isHealthy()).isTrue();
+        assertThat(testee().check().block())
             .isEqualTo(Result.healthy(COMPONENT_NAME));
     }
 
@@ -103,14 +103,14 @@ interface EventDeadLettersHealthCheckContract {
         EventDeadLetters.InsertionId insertionId1 = eventDeadLetters().store(GROUP_A, EVENT_1).block();
         eventDeadLetters().store(GROUP_B, EVENT_2).block();
 
-        assertThat(testee().checkReactive().block().isDegraded()).isTrue();
-        assertThat(testee().checkReactive().block())
+        assertThat(testee().check().block().isDegraded()).isTrue();
+        assertThat(testee().check().block())
             .isEqualTo(Result.degraded(COMPONENT_NAME, EXPECTED_DEGRADED_MESSAGE));
 
         eventDeadLetters().remove(GROUP_A, insertionId1).block();
 
-        assertThat(testee().checkReactive().block().isDegraded()).isTrue();
-        assertThat(testee().checkReactive().block())
+        assertThat(testee().check().block().isDegraded()).isTrue();
+        assertThat(testee().check().block())
             .isEqualTo(Result.degraded(COMPONENT_NAME, EXPECTED_DEGRADED_MESSAGE));
     }
 
@@ -119,7 +119,7 @@ interface EventDeadLettersHealthCheckContract {
         Result actualResult;
         try {
             createErrorWhenDoingHealthCheck();
-            actualResult = testee().checkReactive().block();
+            actualResult = testee().check().block();
         } finally {
             resolveErrorWhenDoingHealthCheck();
         }
diff --git a/server/container/guice/guice-common/src/main/java/org/apache/james/GuiceLifecycleHealthCheck.java b/server/container/guice/guice-common/src/main/java/org/apache/james/GuiceLifecycleHealthCheck.java
index 2dacb09..1da811a 100644
--- a/server/container/guice/guice-common/src/main/java/org/apache/james/GuiceLifecycleHealthCheck.java
+++ b/server/container/guice/guice-common/src/main/java/org/apache/james/GuiceLifecycleHealthCheck.java
@@ -41,7 +41,7 @@ public class GuiceLifecycleHealthCheck implements HealthCheck {
     }
 
     @Override
-    public Mono<Result> checkReactive() {
+    public Mono<Result> check() {
         if (probe.isStarted()) {
             return Mono.just(Result.healthy(componentName()));
         } else {
diff --git a/server/container/guice/guice-common/src/main/java/org/apache/james/PeriodicalHealthChecks.java b/server/container/guice/guice-common/src/main/java/org/apache/james/PeriodicalHealthChecks.java
index 68c08b7..c51efdf 100644
--- a/server/container/guice/guice-common/src/main/java/org/apache/james/PeriodicalHealthChecks.java
+++ b/server/container/guice/guice-common/src/main/java/org/apache/james/PeriodicalHealthChecks.java
@@ -60,7 +60,7 @@ public class PeriodicalHealthChecks implements Startable {
     public void start() {
         disposable = Flux.interval(configuration.getPeriod(), scheduler)
             .flatMap(any -> Flux.fromIterable(healthChecks)
-                .flatMap(healthCheck -> Mono.from(healthCheck.checkReactive())))
+                .flatMap(healthCheck -> Mono.from(healthCheck.check())))
             .doOnNext(this::logResult)
             .onErrorContinue(this::logError)
             .subscribeOn(Schedulers.elastic())
diff --git a/server/container/guice/guice-common/src/test/java/org/apache/james/PeriodicalHealthChecksTest.java b/server/container/guice/guice-common/src/test/java/org/apache/james/PeriodicalHealthChecksTest.java
index 8627197..49e346b 100644
--- a/server/container/guice/guice-common/src/test/java/org/apache/james/PeriodicalHealthChecksTest.java
+++ b/server/container/guice/guice-common/src/test/java/org/apache/james/PeriodicalHealthChecksTest.java
@@ -54,8 +54,8 @@ public class PeriodicalHealthChecksTest {
     void setUp() {
         mockHealthCheck1 = Mockito.mock(EventDeadLettersHealthCheck.class);
         mockHealthCheck2 = Mockito.mock(GuiceLifecycleHealthCheck.class);
-        when(mockHealthCheck1.checkReactive()).thenReturn(Mono.just(Result.healthy(new ComponentName("mockHealthCheck1"))));
-        when(mockHealthCheck2.checkReactive()).thenReturn(Mono.just(Result.healthy(new ComponentName("mockHealthCheck2"))));
+        when(mockHealthCheck1.check()).thenReturn(Mono.just(Result.healthy(new ComponentName("mockHealthCheck1"))));
+        when(mockHealthCheck2.check()).thenReturn(Mono.just(Result.healthy(new ComponentName("mockHealthCheck2"))));
 
         scheduler = VirtualTimeScheduler.getOrSet();
         testee = new PeriodicalHealthChecks(ImmutableSet.of(mockHealthCheck1, mockHealthCheck2),
@@ -73,7 +73,7 @@ public class PeriodicalHealthChecksTest {
         testee.start();
 
         scheduler.advanceTimeBy(Duration.ofSeconds(PERIOD));
-        verify(mockHealthCheck1, atLeast(1)).checkReactive();
+        verify(mockHealthCheck1, atLeast(1)).check();
     }
 
     @Test
@@ -81,7 +81,7 @@ public class PeriodicalHealthChecksTest {
         testee.start();
 
         scheduler.advanceTimeBy(Duration.ofSeconds(PERIOD * EXPECTED_INVOKED_TIME));
-        verify(mockHealthCheck1, times(EXPECTED_INVOKED_TIME)).checkReactive();
+        verify(mockHealthCheck1, times(EXPECTED_INVOKED_TIME)).check();
     }
 
     @Test
@@ -89,17 +89,17 @@ public class PeriodicalHealthChecksTest {
         testee.start();
 
         scheduler.advanceTimeBy(Duration.ofSeconds(PERIOD * EXPECTED_INVOKED_TIME));
-        verify(mockHealthCheck1, times(EXPECTED_INVOKED_TIME)).checkReactive();
-        verify(mockHealthCheck2, times(EXPECTED_INVOKED_TIME)).checkReactive();
+        verify(mockHealthCheck1, times(EXPECTED_INVOKED_TIME)).check();
+        verify(mockHealthCheck2, times(EXPECTED_INVOKED_TIME)).check();
     }
 
     @Test
     void startShouldCallRemainingHealthChecksWhenAHealthCheckThrows() {
-        when(mockHealthCheck1.checkReactive()).thenReturn(Mono.error(RuntimeException::new));
+        when(mockHealthCheck1.check()).thenReturn(Mono.error(RuntimeException::new));
 
         testee.start();
 
         scheduler.advanceTimeBy(Duration.ofSeconds(PERIOD * EXPECTED_INVOKED_TIME));
-        verify(mockHealthCheck2, times(EXPECTED_INVOKED_TIME)).checkReactive();
+        verify(mockHealthCheck2, times(EXPECTED_INVOKED_TIME)).check();
     }
 }
\ No newline at end of file
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/projections/MessageFastViewProjectionHealthCheck.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/projections/MessageFastViewProjectionHealthCheck.java
index 61c5f79..25adc02 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/projections/MessageFastViewProjectionHealthCheck.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/projections/MessageFastViewProjectionHealthCheck.java
@@ -52,7 +52,7 @@ public class MessageFastViewProjectionHealthCheck implements HealthCheck {
     }
 
     @Override
-    public Mono<Result> checkReactive() {
+    public Mono<Result> check() {
         return Mono.fromCallable(() -> retrieveMissCountMetric.getCount())
             .flatMap(missCount -> {
                 if (missCount == 0) {
diff --git a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/projections/MessageFastViewProjectionHealthCheckTest.java b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/projections/MessageFastViewProjectionHealthCheckTest.java
index f7ac3fa..2b4dc5f 100644
--- a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/projections/MessageFastViewProjectionHealthCheckTest.java
+++ b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/projections/MessageFastViewProjectionHealthCheckTest.java
@@ -62,7 +62,7 @@ class MessageFastViewProjectionHealthCheckTest {
 
         @Test
         void checkShouldReturnHealthyWhenNoRetrieveCalled() {
-            assertThat(testee.checkReactive().block())
+            assertThat(testee.check().block())
                 .isEqualTo(Result.healthy(COMPONENT_NAME));
         }
 
@@ -71,7 +71,7 @@ class MessageFastViewProjectionHealthCheckTest {
             missMetric.increment();
             missMetric.increment();
 
-            assertThat(testee.checkReactive().block())
+            assertThat(testee.check().block())
                 .isEqualTo(Result.degraded(COMPONENT_NAME, "retrieveMissCount percentage 100.0% (2/2) is higher than the threshold 10.0%"));
         }
 
@@ -80,7 +80,7 @@ class MessageFastViewProjectionHealthCheckTest {
             missMetric.increment();
             hitMetric.add(43);
 
-            assertThat(testee.checkReactive().block())
+            assertThat(testee.check().block())
                 .isEqualTo(Result.healthy(COMPONENT_NAME));
         }
 
@@ -89,7 +89,7 @@ class MessageFastViewProjectionHealthCheckTest {
             missMetric.increment();
             hitMetric.add(9);
 
-            assertThat(testee.checkReactive().block())
+            assertThat(testee.check().block())
                 .isEqualTo(Result.healthy(COMPONENT_NAME));
         }
 
@@ -98,7 +98,7 @@ class MessageFastViewProjectionHealthCheckTest {
             missMetric.increment();
             hitMetric.add(3);
 
-            assertThat(testee.checkReactive().block())
+            assertThat(testee.check().block())
                 .isEqualTo(Result.degraded(COMPONENT_NAME,
                     "retrieveMissCount percentage 25.0% (1/4) is higher than the threshold 10.0%"));
         }
@@ -107,11 +107,11 @@ class MessageFastViewProjectionHealthCheckTest {
         void checkShouldReturnHealthyAfterMoreHits() {
             missMetric.increment();
             hitMetric.increment();
-            Result resultWithLessHit = testee.checkReactive().block();
+            Result resultWithLessHit = testee.check().block();
 
             // more hits
             hitMetric.add(10);
-            Result resultWithMoreHit = testee.checkReactive().block();
+            Result resultWithMoreHit = testee.check().block();
 
             SoftAssertions.assertSoftly(softly -> {
                 softly.assertThat(resultWithLessHit)
@@ -126,11 +126,11 @@ class MessageFastViewProjectionHealthCheckTest {
         void checkShouldKeepBeingDegradedAfterNotEnoughOfHits() {
             missMetric.increment();
             hitMetric.increment();
-            Result resultWithLessHit = testee.checkReactive().block();
+            Result resultWithLessHit = testee.check().block();
 
             // more hits, but not enough
             hitMetric.add(3);
-            Result resultWithMoreHit = testee.checkReactive().block();
+            Result resultWithMoreHit = testee.check().block();
 
             SoftAssertions.assertSoftly(softly -> {
                 softly.assertThat(resultWithLessHit)
@@ -148,11 +148,11 @@ class MessageFastViewProjectionHealthCheckTest {
             // enough of hits
             hitMetric.add(10);
 
-            Result resultWithEnoughOfHits = testee.checkReactive().block();
+            Result resultWithEnoughOfHits = testee.check().block();
 
             // more miss
             missMetric.increment();
-            Result resultWithMoreMiss = testee.checkReactive().block();
+            Result resultWithMoreMiss = testee.check().block();
 
             SoftAssertions.assertSoftly(softly -> {
                 softly.assertThat(resultWithEnoughOfHits)
@@ -168,12 +168,12 @@ class MessageFastViewProjectionHealthCheckTest {
             missMetric.increment();
             // enough of hits
             hitMetric.add(10000);
-            Result resultWithEnoughOfHits = testee.checkReactive().block();
+            Result resultWithEnoughOfHits = testee.check().block();
 
             // more miss, but not enough
             IntStream.rangeClosed(1, 3)
                 .forEach(counter -> missMetric.increment());
-            Result resultWithMoreMiss = testee.checkReactive().block();
+            Result resultWithMoreMiss = testee.check().block();
 
             SoftAssertions.assertSoftly(softly -> {
                 softly.assertThat(resultWithEnoughOfHits)
diff --git a/server/data/data-jpa/src/main/java/org/apache/james/jpa/healthcheck/JPAHealthCheck.java b/server/data/data-jpa/src/main/java/org/apache/james/jpa/healthcheck/JPAHealthCheck.java
index 281626c..fce5c66 100644
--- a/server/data/data-jpa/src/main/java/org/apache/james/jpa/healthcheck/JPAHealthCheck.java
+++ b/server/data/data-jpa/src/main/java/org/apache/james/jpa/healthcheck/JPAHealthCheck.java
@@ -45,7 +45,7 @@ public class JPAHealthCheck implements HealthCheck {
     }
 
     @Override
-    public Mono<Result> checkReactive() {
+    public Mono<Result> check() {
         return Mono.fromCallable(entityManagerFactory::createEntityManager)
             .map(entityManager -> entityManager.isOpen())
             .map(open -> {
diff --git a/server/data/data-jpa/src/test/java/org/apache/james/jpa/healthcheck/JPAHealthCheckTest.java b/server/data/data-jpa/src/test/java/org/apache/james/jpa/healthcheck/JPAHealthCheckTest.java
index a365abe..38e8326 100644
--- a/server/data/data-jpa/src/test/java/org/apache/james/jpa/healthcheck/JPAHealthCheckTest.java
+++ b/server/data/data-jpa/src/test/java/org/apache/james/jpa/healthcheck/JPAHealthCheckTest.java
@@ -46,7 +46,7 @@ class JPAHealthCheckTest {
 
     @Test
     void testWhenActive() {
-        Result result = jpaHealthCheck.checkReactive().block();
+        Result result = jpaHealthCheck.check().block();
         ResultStatus healthy = ResultStatus.HEALTHY;
         assertThat(result.getStatus()).as("Result %s status should be %s", result.getStatus(), healthy)
                 .isEqualTo(healthy);
@@ -57,7 +57,7 @@ class JPAHealthCheckTest {
         jpaTestCluster.getEntityManagerFactory().close();
         Result result = Result.healthy(jpaHealthCheck.componentName());
         try {
-            result = jpaHealthCheck.checkReactive().block();
+            result = jpaHealthCheck.check().block();
         } catch (IllegalStateException e) {
             fail("The exception of the EMF was not handled property.ยช");
         }
diff --git a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/routes/HealthCheckRoutes.java b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/routes/HealthCheckRoutes.java
index ce75afa..32747c2 100644
--- a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/routes/HealthCheckRoutes.java
+++ b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/routes/HealthCheckRoutes.java
@@ -122,7 +122,7 @@ public class HealthCheckRoutes implements PublicRoutes {
             .findFirst()
             .orElseThrow(() -> throw404(componentName));
 
-        Result result = Mono.from(healthCheck.checkReactive()).block();
+        Result result = Mono.from(healthCheck.check()).block();
         logFailedCheck(result);
         response.status(getCorrespondingStatusCode(result.getStatus()));
         return new HealthCheckExecutionResultDto(result);
@@ -179,7 +179,7 @@ public class HealthCheckRoutes implements PublicRoutes {
 
     private Flux<Result> executeHealthChecks() {
         return Flux.fromIterable(healthChecks)
-            .flatMap(HealthCheck::checkReactive)
+            .flatMap(HealthCheck::check)
             .doOnNext(this::logFailedCheck);
     }
 
diff --git a/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/HealthCheckRoutesTest.java b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/HealthCheckRoutesTest.java
index a394531..939175f 100644
--- a/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/HealthCheckRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/HealthCheckRoutesTest.java
@@ -67,7 +67,7 @@ public class HealthCheckRoutesTest {
             }
 
             @Override
-            public Publisher<Result> checkReactive() {
+            public Publisher<Result> check() {
                 return Mono.just(result);
             }
         };


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


[james-project] 05/27: JAMES-3143 SolveMessageInconsistenciesTask/DTO

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 84374e53305d80483b09799537f22fc97c6fb2b9
Author: LanKhuat <kh...@gmail.com>
AuthorDate: Tue Apr 21 00:09:51 2020 +0700

    JAMES-3143 SolveMessageInconsistenciesTask/DTO
---
 .../mail/task/MessageInconsistenciesEntry.java     |  92 +++++++++++++
 .../mail/task/SolveMessageInconsistenciesTask.java | 143 +++++++++++++++++++++
 ...nconsistenciesTaskAdditionalInformationDTO.java | 135 +++++++++++++++++++
 .../task/SolveMessageInconsistenciesTaskDTO.java   |  60 +++++++++
 ...essageInconsistenciesTaskSerializationTest.java |  78 +++++++++++
 ...ssageInconsistencies.additionalInformation.json |  28 ++++
 .../json/solveMessageInconsistencies.task.json     |   3 +
 .../SolveMessageInconsistenciesRequestToTask.java  |  36 ++++++
 8 files changed, 575 insertions(+)

diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/MessageInconsistenciesEntry.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/MessageInconsistenciesEntry.java
new file mode 100644
index 0000000..0dec7f2
--- /dev/null
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/MessageInconsistenciesEntry.java
@@ -0,0 +1,92 @@
+/****************************************************************
+ * 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.mailbox.cassandra.mail.task;
+
+import java.util.Objects;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class MessageInconsistenciesEntry {
+
+    public interface Builder {
+        @FunctionalInterface
+        interface RequireMailboxId {
+            RequireMessageId mailboxId(String mailboxId);
+        }
+
+        @FunctionalInterface
+        interface RequireMessageId {
+            RequireMessageUid messageId(String messageId);
+        }
+
+        @FunctionalInterface
+        interface RequireMessageUid {
+            MessageInconsistenciesEntry messageUid(Long messageUid);
+        }
+    }
+
+    public static Builder.RequireMailboxId builder() {
+        return mailboxId -> messageId -> messageUid -> new MessageInconsistenciesEntry(mailboxId, messageId, messageUid);
+    }
+
+    private final String mailboxId;
+    private final String messageId;
+    private final Long messageUid;
+
+    private MessageInconsistenciesEntry(@JsonProperty("mailboxId") String mailboxId,
+                                        @JsonProperty("messageId") String messageId,
+                                       @JsonProperty("uid") Long messageUid) {
+        this.mailboxId = mailboxId;
+        this.messageId = messageId;
+        this.messageUid = messageUid;
+    }
+
+    @JsonProperty("mailboxId")
+    public String getMailboxId() {
+        return mailboxId;
+    }
+
+    @JsonProperty("messageId")
+    public String getMessageId() {
+        return messageId;
+    }
+
+    @JsonProperty("uid")
+    public Long getMessageUid() {
+        return messageUid;
+    }
+
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof MessageInconsistenciesEntry) {
+            MessageInconsistenciesEntry that = (MessageInconsistenciesEntry) o;
+
+            return Objects.equals(this.mailboxId, that.mailboxId)
+                && Objects.equals(this.messageId, that.messageId)
+                && Objects.equals(this.messageUid, that.messageUid);
+        }
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(mailboxId, messageId, messageUid);
+    }
+}
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesTask.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesTask.java
new file mode 100644
index 0000000..1fb540c
--- /dev/null
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesTask.java
@@ -0,0 +1,143 @@
+/****************************************************************
+ * 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.mailbox.cassandra.mail.task;
+
+import java.time.Clock;
+import java.time.Instant;
+import java.util.Optional;
+
+import org.apache.james.mailbox.cassandra.mail.task.SolveMessageInconsistenciesService.Context;
+import org.apache.james.mailbox.cassandra.mail.task.SolveMessageInconsistenciesService.Context.Snapshot;
+import org.apache.james.mailbox.model.ComposedMessageId;
+import org.apache.james.task.Task;
+import org.apache.james.task.TaskExecutionDetails;
+import org.apache.james.task.TaskType;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.steveash.guavate.Guavate;
+import com.google.common.collect.ImmutableList;
+
+public class SolveMessageInconsistenciesTask implements Task {
+
+    static final TaskType SOLVE_MESSAGE_INCONSISTENCIES = TaskType.of("solve-message-inconsistencies");
+
+    public static class Details implements TaskExecutionDetails.AdditionalInformation {
+        private final Instant instant;
+        private final long processedImapUidEntries;
+        private final long processedMessageIdEntries;
+        private final long addedMessageIdEntries;
+        private final long updatedMessageIdEntries;
+        private final long removedMessageIdEntries;
+        private final ImmutableList<MessageInconsistenciesEntry> fixedInconsistencies;
+        private final ImmutableList<MessageInconsistenciesEntry> errors;
+
+        public Details(Instant instant, long processedImapUidEntries, long processedMessageIdEntries,
+                       long addedMessageIdEntries, long updatedMessageIdEntries, long removedMessageIdEntries,
+                       ImmutableList<MessageInconsistenciesEntry> fixedInconsistencies, ImmutableList<MessageInconsistenciesEntry> errors) {
+            this.instant = instant;
+            this.processedImapUidEntries = processedImapUidEntries;
+            this.processedMessageIdEntries = processedMessageIdEntries;
+            this.addedMessageIdEntries = addedMessageIdEntries;
+            this.updatedMessageIdEntries = updatedMessageIdEntries;
+            this.removedMessageIdEntries = removedMessageIdEntries;
+            this.fixedInconsistencies = fixedInconsistencies;
+            this.errors = errors;
+        }
+
+        @Override
+        public Instant timestamp() {
+            return instant;
+        }
+
+        @JsonProperty("processedImapUidEntries")
+        public long getProcessedImapUidEntries() {
+            return processedImapUidEntries;
+        }
+
+        @JsonProperty("processedMessageIdEntries")
+        public long getProcessedMessageIdEntries() {
+            return processedMessageIdEntries;
+        }
+
+        @JsonProperty("addedMessageIdEntries")
+        public long getAddedMessageIdEntries() {
+            return addedMessageIdEntries;
+        }
+
+        @JsonProperty("updatedMessageIdEntries")
+        public long getUpdatedMessageIdEntries() {
+            return updatedMessageIdEntries;
+        }
+
+        @JsonProperty("removedMessageIdEntries")
+        public long getRemovedMessageIdEntries() {
+            return removedMessageIdEntries;
+        }
+
+        @JsonProperty("fixedInconsistencies")
+        public ImmutableList<MessageInconsistenciesEntry> getFixedInconsistencies() {
+            return fixedInconsistencies;
+        }
+
+        @JsonProperty("errors")
+        public ImmutableList<MessageInconsistenciesEntry> getErrors() {
+            return errors;
+        }
+    }
+
+    private final SolveMessageInconsistenciesService service;
+    private Context context;
+
+    public SolveMessageInconsistenciesTask(SolveMessageInconsistenciesService service) {
+        this.service = service;
+        this.context = new Context();
+    }
+
+    @Override
+    public Result run() {
+        return service.fixMessageInconsistencies(context)
+            .block();
+    }
+
+    @Override
+    public TaskType type() {
+        return SOLVE_MESSAGE_INCONSISTENCIES;
+    }
+
+    @Override
+    public Optional<TaskExecutionDetails.AdditionalInformation> details() {
+        Snapshot snapshot = context.snapshot();
+        return Optional.of(new Details(Clock.systemUTC().instant(), snapshot.getProcessedImapUidEntries(), snapshot.getProcessedMessageIdEntries(),
+            snapshot.getAddedMessageIdEntries(), snapshot.getUpdatedMessageIdEntries(), snapshot.getRemovedMessageIdEntries(),
+            snapshot.getFixedInconsistencies().stream()
+                .map(this::toMessageInconsistenciesEntry)
+                .collect(Guavate.toImmutableList()),
+            snapshot.getErrors().stream()
+                .map(this::toMessageInconsistenciesEntry)
+                .collect(Guavate.toImmutableList())));
+    }
+
+    private MessageInconsistenciesEntry toMessageInconsistenciesEntry(ComposedMessageId composedMessageId) {
+        return MessageInconsistenciesEntry.builder()
+            .mailboxId(composedMessageId.getMailboxId().serialize())
+            .messageId(composedMessageId.getMessageId().serialize())
+            .messageUid(composedMessageId.getUid().asLong());
+    }
+}
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesTaskAdditionalInformationDTO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesTaskAdditionalInformationDTO.java
new file mode 100644
index 0000000..a7792db
--- /dev/null
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesTaskAdditionalInformationDTO.java
@@ -0,0 +1,135 @@
+/****************************************************************
+ * 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.mailbox.cassandra.mail.task;
+
+import static org.apache.james.mailbox.cassandra.mail.task.SolveMessageInconsistenciesTask.SOLVE_MESSAGE_INCONSISTENCIES;
+
+import java.time.Instant;
+
+import org.apache.james.json.DTOModule;
+import org.apache.james.mailbox.cassandra.mail.task.SolveMessageInconsistenciesTask.Details;
+import org.apache.james.server.task.json.dto.AdditionalInformationDTO;
+import org.apache.james.server.task.json.dto.AdditionalInformationDTOModule;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.ImmutableList;
+
+public class SolveMessageInconsistenciesTaskAdditionalInformationDTO implements AdditionalInformationDTO {
+
+    private static SolveMessageInconsistenciesTaskAdditionalInformationDTO fromDomainObject(Details details, String type) {
+        return new SolveMessageInconsistenciesTaskAdditionalInformationDTO(
+            details.timestamp(),
+            type,
+            details.getProcessedImapUidEntries(),
+            details.getProcessedMessageIdEntries(),
+            details.getAddedMessageIdEntries(),
+            details.getUpdatedMessageIdEntries(),
+            details.getRemovedMessageIdEntries(),
+            details.getFixedInconsistencies(),
+            details.getErrors());
+    }
+
+    public static final AdditionalInformationDTOModule<Details, SolveMessageInconsistenciesTaskAdditionalInformationDTO> MODULE =
+        DTOModule
+            .forDomainObject(Details.class)
+            .convertToDTO(SolveMessageInconsistenciesTaskAdditionalInformationDTO.class)
+            .toDomainObjectConverter(SolveMessageInconsistenciesTaskAdditionalInformationDTO::toDomainObject)
+            .toDTOConverter(SolveMessageInconsistenciesTaskAdditionalInformationDTO::fromDomainObject)
+            .typeName(SOLVE_MESSAGE_INCONSISTENCIES.asString())
+            .withFactory(AdditionalInformationDTOModule::new);
+
+    private final Instant timestamp;
+    private final String type;
+    private final long processedImapUidEntries;
+    private final long processedMessageIdEntries;
+    private final long addedMessageIdEntries;
+    private final long updatedMessageIdEntries;
+    private final long removedMessageIdEntries;
+    private final ImmutableList<MessageInconsistenciesEntry> fixedInconsistencies;
+    private final ImmutableList<MessageInconsistenciesEntry> errors;
+
+    public SolveMessageInconsistenciesTaskAdditionalInformationDTO(@JsonProperty("timestamp") Instant timestamp, @JsonProperty("type") String type,
+                                                                   @JsonProperty("processedImapUidEntries") long processedImapUidEntries,
+                                                                   @JsonProperty("processedMessageIdEntries") long processedMessageIdEntries,
+                                                                   @JsonProperty("addedMessageIdEntries") long addedMessageIdEntries,
+                                                                   @JsonProperty("updatedMessageIdEntries") long updatedMessageIdEntries,
+                                                                   @JsonProperty("removedMessageIdEntries")long removedMessageIdEntries,
+                                                                   @JsonProperty("fixedInconsistencies") ImmutableList<MessageInconsistenciesEntry> fixedInconsistencies,
+                                                                   @JsonProperty("errors") ImmutableList<MessageInconsistenciesEntry> errors) {
+        this.timestamp = timestamp;
+        this.type = type;
+        this.processedImapUidEntries = processedImapUidEntries;
+        this.processedMessageIdEntries = processedMessageIdEntries;
+        this.addedMessageIdEntries = addedMessageIdEntries;
+        this.updatedMessageIdEntries = updatedMessageIdEntries;
+        this.removedMessageIdEntries = removedMessageIdEntries;
+        this.fixedInconsistencies = fixedInconsistencies;
+        this.errors = errors;
+    }
+
+    public long getProcessedImapUidEntries() {
+        return processedImapUidEntries;
+    }
+
+    public long getProcessedMessageIdEntries() {
+        return processedMessageIdEntries;
+    }
+
+    public long getAddedMessageIdEntries() {
+        return addedMessageIdEntries;
+    }
+
+    public long getUpdatedMessageIdEntries() {
+        return updatedMessageIdEntries;
+    }
+
+    public long getRemovedMessageIdEntries() {
+        return removedMessageIdEntries;
+    }
+
+    public ImmutableList<MessageInconsistenciesEntry> getFixedInconsistencies() {
+        return fixedInconsistencies;
+    }
+
+    public ImmutableList<MessageInconsistenciesEntry> getErrors() {
+        return errors;
+    }
+
+    @Override
+    public String getType() {
+        return this.type;
+    }
+
+    @Override
+    public Instant getTimestamp() {
+        return this.timestamp;
+    }
+
+    private Details toDomainObject() {
+        return new Details(timestamp,
+            processedImapUidEntries,
+            processedMessageIdEntries,
+            addedMessageIdEntries,
+            updatedMessageIdEntries,
+            removedMessageIdEntries,
+            fixedInconsistencies,
+            errors);
+    }
+}
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesTaskDTO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesTaskDTO.java
new file mode 100644
index 0000000..7caeea6
--- /dev/null
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesTaskDTO.java
@@ -0,0 +1,60 @@
+/****************************************************************
+ * 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.mailbox.cassandra.mail.task;
+
+import static org.apache.james.mailbox.cassandra.mail.task.SolveMessageInconsistenciesTask.SOLVE_MESSAGE_INCONSISTENCIES;
+
+import org.apache.james.json.DTOModule;
+import org.apache.james.server.task.json.dto.TaskDTO;
+import org.apache.james.server.task.json.dto.TaskDTOModule;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class SolveMessageInconsistenciesTaskDTO implements TaskDTO {
+
+    private static SolveMessageInconsistenciesTaskDTO toDTO(SolveMessageInconsistenciesTask domainObject, String typeName) {
+        return new SolveMessageInconsistenciesTaskDTO(typeName);
+    }
+
+    public static TaskDTOModule<SolveMessageInconsistenciesTask, SolveMessageInconsistenciesTaskDTO> module(SolveMessageInconsistenciesService service) {
+        return DTOModule
+            .forDomainObject(SolveMessageInconsistenciesTask.class)
+            .convertToDTO(SolveMessageInconsistenciesTaskDTO.class)
+            .toDomainObjectConverter(dto -> dto.toDomainObject(service))
+            .toDTOConverter(SolveMessageInconsistenciesTaskDTO::toDTO)
+            .typeName(SOLVE_MESSAGE_INCONSISTENCIES.asString())
+            .withFactory(TaskDTOModule::new);
+    }
+
+    private final String type;
+
+    public SolveMessageInconsistenciesTaskDTO(@JsonProperty("type") String type) {
+        this.type = type;
+    }
+
+    private SolveMessageInconsistenciesTask toDomainObject(SolveMessageInconsistenciesService service) {
+        return new SolveMessageInconsistenciesTask(service);
+    }
+
+    @Override
+    public String getType() {
+        return type;
+    }
+}
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesTaskSerializationTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesTaskSerializationTest.java
new file mode 100644
index 0000000..f6ec69d
--- /dev/null
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesTaskSerializationTest.java
@@ -0,0 +1,78 @@
+/****************************************************************
+ * 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.mailbox.cassandra.mail.task;
+
+import static org.mockito.Mockito.mock;
+
+import java.time.Instant;
+
+import org.apache.james.JsonSerializationVerifier;
+import org.apache.james.mailbox.cassandra.mail.task.SolveMessageInconsistenciesTask.Details;
+import org.apache.james.util.ClassLoaderUtils;
+import org.junit.jupiter.api.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class SolveMessageInconsistenciesTaskSerializationTest {
+
+    private static final SolveMessageInconsistenciesService SERVICE = mock(SolveMessageInconsistenciesService.class);
+    private static final SolveMessageInconsistenciesTask TASK = new SolveMessageInconsistenciesTask(SERVICE);
+
+    private static final Instant INSTANT = Instant.parse("2007-12-03T10:15:30.00Z");
+    private static final String MAILBOX_ID = "551f0580-82fb-11ea-970e-f9c83d4cf8c2";
+    private static final String MESSAGE_ID_1 = "d2bee791-7e63-11ea-883c-95b84008f979";
+    private static final String MESSAGE_ID_2 = "d2bee792-7e63-11ea-883c-95b84008f979";
+    private static final String MESSAGE_ID_3 = "ffffffff-7e63-11ea-883c-95b84008f979";
+    private static final Long MESSAGE_UID_1 = 1L;
+    private static final Long MESSAGE_UID_2 = 2L;
+    private static final Long MESSAGE_UID_3 = 3L;
+
+    private static final MessageInconsistenciesEntry MESSAGE_1 = MessageInconsistenciesEntry.builder()
+        .mailboxId(MAILBOX_ID)
+        .messageId(MESSAGE_ID_1)
+        .messageUid(MESSAGE_UID_1);
+    private static final MessageInconsistenciesEntry MESSAGE_2 = MessageInconsistenciesEntry.builder()
+        .mailboxId(MAILBOX_ID)
+        .messageId(MESSAGE_ID_2)
+        .messageUid(MESSAGE_UID_2);
+    private static final MessageInconsistenciesEntry MESSAGE_3 = MessageInconsistenciesEntry.builder()
+        .mailboxId(MAILBOX_ID)
+        .messageId(MESSAGE_ID_3)
+        .messageUid(MESSAGE_UID_3);
+
+    private static final Details DETAILS = new SolveMessageInconsistenciesTask.Details(INSTANT, 2, 1, 1, 0, 1, ImmutableList.of(MESSAGE_1, MESSAGE_2), ImmutableList.of(MESSAGE_3));
+
+    @Test
+    void taskShouldBeSerializable() throws Exception {
+        JsonSerializationVerifier.dtoModule(SolveMessageInconsistenciesTaskDTO.module(SERVICE))
+            .bean(TASK)
+            .json(ClassLoaderUtils.getSystemResourceAsString("json/solveMessageInconsistencies.task.json"))
+            .verify();
+    }
+
+    @Test
+    void additionalInformationShouldBeSerializable() throws Exception {
+        JsonSerializationVerifier.dtoModule(SolveMessageInconsistenciesTaskAdditionalInformationDTO.MODULE)
+            .bean(DETAILS)
+            .json(ClassLoaderUtils.getSystemResourceAsString("json/solveMessageInconsistencies.additionalInformation.json"))
+            .verify();
+    }
+
+}
diff --git a/mailbox/cassandra/src/test/resources/json/solveMessageInconsistencies.additionalInformation.json b/mailbox/cassandra/src/test/resources/json/solveMessageInconsistencies.additionalInformation.json
new file mode 100644
index 0000000..5ab72a0
--- /dev/null
+++ b/mailbox/cassandra/src/test/resources/json/solveMessageInconsistencies.additionalInformation.json
@@ -0,0 +1,28 @@
+{
+  "type":"solve-message-inconsistencies",
+  "timestamp":"2007-12-03T10:15:30Z",
+  "processedImapUidEntries": 2,
+  "processedMessageIdEntries": 1,
+  "addedMessageIdEntries": 1,
+  "updatedMessageIdEntries": 0,
+  "removedMessageIdEntries": 1,
+  "fixedInconsistencies": [
+    {
+      "mailboxId": "551f0580-82fb-11ea-970e-f9c83d4cf8c2",
+      "messageId": "d2bee791-7e63-11ea-883c-95b84008f979",
+      "uid": 1
+    },
+    {
+      "mailboxId": "551f0580-82fb-11ea-970e-f9c83d4cf8c2",
+      "messageId": "d2bee792-7e63-11ea-883c-95b84008f979",
+      "uid": 2
+    }
+  ],
+  "errors": [
+    {
+      "mailboxId": "551f0580-82fb-11ea-970e-f9c83d4cf8c2",
+      "messageId": "ffffffff-7e63-11ea-883c-95b84008f979",
+      "uid": 3
+    }
+  ]
+}
\ No newline at end of file
diff --git a/mailbox/cassandra/src/test/resources/json/solveMessageInconsistencies.task.json b/mailbox/cassandra/src/test/resources/json/solveMessageInconsistencies.task.json
new file mode 100644
index 0000000..f1c66ee
--- /dev/null
+++ b/mailbox/cassandra/src/test/resources/json/solveMessageInconsistencies.task.json
@@ -0,0 +1,3 @@
+{
+  "type":"solve-message-inconsistencies"
+}
\ No newline at end of file
diff --git a/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/routes/SolveMessageInconsistenciesRequestToTask.java b/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/routes/SolveMessageInconsistenciesRequestToTask.java
new file mode 100644
index 0000000..8702b86
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/routes/SolveMessageInconsistenciesRequestToTask.java
@@ -0,0 +1,36 @@
+/****************************************************************
+ * 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.webadmin.routes;
+
+import javax.inject.Inject;
+
+import org.apache.james.mailbox.cassandra.mail.task.SolveMessageInconsistenciesService;
+import org.apache.james.mailbox.cassandra.mail.task.SolveMessageInconsistenciesTask;
+import org.apache.james.webadmin.tasks.TaskFromRequestRegistry;
+import org.apache.james.webadmin.tasks.TaskRegistrationKey;
+
+public class SolveMessageInconsistenciesRequestToTask extends TaskFromRequestRegistry.TaskRegistration {
+    private static final TaskRegistrationKey REGISTRATION_KEY = TaskRegistrationKey.of("SolveInconsistencies");
+
+    @Inject
+    public SolveMessageInconsistenciesRequestToTask(SolveMessageInconsistenciesService service) {
+        super(REGISTRATION_KEY, request -> new SolveMessageInconsistenciesTask(service));
+    }
+}


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


[james-project] 03/27: JAMES-3143 SolveMessageInconsistenciesService/Test

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 e31d7894b0dd820553b3dfc86f96536bd02e2dc9
Author: LanKhuat <kh...@gmail.com>
AuthorDate: Tue Apr 14 10:24:28 2020 +0700

    JAMES-3143 SolveMessageInconsistenciesService/Test
---
 .../cassandra/mail/CassandraMessageIdDAO.java      |   2 +-
 .../mail/CassandraMessageIdToImapUidDAO.java       |   2 +-
 .../task/SolveMessageInconsistenciesService.java   | 131 +++++++
 .../SolveMessageInconsistenciesServiceTest.java    | 398 +++++++++++++++++++++
 4 files changed, 531 insertions(+), 2 deletions(-)

diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAO.java
index c2342b3..d15e121 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAO.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAO.java
@@ -149,7 +149,7 @@ public class CassandraMessageIdDAO {
     }
 
     private PreparedStatement prepareList(Session session) {
-        return session.prepare(select(new String[] {MESSAGE_ID, MAILBOX_ID, IMAP_UID})
+        return session.prepare(select(FIELDS)
             .from(TABLE_NAME));
     }
 
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAO.java
index 83099ba..91cf3e3 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAO.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAO.java
@@ -139,7 +139,7 @@ public class CassandraMessageIdToImapUidDAO {
     }
 
     private PreparedStatement prepareList(Session session) {
-        return session.prepare(select(new String[] {MESSAGE_ID, MAILBOX_ID, IMAP_UID}).from(TABLE_NAME));
+        return session.prepare(select(FIELDS).from(TABLE_NAME));
     }
 
     private PreparedStatement prepareSelect(Session session) {
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesService.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesService.java
new file mode 100644
index 0000000..f67ff73
--- /dev/null
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesService.java
@@ -0,0 +1,131 @@
+/****************************************************************
+ * 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.mailbox.cassandra.mail.task;
+
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+import org.apache.james.mailbox.cassandra.ids.CassandraId;
+import org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
+import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdDAO;
+import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdToImapUidDAO;
+import org.apache.james.mailbox.model.ComposedMessageIdWithMetaData;
+import org.apache.james.task.Task;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public class SolveMessageInconsistenciesService {
+    public static final Logger LOGGER = LoggerFactory.getLogger(SolveMessageInconsistenciesService.class);
+
+    private final CassandraMessageIdToImapUidDAO idToImapUidDAO;
+    private final CassandraMessageIdDAO messageIdDAO;
+
+    @Inject
+    SolveMessageInconsistenciesService(CassandraMessageIdToImapUidDAO idToImapUidDAO, CassandraMessageIdDAO messageIdDAO) {
+        this.idToImapUidDAO = idToImapUidDAO;
+        this.messageIdDAO = messageIdDAO;
+    }
+
+    Mono<Task.Result> fixMessageInconsistencies() {
+        return Flux.concat(
+            fixMessageIdInconsistencies(),
+            fixImapUidInconsistencies())
+            .reduce(Task.Result.COMPLETED, Task::combine);
+    }
+
+    private Mono<Task.Result> fixMessageIdInconsistencies() {
+        return idToImapUidDAO.retrieveAllMessages()
+            .concatMap(this::fetchAndFixMessageId)
+            .reduce(Task.Result.COMPLETED, Task::combine);
+    }
+
+    private Mono<Task.Result> fetchAndFixMessageId(ComposedMessageIdWithMetaData message) {
+        return idToImapUidDAO.retrieve((CassandraMessageId) message.getComposedMessageId().getMessageId(), Optional.of((CassandraId) message.getComposedMessageId().getMailboxId()))
+            .single()
+            .flatMap(upToDateMessage -> messageIdDAO.retrieve((CassandraId) upToDateMessage.getComposedMessageId().getMailboxId(), upToDateMessage.getComposedMessageId().getUid())
+                .flatMap(Mono::justOrEmpty)
+                .flatMap(fetchedFromMessageId -> fixWhenMessageFoundInMessageId(upToDateMessage, fetchedFromMessageId)))
+            .switchIfEmpty(fixWhenMessageNotFoundInMessageId(message));
+    }
+
+    private Mono<Task.Result> fixWhenMessageFoundInMessageId(ComposedMessageIdWithMetaData messageFromImapUid, ComposedMessageIdWithMetaData messageFromMessageId) {
+        return Mono.fromCallable(() -> messageFromImapUid.equals(messageFromMessageId))
+            .flatMap(isEqual -> {
+                if (isEqual) {
+                    return Mono.just(Task.Result.COMPLETED);
+                }
+
+                return messageIdDAO.updateMetadata(messageFromImapUid)
+                    .then(Mono.just(Task.Result.COMPLETED))
+                    .onErrorResume(error -> {
+                        LOGGER.error("Error when fixing inconsistency for message: {}", messageFromImapUid, error);
+                        return Mono.just(Task.Result.PARTIAL);
+                    });
+            });
+    }
+
+    private Mono<Task.Result> fixWhenMessageNotFoundInMessageId(ComposedMessageIdWithMetaData message) {
+        return messageIdDAO.insert(message)
+            .then(Mono.just(Task.Result.COMPLETED))
+            .onErrorResume(error -> {
+                LOGGER.error("Error when fixing inconsistency for message: {}", message, error);
+                return Mono.just(Task.Result.PARTIAL);
+            });
+    }
+
+    @VisibleForTesting
+    Mono<Task.Result> fixImapUidInconsistencies() {
+        return messageIdDAO.retrieveAllMessages()
+            .concatMap(message -> process(message))
+            .reduce(Task.Result.COMPLETED, Task::combine);
+    }
+
+    private Mono<Task.Result> process(ComposedMessageIdWithMetaData message) {
+        return messageIdDAO.retrieve((CassandraId) message.getComposedMessageId().getMailboxId(), message.getComposedMessageId().getUid())
+            .flatMap(Mono::justOrEmpty)
+            .flatMap(this::fixWhenMessageFound)
+            .switchIfEmpty(Mono.just(Task.Result.COMPLETED));
+    }
+
+    private Mono<Task.Result> fixWhenMessageFound(ComposedMessageIdWithMetaData message) {
+        return idToImapUidDAO.retrieve((CassandraMessageId) message.getComposedMessageId().getMessageId(), Optional.of((CassandraId) message.getComposedMessageId().getMailboxId()))
+            .flatMap(uidRecord -> {
+                if (uidRecord.equals(message)) {
+                    return Mono.just(Task.Result.COMPLETED);
+                }
+
+                return messageIdDAO.updateMetadata(uidRecord)
+                    .then(Mono.just(Task.Result.COMPLETED));
+            })
+            .switchIfEmpty(messageIdDAO.delete((CassandraId) message.getComposedMessageId().getMailboxId(), message.getComposedMessageId().getUid())
+                .then(Mono.just(Task.Result.COMPLETED)))
+            .single()
+            .onErrorResume(error -> {
+                LOGGER.error("Error when fixing inconsistency for message {}", message, error);
+                return Mono.just(Task.Result.PARTIAL);
+            });
+    }
+}
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesServiceTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesServiceTest.java
new file mode 100644
index 0000000..8bb845b
--- /dev/null
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesServiceTest.java
@@ -0,0 +1,398 @@
+/****************************************************************
+ * 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.mailbox.cassandra.mail.task;
+
+import static org.apache.james.backends.cassandra.Scenario.Builder.fail;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.doReturn;
+
+import java.util.Optional;
+
+import javax.mail.Flags;
+
+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.mailbox.MessageUid;
+import org.apache.james.mailbox.ModSeq;
+import org.apache.james.mailbox.cassandra.ids.CassandraId;
+import org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
+import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdDAO;
+import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdToImapUidDAO;
+import org.apache.james.mailbox.cassandra.modules.CassandraMessageModule;
+import org.apache.james.mailbox.model.ComposedMessageId;
+import org.apache.james.mailbox.model.ComposedMessageIdWithMetaData;
+import org.apache.james.task.Task;
+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 org.junit.jupiter.api.extension.RegisterExtension;
+
+import reactor.core.publisher.Mono;
+
+public class SolveMessageInconsistenciesServiceTest {
+
+    private static final CassandraId MAILBOX_ID = CassandraId.timeBased();
+    private static final CassandraMessageId MESSAGE_ID_1 = new CassandraMessageId.Factory().fromString("d2bee791-7e63-11ea-883c-95b84008f979");
+    private static final CassandraMessageId MESSAGE_ID_2 = new CassandraMessageId.Factory().fromString("eeeeeeee-7e63-11ea-883c-95b84008f979");
+    private static final MessageUid MESSAGE_UID_1 = MessageUid.of(1L);
+    private static final MessageUid MESSAGE_UID_2 = MessageUid.of(2L);
+    private static final ModSeq MOD_SEQ_1 = ModSeq.of(1L);
+    private static final ModSeq MOD_SEQ_2 = ModSeq.of(2L);
+
+    private static final ComposedMessageIdWithMetaData MESSAGE_1 = ComposedMessageIdWithMetaData.builder()
+        .composedMessageId(new ComposedMessageId(MAILBOX_ID, MESSAGE_ID_1, MESSAGE_UID_1))
+        .modSeq(MOD_SEQ_1)
+        .flags(new Flags())
+        .build();
+
+    private static final ComposedMessageIdWithMetaData MESSAGE_1_WITH_SEEN_FLAG = ComposedMessageIdWithMetaData.builder()
+        .composedMessageId(new ComposedMessageId(MAILBOX_ID, MESSAGE_ID_1, MESSAGE_UID_1))
+        .modSeq(MOD_SEQ_1)
+        .flags(new Flags(Flags.Flag.SEEN))
+        .build();
+
+    private static final ComposedMessageIdWithMetaData MESSAGE_1_WITH_MOD_SEQ_2 = ComposedMessageIdWithMetaData.builder()
+        .composedMessageId(new ComposedMessageId(MAILBOX_ID, MESSAGE_ID_1, MESSAGE_UID_1))
+        .modSeq(MOD_SEQ_2)
+        .flags(new Flags(Flags.Flag.SEEN))
+        .build();
+
+    private static final ComposedMessageIdWithMetaData MESSAGE_2 = ComposedMessageIdWithMetaData.builder()
+        .composedMessageId(new ComposedMessageId(MAILBOX_ID, MESSAGE_ID_2, MESSAGE_UID_2))
+        .modSeq(MOD_SEQ_2)
+        .flags(new Flags())
+        .build();
+
+    @RegisterExtension
+    static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(
+        CassandraModule.aggregateModules(
+            CassandraSchemaVersionModule.MODULE,
+            CassandraMessageModule.MODULE));
+
+    CassandraMessageIdToImapUidDAO imapUidDAO;
+    CassandraMessageIdDAO messageIdDAO;
+    SolveMessageInconsistenciesService testee;
+
+    @BeforeEach
+    void setUp(CassandraCluster cassandra) {
+        imapUidDAO = new CassandraMessageIdToImapUidDAO(cassandra.getConf(), new CassandraMessageId.Factory());
+        messageIdDAO = new CassandraMessageIdDAO(cassandra.getConf(), new CassandraMessageId.Factory());
+        testee = new SolveMessageInconsistenciesService(imapUidDAO, messageIdDAO);
+    }
+
+    @Test
+    void fixMessageInconsistenciesShouldReturnCompletedWhenNoData() {
+        assertThat(testee.fixMessageInconsistencies().block())
+            .isEqualTo(Task.Result.COMPLETED);
+    }
+
+    @Test
+    void fixMessageInconsistenciesShouldReturnCompletedWhenConsistentData() {
+        imapUidDAO.insert(MESSAGE_1).block();
+        messageIdDAO.insert(MESSAGE_1).block();
+
+        assertThat(testee.fixMessageInconsistencies().block())
+            .isEqualTo(Task.Result.COMPLETED);
+    }
+
+    @Test
+    void fixMailboxInconsistenciesShouldNotAlterStateWhenEmpty() {
+        testee.fixMessageInconsistencies().block();
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(imapUidDAO.retrieveAllMessages().collectList().block()).isEmpty();
+            softly.assertThat(messageIdDAO.retrieveAllMessages().collectList().block()).isEmpty();
+        });
+    }
+
+    @Test
+    void fixMailboxInconsistenciesShouldNotAlterStateWhenConsistent() {
+        imapUidDAO.insert(MESSAGE_1).block();
+        messageIdDAO.insert(MESSAGE_1).block();
+
+        testee.fixMessageInconsistencies().block();
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(imapUidDAO.retrieveAllMessages().collectList().block())
+                .containsExactlyInAnyOrder(MESSAGE_1);
+            softly.assertThat(messageIdDAO.retrieveAllMessages().collectList().block())
+                .containsExactlyInAnyOrder(MESSAGE_1);
+        });
+    }
+
+    @Nested
+    class ImapUidScanningTest {
+
+        @Test
+        void fixMessageInconsistenciesShouldReturnCompletedWhenInconsistentData() {
+            imapUidDAO.insert(MESSAGE_1).block();
+
+            assertThat(testee.fixMessageInconsistencies().block())
+                .isEqualTo(Task.Result.COMPLETED);
+        }
+
+        @Test
+        void fixMessageInconsistenciesShouldResolveInconsistentData() {
+            imapUidDAO.insert(MESSAGE_1).block();
+
+            testee.fixMessageInconsistencies().block();
+
+            SoftAssertions.assertSoftly(softly -> {
+                softly.assertThat(imapUidDAO.retrieve(MESSAGE_ID_1, Optional.of(MAILBOX_ID)).collectList().block())
+                    .containsExactly(MESSAGE_1);
+                softly.assertThat(messageIdDAO.retrieve(MAILBOX_ID, MESSAGE_UID_1).block().get())
+                    .isEqualTo(MESSAGE_1);
+            });
+        }
+
+        @Test
+        void fixMessageInconsistenciesShouldReturnCompletedWhenInconsistentFlags() {
+            imapUidDAO.insert(MESSAGE_1).block();
+            messageIdDAO.insert(MESSAGE_1_WITH_SEEN_FLAG).block();
+
+            assertThat(testee.fixMessageInconsistencies().block())
+                .isEqualTo(Task.Result.COMPLETED);
+        }
+
+        @Test
+        void fixMessageInconsistenciesShouldResolveInconsistentFlags() {
+            imapUidDAO.insert(MESSAGE_1).block();
+            messageIdDAO.insert(MESSAGE_1_WITH_SEEN_FLAG).block();
+
+            testee.fixMessageInconsistencies().block();
+
+            SoftAssertions.assertSoftly(softly -> {
+                softly.assertThat(imapUidDAO.retrieve(MESSAGE_ID_1, Optional.of(MAILBOX_ID)).collectList().block())
+                    .containsExactly(MESSAGE_1);
+                softly.assertThat(messageIdDAO.retrieve(MAILBOX_ID, MESSAGE_UID_1).block().get())
+                    .isEqualTo(MESSAGE_1);
+            });
+        }
+
+        @Test
+        void fixMessageInconsistenciesShouldReturnCompletedWhenInconsistentModSeq() {
+            imapUidDAO.insert(MESSAGE_1).block();
+            messageIdDAO.insert(MESSAGE_1_WITH_MOD_SEQ_2).block();
+
+            assertThat(testee.fixMessageInconsistencies().block())
+                .isEqualTo(Task.Result.COMPLETED);
+        }
+
+        @Test
+        void fixMessageInconsistenciesShouldResolveInconsistentModSeq() {
+            imapUidDAO.insert(MESSAGE_1).block();
+            messageIdDAO.insert(MESSAGE_1_WITH_MOD_SEQ_2).block();
+
+            testee.fixMessageInconsistencies().block();
+
+            SoftAssertions.assertSoftly(softly -> {
+                softly.assertThat(imapUidDAO.retrieve(MESSAGE_ID_1, Optional.of(MAILBOX_ID)).collectList().block())
+                    .containsExactly(MESSAGE_1);
+                softly.assertThat(messageIdDAO.retrieve(MAILBOX_ID, MESSAGE_UID_1).block().get())
+                    .isEqualTo(MESSAGE_1);
+            });
+        }
+
+        @Nested
+        class FailureTesting {
+            @Test
+            void fixMessageInconsistenciesShouldReturnPartialWhenError(CassandraCluster cassandra) {
+                imapUidDAO.insert(MESSAGE_1).block();
+
+                cassandra.getConf()
+                    .registerScenario(fail()
+                        .forever()
+                        .whenQueryStartsWith("INSERT INTO messageIdTable (mailboxId,uid,modSeq,messageId,flagAnswered,flagDeleted,flagDraft,flagFlagged,flagRecent,flagSeen,flagUser,userFlags) VALUES (:mailboxId,:uid,:modSeq,:messageId,:flagAnswered,:flagDeleted,:flagDraft,:flagFlagged,:flagRecent,:flagSeen,:flagUser,:userFlags)"));
+
+                assertThat(testee.fixMessageInconsistencies().block())
+                    .isEqualTo(Task.Result.PARTIAL);
+            }
+
+            @Test
+            void fixMessageInconsistenciesShouldReturnPartialWhenPartialError(CassandraCluster cassandra) {
+                imapUidDAO.insert(MESSAGE_1).block();
+                imapUidDAO.insert(MESSAGE_2).block();
+
+                cassandra.getConf()
+                    .registerScenario(fail()
+                        .times(1)
+                        .whenQueryStartsWith("INSERT INTO messageIdTable (mailboxId,uid,modSeq,messageId,flagAnswered,flagDeleted,flagDraft,flagFlagged,flagRecent,flagSeen,flagUser,userFlags) VALUES (:mailboxId,:uid,:modSeq,:messageId,:flagAnswered,:flagDeleted,:flagDraft,:flagFlagged,:flagRecent,:flagSeen,:flagUser,:userFlags)"));
+
+                assertThat(testee.fixMessageInconsistencies().block())
+                    .isEqualTo(Task.Result.PARTIAL);
+            }
+
+            @Test
+            void fixMessageInconsistenciesShouldResolveSuccessPartially(CassandraCluster cassandra) {
+                imapUidDAO.insert(MESSAGE_1).block();
+                imapUidDAO.insert(MESSAGE_2).block();
+
+                cassandra.getConf()
+                    .registerScenario(fail()
+                        .times(1)
+                        .whenQueryStartsWith("INSERT INTO messageIdTable (mailboxId,uid,modSeq,messageId,flagAnswered,flagDeleted,flagDraft,flagFlagged,flagRecent,flagSeen,flagUser,userFlags) VALUES (:mailboxId,:uid,:modSeq,d2bee791-7e63-11ea-883c-95b84008f979,:flagAnswered,:flagDeleted,:flagDraft,:flagFlagged,:flagRecent,:flagSeen,:flagUser,:userFlags)"));
+
+                testee.fixMessageInconsistencies().block();
+
+                SoftAssertions.assertSoftly(softly -> {
+                    softly.assertThat(imapUidDAO.retrieve(MESSAGE_ID_2, Optional.of(MAILBOX_ID)).collectList().block())
+                        .containsExactly(MESSAGE_2);
+                    softly.assertThat(messageIdDAO.retrieve(MAILBOX_ID, MESSAGE_UID_2).block().get())
+                        .isEqualTo(MESSAGE_2);
+                });
+            }
+        }
+    }
+
+    @Nested
+    class MessageIdScanningTest {
+
+        @Test
+        void fixMessageInconsistenciesShouldReturnCompletedWhenInconsistentData() {
+            messageIdDAO.insert(MESSAGE_1).block();
+
+            assertThat(testee.fixMessageInconsistencies().block())
+                .isEqualTo(Task.Result.COMPLETED);
+        }
+
+        @Test
+        void fixMessageInconsistenciesShouldResolveInconsistentData() {
+            messageIdDAO.insert(MESSAGE_1).block();
+
+            testee.fixMessageInconsistencies().block();
+
+            SoftAssertions.assertSoftly(softly -> {
+                softly.assertThat(imapUidDAO.retrieveAllMessages().collectList().block())
+                    .isEmpty();
+                softly.assertThat(messageIdDAO.retrieveAllMessages().collectList().block())
+                    .isEmpty();
+            });
+        }
+
+        @Test
+        void fixMessageInconsistenciesShouldReturnCompletedWhenPartialInconsistentData() {
+            messageIdDAO.insert(MESSAGE_1).block();
+            messageIdDAO.insert(MESSAGE_2).block();
+
+            imapUidDAO.insert(MESSAGE_1).block();
+
+            assertThat(testee.fixMessageInconsistencies().block())
+                .isEqualTo(Task.Result.COMPLETED);
+        }
+
+        @Test
+        void fixMessageInconsistenciesShouldResolvePartialInconsistentData() {
+            messageIdDAO.insert(MESSAGE_1).block();
+            messageIdDAO.insert(MESSAGE_2).block();
+
+            imapUidDAO.insert(MESSAGE_1).block();
+
+            testee.fixMessageInconsistencies().block();
+
+            SoftAssertions.assertSoftly(softly -> {
+                softly.assertThat(imapUidDAO.retrieveAllMessages().collectList().block())
+                    .containsExactly(MESSAGE_1);
+                softly.assertThat(messageIdDAO.retrieveAllMessages().collectList().block())
+                    .containsExactly(MESSAGE_1);
+            });
+        }
+
+        @Test
+        void fixImapUidInconsistenciesShouldCompleteWhenInconsistent() {
+            messageIdDAO.insert(MESSAGE_1_WITH_MOD_SEQ_2).block();
+
+            imapUidDAO.insert(MESSAGE_1).block();
+
+            testee.fixMessageInconsistencies().block();
+
+            assertThat(testee.fixImapUidInconsistencies().block())
+                .isEqualTo(Task.Result.COMPLETED);
+        }
+
+        @Test
+        void fixImapUidInconsistenciesShouldResolveInconsistent() {
+            messageIdDAO.insert(MESSAGE_1_WITH_MOD_SEQ_2).block();
+
+            imapUidDAO.insert(MESSAGE_1).block();
+
+            testee.fixMessageInconsistencies().block();
+
+            SoftAssertions.assertSoftly(softly -> {
+                softly.assertThat(imapUidDAO.retrieveAllMessages().collectList().block())
+                    .containsExactly(MESSAGE_1);
+                softly.assertThat(messageIdDAO.retrieveAllMessages().collectList().block())
+                    .containsExactly(MESSAGE_1);
+            });
+        }
+
+        @Nested
+        class FailureTesting {
+            @Test
+            void fixMessageInconsistenciesShouldReturnPartialWhenError(CassandraCluster cassandra) {
+                messageIdDAO.insert(MESSAGE_1).block();
+
+                cassandra.getConf()
+                    .registerScenario(fail()
+                        .forever()
+                        .whenQueryStartsWith("DELETE FROM messageIdTable WHERE mailboxId=:mailboxId AND uid=:uid"));
+
+                assertThat(testee.fixMessageInconsistencies().block())
+                    .isEqualTo(Task.Result.PARTIAL);
+            }
+
+            @Test
+            void fixMessageInconsistenciesShouldReturnPartialWhenPartialError(CassandraCluster cassandra) {
+                messageIdDAO.insert(MESSAGE_1).block();
+                messageIdDAO.insert(MESSAGE_2).block();
+
+                cassandra.getConf()
+                    .registerScenario(fail()
+                        .times(1)
+                        .whenQueryStartsWith("DELETE FROM messageIdTable WHERE mailboxId=:mailboxId AND uid=:uid"));
+
+                assertThat(testee.fixMessageInconsistencies().block())
+                    .isEqualTo(Task.Result.PARTIAL);
+            }
+
+            @Test
+            void fixMessageInconsistenciesShouldResolveSuccessPartially(CassandraCluster cassandra) {
+                messageIdDAO.insert(MESSAGE_1).block();
+                messageIdDAO.insert(MESSAGE_2).block();
+
+                cassandra.getConf()
+                    .registerScenario(fail()
+                        .times(1)
+                        .whenQueryStartsWith("DELETE FROM messageIdTable WHERE mailboxId=:mailboxId AND uid=:uid;"));
+
+                testee.fixMessageInconsistencies().block();
+
+                SoftAssertions.assertSoftly(softly -> {
+                    softly.assertThat(imapUidDAO.retrieveAllMessages().collectList().block())
+                        .isEmpty();
+                    softly.assertThat(messageIdDAO.retrieveAllMessages().collectList().block())
+                        .containsExactly(MESSAGE_1);
+                });
+            }
+        }
+    }
+}


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