You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by rc...@apache.org on 2021/01/05 04:55:39 UTC

[james-project] branch master updated (e9a3cbf -> 5e80fe6)

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

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


    from e9a3cbf  JAMES-3462 Binding for CassandraMailboxChangeModule
     new 39ee787  JAMES-3486 Add missing binding
     new 4ac8c99  JAMES-3486 Fix indent
     new 49b3668  JAMES-3486 Test MailboxId should be generated
     new 6efd140  JAMES-3486 Distributed MailboxChangeMethod test
     new 47ee646  JAMES-3486 MemoryMailboxChangeRepository getSinceState should not include delegated changes
     new da29db1  JAMES-3486 RightManager listRights should depend on mailboxId
     new b6fee54  JAMES-3486 Adapt MailboxChangesMethodContract for stability against distributed environment
     new d619483  JAMES-3461 Register MailboxChangeListener by default
     new 91fd382  JAMES-3431 RecipientRewriteTableProcessor needs to modify DSN parameters
     new be946ff  JAMES-3407 Disable read repairs on outdated schema verison
     new 9fe885b  JAMES-3485 Group attachment right checking together
     new 1ef4ffb  JAMES-3485 Avoid reading messageV3 table upon attachment right setting
     new 893ccaa  JAMES-3485 Remove unused method
     new 0ad37ca  JAMES-3485 Searching all mailboxes reads too much ACLs
     new 7a7afdd  JAMES-3485 MessageAppender should group attachment reads too
     new b2c9553  JAMES-3202 ReIndexerPerformer in corrective mode should add missing messages in the index
     new 8630f34  JAMES-3202 ReIndexerPerformer in corrective mode: add missing error handling
     new c8033f4  JAMES-3202 Leverage the fact that index does an upsert, removing pre-existing documents
     new 4b3785d  [REFACTORING] Steps of the staged builder should be inside the Builder of MailboxChange
     new 5fc498a  [REFACTORING] MailboxManager is not used in MailboxChangeListener anymore
     new a8e7a33  JAMES-3471 Should be able to serialize UpdatedFlags with and without a message id
     new 3ba4665  JAMES-3471 Adapt the different implementations of MessageMapper and MessageIdMapper to add the message id
     new ebad11e  JAMES-3471 Computing uids and message ids from updated flags is easy
     new 5e80fe6  JAMES-3471 Disable tests on JpaMessageMapperTest using messageId, as it is not supported

The 24 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:
 .../org/apache/james/mailbox/RightManager.java     |    2 +
 .../james/mailbox/events/MailboxListener.java      |   23 +-
 .../mailbox/model/MultimailboxesSearchQuery.java   |   12 +
 .../apache/james/mailbox/model/UpdatedFlags.java   |   37 +-
 .../cassandra/mail/CassandraMessageIdMapper.java   |    1 +
 .../cassandra/mail/CassandraMessageMapper.java     |    2 +
 .../cassandra/mail/CassandraMailboxMapperTest.java |  599 ++++----
 .../ElasticSearchListeningMessageSearchIndex.java  |    5 +-
 ...asticSearchListeningMessageSearchIndexTest.java |    6 +-
 .../scala/org/apache/james/event/json/DTOs.scala   |    5 +-
 .../event/json/FlagsUpdatedSerializationTest.java  |   72 +
 .../james/mailbox/jpa/mail/MessageUtils.java       |    1 +
 .../mailbox/jpa/mail/JpaMessageMapperTest.java     |   33 +
 .../mailbox/maildir/mail/MaildirMessageMapper.java |    1 +
 .../inmemory/mail/InMemoryMessageIdMapper.java     |    1 +
 .../mailbox/store/StoreAttachmentManager.java      |   48 +-
 .../james/mailbox/store/StoreMailboxManager.java   |   42 +-
 .../james/mailbox/store/StoreMessageIdManager.java |   24 +-
 .../james/mailbox/store/StoreRightManager.java     |    7 +
 .../mailbox/store/mail/AbstractMessageMapper.java  |    1 +
 .../james/mailbox/store/mail/MailboxMapper.java    |   12 +
 .../AbstractMessageIdManagerSideEffectTest.java    |    1 +
 .../store/mail/model/MessageIdMapperTest.java      |    7 +
 .../store/mail/model/MessageMapperTest.java        |   15 +-
 .../mailbox/tools/indexer/ReIndexerPerformer.java  |   20 +-
 .../mailbox/tools/indexer/ReIndexerImplTest.java   |   30 +
 .../main/java/org/apache/mailet/DsnParameters.java |    4 +
 .../modules/mailbox/CassandraMailboxModule.java    |    6 +
 .../org/apache/james/jmap/draft/JMAPModule.java    |    2 +
 .../apache/james/jmap/draft/JmapGuiceProbe.java    |    6 +-
 .../james/jmap/api/change/MailboxChange.java       |   51 +-
 .../change/MemoryMailboxChangeRepository.java      |    1 +
 .../change/MailboxChangeRepositoryContract.java    |   48 +
 .../java/org/apache/james/smtp/DSNRelayTest.java   |   47 +-
 .../mailets/RecipientRewriteTableProcessor.java    |   76 +-
 .../james/jmap/draft/methods/MessageAppender.java  |   28 +-
 ...ava => DistributedMailboxChangeMethodTest.java} |   22 +-
 .../DistributedMailboxGetMethodTest.java           |    6 +-
 .../contract/MailboxChangesMethodContract.scala    | 1613 +++++++++++---------
 .../memory/MemoryMailboxChangesMethodTest.java     |    8 +
 .../src/test/resources/listeners.xml               |    3 -
 .../james/jmap/change/MailboxChangeListener.scala  |    2 -
 .../jmap/change/MailboxChangeListenerTest.scala    |    2 +-
 .../james/webadmin/routes/MailboxesRoutesTest.java |    8 +-
 .../webadmin/routes/UserMailboxesRoutesTest.java   |    4 +-
 45 files changed, 1770 insertions(+), 1174 deletions(-)
 copy server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/{DistributedMailboxSetMethodTest.java => DistributedMailboxChangeMethodTest.java} (83%)


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


[james-project] 24/24: JAMES-3471 Disable tests on JpaMessageMapperTest using messageId, as it is not supported

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 5e80fe6d73c06d4f1b40c17885a13b01d6f1fbbb
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Mon Jan 4 15:10:57 2021 +0700

    JAMES-3471 Disable tests on JpaMessageMapperTest using messageId, as it is not supported
---
 .../mailbox/jpa/mail/JpaMessageMapperTest.java     | 33 ++++++++++++++++++++++
 .../store/mail/model/MessageMapperTest.java        | 10 +++----
 2 files changed, 38 insertions(+), 5 deletions(-)

diff --git a/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/mail/JpaMessageMapperTest.java b/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/mail/JpaMessageMapperTest.java
index 87cb269..5fea0fd 100644
--- a/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/mail/JpaMessageMapperTest.java
+++ b/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/mail/JpaMessageMapperTest.java
@@ -20,10 +20,13 @@
 package org.apache.james.mailbox.jpa.mail;
 
 import org.apache.james.backends.jpa.JpaTestCluster;
+import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.jpa.JPAMailboxFixture;
 import org.apache.james.mailbox.store.mail.model.MapperProvider;
 import org.apache.james.mailbox.store.mail.model.MessageMapperTest;
 import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
 
 class JpaMessageMapperTest extends MessageMapperTest {
 
@@ -38,4 +41,34 @@ class JpaMessageMapperTest extends MessageMapperTest {
     void cleanUp() {
         JPA_TEST_CLUSTER.clear(JPAMailboxFixture.MAILBOX_TABLE_NAMES);
     }
+
+    @Disabled("JAMES-3471 messageId is not supported by JPA ")
+    @Test
+    public void flagsAdditionShouldReturnAnUpdatedFlagHighlightingTheAddition() throws MailboxException {
+
+    }
+
+    @Disabled("")
+    @Test
+    public void flagsReplacementShouldReturnAnUpdatedFlagHighlightingTheReplacement() throws MailboxException {
+
+    }
+
+    @Disabled("")
+    @Test
+    public void flagsRemovalShouldReturnAnUpdatedFlagHighlightingTheRemoval() throws MailboxException {
+
+    }
+
+    @Disabled("")
+    @Test
+    public void userFlagsUpdateShouldReturnCorrectUpdatedFlags() throws MailboxException {
+
+    }
+
+    @Disabled("")
+    @Test
+    public void userFlagsUpdateShouldReturnCorrectUpdatedFlagsWhenNoop() throws MailboxException {
+
+    }
 }
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageMapperTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageMapperTest.java
index cc949d5..6f4d93d 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageMapperTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageMapperTest.java
@@ -701,7 +701,7 @@ public abstract class MessageMapperTest {
     }
 
     @Test
-    void flagsReplacementShouldReturnAnUpdatedFlagHighlightingTheReplacement() throws MailboxException {
+    protected void flagsReplacementShouldReturnAnUpdatedFlagHighlightingTheReplacement() throws MailboxException {
         saveMessages();
         ModSeq modSeq = messageMapper.getHighestModSeq(benwaInboxMailbox);
         Optional<UpdatedFlags> updatedFlags = messageMapper.updateFlags(benwaInboxMailbox, message1.getUid(),
@@ -717,7 +717,7 @@ public abstract class MessageMapperTest {
     }
 
     @Test
-    void flagsAdditionShouldReturnAnUpdatedFlagHighlightingTheAddition() throws MailboxException {
+    protected void flagsAdditionShouldReturnAnUpdatedFlagHighlightingTheAddition() throws MailboxException {
         saveMessages();
         messageMapper.updateFlags(benwaInboxMailbox, message1.getUid(), new FlagsUpdateCalculator(new Flags(Flags.Flag.FLAGGED), FlagsUpdateMode.REPLACE));
         ModSeq modSeq = messageMapper.getHighestModSeq(benwaInboxMailbox);
@@ -750,7 +750,7 @@ public abstract class MessageMapperTest {
     }
 
     @Test
-    void flagsRemovalShouldReturnAnUpdatedFlagHighlightingTheRemoval() throws MailboxException {
+    protected void flagsRemovalShouldReturnAnUpdatedFlagHighlightingTheRemoval() throws MailboxException {
         saveMessages();
         messageMapper.updateFlags(benwaInboxMailbox, message1.getUid(), new FlagsUpdateCalculator(new FlagsBuilder().add(Flags.Flag.FLAGGED, Flags.Flag.SEEN).build(), FlagsUpdateMode.REPLACE));
         ModSeq modSeq = messageMapper.getHighestModSeq(benwaInboxMailbox);
@@ -877,7 +877,7 @@ public abstract class MessageMapperTest {
     }
 
     @Test
-    void userFlagsUpdateShouldReturnCorrectUpdatedFlags() throws Exception {
+    protected void userFlagsUpdateShouldReturnCorrectUpdatedFlags() throws Exception {
         saveMessages();
         ModSeq modSeq = messageMapper.getHighestModSeq(benwaInboxMailbox);
         assertThat(messageMapper.updateFlags(benwaInboxMailbox, message1.getUid(), new FlagsUpdateCalculator(new Flags(USER_FLAG), FlagsUpdateMode.ADD)))
@@ -892,7 +892,7 @@ public abstract class MessageMapperTest {
     }
 
     @Test
-    void userFlagsUpdateShouldReturnCorrectUpdatedFlagsWhenNoop() throws Exception {
+    protected void userFlagsUpdateShouldReturnCorrectUpdatedFlagsWhenNoop() throws Exception {
         saveMessages();
 
         assertThat(


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


[james-project] 14/24: JAMES-3485 Searching all mailboxes reads too much ACLs

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 0ad37ca0a5cb230f7fed2c8d76d0c49cfd5f2547
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Dec 25 19:51:23 2020 +0700

    JAMES-3485 Searching all mailboxes reads too much ACLs
    
    Reference updater is called after sending a message. In order
    to retrieve the message being replied/ forwarded and update its flags, a full mailbox search is being performed.
    
    This unrestricted search was leading to unecessary ACL reads (1 ACL reads per mailbox accessible by the owner)
    
    Upon Message search, we can skip safely
---
 .../mailbox/model/MultimailboxesSearchQuery.java   | 12 ++++++++
 .../james/mailbox/store/StoreMailboxManager.java   | 33 ++++++++++++++++------
 .../james/mailbox/store/mail/MailboxMapper.java    | 12 ++++++++
 3 files changed, 49 insertions(+), 8 deletions(-)

diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/model/MultimailboxesSearchQuery.java b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MultimailboxesSearchQuery.java
index 540f05f..4785111 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/model/MultimailboxesSearchQuery.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/model/MultimailboxesSearchQuery.java
@@ -40,6 +40,8 @@ public class MultimailboxesSearchQuery {
     public interface Namespace {
         boolean keepAccessible(Mailbox mailbox);
 
+        boolean accessDelegatedMailboxes();
+
         MailboxQuery associatedMailboxSearchQuery();
     }
 
@@ -61,6 +63,11 @@ public class MultimailboxesSearchQuery {
                 .matchesAllMailboxNames()
                 .build();
         }
+
+        @Override
+        public boolean accessDelegatedMailboxes() {
+            return false;
+        }
     }
 
     public static class AccessibleNamespace implements Namespace {
@@ -83,6 +90,11 @@ public class MultimailboxesSearchQuery {
         public boolean equals(Object obj) {
             return obj instanceof AccessibleNamespace;
         }
+
+        @Override
+        public boolean accessDelegatedMailboxes() {
+            return true;
+        }
     }
     
     public static class Builder {
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java
index 23b3e25..3ec0836 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java
@@ -688,6 +688,15 @@ public class StoreMailboxManager implements MailboxManager {
             .filter(Throwing.predicate(mailbox -> storeRightManager.hasRight(mailbox, right, session)));
     }
 
+    private Flux<MailboxId> accessibleMailboxIds(MultimailboxesSearchQuery.Namespace namespace, Right right, MailboxSession session) {
+        MailboxMapper mailboxMapper = mailboxSessionMapperFactory.getMailboxMapper(session);
+        Flux<MailboxId> baseMailboxes = mailboxMapper
+            .userMailboxes(session.getUser());
+        Flux<MailboxId> delegatedMailboxes = getDelegatedMailboxes(mailboxMapper, namespace, right, session);
+        return Flux.concat(baseMailboxes, delegatedMailboxes)
+            .distinct();
+    }
+
     static MailboxQuery.UserBound toSingleUserQuery(MailboxQuery mailboxQuery, MailboxSession mailboxSession) {
         return MailboxQuery.builder()
             .namespace(mailboxQuery.getNamespace().orElse(MailboxConstants.USER_NAMESPACE))
@@ -706,6 +715,15 @@ public class StoreMailboxManager implements MailboxManager {
         return mailboxMapper.findNonPersonalMailboxes(session.getUser(), right);
     }
 
+    private Flux<MailboxId> getDelegatedMailboxes(MailboxMapper mailboxMapper, MultimailboxesSearchQuery.Namespace namespace,
+                                                Right right, MailboxSession session) {
+        if (!namespace.accessDelegatedMailboxes()) {
+            return Flux.empty();
+        }
+        return mailboxMapper.findNonPersonalMailboxes(session.getUser(), right)
+            .map(Mailbox::getMailboxId);
+    }
+
     private MailboxMetaData toMailboxMetadata(MailboxSession session, List<Mailbox> mailboxes, Mailbox mailbox, MailboxCounters counters) throws UnsupportedRightException {
         return new MailboxMetaData(
             mailbox.generateAssociatedPath(),
@@ -732,20 +750,19 @@ public class StoreMailboxManager implements MailboxManager {
 
     @Override
     public Flux<MessageId> search(MultimailboxesSearchQuery expression, MailboxSession session, long limit) throws MailboxException {
-        return getInMailboxes(expression, session)
-            .filter(id -> !expression.getNotInMailboxes().contains(id.getMailboxId()))
-            .filter(mailbox -> expression.getNamespace().keepAccessible(mailbox))
-            .map(Mailbox::getMailboxId)
+        return getInMailboxIds(expression, session)
+            .filter(id -> !expression.getNotInMailboxes().contains(id))
             .collect(Guavate.toImmutableSet())
             .flatMapMany(Throwing.function(ids -> index.search(session, ids, expression.getSearchQuery(), limit)));
     }
 
-
-    private Flux<Mailbox> getInMailboxes(MultimailboxesSearchQuery expression, MailboxSession session) throws MailboxException {
+    private Flux<MailboxId> getInMailboxIds(MultimailboxesSearchQuery expression, MailboxSession session) throws MailboxException {
         if (expression.getInMailboxes().isEmpty()) {
-            return searchMailboxes(expression.getNamespace().associatedMailboxSearchQuery(), session, Right.Read);
+            return accessibleMailboxIds(expression.getNamespace(), Right.Read, session);
         } else {
-            return filterReadable(expression.getInMailboxes(), session);
+            return filterReadable(expression.getInMailboxes(), session)
+                .filter(mailbox -> expression.getNamespace().keepAccessible(mailbox))
+                .map(Mailbox::getMailboxId);
         }
     }
 
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MailboxMapper.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MailboxMapper.java
index 8743be6..a88d6a1 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MailboxMapper.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MailboxMapper.java
@@ -29,6 +29,7 @@ import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.mailbox.model.MailboxPath;
 import org.apache.james.mailbox.model.UidValidity;
 import org.apache.james.mailbox.model.search.MailboxQuery;
+import org.apache.james.mailbox.model.search.Wildcard;
 import org.apache.james.mailbox.store.transaction.Mapper;
 
 import reactor.core.publisher.Flux;
@@ -81,6 +82,17 @@ public interface MailboxMapper extends Mapper {
      */
     Flux<Mailbox> findMailboxWithPathLike(MailboxQuery.UserBound query);
 
+    default Flux<MailboxId> userMailboxes(Username username) {
+        return findMailboxWithPathLike(
+            MailboxQuery.builder()
+                .privateNamespace()
+                .username(username)
+                .expression(Wildcard.INSTANCE)
+                .build()
+                .asUserBound())
+            .map(Mailbox::getMailboxId);
+    }
+
     /**
      * Return if the given {@link Mailbox} has children
      * 


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


[james-project] 20/24: [REFACTORING] MailboxManager is not used in MailboxChangeListener anymore

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 5fc498af76fb0026de3d165968431b26eec4ac3d
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Thu Dec 31 11:44:53 2020 +0700

    [REFACTORING] MailboxManager is not used in MailboxChangeListener anymore
---
 .../main/scala/org/apache/james/jmap/change/MailboxChangeListener.scala | 2 --
 .../scala/org/apache/james/jmap/change/MailboxChangeListenerTest.scala  | 2 +-
 2 files changed, 1 insertion(+), 3 deletions(-)

diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/MailboxChangeListener.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/MailboxChangeListener.scala
index aba030f..f84e64a 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/MailboxChangeListener.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/MailboxChangeListener.scala
@@ -21,7 +21,6 @@ package org.apache.james.jmap.change
 
 import javax.inject.Inject
 import org.apache.james.jmap.api.change.{MailboxChange, MailboxChangeRepository}
-import org.apache.james.mailbox.MailboxManager
 import org.apache.james.mailbox.events.MailboxListener.{MailboxEvent, ReactiveGroupMailboxListener}
 import org.apache.james.mailbox.events.{Event, Group}
 import org.apache.james.util.ReactorUtils.DEFAULT_CONCURRENCY
@@ -33,7 +32,6 @@ import scala.jdk.CollectionConverters._
 case class MailboxChangeListenerGroup() extends Group {}
 
 case class MailboxChangeListener @Inject() (mailboxChangeRepository: MailboxChangeRepository,
-                                            mailboxManager: MailboxManager,
                                             mailboxChangeFactory: MailboxChange.Factory) extends ReactiveGroupMailboxListener {
 
   override def reactiveEvent(event: Event): Publisher[Void] =
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/MailboxChangeListenerTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/MailboxChangeListenerTest.scala
index edced0c..ba6ec97 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/MailboxChangeListenerTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/MailboxChangeListenerTest.scala
@@ -67,7 +67,7 @@ class MailboxChangeListenerTest {
     stateFactory = new State.DefaultFactory
     mailboxChangeFactory = new MailboxChange.Factory(clock, mailboxManager, stateFactory)
     repository = new MemoryMailboxChangeRepository()
-    listener = MailboxChangeListener(repository, mailboxManager, mailboxChangeFactory)
+    listener = MailboxChangeListener(repository, mailboxChangeFactory)
     resources.getEventBus.register(listener)
   }
 


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


[james-project] 18/24: JAMES-3202 Leverage the fact that index does an upsert, removing pre-existing documents

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit c8033f4bfe68810bc3e8a131aaf93926d8980349
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Dec 30 17:02:51 2020 +0700

    JAMES-3202 Leverage the fact that index does an upsert, removing pre-existing documents
    
    We do not need to delete the existing document before indexing it
---
 .../org/apache/mailbox/tools/indexer/ReIndexerPerformer.java     | 9 +++++----
 .../java/org/apache/mailbox/tools/indexer/ReIndexerImplTest.java | 1 -
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/mailbox/tools/indexer/src/main/java/org/apache/mailbox/tools/indexer/ReIndexerPerformer.java b/mailbox/tools/indexer/src/main/java/org/apache/mailbox/tools/indexer/ReIndexerPerformer.java
index 2dd9cde..8343f32 100644
--- a/mailbox/tools/indexer/src/main/java/org/apache/mailbox/tools/indexer/ReIndexerPerformer.java
+++ b/mailbox/tools/indexer/src/main/java/org/apache/mailbox/tools/indexer/ReIndexerPerformer.java
@@ -329,7 +329,7 @@ public class ReIndexerPerformer {
                     if (upToDate) {
                         return Mono.just(Either.<Failure, Result>right(Result.COMPLETED));
                     }
-                    return correct(entry, message);
+                    return correct(entry);
                 }))
             .onErrorResume(e -> {
                 LOGGER.warn("ReIndexing failed for {} {}", entry.getMailbox().generateAssociatedPath(), entry.getUid(), e);
@@ -337,9 +337,10 @@ public class ReIndexerPerformer {
             });
     }
 
-    private Mono<Either<Failure, Result>> correct(ReIndexingEntry entry, MailboxMessage message) {
-        return messageSearchIndex.delete(entry.getMailboxSession(), entry.getMailbox().getMailboxId(), ImmutableList.of(message.getUid()))
-            .then(index(entry));
+    private Mono<Either<Failure, Result>> correct(ReIndexingEntry entry) {
+        // Leverage the fact that index does an upsert, removing pre-existing documents
+        // We do not need to delete the existing document before indexing it
+        return index(entry);
     }
 
     private Mono<Boolean> isIndexUpToDate(Mailbox mailbox, MailboxMessage message) {
diff --git a/mailbox/tools/indexer/src/test/java/org/apache/mailbox/tools/indexer/ReIndexerImplTest.java b/mailbox/tools/indexer/src/test/java/org/apache/mailbox/tools/indexer/ReIndexerImplTest.java
index a47f208..dc9088a 100644
--- a/mailbox/tools/indexer/src/test/java/org/apache/mailbox/tools/indexer/ReIndexerImplTest.java
+++ b/mailbox/tools/indexer/src/test/java/org/apache/mailbox/tools/indexer/ReIndexerImplTest.java
@@ -261,7 +261,6 @@ public class ReIndexerImplTest {
         ArgumentCaptor<Mailbox> mailboxCaptor = ArgumentCaptor.forClass(Mailbox.class);
 
         verify(messageSearchIndex).retrieveIndexedFlags(any(), any());
-        verify(messageSearchIndex).delete(any(), any(), any());
         verify(messageSearchIndex).add(any(MailboxSession.class), mailboxCaptor.capture(), messageCaptor.capture());
         verifyNoMoreInteractions(messageSearchIndex);
 


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


[james-project] 06/24: JAMES-3486 RightManager listRights should depend on mailboxId

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit da29db1d8798efa25afd8c98db4b1d5590b11e35
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Mon Dec 28 11:02:19 2020 +0700

    JAMES-3486 RightManager listRights should depend on mailboxId
---
 .../main/java/org/apache/james/mailbox/RightManager.java  |  2 ++
 .../apache/james/mailbox/store/StoreMailboxManager.java   |  5 +++++
 .../org/apache/james/mailbox/store/StoreRightManager.java |  7 +++++++
 .../org/apache/james/jmap/api/change/MailboxChange.java   | 15 +++++++--------
 4 files changed, 21 insertions(+), 8 deletions(-)

diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/RightManager.java b/mailbox/api/src/main/java/org/apache/james/mailbox/RightManager.java
index deade96..7ec2421 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/RightManager.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/RightManager.java
@@ -85,6 +85,8 @@ public interface RightManager {
 
     MailboxACL listRights(MailboxPath mailboxPath, MailboxSession session) throws MailboxException;
 
+    MailboxACL listRights(MailboxId mailboxId, MailboxSession session) throws MailboxException;
+
     /**
      * Returns the rights applicable to the user who has sent the current
      * request on the mailbox designated by this mailboxPath.
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java
index f51b780..d9fdb2c 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java
@@ -828,6 +828,11 @@ public class StoreMailboxManager implements MailboxManager {
     }
 
     @Override
+    public MailboxACL listRights(MailboxId mailboxId, MailboxSession session) throws MailboxException {
+        return storeRightManager.listRights(mailboxId, session);
+    }
+
+    @Override
     public void applyRightsCommand(MailboxPath mailboxPath, MailboxACL.ACLCommand mailboxACLCommand, MailboxSession session) throws MailboxException {
         storeRightManager.applyRightsCommand(mailboxPath, mailboxACLCommand, session);
     }
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreRightManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreRightManager.java
index 3c15ace..89a2213 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreRightManager.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreRightManager.java
@@ -151,6 +151,13 @@ public class StoreRightManager implements RightManager {
         return mailbox.getACL();
     }
 
+    public MailboxACL listRights(MailboxId mailboxId, MailboxSession session) throws MailboxException {
+        MailboxMapper mapper = mailboxSessionMapperFactory.getMailboxMapper(session);
+        Mailbox mailbox = blockOptional(mapper.findMailboxById(mailboxId))
+            .orElseThrow(() -> new MailboxNotFoundException(mailboxId));
+        return mailbox.getACL();
+    }
+
     @Override
     public void applyRightsCommand(MailboxPath mailboxPath, ACLCommand mailboxACLCommand, MailboxSession session) throws MailboxException {
         assertSharesBelongsToUserDomain(mailboxPath.getUser(), mailboxACLCommand);
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChange.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChange.java
index 2b04691..d4ea806 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChange.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChange.java
@@ -44,7 +44,6 @@ import org.apache.james.mailbox.events.MailboxListener.MailboxRenamed;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.model.MailboxACL;
 import org.apache.james.mailbox.model.MailboxId;
-import org.apache.james.mailbox.model.MailboxPath;
 
 import com.github.steveash.guavate.Guavate;
 import com.google.common.base.MoreObjects;
@@ -165,7 +164,7 @@ public class MailboxChange {
                     .updated(ImmutableList.of(mailboxRenamed.getMailboxId()))
                     .build();
 
-                Stream<MailboxChange> shareeChanges = getSharees(mailboxRenamed.getNewPath(), mailboxRenamed.getUsername(), mailboxManager)
+                Stream<MailboxChange> shareeChanges = getSharees(mailboxRenamed.getMailboxId(), mailboxRenamed.getUsername(), mailboxManager)
                     .map(name -> MailboxChange.builder()
                         .accountId(AccountId.fromString(name))
                         .state(stateFactory.generate())
@@ -189,7 +188,7 @@ public class MailboxChange {
                     .updated(ImmutableList.of(mailboxACLUpdated.getMailboxId()))
                     .build();
 
-                Stream<MailboxChange> shareeChanges = getSharees(mailboxACLUpdated.getMailboxPath(), mailboxACLUpdated.getUsername(), mailboxManager)
+                Stream<MailboxChange> shareeChanges = getSharees(mailboxACLUpdated.getMailboxId(), mailboxACLUpdated.getUsername(), mailboxManager)
                     .map(name -> MailboxChange.builder()
                         .accountId(AccountId.fromString(name))
                         .state(stateFactory.generate())
@@ -242,7 +241,7 @@ public class MailboxChange {
                     .updated(ImmutableList.of(messageAdded.getMailboxId()))
                     .build();
 
-                Stream<MailboxChange> shareeChanges = getSharees(messageAdded.getMailboxPath(), messageAdded.getUsername(), mailboxManager)
+                Stream<MailboxChange> shareeChanges = getSharees(messageAdded.getMailboxId(), messageAdded.getUsername(), mailboxManager)
                     .map(name -> MailboxChange.builder()
                         .accountId(AccountId.fromString(name))
                         .state(stateFactory.generate())
@@ -269,7 +268,7 @@ public class MailboxChange {
                         .updated(ImmutableList.of(messageFlagUpdated.getMailboxId()))
                         .build();
 
-                    Stream<MailboxChange> shareeChanges = getSharees(messageFlagUpdated.getMailboxPath(), messageFlagUpdated.getUsername(), mailboxManager)
+                    Stream<MailboxChange> shareeChanges = getSharees(messageFlagUpdated.getMailboxId(), messageFlagUpdated.getUsername(), mailboxManager)
                         .map(name -> MailboxChange.builder()
                             .accountId(AccountId.fromString(name))
                             .state(stateFactory.generate())
@@ -293,7 +292,7 @@ public class MailboxChange {
                     .updated(ImmutableList.of(expunged.getMailboxId()))
                     .build();
 
-                Stream<MailboxChange> shareeChanges = getSharees(expunged.getMailboxPath(), expunged.getUsername(), mailboxManager)
+                Stream<MailboxChange> shareeChanges = getSharees(expunged.getMailboxId(), expunged.getUsername(), mailboxManager)
                     .map(name -> MailboxChange.builder()
                         .accountId(AccountId.fromString(name))
                         .state(stateFactory.generate())
@@ -311,10 +310,10 @@ public class MailboxChange {
         }
     }
 
-    private static Stream<String> getSharees(MailboxPath path, Username username, MailboxManager mailboxManager) {
+    private static Stream<String> getSharees(MailboxId mailboxId, Username username, MailboxManager mailboxManager) {
         MailboxSession mailboxSession = mailboxManager.createSystemSession(username);
         try {
-            MailboxACL mailboxACL = mailboxManager.listRights(path, mailboxSession);
+            MailboxACL mailboxACL = mailboxManager.listRights(mailboxId, mailboxSession);
             return mailboxACL.getEntries().keySet()
                 .stream()
                 .filter(rfc4314Rights -> !rfc4314Rights.isNegative())


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


[james-project] 10/24: JAMES-3407 Disable read repairs on outdated schema verison

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit be946ffc69823fa98d8fcd4d6f7c678c0d15bcf4
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Dec 29 18:01:25 2020 +0700

    JAMES-3407 Disable read repairs on outdated schema verison
    
    Here is a flacky test output:
    
    ```
    [ERROR]   CassandraMailboxMapperTest$ConsistencyTest.createAfterPreviousDeleteOnFailedCreateShouldCreateAMailbox:617 Multiple Failures (1 failure)
    
    Expected size:<1> but was:<2> in:
    <[Mailbox{id=88fcc460-49bf-11eb-84d0-b78591c28a40, namespace=#private, user=Username{localPart=user, domainPart=Optional.empty}, name=name},
        Mailbox{id=9a2095a0-49bf-11eb-84d0-b78591c28a40, namespace=#private, user=Username{localPart=user, domainPart=Optional.empty}, name=INBOX}]>
    at CassandraMailboxMapperTest$ConsistencyTest.lambda$createAfterPreviousDeleteOnFailedCreateShouldCreateAMailbox$34(CassandraMailboxMapperTest$ConsistencyTest.java:628)
    ```
    
    What is stricking is that createAfterPreviousDeleteOnFailedCreateShouldCreateAMailbox
    do not rely on "name" mailbox, which do not appear in the test.
    
    Likely an asynchronous read repair being triggered, that violates
    test encapsulation...
---
 .../cassandra/mail/CassandraMailboxMapperTest.java | 599 +++++++++++----------
 1 file changed, 312 insertions(+), 287 deletions(-)

diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperTest.java
index 7e9fb00..d2f060b 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxMapperTest.java
@@ -69,6 +69,7 @@ import com.github.fge.lambdas.Throwing;
 import com.github.fge.lambdas.runnable.ThrowingRunnable;
 
 import reactor.core.publisher.Mono;
+import reactor.core.scheduler.Schedulers;
 
 class CassandraMailboxMapperTest {
     private static final UidValidity UID_VALIDITY = UidValidity.of(52);
@@ -112,9 +113,14 @@ class CassandraMailboxMapperTest {
         versionDAO.truncateVersion()
             .then(versionDAO.updateVersion(new SchemaVersion(7)))
             .block();
+        setUpTestee(CassandraConfiguration.DEFAULT_CONFIGURATION);
+    }
+
+    private void setUpTestee(CassandraConfiguration cassandraConfiguration) {
+        CassandraCluster cassandra = cassandraCluster.getCassandraCluster();
         CassandraSchemaVersionManager versionManager = new CassandraSchemaVersionManager(versionDAO);
 
-        CassandraACLDAOV1 aclDAOV1 = new CassandraACLDAOV1(cassandra.getConf(), CassandraConfiguration.DEFAULT_CONFIGURATION, CassandraConsistenciesConfiguration.DEFAULT);
+        CassandraACLDAOV1 aclDAOV1 = new CassandraACLDAOV1(cassandra.getConf(), cassandraConfiguration, CassandraConsistenciesConfiguration.DEFAULT);
         CassandraACLDAOV2 aclDAOv2 = new CassandraACLDAOV2(cassandra.getConf());
         JsonEventSerializer jsonEventSerializer = JsonEventSerializer
             .forModules(ACLModule.ACL_UPDATE)
@@ -133,7 +139,7 @@ class CassandraMailboxMapperTest {
             usersRightDAO,
             aclMapper,
             versionManager,
-            CassandraConfiguration.DEFAULT_CONFIGURATION);
+            cassandraConfiguration);
     }
 
     @Nested
@@ -273,6 +279,14 @@ class CassandraMailboxMapperTest {
 
         @Nested
         class ReadRepairs {
+            @BeforeEach
+            void setVersion() {
+                // Read repairs should not be performed with an outdated data representation
+                versionDAO.truncateVersion()
+                    .then(versionDAO.updateVersion(new SchemaVersion(8)))
+                    .block();
+            }
+
             @Test
             void findMailboxByIdShouldEventuallyFixInconsistencyWhenMailboxIsNotInPath() {
                 mailboxDAO.save(MAILBOX)
@@ -770,335 +784,346 @@ class CassandraMailboxMapperTest {
         return new MailboxPath(fromMailboxPath, StringUtils.repeat("b", 65537));
     }
 
-    @Test
-    void deleteShouldDeleteMailboxAndMailboxPathFromV1Table() {
-        mailboxDAO.save(MAILBOX)
-            .block();
-        mailboxPathDAO.save(MAILBOX_PATH, MAILBOX_ID)
-            .block();
-
-        testee.delete(MAILBOX).block();
-
-        assertThat(testee.findMailboxByPath(MAILBOX_PATH).blockOptional())
-            .isEmpty();
-    }
-
-    @Test
-    void deleteShouldDeleteMailboxAndMailboxPathFromV2Table() {
-        mailboxDAO.save(MAILBOX)
-            .block();
-        mailboxPathV2DAO.save(MAILBOX_PATH, MAILBOX_ID)
-            .block();
-
-        testee.delete(MAILBOX).block();
-
-        assertThat(testee.findMailboxByPath(MAILBOX_PATH).blockOptional())
-            .isEmpty();
-    }
-
-    @Test
-    void deleteShouldDeleteMailboxAndMailboxPathFromV3Table() {
-        mailboxDAO.save(MAILBOX)
-            .block();
-        mailboxPathV3DAO.save(MAILBOX)
-            .block();
-
-        testee.delete(MAILBOX).block();
-
-        assertThat(testee.findMailboxByPath(MAILBOX_PATH).blockOptional())
-            .isEmpty();
-    }
-
-    @Test
-    void deleteShouldDeleteMailboxAndMailboxPathFromAllTables() {
-        mailboxDAO.save(MAILBOX)
-            .block();
-        mailboxPathDAO.save(MAILBOX_PATH, MAILBOX_ID)
-            .block();
-        mailboxPathV2DAO.save(MAILBOX_PATH, MAILBOX_ID)
-            .block();
-        mailboxPathV3DAO.save(MAILBOX)
-            .block();
-
-        testee.delete(MAILBOX).block();
-
-        assertThat(testee.findMailboxByPath(MAILBOX_PATH).blockOptional())
-            .isEmpty();
-    }
-
-    @Test
-    void findMailboxByPathShouldReturnMailboxWhenExistsInV1Table() {
-        mailboxDAO.save(MAILBOX)
-            .block();
-        mailboxPathDAO.save(MAILBOX_PATH, MAILBOX_ID)
-            .block();
-
-        Mailbox mailbox = testee.findMailboxByPath(MAILBOX_PATH).block();
+    @Nested
+    class ReadFallbackToPreviousTables {
+        @BeforeEach
+        void setUp() {
+            // Read repairs are not supported accross schema versions...
+            setUpTestee(CassandraConfiguration.builder()
+                .mailboxReadRepair(0f)
+                .build());
+        }
 
-        assertThat(mailbox.generateAssociatedPath()).isEqualTo(MAILBOX_PATH);
-    }
+        @Test
+        void deleteShouldDeleteMailboxAndMailboxPathFromV1Table() {
+            mailboxDAO.save(MAILBOX)
+                .block();
+            mailboxPathDAO.save(MAILBOX_PATH, MAILBOX_ID)
+                .block();
 
-    @Test
-    void findMailboxByPathShouldReturnMailboxWhenExistsInV2Table() {
-        mailboxDAO.save(MAILBOX)
-            .block();
-        mailboxPathV2DAO.save(MAILBOX_PATH, MAILBOX_ID)
-            .block();
+            testee.delete(MAILBOX).block();
 
-        Mailbox mailbox = testee.findMailboxByPath(MAILBOX_PATH).block();
+            assertThat(testee.findMailboxByPath(MAILBOX_PATH).blockOptional())
+                .isEmpty();
+        }
 
-        assertThat(mailbox.generateAssociatedPath()).isEqualTo(MAILBOX_PATH);
-    }
+        @Test
+        void deleteShouldDeleteMailboxAndMailboxPathFromV2Table() {
+            mailboxDAO.save(MAILBOX)
+                .block();
+            mailboxPathV2DAO.save(MAILBOX_PATH, MAILBOX_ID)
+                .block();
 
-    @Test
-    void findMailboxByPathShouldReturnMailboxWhenExistsInV3Table() {
-        mailboxDAO.save(MAILBOX)
-            .block();
-        mailboxPathV3DAO.save(MAILBOX)
-            .block();
+            testee.delete(MAILBOX).block();
 
-        Mailbox mailbox = testee.findMailboxByPath(MAILBOX_PATH).block();
+            assertThat(testee.findMailboxByPath(MAILBOX_PATH).blockOptional())
+                .isEmpty();
+        }
 
-        assertThat(mailbox.generateAssociatedPath()).isEqualTo(MAILBOX_PATH);
-    }
+        @Test
+        void deleteShouldDeleteMailboxAndMailboxPathFromV3Table() {
+            mailboxDAO.save(MAILBOX)
+                .block();
+            mailboxPathV3DAO.save(MAILBOX)
+                .block();
 
-    @Test
-    void findMailboxByPathShouldReturnMailboxWhenExistsInAllTables() {
-        mailboxDAO.save(MAILBOX)
-            .block();
-        mailboxPathDAO.save(MAILBOX_PATH, MAILBOX_ID)
-            .block();
-        mailboxPathV2DAO.save(MAILBOX_PATH, MAILBOX_ID)
-            .block();
-        mailboxPathV3DAO.save(MAILBOX)
-            .block();
+            testee.delete(MAILBOX).block();
 
-        Mailbox mailbox = testee.findMailboxByPath(MAILBOX_PATH).block();
+            assertThat(testee.findMailboxByPath(MAILBOX_PATH).blockOptional())
+                .isEmpty();
+        }
 
-        assertThat(mailbox.generateAssociatedPath()).isEqualTo(MAILBOX_PATH);
-    }
+        @Test
+        void deleteShouldDeleteMailboxAndMailboxPathFromAllTables() {
+            mailboxDAO.save(MAILBOX)
+                .block();
+            mailboxPathDAO.save(MAILBOX_PATH, MAILBOX_ID)
+                .block();
+            mailboxPathV2DAO.save(MAILBOX_PATH, MAILBOX_ID)
+                .block();
+            mailboxPathV3DAO.save(MAILBOX)
+                .block();
+
+            testee.delete(MAILBOX).block();
+
+            assertThat(testee.findMailboxByPath(MAILBOX_PATH).blockOptional())
+                .isEmpty();
+        }
 
-    @Test
-    void deleteShouldRemoveMailboxWhenInAllTables() {
-        mailboxDAO.save(MAILBOX)
-            .block();
-        mailboxPathDAO.save(MAILBOX_PATH, MAILBOX_ID)
-            .block();
-        mailboxPathV2DAO.save(MAILBOX_PATH, MAILBOX_ID)
-            .block();
-        mailboxPathV3DAO.save(MAILBOX)
-            .block();
+        @Test
+        void findMailboxByPathShouldReturnMailboxWhenExistsInV1Table() {
+            mailboxDAO.save(MAILBOX)
+                .block();
+            mailboxPathDAO.save(MAILBOX_PATH, MAILBOX_ID)
+                .block();
 
-        testee.delete(MAILBOX).block();
+            Mailbox mailbox = testee.findMailboxByPath(MAILBOX_PATH).block();
 
-        assertThat(testee.findMailboxByPath(MAILBOX_PATH).blockOptional())
-            .isEmpty();
-    }
+            assertThat(mailbox.generateAssociatedPath()).isEqualTo(MAILBOX_PATH);
+        }
 
-    @Test
-    void deleteShouldRemoveMailboxWhenInV1Tables() {
-        mailboxDAO.save(MAILBOX)
-            .block();
-        mailboxPathDAO.save(MAILBOX_PATH, MAILBOX_ID)
-            .block();
+        @Test
+        void findMailboxByPathShouldReturnMailboxWhenExistsInV2Table() {
+            mailboxDAO.save(MAILBOX)
+                .block();
+            mailboxPathV2DAO.save(MAILBOX_PATH, MAILBOX_ID)
+                .block();
 
-        testee.delete(MAILBOX).block();
+            Mailbox mailbox = testee.findMailboxByPath(MAILBOX_PATH).block();
 
-        assertThat(testee.findMailboxByPath(MAILBOX_PATH).blockOptional())
-            .isEmpty();
-    }
+            assertThat(mailbox.generateAssociatedPath()).isEqualTo(MAILBOX_PATH);
+        }
 
-    @Test
-    void deleteShouldRemoveMailboxWhenInV2Table() {
-        mailboxDAO.save(MAILBOX)
-            .block();
-        mailboxPathV2DAO.save(MAILBOX_PATH, MAILBOX_ID)
-            .block();
+        @Test
+        void findMailboxByPathShouldReturnMailboxWhenExistsInV3Table() {
+            mailboxDAO.save(MAILBOX)
+                .block();
+            mailboxPathV3DAO.save(MAILBOX)
+                .block();
 
-        testee.delete(MAILBOX).block();
+            Mailbox mailbox = testee.findMailboxByPath(MAILBOX_PATH).block();
 
-        assertThat(testee.findMailboxByPath(MAILBOX_PATH).blockOptional())
-            .isEmpty();
-    }
+            assertThat(mailbox.generateAssociatedPath()).isEqualTo(MAILBOX_PATH);
+        }
 
-    @Test
-    void findMailboxByPathShouldThrowWhenDoesntExistInBothTables() {
-        mailboxDAO.save(MAILBOX)
-            .block();
+        @Test
+        void findMailboxByPathShouldReturnMailboxWhenExistsInAllTables() {
+            mailboxDAO.save(MAILBOX)
+                .block();
+            mailboxPathDAO.save(MAILBOX_PATH, MAILBOX_ID)
+                .block();
+            mailboxPathV2DAO.save(MAILBOX_PATH, MAILBOX_ID)
+                .block();
+            mailboxPathV3DAO.save(MAILBOX)
+                .block();
+
+            Mailbox mailbox = testee.findMailboxByPath(MAILBOX_PATH).block();
+
+            assertThat(mailbox.generateAssociatedPath()).isEqualTo(MAILBOX_PATH);
+        }
 
-        assertThat(testee.findMailboxByPath(MAILBOX_PATH).blockOptional())
-            .isEmpty();
-    }
+        @Test
+        void deleteShouldRemoveMailboxWhenInAllTables() {
+            mailboxDAO.save(MAILBOX)
+                .block();
+            mailboxPathDAO.save(MAILBOX_PATH, MAILBOX_ID)
+                .block();
+            mailboxPathV2DAO.save(MAILBOX_PATH, MAILBOX_ID)
+                .block();
+            mailboxPathV3DAO.save(MAILBOX)
+                .block();
+
+            testee.delete(MAILBOX).block();
+
+            assertThat(testee.findMailboxByPath(MAILBOX_PATH).blockOptional())
+                .isEmpty();
+        }
 
-    @Test
-    void findMailboxWithPathLikeShouldReturnMailboxesWhenExistsInV1Table() {
-        mailboxDAO.save(MAILBOX)
-            .block();
-        mailboxPathDAO.save(MAILBOX_PATH, MAILBOX_ID)
-            .block();
+        @Test
+        void deleteShouldRemoveMailboxWhenInV1Tables() {
+            mailboxDAO.save(MAILBOX)
+                .block();
+            mailboxPathDAO.save(MAILBOX_PATH, MAILBOX_ID)
+                .block();
 
-        List<Mailbox> mailboxes = testee.findMailboxWithPathLike(MailboxQuery.builder()
-            .privateNamespace()
-            .username(USER)
-            .expression(Wildcard.INSTANCE)
-            .build()
-            .asUserBound())
-            .collectList().block();
+            testee.delete(MAILBOX).block();
 
-        assertThat(mailboxes).containsOnly(MAILBOX);
-    }
+            assertThat(testee.findMailboxByPath(MAILBOX_PATH).blockOptional())
+                .isEmpty();
+        }
 
-    @Test
-    void findMailboxWithPathLikeShouldReturnMailboxesWhenExistsInBothTables() {
-        mailboxDAO.save(MAILBOX)
-            .block();
-        mailboxPathDAO.save(MAILBOX_PATH, MAILBOX_ID)
-            .block();
-        mailboxPathV2DAO.save(MAILBOX_PATH, MAILBOX_ID)
-            .block();
+        @Test
+        void deleteShouldRemoveMailboxWhenInV2Table() {
+            mailboxDAO.save(MAILBOX)
+                .block();
+            mailboxPathV2DAO.save(MAILBOX_PATH, MAILBOX_ID)
+                .block();
 
-        List<Mailbox> mailboxes = testee.findMailboxWithPathLike(MailboxQuery.builder()
-            .privateNamespace()
-            .username(USER)
-            .expression(Wildcard.INSTANCE)
-            .build()
-            .asUserBound())
-            .collectList().block();
+            testee.delete(MAILBOX).block();
 
-        assertThat(mailboxes).containsOnly(MAILBOX);
-    }
+            assertThat(testee.findMailboxByPath(MAILBOX_PATH).blockOptional())
+                .isEmpty();
+        }
 
-    @Test
-    void findMailboxWithPathLikeShouldReturnMailboxesWhenExistsInV2Table() {
-        mailboxDAO.save(MAILBOX)
-            .block();
-        mailboxPathV2DAO.save(MAILBOX_PATH, MAILBOX_ID)
-            .block();
+        @Test
+        void findMailboxByPathShouldThrowWhenDoesntExistInBothTables() {
+            mailboxDAO.save(MAILBOX)
+                .block();
 
-        List<Mailbox> mailboxes = testee.findMailboxWithPathLike(MailboxQuery.builder()
-            .privateNamespace()
-            .username(USER)
-            .expression(Wildcard.INSTANCE)
-            .build()
-            .asUserBound())
-            .collectList().block();
+            assertThat(testee.findMailboxByPath(MAILBOX_PATH).blockOptional())
+                .isEmpty();
+        }
 
-        assertThat(mailboxes).containsOnly(MAILBOX);
-    }
+        @Test
+        void findMailboxWithPathLikeShouldReturnMailboxesWhenExistsInV1Table() {
+            mailboxDAO.save(MAILBOX)
+                .block();
+            mailboxPathDAO.save(MAILBOX_PATH, MAILBOX_ID)
+                .block();
 
-    @Test
-    void findMailboxWithPathLikeShouldReturnMailboxesWhenExistsInV3Table() {
-        mailboxDAO.save(MAILBOX)
-            .block();
-        mailboxPathV3DAO.save(MAILBOX)
-            .block();
+            List<Mailbox> mailboxes = testee.findMailboxWithPathLike(MailboxQuery.builder()
+                .privateNamespace()
+                .username(USER)
+                .expression(Wildcard.INSTANCE)
+                .build()
+                .asUserBound())
+                .collectList().block();
 
-        List<Mailbox> mailboxes = testee.findMailboxWithPathLike(MailboxQuery.builder()
-            .privateNamespace()
-            .username(USER)
-            .expression(Wildcard.INSTANCE)
-            .build()
-            .asUserBound())
-            .collectList()
-            .block();
+            assertThat(mailboxes).containsOnly(MAILBOX);
+        }
 
-        assertThat(mailboxes).containsOnly(MAILBOX);
-    }
+        @Test
+        void findMailboxWithPathLikeShouldReturnMailboxesWhenExistsInBothTables() {
+            mailboxDAO.save(MAILBOX)
+                .block();
+            mailboxPathDAO.save(MAILBOX_PATH, MAILBOX_ID)
+                .block();
+            mailboxPathV2DAO.save(MAILBOX_PATH, MAILBOX_ID)
+                .block();
+
+            List<Mailbox> mailboxes = testee.findMailboxWithPathLike(MailboxQuery.builder()
+                .privateNamespace()
+                .username(USER)
+                .expression(Wildcard.INSTANCE)
+                .build()
+                .asUserBound())
+                .collectList().block();
 
-    @Test
-    void hasChildrenShouldReturnChildWhenExistsInV1Table() {
-        mailboxDAO.save(MAILBOX)
-            .block();
-        mailboxPathDAO.save(MAILBOX_PATH, MAILBOX_ID)
-            .block();
-        CassandraId childMailboxId = CassandraId.timeBased();
-        MailboxPath childMailboxPath = MailboxPath.forUser(USER, "name.child");
-        Mailbox childMailbox = new Mailbox(childMailboxPath, UID_VALIDITY, childMailboxId);
-        mailboxDAO.save(childMailbox)
-            .block();
-        mailboxPathDAO.save(childMailboxPath, childMailboxId)
-            .block();
-    
-        boolean hasChildren = testee.hasChildren(MAILBOX, '.').block();
+            assertThat(mailboxes).containsOnly(MAILBOX);
+        }
 
-        assertThat(hasChildren).isTrue();
-    }
+        @Test
+        void findMailboxWithPathLikeShouldReturnMailboxesWhenExistsInV2Table() {
+            mailboxDAO.save(MAILBOX)
+                .block();
+            mailboxPathV2DAO.save(MAILBOX_PATH, MAILBOX_ID)
+                .block();
 
-    @Test
-    void hasChildrenShouldReturnChildWhenExistsInBothTables() {
-        mailboxDAO.save(MAILBOX)
-            .block();
-        mailboxPathDAO.save(MAILBOX_PATH, MAILBOX_ID)
-            .block();
-        mailboxPathV2DAO.save(MAILBOX_PATH, MAILBOX_ID)
-            .block();
-        CassandraId childMailboxId = CassandraId.timeBased();
-        MailboxPath childMailboxPath = MailboxPath.forUser(USER, "name.child");
-        Mailbox childMailbox = new Mailbox(childMailboxPath, UID_VALIDITY, childMailboxId);
-        mailboxDAO.save(childMailbox)
-            .block();
-        mailboxPathDAO.save(childMailboxPath, childMailboxId)
-            .block();
+            List<Mailbox> mailboxes = testee.findMailboxWithPathLike(MailboxQuery.builder()
+                .privateNamespace()
+                .username(USER)
+                .expression(Wildcard.INSTANCE)
+                .build()
+                .asUserBound())
+                .collectList().block();
 
-        boolean hasChildren = testee.hasChildren(MAILBOX, '.').block();
+            assertThat(mailboxes).containsOnly(MAILBOX);
+        }
 
-        assertThat(hasChildren).isTrue();
-    }
+        @Test
+        void findMailboxWithPathLikeShouldReturnMailboxesWhenExistsInV3Table() {
+            mailboxDAO.save(MAILBOX)
+                .block();
+            mailboxPathV3DAO.save(MAILBOX)
+                .block();
 
-    @Test
-    void hasChildrenShouldReturnChildWhenExistsInV2Table() {
-        mailboxDAO.save(MAILBOX)
-            .block();
-        mailboxPathV2DAO.save(MAILBOX_PATH, MAILBOX_ID)
-            .block();
-        CassandraId childMailboxId = CassandraId.timeBased();
-        MailboxPath childMailboxPath = MailboxPath.forUser(USER, "name.child");
-        Mailbox childMailbox = new Mailbox(childMailboxPath, UID_VALIDITY, childMailboxId);
-        mailboxDAO.save(childMailbox)
-            .block();
-        mailboxPathV2DAO.save(childMailboxPath, childMailboxId)
-            .block();
-    
-        boolean hasChildren = testee.hasChildren(MAILBOX, '.').block();
-    
-        assertThat(hasChildren).isTrue();
-    }
+            List<Mailbox> mailboxes = testee.findMailboxWithPathLike(MailboxQuery.builder()
+                .privateNamespace()
+                .username(USER)
+                .expression(Wildcard.INSTANCE)
+                .build()
+                .asUserBound())
+                .collectList()
+                .block();
 
-    @Test
-    void hasChildrenShouldReturnChildWhenExistsInV3Table() {
-        mailboxDAO.save(MAILBOX)
-            .block();
-        mailboxPathV3DAO.save(MAILBOX)
-            .block();
-        CassandraId childMailboxId = CassandraId.timeBased();
-        MailboxPath childMailboxPath = MailboxPath.forUser(USER, "name.child");
-        Mailbox childMailbox = new Mailbox(childMailboxPath, UID_VALIDITY, childMailboxId);
-        mailboxDAO.save(childMailbox)
-            .block();
-        mailboxPathV3DAO.save(childMailbox)
-            .block();
+            assertThat(mailboxes).containsOnly(MAILBOX);
+        }
 
-        boolean hasChildren = testee.hasChildren(MAILBOX, '.').block();
+        @Test
+        void hasChildrenShouldReturnChildWhenExistsInV1Table() {
+            mailboxDAO.save(MAILBOX)
+                .block();
+            mailboxPathDAO.save(MAILBOX_PATH, MAILBOX_ID)
+                .block();
+            CassandraId childMailboxId = CassandraId.timeBased();
+            MailboxPath childMailboxPath = MailboxPath.forUser(USER, "name.child");
+            Mailbox childMailbox = new Mailbox(childMailboxPath, UID_VALIDITY, childMailboxId);
+            mailboxDAO.save(childMailbox)
+                .block();
+            mailboxPathDAO.save(childMailboxPath, childMailboxId)
+                .block();
+
+            boolean hasChildren = testee.hasChildren(MAILBOX, '.').block();
+
+            assertThat(hasChildren).isTrue();
+        }
 
-        assertThat(hasChildren).isTrue();
-    }
+        @Test
+        void hasChildrenShouldReturnChildWhenExistsInBothTables() {
+            mailboxDAO.save(MAILBOX)
+                .block();
+            mailboxPathDAO.save(MAILBOX_PATH, MAILBOX_ID)
+                .block();
+            mailboxPathV2DAO.save(MAILBOX_PATH, MAILBOX_ID)
+                .block();
+            CassandraId childMailboxId = CassandraId.timeBased();
+            MailboxPath childMailboxPath = MailboxPath.forUser(USER, "name.child");
+            Mailbox childMailbox = new Mailbox(childMailboxPath, UID_VALIDITY, childMailboxId);
+            mailboxDAO.save(childMailbox)
+                .block();
+            mailboxPathDAO.save(childMailboxPath, childMailboxId)
+                .block();
+
+            boolean hasChildren = testee.hasChildren(MAILBOX, '.').block();
+
+            assertThat(hasChildren).isTrue();
+        }
 
-    @Test
-    void findMailboxWithPathLikeShouldRemoveDuplicatesAndKeepV3() {
-        mailboxDAO.save(MAILBOX).block();
-        mailboxPathV3DAO.save(MAILBOX).block();
+        @Test
+        void hasChildrenShouldReturnChildWhenExistsInV2Table() {
+            mailboxDAO.save(MAILBOX)
+                .block();
+            mailboxPathV2DAO.save(MAILBOX_PATH, MAILBOX_ID)
+                .block();
+            CassandraId childMailboxId = CassandraId.timeBased();
+            MailboxPath childMailboxPath = MailboxPath.forUser(USER, "name.child");
+            Mailbox childMailbox = new Mailbox(childMailboxPath, UID_VALIDITY, childMailboxId);
+            mailboxDAO.save(childMailbox)
+                .block();
+            mailboxPathV2DAO.save(childMailboxPath, childMailboxId)
+                .block();
+
+            boolean hasChildren = testee.hasChildren(MAILBOX, '.').block();
+
+            assertThat(hasChildren).isTrue();
+        }
 
-        mailboxDAO.save(MAILBOX_BIS).block();
-        mailboxPathDAO.save(MAILBOX_PATH, MAILBOX_ID_2).block();
+        @Test
+        void hasChildrenShouldReturnChildWhenExistsInV3Table() {
+            mailboxDAO.save(MAILBOX)
+                .block();
+            mailboxPathV3DAO.save(MAILBOX)
+                .block();
+            CassandraId childMailboxId = CassandraId.timeBased();
+            MailboxPath childMailboxPath = MailboxPath.forUser(USER, "name.child");
+            Mailbox childMailbox = new Mailbox(childMailboxPath, UID_VALIDITY, childMailboxId);
+            mailboxDAO.save(childMailbox)
+                .block();
+            mailboxPathV3DAO.save(childMailbox)
+                .block();
+
+            boolean hasChildren = testee.hasChildren(MAILBOX, '.').block();
+
+            assertThat(hasChildren).isTrue();
+        }
 
-        assertThat(testee.findMailboxWithPathLike(
-            MailboxQuery.builder()
-                .privateNamespace()
-                .username(USER)
-                .expression(Wildcard.INSTANCE)
-                .build()
-                .asUserBound())
-            .collectList().block())
-            .containsOnly(MAILBOX);
+        @Test
+        void findMailboxWithPathLikeShouldRemoveDuplicatesAndKeepV3() {
+            mailboxDAO.save(MAILBOX).block();
+            mailboxPathV3DAO.save(MAILBOX).block();
+
+            mailboxDAO.save(MAILBOX_BIS).block();
+            mailboxPathDAO.save(MAILBOX_PATH, MAILBOX_ID_2).block();
+
+            assertThat(testee.findMailboxWithPathLike(
+                MailboxQuery.builder()
+                    .privateNamespace()
+                    .username(USER)
+                    .expression(Wildcard.INSTANCE)
+                    .build()
+                    .asUserBound())
+                .collectList().block())
+                .containsOnly(MAILBOX);
+        }
     }
 }


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


[james-project] 03/24: JAMES-3486 Test MailboxId should be generated

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 49b3668bf6ebd66726a364788f24c3694a67891e
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Mon Dec 28 10:44:40 2020 +0700

    JAMES-3486 Test MailboxId should be generated
---
 .../james/jmap/rfc8621/memory/MemoryMailboxChangesMethodTest.java | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryMailboxChangesMethodTest.java b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryMailboxChangesMethodTest.java
index 58e6359..df68a52 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryMailboxChangesMethodTest.java
+++ b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryMailboxChangesMethodTest.java
@@ -26,10 +26,13 @@ import org.apache.james.JamesServerBuilder;
 import org.apache.james.JamesServerExtension;
 import org.apache.james.jmap.api.change.State;
 import org.apache.james.jmap.rfc8621.contract.MailboxChangesMethodContract;
+import org.apache.james.mailbox.inmemory.InMemoryId;
+import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.modules.TestJMAPServerModule;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
 public class MemoryMailboxChangesMethodTest implements MailboxChangesMethodContract {
+
     @RegisterExtension
     static JamesServerExtension testExtension = new JamesServerBuilder<>(JamesServerBuilder.defaultConfigurationProvider())
         .server(configuration -> GuiceJamesServer.forConfiguration(configuration)
@@ -41,4 +44,9 @@ public class MemoryMailboxChangesMethodTest implements MailboxChangesMethodContr
     public State.Factory stateFactory() {
         return new State.DefaultFactory();
     }
+
+    @Override
+    public MailboxId generateMailboxId() {
+        return InMemoryId.of(0);
+    }
 }
\ No newline at end of file


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


[james-project] 13/24: JAMES-3485 Remove unused method

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 893ccaa5232729c55530b3f84bfc02f32478a045
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Dec 25 19:32:12 2020 +0700

    JAMES-3485 Remove unused method
---
 .../main/java/org/apache/james/mailbox/store/StoreMailboxManager.java | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java
index d9fdb2c..23b3e25 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java
@@ -749,10 +749,6 @@ public class StoreMailboxManager implements MailboxManager {
         }
     }
 
-    private Flux<Mailbox> getAllReadableMailbox(MailboxQuery mailboxQuery, MailboxSession session) throws MailboxException {
-        return searchMailboxes(mailboxQuery, session, Right.Read);
-    }
-
     private Flux<Mailbox> filterReadable(ImmutableSet<MailboxId> inMailboxes, MailboxSession session) throws MailboxException {
         MailboxMapper mailboxMapper = mailboxSessionMapperFactory.getMailboxMapper(session);
         return Flux.fromIterable(inMailboxes)


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


[james-project] 11/24: JAMES-3485 Group attachment right checking together

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 9fe885b27a0791a8e2f700adb210faf3dd4b045e
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Dec 25 19:14:30 2020 +0700

    JAMES-3485 Group attachment right checking together
    
    A common JMAP pattern is to save a message being a copy of
    another one (draft update).
    
    Updating a draft with several attachment will lead to right
    checking to happen for each attachment independently despite them
    to be related to the same message.
    
    So here are the reads being performed:
     - From attachmentId retrieve the owner -> miss
     - From attachmentId retrieve the messageId
     - From the messageId retrieve the message (and its mailbox
       context) - imapUidTable + messageV3 table
     - Then read the mailbox to validate the ACLs (mailbox + aclV2)
---
 .../mailbox/store/StoreAttachmentManager.java      | 48 ++++++++++++++++++++--
 1 file changed, 45 insertions(+), 3 deletions(-)

diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreAttachmentManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreAttachmentManager.java
index 7a4efe2..4044b4c 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreAttachmentManager.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreAttachmentManager.java
@@ -23,9 +23,13 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
 
 import javax.inject.Inject;
 
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.james.core.Username;
 import org.apache.james.mailbox.AttachmentManager;
 import org.apache.james.mailbox.MailboxSession;
@@ -41,7 +45,10 @@ import org.reactivestreams.Publisher;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.github.fge.lambdas.Throwing;
 import com.github.steveash.guavate.Guavate;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
 
 public class StoreAttachmentManager implements AttachmentManager {
     private static final Logger LOGGER = LoggerFactory.getLogger(StoreAttachmentManager.class);
@@ -70,9 +77,7 @@ public class StoreAttachmentManager implements AttachmentManager {
 
     @Override
     public List<AttachmentMetadata> getAttachments(List<AttachmentId> attachmentIds, MailboxSession mailboxSession) throws MailboxException {
-        List<AttachmentId> accessibleAttachmentIds = attachmentIds.stream()
-            .filter(attachmentId -> userHasAccessToAttachment(attachmentId, mailboxSession))
-            .collect(Guavate.toImmutableList());
+        Collection<AttachmentId> accessibleAttachmentIds = keepAccessible(attachmentIds, mailboxSession);
 
         return attachmentMapperFactory.getAttachmentMapper(mailboxSession).getAttachments(accessibleAttachmentIds);
     }
@@ -93,6 +98,24 @@ public class StoreAttachmentManager implements AttachmentManager {
         }
     }
 
+    private Collection<AttachmentId> keepAccessible(Collection<AttachmentId> attachmentIds, MailboxSession mailboxSession) {
+        try {
+            Set<AttachmentId> referencedByMessages = referencedInUserMessages(attachmentIds, mailboxSession);
+            ImmutableSet<AttachmentId> owned = Sets.difference(ImmutableSet.copyOf(attachmentIds), referencedByMessages)
+                .stream()
+                .filter(Throwing.<AttachmentId>predicate(id -> isExplicitlyAOwner(id, mailboxSession)).sneakyThrow())
+                .collect(Guavate.toImmutableSet());
+
+            return ImmutableSet.<AttachmentId>builder()
+                .addAll(referencedByMessages)
+                .addAll(owned)
+                .build();
+        } catch (MailboxException e) {
+            LOGGER.warn("Error while checking attachment related accessible message ids", e);
+            throw new RuntimeException(e);
+        }
+    }
+
     private boolean isReferencedInUserMessages(AttachmentId attachmentId, MailboxSession mailboxSession) throws MailboxException {
         Collection<MessageId> relatedMessageIds = getRelatedMessageIds(attachmentId, mailboxSession);
         return !messageIdManager
@@ -100,6 +123,25 @@ public class StoreAttachmentManager implements AttachmentManager {
             .isEmpty();
     }
 
+    private Set<AttachmentId> referencedInUserMessages(Collection<AttachmentId> attachmentIds, MailboxSession mailboxSession) throws MailboxException {
+        Map<MessageId, Collection<AttachmentId>> entries = attachmentIds.stream()
+            .flatMap(Throwing.<AttachmentId, Stream<Pair<MessageId, AttachmentId>>>function(
+                attachmentId -> getRelatedMessageIds(attachmentId, mailboxSession).stream()
+                    .map(messageId -> Pair.of(messageId, attachmentId)))
+                .sneakyThrow())
+            .collect(Guavate.toImmutableListMultimap(
+                Pair::getKey,
+                Pair::getValue))
+            .asMap();
+
+        Set<MessageId> accessibleMessages = messageIdManager.accessibleMessages(entries.keySet(), mailboxSession);
+
+        return entries.entrySet().stream()
+            .filter(entry -> accessibleMessages.contains(entry.getKey()))
+            .flatMap(entry -> entry.getValue().stream())
+            .collect(Guavate.toImmutableSet());
+    }
+
     private boolean isExplicitlyAOwner(AttachmentId attachmentId, MailboxSession mailboxSession) throws MailboxException {
         Collection<Username> explicitOwners = attachmentMapperFactory.getAttachmentMapper(mailboxSession)
             .getOwners(attachmentId);


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


[james-project] 08/24: JAMES-3461 Register MailboxChangeListener by default

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit d619483c7c554b809300a4a4cceb0db2dc51f3c0
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Dec 31 18:15:38 2020 +0700

    JAMES-3461 Register MailboxChangeListener by default
    
    tI usage is needed for JMAP RFC-8621 correctness, running it is not an option
---
 .../java/org/apache/james/modules/mailbox/CassandraMailboxModule.java  | 1 +
 .../jmap/src/main/java/org/apache/james/jmap/draft/JMAPModule.java     | 2 ++
 .../src/test/resources/listeners.xml                                   | 3 ---
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java
index 86ac2e8..722a653 100644
--- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java
+++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java
@@ -184,6 +184,7 @@ public class CassandraMailboxModule extends AbstractModule {
         bind(MailboxManager.class).to(CassandraMailboxManager.class);
         bind(StoreMailboxManager.class).to(CassandraMailboxManager.class);
         bind(MailboxChangeRepository.class).to(CassandraMailboxChangeRepository.class);
+        bind(State.Factory.class).to(CassandraStateFactory.class);
         bind(MailboxId.Factory.class).to(CassandraId.Factory.class);
         bind(State.Factory.class).to(CassandraStateFactory.class);
         bind(MessageId.Factory.class).to(CassandraMessageId.Factory.class);
diff --git a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/draft/JMAPModule.java b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/draft/JMAPModule.java
index 49613ca..95f6f5b 100644
--- a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/draft/JMAPModule.java
+++ b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/draft/JMAPModule.java
@@ -34,6 +34,7 @@ import org.apache.james.filesystem.api.FileSystem;
 import org.apache.james.jmap.JMAPConfiguration;
 import org.apache.james.jmap.JMAPServer;
 import org.apache.james.jmap.Version;
+import org.apache.james.jmap.change.MailboxChangeListener;
 import org.apache.james.jmap.draft.methods.RequestHandler;
 import org.apache.james.jmap.draft.send.PostDequeueDecoratorFactory;
 import org.apache.james.jmap.draft.utils.JsoupHtmlTextExtractor;
@@ -125,6 +126,7 @@ public class JMAPModule extends AbstractModule {
         bind(MailQueueItemDecoratorFactory.class).to(PostDequeueDecoratorFactory.class).in(Scopes.SINGLETON);
 
         Multibinder.newSetBinder(binder(), MailboxListener.GroupMailboxListener.class).addBinding().to(PropagateLookupRightListener.class);
+        Multibinder.newSetBinder(binder(), MailboxListener.GroupMailboxListener.class).addBinding().to(MailboxChangeListener.class);
 
         Multibinder<Version> supportedVersions = Multibinder.newSetBinder(binder(), Version.class);
         supportedVersions.addBinding().toInstance(Version.DRAFT);
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/resources/listeners.xml b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/resources/listeners.xml
index af44f35..a1a139d 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/resources/listeners.xml
+++ b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/resources/listeners.xml
@@ -47,7 +47,4 @@
     <class>org.apache.james.jmap.event.PopulateEmailQueryViewListener</class>
     <async>true</async>
   </listener>
-  <listener>
-    <class>org.apache.james.jmap.change.MailboxChangeListener</class>
-  </listener>
 </listeners>
\ No newline at end of file


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


[james-project] 12/24: JAMES-3485 Avoid reading messageV3 table upon attachment right setting

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 1ef4ffbf7f9ca5dc7fed1580cbb8059a415b0c0a
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Dec 25 19:21:46 2020 +0700

    JAMES-3485 Avoid reading messageV3 table upon attachment right setting
    
    Relying on message metadata reads allow to bypass this step...
---
 .../james/mailbox/store/StoreMessageIdManager.java | 24 ++++++++++++++--------
 1 file changed, 15 insertions(+), 9 deletions(-)

diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageIdManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageIdManager.java
index 43578c4..eecc808 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageIdManager.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageIdManager.java
@@ -29,6 +29,7 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.function.Function;
 import java.util.function.Predicate;
+import java.util.stream.Stream;
 
 import javax.inject.Inject;
 import javax.mail.Flags;
@@ -141,13 +142,17 @@ public class StoreMessageIdManager implements MessageIdManager {
     @Override
     public Set<MessageId> accessibleMessages(Collection<MessageId> messageIds, MailboxSession mailboxSession) throws MailboxException {
         MessageIdMapper messageIdMapper = mailboxSessionMapperFactory.getMessageIdMapper(mailboxSession);
-        List<MailboxMessage> messageList = messageIdMapper.find(messageIds, MessageMapper.FetchType.Metadata);
+        ImmutableList<ComposedMessageIdWithMetaData> idList = Flux.fromIterable(messageIds)
+            .flatMap(messageIdMapper::findMetadata, DEFAULT_CONCURRENCY)
+            .collect(Guavate.toImmutableList())
+            .block();
 
-        ImmutableSet<MailboxId> allowedMailboxIds = getAllowedMailboxIds(mailboxSession, messageList, Right.Read);
+        ImmutableSet<MailboxId> allowedMailboxIds = getAllowedMailboxIds(mailboxSession, idList.stream()
+            .map(id -> id.getComposedMessageId().getMailboxId()), Right.Read);
 
-        return messageList.stream()
-            .filter(message -> allowedMailboxIds.contains(message.getMailboxId()))
-            .map(MailboxMessage::getMessageId)
+        return idList.stream()
+            .filter(id -> allowedMailboxIds.contains(id.getComposedMessageId().getMailboxId()))
+            .map(id -> id.getComposedMessageId().getMessageId())
             .collect(Guavate.toImmutableSet());
     }
 
@@ -181,9 +186,8 @@ public class StoreMessageIdManager implements MessageIdManager {
             .flatMap(Function.identity(), DEFAULT_CONCURRENCY);
     }
 
-    private ImmutableSet<MailboxId> getAllowedMailboxIds(MailboxSession mailboxSession, List<MailboxMessage> messageList, Right... rights) throws MailboxException {
-        return MailboxReactorUtils.block(Flux.fromIterable(messageList)
-            .map(MailboxMessage::getMailboxId)
+    private ImmutableSet<MailboxId> getAllowedMailboxIds(MailboxSession mailboxSession, Stream<MailboxId> idList, Right... rights) throws MailboxException {
+        return MailboxReactorUtils.block(Flux.fromStream(idList)
             .distinct()
             .filterWhen(hasRightsOnMailboxReactive(mailboxSession, rights), DEFAULT_CONCURRENCY)
             .collect(Guavate.toImmutableSet()));
@@ -213,7 +217,9 @@ public class StoreMessageIdManager implements MessageIdManager {
         MessageIdMapper messageIdMapper = mailboxSessionMapperFactory.getMessageIdMapper(mailboxSession);
 
         List<MailboxMessage> messageList = messageIdMapper.find(messageIds, MessageMapper.FetchType.Metadata);
-        ImmutableSet<MailboxId> allowedMailboxIds = getAllowedMailboxIds(mailboxSession, messageList, Right.DeleteMessages);
+        ImmutableSet<MailboxId> allowedMailboxIds = getAllowedMailboxIds(mailboxSession, messageList
+            .stream()
+            .map(MailboxMessage::getMailboxId), Right.DeleteMessages);
 
         ImmutableSet<MessageId> accessibleMessages = messageList.stream()
             .filter(message -> allowedMailboxIds.contains(message.getMailboxId()))


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


[james-project] 22/24: JAMES-3471 Adapt the different implementations of MessageMapper and MessageIdMapper to add the message id

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 3ba4665a13ebbd2218555c7bc8e7969f68c08b8c
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Thu Dec 31 17:50:07 2020 +0700

    JAMES-3471 Adapt the different implementations of MessageMapper and MessageIdMapper to add the message id
---
 .../james/mailbox/cassandra/mail/CassandraMessageIdMapper.java     | 1 +
 .../james/mailbox/cassandra/mail/CassandraMessageMapper.java       | 2 ++
 .../main/java/org/apache/james/mailbox/jpa/mail/MessageUtils.java  | 1 +
 .../apache/james/mailbox/maildir/mail/MaildirMessageMapper.java    | 1 +
 .../james/mailbox/inmemory/mail/InMemoryMessageIdMapper.java       | 1 +
 .../org/apache/james/mailbox/store/mail/AbstractMessageMapper.java | 1 +
 .../mailbox/store/AbstractMessageIdManagerSideEffectTest.java      | 1 +
 .../apache/james/mailbox/store/mail/model/MessageIdMapperTest.java | 7 +++++++
 .../apache/james/mailbox/store/mail/model/MessageMapperTest.java   | 5 +++++
 9 files changed, 20 insertions(+)

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 19fe49b..3fae704 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
@@ -268,6 +268,7 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
         return Pair.of(composedMessageIdWithMetaData.getComposedMessageId().getMailboxId(),
                 UpdatedFlags.builder()
                     .uid(composedMessageIdWithMetaData.getComposedMessageId().getUid())
+                    .messageId(composedMessageIdWithMetaData.getComposedMessageId().getMessageId())
                     .modSeq(composedMessageIdWithMetaData.getModSeq())
                     .oldFlags(oldFlags)
                     .newFlags(composedMessageIdWithMetaData.getFlags())
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 88c23e6..419b8f8 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
@@ -541,6 +541,7 @@ public class CassandraMessageMapper implements MessageMapper {
         if (identicalFlags(oldFlags, newFlags)) {
             return Mono.just(FlagsUpdateStageResult.success(UpdatedFlags.builder()
                 .uid(oldMetaData.getComposedMessageId().getUid())
+                .messageId(oldMetaData.getComposedMessageId().getMessageId())
                 .modSeq(oldMetaData.getModSeq())
                 .oldFlags(oldFlags)
                 .newFlags(newFlags)
@@ -552,6 +553,7 @@ public class CassandraMessageMapper implements MessageMapper {
                 if (success) {
                     return FlagsUpdateStageResult.success(UpdatedFlags.builder()
                         .uid(oldMetaData.getComposedMessageId().getUid())
+                        .messageId(oldMetaData.getComposedMessageId().getMessageId())
                         .modSeq(newModSeq)
                         .oldFlags(oldFlags)
                         .newFlags(newFlags)
diff --git a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/MessageUtils.java b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/MessageUtils.java
index bd5d513..1c097e7 100644
--- a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/MessageUtils.java
+++ b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/MessageUtils.java
@@ -74,6 +74,7 @@ class MessageUtils {
 
             updatedFlags.add(UpdatedFlags.builder()
                 .uid(member.getUid())
+                .messageId(member.getMessageId())
                 .modSeq(member.getModSeq())
                 .newFlags(newFlags)
                 .oldFlags(originalFlags)
diff --git a/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/MaildirMessageMapper.java b/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/MaildirMessageMapper.java
index e670055..f8f7894 100644
--- a/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/MaildirMessageMapper.java
+++ b/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/MaildirMessageMapper.java
@@ -194,6 +194,7 @@ public class MaildirMessageMapper extends AbstractMessageMapper {
 
                     updatedFlags.add(UpdatedFlags.builder()
                         .uid(member.getUid())
+                        .messageId(member.getMessageId())
                         .modSeq(member.getModSeq())
                         .newFlags(newFlags)
                         .oldFlags(originalFlags)
diff --git a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageIdMapper.java b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageIdMapper.java
index 521cb96..626640a 100644
--- a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageIdMapper.java
+++ b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageIdMapper.java
@@ -151,6 +151,7 @@ public class InMemoryMessageIdMapper implements MessageIdMapper {
                 UpdatedFlags updatedFlags = UpdatedFlags.builder()
                     .modSeq(message.getModSeq())
                     .uid(message.getUid())
+                    .messageId(message.getMessageId())
                     .oldFlags(message.createFlags())
                     .newFlags(newState)
                     .build();
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractMessageMapper.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractMessageMapper.java
index 9fd5cf3..a80eead 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractMessageMapper.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractMessageMapper.java
@@ -102,6 +102,7 @@ public abstract class AbstractMessageMapper extends TransactionalMapper implemen
             
             updatedFlags.add(UpdatedFlags.builder()
                 .uid(member.getUid())
+                .messageId(member.getMessageId())
                 .modSeq(member.getModSeq())
                 .newFlags(newFlags)
                 .oldFlags(originalFlags)
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/AbstractMessageIdManagerSideEffectTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/AbstractMessageIdManagerSideEffectTest.java
index 751704b..63e87f1 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/AbstractMessageIdManagerSideEffectTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/AbstractMessageIdManagerSideEffectTest.java
@@ -467,6 +467,7 @@ public abstract class AbstractMessageIdManagerSideEffectTest {
         ModSeq modSeq = messageResult.getModSeq();
         UpdatedFlags updatedFlags = UpdatedFlags.builder()
             .uid(messageUid)
+            .messageId(messageId)
             .modSeq(modSeq)
             .oldFlags(FLAGS)
             .newFlags(newFlags)
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageIdMapperTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageIdMapperTest.java
index 6f97388..5093ea9 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageIdMapperTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageIdMapperTest.java
@@ -355,6 +355,7 @@ public abstract class MessageIdMapperTest {
         ModSeq modSeq = mapperProvider.highestModSeq(benwaInboxMailbox);
         UpdatedFlags expectedUpdatedFlags = UpdatedFlags.builder()
             .uid(message1.getUid())
+            .messageId(messageId)
             .modSeq(modSeq)
             .oldFlags(new Flags())
             .newFlags(newFlags)
@@ -382,6 +383,7 @@ public abstract class MessageIdMapperTest {
         ModSeq modSeq = mapperProvider.highestModSeq(benwaInboxMailbox);
         UpdatedFlags expectedUpdatedFlags = UpdatedFlags.builder()
             .uid(message1.getUid())
+            .messageId(messageId)
             .modSeq(modSeq)
             .oldFlags(messageFlags)
             .newFlags(newFlags)
@@ -411,6 +413,7 @@ public abstract class MessageIdMapperTest {
         ModSeq modSeq = mapperProvider.highestModSeq(benwaInboxMailbox);
         UpdatedFlags expectedUpdatedFlags = UpdatedFlags.builder()
             .uid(message1.getUid())
+            .messageId(messageId)
             .modSeq(modSeq)
             .oldFlags(messageFlags)
             .newFlags(new Flags(Flags.Flag.RECENT))
@@ -483,6 +486,7 @@ public abstract class MessageIdMapperTest {
         ModSeq modSeq = mapperProvider.highestModSeq(benwaInboxMailbox);
         UpdatedFlags expectedUpdatedFlags = UpdatedFlags.builder()
             .uid(message1.getUid())
+            .messageId(messageId)
             .modSeq(modSeq)
             .oldFlags(initialFlags)
             .newFlags(newFlags)
@@ -510,12 +514,14 @@ public abstract class MessageIdMapperTest {
         ModSeq modSeqBenwaWorkMailbox = mapperProvider.highestModSeq(benwaWorkMailbox);
         UpdatedFlags expectedUpdatedFlags = UpdatedFlags.builder()
             .uid(message1.getUid())
+            .messageId(messageId)
             .modSeq(modSeqBenwaInboxMailbox)
             .oldFlags(new Flags())
             .newFlags(newFlags)
             .build();
         UpdatedFlags expectedUpdatedFlags2 = UpdatedFlags.builder()
             .uid(message1InOtherMailbox.getUid())
+            .messageId(messageId)
             .modSeq(modSeqBenwaWorkMailbox)
             .oldFlags(new Flags())
             .newFlags(newFlags)
@@ -854,6 +860,7 @@ public abstract class MessageIdMapperTest {
                 ImmutableList.of(UpdatedFlags.builder()
                     .modSeq(modSeq)
                     .uid(message1.getUid())
+                    .messageId(message1.getMessageId())
                     .newFlags(flags)
                     .oldFlags(flags)
                     .build())));
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageMapperTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageMapperTest.java
index 0a59a33..cc949d5 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageMapperTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageMapperTest.java
@@ -709,6 +709,7 @@ public abstract class MessageMapperTest {
         assertThat(updatedFlags)
             .contains(UpdatedFlags.builder()
                 .uid(message1.getUid())
+                .messageId(message1.getMessageId())
                 .modSeq(modSeq.next())
                 .oldFlags(new Flags())
                 .newFlags(new Flags(Flags.Flag.FLAGGED))
@@ -723,6 +724,7 @@ public abstract class MessageMapperTest {
         assertThat(messageMapper.updateFlags(benwaInboxMailbox, message1.getUid(), new FlagsUpdateCalculator(new Flags(Flags.Flag.SEEN), FlagsUpdateMode.ADD)))
             .contains(UpdatedFlags.builder()
                     .uid(message1.getUid())
+                    .messageId(message1.getMessageId())
                     .modSeq(modSeq.next())
                     .oldFlags(new Flags(Flags.Flag.FLAGGED))
                     .newFlags(new FlagsBuilder().add(Flags.Flag.SEEN, Flags.Flag.FLAGGED).build())
@@ -756,6 +758,7 @@ public abstract class MessageMapperTest {
             .contains(
                 UpdatedFlags.builder()
                     .uid(message1.getUid())
+                    .messageId(message1.getMessageId())
                     .modSeq(modSeq.next())
                     .oldFlags(new FlagsBuilder().add(Flags.Flag.SEEN, Flags.Flag.FLAGGED).build())
                     .newFlags(new Flags(Flags.Flag.FLAGGED))
@@ -881,6 +884,7 @@ public abstract class MessageMapperTest {
             .contains(
                 UpdatedFlags.builder()
                     .uid(message1.getUid())
+                    .messageId(message1.getMessageId())
                     .modSeq(modSeq.next())
                     .oldFlags(new Flags())
                     .newFlags(new Flags(USER_FLAG))
@@ -897,6 +901,7 @@ public abstract class MessageMapperTest {
             .contains(
                 UpdatedFlags.builder()
                     .uid(message1.getUid())
+                    .messageId(message1.getMessageId())
                     .modSeq(message1.getModSeq())
                     .oldFlags(new Flags())
                     .newFlags(new Flags())


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


[james-project] 05/24: JAMES-3486 MemoryMailboxChangeRepository getSinceState should not include delegated changes

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 47ee6467ebb525d9b80d5bb3dea83ac642e7e80a
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Mon Dec 28 11:00:11 2020 +0700

    JAMES-3486 MemoryMailboxChangeRepository getSinceState should not include delegated changes
---
 .../change/MemoryMailboxChangeRepository.java      |  1 +
 .../change/MailboxChangeRepositoryContract.java    | 48 ++++++++++++++++++++++
 .../memory/MemoryMailboxChangesMethodTest.java     |  7 ----
 3 files changed, 49 insertions(+), 7 deletions(-)

diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/change/MemoryMailboxChangeRepository.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/change/MemoryMailboxChangeRepository.java
index 6dc9105..efcc22a 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/change/MemoryMailboxChangeRepository.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/change/MemoryMailboxChangeRepository.java
@@ -61,6 +61,7 @@ public class MemoryMailboxChangeRepository implements MailboxChangeRepository {
 
         if (state.equals(State.INITIAL)) {
             return Flux.fromIterable(mailboxChangeMap.get(accountId))
+                .filter(Predicate.not(MailboxChange::isDelegated))
                 .sort(Comparator.comparing(MailboxChange::getDate))
                 .collect(new MailboxChangeCollector(state, maxChanges.orElse(DEFAULT_NUMBER_OF_CHANGES)));
         }
diff --git a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/change/MailboxChangeRepositoryContract.java b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/change/MailboxChangeRepositoryContract.java
index 4034849..fc895a8 100644
--- a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/change/MailboxChangeRepositoryContract.java
+++ b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/change/MailboxChangeRepositoryContract.java
@@ -281,6 +281,54 @@ public interface MailboxChangeRepositoryContract {
     }
 
     @Test
+    default void getSinceStateFromInitialShouldNotIncludeDeletegatedChanges() {
+        MailboxChangeRepository repository = mailboxChangeRepository();
+        State.Factory stateFactory = stateFactory();
+        State referenceState = stateFactory.generate();
+
+        MailboxId id1 = generateNewMailboxId();
+        MailboxId id2 = generateNewMailboxId();
+        MailboxId id3 = generateNewMailboxId();
+        MailboxId id4 = generateNewMailboxId();
+
+        MailboxChange change1 = MailboxChange.builder().accountId(ACCOUNT_ID).state(referenceState).date(DATE.minusHours(3)).isCountChange(false).created(ImmutableList.of(id1)).build();
+        MailboxChange change2 = MailboxChange.builder().accountId(ACCOUNT_ID).state(stateFactory.generate()).date(DATE.minusHours(2)).isCountChange(false).created(ImmutableList.of(id2)).build();
+        MailboxChange change3 = MailboxChange.builder().accountId(ACCOUNT_ID).state(stateFactory.generate()).date(DATE.minusHours(1)).isCountChange(false).delegated(true).created(ImmutableList.of(id3)).build();
+        MailboxChange change4 = MailboxChange.builder().accountId(ACCOUNT_ID).state(stateFactory.generate()).date(DATE).isCountChange(false).delegated(true).created(ImmutableList.of(id4)).build();
+        repository.save(change1).block();
+        repository.save(change2).block();
+        repository.save(change3).block();
+        repository.save(change4).block();
+
+        assertThat(repository.getSinceState(ACCOUNT_ID, State.INITIAL, Optional.empty()).block().getCreated())
+            .containsExactlyInAnyOrder(id1, id2);
+    }
+
+    @Test
+    default void getSinceStateWithDelegationFromInitialShouldIncludeDeletegatedChanges() {
+        MailboxChangeRepository repository = mailboxChangeRepository();
+        State.Factory stateFactory = stateFactory();
+        State referenceState = stateFactory.generate();
+
+        MailboxId id1 = generateNewMailboxId();
+        MailboxId id2 = generateNewMailboxId();
+        MailboxId id3 = generateNewMailboxId();
+        MailboxId id4 = generateNewMailboxId();
+
+        MailboxChange change1 = MailboxChange.builder().accountId(ACCOUNT_ID).state(referenceState).date(DATE.minusHours(3)).isCountChange(false).created(ImmutableList.of(id1)).build();
+        MailboxChange change2 = MailboxChange.builder().accountId(ACCOUNT_ID).state(stateFactory.generate()).date(DATE.minusHours(2)).isCountChange(false).created(ImmutableList.of(id2)).build();
+        MailboxChange change3 = MailboxChange.builder().accountId(ACCOUNT_ID).state(stateFactory.generate()).date(DATE.minusHours(1)).isCountChange(false).delegated(true).created(ImmutableList.of(id3)).build();
+        MailboxChange change4 = MailboxChange.builder().accountId(ACCOUNT_ID).state(stateFactory.generate()).date(DATE).isCountChange(false).delegated(true).created(ImmutableList.of(id4)).build();
+        repository.save(change1).block();
+        repository.save(change2).block();
+        repository.save(change3).block();
+        repository.save(change4).block();
+
+        assertThat(repository.getSinceStateWithDelegation(ACCOUNT_ID, State.INITIAL, Optional.empty()).block().getCreated())
+            .containsExactlyInAnyOrder(id1, id2, id3, id4);
+    }
+
+    @Test
     default void getChangesShouldLimitChangesWhenMaxChangesOmitted() {
         MailboxChangeRepository repository = mailboxChangeRepository();
         State.Factory stateFactory = stateFactory();
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryMailboxChangesMethodTest.java b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryMailboxChangesMethodTest.java
index df68a52..b01641a 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryMailboxChangesMethodTest.java
+++ b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryMailboxChangesMethodTest.java
@@ -26,8 +26,6 @@ import org.apache.james.JamesServerBuilder;
 import org.apache.james.JamesServerExtension;
 import org.apache.james.jmap.api.change.State;
 import org.apache.james.jmap.rfc8621.contract.MailboxChangesMethodContract;
-import org.apache.james.mailbox.inmemory.InMemoryId;
-import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.modules.TestJMAPServerModule;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
@@ -44,9 +42,4 @@ public class MemoryMailboxChangesMethodTest implements MailboxChangesMethodContr
     public State.Factory stateFactory() {
         return new State.DefaultFactory();
     }
-
-    @Override
-    public MailboxId generateMailboxId() {
-        return InMemoryId.of(0);
-    }
 }
\ No newline at end of file


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


[james-project] 19/24: [REFACTORING] Steps of the staged builder should be inside the Builder of MailboxChange

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 4b3785dea7283c988206de6303d19bb0c6da7dbc
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Thu Dec 31 10:54:26 2020 +0700

    [REFACTORING] Steps of the staged builder should be inside the Builder of MailboxChange
    
    That's the pattern we use generally in the code... And my IDE was screaming at it for some reasons
---
 .../james/jmap/api/change/MailboxChange.java       | 36 +++++++++++-----------
 1 file changed, 18 insertions(+), 18 deletions(-)

diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChange.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChange.java
index d4ea806..3d8533d 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChange.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChange.java
@@ -51,27 +51,27 @@ import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 
 public class MailboxChange {
-    @FunctionalInterface
-    public interface RequiredAccountId {
-        RequiredState accountId(AccountId accountId);
-    }
+    public static class Builder {
+        @FunctionalInterface
+        public interface RequiredAccountId {
+            RequiredState accountId(AccountId accountId);
+        }
 
-    @FunctionalInterface
-    public interface RequiredState {
-        RequiredDate state(State state);
-    }
+        @FunctionalInterface
+        public interface RequiredState {
+            RequiredDate state(State state);
+        }
 
-    @FunctionalInterface
-    public interface RequiredDate {
-        RequiredIsCountChange date(ZonedDateTime date);
-    }
+        @FunctionalInterface
+        public interface RequiredDate {
+            RequiredIsCountChange date(ZonedDateTime date);
+        }
 
-    @FunctionalInterface
-    public interface RequiredIsCountChange {
-        Builder isCountChange(boolean isCountChange);
-    }
+        @FunctionalInterface
+        public interface RequiredIsCountChange {
+            Builder isCountChange(boolean isCountChange);
+        }
 
-    public static class Builder {
         private final AccountId accountId;
         private final State state;
         private final ZonedDateTime date;
@@ -124,7 +124,7 @@ public class MailboxChange {
         }
     }
 
-    public static RequiredAccountId builder() {
+    public static Builder.RequiredAccountId builder() {
         return accountId -> state -> date -> isCountChange -> new Builder(accountId, state, date, isCountChange);
     }
 


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


[james-project] 02/24: JAMES-3486 Fix indent

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 4ac8c99d000d6ab8a60f3ba32767480b474f6214
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Mon Dec 28 10:42:43 2020 +0700

    JAMES-3486 Fix indent
---
 .../jmap/rfc8621/distributed/DistributedMailboxGetMethodTest.java   | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedMailboxGetMethodTest.java b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedMailboxGetMethodTest.java
index 5de500d..0235d09 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedMailboxGetMethodTest.java
+++ b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedMailboxGetMethodTest.java
@@ -43,9 +43,9 @@ public class DistributedMailboxGetMethodTest implements MailboxGetMethodContract
             .workingDirectory(tmpDir)
             .configurationFromClasspath()
             .blobStore(BlobStoreConfiguration.builder()
-                    .s3()
-                    .disableCache()
-                    .deduplication())
+                .s3()
+                .disableCache()
+                .deduplication())
             .build())
         .extension(new DockerElasticSearchExtension())
         .extension(new CassandraExtension())


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


[james-project] 09/24: JAMES-3431 RecipientRewriteTableProcessor needs to modify DSN parameters

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 91fd382e1949db9d888262a1ceeb53093be76177
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Dec 30 10:47:54 2020 +0700

    JAMES-3431 RecipientRewriteTableProcessor needs to modify DSN parameters
    
    ORCPT is preserved (should not be rewritten):
    
    ```
    The purpose of ORCPT is to preserve the original recipient of an email message, for example, if it is forwarded to another address.
    ```
    
    (source: https://www.lifewire.com/what-is-dsn-delivery-status-notification-for-smtp-email-3860942, for what it is worth)
---
 .../main/java/org/apache/mailet/DsnParameters.java |  4 ++
 .../java/org/apache/james/smtp/DSNRelayTest.java   | 47 ++++++++++++-
 .../mailets/RecipientRewriteTableProcessor.java    | 76 +++++++++++++++++++---
 3 files changed, 118 insertions(+), 9 deletions(-)

diff --git a/mailet/api/src/main/java/org/apache/mailet/DsnParameters.java b/mailet/api/src/main/java/org/apache/mailet/DsnParameters.java
index 612fbc0..6dc46ee 100644
--- a/mailet/api/src/main/java/org/apache/mailet/DsnParameters.java
+++ b/mailet/api/src/main/java/org/apache/mailet/DsnParameters.java
@@ -424,6 +424,10 @@ public class DsnParameters {
         this.rcptParameters = rcptParameters;
     }
 
+    public DsnParameters withRcptParameters(Map<MailAddress, RecipientDsnParameters> rcptParameters) {
+        return new DsnParameters(envIdParameter, retParameter, ImmutableMap.copyOf(rcptParameters));
+    }
+
     public Optional<EnvId> getEnvIdParameter() {
         return envIdParameter;
     }
diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/smtp/DSNRelayTest.java b/server/mailet/integration-testing/src/test/java/org/apache/james/smtp/DSNRelayTest.java
index 4f79750..3a1b00d 100644
--- a/server/mailet/integration-testing/src/test/java/org/apache/james/smtp/DSNRelayTest.java
+++ b/server/mailet/integration-testing/src/test/java/org/apache/james/smtp/DSNRelayTest.java
@@ -29,6 +29,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.awaitility.Duration.TEN_SECONDS;
 
 import org.apache.commons.net.smtp.AuthenticatingSMTPClient;
+import org.apache.james.core.Domain;
 import org.apache.james.core.MailAddress;
 import org.apache.james.dnsservice.api.DNSService;
 import org.apache.james.dnsservice.api.InMemoryDNSService;
@@ -47,6 +48,7 @@ import org.apache.james.smtpserver.dsn.DSNEhloHook;
 import org.apache.james.smtpserver.dsn.DSNMailParameterHook;
 import org.apache.james.smtpserver.dsn.DSNMessageHook;
 import org.apache.james.smtpserver.dsn.DSNRcptParameterHook;
+import org.apache.james.transport.mailets.RecipientRewriteTable;
 import org.apache.james.transport.mailets.RemoteDelivery;
 import org.apache.james.transport.matchers.All;
 import org.apache.james.util.Host;
@@ -327,6 +329,47 @@ public class DSNRelayTest {
     }
 
     @Test
+    public void dsnShouldBeCarriedAfterRRT() throws Exception {
+        DataProbeImpl dataProbe = jamesServer.getProbe(DataProbeImpl.class);
+        dataProbe.addDomain(ANOTHER_DOMAIN);
+        dataProbe.addAddressMapping("touser", ANOTHER_DOMAIN, "touser-alias@other.com");
+
+        AuthenticatingSMTPClient smtpClient = new AuthenticatingSMTPClient("TLS", "UTF-8");
+
+        try {
+            smtpClient.connect("localhost", jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort().getValue());
+            smtpClient.ehlo(DEFAULT_DOMAIN);
+            smtpClient.mail("<" + FROM + "> RET=HDRS ENVID=gabouzomeuh");
+            smtpClient.rcpt("<" + RECIPIENT + "> NOTIFY=FAILURE,DELAY");
+            smtpClient.sendShortMessageData("A short message...");
+        } finally {
+            smtpClient.disconnect();
+        }
+
+        calmlyAwait.atMost(TEN_SECONDS).untilAsserted(() -> assertThat(mockSMTPConfiguration.listMails())
+            .hasSize(1)
+            .extracting(Mail::getEnvelope)
+            .containsExactly(Mail.Envelope.builder()
+                .from(new MailAddress(FROM))
+                .addMailParameter(Mail.Parameter.builder()
+                    .name("RET")
+                    .value("HDRS")
+                    .build())
+                .addMailParameter(Mail.Parameter.builder()
+                    .name("ENVID")
+                    .value("gabouzomeuh")
+                    .build())
+                .addRecipient(Mail.Recipient.builder()
+                    .address(new MailAddress("touser-alias@other.com"))
+                    .addParameter(Mail.Parameter.builder()
+                        .name("NOTIFY")
+                        .value("FAILURE,DELAY")
+                        .build())
+                    .build())
+                .build()));
+    }
+
+    @Test
     public void remoteDeliveryShouldCarryOverDSNMailParameters() throws Exception {
         AuthenticatingSMTPClient smtpClient = new AuthenticatingSMTPClient("TLS", "UTF-8");
 
@@ -363,7 +406,9 @@ public class DSNRelayTest {
     private ProcessorConfiguration.Builder directResolutionTransport() {
         return ProcessorConfiguration.transport()
             .addMailet(MailetConfiguration.BCC_STRIPPER)
-            .addMailet(MailetConfiguration.LOCAL_DELIVERY)
+            .addMailet(MailetConfiguration.builder()
+                .matcher(All.class)
+                .mailet(RecipientRewriteTable.class))
             .addMailet(MailetConfiguration.builder()
                 .mailet(RemoteDelivery.class)
                 .matcher(All.class)
diff --git a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/RecipientRewriteTableProcessor.java b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/RecipientRewriteTableProcessor.java
index 8eef3e9..77660c2 100644
--- a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/RecipientRewriteTableProcessor.java
+++ b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/RecipientRewriteTableProcessor.java
@@ -31,6 +31,8 @@ import java.util.stream.Stream;
 
 import javax.mail.MessagingException;
 
+import org.apache.commons.lang3.NotImplementedException;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.james.core.Domain;
 import org.apache.james.core.MailAddress;
 import org.apache.james.domainlist.api.DomainList;
@@ -42,6 +44,8 @@ import org.apache.james.rrt.lib.Mapping;
 import org.apache.james.rrt.lib.Mappings;
 import org.apache.james.server.core.MailImpl;
 import org.apache.james.util.MemoizedSupplier;
+import org.apache.mailet.DsnParameters;
+import org.apache.mailet.DsnParameters.RecipientDsnParameters;
 import org.apache.mailet.Mail;
 import org.apache.mailet.MailetContext;
 import org.slf4j.Logger;
@@ -58,6 +62,44 @@ import com.google.common.collect.ImmutableSet;
 public class RecipientRewriteTableProcessor {
     private static final Logger LOGGER = LoggerFactory.getLogger(RecipientRewriteTableProcessor.class);
 
+    private static class Decision {
+        private final MailAddress originalAddress;
+        private final RrtExecutionResult executionResult;
+
+        private Decision(MailAddress originalAddress, RrtExecutionResult executionResult) {
+            this.originalAddress = originalAddress;
+            this.executionResult = executionResult;
+        }
+
+        MailAddress originalAddress() {
+            return originalAddress;
+        }
+
+        RrtExecutionResult executionResult() {
+            return executionResult;
+        }
+
+        DsnParameters applyOnDsnParameters(DsnParameters dsnParameters) {
+            ImmutableMap<MailAddress, RecipientDsnParameters> rcptParameters = dsnParameters.getRcptParameters();
+
+            Optional<RecipientDsnParameters> originalRcptParameter = Optional.ofNullable(rcptParameters.get(originalAddress));
+
+            return originalRcptParameter.map(parameters -> {
+                Map<MailAddress, RecipientDsnParameters> newRcptParameters = executionResult.getNewRecipients().stream()
+                    .map(newRcpt -> Pair.of(newRcpt, parameters))
+                    .collect(Guavate.toImmutableMap(Pair::getKey, Pair::getValue));
+                Map<MailAddress, RecipientDsnParameters> rcptParametersWithoutOriginal = rcptParameters.entrySet().stream()
+                    .filter(rcpt -> !rcpt.getKey().equals(originalAddress))
+                    .collect(Guavate.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue));
+
+                return dsnParameters.withRcptParameters(ImmutableMap.<MailAddress, RecipientDsnParameters>builder()
+                    .putAll(rcptParametersWithoutOriginal)
+                    .putAll(newRcptParameters)
+                    .build());
+            }).orElse(dsnParameters);
+        }
+    }
+
     private static class RrtExecutionResult {
         private static RrtExecutionResult empty() {
             return new RrtExecutionResult(ImmutableSet.of(), ImmutableSet.of());
@@ -131,7 +173,25 @@ public class RecipientRewriteTableProcessor {
     }
 
     public void processMail(Mail mail) throws MessagingException {
-        RrtExecutionResult executionResults = executeRrtFor(mail);
+        List<Decision> decisions = executeRrtFor(mail);
+
+        applyDecisionsOnMailRecipients(mail, decisions);
+        applyDecisionOnDSNParameters(mail, decisions);
+    }
+
+    private void applyDecisionOnDSNParameters(Mail mail, List<Decision> decisions) {
+        mail.dsnParameters()
+            .map(dsnParameters -> decisions.stream()
+                .reduce(dsnParameters, (parameters, decision) -> decision.applyOnDsnParameters(parameters), (a, b) -> {
+                    throw new NotImplementedException("No combiner needed as we are not in a multi-threaded environment");
+                }))
+            .ifPresent(mail::setDsnParameters);
+    }
+
+    private void applyDecisionsOnMailRecipients(Mail mail, List<Decision> decisions) throws MessagingException {
+        RrtExecutionResult executionResults = decisions.stream()
+            .map(Decision::executionResult)
+            .reduce(RrtExecutionResult.empty(), RrtExecutionResult::merge);
 
         if (!executionResults.recipientWithError.isEmpty()) {
             MailImpl newMail = MailImpl.builder()
@@ -151,8 +211,8 @@ public class RecipientRewriteTableProcessor {
         mail.setRecipients(executionResults.newRecipients);
     }
 
-    private RrtExecutionResult executeRrtFor(Mail mail) {
-        Function<MailAddress, RrtExecutionResult> convertToMappingData = recipient -> {
+    private List<Decision> executeRrtFor(Mail mail) {
+        Function<MailAddress, Decision> convertToMappingData = recipient -> {
             Preconditions.checkNotNull(recipient);
 
             return executeRrtForRecipient(mail, recipient);
@@ -161,21 +221,21 @@ public class RecipientRewriteTableProcessor {
         return mail.getRecipients()
             .stream()
             .map(convertToMappingData)
-            .reduce(RrtExecutionResult.empty(), RrtExecutionResult::merge);
+            .collect(Guavate.toImmutableList());
     }
 
-    private RrtExecutionResult executeRrtForRecipient(Mail mail, MailAddress recipient) {
+    private Decision executeRrtForRecipient(Mail mail, MailAddress recipient) {
         try {
             Mappings mappings = virtualTableStore.getResolvedMappings(recipient.getLocalPart(), recipient.getDomain());
 
             if (mappings != null && !mappings.isEmpty()) {
                 List<MailAddress> newMailAddresses = handleMappings(mappings, mail, recipient);
-                return RrtExecutionResult.success(newMailAddresses);
+                return new Decision(recipient, RrtExecutionResult.success(newMailAddresses));
             }
-            return RrtExecutionResult.success(recipient);
+            return new Decision(recipient, RrtExecutionResult.success(recipient));
         } catch (ErrorMappingException | RecipientRewriteTableException e) {
             LOGGER.warn("Could not rewrite recipient {}", recipient, e);
-            return RrtExecutionResult.error(recipient);
+            return new Decision(recipient, RrtExecutionResult.error(recipient));
         }
     }
 


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


[james-project] 01/24: JAMES-3486 Add missing binding

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 39ee787387031b4ef981e52ac3e198a2793b2cc7
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Fri Dec 25 16:36:00 2020 +0700

    JAMES-3486 Add missing binding
---
 .../org/apache/james/modules/mailbox/CassandraMailboxModule.java     | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java
index 0e393d9..86ac2e8 100644
--- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java
+++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java
@@ -29,7 +29,10 @@ import org.apache.james.eventsourcing.Event;
 import org.apache.james.eventsourcing.eventstore.cassandra.dto.EventDTO;
 import org.apache.james.eventsourcing.eventstore.cassandra.dto.EventDTOModule;
 import org.apache.james.jmap.api.change.MailboxChangeRepository;
+import org.apache.james.jmap.api.change.State;
 import org.apache.james.jmap.cassandra.change.CassandraMailboxChangeRepository;
+import org.apache.james.jmap.cassandra.change.CassandraStateFactory;
+import org.apache.james.jmap.change.MailboxChangeListener;
 import org.apache.james.mailbox.AttachmentContentLoader;
 import org.apache.james.mailbox.AttachmentManager;
 import org.apache.james.mailbox.Authenticator;
@@ -182,6 +185,7 @@ public class CassandraMailboxModule extends AbstractModule {
         bind(StoreMailboxManager.class).to(CassandraMailboxManager.class);
         bind(MailboxChangeRepository.class).to(CassandraMailboxChangeRepository.class);
         bind(MailboxId.Factory.class).to(CassandraId.Factory.class);
+        bind(State.Factory.class).to(CassandraStateFactory.class);
         bind(MessageId.Factory.class).to(CassandraMessageId.Factory.class);
         bind(MessageIdManager.class).to(StoreMessageIdManager.class);
         bind(AttachmentManager.class).to(StoreAttachmentManager.class);
@@ -212,6 +216,7 @@ public class CassandraMailboxModule extends AbstractModule {
         Multibinder<MailboxListener.GroupMailboxListener> mailboxListeners = Multibinder.newSetBinder(binder(), MailboxListener.GroupMailboxListener.class);
         mailboxListeners.addBinding().to(MailboxAnnotationListener.class);
         mailboxListeners.addBinding().to(DeleteMessageListener.class);
+        mailboxListeners.addBinding().to(MailboxChangeListener.class);
 
         bind(MailboxManager.class).annotatedWith(Names.named(MAILBOXMANAGER_NAME)).to(MailboxManager.class);
 


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


[james-project] 21/24: JAMES-3471 Should be able to serialize UpdatedFlags with and without a message id

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit a8e7a33d134e9a32bef64f65304b822a2662d945
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Thu Dec 31 16:54:19 2020 +0700

    JAMES-3471 Should be able to serialize UpdatedFlags with and without a message id
---
 .../james/mailbox/events/MailboxListener.java      | 18 +++++-
 .../apache/james/mailbox/model/UpdatedFlags.java   | 37 ++++++++---
 .../scala/org/apache/james/event/json/DTOs.scala   |  5 +-
 .../event/json/FlagsUpdatedSerializationTest.java  | 72 ++++++++++++++++++++++
 4 files changed, 121 insertions(+), 11 deletions(-)

diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/events/MailboxListener.java b/mailbox/api/src/main/java/org/apache/james/mailbox/events/MailboxListener.java
index d305d82..3517801 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/events/MailboxListener.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/events/MailboxListener.java
@@ -24,6 +24,7 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.SortedMap;
 
 import org.apache.james.core.Username;
@@ -587,15 +588,21 @@ public interface MailboxListener {
      */
     class FlagsUpdated extends MessageEvent {
         private final List<MessageUid> uids;
+        private final List<MessageId> messageIds;
         private final List<UpdatedFlags> updatedFlags;
 
-        public FlagsUpdated(MailboxSession.SessionId sessionId, Username username, MailboxPath path, MailboxId mailboxId, List<UpdatedFlags> updatedFlags,
-                            EventId eventId) {
+        public FlagsUpdated(MailboxSession.SessionId sessionId, Username username, MailboxPath path,
+                            MailboxId mailboxId, List<UpdatedFlags> updatedFlags, EventId eventId) {
             super(sessionId, username, path, mailboxId, eventId);
             this.updatedFlags = ImmutableList.copyOf(updatedFlags);
             this.uids = updatedFlags.stream()
                 .map(UpdatedFlags::getUid)
                 .collect(Guavate.toImmutableList());
+            this.messageIds = updatedFlags.stream()
+                .map(UpdatedFlags::getMessageId)
+                .filter(Optional::isPresent)
+                .map(Optional::get)
+                .collect(Guavate.toImmutableList());
         }
 
         @Override
@@ -603,6 +610,10 @@ public interface MailboxListener {
             return uids;
         }
 
+        public Collection<MessageId> getMessageIds() {
+            return messageIds;
+        }
+
         public List<UpdatedFlags> getUpdatedFlags() {
             return updatedFlags;
         }
@@ -623,6 +634,7 @@ public interface MailboxListener {
                     && Objects.equals(this.path, that.path)
                     && Objects.equals(this.mailboxId, that.mailboxId)
                     && Objects.equals(this.uids, that.uids)
+                    && Objects.equals(this.messageIds, that.messageIds)
                     && Objects.equals(this.updatedFlags, that.updatedFlags);
             }
             return false;
@@ -630,7 +642,7 @@ public interface MailboxListener {
 
         @Override
         public final int hashCode() {
-            return Objects.hash(eventId, sessionId, username, path, mailboxId, uids, updatedFlags);
+            return Objects.hash(eventId, sessionId, username, path, mailboxId, uids, messageIds, updatedFlags);
         }
     }
 
diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/model/UpdatedFlags.java b/mailbox/api/src/main/java/org/apache/james/mailbox/model/UpdatedFlags.java
index 1611a63..0649c58 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/model/UpdatedFlags.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/model/UpdatedFlags.java
@@ -43,6 +43,7 @@ public class UpdatedFlags {
 
     public static class Builder {
         private MessageUid uid;
+        private Optional<MessageId> messageId = Optional.empty();
         private Flags oldFlags;
         private Flags newFlags;
         private Optional<ModSeq> modSeq = Optional.empty();
@@ -55,6 +56,16 @@ public class UpdatedFlags {
             return this;
         }
 
+        public Builder messageId(MessageId messageId) {
+            this.messageId = Optional.of(messageId);
+            return this;
+        }
+
+        public Builder messageId(Optional<MessageId> messageId) {
+            this.messageId = messageId;
+            return this;
+        }
+
         public Builder oldFlags(Flags oldFlags) {
             this.oldFlags = oldFlags;
             return this;
@@ -71,22 +82,27 @@ public class UpdatedFlags {
         }
 
         public UpdatedFlags build() {
-            Preconditions.checkState(uid != null);
-            Preconditions.checkState(newFlags != null);
-            Preconditions.checkState(oldFlags != null);
+            Preconditions.checkNotNull(uid);
+            Preconditions.checkNotNull(newFlags);
+            Preconditions.checkNotNull(oldFlags);
             Preconditions.checkState(modSeq.isPresent());
-            return new UpdatedFlags(uid, modSeq.get(), oldFlags, newFlags);
+            return new UpdatedFlags(uid, messageId, modSeq.get(), oldFlags, newFlags);
         }
     }
 
     private final MessageUid uid;
+    /**
+     * The usage of Optional here is for backward compatibility (to be able to still dequeue older events)
+     */
+    private final Optional<MessageId> messageId;
     private final Flags oldFlags;
     private final Flags newFlags;
     private final Flags modifiedFlags;
     private final ModSeq modSeq;
 
-    private UpdatedFlags(MessageUid uid, ModSeq modSeq, Flags oldFlags, Flags newFlags) {
+    private UpdatedFlags(MessageUid uid, Optional<MessageId> messageId, ModSeq modSeq, Flags oldFlags, Flags newFlags) {
        this.uid = uid;
+       this.messageId = messageId;
        this.modSeq = modSeq;
        this.oldFlags = oldFlags;
        this.newFlags = newFlags;
@@ -177,7 +193,13 @@ public class UpdatedFlags {
     public MessageUid getUid() {
         return uid;
     }
-   
+
+    /**
+     * Return the uid for the message whichs {@link Flags} was updated
+     */
+    public Optional<MessageId> getMessageId() {
+        return messageId;
+    }
 
     /**
      * Gets an iterator for the system flags changed.
@@ -238,6 +260,7 @@ public class UpdatedFlags {
         UpdatedFlags that = (UpdatedFlags) other;
 
         return Objects.equals(uid, that.uid) &&
+                Objects.equals(messageId, that.messageId) &&
                 Objects.equals(oldFlags, that.oldFlags) &&
                 Objects.equals(newFlags, that.newFlags) &&
                 Objects.equals(modSeq, that.modSeq);
@@ -245,7 +268,7 @@ public class UpdatedFlags {
 
     @Override
     public final int hashCode() {
-        return Objects.hash(uid, oldFlags, newFlags, modSeq);
+        return Objects.hash(uid, messageId, oldFlags, newFlags, modSeq);
     }
     
     @Override
diff --git a/mailbox/event/json/src/main/scala/org/apache/james/event/json/DTOs.scala b/mailbox/event/json/src/main/scala/org/apache/james/event/json/DTOs.scala
index 08ab9e3..f51f100 100644
--- a/mailbox/event/json/src/main/scala/org/apache/james/event/json/DTOs.scala
+++ b/mailbox/event/json/src/main/scala/org/apache/james/event/json/DTOs.scala
@@ -32,6 +32,7 @@ import org.apache.james.mailbox.model.{MessageId, MailboxACL => JavaMailboxACL,
 import org.apache.james.mailbox.{FlagsBuilder, MessageUid, ModSeq}
 
 import scala.jdk.CollectionConverters._
+import scala.jdk.OptionConverters._
 
 object DTOs {
 
@@ -142,14 +143,16 @@ object DTOs {
   object UpdatedFlags {
     def toUpdatedFlags(javaUpdatedFlags: JavaUpdatedFlags): UpdatedFlags = UpdatedFlags(
       javaUpdatedFlags.getUid,
+      javaUpdatedFlags.getMessageId.toScala,
       javaUpdatedFlags.getModSeq,
       Flags.fromJavaFlags(javaUpdatedFlags.getOldFlags),
       Flags.fromJavaFlags(javaUpdatedFlags.getNewFlags))
   }
 
-  case class UpdatedFlags(uid: MessageUid, modSeq: ModSeq, oldFlags: Flags, newFlags: Flags) {
+  case class UpdatedFlags(uid: MessageUid, messageId: Option[MessageId], modSeq: ModSeq, oldFlags: Flags, newFlags: Flags) {
     def toJava: JavaUpdatedFlags = JavaUpdatedFlags.builder()
       .uid(uid)
+      .messageId(messageId.toJava)
       .modSeq(modSeq)
       .oldFlags(Flags.toJavaFlags(oldFlags))
       .newFlags(Flags.toJavaFlags(newFlags))
diff --git a/mailbox/event/json/src/test/java/org/apache/james/event/json/FlagsUpdatedSerializationTest.java b/mailbox/event/json/src/test/java/org/apache/james/event/json/FlagsUpdatedSerializationTest.java
index 8a2347e..6cb82f3 100644
--- a/mailbox/event/json/src/test/java/org/apache/james/event/json/FlagsUpdatedSerializationTest.java
+++ b/mailbox/event/json/src/test/java/org/apache/james/event/json/FlagsUpdatedSerializationTest.java
@@ -39,7 +39,9 @@ import org.apache.james.mailbox.events.MailboxListener;
 import org.apache.james.mailbox.model.MailboxConstants;
 import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.model.MessageId;
 import org.apache.james.mailbox.model.TestId;
+import org.apache.james.mailbox.model.TestMessageId;
 import org.apache.james.mailbox.model.UpdatedFlags;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
@@ -173,6 +175,76 @@ class FlagsUpdatedSerializationTest {
     }
 
     @Nested
+    class WithMessageId {
+        private final MessageId messageId1 = TestMessageId.of(23456);
+        private final MessageId messageId2 = TestMessageId.of(78901);
+
+        private final UpdatedFlags updatedFlagsWithMessageId1 = UpdatedFlags.builder()
+            .uid(MESSAGE_UID_1)
+            .messageId(messageId1)
+            .modSeq(MOD_SEQ_1)
+            .oldFlags(OLD_FLAGS_1)
+            .newFlags(NEW_FLAGS_1)
+            .build();
+        private final UpdatedFlags updatedFlagsWithMessageId2 = UpdatedFlags.builder()
+            .uid(MESSAGE_UID_2)
+            .messageId(messageId2)
+            .modSeq(MOD_SEQ_2)
+            .oldFlags(OLD_FLAGS_2)
+            .newFlags(NEW_FLAGS_2)
+            .build();
+
+        private final List<UpdatedFlags> updatedFlagsListWithMessageIds = ImmutableList.of(updatedFlagsWithMessageId1, updatedFlagsWithMessageId2);
+
+        private final MailboxListener.FlagsUpdated eventWithMessageIds = new MailboxListener.FlagsUpdated(SESSION_ID, USERNAME,
+            MAILBOX_PATH, MAILBOX_ID, updatedFlagsListWithMessageIds, EVENT_ID);
+
+        private static final String EVENT_WITH_MESSAGE_IDS_JSON =
+            "{" +
+                "  \"FlagsUpdated\": {" +
+                "    \"eventId\":\"6e0dd59d-660e-4d9b-b22f-0354479f47b4\"," +
+                "    \"path\": {" +
+                "      \"namespace\": \"#private\"," +
+                "      \"user\": \"user\"," +
+                "      \"name\": \"mailboxName\"" +
+                "    }," +
+                "    \"mailboxId\": \"18\"," +
+                "    \"sessionId\": 42," +
+                "    \"updatedFlags\": [" +
+                "      {" +
+                "        \"uid\": 123456," +
+                "        \"messageId\": \"23456\"," +
+                "        \"modSeq\": 35," +
+                "        \"oldFlags\": {\"systemFlags\":[\"Deleted\",\"Seen\"],\"userFlags\":[\"Old Flag 1\"]}," +
+                "        \"newFlags\": {\"systemFlags\":[\"Answered\",\"Draft\"],\"userFlags\":[\"New Flag 1\"]}" +
+                "      }," +
+                "      {" +
+                "        \"uid\": 654321," +
+                "        \"messageId\": \"78901\"," +
+                "        \"modSeq\": 36," +
+                "        \"oldFlags\": {\"systemFlags\":[\"Flagged\",\"Recent\"],\"userFlags\":[\"Old Flag 2\"]}," +
+                "        \"newFlags\": {\"systemFlags\":[\"Answered\",\"Seen\"],\"userFlags\":[\"New Flag 2\"]}" +
+                "      }" +
+                "    ]," +
+                "    \"user\": \"user\"" +
+                "  }" +
+                "}";
+
+        @Test
+        void flagsUpdatedShouldBeWellSerialized() {
+            assertThatJson(EVENT_SERIALIZER.toJson(eventWithMessageIds))
+                .when(Option.IGNORING_ARRAY_ORDER)
+                .isEqualTo(EVENT_WITH_MESSAGE_IDS_JSON);
+        }
+
+        @Test
+        void flagsUpdatedShouldBeWellDeSerialized() {
+            assertThat(EVENT_SERIALIZER.fromJson(EVENT_WITH_MESSAGE_IDS_JSON).get())
+                .isEqualTo(eventWithMessageIds);
+        }
+    }
+
+    @Nested
     class DeserializationError {
         @Test
         void flagsUpdatedShouldThrowWhenMissingSessionId() {


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


[james-project] 07/24: JAMES-3486 Adapt MailboxChangesMethodContract for stability against distributed environment

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit b6fee54188409eb5a115468d52511f78cac08ca6
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Mon Dec 28 11:03:48 2020 +0700

    JAMES-3486 Adapt MailboxChangesMethodContract for stability against distributed environment
    
    Because changes in distributed environment do not happen instantaneously, we need to adapt the contract so that the tests behave in a more reliable way.
    
    Before, we were storing a state manually as a reference point, then the change(s) that we interested in would be conducted after that. This will not work
    in distributed environment, since the reference state might be stored even before the provisioning process complete and leads to unpredictable result.
    
    The fix:
    - We will wait for a new state to be recorded successfully each time there is a change happen
    - Fetch them sequentially until all the preparation steps are completed
    - Mark the latest stage
    - Conduct the change that we are interested in
    - fetch the result with the latest state as reference point.
---
 .../apache/james/jmap/draft/JmapGuiceProbe.java    |    6 +-
 .../contract/MailboxChangesMethodContract.scala    | 1613 +++++++++++---------
 .../memory/MemoryMailboxChangesMethodTest.java     |    7 +
 3 files changed, 869 insertions(+), 757 deletions(-)

diff --git a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/draft/JmapGuiceProbe.java b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/draft/JmapGuiceProbe.java
index 9c9affe..ca1616a 100644
--- a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/draft/JmapGuiceProbe.java
+++ b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/draft/JmapGuiceProbe.java
@@ -96,7 +96,11 @@ public class JmapGuiceProbe implements GuiceProbe {
         mailboxChangeRepository.save(change).block();
     }
 
-    public State latestState(AccountId accountId) {
+    public State getLastestState(AccountId accountId) {
         return mailboxChangeRepository.getLatestState(accountId).block();
     }
+
+    public State getLastestStateWithDelegation(AccountId accountId) {
+        return mailboxChangeRepository.getLatestStateWithDelegation(accountId).block();
+    }
 }
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxChangesMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxChangesMethodContract.scala
index 85f88e6..7ecf86f 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxChangesMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxChangesMethodContract.scala
@@ -20,8 +20,7 @@
 package org.apache.james.jmap.rfc8621.contract
 
 import java.nio.charset.StandardCharsets
-import java.time.ZonedDateTime
-import java.util.UUID
+import java.util.concurrent.TimeUnit
 
 import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
 import io.restassured.RestAssured.{`given`, requestSpecification}
@@ -33,8 +32,6 @@ import net.javacrumbs.jsonunit.core.Option.IGNORING_ARRAY_ORDER
 import net.javacrumbs.jsonunit.core.internal.Options
 import org.apache.http.HttpStatus.SC_OK
 import org.apache.james.GuiceJamesServer
-import org.apache.james.core.Username
-import org.apache.james.jmap.api.change.MailboxChange
 import org.apache.james.jmap.api.change.State
 import org.apache.james.jmap.api.model.AccountId
 import org.apache.james.jmap.core.ResponseObject.SESSION_STATE
@@ -48,22 +45,22 @@ import org.apache.james.mime4j.dom.Message
 import org.apache.james.modules.{ACLProbeImpl, MailboxProbeImpl}
 import org.apache.james.utils.DataProbeImpl
 import org.assertj.core.api.Assertions.assertThat
+import org.awaitility.Awaitility
+import org.awaitility.Duration.ONE_HUNDRED_MILLISECONDS
 import org.junit.jupiter.api.{BeforeEach, Nested, Test}
-import play.api.libs.json.{JsString, Json}
-
-import scala.jdk.CollectionConverters._
-
-object TestId {
-  def of(value: Long): MailboxId = TestId(value)
-}
-
-case class TestId(value: Long) extends MailboxId {
-  override def serialize(): String = String.valueOf(value)
-}
+import play.api.libs.json.{JsArray, JsString, Json}
 
 trait MailboxChangesMethodContract {
 
+  private lazy val slowPacedPollInterval = ONE_HUNDRED_MILLISECONDS
+  private lazy val calmlyAwait = Awaitility.`with`
+    .pollInterval(slowPacedPollInterval)
+    .and.`with`.pollDelay(slowPacedPollInterval)
+    .await
+  private lazy val awaitAtMostTenSeconds = calmlyAwait.atMost(10, TimeUnit.SECONDS)
+
   def stateFactory: State.Factory
+  def generateMailboxId: MailboxId
 
   @BeforeEach
   def setUp(server: GuiceJamesServer): Unit = {
@@ -82,9 +79,7 @@ trait MailboxChangesMethodContract {
   @Test
   def mailboxChangesShouldReturnCreatedChanges(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
-    provisionSystemMailboxes(server)
-
-    val oldState: State = storeReferenceState(server, BOB)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val mailboxId1: String = mailboxProbe
       .createMailbox(MailboxPath.forUser(BOB, "mailbox1"))
@@ -105,11 +100,12 @@ trait MailboxChangesMethodContract {
          |    "Mailbox/changes",
          |    {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "sinceState": "${oldState.getValue}"
+         |      "sinceState": "${provisioningState.getValue}"
          |    },
          |    "c1"]]
          |}""".stripMargin
 
+    awaitAtMostTenSeconds.untilAsserted { () =>
       val response = `given`
         .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
         .body(request)
@@ -127,32 +123,35 @@ trait MailboxChangesMethodContract {
         .withOptions(new Options(IGNORING_ARRAY_ORDER))
         .isEqualTo(
           s"""{
-            |    "sessionState": "${SESSION_STATE.value}",
-            |    "methodResponses": [
-            |      [ "Mailbox/changes", {
-            |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-            |        "oldState": "${oldState.getValue}",
-            |        "hasMoreChanges": false,
-            |        "updatedProperties":null,
-            |        "created": ["$mailboxId1", "$mailboxId2", "$mailboxId3"],
-            |        "updated": [],
-            |        "destroyed": []
-            |      }, "c1"]
-            |    ]
-            |}""".stripMargin)
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${provisioningState.getValue}",
+             |        "hasMoreChanges": false,
+             |        "updatedProperties": null,
+             |        "created": ["$mailboxId1", "$mailboxId2", "$mailboxId3"],
+             |        "updated": [],
+             |        "destroyed": []
+             |      }, "c1"]
+             |    ]
+             |}""".stripMargin)
+    }
   }
 
   @Test
   def mailboxChangesShouldReturnUpdatedChangesWhenRenameMailbox(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+    val accountId: AccountId = AccountId.fromUsername(BOB)
+
+    val provisioningState: State = provisionSystemMailboxes(server)
 
-    provisionSystemMailboxes(server)
     val path = MailboxPath.forUser(BOB, "mailbox1")
     val mailboxId: String = mailboxProbe
       .createMailbox(path)
       .serialize
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val oldState: State = waitForNextState(server, accountId, provisioningState)
 
     JmapRequests.renameMailbox(mailboxId, "mailbox11")
 
@@ -168,50 +167,52 @@ trait MailboxChangesMethodContract {
          |    "c1"]]
          |}""".stripMargin
 
-    val response = `given`
-      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(request)
-    .when
-      .post
-    .`then`
-      .statusCode(SC_OK)
-      .contentType(JSON)
-      .extract
-      .body
-      .asString
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].newState")
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |    "sessionState": "${SESSION_STATE.value}",
-           |    "methodResponses": [
-           |      [ "Mailbox/changes", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "oldState": "${oldState.getValue}",
-           |        "hasMoreChanges": false,
-           |        "updatedProperties":null,
-           |        "created": [],
-           |        "updated": ["$mailboxId"],
-           |        "destroyed": []
-           |      }, "c1"]
-           |    ]
-           |}""".stripMargin)
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${oldState.getValue}",
+             |        "hasMoreChanges": false,
+             |        "updatedProperties": null,
+             |        "created": [],
+             |        "updated": ["$mailboxId"],
+             |        "destroyed": []
+             |      }, "c1"]
+             |    ]
+             |}""".stripMargin)
+    }
   }
 
   @Test
   def mailboxChangesShouldReturnUpdatedChangesWhenAppendMessageToMailbox(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
 
-    provisionSystemMailboxes(server)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val path = MailboxPath.forUser(BOB, "mailbox1")
     val mailboxId: String = mailboxProbe
       .createMailbox(path)
       .serialize
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val oldState: State = waitForNextState(server, AccountId.fromUsername(BOB), provisioningState)
 
     val message: Message = Message.Builder
       .of
@@ -232,49 +233,54 @@ trait MailboxChangesMethodContract {
          |    "c1"]]
          |}""".stripMargin
 
-    val response = `given`
-      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(request)
-    .when
-      .post
-    .`then`
-      .statusCode(SC_OK)
-      .contentType(JSON)
-      .extract
-      .body
-      .asString
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].newState")
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |    "sessionState": "${SESSION_STATE.value}",
-           |    "methodResponses": [
-           |      [ "Mailbox/changes", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "oldState": "${oldState.getValue}",
-           |        "hasMoreChanges": false,
-           |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
-           |        "created": [],
-           |        "updated": ["$mailboxId"],
-           |        "destroyed": []
-           |      }, "c1"]
-           |    ]
-           |}""".stripMargin)
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${oldState.getValue}",
+             |        "hasMoreChanges": false,
+             |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
+             |        "created": [],
+             |        "updated": ["$mailboxId"],
+             |        "destroyed": []
+             |      }, "c1"]
+             |    ]
+             |}""".stripMargin)
+    }
   }
 
   @Test
   def mailboxChangesShouldReturnUpdatedChangesWhenAddSeenFlag(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+    val accountId: AccountId = AccountId.fromUsername(BOB)
 
-    provisionSystemMailboxes(server)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val path = MailboxPath.forUser(BOB, "mailbox1")
     val mailboxId: String = mailboxProbe
       .createMailbox(path)
       .serialize
 
+    val state1: State = waitForNextState(server, accountId, provisioningState)
+
     val message: Message = Message.Builder
       .of
       .setSubject("test")
@@ -282,7 +288,7 @@ trait MailboxChangesMethodContract {
       .build
     val messageId: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val oldState: State = waitForNextState(server, accountId, state1)
 
     JmapRequests.markEmailAsSeen(messageId)
 
@@ -298,59 +304,66 @@ trait MailboxChangesMethodContract {
          |    "c1"]]
          |}""".stripMargin
 
-    val response = `given`
-      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(request)
-    .when
-      .post
-    .`then`
-      .statusCode(SC_OK)
-      .contentType(JSON)
-      .extract
-      .body
-      .asString
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].newState")
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |    "sessionState": "${SESSION_STATE.value}",
-           |    "methodResponses": [
-           |      [ "Mailbox/changes", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "oldState": "${oldState.getValue}",
-           |        "hasMoreChanges": false,
-           |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
-           |        "created": [],
-           |        "updated": ["$mailboxId"],
-           |        "destroyed": []
-           |      }, "c1"]
-           |    ]
-           |}""".stripMargin)
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${oldState.getValue}",
+             |        "hasMoreChanges": false,
+             |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
+             |        "created": [],
+             |        "updated": ["$mailboxId"],
+             |        "destroyed": []
+             |      }, "c1"]
+             |    ]
+             |}""".stripMargin)
+    }
   }
 
   @Test
   def mailboxChangesShouldReturnUpdatedChangesWhenRemoveSeenFlag(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+    val accountId: AccountId = AccountId.fromUsername(BOB)
 
-    provisionSystemMailboxes(server)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val path = MailboxPath.forUser(BOB, "mailbox1")
     val mailboxId: String = mailboxProbe
       .createMailbox(path)
       .serialize
 
+    val state1: State = waitForNextState(server, accountId, provisioningState)
+
     server.getProbe(classOf[ACLProbeImpl])
       .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read))
 
+    val state2: State = waitForNextState(server, accountId, state1)
+
     val messageId: MessageId = mailboxProbe.appendMessage(BOB.asString(), path,
       AppendCommand.builder()
         .withFlags(new Flags(Flags.Flag.SEEN))
         .build("header: value\r\n\r\nbody"))
       .getMessageId
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val oldState: State = waitForNextState(server, accountId, state2)
 
     JmapRequests.markEmailAsNotSeen(messageId)
 
@@ -366,7 +379,8 @@ trait MailboxChangesMethodContract {
          |    "c1"]]
          |}""".stripMargin
 
-    val response = `given`
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
         .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
         .body(request)
       .when
@@ -378,37 +392,41 @@ trait MailboxChangesMethodContract {
         .body
         .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].newState")
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |  "sessionState": "${SESSION_STATE.value}",
-           |  "methodResponses": [
-           |    ["Mailbox/changes", {
-           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |      "oldState": "${oldState.getValue}",
-           |      "hasMoreChanges": false,
-           |      "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
-           |      "created": [],
-           |      "updated": ["$mailboxId"],
-           |      "destroyed": []
-           |    }, "c1"]
-           |  ]
-           |}""".stripMargin)
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |  "sessionState": "${SESSION_STATE.value}",
+             |  "methodResponses": [
+             |    ["Mailbox/changes", {
+             |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |      "oldState": "${oldState.getValue}",
+             |      "hasMoreChanges": false,
+             |      "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
+             |      "created": [],
+             |      "updated": ["$mailboxId"],
+             |      "destroyed": []
+             |    }, "c1"]
+             |  ]
+             |}""".stripMargin)
+    }
   }
 
   @Test
   def mailboxChangesShouldReturnUpdatedChangesWhenDestroyEmail(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+    val accountId: AccountId = AccountId.fromUsername(BOB)
 
-    provisionSystemMailboxes(server)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val path = MailboxPath.forUser(BOB, "mailbox1")
     val mailboxId: String = mailboxProbe
       .createMailbox(path)
       .serialize
 
+    val state1: State = waitForNextState(server, accountId, provisioningState)
+
     val message: Message = Message.Builder
       .of
       .setSubject("test")
@@ -416,7 +434,7 @@ trait MailboxChangesMethodContract {
       .build
     val messageId: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val oldState: State = waitForNextState(server, accountId, state1)
 
     JmapRequests.destroyEmail(messageId)
 
@@ -432,7 +450,8 @@ trait MailboxChangesMethodContract {
          |    "c1"]]
          |}""".stripMargin
 
-    val response = `given`
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
         .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
         .body(request)
       .when
@@ -444,24 +463,25 @@ trait MailboxChangesMethodContract {
         .body
         .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].newState")
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |  "sessionState": "${SESSION_STATE.value}",
-           |  "methodResponses": [
-           |    ["Mailbox/changes", {
-           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |      "oldState": "${oldState.getValue}",
-           |      "hasMoreChanges": false,
-           |      "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
-           |      "created": [],
-           |      "updated": ["$mailboxId"],
-           |      "destroyed": []
-           |    }, "c1"]
-           |  ]
-           |}""".stripMargin)
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |  "sessionState": "${SESSION_STATE.value}",
+             |  "methodResponses": [
+             |    ["Mailbox/changes", {
+             |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |      "oldState": "${oldState.getValue}",
+             |      "hasMoreChanges": false,
+             |      "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
+             |      "created": [],
+             |      "updated": ["$mailboxId"],
+             |      "destroyed": []
+             |    }, "c1"]
+             |  ]
+             |}""".stripMargin)
+    }
   }
 
   @Nested
@@ -480,7 +500,7 @@ trait MailboxChangesMethodContract {
       server.getProbe(classOf[ACLProbeImpl])
         .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read))
 
-      val oldState: State = storeReferenceState(server, ANDRE)
+      val oldState: State = waitForNextStateWithDelegation(server, AccountId.fromUsername(ANDRE), State.INITIAL)
 
       val message: Message = Message.Builder
         .of
@@ -501,39 +521,41 @@ trait MailboxChangesMethodContract {
            |    "c1"]]
            |}""".stripMargin
 
-      val response = `given`(
-        baseRequestSpecBuilder(server)
-          .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
-          .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-          .setBody(request)
-          .build, new ResponseSpecBuilder().build)
-        .post
-      .`then`
-        .statusCode(SC_OK)
-        .contentType(JSON)
-        .extract
-        .body
-        .asString
-
-      assertThatJson(response)
-        .whenIgnoringPaths("methodResponses[0][1].newState")
-        .withOptions(new Options(IGNORING_ARRAY_ORDER))
-        .isEqualTo(
-          s"""{
-             |    "sessionState": "${SESSION_STATE.value}",
-             |    "methodResponses": [
-             |      [ "Mailbox/changes", {
-             |        "accountId": "$ANDRE_ACCOUNT_ID",
-             |        "oldState": "${oldState.getValue}",
-             |        "hasMoreChanges": false,
-             |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
-             |        "created": [],
-             |        "updated": ["$mailboxId"],
-             |        "destroyed": []
-             |      }, "c1"]
-             |    ]
-             |}""".stripMargin)
-    }
+      awaitAtMostTenSeconds.untilAsserted { () =>
+        val response = `given`(
+          baseRequestSpecBuilder(server)
+            .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
+            .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+            .setBody(request)
+            .build, new ResponseSpecBuilder().build)
+          .post
+          .`then`
+            .statusCode(SC_OK)
+            .contentType(JSON)
+            .extract
+            .body
+            .asString
+
+        assertThatJson(response)
+          .whenIgnoringPaths("methodResponses[0][1].newState")
+          .withOptions(new Options(IGNORING_ARRAY_ORDER))
+          .isEqualTo(
+            s"""{
+               |    "sessionState": "${SESSION_STATE.value}",
+               |    "methodResponses": [
+               |      [ "Mailbox/changes", {
+               |        "accountId": "$ANDRE_ACCOUNT_ID",
+               |        "oldState": "${oldState.getValue}",
+               |        "hasMoreChanges": false,
+               |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
+               |        "created": [],
+               |        "updated": ["$mailboxId"],
+               |        "destroyed": []
+               |      }, "c1"]
+               |    ]
+               |}""".stripMargin)
+      }
+    }
 
     @Test
     def mailboxChangesShouldReturnUpdatedChangesWhenRenameMailbox(server: GuiceJamesServer): Unit = {
@@ -549,7 +571,7 @@ trait MailboxChangesMethodContract {
       server.getProbe(classOf[ACLProbeImpl])
         .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read))
 
-      val oldState: State = storeReferenceState(server, ANDRE)
+      val oldState: State = waitForNextStateWithDelegation(server, AccountId.fromUsername(ANDRE), State.INITIAL)
 
       JmapRequests.renameMailbox(mailboxId, "mailbox11")
 
@@ -565,38 +587,40 @@ trait MailboxChangesMethodContract {
            |    "c1"]]
            |}""".stripMargin
 
-      val response = `given`(
-        baseRequestSpecBuilder(server)
-          .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
-          .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-          .setBody(request)
-          .build, new ResponseSpecBuilder().build)
-        .post
-      .`then`
-        .statusCode(SC_OK)
-        .contentType(JSON)
-        .extract
-        .body
-        .asString
-
-      assertThatJson(response)
-        .whenIgnoringPaths("methodResponses[0][1].newState")
-        .withOptions(new Options(IGNORING_ARRAY_ORDER))
-        .isEqualTo(
-          s"""{
-             |    "sessionState": "${SESSION_STATE.value}",
-             |    "methodResponses": [
-             |      [ "Mailbox/changes", {
-             |        "accountId": "$ANDRE_ACCOUNT_ID",
-             |        "oldState": "${oldState.getValue}",
-             |        "hasMoreChanges": false,
-             |        "updatedProperties":null,
-             |        "created": [],
-             |        "updated": ["$mailboxId"],
-             |        "destroyed": []
-             |      }, "c1"]
-             |    ]
-             |}""".stripMargin)
+      awaitAtMostTenSeconds.untilAsserted { () =>
+        val response = `given`(
+          baseRequestSpecBuilder(server)
+            .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
+            .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+            .setBody(request)
+            .build, new ResponseSpecBuilder().build)
+          .post
+          .`then`
+            .statusCode(SC_OK)
+            .contentType(JSON)
+            .extract
+            .body
+            .asString
+
+        assertThatJson(response)
+          .whenIgnoringPaths("methodResponses[0][1].newState")
+          .withOptions(new Options(IGNORING_ARRAY_ORDER))
+          .isEqualTo(
+            s"""{
+               |    "sessionState": "${SESSION_STATE.value}",
+               |    "methodResponses": [
+               |      [ "Mailbox/changes", {
+               |        "accountId": "$ANDRE_ACCOUNT_ID",
+               |        "oldState": "${oldState.getValue}",
+               |        "hasMoreChanges": false,
+               |        "updatedProperties":null,
+               |        "created": [],
+               |        "updated": ["$mailboxId"],
+               |        "destroyed": []
+               |      }, "c1"]
+               |    ]
+               |}""".stripMargin)
+      }
     }
 
     @Test
@@ -613,6 +637,8 @@ trait MailboxChangesMethodContract {
       server.getProbe(classOf[ACLProbeImpl])
         .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read))
 
+      val state1: State = waitForNextStateWithDelegation(server, AccountId.fromUsername(ANDRE), State.INITIAL)
+
       val message: Message = Message.Builder
         .of
         .setSubject("test")
@@ -620,7 +646,7 @@ trait MailboxChangesMethodContract {
         .build
       val messageId: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
 
-      val oldState: State = storeReferenceState(server, ANDRE)
+      val oldState: State = waitForNextStateWithDelegation(server, AccountId.fromUsername(ANDRE), state1)
 
       JmapRequests.markEmailAsSeen(messageId)
 
@@ -636,38 +662,40 @@ trait MailboxChangesMethodContract {
            |    "c1"]]
            |}""".stripMargin
 
-      val response = `given`(
-        baseRequestSpecBuilder(server)
-          .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
-          .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-          .setBody(request)
-          .build, new ResponseSpecBuilder().build)
-        .post
-      .`then`
-        .statusCode(SC_OK)
-        .contentType(JSON)
-        .extract
-        .body
-        .asString
-
-      assertThatJson(response)
-        .whenIgnoringPaths("methodResponses[0][1].newState")
-        .withOptions(new Options(IGNORING_ARRAY_ORDER))
-        .isEqualTo(
-          s"""{
-             |    "sessionState": "${SESSION_STATE.value}",
-             |    "methodResponses": [
-             |      [ "Mailbox/changes", {
-             |        "accountId": "$ANDRE_ACCOUNT_ID",
-             |        "oldState": "${oldState.getValue}",
-             |        "hasMoreChanges": false,
-             |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
-             |        "created": [],
-             |        "updated": ["$mailboxId"],
-             |        "destroyed": []
-             |      }, "c1"]
-             |    ]
-             |}""".stripMargin)
+      awaitAtMostTenSeconds.untilAsserted { () =>
+        val response = `given`(
+          baseRequestSpecBuilder(server)
+            .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
+            .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+            .setBody(request)
+            .build, new ResponseSpecBuilder().build)
+          .post
+          .`then`
+            .statusCode(SC_OK)
+            .contentType(JSON)
+            .extract
+            .body
+            .asString
+
+          assertThatJson(response)
+            .whenIgnoringPaths("methodResponses[0][1].newState")
+            .withOptions(new Options(IGNORING_ARRAY_ORDER))
+            .isEqualTo(
+              s"""{
+                 |    "sessionState": "${SESSION_STATE.value}",
+                 |    "methodResponses": [
+                 |      [ "Mailbox/changes", {
+                 |        "accountId": "$ANDRE_ACCOUNT_ID",
+                 |        "oldState": "${oldState.getValue}",
+                 |        "hasMoreChanges": false,
+                 |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
+                 |        "created": [],
+                 |        "updated": ["$mailboxId"],
+                 |        "destroyed": []
+                 |      }, "c1"]
+                 |    ]
+                 |}""".stripMargin)
+      }
     }
 
     @Test
@@ -684,13 +712,15 @@ trait MailboxChangesMethodContract {
       server.getProbe(classOf[ACLProbeImpl])
         .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read))
 
+      val state1: State = waitForNextStateWithDelegation(server, AccountId.fromUsername(ANDRE), State.INITIAL)
+
       val messageId: MessageId = mailboxProbe.appendMessage(BOB.asString(), path,
         AppendCommand.builder()
           .withFlags(new Flags(Flags.Flag.SEEN))
           .build("header: value\r\n\r\nbody"))
         .getMessageId
 
-      val oldState: State = storeReferenceState(server, ANDRE)
+      val oldState: State = waitForNextStateWithDelegation(server, AccountId.fromUsername(ANDRE), state1)
 
       JmapRequests.markEmailAsNotSeen(messageId)
 
@@ -706,38 +736,40 @@ trait MailboxChangesMethodContract {
            |    "c1"]]
            |}""".stripMargin
 
-      val response = `given`(
-        baseRequestSpecBuilder(server)
-          .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
-          .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-          .setBody(request)
-          .build, new ResponseSpecBuilder().build)
-        .post
-      .`then`
-        .statusCode(SC_OK)
-        .contentType(JSON)
-        .extract
-        .body
-        .asString
-
-      assertThatJson(response)
-        .whenIgnoringPaths("methodResponses[0][1].newState")
-        .withOptions(new Options(IGNORING_ARRAY_ORDER))
-        .isEqualTo(
-          s"""{
-             |    "sessionState": "${SESSION_STATE.value}",
-             |    "methodResponses": [
-             |      [ "Mailbox/changes", {
-             |        "accountId": "$ANDRE_ACCOUNT_ID",
-             |        "oldState": "${oldState.getValue}",
-             |        "hasMoreChanges": false,
-             |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
-             |        "created": [],
-             |        "updated": ["$mailboxId"],
-             |        "destroyed": []
-             |      }, "c1"]
-             |    ]
-             |}""".stripMargin)
+      awaitAtMostTenSeconds.untilAsserted { () =>
+        val response = `given`(
+          baseRequestSpecBuilder(server)
+            .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
+            .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+            .setBody(request)
+            .build, new ResponseSpecBuilder().build)
+          .post
+          .`then`
+            .statusCode(SC_OK)
+            .contentType(JSON)
+            .extract
+            .body
+            .asString
+
+        assertThatJson(response)
+          .whenIgnoringPaths("methodResponses[0][1].newState")
+          .withOptions(new Options(IGNORING_ARRAY_ORDER))
+          .isEqualTo(
+            s"""{
+               |    "sessionState": "${SESSION_STATE.value}",
+               |    "methodResponses": [
+               |      [ "Mailbox/changes", {
+               |        "accountId": "$ANDRE_ACCOUNT_ID",
+               |        "oldState": "${oldState.getValue}",
+               |        "hasMoreChanges": false,
+               |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
+               |        "created": [],
+               |        "updated": ["$mailboxId"],
+               |        "destroyed": []
+               |      }, "c1"]
+               |    ]
+               |}""".stripMargin)
+      }
     }
 
     @Test
@@ -754,6 +786,8 @@ trait MailboxChangesMethodContract {
       server.getProbe(classOf[ACLProbeImpl])
         .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read))
 
+      val state1: State = waitForNextStateWithDelegation(server, AccountId.fromUsername(ANDRE), State.INITIAL)
+
       val message: Message = Message.Builder
         .of
         .setSubject("test")
@@ -761,7 +795,7 @@ trait MailboxChangesMethodContract {
         .build
       val messageId: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
 
-      val oldState: State = storeReferenceState(server, ANDRE)
+      val oldState: State = waitForNextStateWithDelegation(server, AccountId.fromUsername(ANDRE), state1)
 
       JmapRequests.destroyEmail(messageId)
 
@@ -777,38 +811,40 @@ trait MailboxChangesMethodContract {
            |    "c1"]]
            |}""".stripMargin
 
-      val response = `given`(
-        baseRequestSpecBuilder(server)
-          .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
-          .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-          .setBody(request)
-          .build, new ResponseSpecBuilder().build)
-        .post
-      .`then`
-        .statusCode(SC_OK)
-        .contentType(JSON)
-        .extract
-        .body
-        .asString
-
-      assertThatJson(response)
-        .whenIgnoringPaths("methodResponses[0][1].newState")
-        .withOptions(new Options(IGNORING_ARRAY_ORDER))
-        .isEqualTo(
-          s"""{
-             |    "sessionState": "${SESSION_STATE.value}",
-             |    "methodResponses": [
-             |      [ "Mailbox/changes", {
-             |        "accountId": "$ANDRE_ACCOUNT_ID",
-             |        "oldState": "${oldState.getValue}",
-             |        "hasMoreChanges": false,
-             |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
-             |        "created": [],
-             |        "updated": ["$mailboxId"],
-             |        "destroyed": []
-             |      }, "c1"]
-             |    ]
-             |}""".stripMargin)
+      awaitAtMostTenSeconds.untilAsserted { () =>
+        val response = `given`(
+          baseRequestSpecBuilder(server)
+            .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
+            .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+            .setBody(request)
+            .build, new ResponseSpecBuilder().build)
+          .post
+          .`then`
+            .statusCode(SC_OK)
+            .contentType(JSON)
+            .extract
+            .body
+            .asString
+
+        assertThatJson(response)
+          .whenIgnoringPaths("methodResponses[0][1].newState")
+          .withOptions(new Options(IGNORING_ARRAY_ORDER))
+          .isEqualTo(
+            s"""{
+               |    "sessionState": "${SESSION_STATE.value}",
+               |    "methodResponses": [
+               |      [ "Mailbox/changes", {
+               |        "accountId": "$ANDRE_ACCOUNT_ID",
+               |        "oldState": "${oldState.getValue}",
+               |        "hasMoreChanges": false,
+               |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
+               |        "created": [],
+               |        "updated": ["$mailboxId"],
+               |        "destroyed": []
+               |      }, "c1"]
+               |    ]
+               |}""".stripMargin)
+      }
     }
 
     @Test
@@ -818,9 +854,7 @@ trait MailboxChangesMethodContract {
       provisionSystemMailboxes(server)
 
       val path = MailboxPath.forUser(BOB, "mailbox1")
-      val mailboxId: String = mailboxProbe
-        .createMailbox(path)
-        .serialize
+      mailboxProbe.createMailbox(path)
 
       server.getProbe(classOf[ACLProbeImpl])
         .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read))
@@ -832,7 +866,7 @@ trait MailboxChangesMethodContract {
         .build
       val messageId: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
 
-      val oldState: State = storeReferenceState(server, ANDRE)
+      waitForNextStateWithDelegation(server, AccountId.fromUsername(ANDRE), State.INITIAL)
 
       JmapRequests.destroyEmail(messageId)
 
@@ -843,47 +877,49 @@ trait MailboxChangesMethodContract {
            |    "Mailbox/changes",
            |    {
            |      "accountId": "$ANDRE_ACCOUNT_ID",
-           |      "sinceState": "${oldState.getValue}"
+           |      "sinceState": "${State.INITIAL.getValue}"
            |    },
            |    "c1"]]
            |}""".stripMargin
 
-      val response = `given`(
-        baseRequestSpecBuilder(server)
-          .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
-          .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-          .setBody(request)
-          .build, new ResponseSpecBuilder().build)
-        .post
-      .`then`
-        .statusCode(SC_OK)
-        .contentType(JSON)
-        .extract
-        .body
-        .asString
-
-      assertThatJson(response)
-        .whenIgnoringPaths("methodResponses[0][1].newState")
-        .withOptions(new Options(IGNORING_ARRAY_ORDER))
-        .isEqualTo(
-          s"""{
-             |    "sessionState": "${SESSION_STATE.value}",
-             |    "methodResponses": [
-             |      [ "Mailbox/changes", {
-             |        "accountId": "$ANDRE_ACCOUNT_ID",
-             |        "oldState": "${oldState.getValue}",
-             |        "hasMoreChanges": false,
-             |        "updatedProperties":null,
-             |        "created": [],
-             |        "updated": [],
-             |        "destroyed": []
-             |      }, "c1"]
-             |    ]
-             |}""".stripMargin)
+      awaitAtMostTenSeconds.untilAsserted { () =>
+        val response = `given`(
+          baseRequestSpecBuilder(server)
+            .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
+            .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+            .setBody(request)
+            .build, new ResponseSpecBuilder().build)
+          .post
+          .`then`
+            .statusCode(SC_OK)
+            .contentType(JSON)
+            .extract
+            .body
+            .asString
+
+        assertThatJson(response)
+          .whenIgnoringPaths("methodResponses[0][1].newState")
+          .withOptions(new Options(IGNORING_ARRAY_ORDER))
+          .isEqualTo(
+            s"""{
+               |    "sessionState": "${SESSION_STATE.value}",
+               |    "methodResponses": [
+               |      [ "Mailbox/changes", {
+               |        "accountId": "$ANDRE_ACCOUNT_ID",
+               |        "oldState": "${State.INITIAL.getValue}",
+               |        "hasMoreChanges": false,
+               |        "updatedProperties": null,
+               |        "created": [],
+               |        "updated": [],
+               |        "destroyed": []
+               |      }, "c1"]
+               |    ]
+               |}""".stripMargin)
+      }
     }
 
     @Test
-    def mailboxChangesShouldReturnUpdatedChangesWhenDestroyDelegatedMailbox(server: GuiceJamesServer): Unit = {
+    def mailboxChangesShouldReturnDestroyedChangesWhenDestroyDelegatedMailbox(server: GuiceJamesServer): Unit = {
       val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
 
       provisionSystemMailboxes(server)
@@ -896,7 +932,7 @@ trait MailboxChangesMethodContract {
       server.getProbe(classOf[ACLProbeImpl])
         .replaceRights(path, ANDRE.asString, new MailboxACL.Rfc4314Rights(Right.Lookup, Right.Read))
 
-      val oldState: State = storeReferenceState(server, ANDRE)
+      val oldState: State = waitForNextStateWithDelegation(server, AccountId.fromUsername(ANDRE), State.INITIAL)
 
       JmapRequests.destroyMailbox(mailboxId)
 
@@ -912,56 +948,58 @@ trait MailboxChangesMethodContract {
            |    "c1"]]
            |}""".stripMargin
 
-      val response = `given`(
-        baseRequestSpecBuilder(server)
-          .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
-          .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-          .setBody(request)
-          .build, new ResponseSpecBuilder().build)
-        .post
-      .`then`
-        .statusCode(SC_OK)
-        .contentType(JSON)
-        .extract
-        .body
-        .asString
-
-      assertThatJson(response)
-        .whenIgnoringPaths("methodResponses[0][1].newState")
-        .withOptions(new Options(IGNORING_ARRAY_ORDER))
-        .isEqualTo(
-          s"""{
-             |    "sessionState": "${SESSION_STATE.value}",
-             |    "methodResponses": [
-             |      [ "Mailbox/changes", {
-             |        "accountId": "$ANDRE_ACCOUNT_ID",
-             |        "oldState": "${oldState.getValue}",
-             |        "hasMoreChanges": false,
-             |        "updatedProperties": null,
-             |        "created": [],
-             |        "updated": [],
-             |        "destroyed": ["$mailboxId"]
-             |      }, "c1"]
-             |    ]
-             |}""".stripMargin)
+      awaitAtMostTenSeconds.untilAsserted { () =>
+        val response = `given`(
+          baseRequestSpecBuilder(server)
+            .setAuth(authScheme(UserCredential(ANDRE, ANDRE_PASSWORD)))
+            .addHeader(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+            .setBody(request)
+            .build, new ResponseSpecBuilder().build)
+          .post
+          .`then`
+            .statusCode(SC_OK)
+            .contentType(JSON)
+            .extract
+            .body
+            .asString
+
+        assertThatJson(response)
+          .whenIgnoringPaths("methodResponses[0][1].newState")
+          .withOptions(new Options(IGNORING_ARRAY_ORDER))
+          .isEqualTo(
+            s"""{
+               |    "sessionState": "${SESSION_STATE.value}",
+               |    "methodResponses": [
+               |      [ "Mailbox/changes", {
+               |        "accountId": "$ANDRE_ACCOUNT_ID",
+               |        "oldState": "${oldState.getValue}",
+               |        "hasMoreChanges": false,
+               |        "updatedProperties": null,
+               |        "created": [],
+               |        "updated": [],
+               |        "destroyed": ["$mailboxId"]
+               |      }, "c1"]
+               |    ]
+               |}""".stripMargin)
+      }
     }
   }
 
   @Test
   def mailboxChangesShouldReturnDestroyedChanges(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+    val accountId: AccountId = AccountId.fromUsername(BOB)
 
-    provisionSystemMailboxes(server)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val path = MailboxPath.forUser(BOB, "mailbox1")
     val mailboxId: String = mailboxProbe
       .createMailbox(path)
       .serialize
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val oldState: State = waitForNextState(server, accountId, provisioningState)
 
-    mailboxProbe
-      .deleteMailbox(path.getNamespace, BOB.asString(), path.getName)
+    mailboxProbe.deleteMailbox(path.getNamespace, BOB.asString(), path.getName)
 
     val request =
       s"""{
@@ -975,58 +1013,51 @@ trait MailboxChangesMethodContract {
          |    "c1"]]
          |}""".stripMargin
 
-    val response = `given`
-      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(request)
-    .when
-      .post
-    .`then`
-      .statusCode(SC_OK)
-      .contentType(JSON)
-      .extract
-      .body
-      .asString
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].newState")
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |    "sessionState": "${SESSION_STATE.value}",
-           |    "methodResponses": [
-           |      [ "Mailbox/changes", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "oldState": "${oldState.getValue}",
-           |        "hasMoreChanges": false,
-           |        "updatedProperties":null,
-           |        "created": [],
-           |        "updated": [],
-           |        "destroyed": ["$mailboxId"]
-           |      }, "c1"]
-           |    ]
-           |}""".stripMargin)
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${oldState.getValue}",
+             |        "hasMoreChanges": false,
+             |        "updatedProperties":null,
+             |        "created": [],
+             |        "updated": [],
+             |        "destroyed": ["$mailboxId"]
+             |      }, "c1"]
+             |    ]
+             |}""".stripMargin)
+    }
   }
 
   @Test
-  def mailboxChangesShouldReturnAllTypeOfChanges(server: GuiceJamesServer): Unit = {
+  def returnedIdsShouldNotReturnDuplicatesAccrossCreatedUpdatedOrDestroyed(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
 
-    provisionSystemMailboxes(server)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val path1 = MailboxPath.forUser(BOB, "mailbox1")
     val mailboxId1: String = mailboxProbe
       .createMailbox(path1)
       .serialize
-    val path2 = MailboxPath.forUser(BOB, "mailbox2")
-    val mailboxId2: String = mailboxProbe
-      .createMailbox(path2)
-      .serialize
-    val path3 = MailboxPath.forUser(BOB, "mailbox3")
-    val oldState: State = server.getProbe(classOf[JmapGuiceProbe]).latestState(AccountId.fromUsername(BOB))
 
-    val mailboxId3: String = mailboxProbe
-      .createMailbox(path3)
-      .serialize
     val message: Message = Message.Builder
       .of
       .setSubject("test")
@@ -1034,8 +1065,13 @@ trait MailboxChangesMethodContract {
       .build
     mailboxProbe.appendMessage(BOB.asString(), path1, AppendCommand.from(message))
 
-    server.getProbe(classOf[MailboxProbeImpl])
-      .deleteMailbox(path1.getNamespace, BOB.asString(), path2.getName)
+    val path2 = MailboxPath.forUser(BOB, "mailbox2")
+    val mailboxId2: String = mailboxProbe
+      .createMailbox(path2)
+      .serialize
+    JmapRequests.renameMailbox(mailboxId2, "mailbox22")
+
+    mailboxProbe.deleteMailbox(path1.getNamespace, BOB.asString(), path1.getName)
 
     val request =
       s"""{
@@ -1044,56 +1080,71 @@ trait MailboxChangesMethodContract {
          |    "Mailbox/changes",
          |    {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "sinceState": "${oldState.getValue}"
+         |      "sinceState": "${provisioningState.getValue}"
          |    },
          |    "c1"]]
          |}""".stripMargin
 
-    val response = `given`
-      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(request)
-    .when
-      .post
-    .`then`
-      .statusCode(SC_OK)
-      .contentType(JSON)
-      .extract
-      .body
-      .asString
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].newState")
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |    "sessionState": "${SESSION_STATE.value}",
-           |    "methodResponses": [
-           |      [ "Mailbox/changes", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "oldState": "${oldState.getValue}",
-           |        "hasMoreChanges": false,
-           |        "updatedProperties": null,
-           |        "created": ["$mailboxId3"],
-           |        "updated": ["$mailboxId1"],
-           |        "destroyed": ["$mailboxId2"]
-           |      }, "c1"]
-           |    ]
-           |}""".stripMargin)
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${provisioningState.getValue}",
+             |        "hasMoreChanges": false,
+             |        "updatedProperties":null,
+             |        "created": ["$mailboxId2"],
+             |        "updated": [],
+             |        "destroyed": []
+             |      }, "c1"]
+             |    ]
+             |}""".stripMargin)
+    }
   }
 
   @Test
-  def returnedIdsShouldNotReturnDuplicatesAccrossCreatedUpdatedOrDestroyed(server: GuiceJamesServer): Unit = {
+  def mailboxChangesShouldReturnAllTypeOfChanges(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+    val accountId: AccountId = AccountId.fromUsername(BOB)
 
-    provisionSystemMailboxes(server)
-
-    val oldState: State = storeReferenceState(server, BOB)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val path1 = MailboxPath.forUser(BOB, "mailbox1")
     val mailboxId1: String = mailboxProbe
       .createMailbox(path1)
       .serialize
 
+    val state1: State = waitForNextState(server, accountId, provisioningState)
+
+    val path2 = MailboxPath.forUser(BOB, "mailbox2")
+    val mailboxId2: String = mailboxProbe
+      .createMailbox(path2)
+      .serialize
+
+    val oldState: State = waitForNextState(server, accountId, state1)
+
+    val path3 = MailboxPath.forUser(BOB, "mailbox3")
+    val mailboxId3: String = mailboxProbe
+      .createMailbox(path3)
+      .serialize
+
     val message: Message = Message.Builder
       .of
       .setSubject("test")
@@ -1101,14 +1152,7 @@ trait MailboxChangesMethodContract {
       .build
     mailboxProbe.appendMessage(BOB.asString(), path1, AppendCommand.from(message))
 
-    val path2 = MailboxPath.forUser(BOB, "mailbox2")
-    val mailboxId2: String = mailboxProbe
-      .createMailbox(path2)
-      .serialize
-    JmapRequests.renameMailbox(mailboxId2, "mailbox22")
-
-    server.getProbe(classOf[MailboxProbeImpl])
-      .deleteMailbox(path1.getNamespace, BOB.asString(), path1.getName)
+    mailboxProbe.deleteMailbox(path2.getNamespace, BOB.asString(), path2.getName)
 
     val request =
       s"""{
@@ -1122,44 +1166,45 @@ trait MailboxChangesMethodContract {
          |    "c1"]]
          |}""".stripMargin
 
-    val response = `given`
-      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(request)
-    .when
-      .post
-    .`then`
-      .statusCode(SC_OK)
-      .contentType(JSON)
-      .extract
-      .body
-      .asString
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].newState")
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |    "sessionState": "${SESSION_STATE.value}",
-           |    "methodResponses": [
-           |      [ "Mailbox/changes", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "oldState": "${oldState.getValue}",
-           |        "hasMoreChanges": false,
-           |        "updatedProperties":null,
-           |        "created": ["$mailboxId2"],
-           |        "updated": [],
-           |        "destroyed": []
-           |      }, "c1"]
-           |    ]
-           |}""".stripMargin)
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${oldState.getValue}",
+             |        "hasMoreChanges": false,
+             |        "updatedProperties": null,
+             |        "created": ["$mailboxId3"],
+             |        "updated": ["$mailboxId1"],
+             |        "destroyed": ["$mailboxId2"]
+             |      }, "c1"]
+             |    ]
+             |}""".stripMargin)
+    }
   }
 
   @Test
   def mailboxChangesShouldReturnHasMoreChangesWhenTrue(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
-    provisionSystemMailboxes(server)
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val mailboxId1: String = mailboxProbe
       .createMailbox(MailboxPath.forUser(BOB, "mailbox1"))
@@ -1192,46 +1237,49 @@ trait MailboxChangesMethodContract {
          |    "Mailbox/changes",
          |    {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "sinceState": "${oldState.getValue}"
+         |      "sinceState": "${provisioningState.getValue}"
          |    },
          |    "c1"]]
          |}""".stripMargin
 
-    val response = `given`
-      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(request)
-    .when
-      .post
-    .`then`
-      .statusCode(SC_OK)
-      .contentType(JSON)
-      .extract
-      .body
-      .asString
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].newState")
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |    "sessionState": "${SESSION_STATE.value}",
-           |    "methodResponses": [
-           |      [ "Mailbox/changes", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "oldState": "${oldState.getValue}",
-           |        "hasMoreChanges": true,
-           |        "updatedProperties":null,
-           |        "created": ["$mailboxId1", "$mailboxId2", "$mailboxId3", "$mailboxId4", "$mailboxId5"],
-           |        "updated": [],
-           |        "destroyed": []
-           |      }, "c1"]
-           |    ]
-           |}""".stripMargin)
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${provisioningState.getValue}",
+             |        "hasMoreChanges": true,
+             |        "updatedProperties": null,
+             |        "created": ["$mailboxId1", "$mailboxId2", "$mailboxId3", "$mailboxId4", "$mailboxId5"],
+             |        "updated": [],
+             |        "destroyed": []
+             |      }, "c1"]
+             |    ]
+             |}""".stripMargin)
+    }
   }
 
   @Test
   def mailboxChangesShouldFailWhenAccountIdNotFound(server: GuiceJamesServer): Unit = {
-    val oldState: State = storeReferenceState(server, BOB)
+    val jmapGuiceProbe:JmapGuiceProbe = server.getProbe(classOf[JmapGuiceProbe])
+    val oldState: State = jmapGuiceProbe.getLastestState(AccountId.fromUsername(BOB))
 
     val request =
       s"""{
@@ -1273,7 +1321,7 @@ trait MailboxChangesMethodContract {
   def mailboxChangesShouldFailWhenStateNotFound(server: GuiceJamesServer): Unit = {
     provisionSystemMailboxes(server)
 
-    val state: String = UUID.randomUUID().toString
+    val state: String = stateFactory.generate().getValue.toString
 
     val request =
       s"""{
@@ -1316,9 +1364,7 @@ trait MailboxChangesMethodContract {
 
   @Test
   def mailboxChangesShouldReturnNoChangesWhenNoNewerState(server: GuiceJamesServer): Unit = {
-    provisionSystemMailboxes(server)
-
-    val oldState: State = storeReferenceState(server, BOB)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val request =
       s"""{
@@ -1327,49 +1373,51 @@ trait MailboxChangesMethodContract {
          |    "Mailbox/changes",
          |    {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "sinceState": "${oldState.getValue}"
+         |      "sinceState": "${provisioningState.getValue}"
          |    },
          |    "c1"]]
          |}""".stripMargin
 
-    val response = `given`
-      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(request)
-    .when
-      .post
-    .`then`
-      .statusCode(SC_OK)
-      .contentType(JSON)
-      .extract
-      .body
-      .asString
-
-    assertThatJson(response)
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |    "sessionState": "${SESSION_STATE.value}",
-           |    "methodResponses": [
-           |      [ "Mailbox/changes", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "oldState": "${oldState.getValue}",
-           |        "newState": "${oldState.getValue}",
-           |        "hasMoreChanges": false,
-           |        "updatedProperties":null,
-           |        "created": [],
-           |        "updated": [],
-           |        "destroyed": []
-           |      }, "c1"]
-           |    ]
-           |}""".stripMargin)
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response)
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${provisioningState.getValue}",
+             |        "newState": "${provisioningState.getValue}",
+             |        "hasMoreChanges": false,
+             |        "updatedProperties": null,
+             |        "created": [],
+             |        "updated": [],
+             |        "destroyed": []
+             |      }, "c1"]
+             |    ]
+             |}""".stripMargin)
+    }
   }
 
   @Test
   def mailboxChangesShouldReturnDifferentStateThanOldState(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
-    provisionSystemMailboxes(server)
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val provisioningState: State = provisionSystemMailboxes(server)
+
     mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "mailbox1"))
     mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "mailbox2"))
 
@@ -1380,40 +1428,41 @@ trait MailboxChangesMethodContract {
          |    "Mailbox/changes",
          |    {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "sinceState": "${oldState.getValue}"
+         |      "sinceState": "${provisioningState.getValue}"
          |    },
          |    "c1"]]
          |}""".stripMargin
 
-    val response = `given`
-      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(request)
-    .when
-      .post
-    .`then`
-      .statusCode(SC_OK)
-      .contentType(JSON)
-      .extract
-      .body
-      .asString
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+        .when
+        .post
+        .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
 
-    val newState = Json.parse(response)
-      .\("methodResponses")
-      .\(0).\(1)
-      .\("newState")
-      .get.asInstanceOf[JsString].value
+      val newState = Json.parse(response)
+        .\("methodResponses")
+        .\(0).\(1)
+        .\("newState")
+        .get.asInstanceOf[JsString].value
 
-    assertThat(oldState.getValue.toString).isNotEqualTo(newState)
+      assertThat(provisioningState.getValue.toString).isNotEqualTo(newState)
+    }
   }
 
   @Test
   def mailboxChangesShouldEventuallyReturnNoChanges(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
-    provisionSystemMailboxes(server)
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val provisioningState: State = provisionSystemMailboxes(server)
+
     mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "mailbox1"))
-    mailboxProbe.createMailbox(MailboxPath.forUser(BOB, "mailbox2"))
 
     val request1 =
       s"""{
@@ -1422,7 +1471,7 @@ trait MailboxChangesMethodContract {
          |    "Mailbox/changes",
          |    {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "sinceState": "${oldState.getValue}"
+         |      "sinceState": "${provisioningState.getValue}"
          |    },
          |    "c1"]]
          |}""".stripMargin
@@ -1492,15 +1541,16 @@ trait MailboxChangesMethodContract {
   @Test
   def mailboxChangesShouldReturnUpdatedPropertiesWhenOnlyCountChanges(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+    val accountId: AccountId = AccountId.fromUsername(BOB)
 
-    provisionSystemMailboxes(server)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val path = MailboxPath.forUser(BOB, "mailbox1")
     val mailboxId: String = mailboxProbe
       .createMailbox(path)
       .serialize
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val oldState: State = waitForNextState(server, accountId, provisioningState)
 
     val message: Message = Message.Builder
       .of
@@ -1561,15 +1611,16 @@ trait MailboxChangesMethodContract {
   @Test
   def mailboxChangesShouldNotReturnUpdatedPropertiesWhenMixedChanges(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+    val accountId: AccountId = AccountId.fromUsername(BOB)
 
-    provisionSystemMailboxes(server)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val path = MailboxPath.forUser(BOB, "mailbox1")
     val mailboxId1: String = mailboxProbe
       .createMailbox(path)
       .serialize
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val oldState: State = waitForNextState(server, accountId, provisioningState)
 
     val path2 = MailboxPath.forUser(BOB, "mailbox2")
     val mailboxId2: String = mailboxProbe
@@ -1595,50 +1646,53 @@ trait MailboxChangesMethodContract {
          |    "c1"]]
          |}""".stripMargin
 
-    val response = `given`
-      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(request)
-    .when
-      .post
-    .`then`
-      .statusCode(SC_OK)
-      .contentType(JSON)
-      .extract
-      .body
-      .asString
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].newState")
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |    "sessionState": "${SESSION_STATE.value}",
-           |    "methodResponses": [
-           |      [ "Mailbox/changes", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "oldState": "${oldState.getValue}",
-           |        "hasMoreChanges": false,
-           |        "updatedProperties":null,
-           |        "created": ["$mailboxId2"],
-           |        "updated": ["$mailboxId1"],
-           |        "destroyed": []
-           |      }, "c1"]
-           |    ]
-           |}""".stripMargin)
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${oldState.getValue}",
+             |        "hasMoreChanges": false,
+             |        "updatedProperties":null,
+             |        "created": ["$mailboxId2"],
+             |        "updated": ["$mailboxId1"],
+             |        "destroyed": []
+             |      }, "c1"]
+             |    ]
+             |}""".stripMargin)
+    }
   }
 
   @Test
   def mailboxChangesShouldSupportBackReferenceWithUpdatedProperties(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+    val accountId: AccountId = AccountId.fromUsername(BOB)
 
-    provisionSystemMailboxes(server)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val path = MailboxPath.forUser(BOB, "mailbox1")
     val mailboxId: String = mailboxProbe
       .createMailbox(path)
       .serialize
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val oldState: State = waitForNextState(server, accountId, provisioningState)
 
     val message: Message = Message.Builder
       .of
@@ -1646,9 +1700,6 @@ trait MailboxChangesMethodContract {
       .setBody("testmail", StandardCharsets.UTF_8)
       .build
     mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
-    val messageId2: MessageId = mailboxProbe.appendMessage(BOB.asString(), path, AppendCommand.from(message)).getMessageId
-
-    JmapRequests.destroyEmail(messageId2)
 
     val request =
       s"""{
@@ -1674,63 +1725,66 @@ trait MailboxChangesMethodContract {
          |  ]
          |}""".stripMargin
 
-    val response = `given`
-      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(request)
-    .when
-      .post
-    .`then`
-      .statusCode(SC_OK)
-      .contentType(JSON)
-      .extract
-      .body
-      .asString
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].newState", "methodResponses[1][1].state")
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |    "sessionState": "${SESSION_STATE.value}",
-           |    "methodResponses": [
-           |      [ "Mailbox/changes", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "oldState": "${oldState.getValue}",
-           |        "hasMoreChanges": false,
-           |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
-           |        "created": [],
-           |        "updated": ["$mailboxId"],
-           |        "destroyed": []
-           |      }, "c1"],
-           |      ["Mailbox/get", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "notFound": [],
-           |        "list": [
-           |          {
-           |            "id": "$mailboxId",
-           |            "totalEmails": 1,
-           |            "unreadEmails": 1,
-           |            "totalThreads": 1,
-           |            "unreadThreads": 1
-           |          }
-           |        ]
-           |      }, "c2"]
-           |    ]
-           |}""".stripMargin)
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState", "methodResponses[1][1].state")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${oldState.getValue}",
+             |        "hasMoreChanges": false,
+             |        "updatedProperties": ["totalEmails", "unreadEmails", "totalThreads", "unreadThreads"],
+             |        "created": [],
+             |        "updated": ["$mailboxId"],
+             |        "destroyed": []
+             |      }, "c1"],
+             |      ["Mailbox/get", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "notFound": [],
+             |        "list": [
+             |          {
+             |            "id": "$mailboxId",
+             |            "totalEmails": 1,
+             |            "unreadEmails": 1,
+             |            "totalThreads": 1,
+             |            "unreadThreads": 1
+             |          }
+             |        ]
+             |      }, "c2"]
+             |    ]
+             |}""".stripMargin)
+    }
   }
 
   @Test
   def mailboxChangesShouldSupportBackReferenceWithNullUpdatedProperties(server: GuiceJamesServer): Unit = {
     val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+    val accountId: AccountId = AccountId.fromUsername(BOB)
 
-    provisionSystemMailboxes(server)
+    val provisioningState: State = provisionSystemMailboxes(server)
 
     val path1 = MailboxPath.forUser(BOB, "mailbox1")
     val mailboxId1: String = mailboxProbe
       .createMailbox(path1)
       .serialize
 
-    val oldState: State = storeReferenceState(server, BOB)
+    val oldState: State = waitForNextState(server, accountId, provisioningState)
 
     val path2 = MailboxPath.forUser(BOB, "mailbox2")
     val mailboxId2: String = mailboxProbe
@@ -1768,76 +1822,86 @@ trait MailboxChangesMethodContract {
          |  ]
          |}""".stripMargin
 
-    val response = `given`
-      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-      .body(request)
-    .when
-      .post
-    .`then`
-      .statusCode(SC_OK)
-      .contentType(JSON)
-      .extract
-      .body
-      .asString
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].newState", "methodResponses[1][1].state")
-      .withOptions(new Options(IGNORING_ARRAY_ORDER))
-      .isEqualTo(
-        s"""{
-           |    "sessionState": "${SESSION_STATE.value}",
-           |    "methodResponses": [
-           |      [ "Mailbox/changes", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "oldState": "${oldState.getValue}",
-           |        "hasMoreChanges": false,
-           |        "updatedProperties": null,
-           |        "created": ["$mailboxId2"],
-           |        "updated": ["$mailboxId1"],
-           |        "destroyed": []
-           |      }, "c1"],
-           |      ["Mailbox/get", {
-           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |        "notFound": [],
-           |        "list": [
-           |          {
-           |            "id": "$mailboxId1",
-           |            "name": "mailbox1",
-           |            "sortOrder": 1000,
-           |            "totalEmails": 1,
-           |            "unreadEmails": 1,
-           |            "totalThreads": 1,
-           |            "unreadThreads": 1,
-           |            "myRights": {
-           |              "mayReadItems": true,
-           |              "mayAddItems": true,
-           |              "mayRemoveItems": true,
-           |              "maySetSeen": true,
-           |              "maySetKeywords": true,
-           |              "mayCreateChild": true,
-           |              "mayRename": true,
-           |              "mayDelete": true,
-           |              "maySubmit": true
-           |            },
-           |            "isSubscribed": false
-           |          }
-           |        ]
-           |      }, "c2"]
-           |    ]
-           |}""".stripMargin)
+      assertThatJson(response)
+        .whenIgnoringPaths("methodResponses[0][1].newState", "methodResponses[1][1].state")
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "${SESSION_STATE.value}",
+             |    "methodResponses": [
+             |      [ "Mailbox/changes", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "oldState": "${oldState.getValue}",
+             |        "hasMoreChanges": false,
+             |        "updatedProperties": null,
+             |        "created": ["$mailboxId2"],
+             |        "updated": ["$mailboxId1"],
+             |        "destroyed": []
+             |      }, "c1"],
+             |      ["Mailbox/get", {
+             |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+             |        "notFound": [],
+             |        "list": [
+             |          {
+             |            "id": "$mailboxId1",
+             |            "name": "mailbox1",
+             |            "sortOrder": 1000,
+             |            "totalEmails": 1,
+             |            "unreadEmails": 1,
+             |            "totalThreads": 1,
+             |            "unreadThreads": 1,
+             |            "myRights": {
+             |              "mayReadItems": true,
+             |              "mayAddItems": true,
+             |              "mayRemoveItems": true,
+             |              "maySetSeen": true,
+             |              "maySetKeywords": true,
+             |              "mayCreateChild": true,
+             |              "mayRename": true,
+             |              "mayDelete": true,
+             |              "maySubmit": true
+             |            },
+             |            "isSubscribed": false
+             |          }
+             |        ]
+             |      }, "c2"]
+             |    ]
+             |}""".stripMargin)
+    }
   }
 
-  private def storeReferenceState(server: GuiceJamesServer, username: Username): State = {
-    val state: State = stateFactory.generate()
+  private def waitForNextState(server: GuiceJamesServer, accountId: AccountId, initialState: State): State = {
     val jmapGuiceProbe: JmapGuiceProbe = server.getProbe(classOf[JmapGuiceProbe])
+    awaitAtMostTenSeconds.untilAsserted {
+      () => assertThat(jmapGuiceProbe.getLastestState(accountId)).isNotEqualTo(initialState)
+    }
+
+    jmapGuiceProbe.getLastestState(accountId)
+  }
 
-    jmapGuiceProbe.saveMailboxChange(MailboxChange.builder.accountId(AccountId.fromUsername(username)).state(state).date(ZonedDateTime.now()).isCountChange(false).updated(List(TestId.of(0)).asJava).build)
+  private def waitForNextStateWithDelegation(server: GuiceJamesServer, accountId: AccountId, initialState: State): State = {
+    val jmapGuiceProbe: JmapGuiceProbe = server.getProbe(classOf[JmapGuiceProbe])
+    awaitAtMostTenSeconds.untilAsserted{ () => assertThat(jmapGuiceProbe.getLastestStateWithDelegation(accountId)).isNotEqualTo(initialState) }
 
-    state
+    jmapGuiceProbe.getLastestStateWithDelegation(accountId)
   }
 
-  private def provisionSystemMailboxes(server: GuiceJamesServer): Unit = {
+  private def provisionSystemMailboxes(server: GuiceJamesServer): State = {
     val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    val jmapGuiceProbe: JmapGuiceProbe = server.getProbe(classOf[JmapGuiceProbe])
 
     val request =
       s"""{
@@ -1859,5 +1923,42 @@ trait MailboxChangesMethodContract {
     .`then`
       .statusCode(SC_OK)
       .contentType(JSON)
+
+    //Wait until all the system mailboxes are created
+    val request2 =
+      s"""{
+         |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Mailbox/changes",
+         |    {
+         |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "sinceState": "${State.INITIAL.getValue.toString}"
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response1 = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request2)
+      .when
+        .post
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      val createdSize = Json.parse(response1)
+        .\("methodResponses")
+        .\(0).\(1)
+        .\("created")
+        .get.asInstanceOf[JsArray].value.size
+
+      assertThat(createdSize).isEqualTo(5)
+    }
+
+    jmapGuiceProbe.getLastestState(AccountId.fromUsername(BOB))
   }
 }
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryMailboxChangesMethodTest.java b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryMailboxChangesMethodTest.java
index b01641a..df68a52 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryMailboxChangesMethodTest.java
+++ b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryMailboxChangesMethodTest.java
@@ -26,6 +26,8 @@ import org.apache.james.JamesServerBuilder;
 import org.apache.james.JamesServerExtension;
 import org.apache.james.jmap.api.change.State;
 import org.apache.james.jmap.rfc8621.contract.MailboxChangesMethodContract;
+import org.apache.james.mailbox.inmemory.InMemoryId;
+import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.modules.TestJMAPServerModule;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
@@ -42,4 +44,9 @@ public class MemoryMailboxChangesMethodTest implements MailboxChangesMethodContr
     public State.Factory stateFactory() {
         return new State.DefaultFactory();
     }
+
+    @Override
+    public MailboxId generateMailboxId() {
+        return InMemoryId.of(0);
+    }
 }
\ No newline at end of file


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


[james-project] 17/24: JAMES-3202 ReIndexerPerformer in corrective mode: add missing error handling

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 8630f345ccb9ab26e245c0d992c1f67eadfff7a4
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Dec 30 17:01:10 2020 +0700

    JAMES-3202 ReIndexerPerformer in corrective mode: add missing error handling
---
 .../events/ElasticSearchListeningMessageSearchIndex.java          | 1 -
 .../java/org/apache/mailbox/tools/indexer/ReIndexerPerformer.java | 8 ++++++--
 2 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/mailbox/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/events/ElasticSearchListeningMessageSearchIndex.java b/mailbox/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/events/ElasticSearchListeningMessageSearchIndex.java
index f3af769..6554bb6 100644
--- a/mailbox/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/events/ElasticSearchListeningMessageSearchIndex.java
+++ b/mailbox/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/events/ElasticSearchListeningMessageSearchIndex.java
@@ -62,7 +62,6 @@ import org.apache.james.mailbox.store.MailboxSessionMapperFactory;
 import org.apache.james.mailbox.store.mail.model.MailboxMessage;
 import org.apache.james.mailbox.store.search.ListeningMessageSearchIndex;
 import org.elasticsearch.action.get.GetResponse;
-import org.elasticsearch.index.IndexNotFoundException;
 import org.elasticsearch.index.query.TermQueryBuilder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/mailbox/tools/indexer/src/main/java/org/apache/mailbox/tools/indexer/ReIndexerPerformer.java b/mailbox/tools/indexer/src/main/java/org/apache/mailbox/tools/indexer/ReIndexerPerformer.java
index bff8e27..2dd9cde 100644
--- a/mailbox/tools/indexer/src/main/java/org/apache/mailbox/tools/indexer/ReIndexerPerformer.java
+++ b/mailbox/tools/indexer/src/main/java/org/apache/mailbox/tools/indexer/ReIndexerPerformer.java
@@ -327,10 +327,14 @@ public class ReIndexerPerformer {
             .flatMap(message -> isIndexUpToDate(entry.getMailbox(), message)
                 .flatMap(upToDate -> {
                     if (upToDate) {
-                        return Mono.just(Either.right(Result.COMPLETED));
+                        return Mono.just(Either.<Failure, Result>right(Result.COMPLETED));
                     }
                     return correct(entry, message);
-                }));
+                }))
+            .onErrorResume(e -> {
+                LOGGER.warn("ReIndexing failed for {} {}", entry.getMailbox().generateAssociatedPath(), entry.getUid(), e);
+                return Mono.just(Either.left(new MessageFailure(entry.getMailbox().getMailboxId(), entry.getUid())));
+            });
     }
 
     private Mono<Either<Failure, Result>> correct(ReIndexingEntry entry, MailboxMessage message) {


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


[james-project] 16/24: JAMES-3202 ReIndexerPerformer in corrective mode should add missing messages in the index

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit b2c9553b600542e09360e4210f575e284b382c4f
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Dec 30 16:55:52 2020 +0700

    JAMES-3202 ReIndexerPerformer in corrective mode should add missing messages in the index
    
    The messages were skept
---
 .../ElasticSearchListeningMessageSearchIndex.java  |  4 +--
 ...asticSearchListeningMessageSearchIndexTest.java |  6 ++---
 .../mailbox/tools/indexer/ReIndexerPerformer.java  |  3 ++-
 .../mailbox/tools/indexer/ReIndexerImplTest.java   | 31 ++++++++++++++++++++++
 .../james/webadmin/routes/MailboxesRoutesTest.java |  8 +++---
 .../webadmin/routes/UserMailboxesRoutesTest.java   |  4 +--
 6 files changed, 43 insertions(+), 13 deletions(-)

diff --git a/mailbox/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/events/ElasticSearchListeningMessageSearchIndex.java b/mailbox/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/events/ElasticSearchListeningMessageSearchIndex.java
index b6245a8..f3af769 100644
--- a/mailbox/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/events/ElasticSearchListeningMessageSearchIndex.java
+++ b/mailbox/elasticsearch/src/main/java/org/apache/james/mailbox/elasticsearch/events/ElasticSearchListeningMessageSearchIndex.java
@@ -233,9 +233,7 @@ public class ElasticSearchListeningMessageSearchIndex extends ListeningMessageSe
         return elasticSearchIndexer.get(indexIdFor(mailbox.getMailboxId(), uid), routingKey)
             .filter(GetResponse::isExists)
             .map(GetResponse::getSourceAsMap)
-            .map(this::extractFlags)
-            .switchIfEmpty(Mono.error(() -> new IndexNotFoundException(
-                String.format("Index for message %s in mailbox %s not found", uid.toString(), mailbox.getMailboxId().serialize()))));
+            .map(this::extractFlags);
     }
 
     private Flags extractFlags(Map<String, Object> source) {
diff --git a/mailbox/elasticsearch/src/test/java/org/apache/james/mailbox/elasticsearch/events/ElasticSearchListeningMessageSearchIndexTest.java b/mailbox/elasticsearch/src/test/java/org/apache/james/mailbox/elasticsearch/events/ElasticSearchListeningMessageSearchIndexTest.java
index 1c588af..fcfa31e 100644
--- a/mailbox/elasticsearch/src/test/java/org/apache/james/mailbox/elasticsearch/events/ElasticSearchListeningMessageSearchIndexTest.java
+++ b/mailbox/elasticsearch/src/test/java/org/apache/james/mailbox/elasticsearch/events/ElasticSearchListeningMessageSearchIndexTest.java
@@ -466,9 +466,9 @@ class ElasticSearchListeningMessageSearchIndexTest {
         }
 
         @Test
-        void retrieveIndexedFlagsShouldPropagateExceptionWhenNotFound() {
-            assertThatThrownBy(() -> testee.retrieveIndexedFlags(mailbox, MESSAGE_UID_4).block())
-                .isInstanceOf(IndexNotFoundException.class);
+        void retrieveIndexedFlagsShouldReturnEmptyWhenNotFound() {
+            assertThat(testee.retrieveIndexedFlags(mailbox, MESSAGE_UID_4).blockOptional())
+                .isEmpty();
         }
     }
 }
\ No newline at end of file
diff --git a/mailbox/tools/indexer/src/main/java/org/apache/mailbox/tools/indexer/ReIndexerPerformer.java b/mailbox/tools/indexer/src/main/java/org/apache/mailbox/tools/indexer/ReIndexerPerformer.java
index ad2f2f3..bff8e27 100644
--- a/mailbox/tools/indexer/src/main/java/org/apache/mailbox/tools/indexer/ReIndexerPerformer.java
+++ b/mailbox/tools/indexer/src/main/java/org/apache/mailbox/tools/indexer/ReIndexerPerformer.java
@@ -340,7 +340,8 @@ public class ReIndexerPerformer {
 
     private Mono<Boolean> isIndexUpToDate(Mailbox mailbox, MailboxMessage message) {
         return messageSearchIndex.retrieveIndexedFlags(mailbox, message.getUid())
-            .map(flags -> isIndexUpToDate(message, flags));
+            .map(flags -> isIndexUpToDate(message, flags))
+            .switchIfEmpty(Mono.just(false));
     }
 
     private boolean isIndexUpToDate(MailboxMessage message, Flags flags) {
diff --git a/mailbox/tools/indexer/src/test/java/org/apache/mailbox/tools/indexer/ReIndexerImplTest.java b/mailbox/tools/indexer/src/test/java/org/apache/mailbox/tools/indexer/ReIndexerImplTest.java
index 32135ad..a47f208 100644
--- a/mailbox/tools/indexer/src/test/java/org/apache/mailbox/tools/indexer/ReIndexerImplTest.java
+++ b/mailbox/tools/indexer/src/test/java/org/apache/mailbox/tools/indexer/ReIndexerImplTest.java
@@ -242,6 +242,37 @@ public class ReIndexerImplTest {
     }
 
     @Test
+    void deltaReindexShouldUpdateMissingMessages() throws Exception {
+        MailboxSession systemSession = mailboxManager.createSystemSession(USERNAME);
+        MailboxId mailboxId = mailboxManager.createMailbox(INBOX, systemSession).get();
+        ComposedMessageId createdMessage = mailboxManager.getMailbox(INBOX, systemSession)
+            .appendMessage(
+                MessageManager.AppendCommand.builder().build("header: value\r\n\r\nbody"),
+                systemSession).getId();
+        when(messageSearchIndex.retrieveIndexedFlags(any(), any())).thenReturn(Mono.empty());
+        when(messageSearchIndex.delete(any(), any(), any())).thenReturn(Mono.empty());
+
+        reIndexer.reIndex(mailboxId,
+            RunningOptions.builder()
+                .mode(RunningOptions.Mode.FIX_OUTDATED)
+                .build())
+            .run();
+        ArgumentCaptor<MailboxMessage> messageCaptor = ArgumentCaptor.forClass(MailboxMessage.class);
+        ArgumentCaptor<Mailbox> mailboxCaptor = ArgumentCaptor.forClass(Mailbox.class);
+
+        verify(messageSearchIndex).retrieveIndexedFlags(any(), any());
+        verify(messageSearchIndex).delete(any(), any(), any());
+        verify(messageSearchIndex).add(any(MailboxSession.class), mailboxCaptor.capture(), messageCaptor.capture());
+        verifyNoMoreInteractions(messageSearchIndex);
+
+        assertThat(mailboxCaptor.getValue()).satisfies(mailbox -> assertThat(mailbox.getMailboxId()).isEqualTo(mailboxId));
+        assertThat(messageCaptor.getValue()).satisfies(message -> {
+            assertThat(message.getMailboxId()).isEqualTo(mailboxId);
+            assertThat(message.getUid()).isEqualTo(createdMessage.getUid());
+        });
+    }
+
+    @Test
     void mailboxIdReIndexShouldOnlyDropSearchIndexWhenEmptyMailbox() throws Exception {
         MailboxSession systemSession = mailboxManager.createSystemSession(USERNAME);
         MailboxId mailboxId = mailboxManager.createMailbox(INBOX, systemSession).get();
diff --git a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/MailboxesRoutesTest.java b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/MailboxesRoutesTest.java
index 1bbbbdf..ea4afe3 100644
--- a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/MailboxesRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/MailboxesRoutesTest.java
@@ -535,8 +535,8 @@ class MailboxesRoutesTest {
                 .when()
                     .get(taskId + "/await");
 
-                assertThatThrownBy(() -> searchIndex.retrieveIndexedFlags(mailbox, uid).block())
-                    .isInstanceOf(IndexNotFoundException.class);
+                assertThat(searchIndex.retrieveIndexedFlags(mailbox, uid).blockOptional())
+                    .isEmpty();
             }
         }
 
@@ -956,8 +956,8 @@ class MailboxesRoutesTest {
                 .when()
                     .get(taskId + "/await");
 
-                assertThatThrownBy(() -> searchIndex.retrieveIndexedFlags(mailbox, uid).block())
-                    .isInstanceOf(IndexNotFoundException.class);
+                assertThat(searchIndex.retrieveIndexedFlags(mailbox, uid).blockOptional())
+                    .isEmpty();
             }
         }
 
diff --git a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/UserMailboxesRoutesTest.java b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/UserMailboxesRoutesTest.java
index 7724fec..153641d 100644
--- a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/UserMailboxesRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/UserMailboxesRoutesTest.java
@@ -1574,8 +1574,8 @@ class UserMailboxesRoutesTest {
                 .when()
                     .get(taskId + "/await");
 
-                assertThatThrownBy(() -> searchIndex.retrieveIndexedFlags(mailbox, uid).block())
-                    .isInstanceOf(IndexNotFoundException.class);
+                assertThat(searchIndex.retrieveIndexedFlags(mailbox, uid).blockOptional())
+                    .isEmpty();
             }
         }
 


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


[james-project] 15/24: JAMES-3485 MessageAppender should group attachment reads too

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 7a7afdd1278d44d9c45187e5935df7ebb01569fe
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Dec 25 22:49:19 2020 +0700

    JAMES-3485 MessageAppender should group attachment reads too
---
 .../james/jmap/draft/methods/MessageAppender.java  | 28 +++++++++++++++-------
 1 file changed, 19 insertions(+), 9 deletions(-)

diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/MessageAppender.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/MessageAppender.java
index 611f612..81eb6d8 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/MessageAppender.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/MessageAppender.java
@@ -22,6 +22,7 @@ package org.apache.james.jmap.draft.methods;
 import java.io.IOException;
 import java.util.Date;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 
 import javax.inject.Inject;
@@ -39,15 +40,16 @@ import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.MessageIdManager;
 import org.apache.james.mailbox.MessageManager;
 import org.apache.james.mailbox.MessageManager.AppendResult;
-import org.apache.james.mailbox.exception.AttachmentNotFoundException;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.model.AttachmentId;
+import org.apache.james.mailbox.model.AttachmentMetadata;
 import org.apache.james.mailbox.model.Cid;
 import org.apache.james.mailbox.model.ComposedMessageId;
 import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.mailbox.model.MessageAttachmentMetadata;
 import org.apache.james.mime4j.dom.Message;
 import org.apache.james.mime4j.message.DefaultMessageWriter;
+import org.apache.james.util.OptionalUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -147,24 +149,32 @@ public class MessageAppender {
     }
 
     private ImmutableList<MessageAttachmentMetadata> getMessageAttachments(MailboxSession session, ImmutableList<Attachment> attachments) throws MailboxException {
-        ThrowingFunction<Attachment, Optional<MessageAttachmentMetadata>> toMessageAttachment = att -> messageAttachment(session, att);
+        Map<AttachmentId, AttachmentMetadata> attachmentsById = attachmentManager.getAttachments(attachments.stream()
+            .map(attachment -> AttachmentId.from(attachment.getBlobId().getRawValue()))
+            .collect(Guavate.toImmutableList()), session)
+            .stream()
+            .collect(Guavate.toImmutableMap(AttachmentMetadata::getAttachmentId));
+
+        ThrowingFunction<Attachment, Optional<MessageAttachmentMetadata>> toMessageAttachment = att -> messageAttachment(att, attachmentsById);
+
+
         return attachments.stream()
             .map(Throwing.function(toMessageAttachment).sneakyThrow())
             .flatMap(Optional::stream)
             .collect(Guavate.toImmutableList());
     }
 
-    private Optional<MessageAttachmentMetadata> messageAttachment(MailboxSession session, Attachment attachment) throws MailboxException {
+    private Optional<MessageAttachmentMetadata> messageAttachment(Attachment attachment, Map<AttachmentId, AttachmentMetadata> attachmentsById) throws MailboxException {
         try {
-            return Optional.of(MessageAttachmentMetadata.builder()
-                .attachment(attachmentManager.getAttachment(AttachmentId.from(attachment.getBlobId().getRawValue()), session))
+            AttachmentId attachmentId = AttachmentId.from(attachment.getBlobId().getRawValue());
+            return OptionalUtils.executeIfEmpty(Optional.ofNullable(attachmentsById.get(attachmentId))
+                .map(attachmentMetadata -> MessageAttachmentMetadata.builder()
+                    .attachment(attachmentMetadata)
                 .name(attachment.getName().orElse(null))
                 .cid(attachment.getCid().map(Cid::from).orElse(null))
                 .isInline(attachment.isIsInline())
-                .build());
-        } catch (AttachmentNotFoundException e) {
-            LOGGER.error(String.format("Attachment %s not found", attachment.getBlobId()), e);
-            return Optional.empty();
+                .build()),
+                () -> LOGGER.error(String.format("Attachment %s not found", attachment.getBlobId())));
         } catch (IllegalStateException e) {
             LOGGER.error(String.format("Attachment %s is not well-formed", attachment.getBlobId()), e);
             return Optional.empty();


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


[james-project] 04/24: JAMES-3486 Distributed MailboxChangeMethod test

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 6efd140c5d7c1ac346cca060df935a5c3d22fd13
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Mon Dec 28 10:45:34 2020 +0700

    JAMES-3486 Distributed MailboxChangeMethod test
---
 .../DistributedMailboxChangeMethodTest.java        | 67 ++++++++++++++++++++++
 1 file changed, 67 insertions(+)

diff --git a/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedMailboxChangeMethodTest.java b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedMailboxChangeMethodTest.java
new file mode 100644
index 0000000..76d5c5f
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621-integration-tests/distributed-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/distributed/DistributedMailboxChangeMethodTest.java
@@ -0,0 +1,67 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jmap.rfc8621.distributed;
+
+import org.apache.james.CassandraExtension;
+import org.apache.james.CassandraRabbitMQJamesConfiguration;
+import org.apache.james.CassandraRabbitMQJamesServerMain;
+import org.apache.james.DockerElasticSearchExtension;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.jmap.api.change.State;
+import org.apache.james.jmap.cassandra.change.CassandraStateFactory;
+import org.apache.james.jmap.rfc8621.contract.MailboxChangesMethodContract;
+import org.apache.james.mailbox.cassandra.ids.CassandraId;
+import org.apache.james.mailbox.model.MailboxId;
+import org.apache.james.modules.AwsS3BlobStoreExtension;
+import org.apache.james.modules.RabbitMQExtension;
+import org.apache.james.modules.TestJMAPServerModule;
+import org.apache.james.modules.blobstore.BlobStoreConfiguration;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class DistributedMailboxChangeMethodTest implements MailboxChangesMethodContract {
+    @RegisterExtension
+    static JamesServerExtension testExtension = new JamesServerBuilder<CassandraRabbitMQJamesConfiguration>(tmpDir ->
+        CassandraRabbitMQJamesConfiguration.builder()
+            .workingDirectory(tmpDir)
+            .configurationFromClasspath()
+            .blobStore(BlobStoreConfiguration.builder()
+                .s3()
+                .disableCache()
+                .deduplication())
+            .build())
+        .extension(new DockerElasticSearchExtension())
+        .extension(new CassandraExtension())
+        .extension(new RabbitMQExtension())
+        .extension(new AwsS3BlobStoreExtension())
+        .server(configuration -> CassandraRabbitMQJamesServerMain.createServer(configuration)
+            .overrideWith(new TestJMAPServerModule()))
+        .build();
+
+    @Override
+    public State.Factory stateFactory() {
+        return new CassandraStateFactory();
+    }
+
+    @Override
+    public MailboxId generateMailboxId() {
+        return CassandraId.timeBased();
+    }
+}


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


[james-project] 23/24: JAMES-3471 Computing uids and message ids from updated flags is easy

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit ebad11e25667ba12b1cf344417584ea66cc06e99
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Mon Jan 4 11:43:00 2021 +0700

    JAMES-3471 Computing uids and message ids from updated flags is easy
    
    Thus we don't need to add additional fields to manage them in FlagsUpdated.
---
 .../james/mailbox/events/MailboxListener.java      | 23 ++++++++--------------
 1 file changed, 8 insertions(+), 15 deletions(-)

diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/events/MailboxListener.java b/mailbox/api/src/main/java/org/apache/james/mailbox/events/MailboxListener.java
index 3517801..11bb4e6 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/events/MailboxListener.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/events/MailboxListener.java
@@ -587,31 +587,26 @@ public interface MailboxListener {
      * A mailbox event related to updated flags
      */
     class FlagsUpdated extends MessageEvent {
-        private final List<MessageUid> uids;
-        private final List<MessageId> messageIds;
         private final List<UpdatedFlags> updatedFlags;
 
         public FlagsUpdated(MailboxSession.SessionId sessionId, Username username, MailboxPath path,
                             MailboxId mailboxId, List<UpdatedFlags> updatedFlags, EventId eventId) {
             super(sessionId, username, path, mailboxId, eventId);
             this.updatedFlags = ImmutableList.copyOf(updatedFlags);
-            this.uids = updatedFlags.stream()
-                .map(UpdatedFlags::getUid)
-                .collect(Guavate.toImmutableList());
-            this.messageIds = updatedFlags.stream()
-                .map(UpdatedFlags::getMessageId)
-                .filter(Optional::isPresent)
-                .map(Optional::get)
-                .collect(Guavate.toImmutableList());
         }
 
         @Override
         public Collection<MessageUid> getUids() {
-            return uids;
+            return updatedFlags.stream()
+                .map(UpdatedFlags::getUid)
+                .collect(Guavate.toImmutableList());
         }
 
         public Collection<MessageId> getMessageIds() {
-            return messageIds;
+            return updatedFlags.stream()
+                .map(UpdatedFlags::getMessageId)
+                .flatMap(Optional::stream)
+                .collect(Guavate.toImmutableList());
         }
 
         public List<UpdatedFlags> getUpdatedFlags() {
@@ -633,8 +628,6 @@ public interface MailboxListener {
                     && Objects.equals(this.username, that.username)
                     && Objects.equals(this.path, that.path)
                     && Objects.equals(this.mailboxId, that.mailboxId)
-                    && Objects.equals(this.uids, that.uids)
-                    && Objects.equals(this.messageIds, that.messageIds)
                     && Objects.equals(this.updatedFlags, that.updatedFlags);
             }
             return false;
@@ -642,7 +635,7 @@ public interface MailboxListener {
 
         @Override
         public final int hashCode() {
-            return Objects.hash(eventId, sessionId, username, path, mailboxId, uids, messageIds, updatedFlags);
+            return Objects.hash(eventId, sessionId, username, path, mailboxId, updatedFlags);
         }
     }
 


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