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/05/04 03:21:28 UTC

[james-project] 09/12: JAMES-3105 Add an option to trust message denormalization when recomputing counters

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 4501b921168c7f9b1048f8b1e33a2d64d059aa2e
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Apr 23 17:16:28 2020 +0700

    JAMES-3105 Add an option to trust message denormalization when recomputing counters
---
 .../mail/task/RecomputeMailboxCountersService.java |  39 +-
 .../mail/task/RecomputeMailboxCountersTask.java    |  10 +-
 .../mail/task/RecomputeMailboxCountersTaskDTO.java |  19 +-
 .../task/RecomputeMailboxCountersServiceTest.java  | 438 +++++++++++++--------
 ...omputeMailboxCountersTaskSerializationTest.java |  46 ++-
 .../RecomputeMailboxCountersRequestToTask.java     |   3 +-
 6 files changed, 365 insertions(+), 190 deletions(-)

diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersService.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersService.java
index e14d16e..b39db27 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersService.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersService.java
@@ -55,6 +55,30 @@ public class RecomputeMailboxCountersService {
     private static final int MAILBOX_CONCURRENCY = 2;
     private static final int MESSAGE_CONCURRENCY = 8;
 
+    public static class Options {
+        public static Options trustMessageDenormalization() {
+            return of(true);
+        }
+
+        public static Options recheckMessageDenormalization() {
+            return of(false);
+        }
+
+        public static Options of(boolean value) {
+            return new Options(value);
+        }
+
+        private final boolean trustMessageDenormalization;
+
+        private Options(boolean trustMessageDenormalization) {
+            this.trustMessageDenormalization = trustMessageDenormalization;
+        }
+
+        public boolean isMessageDenormalizationTrusted() {
+            return trustMessageDenormalization;
+        }
+    }
+
     private static class Counter {
         private final CassandraId mailboxId;
         private final AtomicLong total;
@@ -163,9 +187,9 @@ public class RecomputeMailboxCountersService {
         this.counterDAO = counterDAO;
     }
 
-    Mono<Result> recomputeMailboxCounters(Context context) {
+    Mono<Result> recomputeMailboxCounters(Context context, Options options) {
         return mailboxDAO.retrieveAllMailboxes()
-            .flatMap(mailbox -> recomputeMailboxCounter(context, mailbox), MAILBOX_CONCURRENCY)
+            .flatMap(mailbox -> recomputeMailboxCounter(context, mailbox, options), MAILBOX_CONCURRENCY)
             .reduce(Result.COMPLETED, Task::combine)
             .onErrorResume(e -> {
                 LOGGER.error("Error listing mailboxes", e);
@@ -173,12 +197,12 @@ public class RecomputeMailboxCountersService {
             });
     }
 
-    private Mono<Result> recomputeMailboxCounter(Context context, Mailbox mailbox) {
+    private Mono<Result> recomputeMailboxCounter(Context context, Mailbox mailbox, Options options) {
         CassandraId mailboxId = (CassandraId) mailbox.getMailboxId();
         Counter counter = new Counter(mailboxId);
 
         return imapUidToMessageIdDAO.retrieveMessages(mailboxId, MessageRange.all())
-            .flatMap(message -> latestMetadata(mailboxId, message), MESSAGE_CONCURRENCY)
+            .flatMap(message -> latestMetadata(mailboxId, message, options), MESSAGE_CONCURRENCY)
             .doOnNext(counter::process)
             .then(Mono.defer(() -> counterDAO.resetCounters(counter.snapshot())))
             .then(Mono.just(Result.COMPLETED))
@@ -193,7 +217,12 @@ public class RecomputeMailboxCountersService {
             });
     }
 
-    private Flux<ComposedMessageIdWithMetaData> latestMetadata(CassandraId mailboxId, ComposedMessageIdWithMetaData message) {
+    private Flux<ComposedMessageIdWithMetaData> latestMetadata(CassandraId mailboxId,
+                                                               ComposedMessageIdWithMetaData message,
+                                                               Options options) {
+        if (options.isMessageDenormalizationTrusted()) {
+            return Flux.just(message);
+        }
         CassandraMessageId messageId = (CassandraMessageId) message.getComposedMessageId().getMessageId();
 
         return messageIdToImapUidDAO.retrieve(messageId, Optional.of(mailboxId))
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersTask.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersTask.java
index 74082f3..c93e1ea 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersTask.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersTask.java
@@ -70,16 +70,18 @@ public class RecomputeMailboxCountersTask implements Task {
     }
 
     private final RecomputeMailboxCountersService service;
+    private final RecomputeMailboxCountersService.Options options;
     private RecomputeMailboxCountersService.Context context;
 
-    public RecomputeMailboxCountersTask(RecomputeMailboxCountersService service) {
+    public RecomputeMailboxCountersTask(RecomputeMailboxCountersService service, RecomputeMailboxCountersService.Options options) {
         this.service = service;
+        this.options = options;
         this.context = new RecomputeMailboxCountersService.Context();
     }
 
     @Override
     public Result run() {
-        return service.recomputeMailboxCounters(context)
+        return service.recomputeMailboxCounters(context, options)
             .subscribeOn(Schedulers.elastic())
             .block();
     }
@@ -89,6 +91,10 @@ public class RecomputeMailboxCountersTask implements Task {
         return RECOMPUTE_MAILBOX_COUNTERS;
     }
 
+    public RecomputeMailboxCountersService.Options getOptions() {
+        return options;
+    }
+
     @Override
     public Optional<TaskExecutionDetails.AdditionalInformation> details() {
         Snapshot snapshot = context.snapshot();
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersTaskDTO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersTaskDTO.java
index b8fa112..d855aa1 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersTaskDTO.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersTaskDTO.java
@@ -18,7 +18,10 @@
  ****************************************************************/
 package org.apache.james.mailbox.cassandra.mail.task;
 
+import java.util.Optional;
+
 import org.apache.james.json.DTOModule;
+import org.apache.james.mailbox.cassandra.mail.task.RecomputeMailboxCountersService.Options;
 import org.apache.james.server.task.json.dto.TaskDTO;
 import org.apache.james.server.task.json.dto.TaskDTOModule;
 
@@ -26,7 +29,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 
 public class RecomputeMailboxCountersTaskDTO implements TaskDTO {
     private static RecomputeMailboxCountersTaskDTO toDTO(RecomputeMailboxCountersTask domainObject, String typeName) {
-        return new RecomputeMailboxCountersTaskDTO(typeName);
+        return new RecomputeMailboxCountersTaskDTO(typeName, Optional.of(domainObject.getOptions().isMessageDenormalizationTrusted()));
     }
 
     public static TaskDTOModule<RecomputeMailboxCountersTask, RecomputeMailboxCountersTaskDTO> module(RecomputeMailboxCountersService service) {
@@ -40,17 +43,27 @@ public class RecomputeMailboxCountersTaskDTO implements TaskDTO {
     }
 
     private final String type;
+    private final Optional<Boolean> trustMessageDenormalization;
 
-    public RecomputeMailboxCountersTaskDTO(@JsonProperty("type") String type) {
+    public RecomputeMailboxCountersTaskDTO(@JsonProperty("type") String type,
+                                           @JsonProperty("trustMessageDenormalization") Optional<Boolean> trustMessageDenormalization) {
         this.type = type;
+        this.trustMessageDenormalization = trustMessageDenormalization;
     }
 
     private RecomputeMailboxCountersTask toDomainObject(RecomputeMailboxCountersService service) {
-        return new RecomputeMailboxCountersTask(service);
+        Options options = trustMessageDenormalization.map(Options::of).orElse(Options.recheckMessageDenormalization());
+        return new RecomputeMailboxCountersTask(service, options);
     }
 
     @Override
+    @JsonProperty("type")
     public String getType() {
         return type;
     }
+
+    @JsonProperty("trustMessageDenormalization")
+    public Optional<Boolean> trustMessageDenormalization() {
+        return trustMessageDenormalization;
+    }
 }
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersServiceTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersServiceTest.java
index 683e1f7..10382a8 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersServiceTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersServiceTest.java
@@ -51,6 +51,8 @@ import org.apache.james.mailbox.model.MailboxPath;
 import org.apache.james.mailbox.model.UidValidity;
 import org.apache.james.task.Task.Result;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
@@ -89,199 +91,297 @@ class RecomputeMailboxCountersServiceTest {
         testee = new RecomputeMailboxCountersService(mailboxDAO, imapUidToMessageIdDAO, messageIdToImapUidDAO, counterDAO);
     }
 
-    @Test
-    void recomputeMailboxCountersShouldReturnCompletedWhenNoMailboxes() {
-        assertThat(testee.recomputeMailboxCounters(new Context()).block())
-            .isEqualTo(Result.COMPLETED);
-    }
-
-    @Test
-    void recomputeMailboxCountersShouldReturnCompletedWhenMailboxWithNoMessages() {
-        mailboxDAO.save(MAILBOX).block();
-
-        assertThat(testee.recomputeMailboxCounters(new Context()).block())
-            .isEqualTo(Result.COMPLETED);
-    }
-
-    @Test
-    void recomputeMailboxCountersShouldReturnCompletedWhenMailboxWithMessages() {
-        mailboxDAO.save(MAILBOX).block();
-        imapUidToMessageIdDAO.insert(METADATA_UNSEEN).block();
-        messageIdToImapUidDAO.insert(METADATA_UNSEEN).block();
-        counterDAO.incrementUnseenAndCount(CASSANDRA_ID_1).block();
-        counterDAO.incrementCount(CASSANDRA_ID_1).block();
-
-        testee.recomputeMailboxCounters(new Context()).block();
-
-        assertThat(testee.recomputeMailboxCounters(new Context()).block())
-            .isEqualTo(Result.COMPLETED);
-    }
-
-    @Test
-    void recomputeMailboxCountersShouldReturnCompletedWhenMessageDenormalizationIssue() {
-        mailboxDAO.save(MAILBOX).block();
-        imapUidToMessageIdDAO.insert(METADATA_UNSEEN).block();
-        messageIdToImapUidDAO.insert(METADATA_SEEN).block();
-        counterDAO.incrementUnseenAndCount(CASSANDRA_ID_1).block();
-        counterDAO.incrementCount(CASSANDRA_ID_1).block();
-
-        testee.recomputeMailboxCounters(new Context()).block();
-
-        assertThat(testee.recomputeMailboxCounters(new Context()).block())
-            .isEqualTo(Result.COMPLETED);
-    }
+    @Nested
+    class TrustMessageDenormalizationTest implements Contract {
+        @Override
+        public RecomputeMailboxCountersService testee() {
+            return testee;
+        }
 
-    @Test
-    void recomputeMailboxCountersShouldReturnCountersAreIncorrect() {
-        mailboxDAO.save(MAILBOX).block();
-        imapUidToMessageIdDAO.insert(METADATA_UNSEEN).block();
-        messageIdToImapUidDAO.insert(METADATA_UNSEEN).block();
+        @Override
+        public CassandraMailboxDAO mailboxDAO() {
+            return mailboxDAO;
+        }
 
-        testee.recomputeMailboxCounters(new Context()).block();
+        @Override
+        public RecomputeMailboxCountersService.Options options() {
+            return RecomputeMailboxCountersService.Options.trustMessageDenormalization();
+        }
 
-        assertThat(testee.recomputeMailboxCounters(new Context()).block())
-            .isEqualTo(Result.COMPLETED);
-    }
-
-    @Test
-    void recomputeMailboxCountersShouldReturnCompletedWhenOrphanMailboxRegistration() {
-        mailboxDAO.save(MAILBOX).block();
-        imapUidToMessageIdDAO.insert(METADATA_UNSEEN).block();
-        counterDAO.incrementUnseenAndCount(CASSANDRA_ID_1).block();
-        counterDAO.incrementCount(CASSANDRA_ID_1).block();
-
-        testee.recomputeMailboxCounters(new Context()).block();
+        @Override
+        public CassandraMessageIdDAO imapUidToMessageIdDAO() {
+            return imapUidToMessageIdDAO;
+        }
 
-        assertThat(testee.recomputeMailboxCounters(new Context()).block())
-            .isEqualTo(Result.COMPLETED);
-    }
+        @Override
+        public CassandraMessageIdToImapUidDAO messageIdToImapUidDAO() {
+            return messageIdToImapUidDAO;
+        }
 
-    @Test
-    void recomputeMailboxCountersShouldReturnCompletedWhenMailboxListReferenceIsMissing() {
-        mailboxDAO.save(MAILBOX).block();
-        messageIdToImapUidDAO.insert(METADATA_UNSEEN).block();
-        counterDAO.incrementUnseenAndCount(CASSANDRA_ID_1).block();
-        counterDAO.incrementCount(CASSANDRA_ID_1).block();
+        @Override
+        public CassandraMailboxCounterDAO counterDAO() {
+            return counterDAO;
+        }
 
-        testee.recomputeMailboxCounters(new Context()).block();
+        @Disabled("Inconsitencies can not be corrected on the fly as trust avoid their detection")
+        @Override
+        public void recomputeMailboxCountersShouldIgnoreMissingMailboxListReferences() {
 
-        assertThat(testee.recomputeMailboxCounters(new Context()).block())
-            .isEqualTo(Result.COMPLETED);
-    }
+        }
 
-    @Test
-    void recomputeMailboxCountersShouldNoopWhenMailboxWithoutMessage() {
-        mailboxDAO.save(MAILBOX).block();
+        @Disabled("Inconsitencies can not be corrected on the fly as trust avoid their detection")
+        @Override
+        public void recomputeMailboxCountersShouldUseSourceOfTruthForComputation() {
 
-        testee.recomputeMailboxCounters(new Context()).block();
+        }
 
-        assertThat(counterDAO.retrieveMailboxCounters(CASSANDRA_ID_1).blockOptional())
-            .isEmpty();
-    }
+        @Disabled("Inconsitencies can not be corrected on the fly as trust avoid their detection")
+        @Override
+        public void recomputeMailboxCountersShouldIgnoreOrphanMailboxListReference() {
 
-    @Test
-    void recomputeMailboxCountersShouldNoopWhenValidCounters() {
-        mailboxDAO.save(MAILBOX).block();
-        imapUidToMessageIdDAO.insert(METADATA_UNSEEN).block();
-        messageIdToImapUidDAO.insert(METADATA_UNSEEN).block();
-        counterDAO.incrementUnseenAndCount(CASSANDRA_ID_1).block();
-        counterDAO.incrementCount(CASSANDRA_ID_1).block();
-
-        testee.recomputeMailboxCounters(new Context()).block();
-
-        assertThat(counterDAO.retrieveMailboxCounters(CASSANDRA_ID_1).blockOptional())
-            .contains(MailboxCounters.builder()
-                .mailboxId(CASSANDRA_ID_1)
-                .count(1)
-                .unseen(1)
-                .build());
+        }
     }
 
-    @Test
-    void recomputeMailboxCountersShouldRecreateMissingCounters() {
-        mailboxDAO.save(MAILBOX).block();
-        imapUidToMessageIdDAO.insert(METADATA_UNSEEN).block();
-        messageIdToImapUidDAO.insert(METADATA_UNSEEN).block();
-
-        testee.recomputeMailboxCounters(new Context()).block();
-
-        assertThat(counterDAO.retrieveMailboxCounters(CASSANDRA_ID_1).blockOptional())
-            .contains(MailboxCounters.builder()
-                .mailboxId(CASSANDRA_ID_1)
-                .count(1)
-                .unseen(1)
-                .build());
+    @Nested
+    class RecheckMessageDenormalizationTest implements Contract {
+        @Override
+        public RecomputeMailboxCountersService testee() {
+            return testee;
+        }
+
+        @Override
+        public CassandraMailboxDAO mailboxDAO() {
+            return mailboxDAO;
+        }
+
+        @Override
+        public RecomputeMailboxCountersService.Options options() {
+            return RecomputeMailboxCountersService.Options.recheckMessageDenormalization();
+        }
+
+        @Override
+        public CassandraMessageIdDAO imapUidToMessageIdDAO() {
+            return imapUidToMessageIdDAO;
+        }
+
+        @Override
+        public CassandraMessageIdToImapUidDAO messageIdToImapUidDAO() {
+            return messageIdToImapUidDAO;
+        }
+
+        @Override
+        public CassandraMailboxCounterDAO counterDAO() {
+            return counterDAO;
+        }
     }
 
-    @Test
-    void recomputeMailboxCountersShouldResetIncorrectCounters() {
-        mailboxDAO.save(MAILBOX).block();
-        imapUidToMessageIdDAO.insert(METADATA_UNSEEN).block();
-        messageIdToImapUidDAO.insert(METADATA_UNSEEN).block();
-        counterDAO.incrementCount(CASSANDRA_ID_1).block();
-
-        testee.recomputeMailboxCounters(new Context()).block();
-
-        assertThat(counterDAO.retrieveMailboxCounters(CASSANDRA_ID_1).blockOptional())
-            .contains(MailboxCounters.builder()
-                .mailboxId(CASSANDRA_ID_1)
-                .count(1)
-                .unseen(1)
-                .build());
-    }
+    interface Contract {
+        RecomputeMailboxCountersService testee();
 
-    @Test
-    void recomputeMailboxCountersShouldTakeSeenIntoAccount() {
-        mailboxDAO.save(MAILBOX).block();
-        imapUidToMessageIdDAO.insert(METADATA_SEEN).block();
-        messageIdToImapUidDAO.insert(METADATA_SEEN).block();
-        counterDAO.incrementCount(CASSANDRA_ID_1).block();
-
-        testee.recomputeMailboxCounters(new Context()).block();
-
-        assertThat(counterDAO.retrieveMailboxCounters(CASSANDRA_ID_1).blockOptional())
-            .contains(MailboxCounters.builder()
-                .mailboxId(CASSANDRA_ID_1)
-                .count(1)
-                .unseen(0)
-                .build());
-    }
+        CassandraMailboxDAO mailboxDAO();
 
-    @Test
-    void recomputeMailboxCountersShouldUseSourceOfTruthForComputation() {
-        mailboxDAO.save(MAILBOX).block();
-        imapUidToMessageIdDAO.insert(METADATA_SEEN).block();
-        messageIdToImapUidDAO.insert(METADATA_UNSEEN).block();
+        RecomputeMailboxCountersService.Options options();
 
-        testee.recomputeMailboxCounters(new Context()).block();
+        CassandraMessageIdDAO imapUidToMessageIdDAO();
 
-        assertThat(counterDAO.retrieveMailboxCounters(CASSANDRA_ID_1).blockOptional())
-            .contains(MailboxCounters.builder()
-                .mailboxId(CASSANDRA_ID_1)
-                .count(1)
-                .unseen(1)
-                .build());
-    }
+        CassandraMessageIdToImapUidDAO messageIdToImapUidDAO();
 
-    @Test
-    void recomputeMailboxCountersShouldIgnoreMissingMailboxListReferences() {
-        mailboxDAO.save(MAILBOX).block();
-        imapUidToMessageIdDAO.insert(METADATA_SEEN).block();
+        CassandraMailboxCounterDAO counterDAO();
 
-        testee.recomputeMailboxCounters(new Context()).block();
+        @Test
+        default void recomputeMailboxCountersShouldReturnCompletedWhenNoMailboxes() {
+            assertThat(testee().recomputeMailboxCounters(new Context(), options()).block())
+                .isEqualTo(Result.COMPLETED);
+        }
 
-        assertThat(counterDAO.retrieveMailboxCounters(CASSANDRA_ID_1).blockOptional())
-            .isEmpty();
-    }
+        @Test
+        default void recomputeMailboxCountersShouldReturnCompletedWhenMailboxWithNoMessages() {
+            mailboxDAO().save(MAILBOX).block();
 
-    @Test
-    void recomputeMailboxCountersShouldIgnoreOrphanMailboxListReference() {
-        mailboxDAO.save(MAILBOX).block();
-        imapUidToMessageIdDAO.insert(METADATA_UNSEEN).block();
+            assertThat(testee().recomputeMailboxCounters(new Context(), options()).block())
+                .isEqualTo(Result.COMPLETED);
+        }
 
-        testee.recomputeMailboxCounters(new Context()).block();
+        @Test
+        default void recomputeMailboxCountersShouldReturnCompletedWhenMailboxWithMessages() {
+            mailboxDAO().save(MAILBOX).block();
+            imapUidToMessageIdDAO().insert(METADATA_UNSEEN).block();
+            messageIdToImapUidDAO().insert(METADATA_UNSEEN).block();
+            counterDAO().incrementUnseenAndCount(CASSANDRA_ID_1).block();
+            counterDAO().incrementCount(CASSANDRA_ID_1).block();
+
+            testee().recomputeMailboxCounters(new Context(), options()).block();
 
-        assertThat(counterDAO.retrieveMailboxCounters(CASSANDRA_ID_1).blockOptional())
-            .isEmpty();
+            assertThat(testee().recomputeMailboxCounters(new Context(), options()).block())
+                .isEqualTo(Result.COMPLETED);
+        }
+
+        @Test
+        default void recomputeMailboxCountersShouldReturnCompletedWhenMessageDenormalizationIssue() {
+            mailboxDAO().save(MAILBOX).block();
+            imapUidToMessageIdDAO().insert(METADATA_UNSEEN).block();
+            messageIdToImapUidDAO().insert(METADATA_SEEN).block();
+            counterDAO().incrementUnseenAndCount(CASSANDRA_ID_1).block();
+            counterDAO().incrementCount(CASSANDRA_ID_1).block();
+
+            testee().recomputeMailboxCounters(new Context(), options()).block();
+
+            assertThat(testee().recomputeMailboxCounters(new Context(), options()).block())
+                .isEqualTo(Result.COMPLETED);
+        }
+
+        @Test
+        default void recomputeMailboxCountersShouldReturnCountersAreIncorrect() {
+            mailboxDAO().save(MAILBOX).block();
+            imapUidToMessageIdDAO().insert(METADATA_UNSEEN).block();
+            messageIdToImapUidDAO().insert(METADATA_UNSEEN).block();
+
+            testee().recomputeMailboxCounters(new Context(), options()).block();
+
+            assertThat(testee().recomputeMailboxCounters(new Context(), options()).block())
+                .isEqualTo(Result.COMPLETED);
+        }
+
+        @Test
+        default void recomputeMailboxCountersShouldReturnCompletedWhenOrphanMailboxRegistration() {
+            mailboxDAO().save(MAILBOX).block();
+            imapUidToMessageIdDAO().insert(METADATA_UNSEEN).block();
+            counterDAO().incrementUnseenAndCount(CASSANDRA_ID_1).block();
+            counterDAO().incrementCount(CASSANDRA_ID_1).block();
+
+            testee().recomputeMailboxCounters(new Context(), options()).block();
+
+            assertThat(testee().recomputeMailboxCounters(new Context(), options()).block())
+                .isEqualTo(Result.COMPLETED);
+        }
+
+        @Test
+        default void recomputeMailboxCountersShouldReturnCompletedWhenMailboxListReferenceIsMissing() {
+            mailboxDAO().save(MAILBOX).block();
+            messageIdToImapUidDAO().insert(METADATA_UNSEEN).block();
+            counterDAO().incrementUnseenAndCount(CASSANDRA_ID_1).block();
+            counterDAO().incrementCount(CASSANDRA_ID_1).block();
+
+            testee().recomputeMailboxCounters(new Context(), options()).block();
+
+            assertThat(testee().recomputeMailboxCounters(new Context(), options()).block())
+                .isEqualTo(Result.COMPLETED);
+        }
+
+        @Test
+        default void recomputeMailboxCountersShouldNoopWhenMailboxWithoutMessage() {
+            mailboxDAO().save(MAILBOX).block();
+
+            testee().recomputeMailboxCounters(new Context(), options()).block();
+
+            assertThat(counterDAO().retrieveMailboxCounters(CASSANDRA_ID_1).blockOptional())
+                .isEmpty();
+        }
+
+        @Test
+        default void recomputeMailboxCountersShouldNoopWhenValidCounters() {
+            mailboxDAO().save(MAILBOX).block();
+            imapUidToMessageIdDAO().insert(METADATA_UNSEEN).block();
+            messageIdToImapUidDAO().insert(METADATA_UNSEEN).block();
+            counterDAO().incrementUnseenAndCount(CASSANDRA_ID_1).block();
+            counterDAO().incrementCount(CASSANDRA_ID_1).block();
+
+            testee().recomputeMailboxCounters(new Context(), options()).block();
+
+            assertThat(counterDAO().retrieveMailboxCounters(CASSANDRA_ID_1).blockOptional())
+                .contains(MailboxCounters.builder()
+                    .mailboxId(CASSANDRA_ID_1)
+                    .count(1)
+                    .unseen(1)
+                    .build());
+        }
+
+        @Test
+        default void recomputeMailboxCountersShouldRecreateMissingCounters() {
+            mailboxDAO().save(MAILBOX).block();
+            imapUidToMessageIdDAO().insert(METADATA_UNSEEN).block();
+            messageIdToImapUidDAO().insert(METADATA_UNSEEN).block();
+
+            testee().recomputeMailboxCounters(new Context(), options()).block();
+
+            assertThat(counterDAO().retrieveMailboxCounters(CASSANDRA_ID_1).blockOptional())
+                .contains(MailboxCounters.builder()
+                    .mailboxId(CASSANDRA_ID_1)
+                    .count(1)
+                    .unseen(1)
+                    .build());
+        }
+
+        @Test
+        default void recomputeMailboxCountersShouldResetIncorrectCounters() {
+            mailboxDAO().save(MAILBOX).block();
+            imapUidToMessageIdDAO().insert(METADATA_UNSEEN).block();
+            messageIdToImapUidDAO().insert(METADATA_UNSEEN).block();
+            counterDAO().incrementCount(CASSANDRA_ID_1).block();
+
+            testee().recomputeMailboxCounters(new Context(), options()).block();
+
+            assertThat(counterDAO().retrieveMailboxCounters(CASSANDRA_ID_1).blockOptional())
+                .contains(MailboxCounters.builder()
+                    .mailboxId(CASSANDRA_ID_1)
+                    .count(1)
+                    .unseen(1)
+                    .build());
+        }
+
+        @Test
+        default void recomputeMailboxCountersShouldTakeSeenIntoAccount() {
+            mailboxDAO().save(MAILBOX).block();
+            imapUidToMessageIdDAO().insert(METADATA_SEEN).block();
+            messageIdToImapUidDAO().insert(METADATA_SEEN).block();
+            counterDAO().incrementCount(CASSANDRA_ID_1).block();
+
+            testee().recomputeMailboxCounters(new Context(), options()).block();
+
+            assertThat(counterDAO().retrieveMailboxCounters(CASSANDRA_ID_1).blockOptional())
+                .contains(MailboxCounters.builder()
+                    .mailboxId(CASSANDRA_ID_1)
+                    .count(1)
+                    .unseen(0)
+                    .build());
+        }
+
+        @Test
+        default void recomputeMailboxCountersShouldUseSourceOfTruthForComputation() {
+            mailboxDAO().save(MAILBOX).block();
+            imapUidToMessageIdDAO().insert(METADATA_SEEN).block();
+            messageIdToImapUidDAO().insert(METADATA_UNSEEN).block();
+
+            testee().recomputeMailboxCounters(new Context(), options()).block();
+
+            assertThat(counterDAO().retrieveMailboxCounters(CASSANDRA_ID_1).blockOptional())
+                .contains(MailboxCounters.builder()
+                    .mailboxId(CASSANDRA_ID_1)
+                    .count(1)
+                    .unseen(1)
+                    .build());
+        }
+
+        @Test
+        default void recomputeMailboxCountersShouldIgnoreMissingMailboxListReferences() {
+            mailboxDAO().save(MAILBOX).block();
+            imapUidToMessageIdDAO().insert(METADATA_SEEN).block();
+
+            testee().recomputeMailboxCounters(new Context(), options()).block();
+
+            assertThat(counterDAO().retrieveMailboxCounters(CASSANDRA_ID_1).blockOptional())
+                .isEmpty();
+        }
+
+        @Test
+        default void recomputeMailboxCountersShouldIgnoreOrphanMailboxListReference() {
+            mailboxDAO().save(MAILBOX).block();
+            imapUidToMessageIdDAO().insert(METADATA_UNSEEN).block();
+
+            testee().recomputeMailboxCounters(new Context(), options()).block();
+
+            assertThat(counterDAO().retrieveMailboxCounters(CASSANDRA_ID_1).blockOptional())
+                .isEmpty();
+        }
     }
 }
\ No newline at end of file
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersTaskSerializationTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersTaskSerializationTest.java
index 15c787c..e46fa08 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersTaskSerializationTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersTaskSerializationTest.java
@@ -19,16 +19,14 @@
 
 package org.apache.james.mailbox.cassandra.mail.task;
 
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 
 import java.time.Instant;
-import java.util.UUID;
 
 import org.apache.james.JsonSerializationVerifier;
-import org.apache.james.core.Username;
-import org.apache.james.mailbox.cassandra.ids.CassandraId;
-import org.apache.james.mailbox.model.MailboxConstants;
-import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.json.JsonGenericSerializer;
+import org.apache.james.mailbox.cassandra.mail.task.RecomputeMailboxCountersService.Options;
 import org.junit.jupiter.api.Test;
 
 import com.google.common.collect.ImmutableList;
@@ -38,8 +36,17 @@ class RecomputeMailboxCountersTaskSerializationTest {
     private static final String MAILBOX_ID_AS_STRING = "464765a0-e4e7-11e4-aba4-710c1de3782b";
 
     private static final RecomputeMailboxCountersService SERVICE = mock(RecomputeMailboxCountersService.class);
-    private static final RecomputeMailboxCountersTask TASK = new RecomputeMailboxCountersTask(SERVICE);
-    private static final String SERIALIZED_TASK = "{\"type\": \"recompute-mailbox-counters\"}";
+    private static final RecomputeMailboxCountersTask TASK_TRUSTED = new RecomputeMailboxCountersTask(SERVICE, Options.trustMessageDenormalization());
+    private static final RecomputeMailboxCountersTask TASK_UNTRUSTED = new RecomputeMailboxCountersTask(SERVICE, Options.recheckMessageDenormalization());
+    private static final String SERIALIZED_TASK_TRUSTED = "{" +
+        "  \"type\": \"recompute-mailbox-counters\"," +
+        "  \"trustMessageDenormalization\": true" +
+        "}";
+    private static final String SERIALIZED_TASK_UNTRUSTED = "{" +
+        "  \"type\": \"recompute-mailbox-counters\"," +
+        "  \"trustMessageDenormalization\": false" +
+        "}";
+    private static final String SERIALIZED_TASK_OLD = "{\"type\": \"recompute-mailbox-counters\"}";
     private static final RecomputeMailboxCountersTask.Details DETAILS = new RecomputeMailboxCountersTask.Details(TIMESTAMP, 12, ImmutableList.of(MAILBOX_ID_AS_STRING));
     private static final String SERIALIZED_ADDITIONAL_INFORMATION = "{" +
         "  \"type\":\"recompute-mailbox-counters\"," +
@@ -49,14 +56,33 @@ class RecomputeMailboxCountersTaskSerializationTest {
         "}";
 
     @Test
-    void taskShouldBeSerializable() throws Exception {
+    void taskShouldBeSerializableWhenTrusted() throws Exception {
         JsonSerializationVerifier.dtoModule(RecomputeMailboxCountersTaskDTO.module(SERVICE))
-            .bean(TASK)
-            .json(SERIALIZED_TASK)
+            .bean(TASK_TRUSTED)
+            .json(SERIALIZED_TASK_TRUSTED)
             .verify();
     }
 
     @Test
+    void taskShouldBeSerializableWhenUnTrusted() throws Exception {
+        JsonSerializationVerifier.dtoModule(RecomputeMailboxCountersTaskDTO.module(SERVICE))
+            .bean(TASK_UNTRUSTED)
+            .json(SERIALIZED_TASK_UNTRUSTED)
+            .verify();
+    }
+
+    @Test
+    void taskWithoutTrustFieldShouldBeWellDeSerialized() throws Exception {
+        RecomputeMailboxCountersTask domainObject = JsonGenericSerializer
+            .forModules(RecomputeMailboxCountersTaskDTO.module(SERVICE))
+            .withoutNestedType()
+            .deserialize(SERIALIZED_TASK_OLD);
+
+        assertThat(domainObject)
+            .isEqualToComparingFieldByFieldRecursively(TASK_UNTRUSTED);
+    }
+
+    @Test
     void additionalInformationShouldBeSerializable() throws Exception {
         JsonSerializationVerifier.dtoModule(RecomputeMailboxCountersTaskAdditionalInformationDTO.MODULE)
             .bean(DETAILS)
diff --git a/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/routes/RecomputeMailboxCountersRequestToTask.java b/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/routes/RecomputeMailboxCountersRequestToTask.java
index 2ec6679..2d60e61 100644
--- a/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/routes/RecomputeMailboxCountersRequestToTask.java
+++ b/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/routes/RecomputeMailboxCountersRequestToTask.java
@@ -22,6 +22,7 @@ package org.apache.james.webadmin.routes;
 import javax.inject.Inject;
 
 import org.apache.james.mailbox.cassandra.mail.task.RecomputeMailboxCountersService;
+import org.apache.james.mailbox.cassandra.mail.task.RecomputeMailboxCountersService.Options;
 import org.apache.james.mailbox.cassandra.mail.task.RecomputeMailboxCountersTask;
 import org.apache.james.webadmin.tasks.TaskFromRequestRegistry;
 import org.apache.james.webadmin.tasks.TaskRegistrationKey;
@@ -32,6 +33,6 @@ public class RecomputeMailboxCountersRequestToTask extends TaskFromRequestRegist
     @Inject
     public RecomputeMailboxCountersRequestToTask(RecomputeMailboxCountersService service) {
         super(REGISTRATION_KEY,
-            request -> new RecomputeMailboxCountersTask(service));
+            request -> new RecomputeMailboxCountersTask(service, Options.recheckMessageDenormalization()));
     }
 }


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