You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2020/12/18 07:27:28 UTC

[james-project] branch master updated (61bc266 -> 94c1847)

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

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


    from 61bc266  JAMES-1644 AccessTokenAuthenticationStrategy should read data only once
     new dfe87ce  JAMES-3431 Matchers for working with DSN parameters
     new 8420b5b  JAMES-3463 MemoryMailboxChangeRepository State.INITIAL handling
     new 33a65de  JAMES-3463 MailboxChangeRepository::getLatestState
     new df4bd62  JAMES-3463 MailboxChangeRepositoryContract: add missing .block() calls
     new 11bfe3e  JAMES-3463 Mailbox/get should read last state from MailboxChangesRepository
     new 8f581e9  JAMES-3463 Remove LTT.RS trick upon Mailbox/changes
     new 65f0156  JAMES-3435 Make message read isolation configurable
     new c7786ff  JAMES 3400 Add mailbox create command
     new afa7e12  JAMES 3400 Add mailbox exist command
     new bae0c0b  JAMES 3400 Add mailbox list command
     new b8b5eb4  JAMES 3400 Add mailbox delete command
     new 4110679  JAMES 3400 Add mailbox deleteAll command
     new 94c1847  JAMES-3400 Update Webadmin-cli README.md following separation of reindex from mailbox commands group

The 13 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:
 .../init/configuration/CassandraConfiguration.java |  31 ++-
 .../pages/distributed/configure/cassandra.adoc     |   6 +
 .../mailbox/cassandra/DeleteMessageListener.java   |   4 +-
 .../cassandra/mail/CassandraMessageIdMapper.java   |  22 +-
 .../mail/CassandraMessageIdToImapUidDAO.java       |  30 +-
 .../cassandra/mail/CassandraMessageMapper.java     |   4 +-
 .../mail/task/RecomputeMailboxCountersService.java |   4 +-
 .../task/SolveMessageInconsistenciesService.java   |   7 +-
 .../change/CassandraMailboxChangeRepository.java   |   5 +
 .../james/jmap/api/change/MailboxChange.java       |   1 +
 .../jmap/api/change/MailboxChangeRepository.java   |   2 +
 .../change/MemoryMailboxChangeRepository.java      |  15 +-
 .../change/MailboxChangeRepositoryContract.java    | 129 ++++++---
 .../transport/matchers/DSNDelayRequested.java      |  98 +++++++
 .../transport/matchers/DSNFailureRequested.java    | 102 +++++++
 .../transport/matchers/DSNSuccessRequested.java    |  98 +++++++
 .../transport/matchers/DSNDelayRequestedTest.java  | 261 +++++++++++++++++
 .../matchers/DSNFailureRequestedTest.java          | 259 +++++++++++++++++
 .../matchers/DSNSuccessRequestedTest.java          | 259 +++++++++++++++++
 .../rfc8621/contract/BackReferenceContract.scala   |  25 +-
 .../contract/MailboxGetMethodContract.scala        | 115 ++++++--
 .../contract/MailboxSetMethodContract.scala        | 143 ++++++----
 .../scala/org/apache/james/jmap/core/Session.scala |   7 +-
 .../james/jmap/method/MailboxChangesMethod.scala   |  47 +---
 .../james/jmap/method/MailboxGetMethod.scala       |  12 +-
 server/protocols/webadmin-cli/README.md            |  20 --
 .../java/org/apache/james/cli/WebAdminCli.java     |   2 +
 .../MailboxCommand.java}                           |  25 +-
 .../MailboxCreateCommand.java}                     |  33 ++-
 .../MailboxDeleteAllCommand.java}                  |  28 +-
 .../MailboxDeleteCommand.java}                     |  33 ++-
 .../MailboxExistCommand.java}                      |  33 ++-
 .../MailboxListCommand.java}                       |  37 ++-
 .../{DomainClient.java => MailboxClient.java}      |  23 +-
 .../model/{UserName.java => MailboxName.java}      |  13 +-
 .../org/apache/james/cli/MailboxManageTest.java    | 310 +++++++++++++++++++++
 src/site/xdoc/server/config-cassandra.xml          |   7 +
 37 files changed, 1960 insertions(+), 290 deletions(-)
 create mode 100644 server/mailet/mailets/src/main/java/org/apache/james/transport/matchers/DSNDelayRequested.java
 create mode 100644 server/mailet/mailets/src/main/java/org/apache/james/transport/matchers/DSNFailureRequested.java
 create mode 100644 server/mailet/mailets/src/main/java/org/apache/james/transport/matchers/DSNSuccessRequested.java
 create mode 100644 server/mailet/mailets/src/test/java/org/apache/james/transport/matchers/DSNDelayRequestedTest.java
 create mode 100644 server/mailet/mailets/src/test/java/org/apache/james/transport/matchers/DSNFailureRequestedTest.java
 create mode 100644 server/mailet/mailets/src/test/java/org/apache/james/transport/matchers/DSNSuccessRequestedTest.java
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/{user/UserCommand.java => mailbox/MailboxCommand.java} (72%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/{domain/DomainCreateCommand.java => mailbox/MailboxCreateCommand.java} (60%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/{user/UserDeleteCommand.java => mailbox/MailboxDeleteAllCommand.java} (66%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/{user/UserDeleteCommand.java => mailbox/MailboxDeleteCommand.java} (60%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/{domain/DomainExistCommand.java => mailbox/MailboxExistCommand.java} (64%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/{domain/DomainDeleteCommand.java => mailbox/MailboxListCommand.java} (56%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/{DomainClient.java => MailboxClient.java} (59%)
 copy server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/model/{UserName.java => MailboxName.java} (85%)
 create mode 100644 server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/MailboxManageTest.java


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


[james-project] 02/13: JAMES-3463 MemoryMailboxChangeRepository State.INITIAL handling

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

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

commit 8420b5b8a879bab189db8bb4f0b8fc3d70558e94
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Dec 15 10:26:34 2020 +0700

    JAMES-3463 MemoryMailboxChangeRepository State.INITIAL handling
    
    When used we should return all changes from the start
---
 .../james/jmap/api/change/MailboxChange.java       |  1 +
 .../change/MemoryMailboxChangeRepository.java      |  7 ++++-
 .../change/MailboxChangeRepositoryContract.java    | 36 ++++++++++++++++++++++
 .../scala/org/apache/james/jmap/core/Session.scala |  7 +++--
 4 files changed, 48 insertions(+), 3 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 2ed3c64..52461e6 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,6 +44,7 @@ import com.google.common.collect.ImmutableList;
 public class MailboxChange {
 
     public static class State {
+        public static State INITIAL = of(UUID.fromString("2c9f1b12-b35a-43e6-9af2-0106fb53a943"));
 
         public static State of(UUID value) {
             return new State(value);
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 e9a2a79..0ff0fa5 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
@@ -39,8 +39,8 @@ import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
 public class MemoryMailboxChangeRepository implements MailboxChangeRepository {
-
     public static final Limit DEFAULT_NUMBER_OF_CHANGES = Limit.of(5);
+
     private final Multimap<AccountId, MailboxChange> mailboxChangeMap;
 
     public MemoryMailboxChangeRepository() {
@@ -60,6 +60,11 @@ public class MemoryMailboxChangeRepository implements MailboxChangeRepository {
         Preconditions.checkNotNull(accountId);
         Preconditions.checkNotNull(state);
         maxChanges.ifPresent(limit -> Preconditions.checkArgument(limit.getValue() > 0, "maxChanges must be a positive integer"));
+        if (state.equals(State.INITIAL)) {
+            return Flux.fromIterable(mailboxChangeMap.get(accountId))
+                .sort(Comparator.comparing(MailboxChange::getDate))
+                .collect(new MailboxChangeCollector(state, maxChanges.orElse(DEFAULT_NUMBER_OF_CHANGES)));
+        }
 
         return findByState(accountId, state)
             .flatMapMany(currentState -> Flux.fromIterable(mailboxChangeMap.get(accountId))
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 2f3209b..75f10af 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
@@ -128,6 +128,42 @@ public interface MailboxChangeRepositoryContract {
     }
 
     @Test
+    default void getChangesShouldReturnAllFromInitial() {
+        MailboxChangeRepository repository = mailboxChangeRepository();
+
+        MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(3), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(2), ImmutableList.of(TestId.of(2)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(3)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change3 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(4)), ImmutableList.of(), ImmutableList.of());
+        repository.save(oldState);
+        repository.save(change1);
+        repository.save(change2);
+        repository.save(change3);
+
+        assertThat(repository.getSinceState(ACCOUNT_ID, State.INITIAL, Optional.of(Limit.of(3))).block().getCreated())
+            .containsExactlyInAnyOrder(TestId.of(1), TestId.of(2), TestId.of(3));
+    }
+
+    @Test
+    default void getChangesFromInitialShouldReturnNewState() {
+        MailboxChangeRepository repository = mailboxChangeRepository();
+
+        MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(3), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(2), ImmutableList.of(TestId.of(2)), ImmutableList.of(), ImmutableList.of());
+        State state2 = State.of(UUID.randomUUID());
+        MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, state2, DATE.minusHours(1), ImmutableList.of(TestId.of(3)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change3 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(4)), ImmutableList.of(), ImmutableList.of());
+        repository.save(oldState);
+        repository.save(change1);
+        repository.save(change2);
+        repository.save(change3);
+
+
+        assertThat(repository.getSinceState(ACCOUNT_ID, State.INITIAL, Optional.of(Limit.of(3))).block().getNewState())
+            .isEqualTo(state2);
+    }
+
+    @Test
     default void getChangesShouldLimitChangesWhenMaxChangesOmitted() {
         MailboxChangeRepository repository = mailboxChangeRepository();
 
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Session.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Session.scala
index 123aa3d..6949488 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Session.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/Session.scala
@@ -30,6 +30,7 @@ import eu.timepit.refined.refineV
 import eu.timepit.refined.string.Uuid
 import org.apache.james.core.Username
 import org.apache.james.jmap.api.change.MailboxChanges
+import org.apache.james.jmap.api.change.MailboxChange.{State => JavaState}
 import org.apache.james.jmap.core.CapabilityIdentifier.CapabilityIdentifier
 import org.apache.james.jmap.core.Id.Id
 import org.apache.james.jmap.core.State.INSTANCE
@@ -79,11 +80,13 @@ final case class Account private(accountId: AccountId,
 object State {
   type UUIDString = String Refined Uuid
 
-  val INSTANCE: State = fromString("2c9f1b12-b35a-43e6-9af2-0106fb53a943")
+  val INSTANCE: State = fromJava(JavaState.INITIAL)
 
   def fromString(value: UUIDString): State = State(UUID.fromString(value.value))
 
-  def fromMailboxChanges(mailboxChanges: MailboxChanges): State = State(mailboxChanges.getNewState.getValue)
+  def fromMailboxChanges(mailboxChanges: MailboxChanges): State = fromJava(mailboxChanges.getNewState)
+
+  def fromJava(javaState: JavaState): State = State(javaState.getValue)
 }
 
 case class State(value: UUID)


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


[james-project] 12/13: JAMES 3400 Add mailbox deleteAll command

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

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

commit 4110679db85dabaa581b142b492fb3219cb06ae9
Author: quanth <hq...@linagora.com>
AuthorDate: Fri Dec 11 16:42:22 2020 +0700

    JAMES 3400 Add mailbox deleteAll command
---
 .../apache/james/cli/mailbox/MailboxCommand.java   |  3 +-
 ...oxCommand.java => MailboxDeleteAllCommand.java} | 53 ++++++++++++----------
 .../org/apache/james/httpclient/MailboxClient.java |  3 ++
 .../org/apache/james/cli/MailboxManageTest.java    | 40 ++++++++++++++++
 4 files changed, 73 insertions(+), 26 deletions(-)

diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java
index 801ce38..5599365 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java
@@ -34,7 +34,8 @@ import picocli.CommandLine;
         MailboxCreateCommand.class,
         MailboxExistCommand.class,
         MailboxListCommand.class,
-        MailboxDeleteCommand.class
+        MailboxDeleteCommand.class,
+        MailboxDeleteAllCommand.class
     })
 public class MailboxCommand implements Callable<Integer> {
 
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxDeleteAllCommand.java
similarity index 56%
copy from server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java
copy to server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxDeleteAllCommand.java
index 801ce38..200e573 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxDeleteAllCommand.java
@@ -19,42 +19,45 @@
 
 package org.apache.james.cli.mailbox;
 
-import java.io.PrintStream;
 import java.util.concurrent.Callable;
 
 import org.apache.james.cli.WebAdminCli;
 import org.apache.james.httpclient.MailboxClient;
 
+import feign.Response;
 import picocli.CommandLine;
 
 @CommandLine.Command(
-    name = "mailbox",
-    description = "Manage Mailboxes",
-    subcommands = {
-        MailboxCreateCommand.class,
-        MailboxExistCommand.class,
-        MailboxListCommand.class,
-        MailboxDeleteCommand.class
-    })
-public class MailboxCommand implements Callable<Integer> {
-
-    protected final WebAdminCli webAdminCli;
-    protected final PrintStream out;
-    protected final PrintStream err;
-
-    public MailboxCommand(PrintStream out, WebAdminCli webAdminCli, PrintStream err) {
-        this.out = out;
-        this.webAdminCli = webAdminCli;
-        this.err = err;
-    }
+        name = "deleteAll",
+        description = "Delete all mailboxes of a user")
+public class MailboxDeleteAllCommand implements Callable<Integer> {
+
+    public static final int DELETED_CODE = 204;
+    public static final int NOT_FOUND_CODE = 404;
+
+    @CommandLine.ParentCommand MailboxCommand mailboxCommand;
+
+    @CommandLine.Parameters(description = "Username")
+    String userName;
 
     @Override
     public Integer call() {
-        return WebAdminCli.CLI_FINISHED_SUCCEED;
+        try {
+            MailboxClient mailboxClient = mailboxCommand.fullyQualifiedURL("/users");
+            Response rs = mailboxClient.deleteAllMailboxes(userName);
+            if (rs.status() == DELETED_CODE) {
+                mailboxCommand.out.println("The user do not have mailboxes anymore.");
+                return WebAdminCli.CLI_FINISHED_SUCCEED;
+            } else if (rs.status() == NOT_FOUND_CODE) {
+                mailboxCommand.err.println("The user name does not exist.");
+                return WebAdminCli.CLI_FINISHED_FAILED;
+            }
+            return WebAdminCli.CLI_FINISHED_FAILED;
+        } catch (Exception e) {
+            e.printStackTrace(mailboxCommand.err);
+            return WebAdminCli.CLI_FINISHED_FAILED;
+        }
     }
 
-    public MailboxClient fullyQualifiedURL(String partOfUrl) {
-        return webAdminCli.feignClientFactory(err).target(MailboxClient.class, webAdminCli.jamesUrl + partOfUrl);
-    }
+}
 
-}
\ No newline at end of file
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/MailboxClient.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/MailboxClient.java
index f43d303..3bf78cd 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/MailboxClient.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/MailboxClient.java
@@ -37,4 +37,7 @@ public interface MailboxClient {
     @RequestLine("DELETE /{usernameToBeUsed}/mailboxes/{mailboxNameToBeDeleted}")
     Response deleteAMailbox(@Param("usernameToBeUsed") String userName, @Param("mailboxNameToBeDeleted") String mailboxName);
 
+    @RequestLine("DELETE /{usernameToBeUsed}/mailboxes")
+    Response deleteAllMailboxes(@Param("usernameToBeUsed") String userName);
+
 }
diff --git a/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/MailboxManageTest.java b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/MailboxManageTest.java
index 7a4b9d8..1ee0305 100644
--- a/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/MailboxManageTest.java
+++ b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/MailboxManageTest.java
@@ -33,6 +33,7 @@ import org.apache.james.util.Port;
 import org.apache.james.utils.DataProbeImpl;
 import org.apache.james.utils.WebAdminGuiceProbe;
 import org.apache.james.webadmin.integration.WebadminIntegrationTestModule;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
@@ -49,6 +50,13 @@ public class MailboxManageTest {
     private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream();
     private final ByteArrayOutputStream errorStreamCaptor = new ByteArrayOutputStream();
     private DataProbeImpl dataProbe;
+    private Port port;
+
+    @BeforeEach
+    void setUp(GuiceJamesServer server) {
+        port = server.getProbe(WebAdminGuiceProbe.class).getWebAdminPort();
+        dataProbe = server.getProbe(DataProbeImpl.class);
+    }
 
     @Test
     void mailboxCreateWithExistedUsernameAndValidMailboxNameShouldSucceed() throws Exception {
@@ -265,4 +273,36 @@ public class MailboxManageTest {
         assertThat(errorStreamCaptor.toString()).contains("Attempt to delete an invalid mailbox");
     }
 
+    @Test
+    void mailboxDeleteAllWithExistingUserShouldDeleteAllMailboxes() throws Exception {
+        dataProbe.fluent().addDomain("linagora.com")
+                .addUser("hqtran@linagora.com", "123456");
+
+        WebAdminCli.executeFluent(new PrintStream(new ByteArrayOutputStream()), new PrintStream(new ByteArrayOutputStream()),
+                "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "create", "hqtran@linagora.com", "INBOX.1");
+
+        WebAdminCli.executeFluent(new PrintStream(new ByteArrayOutputStream()), new PrintStream(new ByteArrayOutputStream()),
+                "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "create", "hqtran@linagora.com", "DRAFT");
+
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+                "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "deleteAll", "hqtran@linagora.com");
+
+        WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+                "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "list", "hqtran@linagora.com");
+
+        assertThat(exitCode).isEqualTo(0);
+        assertThat(outputStreamCaptor.toString().trim()).isEqualTo("The user do not have mailboxes anymore.");
+    }
+
+    @Test
+    void mailboxDeleteAllWithNonExistingUsernameShouldFail() throws Exception {
+        dataProbe.fluent().addDomain("linagora.com");
+
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+                "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "deleteAll", "hqtran@linagora.com");
+
+        assertThat(exitCode).isEqualTo(1);
+        assertThat(errorStreamCaptor.toString()).contains("The user name does not exist.");
+    }
+
 }
\ 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] 09/13: JAMES 3400 Add mailbox exist command

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

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

commit afa7e121ffc7f1db1e24b1055628cecf8e6594f2
Author: quanth <hq...@linagora.com>
AuthorDate: Mon Nov 30 13:45:11 2020 +0700

    JAMES 3400 Add mailbox exist command
---
 .../apache/james/cli/mailbox/MailboxCommand.java   |  3 +-
 .../james/cli/mailbox/MailboxCreateCommand.java    |  2 +-
 ...CreateCommand.java => MailboxExistCommand.java} | 34 +++++----
 .../org/apache/james/httpclient/MailboxClient.java |  3 +
 .../org/apache/james/cli/MailboxManageTest.java    | 85 +++++++++++++++++++++-
 5 files changed, 108 insertions(+), 19 deletions(-)

diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java
index 908e7dc..6494db2 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java
@@ -31,7 +31,8 @@ import picocli.CommandLine;
     name = "mailbox",
     description = "Manage Mailboxes",
     subcommands = {
-        MailboxCreateCommand.class
+        MailboxCreateCommand.class,
+        MailboxExistCommand.class
     })
 public class MailboxCommand implements Callable<Integer> {
 
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCreateCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCreateCommand.java
index 46a7806..950880d 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCreateCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCreateCommand.java
@@ -50,7 +50,7 @@ public class MailboxCreateCommand implements Callable<Integer> {
             MailboxClient mailboxClient = mailboxCommand.fullyQualifiedURL("/users");
             Response rs = mailboxClient.createAMailbox(userName, mailboxName);
             if (rs.status() == CREATED_CODE) {
-                mailboxCommand.out.println("The mailbox was created successfully.");
+                mailboxCommand.out.println("The mailbox now exists on the server.");
                 return WebAdminCli.CLI_FINISHED_SUCCEED;
             } else if (rs.status() == BAD_REQUEST_CODE) {
                 mailboxCommand.err.println(rs.body());
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCreateCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxExistCommand.java
similarity index 71%
copy from server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCreateCommand.java
copy to server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxExistCommand.java
index 46a7806..bc46953 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCreateCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxExistCommand.java
@@ -28,36 +28,36 @@ import feign.Response;
 import picocli.CommandLine;
 
 @CommandLine.Command(
-    name = "create",
-    description = "Create a new mailbox")
-public class MailboxCreateCommand implements Callable<Integer> {
+    name = "exist",
+    description = "Check if a mailbox exists")
+public class MailboxExistCommand implements Callable<Integer> {
 
-    public static final int CREATED_CODE = 204;
-    public static final int BAD_REQUEST_CODE = 400;
-    public static final int USERNAME_NOT_EXIST_CODE = 404;
+    public static final int EXISTED_CODE = 204;
+    public static final int INVALID_MAILBOX_NAME_CODE = 400;
+    public static final int NOT_EXISTED_CODE = 404;
 
     @CommandLine.ParentCommand MailboxCommand mailboxCommand;
 
-    @CommandLine.Parameters(description = "Username")
+    @CommandLine.Parameters(description = "Username to be checked")
     String userName;
 
-    @CommandLine.Parameters(description = "Mailbox's name to be created")
+    @CommandLine.Parameters(description = "Mailbox's name to be tested existence")
     String mailboxName;
 
     @Override
     public Integer call() {
         try {
             MailboxClient mailboxClient = mailboxCommand.fullyQualifiedURL("/users");
-            Response rs = mailboxClient.createAMailbox(userName, mailboxName);
-            if (rs.status() == CREATED_CODE) {
-                mailboxCommand.out.println("The mailbox was created successfully.");
+            Response rs = mailboxClient.doesExist(userName, mailboxName);
+            if (rs.status() == EXISTED_CODE) {
+                mailboxCommand.out.println("The mailbox exists.");
                 return WebAdminCli.CLI_FINISHED_SUCCEED;
-            } else if (rs.status() == BAD_REQUEST_CODE) {
-                mailboxCommand.err.println(rs.body());
-                return WebAdminCli.CLI_FINISHED_FAILED;
-            } else if (rs.status() == USERNAME_NOT_EXIST_CODE) {
+            } else if (rs.status() == INVALID_MAILBOX_NAME_CODE) {
                 mailboxCommand.err.println(rs.body());
                 return WebAdminCli.CLI_FINISHED_FAILED;
+            } else if (rs.status() == NOT_EXISTED_CODE) {
+                mailboxCommand.out.println("Either the user name or the mailbox does not exist.");
+                return WebAdminCli.CLI_FINISHED_SUCCEED;
             }
             return WebAdminCli.CLI_FINISHED_FAILED;
         } catch (Exception e) {
@@ -66,4 +66,6 @@ public class MailboxCreateCommand implements Callable<Integer> {
         }
     }
 
-}
\ No newline at end of file
+}
+
+
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/MailboxClient.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/MailboxClient.java
index ab781dc..0e77cf2 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/MailboxClient.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/MailboxClient.java
@@ -28,4 +28,7 @@ public interface MailboxClient {
     @RequestLine("PUT /{userNameToBeUsed}/mailboxes/{mailboxNameToBeCreated}")
     Response createAMailbox(@Param("userNameToBeUsed") String userName, @Param("mailboxNameToBeCreated") String mailboxName);
 
+    @RequestLine("GET /{usernameToBeUsed}/mailboxes/{mailboxNameToBeTested}")
+    Response doesExist(@Param("usernameToBeUsed") String userName, @Param("mailboxNameToBeTested") String mailboxName);
+
 }
diff --git a/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/MailboxManageTest.java b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/MailboxManageTest.java
index 8452d0f..4e59b84 100644
--- a/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/MailboxManageTest.java
+++ b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/MailboxManageTest.java
@@ -62,7 +62,7 @@ public class MailboxManageTest {
             "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "exist", "hqtran@linagora.com", "INBOX");
 
         assertThat(exitCode).isEqualTo(0);
-        assertThat(outputStreamCaptor.toString().trim()).isEqualTo("The mailbox was created successfully.\n" +
+        assertThat(outputStreamCaptor.toString().trim()).isEqualTo("The mailbox now exists on the server.\n" +
             "The mailbox exists.");
     }
 
@@ -87,4 +87,87 @@ public class MailboxManageTest {
         assertThat(errorStreamCaptor.toString()).contains("404");
     }
 
+    @Test
+    void mailboxCreateWithAlreadyExistingMailboxShouldSucceed() throws Exception {
+        dataProbe.fluent().addDomain("linagora.com")
+            .addUser("hqtran@linagora.com", "123456");
+
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(new ByteArrayOutputStream()), new PrintStream(new ByteArrayOutputStream()),
+            "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "create", "hqtran@linagora.com", "INBOX");
+
+        int exitCode2 = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "create", "hqtran@linagora.com", "INBOX");
+
+        assertThat(exitCode).isEqualTo(0);
+        assertThat(exitCode2).isEqualTo(0);
+        assertThat(outputStreamCaptor.toString().trim()).isEqualTo("The mailbox now exists on the server.");
+    }
+
+    @Test
+    void mailboxCreateSubMailboxesShouldSucceed() throws Exception {
+        dataProbe.fluent().addDomain("linagora.com")
+            .addUser("hqtran@linagora.com", "123456");
+
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(new ByteArrayOutputStream()), new PrintStream(new ByteArrayOutputStream()),
+            "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "create", "hqtran@linagora.com", "INBOX.1");
+
+        int exitCode2 = WebAdminCli.executeFluent(new PrintStream(new ByteArrayOutputStream()), new PrintStream(new ByteArrayOutputStream()),
+            "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "create", "hqtran@linagora.com", "INBOX.2");
+
+        WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "list", "hqtran@linagora.com");
+
+        assertThat(exitCode).isEqualTo(0);
+        assertThat(exitCode2).isEqualTo(0);
+        assertThat(outputStreamCaptor.toString()).isEqualTo("INBOX\nINBOX.1\nINBOX.2\n");
+    }
+
+    @Test
+    void mailboxExistWithExistedUsernameAndExistedMailboxNameShouldSucceed() throws Exception {
+        dataProbe.fluent().addDomain("linagora.com")
+            .addUser("hqtran@linagora.com", "123456");
+
+        WebAdminCli.executeFluent(new PrintStream(new ByteArrayOutputStream()), new PrintStream(new ByteArrayOutputStream()),
+            "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "create", "hqtran@linagora.com", "INBOX");
+
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "exist", "hqtran@linagora.com", "INBOX");
+
+        assertThat(exitCode).isEqualTo(0);
+        assertThat(outputStreamCaptor.toString().trim()).isEqualTo("The mailbox exists.");
+    }
+
+    @Test
+    void mailboxExistWithInvalidMailboxNameShouldFail() throws Exception {
+        dataProbe.fluent().addDomain("linagora.com")
+            .addUser("hqtran@linagora.com", "123456");
+
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "exist", "hqtran@linagora.com", "#INBOX");
+
+        assertThat(exitCode).isEqualTo(1);
+        assertThat(errorStreamCaptor.toString()).contains("400");
+    }
+
+    @Test
+    void mailboxExistWithExistedUserAndNonExistingMailboxNameShouldFail() throws Exception {
+        dataProbe.fluent().addDomain("linagora.com")
+            .addUser("hqtran@linagora.com", "123456");
+
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "exist", "hqtran@linagora.com", "INBOX");
+
+        assertThat(exitCode).isEqualTo(0);
+        assertThat(outputStreamCaptor.toString().trim()).isEqualTo("Either the user name or the mailbox does not exist.");
+    }
+
+    @Test
+    void mailboxExistWithNonExistingUserAndNonExistingMailboxNameShouldFail() {
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "exist", "hqtran@linagora.com", "INBOX");
+
+        assertThat(exitCode).isEqualTo(0);
+        assertThat(outputStreamCaptor.toString().trim()).isEqualTo("Either the user name or the mailbox does not exist.");
+    }
+
 }
\ 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] 06/13: JAMES-3463 Remove LTT.RS trick upon Mailbox/changes

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

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

commit 8f581e95450d75bd58754d5de08f0956871d10d3
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Dec 15 12:39:58 2020 +0700

    JAMES-3463 Remove LTT.RS trick upon Mailbox/changes
    
    Mailbox/get now returns a state that can be piped to
    Mailbox/changes
---
 .../james/jmap/method/MailboxChangesMethod.scala   | 47 +++++++---------------
 1 file changed, 14 insertions(+), 33 deletions(-)

diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxChangesMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxChangesMethod.scala
index 438704e..8daac10 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxChangesMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxChangesMethod.scala
@@ -46,41 +46,22 @@ class MailboxChangesMethod @Inject()(mailboxSerializer: MailboxSerializer,
   override val requiredCapabilities: Set[CapabilityIdentifier] = Set(JMAP_MAIL)
 
   override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: MailboxChangesRequest): SMono[InvocationWithContext] =
-
-    // Support for LTT.RS. This should be removed as soon as Mailbox/get returns the correct state
-    if (request.sinceState.equals(State.INSTANCE)) {
-      val response: MailboxChangesResponse = MailboxChangesResponse(
+    SMono.fromPublisher(mailboxChangeRepository.getSinceState(JavaAccountId.fromUsername(mailboxSession.getUser), JavaState.of(request.sinceState.value), request.maxChanged.toJava))
+      .map(mailboxChanges => MailboxChangesResponse(
         accountId = request.accountId,
-        oldState = State.INSTANCE,
-        newState = State.INSTANCE,
-        hasMoreChanges = HasMoreChanges(false),
+        oldState = request.sinceState,
+        newState = State.fromMailboxChanges(mailboxChanges),
+        hasMoreChanges = HasMoreChanges.fromMailboxChanges(mailboxChanges),
         updatedProperties = Some(Properties()),
-        created = Set(),
-        updated = Set(),
-        destroyed = Set())
-      SMono.just(InvocationWithContext(invocation = Invocation(
-        methodName = methodName,
-        arguments = Arguments(mailboxSerializer.serializeChanges(response)),
-        methodCallId = invocation.invocation.methodCallId
-      ), processingContext = invocation.processingContext))
-    } else {
-      SMono.fromPublisher(mailboxChangeRepository.getSinceState(JavaAccountId.fromUsername(mailboxSession.getUser), JavaState.of(request.sinceState.value), request.maxChanged.toJava))
-        .map(mailboxChanges => MailboxChangesResponse(
-          accountId = request.accountId,
-          oldState = request.sinceState,
-          newState = State.fromMailboxChanges(mailboxChanges),
-          hasMoreChanges = HasMoreChanges.fromMailboxChanges(mailboxChanges),
-          updatedProperties = Some(Properties()),
-          created = mailboxChanges.getCreated.asScala.toSet,
-          updated = mailboxChanges.getUpdated.asScala.toSet,
-          destroyed = mailboxChanges.getDestroyed.asScala.toSet))
-        .map(response => InvocationWithContext(
-          invocation = Invocation(
-            methodName = methodName,
-            arguments = Arguments(mailboxSerializer.serializeChanges(response)),
-            methodCallId = invocation.invocation.methodCallId),
-          processingContext = invocation.processingContext))
-    }
+        created = mailboxChanges.getCreated.asScala.toSet,
+        updated = mailboxChanges.getUpdated.asScala.toSet,
+        destroyed = mailboxChanges.getDestroyed.asScala.toSet))
+      .map(response => InvocationWithContext(
+        invocation = Invocation(
+          methodName = methodName,
+          arguments = Arguments(mailboxSerializer.serializeChanges(response)),
+          methodCallId = invocation.invocation.methodCallId),
+        processingContext = invocation.processingContext))
 
   override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[IllegalArgumentException, MailboxChangesRequest] =
     mailboxSerializer.deserializeMailboxChangesRequest(invocation.arguments.value) match {


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


[james-project] 10/13: JAMES 3400 Add mailbox list command

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

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

commit bae0c0b640e74f40fcc92d42c45696bff7a0746c
Author: quanth <hq...@linagora.com>
AuthorDate: Mon Nov 30 15:57:59 2020 +0700

    JAMES 3400 Add mailbox list command
---
 .../apache/james/cli/mailbox/MailboxCommand.java   |  3 +-
 ...MailboxCommand.java => MailboxListCommand.java} | 55 +++++++++++++---------
 .../org/apache/james/httpclient/MailboxClient.java |  3 ++
 .../{MailboxClient.java => model/MailboxName.java} | 20 ++++----
 .../org/apache/james/cli/MailboxManageTest.java    | 39 +++++++++++++++
 5 files changed, 87 insertions(+), 33 deletions(-)

diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java
index 6494db2..f4cbb1d 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java
@@ -32,7 +32,8 @@ import picocli.CommandLine;
     description = "Manage Mailboxes",
     subcommands = {
         MailboxCreateCommand.class,
-        MailboxExistCommand.class
+        MailboxExistCommand.class,
+        MailboxListCommand.class
     })
 public class MailboxCommand implements Callable<Integer> {
 
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxListCommand.java
similarity index 50%
copy from server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java
copy to server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxListCommand.java
index 6494db2..3924656 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxListCommand.java
@@ -19,40 +19,49 @@
 
 package org.apache.james.cli.mailbox;
 
-import java.io.PrintStream;
+import java.util.List;
 import java.util.concurrent.Callable;
 
 import org.apache.james.cli.WebAdminCli;
 import org.apache.james.httpclient.MailboxClient;
+import org.apache.james.httpclient.model.MailboxName;
 
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import feign.Response;
 import picocli.CommandLine;
 
 @CommandLine.Command(
-    name = "mailbox",
-    description = "Manage Mailboxes",
-    subcommands = {
-        MailboxCreateCommand.class,
-        MailboxExistCommand.class
-    })
-public class MailboxCommand implements Callable<Integer> {
-
-    protected final WebAdminCli webAdminCli;
-    protected final PrintStream out;
-    protected final PrintStream err;
-
-    public MailboxCommand(PrintStream out, WebAdminCli webAdminCli, PrintStream err) {
-        this.out = out;
-        this.webAdminCli = webAdminCli;
-        this.err = err;
-    }
+    name = "list",
+    description = "Show all mailboxes of a user")
+public class MailboxListCommand implements Callable<Integer> {
+
+    public static int SUCCEED_CODE = 200;
+    public static int USERNAME_NOT_FOUND_CODE = 404;
+
+    @CommandLine.ParentCommand MailboxCommand mailboxCommand;
+
+    @CommandLine.Parameters(description = "Username to be used")
+    String userName;
 
     @Override
     public Integer call() {
-        return WebAdminCli.CLI_FINISHED_SUCCEED;
-    }
-
-    public MailboxClient fullyQualifiedURL(String partOfUrl) {
-        return webAdminCli.feignClientFactory(err).target(MailboxClient.class, webAdminCli.jamesUrl + partOfUrl);
+        try {
+            MailboxClient mailboxClient = mailboxCommand.fullyQualifiedURL("/users");
+            Response rs = mailboxClient.getMailboxList(userName);
+            if (rs.status() == SUCCEED_CODE) {
+                List<MailboxName> mailboxNameList = new ObjectMapper().readValue(String.valueOf(rs.body()), new TypeReference<List<MailboxName>>(){});
+                mailboxNameList.forEach(mailboxName -> mailboxCommand.out.println(mailboxName.getMailboxName()));
+                return WebAdminCli.CLI_FINISHED_SUCCEED;
+            } else if (rs.status() == USERNAME_NOT_FOUND_CODE) {
+                return WebAdminCli.CLI_FINISHED_FAILED;
+            }
+            return WebAdminCli.CLI_FINISHED_FAILED;
+        } catch (Exception e) {
+            e.printStackTrace(mailboxCommand.err);
+            return WebAdminCli.CLI_FINISHED_FAILED;
+        }
     }
 
 }
\ No newline at end of file
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/MailboxClient.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/MailboxClient.java
index 0e77cf2..0fb0e35 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/MailboxClient.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/MailboxClient.java
@@ -31,4 +31,7 @@ public interface MailboxClient {
     @RequestLine("GET /{usernameToBeUsed}/mailboxes/{mailboxNameToBeTested}")
     Response doesExist(@Param("usernameToBeUsed") String userName, @Param("mailboxNameToBeTested") String mailboxName);
 
+    @RequestLine("GET /{usernameToBeUsed}/mailboxes")
+    Response getMailboxList(@Param("usernameToBeUsed") String userName);
+
 }
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/MailboxClient.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/model/MailboxName.java
similarity index 69%
copy from server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/MailboxClient.java
copy to server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/model/MailboxName.java
index 0e77cf2..bc3ce7e 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/MailboxClient.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/model/MailboxName.java
@@ -17,18 +17,20 @@
  * under the License.                                             *
  ******************************************************************/
 
-package org.apache.james.httpclient;
+package org.apache.james.httpclient.model;
 
-import feign.Param;
-import feign.RequestLine;
-import feign.Response;
+import com.fasterxml.jackson.annotation.JsonProperty;
 
-public interface MailboxClient {
+public class MailboxName {
 
-    @RequestLine("PUT /{userNameToBeUsed}/mailboxes/{mailboxNameToBeCreated}")
-    Response createAMailbox(@Param("userNameToBeUsed") String userName, @Param("mailboxNameToBeCreated") String mailboxName);
+    @JsonProperty("mailboxName")
+    private String mailboxName;
 
-    @RequestLine("GET /{usernameToBeUsed}/mailboxes/{mailboxNameToBeTested}")
-    Response doesExist(@Param("usernameToBeUsed") String userName, @Param("mailboxNameToBeTested") String mailboxName);
+    @JsonProperty("mailboxId")
+    private String mailboxId;
+
+    public String getMailboxName() {
+        return mailboxName;
+    }
 
 }
diff --git a/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/MailboxManageTest.java b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/MailboxManageTest.java
index 4e59b84..2af4cbd 100644
--- a/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/MailboxManageTest.java
+++ b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/MailboxManageTest.java
@@ -170,4 +170,43 @@ public class MailboxManageTest {
         assertThat(outputStreamCaptor.toString().trim()).isEqualTo("Either the user name or the mailbox does not exist.");
     }
 
+    @Test
+    void mailboxListWithTwoAddedMailboxesAndExistedUsernameShouldShowMailboxesNameExactly() throws Exception {
+        dataProbe.fluent().addDomain("linagora.com")
+            .addUser("hqtran@linagora.com", "123456");
+
+        WebAdminCli.executeFluent(new PrintStream(new ByteArrayOutputStream()), new PrintStream(new ByteArrayOutputStream()),
+            "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "create", "hqtran@linagora.com", "INBOX1");
+
+        WebAdminCli.executeFluent(new PrintStream(new ByteArrayOutputStream()), new PrintStream(new ByteArrayOutputStream()),
+            "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "create", "hqtran@linagora.com", "INBOX2");
+
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "list", "hqtran@linagora.com");
+
+        assertThat(exitCode).isEqualTo(0);
+        assertThat(outputStreamCaptor.toString()).isEqualTo("INBOX1\nINBOX2\n");
+    }
+
+    @Test
+    void mailboxListWithAValidUserAndNonExistingMailboxesShouldShowNothing() throws Exception {
+        dataProbe.fluent().addDomain("linagora.com")
+            .addUser("hqtran@linagora.com", "123456");
+
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "list", "hqtran@linagora.com");
+
+        assertThat(exitCode).isEqualTo(0);
+        assertThat(outputStreamCaptor.toString()).isEqualTo("");
+    }
+
+    @Test
+    void mailboxListWithNonExistingUsernameShouldFail() {
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "list", "hqtran@linagora.com");
+
+        assertThat(exitCode).isEqualTo(1);
+        assertThat(outputStreamCaptor.toString()).isEmpty();
+    }
+
 }
\ 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] 01/13: JAMES-3431 Matchers for working with DSN parameters

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

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

commit dfe87cef3ffe7661fe1656fe11af15c3adaae86d
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Dec 16 15:07:25 2020 +0700

    JAMES-3431 Matchers for working with DSN parameters
    
    They allow knowing when DSN notifications should be sent.
    
    This includes:
     - DSNSuccessRequested
     - DSNFailureRequested
     - DSNDelayRequested
---
 .../transport/matchers/DSNDelayRequested.java      |  98 ++++++++
 .../transport/matchers/DSNFailureRequested.java    | 102 ++++++++
 .../transport/matchers/DSNSuccessRequested.java    |  98 ++++++++
 .../transport/matchers/DSNDelayRequestedTest.java  | 261 +++++++++++++++++++++
 .../matchers/DSNFailureRequestedTest.java          | 259 ++++++++++++++++++++
 .../matchers/DSNSuccessRequestedTest.java          | 259 ++++++++++++++++++++
 6 files changed, 1077 insertions(+)

diff --git a/server/mailet/mailets/src/main/java/org/apache/james/transport/matchers/DSNDelayRequested.java b/server/mailet/mailets/src/main/java/org/apache/james/transport/matchers/DSNDelayRequested.java
new file mode 100644
index 0000000..bee7072
--- /dev/null
+++ b/server/mailet/mailets/src/main/java/org/apache/james/transport/matchers/DSNDelayRequested.java
@@ -0,0 +1,98 @@
+/****************************************************************
+ * 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.transport.matchers;
+
+import static org.apache.mailet.DsnParameters.Notify.DELAY;
+
+import java.util.Collection;
+import java.util.Optional;
+
+import javax.mail.MessagingException;
+
+import org.apache.james.core.MailAddress;
+import org.apache.mailet.DsnParameters;
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.GenericMatcher;
+
+import com.github.steveash.guavate.Guavate;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+
+/**
+ * Returns the list of Recipients for which DSN DELAY notifications
+ * should be generated.
+ *
+ * This include only by default recipients explicitly positioning DELAY value as part of the NOTIFY RCPT parameter.
+ *
+ * Example:
+ *
+ * <pre>
+ * &lt;mailet match="DSNDelayRequested" class="XXX"/&gt;
+ * </pre>
+ *
+ * This can be configured to also include recipients not having specified NOTIFY RCPT parameters.
+ *
+ * Example:
+ *
+ * <pre>
+ * &lt;mailet match="DSNDelayRequested=shouldMatchByDefault" class="XXX"/&gt;
+ * </pre>
+ */
+public class DSNDelayRequested extends GenericMatcher {
+    private static final boolean DEFAULT_PRESENT = true;
+    public static final String CONDITION = "shouldMatchByDefault";
+
+    private boolean shouldBeMatchedByDefault;
+
+    @Override
+    public void init() throws MessagingException {
+        Preconditions.checkState(Strings.isNullOrEmpty(getCondition()) || CONDITION.equals(getCondition()),
+            "DSNSuccessRequested condition, when specified, should be '%s'", CONDITION);
+        shouldBeMatchedByDefault = Optional.ofNullable(getCondition())
+            .map(CONDITION::equals)
+            .orElse(!DEFAULT_PRESENT);
+    }
+
+    @Override
+    public Collection<MailAddress> match(Mail mail) {
+        return mail.getRecipients().stream()
+            .filter(recipient -> delayRequested(mail, recipient))
+            .collect(Guavate.toImmutableList());
+    }
+
+    private Boolean delayRequested(Mail mail, MailAddress recipient) {
+        return mail.dsnParameters()
+            .map(dsnParameters -> delayRequested(recipient, dsnParameters))
+            .orElse(shouldBeMatchedByDefault);
+    }
+
+    private boolean delayRequested(MailAddress recipient, DsnParameters dsnParameters) {
+        return Optional.ofNullable(dsnParameters.getRcptParameters().get(recipient))
+            .map(rcptParams -> rcptParams.getNotifyParameter()
+                .map(notifies -> notifies.contains(DELAY))
+                .orElse(shouldBeMatchedByDefault))
+            .orElse(shouldBeMatchedByDefault);
+    }
+
+    @Override
+    public String getMatcherName() {
+        return "DSNDelayRequested";
+    }
+}
diff --git a/server/mailet/mailets/src/main/java/org/apache/james/transport/matchers/DSNFailureRequested.java b/server/mailet/mailets/src/main/java/org/apache/james/transport/matchers/DSNFailureRequested.java
new file mode 100644
index 0000000..36ed8fb
--- /dev/null
+++ b/server/mailet/mailets/src/main/java/org/apache/james/transport/matchers/DSNFailureRequested.java
@@ -0,0 +1,102 @@
+/****************************************************************
+ * 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.transport.matchers;
+
+import static org.apache.mailet.DsnParameters.Notify.FAILURE;
+
+import java.util.Collection;
+import java.util.Optional;
+
+import javax.mail.MessagingException;
+
+import org.apache.james.core.MailAddress;
+import org.apache.james.util.FunctionalUtils;
+import org.apache.mailet.DsnParameters;
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.GenericMatcher;
+
+import com.github.steveash.guavate.Guavate;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+
+/**
+ * Returns the list of Recipients for which DSN FAILURE notifications
+ * should be generated.
+ *
+ * This include only by default recipients explicitly positioning FAILURE value as part of the NOTIFY RCPT parameter,
+ * as well as those not having specified NOTIFY RCPT parameters.
+ *
+ * Example:
+ *
+ * <pre>
+ * &lt;mailet match="DSNFailureRequested" class="XXX"/&gt;
+ * </pre>
+ *
+ * This can be configured to not include recipients not having specified NOTIFY RCPT parameters.
+ *
+ * Example:
+ *
+ * <pre>
+ * &lt;mailet match="DSNFailureRequested=shouldNotMatchByDefault" class="XXX"/&gt;
+ * </pre>
+ */
+public class DSNFailureRequested extends GenericMatcher {
+    private static final boolean DEFAULT_PRESENT = true;
+    public static final String CONDITION = "shouldNotMatchByDefault";
+
+    private boolean shouldBeMatchedByDefault;
+
+    @Override
+    public void init() throws MessagingException {
+        Preconditions.checkState(Strings.isNullOrEmpty(getCondition()) || CONDITION.equals(getCondition()),
+            "DSNFailureRequested condition, when specified, should be '%s'", CONDITION);
+        shouldBeMatchedByDefault = Optional.ofNullable(getCondition())
+            .map(CONDITION::equals)
+            .map(FunctionalUtils.negate())
+            .orElse(DEFAULT_PRESENT);
+    }
+
+    @Override
+    public Collection<MailAddress> match(Mail mail) {
+        return mail.getRecipients().stream()
+            .filter(recipient -> failureRequested(mail, recipient))
+            .collect(Guavate.toImmutableList());
+    }
+
+    private Boolean failureRequested(Mail mail, MailAddress recipient) {
+        return mail.dsnParameters()
+            .map(dsnParameters -> failureRequested(recipient, dsnParameters))
+            .orElse(shouldBeMatchedByDefault);
+    }
+
+    private boolean failureRequested(MailAddress recipient, DsnParameters dsnParameters) {
+        return Optional.ofNullable(dsnParameters.getRcptParameters().get(recipient))
+            .map(rcptParams -> rcptParams.getNotifyParameter()
+                .map(notifies -> notifies.contains(FAILURE))
+                .orElse(shouldBeMatchedByDefault))
+            .orElse(shouldBeMatchedByDefault);
+    }
+
+    @Override
+    public String getMatcherName() {
+        return "DSNFailureRequested";
+    }
+
+}
diff --git a/server/mailet/mailets/src/main/java/org/apache/james/transport/matchers/DSNSuccessRequested.java b/server/mailet/mailets/src/main/java/org/apache/james/transport/matchers/DSNSuccessRequested.java
new file mode 100644
index 0000000..04d1e1e
--- /dev/null
+++ b/server/mailet/mailets/src/main/java/org/apache/james/transport/matchers/DSNSuccessRequested.java
@@ -0,0 +1,98 @@
+/****************************************************************
+ * 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.transport.matchers;
+
+import static org.apache.mailet.DsnParameters.Notify.SUCCESS;
+
+import java.util.Collection;
+import java.util.Optional;
+
+import javax.mail.MessagingException;
+
+import org.apache.james.core.MailAddress;
+import org.apache.mailet.DsnParameters;
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.GenericMatcher;
+
+import com.github.steveash.guavate.Guavate;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+
+/**
+ * Returns the list of Recipients for which DSN SUCCESS notifications
+ * should be generated.
+ *
+ * This include only by default recipients explicitly positioning SUCCESS value as part of the NOTIFY RCPT parameter.
+ *
+ * Example:
+ *
+ * <pre>
+ * &lt;mailet match="DSNSuccessRequested" class="XXX"/&gt;
+ * </pre>
+ *
+ * This can be configured to also include recipients not having specified NOTIFY RCPT parameters.
+ *
+ * Example:
+ *
+ * <pre>
+ * &lt;mailet match="DSNSuccessRequested=shouldMatchByDefault" class="XXX"/&gt;
+ * </pre>
+ */
+public class DSNSuccessRequested extends GenericMatcher {
+    private static final boolean DEFAULT_PRESENT = true;
+    public static final String CONDITION = "shouldMatchByDefault";
+
+    private boolean shouldBeMatchedByDefault;
+
+    @Override
+    public void init() throws MessagingException {
+        Preconditions.checkState(Strings.isNullOrEmpty(getCondition()) || CONDITION.equals(getCondition()),
+            "DSNSuccessRequested condition, when specified, should be '%s'", CONDITION);
+        shouldBeMatchedByDefault = Optional.ofNullable(getCondition())
+            .map(CONDITION::equals)
+            .orElse(!DEFAULT_PRESENT);
+    }
+
+    @Override
+    public Collection<MailAddress> match(Mail mail) {
+        return mail.getRecipients().stream()
+            .filter(recipient -> successRequested(mail, recipient))
+            .collect(Guavate.toImmutableList());
+    }
+
+    private Boolean successRequested(Mail mail, MailAddress recipient) {
+        return mail.dsnParameters()
+            .map(dsnParameters -> successRequested(recipient, dsnParameters))
+            .orElse(shouldBeMatchedByDefault);
+    }
+
+    private boolean successRequested(MailAddress recipient, DsnParameters dsnParameters) {
+        return Optional.ofNullable(dsnParameters.getRcptParameters().get(recipient))
+            .map(rcptParams -> rcptParams.getNotifyParameter()
+                .map(notifies -> notifies.contains(SUCCESS))
+                .orElse(shouldBeMatchedByDefault))
+            .orElse(shouldBeMatchedByDefault);
+    }
+
+    @Override
+    public String getMatcherName() {
+        return "DSNSuccessRequested";
+    }
+}
diff --git a/server/mailet/mailets/src/test/java/org/apache/james/transport/matchers/DSNDelayRequestedTest.java b/server/mailet/mailets/src/test/java/org/apache/james/transport/matchers/DSNDelayRequestedTest.java
new file mode 100644
index 0000000..7a6f714
--- /dev/null
+++ b/server/mailet/mailets/src/test/java/org/apache/james/transport/matchers/DSNDelayRequestedTest.java
@@ -0,0 +1,261 @@
+/****************************************************************
+ * 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.transport.matchers;
+
+import static org.apache.mailet.DsnParameters.Notify.DELAY;
+import static org.apache.mailet.DsnParameters.Notify.FAILURE;
+import static org.apache.mailet.DsnParameters.Notify.NEVER;
+import static org.apache.mailet.DsnParameters.Notify.SUCCESS;
+import static org.apache.mailet.base.MailAddressFixture.RECIPIENT1;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.EnumSet;
+
+import org.apache.james.server.core.MailImpl;
+import org.apache.mailet.DsnParameters;
+import org.apache.mailet.base.MailAddressFixture;
+import org.apache.mailet.base.test.FakeMatcherConfig;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+class DSNDelayRequestedTest {
+    @Nested
+    class Default {
+        DSNDelayRequested testee;
+
+        @BeforeEach
+        void setUp() throws Exception {
+            testee = new DSNDelayRequested();
+            testee.init(FakeMatcherConfig.builder()
+                .matcherName(testee.getMatcherName())
+                .build());
+        }
+
+        @Test
+        void shouldReturnEmptyWhenNoDsnParameters() {
+            assertThat(
+                testee.match(MailImpl.builder()
+                    .name("mail")
+                    .addRecipient(RECIPIENT1)
+                    .build()))
+                .isEmpty();
+        }
+
+        @Test
+        void shouldReturnEmptyWhenNoDsnRcptParameters() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .envId(DsnParameters.EnvId.of("39"))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .isEmpty();
+        }
+
+        @Test
+        void shouldReturnEmptyWhenNoDsnNotifyParameters() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .addRcptParameter(RECIPIENT1, DsnParameters.RecipientDsnParameters.of(RECIPIENT1))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .isEmpty();
+        }
+
+        @Test
+        void shouldReturnEmptyWhenNeverNotifyParameter() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .addRcptParameter(RECIPIENT1, DsnParameters.RecipientDsnParameters.of(EnumSet.of(NEVER)))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .isEmpty();
+        }
+
+        @Test
+        void shouldReturnEmptyWhenNotDelayNotifyParameter() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .addRcptParameter(RECIPIENT1, DsnParameters.RecipientDsnParameters.of(EnumSet.of(SUCCESS, FAILURE)))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .isEmpty();
+        }
+
+        @Test
+        void shouldReturnCollectionWhenDelayNotifyParameter() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .addRcptParameter(RECIPIENT1, DsnParameters.RecipientDsnParameters.of(EnumSet.of(DELAY)))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .containsOnly(RECIPIENT1);
+        }
+    }
+
+    @Nested
+    class ShouldMatchByDefault {
+        DSNDelayRequested testee;
+
+        @BeforeEach
+        void setUp() throws Exception {
+            testee = new DSNDelayRequested();
+            testee.init(FakeMatcherConfig.builder()
+                .matcherName(testee.getMatcherName())
+                .condition("shouldMatchByDefault")
+                .build());
+        }
+
+        @Test
+        void shouldReturnCollectionWhenNoDsnParameters() {
+            assertThat(
+                testee.match(MailImpl.builder()
+                    .name("mail")
+                    .addRecipient(RECIPIENT1)
+                    .build()))
+                .containsOnly(RECIPIENT1);
+        }
+
+        @Test
+        void shouldReturnCollectionWhenNoDsnRcptParameters() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .envId(DsnParameters.EnvId.of("39"))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .containsOnly(RECIPIENT1);
+        }
+
+        @Test
+        void shouldReturnCollectionWhenNoDsnNotifyParameters() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .addRcptParameter(RECIPIENT1, DsnParameters.RecipientDsnParameters.of(RECIPIENT1))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .containsOnly(RECIPIENT1);
+        }
+
+        @Test
+        void shouldReturnEmptyWhenNeverNotifyParameter() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .addRcptParameter(RECIPIENT1, DsnParameters.RecipientDsnParameters.of(EnumSet.of(NEVER)))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .isEmpty();
+        }
+
+        @Test
+        void shouldReturnEmptyWhenNotDelayNotifyParameter() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .addRcptParameter(RECIPIENT1, DsnParameters.RecipientDsnParameters.of(EnumSet.of(SUCCESS, FAILURE)))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .isEmpty();
+        }
+
+        @Test
+        void shouldReturnCollectionWhenDelayNotifyParameter() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .addRcptParameter(RECIPIENT1, DsnParameters.RecipientDsnParameters.of(EnumSet.of(DELAY)))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .containsOnly(RECIPIENT1);
+        }
+    }
+
+    @Nested
+    class Configuration {
+        @Test
+        void shouldThrowOnInvalidValue() {
+            assertThatThrownBy(() -> new DSNDelayRequested()
+                .init(FakeMatcherConfig.builder()
+                    .matcherName("any")
+                    .condition("bad")
+                    .build()))
+                .isInstanceOf(IllegalStateException.class);
+        }
+
+        @Test
+        void shouldAcceptMatchByDefault() {
+            assertThatCode(() -> new DSNDelayRequested()
+                .init(FakeMatcherConfig.builder()
+                    .matcherName("any")
+                    .condition("shouldMatchByDefault")
+                    .build()))
+                .doesNotThrowAnyException();
+        }
+
+        @Test
+        void shouldAcceptNoCondition() {
+            assertThatCode(() -> new DSNDelayRequested()
+                .init(FakeMatcherConfig.builder()
+                    .matcherName("any")
+                    .build()))
+                .doesNotThrowAnyException();
+        }
+    }
+}
\ No newline at end of file
diff --git a/server/mailet/mailets/src/test/java/org/apache/james/transport/matchers/DSNFailureRequestedTest.java b/server/mailet/mailets/src/test/java/org/apache/james/transport/matchers/DSNFailureRequestedTest.java
new file mode 100644
index 0000000..1bf8249
--- /dev/null
+++ b/server/mailet/mailets/src/test/java/org/apache/james/transport/matchers/DSNFailureRequestedTest.java
@@ -0,0 +1,259 @@
+/****************************************************************
+ * 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.transport.matchers;
+
+import static org.apache.mailet.DsnParameters.Notify.DELAY;
+import static org.apache.mailet.DsnParameters.Notify.FAILURE;
+import static org.apache.mailet.DsnParameters.Notify.NEVER;
+import static org.apache.mailet.DsnParameters.Notify.SUCCESS;
+import static org.apache.mailet.base.MailAddressFixture.RECIPIENT1;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.util.EnumSet;
+
+import org.apache.james.server.core.MailImpl;
+import org.apache.mailet.DsnParameters;
+import org.apache.mailet.base.test.FakeMatcherConfig;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+class DSNFailureRequestedTest {
+    @Nested
+    class Default {
+        DSNFailureRequested testee;
+
+        @BeforeEach
+        void setUp() throws Exception {
+            testee = new DSNFailureRequested();
+            testee.init(FakeMatcherConfig.builder()
+                .matcherName(testee.getMatcherName())
+                .build());
+        }
+
+        @Test
+        void shouldReturnCollectionWhenNoDsnParameters() {
+            assertThat(
+                testee.match(MailImpl.builder()
+                    .name("mail")
+                    .addRecipient(RECIPIENT1)
+                    .build()))
+                .containsOnly(RECIPIENT1);
+        }
+
+        @Test
+        void shouldReturnCollectionWhenNoDsnRcptParameters() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .envId(DsnParameters.EnvId.of("39"))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .containsOnly(RECIPIENT1);
+        }
+
+        @Test
+        void shouldReturnCollectionWhenNoDsnNotifyParameters() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .addRcptParameter(RECIPIENT1, DsnParameters.RecipientDsnParameters.of(RECIPIENT1))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .containsOnly(RECIPIENT1);
+        }
+
+        @Test
+        void shouldReturnEmptyWhenNeverNotifyParameter() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .addRcptParameter(RECIPIENT1, DsnParameters.RecipientDsnParameters.of(EnumSet.of(NEVER)))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .isEmpty();
+        }
+
+        @Test
+        void shouldReturnEmptyWhenNotFailureNotifyParameter() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .addRcptParameter(RECIPIENT1, DsnParameters.RecipientDsnParameters.of(EnumSet.of(DELAY, SUCCESS)))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .isEmpty();
+        }
+
+        @Test
+        void shouldReturnCollectionWhenFailureNotifyParameter() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .addRcptParameter(RECIPIENT1, DsnParameters.RecipientDsnParameters.of(EnumSet.of(FAILURE)))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .containsOnly(RECIPIENT1);
+        }
+    }
+
+    @Nested
+    class ShouldNotMatchByDefault {
+        DSNFailureRequested testee;
+
+        @BeforeEach
+        void setUp() throws Exception {
+            testee = new DSNFailureRequested();
+            testee.init(FakeMatcherConfig.builder()
+                .matcherName(testee.getMatcherName())
+                .condition("shouldNotMatchByDefault")
+                .build());
+        }
+
+        @Test
+        void shouldReturnEmptyWhenNoDsnParameters() {
+            assertThat(
+                testee.match(MailImpl.builder()
+                    .name("mail")
+                    .addRecipient(RECIPIENT1)
+                    .build()))
+                .isEmpty();
+        }
+
+        @Test
+        void shouldReturnEmptyWhenNoDsnRcptParameters() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .envId(DsnParameters.EnvId.of("39"))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .isEmpty();
+        }
+
+        @Test
+        void shouldReturnEmptyWhenNoDsnNotifyParameters() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .addRcptParameter(RECIPIENT1, DsnParameters.RecipientDsnParameters.of(RECIPIENT1))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .isEmpty();
+        }
+
+        @Test
+        void shouldReturnEmptyWhenNeverNotifyParameter() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .addRcptParameter(RECIPIENT1, DsnParameters.RecipientDsnParameters.of(EnumSet.of(NEVER)))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .isEmpty();
+        }
+
+        @Test
+        void shouldReturnEmptyWhenNotFailureNotifyParameter() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .addRcptParameter(RECIPIENT1, DsnParameters.RecipientDsnParameters.of(EnumSet.of(DELAY, SUCCESS)))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .isEmpty();
+        }
+
+        @Test
+        void shouldReturnCollectionWhenFailureNotifyParameter() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .addRcptParameter(RECIPIENT1, DsnParameters.RecipientDsnParameters.of(EnumSet.of(FAILURE)))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .containsOnly(RECIPIENT1);
+        }
+    }
+
+    @Nested
+    class Configuration {
+        @Test
+        void shouldThrowOnInvalidValue() {
+            assertThatThrownBy(() -> new DSNFailureRequested()
+                .init(FakeMatcherConfig.builder()
+                    .matcherName("any")
+                    .condition("bad")
+                    .build()))
+                .isInstanceOf(IllegalStateException.class);
+        }
+
+        @Test
+        void shouldAcceptNotMatchByDefault() {
+            assertThatCode(() -> new DSNFailureRequested()
+                .init(FakeMatcherConfig.builder()
+                    .matcherName("any")
+                    .condition("shouldNotMatchByDefault")
+                    .build()))
+                .doesNotThrowAnyException();
+        }
+
+        @Test
+        void shouldAcceptNoCondition() {
+            assertThatCode(() -> new DSNFailureRequested()
+                .init(FakeMatcherConfig.builder()
+                    .matcherName("any")
+                    .build()))
+                .doesNotThrowAnyException();
+        }
+    }
+}
\ No newline at end of file
diff --git a/server/mailet/mailets/src/test/java/org/apache/james/transport/matchers/DSNSuccessRequestedTest.java b/server/mailet/mailets/src/test/java/org/apache/james/transport/matchers/DSNSuccessRequestedTest.java
new file mode 100644
index 0000000..4de2d49
--- /dev/null
+++ b/server/mailet/mailets/src/test/java/org/apache/james/transport/matchers/DSNSuccessRequestedTest.java
@@ -0,0 +1,259 @@
+/****************************************************************
+ * 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.transport.matchers;
+
+import static org.apache.mailet.DsnParameters.Notify.DELAY;
+import static org.apache.mailet.DsnParameters.Notify.FAILURE;
+import static org.apache.mailet.DsnParameters.Notify.NEVER;
+import static org.apache.mailet.DsnParameters.Notify.SUCCESS;
+import static org.apache.mailet.base.MailAddressFixture.RECIPIENT1;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.util.EnumSet;
+
+import org.apache.james.server.core.MailImpl;
+import org.apache.mailet.DsnParameters;
+import org.apache.mailet.base.test.FakeMatcherConfig;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+class DSNSuccessRequestedTest {
+    @Nested
+    class Default {
+        DSNSuccessRequested testee;
+
+        @BeforeEach
+        void setUp() throws Exception {
+            testee = new DSNSuccessRequested();
+            testee.init(FakeMatcherConfig.builder()
+                .matcherName(testee.getMatcherName())
+                .build());
+        }
+
+        @Test
+        void shouldReturnEmptyWhenNoDsnParameters() {
+            assertThat(
+                testee.match(MailImpl.builder()
+                    .name("mail")
+                    .addRecipient(RECIPIENT1)
+                    .build()))
+                .isEmpty();
+        }
+
+        @Test
+        void shouldReturnEmptyWhenNoDsnRcptParameters() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .envId(DsnParameters.EnvId.of("39"))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .isEmpty();
+        }
+
+        @Test
+        void shouldReturnEmptyWhenNoDsnNotifyParameters() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .addRcptParameter(RECIPIENT1, DsnParameters.RecipientDsnParameters.of(RECIPIENT1))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .isEmpty();
+        }
+
+        @Test
+        void shouldReturnEmptyWhenNeverNotifyParameter() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .addRcptParameter(RECIPIENT1, DsnParameters.RecipientDsnParameters.of(EnumSet.of(NEVER)))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .isEmpty();
+        }
+
+        @Test
+        void shouldReturnEmptyWhenNotSuccessNotifyParameter() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .addRcptParameter(RECIPIENT1, DsnParameters.RecipientDsnParameters.of(EnumSet.of(DELAY, FAILURE)))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .isEmpty();
+        }
+
+        @Test
+        void shouldReturnCollectionWhenSuccessNotifyParameter() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .addRcptParameter(RECIPIENT1, DsnParameters.RecipientDsnParameters.of(EnumSet.of(SUCCESS)))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .containsOnly(RECIPIENT1);
+        }
+    }
+
+    @Nested
+    class ShouldMatchByDefault {
+        DSNSuccessRequested testee;
+
+        @BeforeEach
+        void setUp() throws Exception {
+            testee = new DSNSuccessRequested();
+            testee.init(FakeMatcherConfig.builder()
+                .matcherName(testee.getMatcherName())
+                .condition("shouldMatchByDefault")
+                .build());
+        }
+
+        @Test
+        void shouldReturnCollectionWhenNoDsnParameters() {
+            assertThat(
+                testee.match(MailImpl.builder()
+                    .name("mail")
+                    .addRecipient(RECIPIENT1)
+                    .build()))
+                .containsOnly(RECIPIENT1);
+        }
+
+        @Test
+        void shouldReturnCollectionWhenNoDsnRcptParameters() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .envId(DsnParameters.EnvId.of("39"))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .containsOnly(RECIPIENT1);
+        }
+
+        @Test
+        void shouldReturnCollectionWhenNoDsnNotifyParameters() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .addRcptParameter(RECIPIENT1, DsnParameters.RecipientDsnParameters.of(RECIPIENT1))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .containsOnly(RECIPIENT1);
+        }
+
+        @Test
+        void shouldReturnEmptyWhenNeverNotifyParameter() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .addRcptParameter(RECIPIENT1, DsnParameters.RecipientDsnParameters.of(EnumSet.of(NEVER)))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .isEmpty();
+        }
+
+        @Test
+        void shouldReturnEmptyWhenNotSuccessNotifyParameter() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .addRcptParameter(RECIPIENT1, DsnParameters.RecipientDsnParameters.of(EnumSet.of(DELAY, FAILURE)))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .isEmpty();
+        }
+
+        @Test
+        void shouldReturnCollectionWhenSuccessNotifyParameter() {
+            MailImpl mail = MailImpl.builder()
+                .name("mail")
+                .addRecipient(RECIPIENT1)
+                .build();
+            mail.setDsnParameters(DsnParameters.builder()
+                .addRcptParameter(RECIPIENT1, DsnParameters.RecipientDsnParameters.of(EnumSet.of(SUCCESS)))
+                .build().get());
+
+            assertThat(testee.match(mail))
+                .containsOnly(RECIPIENT1);
+        }
+    }
+
+    @Nested
+    class Configuration {
+        @Test
+        void shouldThrowOnInvalidValue() {
+            assertThatThrownBy(() -> new DSNSuccessRequested()
+                .init(FakeMatcherConfig.builder()
+                    .matcherName("any")
+                    .condition("bad")
+                    .build()))
+                .isInstanceOf(IllegalStateException.class);
+        }
+
+        @Test
+        void shouldAcceptMatchByDefault() {
+            assertThatCode(() -> new DSNSuccessRequested()
+                .init(FakeMatcherConfig.builder()
+                    .matcherName("any")
+                    .condition("shouldMatchByDefault")
+                    .build()))
+                .doesNotThrowAnyException();
+        }
+
+        @Test
+        void shouldAcceptNoCondition() {
+            assertThatCode(() -> new DSNSuccessRequested()
+                .init(FakeMatcherConfig.builder()
+                    .matcherName("any")
+                    .build()))
+                .doesNotThrowAnyException();
+        }
+    }
+}
\ 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] 11/13: JAMES 3400 Add mailbox delete command

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

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

commit b8b5eb49dd90231f72dc74618c513f21324f4a54
Author: quanth <hq...@linagora.com>
AuthorDate: Mon Nov 30 18:09:41 2020 +0700

    JAMES 3400 Add mailbox delete command
---
 .../apache/james/cli/mailbox/MailboxCommand.java   |  3 +-
 ...ilboxCommand.java => MailboxDeleteCommand.java} | 61 +++++++++++++---------
 .../org/apache/james/httpclient/MailboxClient.java |  3 ++
 .../org/apache/james/cli/MailboxManageTest.java    | 56 ++++++++++++++++++++
 4 files changed, 97 insertions(+), 26 deletions(-)

diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java
index f4cbb1d..801ce38 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java
@@ -33,7 +33,8 @@ import picocli.CommandLine;
     subcommands = {
         MailboxCreateCommand.class,
         MailboxExistCommand.class,
-        MailboxListCommand.class
+        MailboxListCommand.class,
+        MailboxDeleteCommand.class
     })
 public class MailboxCommand implements Callable<Integer> {
 
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxDeleteCommand.java
similarity index 50%
copy from server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java
copy to server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxDeleteCommand.java
index f4cbb1d..284f7e3 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxDeleteCommand.java
@@ -19,41 +19,52 @@
 
 package org.apache.james.cli.mailbox;
 
-import java.io.PrintStream;
 import java.util.concurrent.Callable;
 
 import org.apache.james.cli.WebAdminCli;
 import org.apache.james.httpclient.MailboxClient;
 
+import feign.Response;
 import picocli.CommandLine;
 
 @CommandLine.Command(
-    name = "mailbox",
-    description = "Manage Mailboxes",
-    subcommands = {
-        MailboxCreateCommand.class,
-        MailboxExistCommand.class,
-        MailboxListCommand.class
-    })
-public class MailboxCommand implements Callable<Integer> {
-
-    protected final WebAdminCli webAdminCli;
-    protected final PrintStream out;
-    protected final PrintStream err;
-
-    public MailboxCommand(PrintStream out, WebAdminCli webAdminCli, PrintStream err) {
-        this.out = out;
-        this.webAdminCli = webAdminCli;
-        this.err = err;
-    }
+    name = "delete",
+    description = "Delete a mailbox and its children")
+public class MailboxDeleteCommand implements Callable<Integer> {
+
+    public static final int DELETED_CODE = 204;
+    public static final int BAD_REQUEST_CODE = 400;
+    public static final int NOT_FOUND_CODE = 404;
+
+    @CommandLine.ParentCommand MailboxCommand mailboxCommand;
+
+    @CommandLine.Parameters(description = "Username")
+    String userName;
+
+    @CommandLine.Parameters (description = "Mailbox's name to be deleted")
+    String mailboxName;
+
 
     @Override
     public Integer call() {
-        return WebAdminCli.CLI_FINISHED_SUCCEED;
-    }
-
-    public MailboxClient fullyQualifiedURL(String partOfUrl) {
-        return webAdminCli.feignClientFactory(err).target(MailboxClient.class, webAdminCli.jamesUrl + partOfUrl);
+        try {
+            MailboxClient mailboxClient = mailboxCommand.fullyQualifiedURL("/users");
+            Response rs = mailboxClient.deleteAMailbox(userName, mailboxName);
+            if (rs.status() == DELETED_CODE) {
+                mailboxCommand.out.println("The mailbox now does not exist on the server.");
+                return WebAdminCli.CLI_FINISHED_SUCCEED;
+            } else if (rs.status() == BAD_REQUEST_CODE) {
+                mailboxCommand.err.println(rs.body());
+                return WebAdminCli.CLI_FINISHED_FAILED;
+            } else if (rs.status() == NOT_FOUND_CODE) {
+                mailboxCommand.err.println(rs.body());
+                return WebAdminCli.CLI_FINISHED_FAILED;
+            }
+            return WebAdminCli.CLI_FINISHED_FAILED;
+        } catch (Exception e) {
+            e.printStackTrace(mailboxCommand.err);
+            return WebAdminCli.CLI_FINISHED_FAILED;
+        }
     }
 
-}
\ No newline at end of file
+}
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/MailboxClient.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/MailboxClient.java
index 0fb0e35..f43d303 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/MailboxClient.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/MailboxClient.java
@@ -34,4 +34,7 @@ public interface MailboxClient {
     @RequestLine("GET /{usernameToBeUsed}/mailboxes")
     Response getMailboxList(@Param("usernameToBeUsed") String userName);
 
+    @RequestLine("DELETE /{usernameToBeUsed}/mailboxes/{mailboxNameToBeDeleted}")
+    Response deleteAMailbox(@Param("usernameToBeUsed") String userName, @Param("mailboxNameToBeDeleted") String mailboxName);
+
 }
diff --git a/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/MailboxManageTest.java b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/MailboxManageTest.java
index 2af4cbd..7a4b9d8 100644
--- a/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/MailboxManageTest.java
+++ b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/MailboxManageTest.java
@@ -209,4 +209,60 @@ public class MailboxManageTest {
         assertThat(outputStreamCaptor.toString()).isEmpty();
     }
 
+    @Test
+    void mailboxDeleteAParentMailboxWithTwoAddedChildrenMailboxShouldDeleteThemAll() throws Exception {
+        dataProbe.fluent().addDomain("linagora.com")
+            .addUser("hqtran@linagora.com", "123456");
+
+        WebAdminCli.executeFluent(new PrintStream(new ByteArrayOutputStream()), new PrintStream(new ByteArrayOutputStream()),
+            "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "create", "hqtran@linagora.com", "INBOX.1");
+
+        WebAdminCli.executeFluent(new PrintStream(new ByteArrayOutputStream()), new PrintStream(new ByteArrayOutputStream()),
+            "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "create", "hqtran@linagora.com", "INBOX.2");
+
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "delete", "hqtran@linagora.com", "INBOX");
+
+        WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+                "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "list", "hqtran@linagora.com");
+
+        assertThat(exitCode).isEqualTo(0);
+        assertThat(outputStreamCaptor.toString().trim()).isEqualTo("The mailbox now does not exist on the server.");
+    }
+
+    @Test
+    void mailboxDeleteWithNonExistingUsernameShouldFail() throws Exception {
+        dataProbe.fluent().addDomain("linagora.com");
+
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+                "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "delete", "hqtran@linagora.com", "INBOX");
+
+        assertThat(exitCode).isEqualTo(1);
+        assertThat(errorStreamCaptor.toString()).contains("User does not exist");
+    }
+
+    @Test
+    void mailboxDeleteWithInvalidMailboxNameShouldFail() throws Exception {
+        dataProbe.fluent().addDomain("linagora.com")
+                .addUser("hqtran@linagora.com", "123456");
+
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+                "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "delete", "hqtran@linagora.com", "IN#BOX");
+
+        assertThat(exitCode).isEqualTo(1);
+        assertThat(errorStreamCaptor.toString()).contains("Attempt to delete an invalid mailbox");
+    }
+
+    @Test
+    void mailboxDeleteWithInvalidUsernameShouldFail() throws Exception {
+        dataProbe.fluent().addDomain("linagora.com")
+                .addUser("hqtran@linagora.com", "123456");
+
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+                "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "delete", "hqtr@an@linagora.com", "INBOX");
+
+        assertThat(exitCode).isEqualTo(1);
+        assertThat(errorStreamCaptor.toString()).contains("Attempt to delete an invalid mailbox");
+    }
+
 }
\ 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] 04/13: JAMES-3463 MailboxChangeRepositoryContract: add missing .block() calls

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

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

commit df4bd6264409751dcaf04d77f8d35e950e3c6d94
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Dec 15 10:35:19 2020 +0700

    JAMES-3463 MailboxChangeRepositoryContract: add missing .block() calls
    
    Goes unoticed with Memory implementation as storage implementations are not wrapped in reactor entities.
---
 .../change/MailboxChangeRepositoryContract.java    | 92 +++++++++++-----------
 1 file changed, 46 insertions(+), 46 deletions(-)

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 fe9764f..9cb83475 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
@@ -51,7 +51,7 @@ public interface MailboxChangeRepositoryContract {
 
         MailboxChange change = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE, ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
 
-        assertThatCode(() -> repository.save(change))
+        assertThatCode(() -> repository.save(change).block())
             .doesNotThrowAnyException();
     }
 
@@ -70,9 +70,9 @@ public interface MailboxChangeRepositoryContract {
         MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(2), ImmutableList.of(TestId.of(2)), ImmutableList.of(), ImmutableList.of());
         MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(3)), ImmutableList.of(), ImmutableList.of());
         MailboxChange change3 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(4)), ImmutableList.of(), ImmutableList.of());
-        repository.save(change1);
-        repository.save(change2);
-        repository.save(change3);
+        repository.save(change1).block();
+        repository.save(change2).block();
+        repository.save(change3).block();
 
         assertThat(repository.getLatestState(ACCOUNT_ID).block())
             .isEqualTo(change3.getState());
@@ -104,8 +104,8 @@ public interface MailboxChangeRepositoryContract {
 
         MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(1), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
         MailboxChange change = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(), ImmutableList.of(TestId.of(1)), ImmutableList.of());
-        repository.save(oldState);
-        repository.save(change);
+        repository.save(oldState).block();
+        repository.save(change).block();
 
         assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.empty()).block().getAllChanges())
             .hasSameElementsAs(change.getUpdated());
@@ -116,7 +116,7 @@ public interface MailboxChangeRepositoryContract {
         MailboxChangeRepository repository = mailboxChangeRepository();
 
         MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE, ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
-        repository.save(oldState);
+        repository.save(oldState).block();
 
         assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.empty()).block().getAllChanges())
             .isEmpty();
@@ -127,7 +127,7 @@ public interface MailboxChangeRepositoryContract {
         MailboxChangeRepository repository = mailboxChangeRepository();
 
         MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE, ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
-        repository.save(oldState);
+        repository.save(oldState).block();
 
         assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.empty()).block().getNewState())
             .isEqualTo(oldState.getState());
@@ -141,10 +141,10 @@ public interface MailboxChangeRepositoryContract {
         MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(2), ImmutableList.of(TestId.of(2)), ImmutableList.of(), ImmutableList.of());
         MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(3)), ImmutableList.of(), ImmutableList.of());
         MailboxChange change3 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(4)), ImmutableList.of(), ImmutableList.of());
-        repository.save(oldState);
-        repository.save(change1);
-        repository.save(change2);
-        repository.save(change3);
+        repository.save(oldState).block();
+        repository.save(change1).block();
+        repository.save(change2).block();
+        repository.save(change3).block();
 
         assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(3))).block().getCreated())
             .containsExactlyInAnyOrder(TestId.of(2), TestId.of(3), TestId.of(4));
@@ -158,10 +158,10 @@ public interface MailboxChangeRepositoryContract {
         MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(2), ImmutableList.of(TestId.of(2)), ImmutableList.of(), ImmutableList.of());
         MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(3)), ImmutableList.of(), ImmutableList.of());
         MailboxChange change3 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(4)), ImmutableList.of(), ImmutableList.of());
-        repository.save(oldState);
-        repository.save(change1);
-        repository.save(change2);
-        repository.save(change3);
+        repository.save(oldState).block();
+        repository.save(change1).block();
+        repository.save(change2).block();
+        repository.save(change3).block();
 
         assertThat(repository.getSinceState(ACCOUNT_ID, State.INITIAL, Optional.of(Limit.of(3))).block().getCreated())
             .containsExactlyInAnyOrder(TestId.of(1), TestId.of(2), TestId.of(3));
@@ -176,10 +176,10 @@ public interface MailboxChangeRepositoryContract {
         State state2 = State.of(UUID.randomUUID());
         MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, state2, DATE.minusHours(1), ImmutableList.of(TestId.of(3)), ImmutableList.of(), ImmutableList.of());
         MailboxChange change3 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(4)), ImmutableList.of(), ImmutableList.of());
-        repository.save(oldState);
-        repository.save(change1);
-        repository.save(change2);
-        repository.save(change3);
+        repository.save(oldState).block();
+        repository.save(change1).block();
+        repository.save(change2).block();
+        repository.save(change3).block();
 
 
         assertThat(repository.getSinceState(ACCOUNT_ID, State.INITIAL, Optional.of(Limit.of(3))).block().getNewState())
@@ -194,9 +194,9 @@ public interface MailboxChangeRepositoryContract {
         MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(2), TestId.of(3), TestId.of(4), TestId.of(5), TestId.of(6)), ImmutableList.of(), ImmutableList.of());
         MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(7)), ImmutableList.of(), ImmutableList.of());
 
-        repository.save(oldState);
-        repository.save(change1);
-        repository.save(change2);
+        repository.save(oldState).block();
+        repository.save(change1).block();
+        repository.save(change2).block();
 
         assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.empty()).block().getAllChanges())
             .hasSameElementsAs(change1.getCreated());
@@ -209,9 +209,9 @@ public interface MailboxChangeRepositoryContract {
         MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(2), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
         MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of(), ImmutableList.of());
         MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(4), TestId.of(5)), ImmutableList.of(), ImmutableList.of());
-        repository.save(oldState);
-        repository.save(change1);
-        repository.save(change2);
+        repository.save(oldState).block();
+        repository.save(change1).block();
+        repository.save(change2).block();
 
         assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(3))).block().getAllChanges())
             .hasSameElementsAs(change1.getCreated());
@@ -223,8 +223,8 @@ public interface MailboxChangeRepositoryContract {
 
         MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(2), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
         MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of(), ImmutableList.of());
-        repository.save(oldState);
-        repository.save(change1);
+        repository.save(oldState).block();
+        repository.save(change1).block();
 
         assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(1))).block().getAllChanges())
             .isEmpty();
@@ -237,9 +237,9 @@ public interface MailboxChangeRepositoryContract {
         MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(2), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
         MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of(), ImmutableList.of());
         MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of());
-        repository.save(oldState);
-        repository.save(change1);
-        repository.save(change2);
+        repository.save(oldState).block();
+        repository.save(change1).block();
+        repository.save(change2).block();
 
         assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.empty()).block().getNewState())
             .isEqualTo(change2.getState());
@@ -252,9 +252,9 @@ public interface MailboxChangeRepositoryContract {
         MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(2), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
         MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of(), ImmutableList.of());
         MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of());
-        repository.save(oldState);
-        repository.save(change1);
-        repository.save(change2);
+        repository.save(oldState).block();
+        repository.save(change1).block();
+        repository.save(change2).block();
 
         assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(1))).block().hasMoreChanges())
             .isTrue();
@@ -267,9 +267,9 @@ public interface MailboxChangeRepositoryContract {
         MailboxChange oldState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE.minusHours(2), ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
         MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of(), ImmutableList.of());
         MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of());
-        repository.save(oldState);
-        repository.save(change1);
-        repository.save(change2);
+        repository.save(oldState).block();
+        repository.save(change1).block();
+        repository.save(change2).block();
 
         assertThat(repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(4))).block().hasMoreChanges())
             .isFalse();
@@ -283,10 +283,10 @@ public interface MailboxChangeRepositoryContract {
         MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(2), ImmutableList.of(TestId.of(2), TestId.of(3), TestId.of(4), TestId.of(5)), ImmutableList.of(), ImmutableList.of());
         MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(6), TestId.of(7)), ImmutableList.of(TestId.of(2), TestId.of(3)), ImmutableList.of(TestId.of(4)));
         MailboxChange change3 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(8)), ImmutableList.of(TestId.of(6), TestId.of(7)), ImmutableList.of(TestId.of(5)));
-        repository.save(oldState);
-        repository.save(change1);
-        repository.save(change2);
-        repository.save(change3);
+        repository.save(oldState).block();
+        repository.save(change1).block();
+        repository.save(change2).block();
+        repository.save(change3).block();
 
         MailboxChanges mailboxChanges = repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(20))).block();
 
@@ -305,9 +305,9 @@ public interface MailboxChangeRepositoryContract {
         MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(), ImmutableList.of(TestId.of(1), TestId.of(2)), ImmutableList.of());
         MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(3)), ImmutableList.of(TestId.of(1), TestId.of(2)), ImmutableList.of());
 
-        repository.save(oldState);
-        repository.save(change1);
-        repository.save(change2);
+        repository.save(oldState).block();
+        repository.save(change1).block();
+        repository.save(change2).block();
 
         MailboxChanges mailboxChanges = repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(3))).block();
         SoftAssertions.assertSoftly(softly -> {
@@ -322,8 +322,8 @@ public interface MailboxChangeRepositoryContract {
 
         MailboxChange currentState = MailboxChange.of(ACCOUNT_ID, STATE_0, DATE, ImmutableList.of(TestId.of(1)), ImmutableList.of(), ImmutableList.of());
         MailboxChange change = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(2)), ImmutableList.of(), ImmutableList.of());
-        repository.save(currentState);
-        repository.save(change);
+        repository.save(currentState).block();
+        repository.save(change).block();
 
         assertThatThrownBy(() -> repository.getSinceState(ACCOUNT_ID, STATE_0, Optional.of(Limit.of(-1))))
             .isInstanceOf(IllegalArgumentException.class);


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


[james-project] 13/13: JAMES-3400 Update Webadmin-cli README.md following separation of reindex from mailbox commands group

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

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

commit 94c18471c781a29397ce0f251976b217dab2123b
Author: quanth <hq...@linagora.com>
AuthorDate: Mon Dec 14 14:10:07 2020 +0700

    JAMES-3400 Update Webadmin-cli README.md following separation of reindex from mailbox commands group
---
 server/protocols/webadmin-cli/README.md              | 20 --------------------
 .../james/cli/mailbox/MailboxCreateCommand.java      |  2 +-
 .../java/org/apache/james/cli/MailboxManageTest.java | 10 ++++++----
 3 files changed, 7 insertions(+), 25 deletions(-)

diff --git a/server/protocols/webadmin-cli/README.md b/server/protocols/webadmin-cli/README.md
index 6ea0203..b8b4235 100644
--- a/server/protocols/webadmin-cli/README.md
+++ b/server/protocols/webadmin-cli/README.md
@@ -73,8 +73,6 @@ Note: the command line before ENTITY will be documented as {cli}.
    - [Delete a mailbox and its children](#delete-a-mailbox-and-its-children)
    - [Delete all mailboxes of a user](#delete-all-mailboxes-of-a-user)
    - [Get mailboxes list](#get-mailboxes-list)
-   - [Reindex a user mailboxes](#reindex-a-user-mailboxes)
-   - [Reindex all users mailboxes](#reindex-all-users-mailboxes)
 - [Manage Domain Mappings](#manage-domain-mappings)
    - [Listing all domain mappings](#listing-all-domain-mappings)
    - [Listing all destination domains for a source domain](#listing-all-destination-domains-for-a-source-domain)
@@ -237,24 +235,6 @@ Resource name usernameToBeUsed should be an existing user
 
 Resource name usernameToBeUsed should be an existing user
 
-### Reindex a user mailboxes
-
-```
-{cli} mailbox reindex <usernameToBeUsed>
-```
-
-Will schedule a task for reIndexing all the mails in “usernameToBeUsed” mailboxes.
-
-Resource name usernameToBeUsed should be an existing user
-
-### Reindex all users mailboxes
-
-```
-{cli} mailbox reindexAll
-```
-
-Will schedule a task for reIndexing all the mails stored on this James server.
-
 ## Manage Domain Mappings
 
 Given a configured source (from) domain and a destination (to) domain, when an email is sent to an address belonging to the source domain, then the domain part of this address is overwritten, the destination domain is then used. A source (from) domain can have many destination (to) domains.
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCreateCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCreateCommand.java
index 950880d..46a7806 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCreateCommand.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCreateCommand.java
@@ -50,7 +50,7 @@ public class MailboxCreateCommand implements Callable<Integer> {
             MailboxClient mailboxClient = mailboxCommand.fullyQualifiedURL("/users");
             Response rs = mailboxClient.createAMailbox(userName, mailboxName);
             if (rs.status() == CREATED_CODE) {
-                mailboxCommand.out.println("The mailbox now exists on the server.");
+                mailboxCommand.out.println("The mailbox was created successfully.");
                 return WebAdminCli.CLI_FINISHED_SUCCEED;
             } else if (rs.status() == BAD_REQUEST_CODE) {
                 mailboxCommand.err.println(rs.body());
diff --git a/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/MailboxManageTest.java b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/MailboxManageTest.java
index 1ee0305..4835e9a 100644
--- a/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/MailboxManageTest.java
+++ b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/MailboxManageTest.java
@@ -70,7 +70,7 @@ public class MailboxManageTest {
             "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "exist", "hqtran@linagora.com", "INBOX");
 
         assertThat(exitCode).isEqualTo(0);
-        assertThat(outputStreamCaptor.toString().trim()).isEqualTo("The mailbox now exists on the server.\n" +
+        assertThat(outputStreamCaptor.toString().trim()).isEqualTo("The mailbox was created successfully.\n" +
             "The mailbox exists.");
     }
 
@@ -108,7 +108,7 @@ public class MailboxManageTest {
 
         assertThat(exitCode).isEqualTo(0);
         assertThat(exitCode2).isEqualTo(0);
-        assertThat(outputStreamCaptor.toString().trim()).isEqualTo("The mailbox now exists on the server.");
+        assertThat(outputStreamCaptor.toString().trim()).isEqualTo("The mailbox was created successfully.");
     }
 
     @Test
@@ -235,7 +235,8 @@ public class MailboxManageTest {
                 "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "list", "hqtran@linagora.com");
 
         assertThat(exitCode).isEqualTo(0);
-        assertThat(outputStreamCaptor.toString().trim()).isEqualTo("The mailbox now does not exist on the server.");
+        // The outputStreamCaptor should capture the result of delete command and the result of list command(which is nothing)
+        assertThat(outputStreamCaptor.toString()).isEqualTo("The mailbox now does not exist on the server.\n");
     }
 
     @Test
@@ -291,7 +292,8 @@ public class MailboxManageTest {
                 "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "list", "hqtran@linagora.com");
 
         assertThat(exitCode).isEqualTo(0);
-        assertThat(outputStreamCaptor.toString().trim()).isEqualTo("The user do not have mailboxes anymore.");
+        // The outputStreamCaptor should capture the result of deleteAll command and the result of list command(which is nothing)
+        assertThat(outputStreamCaptor.toString()).isEqualTo("The user do not have mailboxes anymore.\n");
     }
 
     @Test


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


[james-project] 03/13: JAMES-3463 MailboxChangeRepository::getLatestState

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

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

commit 33a65de0a28be8dc59705670f06279f4a3d587e5
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Dec 15 10:31:58 2020 +0700

    JAMES-3463 MailboxChangeRepository::getLatestState
---
 .../change/CassandraMailboxChangeRepository.java   |  5 +++++
 .../jmap/api/change/MailboxChangeRepository.java   |  2 ++
 .../change/MemoryMailboxChangeRepository.java      |  8 ++++++++
 .../change/MailboxChangeRepositoryContract.java    | 23 ++++++++++++++++++++++
 4 files changed, 38 insertions(+)

diff --git a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/change/CassandraMailboxChangeRepository.java b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/change/CassandraMailboxChangeRepository.java
index 65b6d3f..738d9c2 100644
--- a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/change/CassandraMailboxChangeRepository.java
+++ b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/change/CassandraMailboxChangeRepository.java
@@ -39,4 +39,9 @@ public class CassandraMailboxChangeRepository implements MailboxChangeRepository
     public Mono<MailboxChanges> getSinceState(AccountId accountId, MailboxChange.State state, Optional<MailboxChange.Limit> maxIdsToReturn) {
         return Mono.empty();
     }
+
+    @Override
+    public Mono<MailboxChange.State> getLatestState(AccountId accountId) {
+        return Mono.just(MailboxChange.State.INITIAL);
+    }
 }
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChangeRepository.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChangeRepository.java
index fb965be..ccc0a36 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChangeRepository.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/change/MailboxChangeRepository.java
@@ -32,4 +32,6 @@ public interface MailboxChangeRepository {
     Mono<Void> save(MailboxChange change);
 
     Mono<MailboxChanges> getSinceState(AccountId accountId, State state, Optional<Limit> maxIdsToReturn);
+
+    Mono<State> getLatestState(AccountId accountId);
 }
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 0ff0fa5..5dad517 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
@@ -79,4 +79,12 @@ public class MemoryMailboxChangeRepository implements MailboxChangeRepository {
             .switchIfEmpty(Mono.error(new ChangeNotFoundException(state, String.format("State '%s' could not be found", state.getValue()))))
             .single();
     }
+
+    @Override
+    public Mono<State> getLatestState(AccountId accountId) {
+        return Flux.fromIterable(mailboxChangeMap.get(accountId))
+            .sort(Comparator.comparing(MailboxChange::getDate))
+            .map(MailboxChange::getState)
+            .last(State.INITIAL);
+    }
 }
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 75f10af..fe9764f 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
@@ -56,6 +56,29 @@ public interface MailboxChangeRepositoryContract {
     }
 
     @Test
+    default void getLatestStateShouldReturnInitialWhenEmpty() {
+        MailboxChangeRepository repository = mailboxChangeRepository();
+
+        assertThat(repository.getLatestState(ACCOUNT_ID).block())
+            .isEqualTo(State.INITIAL);
+    }
+
+    @Test
+    default void getLatestStateShouldReturnLastPersistedState() {
+        MailboxChangeRepository repository = mailboxChangeRepository();
+
+        MailboxChange change1 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(2), ImmutableList.of(TestId.of(2)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change2 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE.minusHours(1), ImmutableList.of(TestId.of(3)), ImmutableList.of(), ImmutableList.of());
+        MailboxChange change3 = MailboxChange.of(ACCOUNT_ID, State.of(UUID.randomUUID()), DATE, ImmutableList.of(TestId.of(4)), ImmutableList.of(), ImmutableList.of());
+        repository.save(change1);
+        repository.save(change2);
+        repository.save(change3);
+
+        assertThat(repository.getLatestState(ACCOUNT_ID).block())
+            .isEqualTo(change3.getState());
+    }
+
+    @Test
     default void saveChangeShouldFailWhenNoAccountId() {
         MailboxChangeRepository repository = mailboxChangeRepository();
 


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


[james-project] 07/13: JAMES-3435 Make message read isolation configurable

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

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

commit 65f0156378881ac43022fd13ebb2991ee6d37994
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Dec 10 09:24:38 2020 +0700

    JAMES-3435 Make message read isolation configurable
    
    Disabling should be considered experimental.
    
    If enabled, regular consistency level is used for
    read transactions for message. This might result
    in stale reads as the system.paxos table will not
    be checked for latest updates. Better performance
    are expected by turning it off.
    
    Note that reads performed as part of write transactions
    are always performed with a strong consistency.
---
 .../init/configuration/CassandraConfiguration.java | 31 +++++++++++++++++++---
 .../pages/distributed/configure/cassandra.adoc     |  6 +++++
 .../mailbox/cassandra/DeleteMessageListener.java   |  4 +--
 .../cassandra/mail/CassandraMessageIdMapper.java   | 22 +++++++++++----
 .../mail/CassandraMessageIdToImapUidDAO.java       | 30 ++++++++++++++++++---
 .../cassandra/mail/CassandraMessageMapper.java     |  4 ++-
 .../mail/task/RecomputeMailboxCountersService.java |  4 ++-
 .../task/SolveMessageInconsistenciesService.java   |  7 ++---
 src/site/xdoc/server/config-cassandra.xml          |  7 +++++
 9 files changed, 96 insertions(+), 19 deletions(-)

diff --git a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/configuration/CassandraConfiguration.java b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/configuration/CassandraConfiguration.java
index 99ac1a7..ad46dd9 100644
--- a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/configuration/CassandraConfiguration.java
+++ b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/configuration/CassandraConfiguration.java
@@ -57,6 +57,7 @@ public class CassandraConfiguration {
     public static final String DEFAULT_CONSISTENCY_LEVEL_LIGHTWEIGHT_TRANSACTION = "SERIAL";
     public static final List<String> VALID_CONSISTENCY_LEVEL_REGULAR = ImmutableList.of("QUORUM", "LOCAL_QUORUM", "EACH_QUORUM");
     public static final List<String> VALID_CONSISTENCY_LEVEL_LIGHTWEIGHT_TRANSACTION = ImmutableList.of("SERIAL", "LOCAL_SERIAL");
+    public static final boolean DEFAULT_STRONG_CONSISTENCY = true;
 
     private static final String MAILBOX_READ_REPAIR = "mailbox.read.repair.chance";
     private static final String MAILBOX_MAX_COUNTERS_READ_REPAIR = "mailbox.counters.read.repair.chance.max";
@@ -72,6 +73,7 @@ public class CassandraConfiguration {
     private static final String BLOB_PART_SIZE = "mailbox.blob.part.size";
     private static final String ATTACHMENT_V2_MIGRATION_READ_TIMEOUT = "attachment.v2.migration.read.timeout";
     private static final String MESSAGE_ATTACHMENTID_READ_TIMEOUT = "message.attachmentids.read.timeout";
+    private static final String MESSAGE_READ_STRONG_CONSISTENCY = "message.read.strong.consistency";
     private static final String CONSISTENCY_LEVEL_REGULAR = "cassandra.consistency_level.regular";
     private static final String CONSISTENCY_LEVEL_LIGHTWEIGHT_TRANSACTION = "cassandra.consistency_level.lightweight_transaction";
 
@@ -94,6 +96,17 @@ public class CassandraConfiguration {
         private Optional<Float> mailboxReadRepair = Optional.empty();
         private Optional<Float> mailboxCountersReadRepairMax = Optional.empty();
         private Optional<Float> mailboxCountersReadRepairChanceOneHundred = Optional.empty();
+        private Optional<Boolean> messageReadStrongConsistency = Optional.empty();
+
+        public Builder messageReadStrongConsistency(boolean value) {
+            this.messageReadStrongConsistency = Optional.of(value);
+            return this;
+        }
+
+        public Builder messageReadStrongConsistency(Optional<Boolean> value) {
+            this.messageReadStrongConsistency = value;
+            return this;
+        }
 
         public Builder messageReadChunkSize(int value) {
             Preconditions.checkArgument(value > 0, "messageReadChunkSize needs to be strictly positive");
@@ -300,7 +313,8 @@ public class CassandraConfiguration {
                 consistencyLevelLightweightTransaction,
                 mailboxReadRepair.orElse(DEFAULT_MAILBOX_READ_REPAIR),
                 mailboxCountersReadRepairMax.orElse(DEFAULT_MAX_MAILBOX_COUNTERS_READ_REPAIR_CHANCE),
-                mailboxCountersReadRepairChanceOneHundred.orElse(DEFAULT_ONE_HUNDRED_MAILBOX_COUNTERS_READ_REPAIR_CHANCE));
+                mailboxCountersReadRepairChanceOneHundred.orElse(DEFAULT_ONE_HUNDRED_MAILBOX_COUNTERS_READ_REPAIR_CHANCE),
+                messageReadStrongConsistency.orElse(DEFAULT_STRONG_CONSISTENCY));
         }
     }
 
@@ -342,6 +356,8 @@ public class CassandraConfiguration {
                 propertiesConfiguration.getFloat(MAILBOX_MAX_COUNTERS_READ_REPAIR, null)))
             .mailboxCountersReadRepairChanceOneHundred(Optional.ofNullable(
                 propertiesConfiguration.getFloat(MAILBOX_ONE_HUNDRED_COUNTERS_READ_REPAIR, null)))
+            .messageReadStrongConsistency(Optional.ofNullable(
+                propertiesConfiguration.getBoolean(MESSAGE_READ_STRONG_CONSISTENCY, null)))
             .build();
     }
 
@@ -361,6 +377,7 @@ public class CassandraConfiguration {
     private final float mailboxReadRepair;
     private final float mailboxCountersReadRepairChanceMax;
     private final float mailboxCountersReadRepairChanceOneHundred;
+    private final boolean messageReadStrongConsistency;
 
     @VisibleForTesting
     CassandraConfiguration(int aclMaxRetry, int messageReadChunkSize, int expungeChunkSize,
@@ -369,7 +386,7 @@ public class CassandraConfiguration {
                            int blobPartSize, final int attachmentV2MigrationReadTimeout, int messageAttachmentIdsReadTimeout,
                            String consistencyLevelRegular, String consistencyLevelLightweightTransaction,
                            float mailboxReadRepair, float mailboxCountersReadRepairChanceMax,
-                           float mailboxCountersReadRepairChanceOneHundred) {
+                           float mailboxCountersReadRepairChanceOneHundred, boolean messageReadStrongConsistency) {
         this.aclMaxRetry = aclMaxRetry;
         this.messageReadChunkSize = messageReadChunkSize;
         this.expungeChunkSize = expungeChunkSize;
@@ -386,6 +403,11 @@ public class CassandraConfiguration {
         this.mailboxReadRepair = mailboxReadRepair;
         this.mailboxCountersReadRepairChanceMax = mailboxCountersReadRepairChanceMax;
         this.mailboxCountersReadRepairChanceOneHundred = mailboxCountersReadRepairChanceOneHundred;
+        this.messageReadStrongConsistency = messageReadStrongConsistency;
+    }
+
+    public boolean isMessageReadStrongConsistency() {
+        return messageReadStrongConsistency;
     }
 
     public float getMailboxReadRepair() {
@@ -471,6 +493,7 @@ public class CassandraConfiguration {
                 && Objects.equals(this.blobPartSize, that.blobPartSize)
                 && Objects.equals(this.attachmentV2MigrationReadTimeout, that.attachmentV2MigrationReadTimeout)
                 && Objects.equals(this.messageAttachmentIdsReadTimeout, that.messageAttachmentIdsReadTimeout)
+                && Objects.equals(this.messageReadStrongConsistency, that.messageReadStrongConsistency)
                 && Objects.equals(this.consistencyLevelRegular, that.consistencyLevelRegular)
                 && Objects.equals(this.consistencyLevelLightweightTransaction, that.consistencyLevelLightweightTransaction);
         }
@@ -483,7 +506,8 @@ public class CassandraConfiguration {
             flagsUpdateMessageMaxRetry, modSeqMaxRetry, uidMaxRetry, fetchNextPageInAdvanceRow,
             mailboxCountersReadRepairChanceOneHundred, mailboxCountersReadRepairChanceMax,
             blobPartSize, attachmentV2MigrationReadTimeout, messageAttachmentIdsReadTimeout,
-            consistencyLevelRegular, consistencyLevelLightweightTransaction, mailboxReadRepair);
+            consistencyLevelRegular, consistencyLevelLightweightTransaction, mailboxReadRepair,
+            messageReadStrongConsistency);
     }
 
     @Override
@@ -503,6 +527,7 @@ public class CassandraConfiguration {
             .add("blobPartSize", blobPartSize)
             .add("attachmentV2MigrationReadTimeout", attachmentV2MigrationReadTimeout)
             .add("messageAttachmentIdsReadTimeout", messageAttachmentIdsReadTimeout)
+            .add("messageReadStrongConsistency", messageReadStrongConsistency)
             .add("consistencyLevelRegular", consistencyLevelRegular)
             .add("consistencyLevelLightweightTransaction", consistencyLevelLightweightTransaction)
             .toString();
diff --git a/docs/modules/servers/pages/distributed/configure/cassandra.adoc b/docs/modules/servers/pages/distributed/configure/cassandra.adoc
index 3439948..019b14b 100644
--- a/docs/modules/servers/pages/distributed/configure/cassandra.adoc
+++ b/docs/modules/servers/pages/distributed/configure/cassandra.adoc
@@ -185,4 +185,10 @@ Controls the number of messages to be retrieved in parallel.
 | Optional. Defaults to 102400 (100KB).
 Controls the size of blob parts used to store messages in the Cassandra blobStore.
 
+| message.read.strong.consistency
+| Optional. Boolean, defaults to true. Disabling should be considered experimental.
+If enabled, regular consistency level is used for read transactions for message. This might result
+in stale reads as the system.paxos table will not be checked for latest updates. Better performance are expected
+by turning it off. Note that reads performed as part of write transactions are always performed with a strong
+consistency.
 |===
\ No newline at end of file
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/DeleteMessageListener.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/DeleteMessageListener.java
index 7412ba4..3122449 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/DeleteMessageListener.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/DeleteMessageListener.java
@@ -237,13 +237,13 @@ public class DeleteMessageListener implements MailboxListener.GroupMailboxListen
     }
 
     private Mono<Boolean> isReferenced(CassandraMessageId id) {
-        return imapUidDAO.retrieve(id, ALL_MAILBOXES)
+        return imapUidDAO.retrieve(id, ALL_MAILBOXES, CassandraMessageIdToImapUidDAO.ReadConsistency.STRONG)
             .hasElements()
             .map(negate());
     }
 
     private Mono<Boolean> isReferenced(CassandraMessageId id, CassandraId excludedId) {
-        return imapUidDAO.retrieve(id, ALL_MAILBOXES)
+        return imapUidDAO.retrieve(id, ALL_MAILBOXES, CassandraMessageIdToImapUidDAO.ReadConsistency.STRONG)
             .filter(metadata -> !metadata.getComposedMessageId().getMailboxId().equals(excludedId))
             .hasElements()
             .map(negate());
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 c9925b9..19fe49b 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
@@ -18,6 +18,8 @@
  ****************************************************************/
 package org.apache.james.mailbox.cassandra.mail;
 
+import static org.apache.james.mailbox.cassandra.mail.CassandraMessageIdToImapUidDAO.ReadConsistency.STRONG;
+import static org.apache.james.mailbox.cassandra.mail.CassandraMessageIdToImapUidDAO.ReadConsistency.WEAK;
 import static org.apache.james.util.ReactorUtils.DEFAULT_CONCURRENCY;
 
 import java.time.Duration;
@@ -33,6 +35,7 @@ import org.apache.james.backends.cassandra.init.configuration.CassandraConfigura
 import org.apache.james.mailbox.MessageManager;
 import org.apache.james.mailbox.cassandra.ids.CassandraId;
 import org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
+import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdToImapUidDAO.ReadConsistency;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.exception.MailboxNotFoundException;
 import org.apache.james.mailbox.model.ComposedMessageId;
@@ -108,7 +111,8 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
     @Override
     public Flux<MailboxMessage> findReactive(Collection<MessageId> messageIds, FetchType fetchType) {
         return Flux.fromIterable(messageIds)
-            .flatMap(messageId -> imapUidDAO.retrieve((CassandraMessageId) messageId, Optional.empty()), cassandraConfiguration.getMessageReadChunkSize())
+            .flatMap(messageId -> imapUidDAO.retrieve((CassandraMessageId) messageId, Optional.empty(), chooseReadConsistency()),
+                cassandraConfiguration.getMessageReadChunkSize())
             .flatMap(composedMessageId -> messageDAOV3.retrieveMessage(composedMessageId, fetchType)
                 .switchIfEmpty(messageDAO.retrieveMessage(composedMessageId, fetchType))
                 .map(messageRepresentation -> Pair.of(composedMessageId, messageRepresentation)), cassandraConfiguration.getMessageReadChunkSize())
@@ -119,7 +123,7 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
 
     @Override
     public Publisher<ComposedMessageIdWithMetaData> findMetadata(MessageId messageId) {
-        return imapUidDAO.retrieve((CassandraMessageId) messageId, Optional.empty());
+        return imapUidDAO.retrieve((CassandraMessageId) messageId, Optional.empty(), chooseReadConsistency());
     }
 
     private Flux<MailboxMessage> keepMessageIfMailboxExists(GroupedFlux<MailboxId, MailboxMessage> groupedFlux) {
@@ -136,13 +140,21 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
 
     @Override
     public List<MailboxId> findMailboxes(MessageId messageId) {
-        return imapUidDAO.retrieve((CassandraMessageId) messageId, Optional.empty())
+        return imapUidDAO.retrieve((CassandraMessageId) messageId, Optional.empty(), chooseReadConsistency())
             .map(ComposedMessageIdWithMetaData::getComposedMessageId)
             .map(ComposedMessageId::getMailboxId)
             .collectList()
             .block();
     }
 
+    public ReadConsistency chooseReadConsistency() {
+        if (cassandraConfiguration.isMessageReadStrongConsistency()) {
+            return STRONG;
+        } else {
+            return WEAK;
+        }
+    }
+
     @Override
     public void save(MailboxMessage mailboxMessage) throws MailboxException {
         CassandraId mailboxId = (CassandraId) mailboxMessage.getMailboxId();
@@ -208,7 +220,7 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
     }
 
     private Mono<Void> retrieveAndDeleteIndices(CassandraMessageId messageId, Optional<CassandraId> mailboxId) {
-        return imapUidDAO.retrieve(messageId, mailboxId)
+        return imapUidDAO.retrieve(messageId, mailboxId, STRONG)
             .flatMap(this::deleteIds, ReactorUtils.DEFAULT_CONCURRENCY)
             .then();
     }
@@ -270,7 +282,7 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
 
     private Mono<List<Pair<Flags, ComposedMessageIdWithMetaData>>> updateFlags(MailboxId mailboxId, MessageId messageId, Flags newState, MessageManager.FlagsUpdateMode updateMode) {
         CassandraId cassandraId = (CassandraId) mailboxId;
-        return imapUidDAO.retrieve((CassandraMessageId) messageId, Optional.of(cassandraId))
+        return imapUidDAO.retrieve((CassandraMessageId) messageId, Optional.of(cassandraId), STRONG)
             .flatMap(oldComposedId -> updateFlags(newState, updateMode, cassandraId, oldComposedId), ReactorUtils.DEFAULT_CONCURRENCY)
             .switchIfEmpty(Mono.error(MailboxDeleteDuringUpdateException::new))
             .collectList();
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAO.java
index 4c6ee7c..2633fbf 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAO.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAO.java
@@ -41,6 +41,7 @@ import static org.apache.james.mailbox.cassandra.table.MessageIdToImapUid.MOD_SE
 import static org.apache.james.mailbox.cassandra.table.MessageIdToImapUid.TABLE_NAME;
 
 import java.util.Optional;
+import java.util.function.Function;
 
 import javax.inject.Inject;
 import javax.mail.Flags;
@@ -62,12 +63,28 @@ import com.datastax.driver.core.Row;
 import com.datastax.driver.core.Session;
 import com.datastax.driver.core.Statement;
 import com.datastax.driver.core.querybuilder.QueryBuilder;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableSet;
 
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
 public class CassandraMessageIdToImapUidDAO {
+    public enum ReadConsistency {
+        STRONG(CassandraConsistenciesConfiguration::getLightweightTransaction),
+        WEAK(CassandraConsistenciesConfiguration::getRegular);
+
+        private final Function<CassandraConsistenciesConfiguration, ConsistencyLevel> consistencyLevelChoice;
+
+
+        ReadConsistency(Function<CassandraConsistenciesConfiguration, ConsistencyLevel> consistencyLevelChoice) {
+            this.consistencyLevelChoice = consistencyLevelChoice;
+        }
+
+        public ConsistencyLevel choose(CassandraConsistenciesConfiguration configuration) {
+            return consistencyLevelChoice.apply(configuration);
+        }
+    }
 
     private static final String MOD_SEQ_CONDITION = "modSeqCondition";
 
@@ -79,13 +96,13 @@ public class CassandraMessageIdToImapUidDAO {
     private final PreparedStatement selectAll;
     private final PreparedStatement select;
     private final PreparedStatement listStatement;
-    private final ConsistencyLevel consistencyLevel;
+    private final CassandraConsistenciesConfiguration consistenciesConfiguration;
 
     @Inject
     public CassandraMessageIdToImapUidDAO(Session session, CassandraConsistenciesConfiguration consistenciesConfiguration,
                                           CassandraMessageId.Factory messageIdFactory) {
         this.cassandraAsyncExecutor = new CassandraAsyncExecutor(session);
-        this.consistencyLevel = consistenciesConfiguration.getLightweightTransaction();
+        this.consistenciesConfiguration = consistenciesConfiguration;
         this.messageIdFactory = messageIdFactory;
         this.delete = prepareDelete(session);
         this.insert = prepareInsert(session);
@@ -196,13 +213,18 @@ public class CassandraMessageIdToImapUidDAO {
                 .setLong(MOD_SEQ_CONDITION, oldModSeq.asLong()));
     }
 
-    public Flux<ComposedMessageIdWithMetaData> retrieve(CassandraMessageId messageId, Optional<CassandraId> mailboxId) {
+    public Flux<ComposedMessageIdWithMetaData> retrieve(CassandraMessageId messageId, Optional<CassandraId> mailboxId, ReadConsistency readConsistency) {
         return cassandraAsyncExecutor.executeRows(
                     selectStatement(messageId, mailboxId)
-                    .setConsistencyLevel(consistencyLevel))
+                    .setConsistencyLevel(readConsistency.choose(consistenciesConfiguration)))
                 .map(this::toComposedMessageIdWithMetadata);
     }
 
+    @VisibleForTesting
+    public Flux<ComposedMessageIdWithMetaData> retrieve(CassandraMessageId messageId, Optional<CassandraId> mailboxId) {
+        return retrieve(messageId, mailboxId, ReadConsistency.STRONG);
+    }
+
     public Flux<ComposedMessageIdWithMetaData> retrieveAllMessages() {
         return cassandraAsyncExecutor.executeRows(listStatement.bind())
             .map(row -> toComposedMessageIdWithMetadata(row));
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 ffc0f36..5ced7bf 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
@@ -19,6 +19,7 @@
 
 package org.apache.james.mailbox.cassandra.mail;
 
+import static org.apache.james.mailbox.cassandra.mail.CassandraMessageIdToImapUidDAO.ReadConsistency.STRONG;
 import static org.apache.james.util.ReactorUtils.DEFAULT_CONCURRENCY;
 
 import java.security.SecureRandom;
@@ -382,7 +383,8 @@ public class CassandraMessageMapper implements MessageMapper {
     private Mono<FlagsUpdateStageResult> retryUpdatesStage(CassandraId mailboxId, FlagsUpdateCalculator flagsUpdateCalculator, List<ComposedMessageId> failed) {
         if (!failed.isEmpty()) {
             Flux<ComposedMessageIdWithMetaData> toUpdate = Flux.fromIterable(failed)
-                .flatMap(ids -> imapUidDAO.retrieve((CassandraMessageId) ids.getMessageId(), Optional.of((CassandraId) ids.getMailboxId())), DEFAULT_CONCURRENCY);
+                .flatMap(ids -> imapUidDAO.retrieve((CassandraMessageId) ids.getMessageId(), Optional.of((CassandraId) ids.getMailboxId()), STRONG),
+                    DEFAULT_CONCURRENCY);
             return runUpdateStage(mailboxId, toUpdate, flagsUpdateCalculator);
         } else {
             return Mono.empty();
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersService.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersService.java
index 18716c5..aa88ef9 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersService.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/RecomputeMailboxCountersService.java
@@ -19,6 +19,8 @@
 
 package org.apache.james.mailbox.cassandra.mail.task;
 
+import static org.apache.james.mailbox.cassandra.mail.CassandraMessageIdToImapUidDAO.ReadConsistency.STRONG;
+
 import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.ConcurrentLinkedDeque;
@@ -241,7 +243,7 @@ public class RecomputeMailboxCountersService {
         }
         CassandraMessageId messageId = (CassandraMessageId) message.getComposedMessageId().getMessageId();
 
-        return messageIdToImapUidDAO.retrieve(messageId, Optional.of(mailboxId))
+        return messageIdToImapUidDAO.retrieve(messageId, Optional.of(mailboxId), STRONG)
             .doOnNext(trustedMessage -> {
                 if (!trustedMessage.equals(message)) {
                     LOGGER.warn("Possible denormalization issue on {}. " +
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesService.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesService.java
index 49936ee..facd5d6 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesService.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMessageInconsistenciesService.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.mailbox.cassandra.mail.task;
 
+import static org.apache.james.mailbox.cassandra.mail.CassandraMessageIdToImapUidDAO.ReadConsistency.STRONG;
 import static org.apache.james.util.ReactorUtils.publishIfPresent;
 
 import java.time.Duration;
@@ -456,7 +457,7 @@ public class SolveMessageInconsistenciesService {
     }
 
     private Mono<Inconsistency> detectOutdatedMessageIdEntry(CassandraId mailboxId, CassandraMessageId messageId, ComposedMessageIdWithMetaData messageIdRecord) {
-        return messageIdToImapUidDAO.retrieve(messageId, Optional.of(mailboxId))
+        return messageIdToImapUidDAO.retrieve(messageId, Optional.of(mailboxId), STRONG)
             .filter(Predicate.not(Predicate.isEqual(messageIdRecord)))
             .<Inconsistency>map(upToDateMessageFromImapUid -> new OutdatedMessageIdEntry(messageIdRecord, upToDateMessageFromImapUid))
             .next()
@@ -464,7 +465,7 @@ public class SolveMessageInconsistenciesService {
     }
 
     private Mono<Inconsistency> detectOrphanImapUidEntry(ComposedMessageIdWithMetaData messageFromImapUid, CassandraId mailboxId, CassandraMessageId messageId) {
-        return messageIdToImapUidDAO.retrieve(messageId, Optional.of(mailboxId))
+        return messageIdToImapUidDAO.retrieve(messageId, Optional.of(mailboxId), STRONG)
             .next()
             .<Inconsistency>map(OrphanImapUidEntry::new)
             .switchIfEmpty(Mono.just(NO_INCONSISTENCY));
@@ -481,7 +482,7 @@ public class SolveMessageInconsistenciesService {
     }
 
     private Mono<Inconsistency> detectInconsistencyInMessageId(ComposedMessageIdWithMetaData message) {
-        return messageIdToImapUidDAO.retrieve((CassandraMessageId) message.getComposedMessageId().getMessageId(), Optional.of((CassandraId) message.getComposedMessageId().getMailboxId()))
+        return messageIdToImapUidDAO.retrieve((CassandraMessageId) message.getComposedMessageId().getMessageId(), Optional.of((CassandraId) message.getComposedMessageId().getMailboxId()), STRONG)
             .map(uidRecord -> NO_INCONSISTENCY)
             .next()
             .switchIfEmpty(detectOrphanMessageIdEntry(message))
diff --git a/src/site/xdoc/server/config-cassandra.xml b/src/site/xdoc/server/config-cassandra.xml
index d779602..6271984 100644
--- a/src/site/xdoc/server/config-cassandra.xml
+++ b/src/site/xdoc/server/config-cassandra.xml
@@ -149,6 +149,13 @@
         <dt><strong>mailbox.blob.part.size</strong></dt>
         <dd>Optional. Defaults to 102400 (100KB).<br/> Controls the size of blob parts used to store messages.</dd>
 
+        <dt><strong>message.read.strong.consistency</strong></dt>
+        <dd>Optional. Boolean, defaults to true. Disabling should be considered experimental.
+            If enabled, regular consistency level is used for read transactions for message. This might result
+            in stale reads as the system.paxos table will not be checked for latest updates. Better performance are expected
+            by turning it off. Note that reads performed as part of write transactions are always performed with a strong
+            consistency.</dd>
+
         <dt><strong>Allows specifying the driver default consistency level.</strong></dt>
         <dt><strong>cassandra.consistency_level.regular</strong></dt>
         <dd>Optional. Defaults to QUORUM.<br/> <a href="https://docs.datastax.com/en/cassandra-oss/3.x/cassandra/dml/dmlConfigConsistency.html">QUORUM, LOCAL_QUORUM, or EACH_QUORUM</a>.</dd>


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


[james-project] 08/13: JAMES 3400 Add mailbox create command

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

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

commit c7786ffe76faf747e6d3d0da94e205fb0f8949a1
Author: quanth <hq...@linagora.com>
AuthorDate: Mon Nov 30 11:27:45 2020 +0700

    JAMES 3400 Add mailbox create command
---
 .../java/org/apache/james/cli/WebAdminCli.java     |  2 +
 .../apache/james/cli/mailbox/MailboxCommand.java   | 57 ++++++++++++++
 .../james/cli/mailbox/MailboxCreateCommand.java    | 69 +++++++++++++++++
 .../org/apache/james/httpclient/MailboxClient.java | 31 ++++++++
 .../org/apache/james/cli/MailboxManageTest.java    | 90 ++++++++++++++++++++++
 5 files changed, 249 insertions(+)

diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java
index 71d20bb..74b450c 100644
--- a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/WebAdminCli.java
@@ -24,6 +24,7 @@ import java.util.Optional;
 import java.util.concurrent.Callable;
 
 import org.apache.james.cli.domain.DomainCommand;
+import org.apache.james.cli.mailbox.MailboxCommand;
 import org.apache.james.cli.user.UserCommand;
 import org.apache.james.httpclient.FeignClientFactory;
 import org.apache.james.httpclient.JwtToken;
@@ -75,6 +76,7 @@ public class WebAdminCli implements Callable<Integer> {
             .addSubcommand(new CommandLine.HelpCommand())
             .addSubcommand(new DomainCommand(out, parent, err))
             .addSubcommand(new UserCommand(out, parent, err))
+            .addSubcommand(new MailboxCommand(out, parent, err))
             .execute(args);
     }
 
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java
new file mode 100644
index 0000000..908e7dc
--- /dev/null
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCommand.java
@@ -0,0 +1,57 @@
+/******************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one     *
+ * or more contributor license agreements.  See the NOTICE file   *
+ * distributed with this work for additional information          *
+ * regarding copyright ownership.  The ASF licenses this file     *
+ * to you under the Apache License, Version 2.0 (the              *
+ * "License"); you may not use this file except in compliance     *
+ * with the License.  You may obtain a copy of the License at     *
+ *                                                                *
+ * http://www.apache.org/licenses/LICENSE-2.0                     *
+ *                                                                *
+ * Unless required by applicable law or agreed to in writing,     *
+ * software distributed under the License is distributed on an    *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY         *
+ * KIND, either express or implied.  See the License for the      *
+ * specific language governing permissions and limitations        *
+ * under the License.                                             *
+ ******************************************************************/
+
+package org.apache.james.cli.mailbox;
+
+import java.io.PrintStream;
+import java.util.concurrent.Callable;
+
+import org.apache.james.cli.WebAdminCli;
+import org.apache.james.httpclient.MailboxClient;
+
+import picocli.CommandLine;
+
+@CommandLine.Command(
+    name = "mailbox",
+    description = "Manage Mailboxes",
+    subcommands = {
+        MailboxCreateCommand.class
+    })
+public class MailboxCommand implements Callable<Integer> {
+
+    protected final WebAdminCli webAdminCli;
+    protected final PrintStream out;
+    protected final PrintStream err;
+
+    public MailboxCommand(PrintStream out, WebAdminCli webAdminCli, PrintStream err) {
+        this.out = out;
+        this.webAdminCli = webAdminCli;
+        this.err = err;
+    }
+
+    @Override
+    public Integer call() {
+        return WebAdminCli.CLI_FINISHED_SUCCEED;
+    }
+
+    public MailboxClient fullyQualifiedURL(String partOfUrl) {
+        return webAdminCli.feignClientFactory(err).target(MailboxClient.class, webAdminCli.jamesUrl + partOfUrl);
+    }
+
+}
\ No newline at end of file
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCreateCommand.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCreateCommand.java
new file mode 100644
index 0000000..46a7806
--- /dev/null
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/cli/mailbox/MailboxCreateCommand.java
@@ -0,0 +1,69 @@
+/******************************************************************
+ * 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.cli.mailbox;
+
+import java.util.concurrent.Callable;
+
+import org.apache.james.cli.WebAdminCli;
+import org.apache.james.httpclient.MailboxClient;
+
+import feign.Response;
+import picocli.CommandLine;
+
+@CommandLine.Command(
+    name = "create",
+    description = "Create a new mailbox")
+public class MailboxCreateCommand implements Callable<Integer> {
+
+    public static final int CREATED_CODE = 204;
+    public static final int BAD_REQUEST_CODE = 400;
+    public static final int USERNAME_NOT_EXIST_CODE = 404;
+
+    @CommandLine.ParentCommand MailboxCommand mailboxCommand;
+
+    @CommandLine.Parameters(description = "Username")
+    String userName;
+
+    @CommandLine.Parameters(description = "Mailbox's name to be created")
+    String mailboxName;
+
+    @Override
+    public Integer call() {
+        try {
+            MailboxClient mailboxClient = mailboxCommand.fullyQualifiedURL("/users");
+            Response rs = mailboxClient.createAMailbox(userName, mailboxName);
+            if (rs.status() == CREATED_CODE) {
+                mailboxCommand.out.println("The mailbox was created successfully.");
+                return WebAdminCli.CLI_FINISHED_SUCCEED;
+            } else if (rs.status() == BAD_REQUEST_CODE) {
+                mailboxCommand.err.println(rs.body());
+                return WebAdminCli.CLI_FINISHED_FAILED;
+            } else if (rs.status() == USERNAME_NOT_EXIST_CODE) {
+                mailboxCommand.err.println(rs.body());
+                return WebAdminCli.CLI_FINISHED_FAILED;
+            }
+            return WebAdminCli.CLI_FINISHED_FAILED;
+        } catch (Exception e) {
+            e.printStackTrace(mailboxCommand.err);
+            return WebAdminCli.CLI_FINISHED_FAILED;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/MailboxClient.java b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/MailboxClient.java
new file mode 100644
index 0000000..ab781dc
--- /dev/null
+++ b/server/protocols/webadmin-cli/src/main/java/org/apache/james/httpclient/MailboxClient.java
@@ -0,0 +1,31 @@
+/******************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one     *
+ * or more contributor license agreements.  See the NOTICE file   *
+ * distributed with this work for additional information          *
+ * regarding copyright ownership.  The ASF licenses this file     *
+ * to you under the Apache License, Version 2.0 (the              *
+ * "License"); you may not use this file except in compliance     *
+ * with the License.  You may obtain a copy of the License at     *
+ *                                                                *
+ * http://www.apache.org/licenses/LICENSE-2.0                     *
+ *                                                                *
+ * Unless required by applicable law or agreed to in writing,     *
+ * software distributed under the License is distributed on an    *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY         *
+ * KIND, either express or implied.  See the License for the      *
+ * specific language governing permissions and limitations        *
+ * under the License.                                             *
+ ******************************************************************/
+
+package org.apache.james.httpclient;
+
+import feign.Param;
+import feign.RequestLine;
+import feign.Response;
+
+public interface MailboxClient {
+
+    @RequestLine("PUT /{userNameToBeUsed}/mailboxes/{mailboxNameToBeCreated}")
+    Response createAMailbox(@Param("userNameToBeUsed") String userName, @Param("mailboxNameToBeCreated") String mailboxName);
+
+}
diff --git a/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/MailboxManageTest.java b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/MailboxManageTest.java
new file mode 100644
index 0000000..8452d0f
--- /dev/null
+++ b/server/protocols/webadmin-cli/src/test/java/org/apache/james/cli/MailboxManageTest.java
@@ -0,0 +1,90 @@
+/******************************************************************
+ * 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.cli;
+
+import static org.apache.james.MemoryJamesServerMain.IN_MEMORY_SERVER_AGGREGATE_MODULE;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import org.apache.james.GuiceJamesServer;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.modules.TestJMAPServerModule;
+import org.apache.james.util.Port;
+import org.apache.james.utils.DataProbeImpl;
+import org.apache.james.utils.WebAdminGuiceProbe;
+import org.apache.james.webadmin.integration.WebadminIntegrationTestModule;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class MailboxManageTest {
+
+    @RegisterExtension
+    static JamesServerExtension testExtension = new JamesServerBuilder<>(JamesServerBuilder.defaultConfigurationProvider())
+        .server(configuration -> GuiceJamesServer.forConfiguration(configuration)
+            .combineWith(IN_MEMORY_SERVER_AGGREGATE_MODULE)
+            .overrideWith(new WebadminIntegrationTestModule())
+            .overrideWith(new TestJMAPServerModule()))
+        .build();
+
+    private final ByteArrayOutputStream outputStreamCaptor = new ByteArrayOutputStream();
+    private final ByteArrayOutputStream errorStreamCaptor = new ByteArrayOutputStream();
+    private DataProbeImpl dataProbe;
+
+    @Test
+    void mailboxCreateWithExistedUsernameAndValidMailboxNameShouldSucceed() throws Exception {
+        dataProbe.fluent().addDomain("linagora.com")
+            .addUser("hqtran@linagora.com", "123456");
+
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "create", "hqtran@linagora.com", "INBOX");
+
+        WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "exist", "hqtran@linagora.com", "INBOX");
+
+        assertThat(exitCode).isEqualTo(0);
+        assertThat(outputStreamCaptor.toString().trim()).isEqualTo("The mailbox was created successfully.\n" +
+            "The mailbox exists.");
+    }
+
+    @Test
+    void mailboxCreateWithExistedUsernameAndInvalidMailboxNameShouldFail() throws Exception {
+        dataProbe.fluent().addDomain("linagora.com")
+            .addUser("hqtran@linagora.com", "123456");
+
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "create", "hqtran@linagora.com", "#&%*INBOX");
+
+        assertThat(exitCode).isEqualTo(1);
+        assertThat(errorStreamCaptor.toString()).contains("400");
+    }
+
+    @Test
+    void mailboxCreateWithNonExistingUsernameShouldFail() {
+        int exitCode = WebAdminCli.executeFluent(new PrintStream(outputStreamCaptor), new PrintStream(errorStreamCaptor),
+            "--url", "http://127.0.0.1:" + port.getValue(), "mailbox", "create", "hqtran@linagora.com", "INBOX");
+
+        assertThat(exitCode).isEqualTo(1);
+        assertThat(errorStreamCaptor.toString()).contains("404");
+    }
+
+}
\ 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] 05/13: JAMES-3463 Mailbox/get should read last state from MailboxChangesRepository

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

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

commit 11bfe3e0b883cfc77d503f1cfe9d71d8cf163df6
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Dec 15 12:38:35 2020 +0700

    JAMES-3463 Mailbox/get should read last state from MailboxChangesRepository
---
 .../rfc8621/contract/BackReferenceContract.scala   |  25 ++--
 .../contract/MailboxGetMethodContract.scala        | 115 +++++++++++++----
 .../contract/MailboxSetMethodContract.scala        | 143 +++++++++++++--------
 .../james/jmap/method/MailboxGetMethod.scala       |  12 +-
 4 files changed, 201 insertions(+), 94 deletions(-)

diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/BackReferenceContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/BackReferenceContract.scala
index 27cc525..535bfd1 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/BackReferenceContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/BackReferenceContract.scala
@@ -89,7 +89,9 @@ trait BackReferenceContract {
       .body()
       .asString()
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].state", "methodResponses[1][1].state")
+      .isEqualTo(
       s"""{
          |    "sessionState": "${SESSION_STATE.value}",
          |    "methodResponses": [
@@ -114,7 +116,6 @@ trait BackReferenceContract {
          |            "Mailbox/get",
          |            {
          |                "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |                "state": "${INSTANCE.value}",
          |                "list": [
          |                    {"id": "1"},
          |                    {"id": "5"},
@@ -168,7 +169,9 @@ trait BackReferenceContract {
       .body()
       .asString()
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].state")
+      .isEqualTo(
       s"""{
          |    "sessionState": "${SESSION_STATE.value}",
          |    "methodResponses": [
@@ -176,7 +179,6 @@ trait BackReferenceContract {
          |            "Mailbox/get",
          |            {
          |                "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |                "state": "${INSTANCE.value}",
          |                "list": [
          |                    {"id": "1"},
          |                    {"id": "5"},
@@ -238,7 +240,9 @@ trait BackReferenceContract {
       .body()
       .asString()
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].state")
+      .isEqualTo(
       s"""{
          |    "sessionState": "${SESSION_STATE.value}",
          |    "methodResponses": [
@@ -246,7 +250,6 @@ trait BackReferenceContract {
          |            "Mailbox/get",
          |            {
          |                "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |                "state": "${INSTANCE.value}",
          |                "list": [
          |                    {"id": "1"},
          |                    {"id": "5"},
@@ -308,7 +311,9 @@ trait BackReferenceContract {
       .body()
       .asString()
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].state")
+      .isEqualTo(
       s"""{
          |    "sessionState": "${SESSION_STATE.value}",
          |    "methodResponses": [
@@ -316,7 +321,6 @@ trait BackReferenceContract {
          |            "Mailbox/get",
          |            {
          |                "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |                "state": "${INSTANCE.value}",
          |                "list": [
          |                    {"id": "1"},
          |                    {"id": "5"},
@@ -378,7 +382,9 @@ trait BackReferenceContract {
       .body()
       .asString()
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].state")
+      .isEqualTo(
       s"""{
          |    "sessionState": "${SESSION_STATE.value}",
          |    "methodResponses": [
@@ -386,7 +392,6 @@ trait BackReferenceContract {
          |            "Mailbox/get",
          |            {
          |                "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |                "state": "${INSTANCE.value}",
          |                "list": [
          |                    {"id": "1"},
          |                    {"id": "5"},
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxGetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxGetMethodContract.scala
index 82f18c4..dce8b7d 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxGetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxGetMethodContract.scala
@@ -27,6 +27,8 @@ import io.restassured.RestAssured._
 import io.restassured.http.ContentType.JSON
 import javax.mail.Flags
 import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
+import net.javacrumbs.jsonunit.core.Option
+import net.javacrumbs.jsonunit.core.internal.Options
 import org.apache.http.HttpStatus.SC_OK
 import org.apache.james.GuiceJamesServer
 import org.apache.james.core.quota.{QuotaCountLimit, QuotaSizeLimit}
@@ -146,14 +148,15 @@ trait MailboxGetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [[
          |    "Mailbox/get",
          |    {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [
          |        {
          |          "id": "${mailboxId}",
@@ -215,14 +218,15 @@ trait MailboxGetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [[
          |    "Mailbox/get",
          |    {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [
          |        {
          |          "id": "${mailboxId}",
@@ -289,14 +293,15 @@ trait MailboxGetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [[
          |    "Mailbox/get",
          |    {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [
          |        {
          |          "id": "${mailboxId}",
@@ -364,14 +369,15 @@ trait MailboxGetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [[
          |    "Mailbox/get",
          |    {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [
          |        {
          |          "id": "${mailboxId}",
@@ -431,14 +437,15 @@ trait MailboxGetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [[
          |    "Mailbox/get",
          |    {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [
          |        {
          |          "id": "${mailboxId}"
@@ -480,14 +487,15 @@ trait MailboxGetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [[
          |    "Mailbox/get",
          |    {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [
          |        {
          |          "id": "${mailboxId}",
@@ -530,14 +538,15 @@ trait MailboxGetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [[
          |    "Mailbox/get",
          |    {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [
          |        {
          |          "id": "${mailboxId}",
@@ -581,14 +590,15 @@ trait MailboxGetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [[
          |    "Mailbox/get",
          |    {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [
          |        {
          |          "id": "${mailboxId}",
@@ -627,14 +637,15 @@ trait MailboxGetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [[
          |    "Mailbox/get",
          |    {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [],
          |      "notFound": ["invalid"]
          |    },
@@ -667,14 +678,15 @@ trait MailboxGetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [[
          |    "Mailbox/get",
          |    {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [],
          |      "notFound": ["#C42"]
          |    },
@@ -1208,14 +1220,15 @@ trait MailboxGetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [[
          |    "Mailbox/get",
          |      {
          |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |        "state": "${INSTANCE.value}",
          |        "list": [
          |          {
          |            "id": "${mailboxId}",
@@ -1282,14 +1295,15 @@ trait MailboxGetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [[
          |    "Mailbox/get",
          |      {
          |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |        "state": "${INSTANCE.value}",
          |        "list": [
          |          {
          |            "id": "${mailboxId}",
@@ -1525,4 +1539,57 @@ trait MailboxGetMethodContract {
          |    "c1"]]
          |}""".stripMargin)
   }
+
+  @Test
+  def mailboxStateShouldBeTheLatestOne(server: GuiceJamesServer): Unit = {
+    val mailboxProbe: MailboxProbeImpl = server.getProbe(classOf[MailboxProbeImpl])
+    mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
+
+    val response = `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(
+        s"""{
+           |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+           |  "methodCalls": [[
+           |     "Mailbox/get",
+           |     {
+           |       "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |       "ids": null,
+           |       "properties": ["id"]
+           |     },
+           |     "c1"],[
+           |      "Mailbox/changes",
+           |      {
+           |        "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |        "#sinceState": {
+           |           "resultOf":"c1",
+           |           "name":"Mailbox/get",
+           |           "path":"state"
+           |         }
+           |      },
+           |      "c2"]]
+           |}""".stripMargin)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .extract()
+      .body()
+      .asString()
+
+    // No changes as Mailbox/get state property is the latest one
+    assertThatJson(response)
+      .withOptions(new Options(Option.IGNORING_ARRAY_ORDER))
+      .whenIgnoringPaths("methodResponses[1][1].oldState", "methodResponses[1][1].newState")
+      .inPath("methodResponses[1][1]")
+      .isEqualTo(
+        s"""{
+           |  "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+           |  "hasMoreChanges": false,
+           |  "updatedProperties": [],
+           |  "created": [],
+           |  "updated": [],
+           |  "destroyed": []
+           |}""".stripMargin)
+  }
 }
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxSetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxSetMethodContract.scala
index 7b1a213..2d58022 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxSetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxSetMethodContract.scala
@@ -1336,7 +1336,9 @@ trait MailboxSetMethodContract {
       .getMailboxId("#private", BOB.asString(), "myMailbox")
       .serialize
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].state")
+      .isEqualTo(
       s"""{
          |	"sessionState": "${SESSION_STATE.value}",
          |	"methodResponses": [
@@ -1543,7 +1545,9 @@ trait MailboxSetMethodContract {
       .getMailboxId("#private", BOB.asString(), "myMailbox")
       .serialize
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -2222,7 +2226,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].state")
+      .isEqualTo(
       s"""{
          |    "sessionState": "${SESSION_STATE.value}",
          |    "methodResponses": [
@@ -2299,6 +2305,7 @@ trait MailboxSetMethodContract {
       .asString
 
     assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].state")
       .withOptions(new Options(Option.IGNORING_ARRAY_ORDER))
       .isEqualTo(
       s"""{
@@ -2986,7 +2993,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -3717,7 +3726,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -3789,7 +3800,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -3802,7 +3815,6 @@ trait MailboxSetMethodContract {
          |    }, "c1"],
          |    ["Mailbox/get", {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [{
          |        "id": "${childId.serialize}",
          |        "name": "child",
@@ -3931,7 +3943,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].state")
+      .isEqualTo(
       s"""{
          |    "sessionState": "${SESSION_STATE.value}",
          |    "methodResponses": [
@@ -3939,13 +3953,8 @@ trait MailboxSetMethodContract {
          |            "Mailbox/get",
          |            {
          |                "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |                "state": "${INSTANCE.value}",
-         |                "list": [
-         |
-         |                ],
-         |                "notFound": [
-         |                    "${mailboxId.serialize}"
-         |                ]
+         |                "list": [],
+         |                "notFound": ["${mailboxId.serialize}"]
          |            },
          |            "c1"
          |        ]
@@ -4114,7 +4123,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -4127,7 +4138,6 @@ trait MailboxSetMethodContract {
          |    }, "c2"],
          |    ["Mailbox/get", {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [{
          |        "id": "${mailboxId.serialize}",
          |        "isSubscribed": true
@@ -4191,7 +4201,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[2][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -4211,7 +4223,6 @@ trait MailboxSetMethodContract {
          |    }, "c3"],
          |    ["Mailbox/get", {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [{
          |        "id": "${mailboxId.serialize}",
          |        "isSubscribed": false
@@ -4265,7 +4276,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -4278,7 +4291,6 @@ trait MailboxSetMethodContract {
          |    }, "c3"],
          |    ["Mailbox/get", {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [{
          |        "id": "${mailboxId.serialize}",
          |        "isSubscribed": true
@@ -4337,7 +4349,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -4350,7 +4364,6 @@ trait MailboxSetMethodContract {
          |    }, "c3"],
          |    ["Mailbox/get", {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [{
          |        "id": "${mailboxId.serialize}",
          |        "isSubscribed": true,
@@ -4419,7 +4432,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[2][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -4439,7 +4454,6 @@ trait MailboxSetMethodContract {
          |    }, "c3"],
          |    ["Mailbox/get", {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [{
          |        "id": "${mailboxId.serialize}",
          |        "isSubscribed": false,
@@ -4640,7 +4654,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[2][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -4660,7 +4676,6 @@ trait MailboxSetMethodContract {
          |    }, "c3"],
          |    ["Mailbox/get", {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [{
          |        "id": "${mailboxId.serialize}",
          |        "isSubscribed": false,
@@ -5041,7 +5056,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -5054,7 +5071,6 @@ trait MailboxSetMethodContract {
          |    }, "c1"],
          |    ["Mailbox/get", {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [{
          |        "id": "${mailboxId.serialize}",
          |        "rights": {
@@ -5122,7 +5138,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -5135,7 +5153,6 @@ trait MailboxSetMethodContract {
          |    }, "c1"],
          |    ["Mailbox/get", {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [{
          |        "id": "${mailboxId.serialize}",
          |        "rights": {
@@ -5201,7 +5218,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -5217,7 +5236,6 @@ trait MailboxSetMethodContract {
          |    }, "c1"],
          |    ["Mailbox/get", {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [{
          |        "id": "${mailboxId.serialize}",
          |        "namespace": "Delegated[andre@domain.tld]",
@@ -5405,7 +5423,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -5792,7 +5812,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -5805,7 +5827,6 @@ trait MailboxSetMethodContract {
          |    }, "c1"],
          |    ["Mailbox/get", {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [{
          |        "id": "${mailboxId.serialize}",
          |        "name": "mailbox",
@@ -6145,7 +6166,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -6158,7 +6181,6 @@ trait MailboxSetMethodContract {
          |    }, "c1"],
          |    ["Mailbox/get", {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [{
          |        "id": "${mailboxId.serialize}",
          |        "name": "mailbox",
@@ -6217,7 +6239,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -6230,7 +6254,6 @@ trait MailboxSetMethodContract {
          |    }, "c1"],
          |    ["Mailbox/get", {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [{
          |        "id": "${mailboxId.serialize}",
          |        "name": "newName",
@@ -6290,7 +6313,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -6303,7 +6328,6 @@ trait MailboxSetMethodContract {
          |    }, "c1"],
          |    ["Mailbox/get", {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [{
          |        "id": "${mailboxId.serialize}",
          |        "name": "newName",
@@ -6362,7 +6386,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -6375,7 +6401,6 @@ trait MailboxSetMethodContract {
          |    }, "c1"],
          |    ["Mailbox/get", {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [{
          |        "id": "${mailboxId.serialize}",
          |        "name": "mailbox"
@@ -6433,7 +6458,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -6446,7 +6473,6 @@ trait MailboxSetMethodContract {
          |    }, "c1"],
          |    ["Mailbox/get", {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [{
          |        "id": "${mailboxId.serialize}",
          |        "name": "newName"
@@ -6505,7 +6531,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -6518,7 +6546,6 @@ trait MailboxSetMethodContract {
          |    }, "c1"],
          |    ["Mailbox/get", {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [{
          |        "id": "${mailboxId.serialize}",
          |        "name": "newName"
@@ -6574,7 +6601,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -6587,7 +6616,6 @@ trait MailboxSetMethodContract {
          |    }, "c1"],
          |    ["Mailbox/get", {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [{
          |        "id": "${mailboxId.serialize}",
          |        "name": "mailbox"
@@ -6645,7 +6673,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -6658,7 +6688,6 @@ trait MailboxSetMethodContract {
          |    }, "c1"],
          |    ["Mailbox/get", {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [{
          |        "id": "${mailboxId.serialize}",
          |        "parentId": "${parentId.serialize}",
@@ -6731,7 +6760,9 @@ trait MailboxSetMethodContract {
       .getMailboxId("#private", BOB.asString(), "parent")
       .serialize
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[2][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -6770,7 +6801,6 @@ trait MailboxSetMethodContract {
          |    }, "c2"],
          |    ["Mailbox/get", {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [{
          |        "id": "${mailboxId.serialize}",
          |        "name": "mailbox",
@@ -7305,7 +7335,9 @@ trait MailboxSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response).isEqualTo(
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].state")
+      .isEqualTo(
       s"""{
          |  "sessionState": "${SESSION_STATE.value}",
          |  "methodResponses": [
@@ -7318,7 +7350,6 @@ trait MailboxSetMethodContract {
          |    }, "c1"],
          |    ["Mailbox/get", {
          |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-         |      "state": "${INSTANCE.value}",
          |      "list": [{
          |        "id": "${parentId.serialize}",
          |        "name": "parent",
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxGetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxGetMethod.scala
index 85a8c6e..9f2b735 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxGetMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxGetMethod.scala
@@ -21,10 +21,12 @@ package org.apache.james.jmap.method
 
 import eu.timepit.refined.auto._
 import javax.inject.Inject
+import org.apache.james.jmap.api.change.MailboxChangeRepository
+import org.apache.james.jmap.api.model.{AccountId => JavaAccountId}
 import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_MAIL}
 import org.apache.james.jmap.core.Invocation.{Arguments, MethodName}
 import org.apache.james.jmap.core.State.INSTANCE
-import org.apache.james.jmap.core.{AccountId, CapabilityIdentifier, ErrorCode, Invocation, Properties}
+import org.apache.james.jmap.core.{AccountId, CapabilityIdentifier, ErrorCode, Invocation, Properties, State}
 import org.apache.james.jmap.http.MailboxesProvisioner
 import org.apache.james.jmap.json.{MailboxSerializer, ResponseSerializer}
 import org.apache.james.jmap.mail.MailboxGet.UnparsedMailboxId
@@ -54,9 +56,9 @@ object MailboxGetResults {
 case class MailboxGetResults(mailboxes: Set[Mailbox], notFound: NotFound) {
   def merge(other: MailboxGetResults): MailboxGetResults = MailboxGetResults(this.mailboxes ++ other.mailboxes, this.notFound.merge(other.notFound))
 
-  def asResponse(accountId: AccountId): MailboxGetResponse = MailboxGetResponse(
+  def asResponse(accountId: AccountId, state: State): MailboxGetResponse = MailboxGetResponse(
     accountId = accountId,
-    state = INSTANCE,
+    state = state,
     list = mailboxes.toList.sortBy(_.sortOrder),
     notFound = notFound)
 }
@@ -68,6 +70,7 @@ class MailboxGetMethod @Inject() (serializer: MailboxSerializer,
                                   mailboxIdFactory: MailboxId.Factory,
                                   mailboxFactory: MailboxFactory,
                                   provisioner: MailboxesProvisioner,
+                                  mailboxChangeRepository: MailboxChangeRepository,
                                   val metricFactory: MetricFactory,
                                   val sessionSupplier: SessionSupplier) extends MethodRequiringAccountId[MailboxGetRequest] {
   override val methodName: MethodName = MethodName("Mailbox/get")
@@ -78,7 +81,8 @@ class MailboxGetMethod @Inject() (serializer: MailboxSerializer,
     (requestedProperties -- Mailbox.allProperties match {
       case invalidProperties if invalidProperties.isEmpty() => getMailboxes(capabilities, request, mailboxSession)
         .reduce(MailboxGetResults.empty(), MailboxGetResults.merge)
-        .map(mailboxes => mailboxes.asResponse(request.accountId))
+        .flatMap(mailboxes => SMono(mailboxChangeRepository.getLatestState(JavaAccountId.fromUsername(mailboxSession.getUser)))
+          .map(state => mailboxes.asResponse(request.accountId, State.fromJava(state))))
         .map(mailboxGetResponse => Invocation(
           methodName = methodName,
           arguments = Arguments(serializer.serialize(mailboxGetResponse, requestedProperties, capabilities).as[JsObject]),


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