You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by rc...@apache.org on 2020/03/06 03:07:01 UTC

[james-project] branch master updated (ba2fbf8 -> 9e7eefb)

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

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


    from ba2fbf8  JAMES-3055 Update Deleted Messages Vault
     new 27fda4a  JAMES-2897 Set ConsistencyLevel to QUORUM at the request level where needed
     new 91a57ad  [Documentation] fix add domain aliases title in webadmin documentation
     new f10f7e0  [Fix] Stabilize CI by retry on flaky test
     new 890f20c  JAMES-3080 use reworked rabbitMQrestart method in tests and make the code more resilient
     new 439aa21  [Refactoring] do ack in mono when consuming workqueue
     new f07ba9a  JAMES-3081 add test to ensure messages are persisted on the rabbitmq mailqueue
     new 6021556  JAMES-3081 make the rabbitmq mailqueue messages persistent
     new 5322274  JAMES-3078 ADR for Reactor-netty adoption for JMAP server implementation
     new 00c9488  Update Java version
     new e60853a  Document publicly and publish our roadmap
     new 562b933  JAMES-3058 Define Cassandra specific routes directly in Cassandra product
     new 4b3ff23  JAMES-3058 Rest test indentation
     new 8138333  JAMES-3058 Expose task solving mailbox inconsistencies over webadmin
     new 548c6a7  JAMES-3058 WebAdmin documentation: solving mailbox inconsistencies
     new 046a791  JAMES-3058 Changelog entry: solving mailbox inconsistencies
     new ee2a84a  JAMES-3058 ReIndexing: product disclaimer
     new 1616eba  JAMES-3058 Fixed mailbox in solveInconsistencies task execution report
     new f31aaaf  JAMES-3058 Update admin instructions
     new ffcbce7  JAMES-3087 LDAP user listing should filter out users without id field
     new d31b8bf  JAMES-3067 Recursively find allowed From headers
     new 9e7eefb  JAMES-3067 Make RecipientRewriteTable configuration immutable

The 21 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 CHANGELOG.md                                       |   1 +
 .../cassandra/mail/CassandraAttachmentDAOV2.java   |   4 +-
 .../cassandra/mail/CassandraMailboxDAO.java        |   4 +-
 .../mail/CassandraMailboxPathDAOImpl.java          |   4 +-
 .../cassandra/mail/CassandraMessageDAO.java        |   4 +-
 .../task/SolveMailboxInconsistenciesService.java   |  41 ++--
 .../mail/task/SolveMailboxInconsistenciesTask.java |  20 +-
 ...nconsistenciesTaskAdditionalInformationDTO.java |   6 +-
 .../SolveMailboxInconsistenciesServiceTest.java    |   9 +-
 ...ailboxInconsistenciesTaskSerializationTest.java |   7 +-
 server/container/guice/cassandra-guice/pom.xml     |   6 +-
 .../org/apache/james/CassandraJamesServerMain.java |   6 +-
 .../data/CassandraRecipientRewriteTableModule.java |   4 +
 .../modules/webadmin}/CassandraRoutesModule.java   |   2 +-
 .../InconsistencySolvingRoutesModule.java}         |  34 ++-
 .../data/JPARecipientRewriteTableModule.java       |   4 +
 .../james/modules/data/MemoryDataModule.java       |   5 +
 server/container/guice/pom.xml                     |  12 --
 .../protocols/webadmin-cassandra-data/pom.xml      |  50 -----
 .../guice/protocols/webadmin-cassandra/pom.xml     |  53 -----
 .../META-INF/org/apache/james/spring-server.xml    |   1 +
 .../java/org/apache/james/util/StreamUtils.java    |  68 ++++++
 .../org/apache/james/util/StreamUtilsTest.java     |  70 +++++++
 server/data/data-api/pom.xml                       |   9 +
 .../james/rrt/api/AliasReverseResolver.java}       |  17 +-
 .../james/rrt/api/RecipientRewriteTable.java       |   2 +
 .../api/RecipientRewriteTableConfiguration.java    |  98 +++++++++
 .../RecipientRewriteTableConfigurationTest.java    |  83 ++++++++
 .../rrt/lib/AliasReverseResolverContract.java      | 167 +++++++++++++++
 .../apache/james/rrt/lib/CanSendFromContract.java  | 147 +++++++++++--
 .../CassandraRecipientRewriteTableV6Test.java      |   1 -
 .../CassandraRecipientRewriteTableV7Test.java      |   1 -
 .../james/rrt/cassandra/CassandraStepdefs.java     |   5 +-
 .../rrt/file/XMLRecipientRewriteTableTest.java     | 229 +++++++++++++++------
 .../org/apache/james/rrt/file/XMLStepdefs.java     |   4 +-
 .../rrt/jpa/JPARecipientRewriteTableTest.java      |   2 -
 .../java/org/apache/james/rrt/jpa/JPAStepdefs.java |   6 +-
 .../user/ldap/ReadOnlyUsersLDAPRepository.java     |  20 +-
 .../james/user/ldap/LdapGenericContainer.java      |  13 +-
 .../ReadOnlyUsersLDAPRepositoryInvalidDnTest.java  |  96 +++++++++
 .../user/ldap/ReadOnlyUsersLDAPRepositoryTest.java |  66 ++++--
 .../resources/{ => invalid}/ldif-files/Dockerfile  |   0
 .../resources/invalid}/ldif-files/populate.ldif    |   0
 .../rrt/lib/AbstractRecipientRewriteTable.java     |  50 ++---
 .../james/rrt/lib/AliasReverseResolverImpl.java    | 107 ++++++++++
 .../org/apache/james/rrt/lib/CanSendFromImpl.java  |  48 +----
 .../rrt/lib/AbstractRecipientRewriteTableTest.java |  44 ++--
 .../james/rrt/lib/RewriteTablesStepdefs.java       |  16 +-
 .../test/resources/cucumber/rewrite_tables.feature |   8 +-
 ...Test.java => AliasReverseResolverImplTest.java} |  21 +-
 .../apache/james/rrt/lib/CanSendFromImplTest.java  |   8 +-
 .../apache/james/rrt/memory/InMemoryStepdefs.java  |   5 +-
 .../memory/MemoryRecipientRewriteTableTest.java    |   7 +-
 .../methods/SetMessagesCreationProcessorTest.java  |   5 +-
 .../methods/SetMessagesUpdateProcessorTest.java    |   5 +-
 .../message/view/MessageFullViewFactoryTest.java   |  12 +-
 .../apache/james/smtpserver/SMTPServerTest.java    |   6 +-
 .../RabbitMQWebAdminServerIntegrationTest.java     |  36 +++-
 .../SolveMailboxInconsistenciesRequestToTask.java} |  15 +-
 .../james/webadmin/routes/UserRoutesTest.java      |   6 +-
 .../org/apache/james/queue/rabbitmq/Enqueuer.java  |   9 +
 .../queue/rabbitmq/RabbitMQMailQueueTest.java      |  30 +++
 .../distributed/RabbitMQWorkQueue.java             |  34 ++-
 .../RabbitMQWorkQueuePersistenceTest.java          |  20 +-
 src/adr/0019-reactor-netty-adoption.md             |  41 ++++
 src/homepage/index.html                            |  42 +++-
 .../server/manage-guice-distributed-james.md       |   7 +-
 src/site/markdown/server/manage-webadmin.md        |  64 +++++-
 68 files changed, 1570 insertions(+), 457 deletions(-)
 rename server/container/guice/{protocols/webadmin-cassandra/src/main/java/org/apache/james/modules/server => cassandra-guice/src/main/java/org/apache/james/modules/webadmin}/CassandraRoutesModule.java (99%)
 rename server/container/guice/{protocols/webadmin-cassandra-data/src/main/java/org/apache/james/modules/server/CassandraDataRoutesModules.java => cassandra-guice/src/main/java/org/apache/james/modules/webadmin/InconsistencySolvingRoutesModule.java} (53%)
 delete mode 100644 server/container/guice/protocols/webadmin-cassandra-data/pom.xml
 delete mode 100644 server/container/guice/protocols/webadmin-cassandra/pom.xml
 copy server/{blob/blob-api/src/test/java/org/apache/james/blob/api/BlobTypeTest.java => data/data-api/src/main/java/org/apache/james/rrt/api/AliasReverseResolver.java} (78%)
 create mode 100644 server/data/data-api/src/main/java/org/apache/james/rrt/api/RecipientRewriteTableConfiguration.java
 create mode 100644 server/data/data-api/src/test/java/org/apache/james/rrt/api/RecipientRewriteTableConfigurationTest.java
 create mode 100644 server/data/data-api/src/test/java/org/apache/james/rrt/lib/AliasReverseResolverContract.java
 create mode 100644 server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryInvalidDnTest.java
 copy server/data/data-ldap/src/test/resources/{ => invalid}/ldif-files/Dockerfile (100%)
 copy server/{container/guice/cassandra-ldap-guice/src/test/resources => data/data-ldap/src/test/resources/invalid}/ldif-files/populate.ldif (100%)
 create mode 100644 server/data/data-library/src/main/java/org/apache/james/rrt/lib/AliasReverseResolverImpl.java
 copy server/data/data-memory/src/test/java/org/apache/james/rrt/lib/{CanSendFromImplTest.java => AliasReverseResolverImplTest.java} (76%)
 copy server/protocols/webadmin/{webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/RecomputeAllFastViewProjectionItemsRequestToTask.java => webadmin-cassandra/src/main/java/org/apache/james/webadmin/routes/SolveMailboxInconsistenciesRequestToTask.java} (65%)
 create mode 100644 src/adr/0019-reactor-netty-adoption.md


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


[james-project] 03/21: [Fix] Stabilize CI by retry on flaky test

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

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

commit f10f7e06cd889d63b2900ff99ac7297d73f7f54f
Author: duc <dt...@linagora.com>
AuthorDate: Tue Mar 3 23:07:01 2020 +0700

    [Fix] Stabilize CI by retry on flaky test
    
    The projection is stored on the fly by another thread. We need to wait
    till the side effect happens.
---
 .../draft/model/message/view/MessageFullViewFactoryTest.java | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/model/message/view/MessageFullViewFactoryTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/model/message/view/MessageFullViewFactoryTest.java
index c725d7a..f6423e4 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/model/message/view/MessageFullViewFactoryTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/model/message/view/MessageFullViewFactoryTest.java
@@ -25,6 +25,7 @@ import static org.apache.james.jmap.draft.model.message.view.MessageViewFixture.
 import static org.apache.james.jmap.draft.model.message.view.MessageViewFixture.JACK_EMAIL;
 import static org.apache.james.jmap.draft.model.message.view.MessageViewFixture.JACOB_EMAIL;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
@@ -34,6 +35,7 @@ import java.nio.charset.StandardCharsets;
 import java.time.Instant;
 import java.util.List;
 import java.util.Optional;
+import java.util.concurrent.TimeUnit;
 
 import javax.mail.Flags;
 
@@ -73,6 +75,7 @@ import org.apache.james.util.ClassLoaderUtils;
 import org.apache.james.util.html.HtmlTextExtractor;
 import org.apache.james.util.mime.MessageContentExtractor;
 import org.assertj.core.api.SoftAssertions;
+import org.awaitility.core.ConditionFactory;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
@@ -88,6 +91,8 @@ class MessageFullViewFactoryTest {
     private static final Instant INTERNAL_DATE = Instant.parse("2012-02-03T14:30:42Z");
     private static final String DEFAULT_PREVIEW_AS_STRING = "blabla bloblo";
     private static final Preview DEFAULT_PREVIEW = Preview.from(DEFAULT_PREVIEW_AS_STRING);
+    private static final ConditionFactory AWAIT_CONDITION = await()
+            .timeout(new org.awaitility.Duration(5, TimeUnit.SECONDS));
 
     private MessageIdManager messageIdManager;
     private MailboxSession session;
@@ -879,9 +884,10 @@ class MessageFullViewFactoryTest {
                 .getMessages(ImmutableList.of(message1.getMessageId()), FetchGroup.FULL_CONTENT, session);
             messageFullViewFactory.fromMessageResults(messages);
 
-            assertThat(Mono.from(fastViewProjection.retrieve(message1.getMessageId())).block())
-                .extracting(MessageFastViewPrecomputedProperties::getPreview)
-                .isEqualTo(DEFAULT_PREVIEW);
+            AWAIT_CONDITION.untilAsserted(() ->
+                assertThat(Mono.from(fastViewProjection.retrieve(message1.getMessageId())).block())
+                    .extracting(MessageFastViewPrecomputedProperties::getPreview)
+                    .isEqualTo(DEFAULT_PREVIEW));
         }
 
         @Test


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


[james-project] 17/21: JAMES-3058 Fixed mailbox in solveInconsistencies task execution report

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

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

commit 1616ebaffdf60dccb4c3a627437ea5d0ca879e9e
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Feb 19 15:36:29 2020 +0700

    JAMES-3058 Fixed mailbox in solveInconsistencies task execution report
---
 .../task/SolveMailboxInconsistenciesService.java   | 41 ++++++++++++----------
 .../mail/task/SolveMailboxInconsistenciesTask.java | 12 ++++---
 ...nconsistenciesTaskAdditionalInformationDTO.java |  6 ++--
 .../SolveMailboxInconsistenciesServiceTest.java    |  9 ++---
 ...ailboxInconsistenciesTaskSerializationTest.java |  7 ++--
 5 files changed, 40 insertions(+), 35 deletions(-)

diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesService.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesService.java
index 7176713..b740233 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesService.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesService.java
@@ -34,6 +34,7 @@ import org.apache.james.mailbox.cassandra.mail.CassandraIdAndPath;
 import org.apache.james.mailbox.cassandra.mail.CassandraMailboxDAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraMailboxPathV2DAO;
 import org.apache.james.mailbox.model.Mailbox;
+import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.task.Task;
 import org.apache.james.task.Task.Result;
 import org.slf4j.Logger;
@@ -94,7 +95,7 @@ public class SolveMailboxInconsistenciesService {
             LOGGER.info("Inconsistency fixed for orphan mailbox {} - {}",
                 mailbox.getMailboxId().serialize(),
                 mailbox.generateAssociatedPath().asString());
-            context.incrementFixedInconsistencies();
+            context.addFixedInconsistency(mailbox.getMailboxId());
         }
     }
 
@@ -124,7 +125,7 @@ public class SolveMailboxInconsistenciesService {
                     LOGGER.info("Inconsistency fixed for orphan mailboxPath {} - {}",
                         pathRegistration.getCassandraId().serialize(),
                         pathRegistration.getMailboxPath().asString());
-                    context.incrementFixedInconsistencies();
+                    context.addFixedInconsistency(pathRegistration.getCassandraId());
                 })
                 .map(any -> Result.COMPLETED)
                 .switchIfEmpty(Mono.just(Result.COMPLETED))
@@ -177,13 +178,13 @@ public class SolveMailboxInconsistenciesService {
         static class Builder {
             private Optional<Long> processedMailboxEntries;
             private Optional<Long> processedMailboxPathEntries;
-            private Optional<Long> fixedInconsistencies;
+            private ImmutableList.Builder<MailboxId>  fixedInconsistencies;
             private ImmutableList.Builder<ConflictingEntry> conflictingEntries;
             private Optional<Long> errors;
 
             Builder() {
                 processedMailboxPathEntries = Optional.empty();
-                fixedInconsistencies = Optional.empty();
+                fixedInconsistencies = ImmutableList.builder();
                 conflictingEntries = ImmutableList.builder();
                 errors = Optional.empty();
                 processedMailboxEntries = Optional.empty();
@@ -199,8 +200,8 @@ public class SolveMailboxInconsistenciesService {
                 return this;
             }
 
-            public Builder fixedInconsistencies(long count) {
-                fixedInconsistencies = Optional.of(count);
+            public Builder addFixedInconsistencies(MailboxId mailboxId) {
+                fixedInconsistencies.add(mailboxId);
                 return this;
             }
 
@@ -218,7 +219,7 @@ public class SolveMailboxInconsistenciesService {
                 return new Context(
                     processedMailboxEntries.orElse(0L),
                     processedMailboxPathEntries.orElse(0L),
-                    fixedInconsistencies.orElse(0L),
+                    fixedInconsistencies.build(),
                     conflictingEntries.build(),
                     errors.orElse(0L));
             }
@@ -231,11 +232,13 @@ public class SolveMailboxInconsistenciesService {
         static class Snapshot {
             private final long processedMailboxEntries;
             private final long processedMailboxPathEntries;
-            private final long fixedInconsistencies;
+            private final ImmutableList<MailboxId> fixedInconsistencies;
             private final ImmutableList<ConflictingEntry> conflictingEntries;
             private final long errors;
 
-            private Snapshot(long processedMailboxEntries, long processedMailboxPathEntries, long fixedInconsistencies, ImmutableList<ConflictingEntry> conflictingEntries, long errors) {
+            private Snapshot(long processedMailboxEntries, long processedMailboxPathEntries,
+                             ImmutableList<MailboxId> fixedInconsistencies,
+                             ImmutableList<ConflictingEntry> conflictingEntries, long errors) {
                 this.processedMailboxEntries = processedMailboxEntries;
                 this.processedMailboxPathEntries = processedMailboxPathEntries;
                 this.fixedInconsistencies = fixedInconsistencies;
@@ -251,7 +254,7 @@ public class SolveMailboxInconsistenciesService {
                 return processedMailboxPathEntries;
             }
 
-            long getFixedInconsistencies() {
+            ImmutableList<MailboxId> getFixedInconsistencies() {
                 return fixedInconsistencies;
             }
 
@@ -296,26 +299,26 @@ public class SolveMailboxInconsistenciesService {
 
         private final AtomicLong processedMailboxEntries;
         private final AtomicLong processedMailboxPathEntries;
-        private final AtomicLong fixedInconsistencies;
+        private final ConcurrentLinkedDeque<MailboxId> fixedInconsistencies;
         private final ConcurrentLinkedDeque<ConflictingEntry> conflictingEntries;
         private final AtomicLong errors;
 
         Context() {
-            this(new AtomicLong(), new AtomicLong(), new AtomicLong(), ImmutableList.of(), new AtomicLong());
+            this(new AtomicLong(), new AtomicLong(), ImmutableList.of(), ImmutableList.of(), new AtomicLong());
         }
 
-        Context(long processedMailboxEntries, long processedMailboxPathEntries, long fixedInconsistencies, Collection<ConflictingEntry> conflictingEntries, long errors) {
+        Context(long processedMailboxEntries, long processedMailboxPathEntries, Collection<MailboxId> fixedInconsistencies, Collection<ConflictingEntry> conflictingEntries, long errors) {
             this(new AtomicLong(processedMailboxEntries),
                 new AtomicLong(processedMailboxPathEntries),
-                new AtomicLong(fixedInconsistencies),
+                fixedInconsistencies,
                 conflictingEntries,
                 new AtomicLong(errors));
         }
 
-        private Context(AtomicLong processedMailboxEntries, AtomicLong processedMailboxPathEntries, AtomicLong fixedInconsistencies, Collection<ConflictingEntry> conflictingEntries, AtomicLong errors) {
+        private Context(AtomicLong processedMailboxEntries, AtomicLong processedMailboxPathEntries, Collection<MailboxId> fixedInconsistencies, Collection<ConflictingEntry> conflictingEntries, AtomicLong errors) {
             this.processedMailboxEntries = processedMailboxEntries;
             this.processedMailboxPathEntries = processedMailboxPathEntries;
-            this.fixedInconsistencies = fixedInconsistencies;
+            this.fixedInconsistencies = new ConcurrentLinkedDeque<>(fixedInconsistencies);
             this.conflictingEntries = new ConcurrentLinkedDeque<>(conflictingEntries);
             this.errors = errors;
         }
@@ -328,8 +331,8 @@ public class SolveMailboxInconsistenciesService {
             processedMailboxPathEntries.incrementAndGet();
         }
 
-        void incrementFixedInconsistencies() {
-            fixedInconsistencies.incrementAndGet();
+        void addFixedInconsistency(MailboxId mailboxId) {
+            fixedInconsistencies.add(mailboxId);
         }
 
         void addConflictingEntries(ConflictingEntry conflictingEntry) {
@@ -344,7 +347,7 @@ public class SolveMailboxInconsistenciesService {
             return new Snapshot(
                 processedMailboxEntries.get(),
                 processedMailboxPathEntries.get(),
-                fixedInconsistencies.get(),
+                ImmutableList.copyOf(fixedInconsistencies),
                 ImmutableList.copyOf(conflictingEntries),
                 errors.get());
         }
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesTask.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesTask.java
index 8cbb7e8..2139d25 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesTask.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesTask.java
@@ -23,6 +23,7 @@ import java.time.Clock;
 import java.time.Instant;
 import java.util.Optional;
 
+import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.task.Task;
 import org.apache.james.task.TaskExecutionDetails;
 import org.apache.james.task.TaskType;
@@ -30,6 +31,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.steveash.guavate.Guavate;
 import com.google.common.collect.ImmutableList;
 
 public class SolveMailboxInconsistenciesTask implements Task {
@@ -41,11 +43,11 @@ public class SolveMailboxInconsistenciesTask implements Task {
         private final Instant instant;
         private final long processedMailboxEntries;
         private final long processedMailboxPathEntries;
-        private final long fixedInconsistencies;
+        private final ImmutableList<String> fixedInconsistencies;
         private final ImmutableList<ConflictingEntry> conflictingEntries;
         private final long errors;
 
-        Details(Instant instant, long processedMailboxEntries, long processedMailboxPathEntries, long fixedInconsistencies,
+        Details(Instant instant, long processedMailboxEntries, long processedMailboxPathEntries, ImmutableList<String> fixedInconsistencies,
                 ImmutableList<ConflictingEntry> conflictingEntries, long errors) {
             this.instant = instant;
             this.processedMailboxEntries = processedMailboxEntries;
@@ -71,7 +73,7 @@ public class SolveMailboxInconsistenciesTask implements Task {
         }
 
         @JsonProperty("fixedInconsistencies")
-        long getFixedInconsistencies() {
+        ImmutableList<String> getFixedInconsistencies() {
             return fixedInconsistencies;
         }
 
@@ -110,7 +112,9 @@ public class SolveMailboxInconsistenciesTask implements Task {
         return Optional.of(new Details(Clock.systemUTC().instant(),
             snapshot.getProcessedMailboxEntries(),
             snapshot.getProcessedMailboxPathEntries(),
-            snapshot.getFixedInconsistencies(),
+            snapshot.getFixedInconsistencies().stream()
+                .map(MailboxId::serialize)
+                .collect(Guavate.toImmutableList()),
             snapshot.getConflictingEntries(),
             snapshot.getErrors()));
     }
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesTaskAdditionalInformationDTO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesTaskAdditionalInformationDTO.java
index b23792b..d74e706 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesTaskAdditionalInformationDTO.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesTaskAdditionalInformationDTO.java
@@ -52,7 +52,7 @@ public class SolveMailboxInconsistenciesTaskAdditionalInformationDTO implements
     private final String type;
     private final long processedMailboxEntries;
     private final long processedMailboxPathEntries;
-    private final long fixedInconsistencies;
+    private final ImmutableList<String> fixedInconsistencies;
     private final ImmutableList<ConflictingEntry> conflictingEntries;
     private final long errors;
     private final Instant timestamp;
@@ -60,7 +60,7 @@ public class SolveMailboxInconsistenciesTaskAdditionalInformationDTO implements
     public SolveMailboxInconsistenciesTaskAdditionalInformationDTO(@JsonProperty("type") String type,
                                                                    @JsonProperty("processedMailboxEntries") long processedMailboxEntries,
                                                                    @JsonProperty("processedMailboxPathEntries") long processedMailboxPathEntries,
-                                                                   @JsonProperty("fixedInconsistencies") long fixedInconsistencies,
+                                                                   @JsonProperty("fixedInconsistencies") ImmutableList<String> fixedInconsistencies,
                                                                    @JsonProperty("conflictingEntries") ImmutableList<ConflictingEntry> conflictingEntries,
                                                                    @JsonProperty("errors") long errors,
                                                                    @JsonProperty("timestamp") Instant timestamp) {
@@ -81,7 +81,7 @@ public class SolveMailboxInconsistenciesTaskAdditionalInformationDTO implements
         return processedMailboxPathEntries;
     }
 
-    public long getFixedInconsistencies() {
+    public ImmutableList<String> getFixedInconsistencies() {
         return fixedInconsistencies;
     }
 
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesServiceTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesServiceTest.java
index 6db7ae0..804d6f9 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesServiceTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesServiceTest.java
@@ -47,8 +47,6 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
-import nl.jqno.equalsverifier.EqualsVerifier;
-
 class SolveMailboxInconsistenciesServiceTest {
     private static final UidValidity UID_VALIDITY_1 = UidValidity.ofValid(145);
     private static final UidValidity UID_VALIDITY_2 = UidValidity.ofValid(147);
@@ -203,7 +201,7 @@ class SolveMailboxInconsistenciesServiceTest {
         assertThat(context.snapshot())
             .isEqualTo(Context.builder()
                 .processedMailboxEntries(1)
-                .fixedInconsistencies(1)
+                .addFixedInconsistencies(MAILBOX.getMailboxId())
                 .build()
                 .snapshot());
     }
@@ -218,7 +216,7 @@ class SolveMailboxInconsistenciesServiceTest {
         assertThat(context.snapshot())
             .isEqualTo(Context.builder()
                 .processedMailboxPathEntries(1)
-                .fixedInconsistencies(1)
+                .addFixedInconsistencies(CASSANDRA_ID_1)
                 .build()
                 .snapshot());
     }
@@ -236,7 +234,6 @@ class SolveMailboxInconsistenciesServiceTest {
             .isEqualTo(Context.builder()
                 .processedMailboxEntries(2)
                 .processedMailboxPathEntries(1)
-                .fixedInconsistencies(0)
                 .addConflictingEntry(ConflictingEntry.builder()
                     .mailboxDaoEntry(MAILBOX)
                     .mailboxPathDaoEntry(MAILBOX_PATH, CASSANDRA_ID_2))
@@ -256,7 +253,7 @@ class SolveMailboxInconsistenciesServiceTest {
             .isEqualTo(Context.builder()
                 .processedMailboxEntries(1)
                 .processedMailboxPathEntries(1)
-                .fixedInconsistencies(1)
+                .addFixedInconsistencies(CASSANDRA_ID_1)
                 .addConflictingEntry(ConflictingEntry.builder()
                     .mailboxDaoEntry(MAILBOX)
                     .mailboxPathDaoEntry(NEW_MAILBOX_PATH, CASSANDRA_ID_1))
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesTaskSerializationTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesTaskSerializationTest.java
index 0ec35ca..f490060 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesTaskSerializationTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesTaskSerializationTest.java
@@ -38,7 +38,8 @@ class SolveMailboxInconsistenciesTaskSerializationTest {
     private static final Username USERNAME = Username.of("user");
     private static final MailboxPath MAILBOX_PATH = new MailboxPath(MailboxConstants.USER_NAMESPACE, USERNAME, "mailboxName");
     private static final MailboxPath MAILBOX_PATH_2 = new MailboxPath(MailboxConstants.USER_NAMESPACE, USERNAME, "mailboxName2");
-    private static final CassandraId MAILBOX_ID = CassandraId.of(UUID.fromString("464765a0-e4e7-11e4-aba4-710c1de3782b"));
+    public static final String MAILBOX_ID_AS_STRING = "464765a0-e4e7-11e4-aba4-710c1de3782b";
+    private static final CassandraId MAILBOX_ID = CassandraId.of(UUID.fromString(MAILBOX_ID_AS_STRING));
 
     private static final SolveMailboxInconsistenciesService SERVICE = mock(SolveMailboxInconsistenciesService.class);
     private static final SolveMailboxInconsistenciesTask TASK = new SolveMailboxInconsistenciesTask(SERVICE);
@@ -47,12 +48,12 @@ class SolveMailboxInconsistenciesTaskSerializationTest {
         .mailboxDaoEntry(MAILBOX_PATH, MAILBOX_ID)
         .mailboxPathDaoEntry(MAILBOX_PATH_2, MAILBOX_ID);
     private static final ImmutableList<ConflictingEntry> CONFLICTING_ENTRIES = ImmutableList.of(CONFLICTING_ENTRY);
-    private static final SolveMailboxInconsistenciesTask.Details DETAILS = new SolveMailboxInconsistenciesTask.Details(TIMESTAMP, 0, 1, 2, CONFLICTING_ENTRIES, 3);
+    private static final SolveMailboxInconsistenciesTask.Details DETAILS = new SolveMailboxInconsistenciesTask.Details(TIMESTAMP, 0, 1, ImmutableList.of(MAILBOX_ID_AS_STRING), CONFLICTING_ENTRIES, 3);
     private static final String SERIALIZED_ADDITIONAL_INFORMATION = "{" +
         "  \"type\":\"solve-mailbox-inconsistencies\"," +
         "  \"processedMailboxEntries\":0," +
         "  \"processedMailboxPathEntries\":1," +
-        "  \"fixedInconsistencies\":2," +
+        "  \"fixedInconsistencies\": [\"464765a0-e4e7-11e4-aba4-710c1de3782b\"]," +
         "  \"conflictingEntries\":[{" +
         "    \"mailboxDaoEntry\":{" +
         "      \"mailboxPath\":\"#private:user:mailboxName\"," +


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


[james-project] 02/21: [Documentation] fix add domain aliases title in webadmin documentation

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

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

commit 91a57adcc993c5c84a6edacb288600f896ebe739
Author: Rémi KOWALSKI <rk...@linagora.com>
AuthorDate: Thu Mar 5 10:07:44 2020 +0100

    [Documentation] fix add domain aliases title in webadmin documentation
---
 src/site/markdown/server/manage-webadmin.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/site/markdown/server/manage-webadmin.md b/src/site/markdown/server/manage-webadmin.md
index a4a9666..dcc35c6 100644
--- a/src/site/markdown/server/manage-webadmin.md
+++ b/src/site/markdown/server/manage-webadmin.md
@@ -40,7 +40,7 @@ Finally, please note that in case of a malformed URL the 400 bad request respons
  - [Cassandra Schema upgrades](#Cassandra_Schema_upgrades)
  - [Correcting ghost mailbox](#Correcting_ghost_mailbox)
  - [Creating address aliases](#Creating_address_aliases)
- - [Creating address domain](#Creating_address_domain)
+ - [Creating address domain aliases](#Creating_address_domain_aliases)
  - [Creating address forwards](#Creating_address_forwards)
  - [Creating address group](#Creating_address_group)
  - [Creating regex mapping](#Creating_regex_mapping)
@@ -1765,7 +1765,7 @@ Response codes:
  - 204: OK
  - 400: Alias structure or member is not valid
 
-## Creating address domain
+## Creating address domain aliases
 
 You can use **webadmin** to define domain mappings.
 


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


[james-project] 05/21: [Refactoring] do ack in mono when consuming workqueue

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

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

commit 439aa21cf6340ec347488eb4cbc7901f33f67547
Author: Rémi KOWALSKI <rk...@linagora.com>
AuthorDate: Tue Mar 3 14:15:33 2020 +0100

    [Refactoring] do ack in mono when consuming workqueue
---
 .../task/eventsourcing/distributed/RabbitMQWorkQueue.java    | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/server/task/task-distributed/src/main/java/org/apache/james/task/eventsourcing/distributed/RabbitMQWorkQueue.java b/server/task/task-distributed/src/main/java/org/apache/james/task/eventsourcing/distributed/RabbitMQWorkQueue.java
index 005b86d..93208fe 100644
--- a/server/task/task-distributed/src/main/java/org/apache/james/task/eventsourcing/distributed/RabbitMQWorkQueue.java
+++ b/server/task/task-distributed/src/main/java/org/apache/james/task/eventsourcing/distributed/RabbitMQWorkQueue.java
@@ -129,13 +129,13 @@ public class RabbitMQWorkQueue implements WorkQueue {
     }
 
     private Mono<Task.Result> executeTask(AcknowledgableDelivery delivery) {
-        delivery.ack();
-        String json = new String(delivery.getBody(), StandardCharsets.UTF_8);
-
         TaskId taskId = TaskId.fromString(delivery.getProperties().getHeaders().get(TASK_ID).toString());
-
-        return deserialize(json, taskId)
-            .flatMap(task -> executeOnWorker(taskId, task));
+        return Mono.fromCallable(() -> {
+            delivery.ack();
+            return new String(delivery.getBody(), StandardCharsets.UTF_8);
+        }).flatMap(json ->
+            deserialize(json, taskId)
+                .flatMap(task -> executeOnWorker(taskId, task)));
     }
 
     private Mono<Task> deserialize(String json, TaskId taskId) {


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


[james-project] 11/21: JAMES-3058 Define Cassandra specific routes directly in Cassandra product

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

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

commit 562b933c5cca18237edc9aeef5b6b5324ca3753e
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Feb 14 14:22:24 2020 +0700

    JAMES-3058 Define Cassandra specific routes directly in Cassandra product
---
 server/container/guice/cassandra-guice/pom.xml     |  6 +--
 .../org/apache/james/CassandraJamesServerMain.java |  6 +--
 .../modules/webadmin}/CassandraRoutesModule.java   |  2 +-
 .../InconsistencySolvingRoutesModule.java}         | 20 +++++---
 server/container/guice/pom.xml                     | 12 -----
 .../protocols/webadmin-cassandra-data/pom.xml      | 50 --------------------
 .../guice/protocols/webadmin-cassandra/pom.xml     | 53 ----------------------
 7 files changed, 20 insertions(+), 129 deletions(-)

diff --git a/server/container/guice/cassandra-guice/pom.xml b/server/container/guice/cassandra-guice/pom.xml
index 01219bb..71649de 100644
--- a/server/container/guice/cassandra-guice/pom.xml
+++ b/server/container/guice/cassandra-guice/pom.xml
@@ -220,15 +220,15 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
-            <artifactId>james-server-guice-webadmin-cassandra</artifactId>
+            <artifactId>james-server-guice-webadmin-data</artifactId>
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
-            <artifactId>james-server-guice-webadmin-cassandra-data</artifactId>
+            <artifactId>james-server-webadmin-cassandra</artifactId>
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
-            <artifactId>james-server-guice-webadmin-data</artifactId>
+            <artifactId>james-server-webadmin-cassandra-data</artifactId>
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/CassandraJamesServerMain.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/CassandraJamesServerMain.java
index 1f5cd85..49a3a86 100644
--- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/CassandraJamesServerMain.java
+++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/CassandraJamesServerMain.java
@@ -51,8 +51,6 @@ import org.apache.james.modules.protocols.ManageSieveServerModule;
 import org.apache.james.modules.protocols.POP3ServerModule;
 import org.apache.james.modules.protocols.ProtocolHandlerModule;
 import org.apache.james.modules.protocols.SMTPServerModule;
-import org.apache.james.modules.server.CassandraDataRoutesModules;
-import org.apache.james.modules.server.CassandraRoutesModule;
 import org.apache.james.modules.server.DKIMMailetModule;
 import org.apache.james.modules.server.DLPRoutesModule;
 import org.apache.james.modules.server.DataRoutesModules;
@@ -70,6 +68,8 @@ import org.apache.james.modules.server.TaskManagerModule;
 import org.apache.james.modules.server.WebAdminServerModule;
 import org.apache.james.modules.spamassassin.SpamAssassinListenerModule;
 import org.apache.james.modules.vault.DeletedMessageVaultRoutesModule;
+import org.apache.james.modules.webadmin.CassandraRoutesModule;
+import org.apache.james.modules.webadmin.InconsistencySolvingRoutesModule;
 import org.apache.james.server.core.configuration.Configuration;
 
 import com.google.common.collect.ImmutableSet;
@@ -82,10 +82,10 @@ public class CassandraJamesServerMain {
 
     public static final Module WEBADMIN = Modules.combine(
         new CassandraRoutesModule(),
-        new CassandraDataRoutesModules(),
         new DataRoutesModules(),
         new DeletedMessageVaultRoutesModule(),
         new DLPRoutesModule(),
+        new InconsistencySolvingRoutesModule(),
         new JmapTasksModule(),
         new MailboxRoutesModule(),
         new MailQueueRoutesModule(),
diff --git a/server/container/guice/protocols/webadmin-cassandra/src/main/java/org/apache/james/modules/server/CassandraRoutesModule.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/webadmin/CassandraRoutesModule.java
similarity index 99%
rename from server/container/guice/protocols/webadmin-cassandra/src/main/java/org/apache/james/modules/server/CassandraRoutesModule.java
rename to server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/webadmin/CassandraRoutesModule.java
index a4bdd83..8812df0 100644
--- a/server/container/guice/protocols/webadmin-cassandra/src/main/java/org/apache/james/modules/server/CassandraRoutesModule.java
+++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/webadmin/CassandraRoutesModule.java
@@ -17,7 +17,7 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.modules.server;
+package org.apache.james.modules.webadmin;
 
 import org.apache.james.backends.cassandra.migration.CassandraMigrationService;
 import org.apache.james.backends.cassandra.migration.Migration;
diff --git a/server/container/guice/protocols/webadmin-cassandra-data/src/main/java/org/apache/james/modules/server/CassandraDataRoutesModules.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/webadmin/InconsistencySolvingRoutesModule.java
similarity index 70%
rename from server/container/guice/protocols/webadmin-cassandra-data/src/main/java/org/apache/james/modules/server/CassandraDataRoutesModules.java
rename to server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/webadmin/InconsistencySolvingRoutesModule.java
index 8c2b4ee..fc49a21 100644
--- a/server/container/guice/protocols/webadmin-cassandra-data/src/main/java/org/apache/james/modules/server/CassandraDataRoutesModules.java
+++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/webadmin/InconsistencySolvingRoutesModule.java
@@ -17,7 +17,7 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.modules.server;
+package org.apache.james.modules.webadmin;
 
 import org.apache.james.webadmin.Routes;
 import org.apache.james.webadmin.routes.CassandraMappingsRoutes;
@@ -27,14 +27,20 @@ import com.google.inject.AbstractModule;
 import com.google.inject.Scopes;
 import com.google.inject.multibindings.Multibinder;
 
-public class CassandraDataRoutesModules extends AbstractModule {
+public class InconsistencySolvingRoutesModule extends AbstractModule {
+    public static class SolveRRTInconsistenciesModules extends AbstractModule {
+        @Override
+        protected void configure() {
+            bind(CassandraMappingsRoutes.class).in(Scopes.SINGLETON);
+            bind(CassandraMappingsService.class).in(Scopes.SINGLETON);
+
+            Multibinder<Routes> routesMultibinder = Multibinder.newSetBinder(binder(), Routes.class);
+            routesMultibinder.addBinding().to(CassandraMappingsRoutes.class);
+        }
+    }
 
     @Override
     protected void configure() {
-        bind(CassandraMappingsRoutes.class).in(Scopes.SINGLETON);
-        bind(CassandraMappingsService.class).in(Scopes.SINGLETON);
-
-        Multibinder<Routes> routesMultibinder = Multibinder.newSetBinder(binder(), Routes.class);
-        routesMultibinder.addBinding().to(CassandraMappingsRoutes.class);
+        install(new SolveRRTInconsistenciesModules());
     }
 }
diff --git a/server/container/guice/pom.xml b/server/container/guice/pom.xml
index 9cefaf5..a7d55fd 100644
--- a/server/container/guice/pom.xml
+++ b/server/container/guice/pom.xml
@@ -66,8 +66,6 @@
         <module>protocols/pop</module>
         <module>protocols/smtp</module>
         <module>protocols/webadmin</module>
-        <module>protocols/webadmin-cassandra</module>
-        <module>protocols/webadmin-cassandra-data</module>
         <module>protocols/webadmin-data</module>
         <module>protocols/webadmin-jmap</module>
         <module>protocols/webadmin-mailbox</module>
@@ -182,16 +180,6 @@
             </dependency>
             <dependency>
                 <groupId>${james.groupId}</groupId>
-                <artifactId>james-server-guice-webadmin-cassandra</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>${james.groupId}</groupId>
-                <artifactId>james-server-guice-webadmin-cassandra-data</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>${james.groupId}</groupId>
                 <artifactId>james-server-guice-webadmin-data</artifactId>
                 <version>${project.version}</version>
             </dependency>
diff --git a/server/container/guice/protocols/webadmin-cassandra-data/pom.xml b/server/container/guice/protocols/webadmin-cassandra-data/pom.xml
deleted file mode 100644
index 164eebe..0000000
--- a/server/container/guice/protocols/webadmin-cassandra-data/pom.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements. See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership. The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License. You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied. See the License for the
-    specific language governing permissions and limitations
-    under the License.
--->
-
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <modelVersion>4.0.0</modelVersion>
-    <parent>
-        <groupId>org.apache.james</groupId>
-        <artifactId>james-server-guice</artifactId>
-        <version>3.5.0-SNAPSHOT</version>
-        <relativePath>../../pom.xml</relativePath>
-    </parent>
-
-    <artifactId>james-server-guice-webadmin-cassandra-data</artifactId>
-
-    <name>Apache James :: Server :: Guice :: Webadmin :: Cassandra :: Data</name>
-    <description>Webadmin cassandra data modules for Guice implementation of James server</description>
-
-    <dependencies>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
-            <artifactId>james-server-webadmin-cassandra-data</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
-            <artifactId>testing-base</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>com.google.inject</groupId>
-            <artifactId>guice</artifactId>
-        </dependency>
-    </dependencies>
-</project>
\ No newline at end of file
diff --git a/server/container/guice/protocols/webadmin-cassandra/pom.xml b/server/container/guice/protocols/webadmin-cassandra/pom.xml
deleted file mode 100644
index 55d0760..0000000
--- a/server/container/guice/protocols/webadmin-cassandra/pom.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements. See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership. The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License. You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied. See the License for the
-    specific language governing permissions and limitations
-    under the License.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <modelVersion>4.0.0</modelVersion>
-    <parent>
-        <groupId>org.apache.james</groupId>
-        <artifactId>james-server-guice</artifactId>
-        <version>3.5.0-SNAPSHOT</version>
-        <relativePath>../../pom.xml</relativePath>
-    </parent>
-
-    <artifactId>james-server-guice-webadmin-cassandra</artifactId>
-
-    <name>Apache James :: Server :: Guice :: Webadmin :: Cassandra</name>
-    <description>Webadmin cassandra modules for Guice implementation of James server</description>
-
-    <dependencies>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
-            <artifactId>james-server-webadmin-cassandra</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
-            <artifactId>james-server-data-cassandra</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>${james.groupId}</groupId>
-            <artifactId>testing-base</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>com.google.inject</groupId>
-            <artifactId>guice</artifactId>
-        </dependency>
-    </dependencies>
-</project>


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


[james-project] 12/21: JAMES-3058 Rest test indentation

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

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

commit 4b3ff23dd1f26b13836c5ba8d49b435f0527f7d5
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Feb 14 14:25:15 2020 +0700

    JAMES-3058 Rest test indentation
---
 .../integration/rabbitmq/RabbitMQWebAdminServerIntegrationTest.java   | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerIntegrationTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerIntegrationTest.java
index 5d04c9f..0d425d1 100644
--- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerIntegrationTest.java
+++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerIntegrationTest.java
@@ -145,7 +145,7 @@ class RabbitMQWebAdminServerIntegrationTest extends WebAdminServerIntegrationTes
 
         String taskId = with()
             .queryParam("action", "SolveInconsistencies")
-            .post(CassandraMappingsRoutes.ROOT_PATH)
+        .post(CassandraMappingsRoutes.ROOT_PATH)
             .jsonPath()
             .get("taskId");
 
@@ -169,7 +169,7 @@ class RabbitMQWebAdminServerIntegrationTest extends WebAdminServerIntegrationTes
     void getSwaggerShouldContainDistributedEndpoints() {
         when()
             .get(SwaggerRoutes.SWAGGER_ENDPOINT)
-            .then()
+        .then()
             .statusCode(HttpStatus.OK_200)
             .body(containsString("\"tags\":[\"Cassandra Mappings Operations\"]"))
             .body(containsString("{\"name\":\"MessageIdReIndexing\"}"));


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


[james-project] 10/21: Document publicly and publish our roadmap

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

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

commit e60853a014ac189387494de4ca42b00a38c60f8b
Author: Raphael Ouazana <ra...@linagora.com>
AuthorDate: Thu Feb 27 13:43:12 2020 +0100

    Document publicly and publish our roadmap
---
 src/homepage/index.html | 38 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/src/homepage/index.html b/src/homepage/index.html
index b10c01d..8c24357 100644
--- a/src/homepage/index.html
+++ b/src/homepage/index.html
@@ -48,6 +48,7 @@ layout: default
         <li class="logo_li"><span class="logo"><img src="images/james-alt.svg" alt="james logo" /></span></li>
         <li><a href="#intro" class="active">About</a></li>
         <li><a href="#first">Get Started</a></li>
+        <li><a href="#roadmap">Roadmap</a></li>
         <li><a href="#posts">Last Posts</a></li>
         <li><a href="#second">Community</a></li>
         <li><a href="#third">Contribute</a></li>
@@ -126,6 +127,42 @@ WHAT WILL YOU TRY:</b><br>
           </section>
         </section>
 
+      <!-- Roadmap -->
+        <section id="roadmap" class="main">
+          <header class="major">
+            <h2>Roadmap</h2>
+          </header>
+          <section>
+            <ul class="james-ul">
+              <li class="post-template"><span><b>MAIN GOALS</b><br>
+                <ul>
+                  <li><span class="long-arrow-right">&#8594;</span>Providing a <b>distributed mail server</b>,
+                    fully horizontally scalable.</li>
+                  <li><span class="long-arrow-right">&#8594;</span><span>Maintaining a <b>standalone version</b>
+                    suitable for a single server, based on <b>JPA</b>.</span></li>
+                </ul>
+              </li>
+              <li class="post-template"><span><b>INCOMING FEATURES</b><br>
+                <ul>
+                  <li><span class="long-arrow-right">&#8594;</span><span><b>Blobs garbage collector</b>:
+                    the deduplication mechanism used to save some spaces makes it complicated to remove blobs. We are implementing a
+                    garbage collector responsible for removing the no more used blobs.
+                    See this <a href="https://github.com/linagora/james-project/pull/3122">ADR</a> (Architecture Decision Record) for more explanation.</span></li>
+                  <li><span class="long-arrow-right">&#8594;</span><span>Update the currently implemented JMAP draft specification
+                    to the <b>new JMAP RFC</b>. See this
+                    <a href="https://github.com/apache/james-project/blob/master/src/adr/0018-jmap-new-specs.md">ADR</a>
+                    for more information.</span></li>
+                  <li><span class="long-arrow-right">&#8594;</span><span>Enhance our eventual consistency model by providing
+                    <b>tools for reparing projections</b>. See this
+                    <a href="https://github.com/linagora/james-project/pull/3125">discussion</a> for more information.</span></li>
+                  <li><span class="long-arrow-right">&#8594;</span><span>Feel free to add your own items here: see <a href="#third">Contribute</a>.</span></li>
+                </ul>
+              </span>
+              </li>
+            </ul>
+          </section>
+        </section>
+
     <!-- Last Posts -->
       <section id="posts" class="main">
         <header class="major">
@@ -368,6 +405,7 @@ WHAT WILL YOU TRY:</b><br>
         <ul class="no-padding">
           <li class="no-padding"><a href="#intro" class="active">About</a></li>
           <li class="no-padding"><a href="#first">Get Started</a></li>
+          <li class="no-padding"><a href="#roadmap">Roadmap</a></li>
           <li class="no-padding"><a href="#posts">Last Posts</a></li>
           <li class="no-padding"><a href="#second">Community</a></li>
           <li class="no-padding"><a href="#third">Contribute</a></li>


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


[james-project] 04/21: JAMES-3080 use reworked rabbitMQrestart method in tests and make the code more resilient

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

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

commit 890f20c323e94a3ee95e2eab455e9e4846d8015a
Author: Rémi KOWALSKI <rk...@linagora.com>
AuthorDate: Tue Mar 3 14:13:48 2020 +0100

    JAMES-3080 use reworked rabbitMQrestart method in tests and make the code more resilient
---
 .../distributed/RabbitMQWorkQueue.java             | 22 +++++++++++++++++++---
 .../RabbitMQWorkQueuePersistenceTest.java          | 20 +++++---------------
 2 files changed, 24 insertions(+), 18 deletions(-)

diff --git a/server/task/task-distributed/src/main/java/org/apache/james/task/eventsourcing/distributed/RabbitMQWorkQueue.java b/server/task/task-distributed/src/main/java/org/apache/james/task/eventsourcing/distributed/RabbitMQWorkQueue.java
index 1efa7cd..005b86d 100644
--- a/server/task/task-distributed/src/main/java/org/apache/james/task/eventsourcing/distributed/RabbitMQWorkQueue.java
+++ b/server/task/task-distributed/src/main/java/org/apache/james/task/eventsourcing/distributed/RabbitMQWorkQueue.java
@@ -23,6 +23,7 @@ package org.apache.james.task.eventsourcing.distributed;
 import static com.rabbitmq.client.MessageProperties.PERSISTENT_TEXT_PLAIN;
 
 import java.nio.charset.StandardCharsets;
+import java.time.Duration;
 import java.util.Optional;
 import java.util.UUID;
 
@@ -69,6 +70,10 @@ public class RabbitMQWorkQueue implements WorkQueue {
     private static final String CANCEL_REQUESTS_QUEUE_NAME_PREFIX = "taskManagerCancelRequestsQueue";
     public static final String TASK_ID = "taskId";
 
+    public static final int NUM_RETRIES = 8;
+    public static final Duration FIRST_BACKOFF = Duration.ofMillis(100);
+    public static final Duration FOREVER = Duration.ofMillis(Long.MAX_VALUE);
+
     private final TaskManagerWorker worker;
     private final ReactorRabbitMQChannelPool channelPool;
     private final JsonTaskSerializer taskSerializer;
@@ -99,9 +104,20 @@ public class RabbitMQWorkQueue implements WorkQueue {
 
     @VisibleForTesting
     void declareQueue() {
-        channelPool.getSender().declareExchange(ExchangeSpecification.exchange(EXCHANGE_NAME)).block();
-        channelPool.getSender().declare(QueueSpecification.queue(QUEUE_NAME).durable(true).arguments(Constants.WITH_SINGLE_ACTIVE_CONSUMER)).block();
-        channelPool.getSender().bind(BindingSpecification.binding(EXCHANGE_NAME, ROUTING_KEY, QUEUE_NAME)).block();
+        Mono<AMQP.Exchange.DeclareOk> declareExchange = channelPool.getSender()
+            .declareExchange(ExchangeSpecification.exchange(EXCHANGE_NAME))
+            .retryBackoff(NUM_RETRIES, FIRST_BACKOFF, FOREVER);
+        Mono<AMQP.Queue.DeclareOk> declareQueue = channelPool.getSender()
+            .declare(QueueSpecification.queue(QUEUE_NAME).durable(true).arguments(Constants.WITH_SINGLE_ACTIVE_CONSUMER))
+            .retryBackoff(NUM_RETRIES, FIRST_BACKOFF, FOREVER);
+        Mono<AMQP.Queue.BindOk> bindQueueToExchange = channelPool.getSender()
+            .bind(BindingSpecification.binding(EXCHANGE_NAME, ROUTING_KEY, QUEUE_NAME))
+            .retryBackoff(NUM_RETRIES, FIRST_BACKOFF, FOREVER);
+
+        declareExchange
+            .then(declareQueue)
+            .then(bindQueueToExchange)
+            .block();
     }
 
     private void consumeWorkqueue() {
diff --git a/server/task/task-distributed/src/test/java/org/apache/james/task/eventsourcing/distributed/RabbitMQWorkQueuePersistenceTest.java b/server/task/task-distributed/src/test/java/org/apache/james/task/eventsourcing/distributed/RabbitMQWorkQueuePersistenceTest.java
index 7040957..298b45d 100644
--- a/server/task/task-distributed/src/test/java/org/apache/james/task/eventsourcing/distributed/RabbitMQWorkQueuePersistenceTest.java
+++ b/server/task/task-distributed/src/test/java/org/apache/james/task/eventsourcing/distributed/RabbitMQWorkQueuePersistenceTest.java
@@ -21,8 +21,6 @@ package org.apache.james.task.eventsourcing.distributed;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.awaitility.Awaitility.await;
-import static org.awaitility.Duration.FIVE_HUNDRED_MILLISECONDS;
-import static org.awaitility.Duration.FIVE_SECONDS;
 import static org.mockito.Mockito.spy;
 
 import org.apache.james.backends.rabbitmq.RabbitMQExtension;
@@ -33,6 +31,7 @@ import org.apache.james.task.MemoryReferenceTask;
 import org.apache.james.task.Task;
 import org.apache.james.task.TaskId;
 import org.apache.james.task.TaskWithId;
+import org.awaitility.Duration;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -72,7 +71,6 @@ class RabbitMQWorkQueuePersistenceTest {
     @Test
     void submittedMessageShouldSurviveRabbitMQRestart() throws Exception {
         Task TASK = new MemoryReferenceTask(() -> Task.Result.COMPLETED);
-
         TaskWithId TASK_WITH_ID = new TaskWithId(TASK_ID, TASK);
 
         testee.submit(TASK_WITH_ID);
@@ -81,27 +79,19 @@ class RabbitMQWorkQueuePersistenceTest {
         Thread.sleep(500);
         testee.close();
 
-        restartRabbitMQ();
+        rabbitMQExtension.getRabbitMQ().restart();
 
-        startNewWorkqueue();
+        startNewConsumingWorkqueue();
 
-        await().atMost(FIVE_HUNDRED_MILLISECONDS).until(() -> !worker.results.isEmpty());
+        await().atMost(Duration.ONE_MINUTE).until(() -> !worker.results.isEmpty());
 
         assertThat(worker.tasks).containsExactly(TASK_WITH_ID);
         assertThat(worker.results).containsExactly(Task.Result.COMPLETED);
     }
 
-    private void startNewWorkqueue() {
+    private void startNewConsumingWorkqueue() {
         worker = spy(new ImmediateWorker());
         testee = new RabbitMQWorkQueue(worker, rabbitMQExtension.getRabbitChannelPool(), serializer);
         testee.start();
     }
-
-    private void restartRabbitMQ() throws Exception {
-        rabbitMQExtension.getRabbitMQ().stopApp();
-        rabbitMQExtension.getRabbitMQ().startApp();
-        //wait until healthcheck is ok
-        await().atMost(FIVE_SECONDS).until(() -> rabbitMQExtension.managementAPI().listQueues().size() > 0);
-        rabbitMQExtension.getRabbitChannelPool().start();
-    }
 }


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


[james-project] 18/21: JAMES-3058 Update admin instructions

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

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

commit f31aaaf35c46f6275213df05b3ad2055af3d3978
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Feb 27 11:34:53 2020 +0700

    JAMES-3058 Update admin instructions
---
 src/site/markdown/server/manage-guice-distributed-james.md | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/site/markdown/server/manage-guice-distributed-james.md b/src/site/markdown/server/manage-guice-distributed-james.md
index 3f8306a..c51a18e 100644
--- a/src/site/markdown/server/manage-guice-distributed-james.md
+++ b/src/site/markdown/server/manage-guice-distributed-james.md
@@ -330,7 +330,12 @@ avoiding peak traffic in order to address both inconsistencies diagnostic and fi
 
 #### How to solve
 
-Under development: Task for solving mailbox inconsistencies ([JAMES-3058](https://issues.apache.org/jira/browse/JAMES-3058)).
+An admin can run offline webadmin 
+[solve Cassandra mailbox object inconsistencies task](manage-webadmin.html#Fixing_mailboxes_inconsistencies) in order 
+to sanitize his mailbox denormalization.
+                                        
+In order to ensure being offline, stop the traffic on SMTP, JMAP and IMAP ports, for example via re-configuration or 
+firewall rules.
 
 ## Setting Cassandra user permissions
 


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


[james-project] 13/21: JAMES-3058 Expose task solving mailbox inconsistencies over webadmin

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

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

commit 81383330c89c6bd51999c6022a61322f50d3c2d8
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Feb 14 14:45:05 2020 +0700

    JAMES-3058 Expose task solving mailbox inconsistencies over webadmin
    
    Detailed unit testing is performed at the mailbox level and will not be
    repeated here.
---
 .../mail/task/SolveMailboxInconsistenciesTask.java |  8 +++++-
 .../webadmin/InconsistencySolvingRoutesModule.java | 14 +++++++++
 .../RabbitMQWebAdminServerIntegrationTest.java     | 32 +++++++++++++++++++++
 .../SolveMailboxInconsistenciesRequestToTask.java} | 33 ++++++++--------------
 4 files changed, 65 insertions(+), 22 deletions(-)

diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesTask.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesTask.java
index f345689..8cbb7e8 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesTask.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/task/SolveMailboxInconsistenciesTask.java
@@ -29,6 +29,7 @@ import org.apache.james.task.TaskType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.collect.ImmutableList;
 
 public class SolveMailboxInconsistenciesTask implements Task {
@@ -59,22 +60,27 @@ public class SolveMailboxInconsistenciesTask implements Task {
             return instant;
         }
 
+        @JsonProperty("processedMailboxEntries")
         long getProcessedMailboxEntries() {
             return processedMailboxEntries;
         }
 
+        @JsonProperty("processedMailboxPathEntries")
         long getProcessedMailboxPathEntries() {
             return processedMailboxPathEntries;
         }
 
+        @JsonProperty("fixedInconsistencies")
         long getFixedInconsistencies() {
             return fixedInconsistencies;
         }
 
+        @JsonProperty("conflictingEntries")
         ImmutableList<ConflictingEntry> getConflictingEntries() {
             return conflictingEntries;
         }
 
+        @JsonProperty("errors")
         long getErrors() {
             return errors;
         }
@@ -82,7 +88,7 @@ public class SolveMailboxInconsistenciesTask implements Task {
 
     private final SolveMailboxInconsistenciesService service;
 
-    SolveMailboxInconsistenciesTask(SolveMailboxInconsistenciesService service) {
+    public SolveMailboxInconsistenciesTask(SolveMailboxInconsistenciesService service) {
         this.service = service;
         this.context = new SolveMailboxInconsistenciesService.Context();
     }
diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/webadmin/InconsistencySolvingRoutesModule.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/webadmin/InconsistencySolvingRoutesModule.java
index fc49a21..bf995c1 100644
--- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/webadmin/InconsistencySolvingRoutesModule.java
+++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/webadmin/InconsistencySolvingRoutesModule.java
@@ -21,11 +21,15 @@ package org.apache.james.modules.webadmin;
 
 import org.apache.james.webadmin.Routes;
 import org.apache.james.webadmin.routes.CassandraMappingsRoutes;
+import org.apache.james.webadmin.routes.MailboxesRoutes;
+import org.apache.james.webadmin.routes.SolveMailboxInconsistenciesRequestToTask;
 import org.apache.james.webadmin.service.CassandraMappingsService;
+import org.apache.james.webadmin.tasks.TaskFromRequestRegistry;
 
 import com.google.inject.AbstractModule;
 import com.google.inject.Scopes;
 import com.google.inject.multibindings.Multibinder;
+import com.google.inject.name.Names;
 
 public class InconsistencySolvingRoutesModule extends AbstractModule {
     public static class SolveRRTInconsistenciesModules extends AbstractModule {
@@ -39,8 +43,18 @@ public class InconsistencySolvingRoutesModule extends AbstractModule {
         }
     }
 
+    public static class SolveMailboxInconsistenciesModules extends AbstractModule {
+        @Override
+        protected void configure() {
+            Multibinder.newSetBinder(binder(), TaskFromRequestRegistry.TaskRegistration.class,
+                Names.named(MailboxesRoutes.ALL_MAILBOXES_TASKS))
+                .addBinding().to(SolveMailboxInconsistenciesRequestToTask.class);
+        }
+    }
+
     @Override
     protected void configure() {
         install(new SolveRRTInconsistenciesModules());
+        install(new SolveMailboxInconsistenciesModules());
     }
 }
diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerIntegrationTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerIntegrationTest.java
index 0d425d1..dcf26ba 100644
--- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerIntegrationTest.java
+++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/RabbitMQWebAdminServerIntegrationTest.java
@@ -26,6 +26,7 @@ import static org.apache.james.webadmin.Constants.JSON_CONTENT_TYPE;
 import static org.apache.james.webadmin.Constants.SEPARATOR;
 import static org.hamcrest.CoreMatchers.hasItems;
 import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.is;
 
 import org.apache.james.CassandraExtension;
@@ -164,6 +165,37 @@ class RabbitMQWebAdminServerIntegrationTest extends WebAdminServerIntegrationTes
             .body("source", hasItems(ALIAS_1, ALIAS_2));
     }
 
+    @Test
+    void solveMailboxInconsistenciesTaskShouldBeExposed() {
+        // schema version 6 or higher required to run solve mailbox inconsistencies task
+        String taskId = with().post(UPGRADE_TO_LATEST_VERSION)
+            .jsonPath()
+            .get("taskId");
+
+        with()
+            .get("/tasks/" + taskId + "/await")
+        .then()
+            .body("status", is("completed"));
+
+        taskId = with()
+            .queryParam("task", "SolveInconsistencies")
+        .post("/mailboxes")
+            .jsonPath()
+            .get("taskId");
+
+        given()
+            .basePath(TasksRoutes.BASE)
+        .when()
+            .get(taskId + "/await")
+        .then()
+            .body("status", is("completed"))
+            .body("type", is("solve-mailbox-inconsistencies"))
+            .body("additionalInformation.processedMailboxEntries", is(0))
+            .body("additionalInformation.processedMailboxPathEntries", is(0))
+            .body("additionalInformation.errors", is(0))
+            .body("additionalInformation.fixedInconsistencies", hasSize(0))
+            .body("additionalInformation.conflictingEntries", hasSize(0));
+    }
 
     @Test
     void getSwaggerShouldContainDistributedEndpoints() {
diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/webadmin/InconsistencySolvingRoutesModule.java b/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/routes/SolveMailboxInconsistenciesRequestToTask.java
similarity index 54%
copy from server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/webadmin/InconsistencySolvingRoutesModule.java
copy to server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/routes/SolveMailboxInconsistenciesRequestToTask.java
index fc49a21..7418226 100644
--- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/webadmin/InconsistencySolvingRoutesModule.java
+++ b/server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/routes/SolveMailboxInconsistenciesRequestToTask.java
@@ -17,30 +17,21 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.modules.webadmin;
+package org.apache.james.webadmin.routes;
 
-import org.apache.james.webadmin.Routes;
-import org.apache.james.webadmin.routes.CassandraMappingsRoutes;
-import org.apache.james.webadmin.service.CassandraMappingsService;
+import javax.inject.Inject;
 
-import com.google.inject.AbstractModule;
-import com.google.inject.Scopes;
-import com.google.inject.multibindings.Multibinder;
+import org.apache.james.mailbox.cassandra.mail.task.SolveMailboxInconsistenciesService;
+import org.apache.james.mailbox.cassandra.mail.task.SolveMailboxInconsistenciesTask;
+import org.apache.james.webadmin.tasks.TaskFromRequestRegistry;
+import org.apache.james.webadmin.tasks.TaskRegistrationKey;
 
-public class InconsistencySolvingRoutesModule extends AbstractModule {
-    public static class SolveRRTInconsistenciesModules extends AbstractModule {
-        @Override
-        protected void configure() {
-            bind(CassandraMappingsRoutes.class).in(Scopes.SINGLETON);
-            bind(CassandraMappingsService.class).in(Scopes.SINGLETON);
+public class SolveMailboxInconsistenciesRequestToTask extends TaskFromRequestRegistry.TaskRegistration {
+    private static final TaskRegistrationKey REGISTRATION_KEY = TaskRegistrationKey.of("SolveInconsistencies");
 
-            Multibinder<Routes> routesMultibinder = Multibinder.newSetBinder(binder(), Routes.class);
-            routesMultibinder.addBinding().to(CassandraMappingsRoutes.class);
-        }
-    }
-
-    @Override
-    protected void configure() {
-        install(new SolveRRTInconsistenciesModules());
+    @Inject
+    public SolveMailboxInconsistenciesRequestToTask(SolveMailboxInconsistenciesService service) {
+        super(REGISTRATION_KEY,
+            request -> new SolveMailboxInconsistenciesTask(service));
     }
 }


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


[james-project] 06/21: JAMES-3081 add test to ensure messages are persisted on the rabbitmq mailqueue

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

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

commit f07ba9a9e1c398fb4fadcb0fa8682cd4b6a418b1
Author: Rémi KOWALSKI <rk...@linagora.com>
AuthorDate: Mon Mar 2 10:13:06 2020 +0100

    JAMES-3081 add test to ensure messages are persisted on the rabbitmq mailqueue
---
 .../queue/rabbitmq/RabbitMQMailQueueTest.java      | 30 ++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/server/queue/queue-rabbitmq/src/test/java/org/apache/james/queue/rabbitmq/RabbitMQMailQueueTest.java b/server/queue/queue-rabbitmq/src/test/java/org/apache/james/queue/rabbitmq/RabbitMQMailQueueTest.java
index 61d6e5b..cfad2b0 100644
--- a/server/queue/queue-rabbitmq/src/test/java/org/apache/james/queue/rabbitmq/RabbitMQMailQueueTest.java
+++ b/server/queue/queue-rabbitmq/src/test/java/org/apache/james/queue/rabbitmq/RabbitMQMailQueueTest.java
@@ -23,6 +23,8 @@ import static java.time.temporal.ChronoUnit.HOURS;
 import static org.apache.james.queue.api.Mails.defaultMail;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.awaitility.Awaitility.await;
+import static org.awaitility.Duration.FIVE_SECONDS;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -250,6 +252,34 @@ class RabbitMQMailQueueTest {
                 .containsExactly(name1, name2, name3);
         }
 
+        @Test
+        void messagesShouldSurviveRabbitMQRestart() throws Exception {
+            String name1 = "myMail1";
+            String name2 = "myMail2";
+            String name3 = "myMail3";
+            Flux<MailQueue.MailQueueItem> dequeueFlux = Flux.from(getMailQueue().deQueue());
+
+            getMailQueue().enQueue(defaultMail()
+                .name(name1)
+                .build());
+
+            getMailQueue().enQueue(defaultMail()
+                .name(name2)
+                .build());
+
+            getMailQueue().enQueue(defaultMail()
+                .name(name3)
+                .build());
+
+            rabbitMQExtension.getRabbitMQ().restart();
+
+            List<MailQueue.MailQueueItem> items = dequeueFlux.take(3).collectList().block(Duration.ofSeconds(10));
+
+            assertThat(items)
+                .extracting(item -> item.getMail().getName())
+                .containsExactly(name1, name2, name3);
+        }
+
         private void enqueueSomeMails(Function<Integer, String> namePattern, int emailCount) {
             IntStream.rangeClosed(1, emailCount)
                 .forEach(Throwing.intConsumer(i -> enQueue(defaultMail()


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


[james-project] 09/21: Update Java version

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

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

commit 00c948838ac04c3ad782bfff3c1f7722f7cace16
Author: Raphael Ouazana <ra...@linagora.com>
AuthorDate: Thu Feb 27 14:14:57 2020 +0100

    Update Java version
---
 src/homepage/index.html | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/homepage/index.html b/src/homepage/index.html
index 94bed80..b10c01d 100644
--- a/src/homepage/index.html
+++ b/src/homepage/index.html
@@ -72,8 +72,8 @@ layout: default
               <p class="align-left">Create your <b>own personal solution</b> of emails treatment by assembling the components you need thanks to the Inversion of Control mail platform offered and  go further customizing filtering and routing rules using <b>James Mailet Container</b>.</p>
               <div class="about-table">
                 <h3 class="tb-h3">James from a technical point of view</h3>
-                <b>Complete portability</b> (100% pure Java)<br/>
-                <b>Built with Java 8</b> and running on the <b>JRE 1.8</b>.<br/><br/>
+                <b>Complete portability</b> (100% pure JVM: Java &amp; Scala)<br/>
+                <b>Built with Java 11</b> and running on <b>JRE 1.8+</b>.<br/><br/>
                 <b>James Components:</b><br/>
                 - <b>Emailing protocols:</b> SMTP, LMTP, POP3, IMAP, ManageSieve, JMAP<br/>
                 - <b>Mailet container:</b> independent, extensible and pluggable email processing agents<br/>


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


[james-project] 07/21: JAMES-3081 make the rabbitmq mailqueue messages persistent

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

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

commit 6021556175d1a5dfde8fae04780ac1e476cdb9aa
Author: Rémi KOWALSKI <rk...@linagora.com>
AuthorDate: Mon Mar 2 10:14:30 2020 +0100

    JAMES-3081 make the rabbitmq mailqueue messages persistent
---
 .../src/main/java/org/apache/james/queue/rabbitmq/Enqueuer.java  | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/server/queue/queue-rabbitmq/src/main/java/org/apache/james/queue/rabbitmq/Enqueuer.java b/server/queue/queue-rabbitmq/src/main/java/org/apache/james/queue/rabbitmq/Enqueuer.java
index 4e0485c..06bb328 100644
--- a/server/queue/queue-rabbitmq/src/main/java/org/apache/james/queue/rabbitmq/Enqueuer.java
+++ b/server/queue/queue-rabbitmq/src/main/java/org/apache/james/queue/rabbitmq/Enqueuer.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.queue.rabbitmq;
 
+import static com.rabbitmq.client.MessageProperties.PERSISTENT_TEXT_PLAIN;
 import static org.apache.james.backends.rabbitmq.Constants.EMPTY_ROUTING_KEY;
 import static org.apache.james.queue.api.MailQueue.ENQUEUED_METRIC_NAME_PREFIX;
 
@@ -38,6 +39,7 @@ import org.apache.mailet.Mail;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.github.fge.lambdas.Throwing;
+import com.rabbitmq.client.AMQP;
 
 import reactor.core.publisher.Mono;
 import reactor.rabbitmq.OutboundMessage;
@@ -83,9 +85,16 @@ class Enqueuer {
     }
 
     private Mono<EnqueuedItem> publishReferenceToRabbit(MailReference mailReference) throws MailQueue.MailQueueException {
+        AMQP.BasicProperties basicProperties = new AMQP.BasicProperties.Builder()
+            .deliveryMode(PERSISTENT_TEXT_PLAIN.getDeliveryMode())
+            .priority(PERSISTENT_TEXT_PLAIN.getPriority())
+            .contentType(PERSISTENT_TEXT_PLAIN.getContentType())
+            .build();
+
         OutboundMessage data = new OutboundMessage(
             name.toRabbitExchangeName().asString(),
             EMPTY_ROUTING_KEY,
+            basicProperties,
             getMailReferenceBytes(mailReference));
         return sender.send(Mono.just(data))
             .then(Mono.just(


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


[james-project] 19/21: JAMES-3087 LDAP user listing should filter out users without id field

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

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

commit ffcbce782b52b409420d3209186c74f4d52a0c87
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Mar 5 10:41:16 2020 +0700

    JAMES-3087 LDAP user listing should filter out users without id field
    
    Before that a NPE was returned.
---
 .../user/ldap/ReadOnlyUsersLDAPRepository.java     | 20 +++--
 .../james/user/ldap/LdapGenericContainer.java      | 13 ++-
 .../ReadOnlyUsersLDAPRepositoryInvalidDnTest.java  | 96 ++++++++++++++++++++++
 .../user/ldap/ReadOnlyUsersLDAPRepositoryTest.java | 66 ++++++++++-----
 .../test/resources/invalid/ldif-files/Dockerfile   |  3 +
 .../resources/invalid/ldif-files/populate.ldif     | 11 +++
 6 files changed, 180 insertions(+), 29 deletions(-)

diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java
index ebbd794..e9af7ac 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java
@@ -54,12 +54,14 @@ import org.apache.james.user.api.UsersRepository;
 import org.apache.james.user.api.UsersRepositoryException;
 import org.apache.james.user.api.model.User;
 import org.apache.james.user.ldap.api.LdapConstants;
+import org.apache.james.util.OptionalUtils;
 import org.apache.james.util.retry.DoublingRetrySchedule;
 import org.apache.james.util.retry.api.RetrySchedule;
 import org.apache.james.util.retry.naming.ldap.RetryingLdapContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.github.fge.lambdas.Throwing;
 import com.github.steveash.guavate.Guavate;
 import com.google.common.base.Strings;
 
@@ -450,8 +452,8 @@ public class ReadOnlyUsersLDAPRepository implements UsersRepository, Configurabl
         List<ReadOnlyLDAPUser> results = new ArrayList<>();
 
         for (String userDN : userDNs) {
-            ReadOnlyLDAPUser user = buildUser(userDN);
-            results.add(user);
+            Optional<ReadOnlyLDAPUser> user = buildUser(userDN);
+            user.ifPresent(results::add);
         }
 
         return results;
@@ -518,10 +520,13 @@ public class ReadOnlyUsersLDAPRepository implements UsersRepository, Configurabl
      * @throws NamingException
      *             Propagated by the underlying LDAP communication layer.
      */
-    private ReadOnlyLDAPUser buildUser(String userDN) throws NamingException {
+    private Optional<ReadOnlyLDAPUser> buildUser(String userDN) throws NamingException {
       Attributes userAttributes = ldapContext.getAttributes(userDN);
-      Attribute userName = userAttributes.get(ldapConfiguration.getUserIdAttribute());
-      return new ReadOnlyLDAPUser(Username.of(userName.get().toString()), userDN, ldapContext);
+      Optional<Attribute> userName = Optional.ofNullable(userAttributes.get(ldapConfiguration.getUserIdAttribute()));
+      return userName
+          .map(Throwing.<Attribute, String>function(u -> u.get().toString()).sneakyThrow())
+          .map(Username::of)
+          .map(username -> new ReadOnlyLDAPUser(username, userDN, ldapContext));
     }
 
     @Override
@@ -537,7 +542,10 @@ public class ReadOnlyUsersLDAPRepository implements UsersRepository, Configurabl
     @Override
     public int countUsers() throws UsersRepositoryException {
         try {
-            return getValidUsers().size();
+            return Math.toIntExact(getValidUsers().stream()
+                .map(Throwing.function(this::buildUser).sneakyThrow())
+                .flatMap(OptionalUtils::toStream)
+                .count());
         } catch (NamingException e) {
             LOGGER.error("Unable to retrieve user count from ldap", e);
             throw new UsersRepositoryException("Unable to retrieve user count from ldap", e);
diff --git a/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/LdapGenericContainer.java b/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/LdapGenericContainer.java
index 050fe4f..36133f9 100644
--- a/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/LdapGenericContainer.java
+++ b/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/LdapGenericContainer.java
@@ -18,6 +18,8 @@
  ****************************************************************/
 package org.apache.james.user.ldap;
 
+import java.util.Optional;
+
 import org.apache.james.util.docker.DockerContainer;
 import org.apache.james.util.docker.RateLimiters;
 import org.junit.rules.ExternalResource;
@@ -36,13 +38,18 @@ public class LdapGenericContainer extends ExternalResource {
     }
 
     public static class Builder {
-
+        private Optional<String> dockerFilePrefix = Optional.empty();
         private String domain;
         private String password;
 
         private Builder() {
         }
 
+        public Builder dockerFilePrefix(String prefix) {
+            this.dockerFilePrefix = Optional.of(prefix);
+            return this;
+        }
+
         public Builder domain(String domain) {
             this.domain = domain;
             return this;
@@ -62,8 +69,8 @@ public class LdapGenericContainer extends ExternalResource {
         private DockerContainer createContainer() {
             return DockerContainer.fromDockerfile(
                 new ImageFromDockerfile()
-                    .withFileFromClasspath("populate.ldif", "ldif-files/populate.ldif")
-                    .withFileFromClasspath("Dockerfile", "ldif-files/Dockerfile"))
+                    .withFileFromClasspath("populate.ldif", dockerFilePrefix.orElse("") + "ldif-files/populate.ldif")
+                    .withFileFromClasspath("Dockerfile", dockerFilePrefix.orElse("") + "ldif-files/Dockerfile"))
                 .withAffinityToContainer()
                 .withEnv("SLAPD_DOMAIN", domain)
                 .withEnv("SLAPD_PASSWORD", password)
diff --git a/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryInvalidDnTest.java b/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryInvalidDnTest.java
new file mode 100644
index 0000000..33fd239
--- /dev/null
+++ b/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryInvalidDnTest.java
@@ -0,0 +1,96 @@
+/****************************************************************
+ * 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.user.ldap;
+
+import static org.apache.james.user.ldap.DockerLdapSingleton.ADMIN_PASSWORD;
+import static org.apache.james.user.ldap.DockerLdapSingleton.DOMAIN;
+import static org.apache.james.user.ldap.DockerLdapSingleton.JAMES_USER;
+import static org.apache.james.user.ldap.ReadOnlyUsersLDAPRepositoryTest.ldapRepositoryConfigurationWithVirtualHosting;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+import org.apache.commons.configuration2.HierarchicalConfiguration;
+import org.apache.commons.configuration2.plist.PropertyListConfiguration;
+import org.apache.commons.configuration2.tree.ImmutableNode;
+import org.apache.james.core.Username;
+import org.apache.james.domainlist.api.DomainList;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableList;
+
+class ReadOnlyUsersLDAPRepositoryInvalidDnTest {
+    static LdapGenericContainer ldapContainer = LdapGenericContainer.builder()
+        .dockerFilePrefix("invalid/")
+        .domain(DOMAIN)
+        .password(ADMIN_PASSWORD)
+        .build();
+
+    DomainList domainList;
+    private ReadOnlyUsersLDAPRepository ldapRepository;
+
+    @BeforeAll
+    static void setUpAll() {
+        ldapContainer.start();
+    }
+
+    @AfterAll
+    static void afterAll() {
+        ldapContainer.start();
+    }
+
+    @BeforeEach
+    void setUp() throws Exception {
+        domainList = mock(DomainList.class);
+        ldapRepository = startUsersRepository(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer));
+    }
+
+    @Test
+    void listShouldFilterOutUsersWithoutIdField() throws Exception {
+        assertThat(ImmutableList.copyOf(ldapRepository.list()))
+            .isEmpty();
+    }
+
+    @Test
+    void getUserByNameShouldReturnNullWhenNoIdField() throws Exception {
+        assertThat(ldapRepository.getUserByName(JAMES_USER)).isNull();
+    }
+
+    @Test
+    void containsShouldReturnFalseWhenNoIdField() throws Exception {
+        assertThat(ldapRepository.contains(JAMES_USER)).isFalse();
+    }
+
+    @Test
+    void contShouldReturnZeroWhenInvalidUser() throws Exception {
+        assertThat(ldapRepository.countUsers()).isEqualTo(0);
+    }
+
+    private ReadOnlyUsersLDAPRepository startUsersRepository(HierarchicalConfiguration<ImmutableNode> ldapRepositoryConfiguration) throws Exception {
+        ReadOnlyUsersLDAPRepository ldapRepository = new ReadOnlyUsersLDAPRepository(domainList);
+        ldapRepository.configure(ldapRepositoryConfiguration);
+        ldapRepository.init();
+        return ldapRepository;
+    }
+}
diff --git a/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryTest.java b/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryTest.java
index fd26257..6b22f32 100644
--- a/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryTest.java
+++ b/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryTest.java
@@ -33,12 +33,16 @@ import org.apache.commons.configuration2.plist.PropertyListConfiguration;
 import org.apache.commons.configuration2.tree.ImmutableNode;
 import org.apache.james.core.Username;
 import org.apache.james.domainlist.api.DomainList;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.collect.ImmutableList;
+
 class ReadOnlyUsersLDAPRepositoryTest {
 
     static final Logger LOGGER = LoggerFactory.getLogger(ReadOnlyUsersLDAPRepositoryTest.class);
@@ -46,8 +50,23 @@ class ReadOnlyUsersLDAPRepositoryTest {
     static final Username UNKNOWN = Username.of("unknown");
     static final String BAD_PASSWORD = "badpassword";
 
+    static LdapGenericContainer ldapContainer = LdapGenericContainer.builder()
+        .domain(DOMAIN)
+        .password(ADMIN_PASSWORD)
+        .build();
+
     DomainList domainList;
 
+    @BeforeAll
+    static void setUpAll() {
+        ldapContainer.start();
+    }
+
+    @AfterAll
+    static void afterAll() {
+        ldapContainer.start();
+    }
+
     @BeforeEach
     void setUp() {
         domainList = mock(DomainList.class);
@@ -59,14 +78,14 @@ class ReadOnlyUsersLDAPRepositoryTest {
         @Test
         void supportVirtualHostingShouldReturnFalseByDefault() throws Exception {
             ReadOnlyUsersLDAPRepository usersLDAPRepository = new ReadOnlyUsersLDAPRepository(domainList);
-            usersLDAPRepository.configure(ldapRepositoryConfiguration());
+            usersLDAPRepository.configure(ldapRepositoryConfiguration(ldapContainer));
 
             assertThat(usersLDAPRepository.supportVirtualHosting()).isFalse();
         }
 
         @Test
         void supportVirtualHostingShouldReturnTrueWhenReportedInConfig() throws Exception {
-            HierarchicalConfiguration<ImmutableNode> configuration = ldapRepositoryConfiguration();
+            HierarchicalConfiguration<ImmutableNode> configuration = ldapRepositoryConfiguration(ldapContainer);
             configuration.addProperty(ReadOnlyUsersLDAPRepository.SUPPORTS_VIRTUAL_HOSTING, "true");
 
             ReadOnlyUsersLDAPRepository usersLDAPRepository = new ReadOnlyUsersLDAPRepository(domainList);
@@ -77,7 +96,7 @@ class ReadOnlyUsersLDAPRepositoryTest {
 
         @Test
         void supportVirtualHostingShouldReturnFalseWhenReportedInConfig() throws Exception {
-            HierarchicalConfiguration<ImmutableNode> configuration = ldapRepositoryConfiguration();
+            HierarchicalConfiguration<ImmutableNode> configuration = ldapRepositoryConfiguration(ldapContainer);
             configuration.addProperty(ReadOnlyUsersLDAPRepository.SUPPORTS_VIRTUAL_HOSTING, "false");
 
             ReadOnlyUsersLDAPRepository usersLDAPRepository = new ReadOnlyUsersLDAPRepository(domainList);
@@ -88,7 +107,7 @@ class ReadOnlyUsersLDAPRepositoryTest {
 
         @Test
         void configureShouldThrowOnNonBooleanValueForSupportsVirtualHosting() {
-            HierarchicalConfiguration<ImmutableNode> configuration = ldapRepositoryConfiguration();
+            HierarchicalConfiguration<ImmutableNode> configuration = ldapRepositoryConfiguration(ldapContainer);
             configuration.addProperty(ReadOnlyUsersLDAPRepository.SUPPORTS_VIRTUAL_HOSTING, "bad");
 
             ReadOnlyUsersLDAPRepository usersLDAPRepository = new ReadOnlyUsersLDAPRepository(domainList);
@@ -103,37 +122,44 @@ class ReadOnlyUsersLDAPRepositoryTest {
 
         @Test
         void knownUserShouldBeAbleToLogInWhenPasswordIsCorrect() throws Exception {
-            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfiguration());
+            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfiguration(ldapContainer));
             assertThat(ldapRepository.test(JAMES_USER, PASSWORD)).isTrue();
         }
 
         @Test
         void knownUserShouldNotBeAbleToLogInWhenPasswordIsNotCorrect() throws Exception {
-            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfiguration());
+            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfiguration(ldapContainer));
             assertThat(ldapRepository.test(JAMES_USER, BAD_PASSWORD)).isFalse();
         }
 
         @Test
         void unknownUserShouldNotBeAbleToLogIn() throws Exception {
-            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfiguration());
+            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfiguration(ldapContainer));
             assertThat(ldapRepository.test(UNKNOWN, BAD_PASSWORD)).isFalse();
         }
 
         @Test
         void unknownUserShouldNotBeAbleToLogInWhenPasswordIsCorrect() throws Exception {
-            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfiguration());
+            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfiguration(ldapContainer));
             assertThat(ldapRepository.test(UNKNOWN, PASSWORD)).isFalse();
         }
 
         @Test
         void knownUserShouldBeAbleToLogInWhenPasswordIsCorrectWithVirtualHosting() throws Exception {
-            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfigurationWithVirtualHosting());
+            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer));
             assertThat(ldapRepository.test(JAMES_USER_MAIL, PASSWORD)).isTrue();
         }
 
         @Test
+        void testShouldListUsers() throws Exception {
+            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer));
+            assertThat(ImmutableList.copyOf(ldapRepository.list()))
+                .containsOnly(JAMES_USER_MAIL);
+        }
+
+        @Test
         void testShouldStillWorkAfterRestartingLDAP() throws Exception {
-            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfigurationWithVirtualHosting());
+            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer));
             ldapRepository.test(JAMES_USER_MAIL, PASSWORD);
 
             DockerLdapSingleton.ldapContainer.pause();
@@ -149,19 +175,19 @@ class ReadOnlyUsersLDAPRepositoryTest {
 
         @Test
         void knownUserShouldNotBeAbleToLogInWhenPasswordIsNotCorrectWithVirtualHosting() throws Exception {
-            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfigurationWithVirtualHosting());
+            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer));
             assertThat(ldapRepository.test(JAMES_USER, BAD_PASSWORD)).isFalse();
         }
 
         @Test
         void unknownUserShouldNotBeAbleToLogInWithVirtualHosting() throws Exception {
-            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfigurationWithVirtualHosting());
+            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer));
             assertThat(ldapRepository.test(UNKNOWN, BAD_PASSWORD)).isFalse();
         }
 
         @Test
         void unknownUserShouldNotBeAbleToLogInWhenPasswordIsCorrectWithVirtualHosting() throws Exception {
-            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfigurationWithVirtualHosting());
+            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer));
             assertThat(ldapRepository.test(UNKNOWN, PASSWORD)).isFalse();
         }
 
@@ -169,19 +195,19 @@ class ReadOnlyUsersLDAPRepositoryTest {
         void specialCharacterInUserInputShouldBeSanitized() throws Exception {
             Username patternMatchingMultipleUsers = Username.of("j*");
 
-            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfigurationWithVirtualHosting());
+            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer));
             assertThat(ldapRepository.test(patternMatchingMultipleUsers, PASSWORD)).isFalse();
         }
 
         @Test
         void containsWithGetUserShouldBeTrue() throws Exception {
-            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfiguration());
+            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfiguration(ldapContainer));
             assertThat(ldapRepository.contains(ldapRepository.getUser(JAMES_USER_MAIL.asMailAddress()))).isTrue();
         }
 
         @Test
         void containsWithGetUserShouldBeTrueWithVirtualHosting() throws Exception {
-            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfigurationWithVirtualHosting());
+            ReadOnlyUsersLDAPRepository ldapRepository = startUsersRepository(ldapRepositoryConfigurationWithVirtualHosting(ldapContainer));
             assertThat(ldapRepository.contains(ldapRepository.getUser(JAMES_USER_MAIL.asMailAddress()))).isTrue();
         }
 
@@ -193,9 +219,9 @@ class ReadOnlyUsersLDAPRepositoryTest {
         }
     }
 
-    private static HierarchicalConfiguration<ImmutableNode> ldapRepositoryConfiguration() {
+    private HierarchicalConfiguration<ImmutableNode> ldapRepositoryConfiguration(LdapGenericContainer ldapContainer) {
         PropertyListConfiguration configuration = new PropertyListConfiguration();
-        configuration.addProperty("[@ldapHost]", DockerLdapSingleton.ldapContainer.getLdapHost());
+        configuration.addProperty("[@ldapHost]", ldapContainer.getLdapHost());
         configuration.addProperty("[@principal]", "cn=admin,dc=james,dc=org");
         configuration.addProperty("[@credentials]", ADMIN_PASSWORD);
         configuration.addProperty("[@userBase]", "ou=People,dc=james,dc=org");
@@ -210,9 +236,9 @@ class ReadOnlyUsersLDAPRepositoryTest {
         return configuration;
     }
 
-    private static HierarchicalConfiguration<ImmutableNode> ldapRepositoryConfigurationWithVirtualHosting() {
+    static HierarchicalConfiguration<ImmutableNode> ldapRepositoryConfigurationWithVirtualHosting(LdapGenericContainer ldapContainer) {
         PropertyListConfiguration configuration = new PropertyListConfiguration();
-        configuration.addProperty("[@ldapHost]", DockerLdapSingleton.ldapContainer.getLdapHost());
+        configuration.addProperty("[@ldapHost]", ldapContainer.getLdapHost());
         configuration.addProperty("[@principal]", "cn=admin,dc=james,dc=org");
         configuration.addProperty("[@credentials]", ADMIN_PASSWORD);
         configuration.addProperty("[@userBase]", "ou=People,dc=james,dc=org");
diff --git a/server/data/data-ldap/src/test/resources/invalid/ldif-files/Dockerfile b/server/data/data-ldap/src/test/resources/invalid/ldif-files/Dockerfile
new file mode 100644
index 0000000..d889a35
--- /dev/null
+++ b/server/data/data-ldap/src/test/resources/invalid/ldif-files/Dockerfile
@@ -0,0 +1,3 @@
+FROM dinkel/openldap:latest
+
+COPY populate.ldif /etc/ldap/prepopulate/prepop.ldif
diff --git a/server/data/data-ldap/src/test/resources/invalid/ldif-files/populate.ldif b/server/data/data-ldap/src/test/resources/invalid/ldif-files/populate.ldif
new file mode 100644
index 0000000..9376a6c
--- /dev/null
+++ b/server/data/data-ldap/src/test/resources/invalid/ldif-files/populate.ldif
@@ -0,0 +1,11 @@
+dn: ou=people, dc=james,dc=org
+ou: people
+objectClass: organizationalUnit
+
+dn: uid=james-user, ou=people, dc=james,dc=org
+objectClass: inetOrgPerson
+uid: james-user
+cn: james-user
+sn: james-user
+userPassword: secret
+description: James user


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


[james-project] 15/21: JAMES-3058 Changelog entry: solving mailbox inconsistencies

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

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

commit 046a79116583ad5b46cc57e674ce93a6ce71fdec
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Feb 14 15:01:16 2020 +0700

    JAMES-3058 Changelog entry: solving mailbox inconsistencies
---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 273bdaa..098cf2b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ of tasks being currently executed.
 - JAMES-2904 Authentication and SSL support for ElasticSearch backend
 - JAMES-3066 Add "allowed From headers" webadmin endpoint
 - JAMES-3062 EventDeadLettersHealthCheck
+- JAMES-3058 WebAdmin offline task to correct mailbox inconsistencies on top of Cassandra products
 
 ### Changed
 - Multiple changes have been made to enhance ElasticSearch performance:


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


[james-project] 21/21: JAMES-3067 Make RecipientRewriteTable configuration immutable

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

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

commit 9e7eefb22b3efc5c96896de40459865adeb5a3c4
Author: Gautier DI FOLCO <gd...@linagora.com>
AuthorDate: Wed Feb 26 09:12:50 2020 +0100

    JAMES-3067 Make RecipientRewriteTable configuration immutable
---
 .../data/CassandraRecipientRewriteTableModule.java |   8 +-
 .../data/JPARecipientRewriteTableModule.java       |   8 +-
 .../james/modules/data/MemoryDataModule.java       |   8 +-
 .../META-INF/org/apache/james/spring-server.xml    |   2 +-
 .../java/org/apache/james/util/StreamUtils.java    |   2 +-
 .../org/apache/james/util/StreamUtilsTest.java     |   2 +-
 server/data/data-api/pom.xml                       |   9 +
 ...RewriteTable.java => AliasReverseResolver.java} |   2 +-
 .../james/rrt/api/RecipientRewriteTable.java       |   2 +-
 .../api/RecipientRewriteTableConfiguration.java    |  98 +++++++++
 .../RecipientRewriteTableConfigurationTest.java    |  83 ++++++++
 ...ract.java => AliasReverseResolverContract.java} |  30 +--
 .../apache/james/rrt/lib/CanSendFromContract.java  |   6 +-
 .../CassandraRecipientRewriteTableV6Test.java      |   1 -
 .../CassandraRecipientRewriteTableV7Test.java      |   1 -
 .../james/rrt/cassandra/CassandraStepdefs.java     |   5 +-
 .../rrt/file/XMLRecipientRewriteTableTest.java     | 229 +++++++++++++++------
 .../org/apache/james/rrt/file/XMLStepdefs.java     |   4 +-
 .../rrt/jpa/JPARecipientRewriteTableTest.java      |   2 -
 .../java/org/apache/james/rrt/jpa/JPAStepdefs.java |   6 +-
 .../rrt/lib/AbstractRecipientRewriteTable.java     |  55 ++---
 ...ableImpl.java => AliasReverseResolverImpl.java} |  12 +-
 .../org/apache/james/rrt/lib/CanSendFromImpl.java  |  10 +-
 .../rrt/lib/AbstractRecipientRewriteTableTest.java |  44 ++--
 .../james/rrt/lib/RewriteTablesStepdefs.java       |  16 +-
 .../test/resources/cucumber/rewrite_tables.feature |   8 +-
 ...Test.java => AliasReverseResolverImplTest.java} |  16 +-
 .../apache/james/rrt/lib/CanSendFromImplTest.java  |  10 +-
 .../apache/james/rrt/memory/InMemoryStepdefs.java  |   5 +-
 .../memory/MemoryRecipientRewriteTableTest.java    |   7 +-
 .../methods/SetMessagesCreationProcessorTest.java  |   8 +-
 .../methods/SetMessagesUpdateProcessorTest.java    |   8 +-
 .../apache/james/smtpserver/SMTPServerTest.java    |  10 +-
 .../james/webadmin/routes/UserRoutesTest.java      |  10 +-
 34 files changed, 510 insertions(+), 217 deletions(-)

diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/data/CassandraRecipientRewriteTableModule.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/data/CassandraRecipientRewriteTableModule.java
index 4eaccfc..709a242 100644
--- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/data/CassandraRecipientRewriteTableModule.java
+++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/data/CassandraRecipientRewriteTableModule.java
@@ -19,15 +19,15 @@
 package org.apache.james.modules.data;
 
 import org.apache.james.backends.cassandra.components.CassandraModule;
+import org.apache.james.rrt.api.AliasReverseResolver;
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.rrt.api.RecipientRewriteTable;
-import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.rrt.cassandra.CassandraMappingsSourcesDAO;
 import org.apache.james.rrt.cassandra.CassandraRRTModule;
 import org.apache.james.rrt.cassandra.CassandraRecipientRewriteTable;
 import org.apache.james.rrt.cassandra.CassandraRecipientRewriteTableDAO;
+import org.apache.james.rrt.lib.AliasReverseResolverImpl;
 import org.apache.james.rrt.lib.CanSendFromImpl;
-import org.apache.james.rrt.lib.ReverseRecipientRewriteTableImpl;
 import org.apache.james.server.core.configuration.ConfigurationProvider;
 import org.apache.james.utils.InitializationOperation;
 import org.apache.james.utils.InitilizationOperationBuilder;
@@ -44,8 +44,8 @@ public class CassandraRecipientRewriteTableModule extends AbstractModule {
         bind(CassandraRecipientRewriteTableDAO.class).in(Scopes.SINGLETON);
         bind(CassandraMappingsSourcesDAO.class).in(Scopes.SINGLETON);
         bind(RecipientRewriteTable.class).to(CassandraRecipientRewriteTable.class);
-        bind(ReverseRecipientRewriteTableImpl.class).in(Scopes.SINGLETON);
-        bind(ReverseRecipientRewriteTable.class).to(ReverseRecipientRewriteTableImpl.class);
+        bind(AliasReverseResolverImpl.class).in(Scopes.SINGLETON);
+        bind(AliasReverseResolver.class).to(AliasReverseResolverImpl.class);
         bind(CanSendFromImpl.class).in(Scopes.SINGLETON);
         bind(CanSendFrom.class).to(CanSendFromImpl.class);
         Multibinder<CassandraModule> cassandraDataDefinitions = Multibinder.newSetBinder(binder(), CassandraModule.class);
diff --git a/server/container/guice/jpa-common-guice/src/main/java/org/apache/james/modules/data/JPARecipientRewriteTableModule.java b/server/container/guice/jpa-common-guice/src/main/java/org/apache/james/modules/data/JPARecipientRewriteTableModule.java
index a608538..f00af56 100644
--- a/server/container/guice/jpa-common-guice/src/main/java/org/apache/james/modules/data/JPARecipientRewriteTableModule.java
+++ b/server/container/guice/jpa-common-guice/src/main/java/org/apache/james/modules/data/JPARecipientRewriteTableModule.java
@@ -18,12 +18,12 @@
  ****************************************************************/
 package org.apache.james.modules.data;
 
+import org.apache.james.rrt.api.AliasReverseResolver;
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.rrt.api.RecipientRewriteTable;
-import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.rrt.jpa.JPARecipientRewriteTable;
+import org.apache.james.rrt.lib.AliasReverseResolverImpl;
 import org.apache.james.rrt.lib.CanSendFromImpl;
-import org.apache.james.rrt.lib.ReverseRecipientRewriteTableImpl;
 import org.apache.james.server.core.configuration.ConfigurationProvider;
 import org.apache.james.utils.InitializationOperation;
 import org.apache.james.utils.InitilizationOperationBuilder;
@@ -37,8 +37,8 @@ public class JPARecipientRewriteTableModule extends AbstractModule {
     public void configure() {
         bind(JPARecipientRewriteTable.class).in(Scopes.SINGLETON);
         bind(RecipientRewriteTable.class).to(JPARecipientRewriteTable.class);
-        bind(ReverseRecipientRewriteTableImpl.class).in(Scopes.SINGLETON);
-        bind(ReverseRecipientRewriteTable.class).to(ReverseRecipientRewriteTableImpl.class);
+        bind(AliasReverseResolverImpl.class).in(Scopes.SINGLETON);
+        bind(AliasReverseResolver.class).to(AliasReverseResolverImpl.class);
         bind(CanSendFromImpl.class).in(Scopes.SINGLETON);
         bind(CanSendFrom.class).to(CanSendFromImpl.class);
     }
diff --git a/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/data/MemoryDataModule.java b/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/data/MemoryDataModule.java
index d33c1ea..a99a408 100644
--- a/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/data/MemoryDataModule.java
+++ b/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/data/MemoryDataModule.java
@@ -32,11 +32,11 @@ import org.apache.james.mailrepository.memory.MailRepositoryStoreConfiguration;
 import org.apache.james.mailrepository.memory.MemoryMailRepository;
 import org.apache.james.mailrepository.memory.MemoryMailRepositoryUrlStore;
 import org.apache.james.modules.server.MailStoreRepositoryModule;
+import org.apache.james.rrt.api.AliasReverseResolver;
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.rrt.api.RecipientRewriteTable;
-import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
+import org.apache.james.rrt.lib.AliasReverseResolverImpl;
 import org.apache.james.rrt.lib.CanSendFromImpl;
-import org.apache.james.rrt.lib.ReverseRecipientRewriteTableImpl;
 import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
 import org.apache.james.server.core.configuration.ConfigurationProvider;
 import org.apache.james.user.api.UsersRepository;
@@ -70,8 +70,8 @@ public class MemoryDataModule extends AbstractModule {
         bind(MemoryRecipientRewriteTable.class).in(Scopes.SINGLETON);
         bind(RecipientRewriteTable.class).to(MemoryRecipientRewriteTable.class);
 
-        bind(ReverseRecipientRewriteTableImpl.class).in(Scopes.SINGLETON);
-        bind(ReverseRecipientRewriteTable.class).to(ReverseRecipientRewriteTableImpl.class);
+        bind(AliasReverseResolverImpl.class).in(Scopes.SINGLETON);
+        bind(AliasReverseResolver.class).to(AliasReverseResolverImpl.class);
 
         bind(CanSendFromImpl.class).in(Scopes.SINGLETON);
         bind(CanSendFrom.class).to(CanSendFromImpl.class);
diff --git a/server/container/spring/src/main/resources/META-INF/org/apache/james/spring-server.xml b/server/container/spring/src/main/resources/META-INF/org/apache/james/spring-server.xml
index 8a37ab5..b4a0753 100644
--- a/server/container/spring/src/main/resources/META-INF/org/apache/james/spring-server.xml
+++ b/server/container/spring/src/main/resources/META-INF/org/apache/james/spring-server.xml
@@ -326,6 +326,6 @@
 
     <bean id="jspfLogger" class="org.apache.james.smtpserver.fastfail.SPFHandler.SPFLogger"/>
 
-    <bean id="reverserecipientrewritetable" class="org.apache.james.rrt.lib.ReverseRecipientRewriteTableImpl" />
+    <bean id="aliasreverseresolver" class="org.apache.james.rrt.lib.AliasReverseResolverImpl" />
     <bean id="cansendfrom" class="org.apache.james.rrt.lib.CanSendFromImpl" />
 </beans>
diff --git a/server/container/util/src/main/java/org/apache/james/util/StreamUtils.java b/server/container/util/src/main/java/org/apache/james/util/StreamUtils.java
index aea59a1..73452ad 100644
--- a/server/container/util/src/main/java/org/apache/james/util/StreamUtils.java
+++ b/server/container/util/src/main/java/org/apache/james/util/StreamUtils.java
@@ -69,7 +69,7 @@ public class StreamUtils {
     }
 
     public static <T> Stream<T> iterate(T seed, Long limit, Function<T, Stream<T>> generator) {
-        Preconditions.checkArgument(limit >= 0, "StreamUtils.iterate have a given limit ok '{}', while it should not be negative", limit);
+        Preconditions.checkArgument(limit >= 0, "StreamUtils.iterate have a given limit '{}', while it should not be negative", limit);
         return StreamUtils.unfold(Arrays.asList(seed), conservativeGenerator(generator))
             .limit(limit + 1)
             .flatMap(List::stream);
diff --git a/server/container/util/src/test/java/org/apache/james/util/StreamUtilsTest.java b/server/container/util/src/test/java/org/apache/james/util/StreamUtilsTest.java
index 67f8527..f29ae43 100644
--- a/server/container/util/src/test/java/org/apache/james/util/StreamUtilsTest.java
+++ b/server/container/util/src/test/java/org/apache/james/util/StreamUtilsTest.java
@@ -117,7 +117,7 @@ class StreamUtilsTest {
     }
 
     @Test
-    void unfoldShouldGenerateAnFiniteStream() {
+    void unfoldShouldGenerateAFiniteStream() {
         Stream<Integer> unfolded = StreamUtils.unfold(1, i -> {
             if (i < 10) {
                 return Optional.of(i + 1);
diff --git a/server/data/data-api/pom.xml b/server/data/data-api/pom.xml
index 3818153..94f8b62 100644
--- a/server/data/data-api/pom.xml
+++ b/server/data/data-api/pom.xml
@@ -47,6 +47,10 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-configuration2</artifactId>
+        </dependency>
+        <dependency>
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
         </dependency>
@@ -54,5 +58,10 @@
             <groupId>com.sun.mail</groupId>
             <artifactId>javax.mail</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/server/data/data-api/src/main/java/org/apache/james/rrt/api/ReverseRecipientRewriteTable.java b/server/data/data-api/src/main/java/org/apache/james/rrt/api/AliasReverseResolver.java
similarity index 96%
rename from server/data/data-api/src/main/java/org/apache/james/rrt/api/ReverseRecipientRewriteTable.java
rename to server/data/data-api/src/main/java/org/apache/james/rrt/api/AliasReverseResolver.java
index 3f17aff..05117c5 100644
--- a/server/data/data-api/src/main/java/org/apache/james/rrt/api/ReverseRecipientRewriteTable.java
+++ b/server/data/data-api/src/main/java/org/apache/james/rrt/api/AliasReverseResolver.java
@@ -24,6 +24,6 @@ import java.util.stream.Stream;
 import org.apache.james.core.MailAddress;
 import org.apache.james.core.Username;
 
-public interface ReverseRecipientRewriteTable {
+public interface AliasReverseResolver {
     Stream<MailAddress> listAddresses(Username user) throws RecipientRewriteTable.ErrorMappingException, RecipientRewriteTableException;
 }
diff --git a/server/data/data-api/src/main/java/org/apache/james/rrt/api/RecipientRewriteTable.java b/server/data/data-api/src/main/java/org/apache/james/rrt/api/RecipientRewriteTable.java
index ece8f55..d11d338 100644
--- a/server/data/data-api/src/main/java/org/apache/james/rrt/api/RecipientRewriteTable.java
+++ b/server/data/data-api/src/main/java/org/apache/james/rrt/api/RecipientRewriteTable.java
@@ -122,7 +122,7 @@ public interface RecipientRewriteTable {
      */
     Map<MappingSource, Mappings> getAllMappings() throws RecipientRewriteTableException;
 
-    int getMappingLimit();
+    RecipientRewriteTableConfiguration getConfiguration();
 
     default Stream<MappingSource> listSources(Mapping mapping) throws RecipientRewriteTableException {
         Preconditions.checkArgument(listSourcesSupportedType.contains(mapping.getType()),
diff --git a/server/data/data-api/src/main/java/org/apache/james/rrt/api/RecipientRewriteTableConfiguration.java b/server/data/data-api/src/main/java/org/apache/james/rrt/api/RecipientRewriteTableConfiguration.java
new file mode 100644
index 0000000..e349ccc
--- /dev/null
+++ b/server/data/data-api/src/main/java/org/apache/james/rrt/api/RecipientRewriteTableConfiguration.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.rrt.api;
+
+import java.util.Objects;
+
+import org.apache.commons.configuration2.HierarchicalConfiguration;
+import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.apache.commons.configuration2.tree.ImmutableNode;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+
+public class RecipientRewriteTableConfiguration {
+
+    public static final boolean RECURSIVE_MAPPING_ENABLE = true;
+    public static final int DEFAULT_ENABLED_MAPPING_LIMIT = 10;
+    public static final int DISABLED_MAPPING_LIMIT = 0;
+
+    // The maximum mappings which will process before throwing exception
+    private final int mappingLimit;
+
+    private final boolean recursive;
+
+    @VisibleForTesting
+    public RecipientRewriteTableConfiguration(boolean recursive, int mappingLimit) {
+        Preconditions.checkArgument(mappingLimit == 0 || recursive, "mappingLimit can not be different than 0 when recursive mode is disabled");
+        this.recursive = recursive;
+        this.mappingLimit = mappingLimit;
+    }
+
+    public static RecipientRewriteTableConfiguration fromConfiguration(HierarchicalConfiguration<ImmutableNode> config) throws ConfigurationException {
+        boolean recursive = config.getBoolean("recursiveMapping", RECURSIVE_MAPPING_ENABLE);
+        int mappingLimit;
+        if (recursive) {
+            mappingLimit = config.getInt("mappingLimit", DEFAULT_ENABLED_MAPPING_LIMIT);
+            checkMappingLimit(mappingLimit);
+        } else {
+            mappingLimit = DISABLED_MAPPING_LIMIT;
+        }
+        return new RecipientRewriteTableConfiguration(recursive, mappingLimit);
+    }
+
+    private static void checkMappingLimit(int mappingLimit) throws ConfigurationException {
+        if (mappingLimit < 1) {
+            throw new ConfigurationException("The minimum mappingLimit is 1");
+        }
+    }
+
+    public int getMappingLimit() {
+        return mappingLimit;
+    }
+
+    public boolean isRecursive() {
+        return recursive;
+    }
+
+    @Override
+    public final boolean equals(Object other) {
+        if (other instanceof RecipientRewriteTableConfiguration) {
+            RecipientRewriteTableConfiguration that = (RecipientRewriteTableConfiguration) other;
+            return Objects.equals(mappingLimit, that.mappingLimit) && Objects.equals(recursive, that.recursive);
+        }
+
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(mappingLimit, recursive);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+            .add("mappingLimit", mappingLimit)
+            .add("recursive", recursive)
+            .toString();
+    }
+}
diff --git a/server/data/data-api/src/test/java/org/apache/james/rrt/api/RecipientRewriteTableConfigurationTest.java b/server/data/data-api/src/test/java/org/apache/james/rrt/api/RecipientRewriteTableConfigurationTest.java
new file mode 100644
index 0000000..f92e79b
--- /dev/null
+++ b/server/data/data-api/src/test/java/org/apache/james/rrt/api/RecipientRewriteTableConfigurationTest.java
@@ -0,0 +1,83 @@
+/****************************************************************
+ * 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.rrt.api;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
+import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.junit.jupiter.api.Test;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+class RecipientRewriteTableConfigurationTest {
+    @Test
+    void shouldRespectBeanContract() {
+        EqualsVerifier.forClass(RecipientRewriteTableConfiguration.class).verify();
+    }
+
+    @Test
+    void emptyConfigurationShouldHaveDefaults() throws ConfigurationException {
+        BaseHierarchicalConfiguration configuration = new BaseHierarchicalConfiguration();
+        RecipientRewriteTableConfiguration recipientRewriteTableConfiguration = RecipientRewriteTableConfiguration.fromConfiguration(configuration);
+
+        assertThat(recipientRewriteTableConfiguration.getMappingLimit())
+            .isEqualTo(10);
+        assertThat(recipientRewriteTableConfiguration.isRecursive())
+            .isTrue();
+    }
+
+    @Test
+    void negativeLimitShouldThrows() {
+        BaseHierarchicalConfiguration configuration = new BaseHierarchicalConfiguration();
+        configuration.addProperty("recursiveMapping", "true");
+        configuration.addProperty("mappingLimit", -1);
+
+        assertThatCode(() -> RecipientRewriteTableConfiguration.fromConfiguration(configuration))
+            .isInstanceOf(ConfigurationException.class);
+    }
+
+    @Test
+    void goodConfigurationShouldPopulateTheConfiguration() throws ConfigurationException {
+        BaseHierarchicalConfiguration configuration = new BaseHierarchicalConfiguration();
+        configuration.addProperty("recursiveMapping", "true");
+        configuration.addProperty("mappingLimit", 42);
+        RecipientRewriteTableConfiguration recipientRewriteTableConfiguration = RecipientRewriteTableConfiguration.fromConfiguration(configuration);
+
+        assertThat(recipientRewriteTableConfiguration.getMappingLimit())
+            .isEqualTo(42);
+        assertThat(recipientRewriteTableConfiguration.isRecursive())
+            .isTrue();
+    }
+
+    @Test
+    void goodConfigurationWithoutMappingShouldHaveANullMappingLevel() throws ConfigurationException {
+        BaseHierarchicalConfiguration configuration = new BaseHierarchicalConfiguration();
+        configuration.addProperty("recursiveMapping", "false");
+        configuration.addProperty("mappingLimit", 42);
+        RecipientRewriteTableConfiguration recipientRewriteTableConfiguration = RecipientRewriteTableConfiguration.fromConfiguration(configuration);
+
+        assertThat(recipientRewriteTableConfiguration.getMappingLimit())
+            .isEqualTo(0);
+        assertThat(recipientRewriteTableConfiguration.isRecursive())
+            .isFalse();
+    }
+}
\ No newline at end of file
diff --git a/server/data/data-api/src/test/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableContract.java b/server/data/data-api/src/test/java/org/apache/james/rrt/lib/AliasReverseResolverContract.java
similarity index 84%
rename from server/data/data-api/src/test/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableContract.java
rename to server/data/data-api/src/test/java/org/apache/james/rrt/lib/AliasReverseResolverContract.java
index edf4372..d0d7f66 100644
--- a/server/data/data-api/src/test/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableContract.java
+++ b/server/data/data-api/src/test/java/org/apache/james/rrt/lib/AliasReverseResolverContract.java
@@ -26,12 +26,12 @@ import java.util.stream.IntStream;
 
 import org.apache.james.core.Domain;
 import org.apache.james.core.Username;
-import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
+import org.apache.james.rrt.api.AliasReverseResolver;
 import org.junit.jupiter.api.Test;
 
 import com.github.fge.lambdas.Throwing;
 
-public interface ReverseRecipientRewriteTableContract {
+public interface AliasReverseResolverContract {
 
     Domain DOMAIN = Domain.of("example.com");
     Domain OTHER_DOMAIN = Domain.of("other.org");
@@ -39,7 +39,7 @@ public interface ReverseRecipientRewriteTableContract {
     Username USER_ALIAS = Username.of("alias@example.com");
     Username OTHER_USER = Username.of("other@example.com");
 
-    ReverseRecipientRewriteTable reverseRecipientRewriteTable();
+    AliasReverseResolver aliasReverseResolver();
 
     void addAliasMapping(Username alias, Username user) throws Exception;
 
@@ -70,14 +70,14 @@ public interface ReverseRecipientRewriteTableContract {
     }
     @Test
     default void listAddressesShouldContainOnlyUserAddressWhenUserHasNoAlias() throws Exception {
-        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+        assertThat(aliasReverseResolver().listAddresses(USER))
             .containsExactly(USER.asMailAddress());
     }
 
     @Test
     default void listAddressesShouldContainOnlyUserAddressWhenUserHasNoAliasAndAnotherUserHasOne() throws Exception {
         redirectUser(USER_ALIAS).to(OTHER_USER);
-        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+        assertThat(aliasReverseResolver().listAddresses(USER))
             .containsExactly(USER.asMailAddress());
     }
 
@@ -85,17 +85,17 @@ public interface ReverseRecipientRewriteTableContract {
     default void listAddressesShouldContainUserAddressAndAnAliasOfTheUser() throws Exception {
         redirectUser(USER_ALIAS).to(USER);
 
-        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+        assertThat(aliasReverseResolver().listAddresses(USER))
             .containsExactlyInAnyOrder(USER.asMailAddress(), USER_ALIAS.asMailAddress());
     }
 
     @Test
-    default void listAddressesFromShouldBeTrueWhenSenderIsAnAliasOfAnAliasOfTheUser() throws Exception {
+    default void listAddressesShouldBeTrueWhenSenderIsAnAliasOfAnAliasOfTheUser() throws Exception {
         Username userAliasBis = Username.of("aliasbis@" + DOMAIN.asString());
         redirectUser(userAliasBis).to(USER_ALIAS);
         redirectUser(USER_ALIAS).to(USER);
 
-        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+        assertThat(aliasReverseResolver().listAddresses(USER))
             .containsExactlyInAnyOrder(USER.asMailAddress(), USER_ALIAS.asMailAddress(), userAliasBis.asMailAddress());
     }
 
@@ -105,7 +105,7 @@ public interface ReverseRecipientRewriteTableContract {
 
         redirectDomain(OTHER_DOMAIN).to(DOMAIN);
 
-        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+        assertThat(aliasReverseResolver().listAddresses(USER))
             .containsExactlyInAnyOrder(USER.asMailAddress(), fromUser.asMailAddress());
     }
 
@@ -118,7 +118,7 @@ public interface ReverseRecipientRewriteTableContract {
 
         Username userAliasMainDomain = USER_ALIAS.withOtherDomain(Optional.of(DOMAIN));
         Username userOtherDomain = USER.withOtherDomain(Optional.of(OTHER_DOMAIN));
-        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+        assertThat(aliasReverseResolver().listAddresses(USER))
             .containsExactlyInAnyOrder(USER.asMailAddress(), userAliasOtherDomain.asMailAddress(), userAliasMainDomain.asMailAddress(), userOtherDomain.asMailAddress());
     }
 
@@ -140,28 +140,28 @@ public interface ReverseRecipientRewriteTableContract {
         Username userAliasExcluded = Username.of("alias" + (recursionLevel - 1) + "@" + DOMAIN.asString());
         redirectUser(USER_ALIAS).to(USER);
 
-        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+        assertThat(aliasReverseResolver().listAddresses(USER))
             .doesNotContain(userAliasExcluded.asMailAddress());
     }
 
     @Test
-    default void listAddressesShouldContainASendersAliasOfAnAliasInAnotherDomainOfTheUser() throws Exception {
+    default void listAddressesShouldContainASenderAliasOfAnAliasInAnotherDomainOfTheUser() throws Exception {
         Username userAlias = Username.of("aliasbis@" + OTHER_DOMAIN.asString());
         Username userAliasBis = Username.of("aliaster@" + OTHER_DOMAIN.asString());
         redirectUser(userAliasBis).to(userAlias);
         redirectUser(userAlias).to(USER);
 
-        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+        assertThat(aliasReverseResolver().listAddresses(USER))
             .contains(userAliasBis.asMailAddress());
     }
 
     @Test
-    default void listAddressesShouldContainAnUserAliasFollowingADomainAliasResolution() throws Exception {
+    default void listAddressesShouldContainAUserAliasFollowingADomainAliasResolution() throws Exception {
         Username userAliasBis = Username.of("aliasbis@" + OTHER_DOMAIN.asString());
         redirectUser(userAliasBis).to(USER_ALIAS);
         redirectUser(USER_ALIAS).to(USER);
 
-        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+        assertThat(aliasReverseResolver().listAddresses(USER))
             .contains(userAliasBis.asMailAddress());
     }
 }
diff --git a/server/data/data-api/src/test/java/org/apache/james/rrt/lib/CanSendFromContract.java b/server/data/data-api/src/test/java/org/apache/james/rrt/lib/CanSendFromContract.java
index 0a0201d..5a182ec 100644
--- a/server/data/data-api/src/test/java/org/apache/james/rrt/lib/CanSendFromContract.java
+++ b/server/data/data-api/src/test/java/org/apache/james/rrt/lib/CanSendFromContract.java
@@ -206,7 +206,7 @@ public interface CanSendFromContract {
     }
 
     @Test
-    default void allValidFromAddressesShouldContainASendersAliasOfAnAliasOfTheUser() throws Exception {
+    default void allValidFromAddressesShouldContainASenderAliasOfAnAliasOfTheUser() throws Exception {
         Username userAliasBis = Username.of("aliasbis@" + DOMAIN.asString());
         redirectUser(userAliasBis).to(USER_ALIAS);
         redirectUser(USER_ALIAS).to(USER);
@@ -237,7 +237,7 @@ public interface CanSendFromContract {
     }
 
     @Test
-    default void allValidFromAddressesShouldContainASendersAliasOfAnAliasInAnotherDomainOfTheUser() throws Exception {
+    default void allValidFromAddressesShouldContainASenderAliasOfAnAliasInAnotherDomainOfTheUser() throws Exception {
         Username userAlias = Username.of("aliasbis@" + OTHER_DOMAIN.asString());
         Username userAliasBis = Username.of("aliaster@" + OTHER_DOMAIN.asString());
         redirectUser(userAliasBis).to(userAlias);
@@ -248,7 +248,7 @@ public interface CanSendFromContract {
     }
 
     @Test
-    default void allValidFromAddressesShouldContainAnUserAliasFollowingADomainAliasResolution() throws Exception {
+    default void allValidFromAddressesShouldContainAUserAliasFollowingADomainAliasResolution() throws Exception {
         Username userAliasBis = Username.of("aliasbis@" + OTHER_DOMAIN.asString());
         redirectUser(userAliasBis).to(USER_ALIAS);
         redirectUser(USER_ALIAS).to(USER);
diff --git a/server/data/data-cassandra/src/test/java/org/apache/james/rrt/cassandra/CassandraRecipientRewriteTableV6Test.java b/server/data/data-cassandra/src/test/java/org/apache/james/rrt/cassandra/CassandraRecipientRewriteTableV6Test.java
index 9412389..34e51ad 100644
--- a/server/data/data-cassandra/src/test/java/org/apache/james/rrt/cassandra/CassandraRecipientRewriteTableV6Test.java
+++ b/server/data/data-cassandra/src/test/java/org/apache/james/rrt/cassandra/CassandraRecipientRewriteTableV6Test.java
@@ -69,7 +69,6 @@ public class CassandraRecipientRewriteTableV6Test extends AbstractRecipientRewri
             new CassandraRecipientRewriteTableDAO(cassandra.getConf(), CassandraUtils.WITH_DEFAULT_CONFIGURATION),
             new CassandraMappingsSourcesDAO(cassandra.getConf()),
             cassandraSchemaVersionDAO);
-        rrt.configure(new BaseHierarchicalConfiguration());
 
         cassandraSchemaVersionDAO.updateVersion(SCHEMA_VERSION_V6);
 
diff --git a/server/data/data-cassandra/src/test/java/org/apache/james/rrt/cassandra/CassandraRecipientRewriteTableV7Test.java b/server/data/data-cassandra/src/test/java/org/apache/james/rrt/cassandra/CassandraRecipientRewriteTableV7Test.java
index 0523ed6..2c6a205 100644
--- a/server/data/data-cassandra/src/test/java/org/apache/james/rrt/cassandra/CassandraRecipientRewriteTableV7Test.java
+++ b/server/data/data-cassandra/src/test/java/org/apache/james/rrt/cassandra/CassandraRecipientRewriteTableV7Test.java
@@ -69,7 +69,6 @@ public class CassandraRecipientRewriteTableV7Test extends AbstractRecipientRewri
             new CassandraRecipientRewriteTableDAO(cassandra.getConf(), CassandraUtils.WITH_DEFAULT_CONFIGURATION),
             new CassandraMappingsSourcesDAO(cassandra.getConf()),
             cassandraSchemaVersionDAO);
-        rrt.configure(new BaseHierarchicalConfiguration());
 
         cassandraSchemaVersionDAO.updateVersion(SCHEMA_VERSION_V7);
 
diff --git a/server/data/data-cassandra/src/test/java/org/apache/james/rrt/cassandra/CassandraStepdefs.java b/server/data/data-cassandra/src/test/java/org/apache/james/rrt/cassandra/CassandraStepdefs.java
index a32812f..eb16b46 100644
--- a/server/data/data-cassandra/src/test/java/org/apache/james/rrt/cassandra/CassandraStepdefs.java
+++ b/server/data/data-cassandra/src/test/java/org/apache/james/rrt/cassandra/CassandraStepdefs.java
@@ -18,7 +18,6 @@
  ****************************************************************/
 package org.apache.james.rrt.cassandra;
 
-import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
 import org.apache.james.backends.cassandra.CassandraCluster;
 import org.apache.james.backends.cassandra.DockerCassandraRule;
 import org.apache.james.backends.cassandra.components.CassandraModule;
@@ -30,6 +29,7 @@ import org.apache.james.rrt.lib.RecipientRewriteTableFixture;
 import org.apache.james.rrt.lib.RewriteTablesStepdefs;
 import org.junit.Rule;
 
+import com.github.fge.lambdas.Throwing;
 import cucumber.api.java.After;
 import cucumber.api.java.Before;
 
@@ -51,7 +51,7 @@ public class CassandraStepdefs {
         cassandra = CassandraCluster.create(
             CassandraModule.aggregateModules(CassandraRRTModule.MODULE, CassandraSchemaVersionModule.MODULE),
             cassandraServer.getHost());
-        mainStepdefs.rewriteTable = getRecipientRewriteTable();
+        mainStepdefs.setUp(Throwing.supplier(this::getRecipientRewriteTable).sneakyThrow());
     }
 
     @After
@@ -64,7 +64,6 @@ public class CassandraStepdefs {
             new CassandraRecipientRewriteTableDAO(cassandra.getConf(), CassandraUtils.WITH_DEFAULT_CONFIGURATION),
             new CassandraMappingsSourcesDAO(cassandra.getConf()),
             new CassandraSchemaVersionDAO(cassandra.getConf()));
-        rrt.configure(new BaseHierarchicalConfiguration());
         rrt.setDomainList(RecipientRewriteTableFixture.domainListForCucumberTests());
         return rrt;
     }
diff --git a/server/data/data-file/src/test/java/org/apache/james/rrt/file/XMLRecipientRewriteTableTest.java b/server/data/data-file/src/test/java/org/apache/james/rrt/file/XMLRecipientRewriteTableTest.java
index 4b3f4d4..7ef0a37 100644
--- a/server/data/data-file/src/test/java/org/apache/james/rrt/file/XMLRecipientRewriteTableTest.java
+++ b/server/data/data-file/src/test/java/org/apache/james/rrt/file/XMLRecipientRewriteTableTest.java
@@ -18,19 +18,10 @@
  ****************************************************************/
 package org.apache.james.rrt.file;
 
-import java.util.ArrayList;
-import java.util.List;
-
 import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
 import org.apache.commons.configuration2.convert.DisabledListDelimiterHandler;
-import org.apache.james.rrt.api.RecipientRewriteTableException;
 import org.apache.james.rrt.lib.AbstractRecipientRewriteTable;
 import org.apache.james.rrt.lib.AbstractRecipientRewriteTableTest;
-import org.apache.james.rrt.lib.Mapping;
-import org.apache.james.rrt.lib.Mapping.Type;
-import org.apache.james.rrt.lib.MappingSource;
-import org.apache.james.rrt.lib.Mappings;
-import org.apache.james.rrt.lib.MappingsImpl;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Ignore;
@@ -55,22 +46,7 @@ public class XMLRecipientRewriteTableTest extends AbstractRecipientRewriteTableT
 
     @Override
     protected AbstractRecipientRewriteTable getRecipientRewriteTable() {
-        return new XMLRecipientRewriteTable() {
-            @Override
-            public void addMapping(MappingSource source, Mapping mapping) throws RecipientRewriteTableException {
-                addMappingToConfiguration(source, mapping.getType().withoutPrefix(mapping.asString()), mapping.getType());
-            }
-
-            @Override
-            public void removeMapping(MappingSource source, Mapping mapping) throws RecipientRewriteTableException {
-                removeMappingFromConfiguration(source, mapping.getType().withoutPrefix(mapping.asString()), mapping.getType());
-            }
-
-            @Override
-            public void addAddressMapping(MappingSource source, String address) throws RecipientRewriteTableException {
-                addMapping(source, Mapping.address(address));
-            }
-        };
+        return new XMLRecipientRewriteTable();
     }
 
     @Test
@@ -85,57 +61,182 @@ public class XMLRecipientRewriteTableTest extends AbstractRecipientRewriteTableT
     public void addMappingShouldThrowWhenMappingAlreadyExists() {
     }
 
-    protected void addMappingToConfiguration(MappingSource source, String mapping, Type type) throws RecipientRewriteTableException {
-        Mappings mappings = virtualUserTable.getStoredMappings(source);
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void testStoreAndGetMappings() {
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void testStoreAndRetrieveRegexMapping() {
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void getAllMappingsShouldListAllEntries() {
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void testStoreAndRetrieveAddressMapping() {
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void testStoreAndRetrieveErrorMapping() {
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void testStoreAndRetrieveWildCardAddressMapping() {
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void testNonRecursiveMapping() {
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void testAliasDomainMapping() {
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void addMappingShouldNotThrowWhenMappingAlreadyExistsWithAnOtherType() {
+    }
 
-        Mappings updatedMappings = MappingsImpl.from(mappings)
-            .add(Mapping.of(type, mapping))
-            .build();
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void addForwardMappingShouldStore() {
+    }
 
-        updateConfiguration(source, mappings, updatedMappings);
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void removeForwardMappingShouldDelete() {
     }
 
-    protected void removeMappingFromConfiguration(MappingSource source, String mapping, Type type) throws RecipientRewriteTableException {
-        Mappings oldMappings = virtualUserTable.getStoredMappings(source);
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void addGroupMappingShouldStore() {
+    }
 
-        if (oldMappings.isEmpty()) {
-            throw new RecipientRewriteTableException("Cannot remove from null mappings");
-        }
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void removeGroupMappingShouldDelete() {
+    }
 
-        Mappings updatedMappings = oldMappings.remove(Mapping.of(type, mapping));
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void addAliasMappingShouldStore() {
 
-        updateConfiguration(source, oldMappings, updatedMappings);
     }
 
-    private void updateConfiguration(MappingSource source, Mappings oldMappings, Mappings updatedMappings) throws RecipientRewriteTableException {
-        removeMappingsFromConfig(source, oldMappings);
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void removeAliasMappingShouldDelete() {
 
-        if (!updatedMappings.isEmpty()) {
-            defaultConfiguration.addProperty("mapping", source.getFixedUser() + "@" + source.getFixedDomain() + "=" + updatedMappings.serialize());
-        }
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void listSourcesShouldReturnWhenHasMapping() {
+
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void listSourcesShouldReturnWhenMultipleSourceMapping() {
+
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void listSourcesShouldReturnWhenHasForwardMapping() {
 
-        try {
-            virtualUserTable.configure(defaultConfiguration);
-        } catch (Exception e) {
-            if (updatedMappings.size() > 0) {
-                throw new RecipientRewriteTableException("Error update mapping", e);
-            }
-        }
     }
 
-    private void removeMappingsFromConfig(MappingSource source, Mappings mappings) {
-        List<String> stored = new ArrayList<>();
-        for (String c : defaultConfiguration.getStringArray("mapping")) {
-            String mapping = source.getFixedUser() + "@" + source.getFixedDomain() + "=" + mappings.serialize();
-            if (!c.equalsIgnoreCase(mapping)) {
-                stored.add(c);
-            }
-        }
-        // clear old values
-        defaultConfiguration.clear();
-        // add stored mappings
-        for (String aStored : stored) {
-            defaultConfiguration.addProperty("mapping", aStored);
-        }
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void listSourcesShouldReturnAliasMappings() {
+
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void listSourcesShouldReturnWhenHasAddressMapping() {
+
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void listSourcesShouldThrowExceptionWhenHasRegexMapping() {
+
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void listSourcesShouldHandleDomainMapping() {
+
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void listSourcesShouldHandleDomainSource() {
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void listSourcesShouldHandleDomainSources() {
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void listSourcesShouldThrowExceptionWhenHasErrorMapping() {
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void listSourcesShouldReturnEmptyWhenMappingDoesNotExist() {
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void getSourcesForTypeShouldReturnEmptyWhenNoMatchingMapping() {
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void getSourcesForTypeShouldReturnMatchingMapping() {
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void getSourcesForTypeShouldNotReturnDuplicatedSources() {
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void getSourcesForTypeShouldReturnSortedStream() {
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void getMappingsForTypeShouldReturnEmptyWhenNoMatchingMapping() {
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void getMappingsForTypeShouldReturnMatchingMapping() {
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void getMappingsForTypeShouldNotReturnDuplicatedDestinations() {
+    }
+
+    @Test
+    @Ignore("XMLRecipientRewriteTable is read only")
+    public void getMappingsForTypeShouldReturnSortedStream() {
     }
 }
diff --git a/server/data/data-file/src/test/java/org/apache/james/rrt/file/XMLStepdefs.java b/server/data/data-file/src/test/java/org/apache/james/rrt/file/XMLStepdefs.java
index 580f928..789f9b3 100644
--- a/server/data/data-file/src/test/java/org/apache/james/rrt/file/XMLStepdefs.java
+++ b/server/data/data-file/src/test/java/org/apache/james/rrt/file/XMLStepdefs.java
@@ -33,10 +33,10 @@ public class XMLStepdefs {
 
     @Before
     public void setup() throws Throwable {
-        mainStepdefs.rewriteTable = getRecipientRewriteTable(); 
+        mainStepdefs.setUp(this::getRecipientRewriteTable);
     }
 
-    private AbstractRecipientRewriteTable getRecipientRewriteTable() throws Exception {
+    private AbstractRecipientRewriteTable getRecipientRewriteTable() {
         return new XMLRecipientRewriteTable();
     }
 }
diff --git a/server/data/data-jpa/src/test/java/org/apache/james/rrt/jpa/JPARecipientRewriteTableTest.java b/server/data/data-jpa/src/test/java/org/apache/james/rrt/jpa/JPARecipientRewriteTableTest.java
index 80f02fa..5f50ab9 100644
--- a/server/data/data-jpa/src/test/java/org/apache/james/rrt/jpa/JPARecipientRewriteTableTest.java
+++ b/server/data/data-jpa/src/test/java/org/apache/james/rrt/jpa/JPARecipientRewriteTableTest.java
@@ -46,8 +46,6 @@ public class JPARecipientRewriteTableTest extends AbstractRecipientRewriteTableT
     protected AbstractRecipientRewriteTable getRecipientRewriteTable() throws Exception {
         JPARecipientRewriteTable localVirtualUserTable = new JPARecipientRewriteTable();
         localVirtualUserTable.setEntityManagerFactory(JPA_TEST_CLUSTER.getEntityManagerFactory());
-        BaseHierarchicalConfiguration defaultConfiguration = new BaseHierarchicalConfiguration();
-        localVirtualUserTable.configure(defaultConfiguration);
         return localVirtualUserTable;
     }
 }
diff --git a/server/data/data-jpa/src/test/java/org/apache/james/rrt/jpa/JPAStepdefs.java b/server/data/data-jpa/src/test/java/org/apache/james/rrt/jpa/JPAStepdefs.java
index 4b27b75..249b0b2 100644
--- a/server/data/data-jpa/src/test/java/org/apache/james/rrt/jpa/JPAStepdefs.java
+++ b/server/data/data-jpa/src/test/java/org/apache/james/rrt/jpa/JPAStepdefs.java
@@ -18,13 +18,13 @@
  ****************************************************************/
 package org.apache.james.rrt.jpa;
 
-import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
 import org.apache.james.backends.jpa.JpaTestCluster;
 import org.apache.james.rrt.jpa.model.JPARecipientRewrite;
 import org.apache.james.rrt.lib.AbstractRecipientRewriteTable;
 import org.apache.james.rrt.lib.RecipientRewriteTableFixture;
 import org.apache.james.rrt.lib.RewriteTablesStepdefs;
 
+import com.github.fge.lambdas.Throwing;
 import cucumber.api.java.After;
 import cucumber.api.java.Before;
 
@@ -40,7 +40,7 @@ public class JPAStepdefs {
 
     @Before
     public void setup() throws Throwable {
-        mainStepdefs.rewriteTable = getRecipientRewriteTable(); 
+        mainStepdefs.setUp(Throwing.supplier(this::getRecipientRewriteTable).sneakyThrow());
     }
 
     @After
@@ -51,8 +51,6 @@ public class JPAStepdefs {
     private AbstractRecipientRewriteTable getRecipientRewriteTable() throws Exception {
         JPARecipientRewriteTable localVirtualUserTable = new JPARecipientRewriteTable();
         localVirtualUserTable.setEntityManagerFactory(JPA_TEST_CLUSTER.getEntityManagerFactory());
-        BaseHierarchicalConfiguration defaultConfiguration = new BaseHierarchicalConfiguration();
-        localVirtualUserTable.configure(defaultConfiguration);
         localVirtualUserTable.setDomainList(RecipientRewriteTableFixture.domainListForCucumberTests());
         return localVirtualUserTable;
     }
diff --git a/server/data/data-library/src/main/java/org/apache/james/rrt/lib/AbstractRecipientRewriteTable.java b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/AbstractRecipientRewriteTable.java
index cafc427..b341d92 100644
--- a/server/data/data-library/src/main/java/org/apache/james/rrt/lib/AbstractRecipientRewriteTable.java
+++ b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/AbstractRecipientRewriteTable.java
@@ -40,6 +40,7 @@ import org.apache.james.lifecycle.api.Configurable;
 import org.apache.james.rrt.api.InvalidRegexException;
 import org.apache.james.rrt.api.MappingAlreadyExistsException;
 import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.RecipientRewriteTableConfiguration;
 import org.apache.james.rrt.api.RecipientRewriteTableException;
 import org.apache.james.rrt.api.SameSourceAndDestinationException;
 import org.apache.james.rrt.api.SourceDomainIsNotInDomainListException;
@@ -49,24 +50,17 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.github.fge.lambdas.Throwing;
+import com.google.common.base.Preconditions;
 
 public abstract class AbstractRecipientRewriteTable implements RecipientRewriteTable, Configurable {
     private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRecipientRewriteTable.class);
 
-    // The maximum mappings which will process before throwing exception
-    private int mappingLimit = 10;
-
-    private boolean recursive = true;
-
+    private RecipientRewriteTableConfiguration configuration;
     private DomainList domainList;
 
-    @Override
-    public int getMappingLimit() {
-        if (recursive) {
-            return mappingLimit;
-        } else {
-            return 0;
-        }
+    public void setConfiguration(RecipientRewriteTableConfiguration configuration) {
+        Preconditions.checkState(this.configuration == null, "A configuration cannot be set twice");
+        this.configuration = configuration;
     }
 
     @Inject
@@ -76,44 +70,23 @@ public abstract class AbstractRecipientRewriteTable implements RecipientRewriteT
 
     @Override
     public void configure(HierarchicalConfiguration<ImmutableNode> config) throws ConfigurationException {
-        setRecursiveMapping(config.getBoolean("recursiveMapping", true));
-        try {
-            setMappingLimit(config.getInt("mappingLimit", 10));
-        } catch (IllegalArgumentException e) {
-            throw new ConfigurationException(e.getMessage());
-        }
+        setConfiguration(RecipientRewriteTableConfiguration.fromConfiguration(config));
         doConfigure(config);
     }
 
-    /**
-     * Override to handle config
-     */
-    protected void doConfigure(HierarchicalConfiguration<ImmutableNode> conf) throws ConfigurationException {
-    }
+    protected void doConfigure(HierarchicalConfiguration<ImmutableNode> arg0) throws ConfigurationException {
 
-    public void setRecursiveMapping(boolean recursive) {
-        this.recursive = recursive;
     }
 
-    /**
-     * Set the mappingLimit
-     * 
-     * @param mappingLimit
-     *            the mappingLimit
-     * @throws IllegalArgumentException
-     *             get thrown if mappingLimit smaller then 1 is used
-     */
-    public void setMappingLimit(int mappingLimit) throws IllegalArgumentException {
-        if (mappingLimit < 1) {
-            throw new IllegalArgumentException("The minimum mappingLimit is 1");
-        }
-        this.mappingLimit = mappingLimit;
+    @Override
+    public RecipientRewriteTableConfiguration getConfiguration() {
+        return configuration;
     }
 
     @Override
     public Mappings getResolvedMappings(String user, Domain domain, EnumSet<Type> mappingTypes) throws ErrorMappingException, RecipientRewriteTableException {
-
-        return getMappings(Username.fromLocalPartWithDomain(user, domain), mappingLimit, mappingTypes);
+        Preconditions.checkState(this.configuration != null, "RecipientRewriteTable is not configured");
+        return getMappings(Username.fromLocalPartWithDomain(user, domain), configuration.getMappingLimit(), mappingTypes);
     }
 
     private Mappings getMappings(Username username, int mappingLimit, EnumSet<Type> mappingTypes) throws ErrorMappingException, RecipientRewriteTableException {
@@ -155,7 +128,7 @@ public abstract class AbstractRecipientRewriteTable implements RecipientRewriteT
         LOGGER.debug("Valid virtual user mapping {} to {}", originalUsername.asString(), rewrittenUsername.asString());
 
         Stream<Mapping> nonRecursiveResult = Stream.of(toMapping(rewrittenUsername, mapping.getType()));
-        if (!recursive) {
+        if (!configuration.isRecursive()) {
             return nonRecursiveResult;
         }
 
diff --git a/server/data/data-library/src/main/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableImpl.java b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/AliasReverseResolverImpl.java
similarity index 91%
rename from server/data/data-library/src/main/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableImpl.java
rename to server/data/data-library/src/main/java/org/apache/james/rrt/lib/AliasReverseResolverImpl.java
index 419a69b..5bec3f7 100644
--- a/server/data/data-library/src/main/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableImpl.java
+++ b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/AliasReverseResolverImpl.java
@@ -31,21 +31,23 @@ import javax.inject.Inject;
 import org.apache.james.core.Domain;
 import org.apache.james.core.MailAddress;
 import org.apache.james.core.Username;
+import org.apache.james.rrt.api.AliasReverseResolver;
 import org.apache.james.rrt.api.RecipientRewriteTable;
 import org.apache.james.rrt.api.RecipientRewriteTableException;
-import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.util.OptionalUtils;
 import org.apache.james.util.StreamUtils;
 
 import com.github.fge.lambdas.Throwing;
 import com.github.steveash.guavate.Guavate;
 
-public class ReverseRecipientRewriteTableImpl implements ReverseRecipientRewriteTable {
+public class AliasReverseResolverImpl implements AliasReverseResolver {
     private final RecipientRewriteTable recipientRewriteTable;
+    private final int mappingLimit;
 
     @Inject
-    public ReverseRecipientRewriteTableImpl(RecipientRewriteTable recipientRewriteTable) {
+    public AliasReverseResolverImpl(RecipientRewriteTable recipientRewriteTable) {
         this.recipientRewriteTable = recipientRewriteTable;
+        this.mappingLimit = recipientRewriteTable.getConfiguration().getMappingLimit();
     }
 
     @Override
@@ -64,7 +66,7 @@ public class ReverseRecipientRewriteTableImpl implements ReverseRecipientRewrite
     private Stream<Username> relatedAliases(Username user) {
         return StreamUtils.iterate(
             user,
-            (long) recipientRewriteTable.getMappingLimit(),
+            (long) mappingLimit,
             Throwing.<Username, Stream<Username>>function(targetUser ->
                 recipientRewriteTable
                     .listSources(Mapping.alias(targetUser.asString()))
@@ -94,7 +96,7 @@ public class ReverseRecipientRewriteTableImpl implements ReverseRecipientRewrite
     private Stream<Domain> fetchDomains(Domain domain) {
         return StreamUtils.iterate(
             domain,
-            (long) recipientRewriteTable.getMappingLimit(),
+            (long) mappingLimit,
             Throwing.<Domain, Stream<Domain>>function(targetDomain ->
                 recipientRewriteTable
                     .listSources(Mapping.domain(targetDomain))
diff --git a/server/data/data-library/src/main/java/org/apache/james/rrt/lib/CanSendFromImpl.java b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/CanSendFromImpl.java
index b2bedc8..70d7a82 100644
--- a/server/data/data-library/src/main/java/org/apache/james/rrt/lib/CanSendFromImpl.java
+++ b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/CanSendFromImpl.java
@@ -30,10 +30,10 @@ import javax.inject.Inject;
 import org.apache.james.core.Domain;
 import org.apache.james.core.MailAddress;
 import org.apache.james.core.Username;
+import org.apache.james.rrt.api.AliasReverseResolver;
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.rrt.api.RecipientRewriteTable;
 import org.apache.james.rrt.api.RecipientRewriteTableException;
-import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.util.OptionalUtils;
 
 public class CanSendFromImpl implements CanSendFrom {
@@ -45,12 +45,12 @@ public class CanSendFromImpl implements CanSendFrom {
 
     public static final EnumSet<Mapping.Type> ALIAS_TYPES_ACCEPTED_IN_FROM = EnumSet.of(Alias, Domain);
     private final RecipientRewriteTable recipientRewriteTable;
-    private final ReverseRecipientRewriteTable reverseRecipientRewriteTable;
+    private final AliasReverseResolver aliasReverseResolver;
 
     @Inject
-    public CanSendFromImpl(RecipientRewriteTable recipientRewriteTable, ReverseRecipientRewriteTable reverseRecipientRewriteTable) {
+    public CanSendFromImpl(RecipientRewriteTable recipientRewriteTable, AliasReverseResolver aliasReverseResolver) {
         this.recipientRewriteTable = recipientRewriteTable;
-        this.reverseRecipientRewriteTable = reverseRecipientRewriteTable;
+        this.aliasReverseResolver = aliasReverseResolver;
     }
 
     @Override
@@ -64,7 +64,7 @@ public class CanSendFromImpl implements CanSendFrom {
 
     @Override
     public Stream<MailAddress> allValidFromAddressesForUser(Username user) throws RecipientRewriteTable.ErrorMappingException, RecipientRewriteTableException {
-        return reverseRecipientRewriteTable.listAddresses(user);
+        return aliasReverseResolver.listAddresses(user);
     }
 
     private boolean emailIsAnAliasOfTheConnectedUser(Username connectedUser, Username fromUser) throws RecipientRewriteTable.ErrorMappingException, RecipientRewriteTableException {
diff --git a/server/data/data-library/src/test/java/org/apache/james/rrt/lib/AbstractRecipientRewriteTableTest.java b/server/data/data-library/src/test/java/org/apache/james/rrt/lib/AbstractRecipientRewriteTableTest.java
index fab6ab3..4f9928c 100644
--- a/server/data/data-library/src/test/java/org/apache/james/rrt/lib/AbstractRecipientRewriteTableTest.java
+++ b/server/data/data-library/src/test/java/org/apache/james/rrt/lib/AbstractRecipientRewriteTableTest.java
@@ -19,6 +19,7 @@
 package org.apache.james.rrt.lib;
 
 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.Map;
@@ -28,6 +29,7 @@ import org.apache.james.core.Domain;
 import org.apache.james.domainlist.api.mock.SimpleDomainList;
 import org.apache.james.lifecycle.api.LifecycleUtil;
 import org.apache.james.rrt.api.RecipientRewriteTable.ErrorMappingException;
+import org.apache.james.rrt.api.RecipientRewriteTableConfiguration;
 import org.apache.james.rrt.api.RecipientRewriteTableException;
 import org.apache.james.rrt.api.SourceDomainIsNotInDomainListException;
 import org.junit.Rule;
@@ -58,6 +60,20 @@ public abstract class AbstractRecipientRewriteTableTest {
     protected AbstractRecipientRewriteTable virtualUserTable;
 
     public void setUp() throws Exception {
+        setRecursiveRecipientRewriteTable();
+    }
+
+    private void setRecursiveRecipientRewriteTable() throws Exception {
+        setNotConfiguredRecipientRewriteTable();
+        virtualUserTable.setConfiguration(new RecipientRewriteTableConfiguration(true, 10));
+    }
+
+    private void setNonRecursiveRecipientRewriteTable() throws Exception {
+        setNotConfiguredRecipientRewriteTable();
+        virtualUserTable.setConfiguration(new RecipientRewriteTableConfiguration(false, 0));
+    }
+
+    private void setNotConfiguredRecipientRewriteTable() throws Exception {
         virtualUserTable = getRecipientRewriteTable();
 
         SimpleDomainList domainList = new SimpleDomainList();
@@ -89,6 +105,19 @@ public abstract class AbstractRecipientRewriteTableTest {
     }
 
     @Test
+    public void notConfiguredResolutionShouldThrow() throws Exception {
+        setNotConfiguredRecipientRewriteTable();
+        assertThatCode(() -> virtualUserTable.getResolvedMappings(USER, Domain.LOCALHOST))
+            .isInstanceOf(IllegalStateException.class);
+    }
+
+    @Test
+    public void configuringTwiceShouldThrow() {
+        assertThatCode(() -> virtualUserTable.setConfiguration(new RecipientRewriteTableConfiguration(true, 10)))
+            .isInstanceOf(IllegalStateException.class);
+    }
+
+    @Test
     public void testStoreAndRetrieveRegexMapping() throws Exception {
         String regex = "(.*)@localhost";
         String regex2 = "(.+)@test";
@@ -211,7 +240,7 @@ public abstract class AbstractRecipientRewriteTableTest {
     }
 
     @Test
-    public void testRecursiveMapping() throws Exception {
+    public void testNonRecursiveMapping() throws Exception {
         String user1 = "user1";
         String user2 = "user2";
         String user3 = "user3";
@@ -220,26 +249,17 @@ public abstract class AbstractRecipientRewriteTableTest {
         Domain domain3 = Domain.of("domain3");
         MappingSource source1 = MappingSource.fromUser(user1, domain1);
         MappingSource source2 = MappingSource.fromUser(user2, domain2);
-        MappingSource source3 = MappingSource.fromUser(user3, domain3);
 
-        virtualUserTable.setRecursiveMapping(true);
+        setNonRecursiveRecipientRewriteTable();
 
         assertThat(virtualUserTable.getAllMappings()).describedAs("No mapping").isEmpty();
 
         virtualUserTable.addMapping(source1, Mapping.address(user2 + "@" + domain2.asString()));
         virtualUserTable.addMapping(source2, Mapping.address(user3 + "@" + domain3.asString()));
-        assertThat(virtualUserTable.getResolvedMappings(user1, domain1)).containsOnly(Mapping.address(user3 + "@" + domain3.asString()));
-        virtualUserTable.addMapping(source3, Mapping.address(user1 + "@" + domain1.asString()));
-
         assertThatThrownBy(() ->
             virtualUserTable.getResolvedMappings(user1, domain1))
-            .describedAs("Exception thrown on to many mappings")
+            .describedAs("Exception thrown on too many mappings")
             .isInstanceOf(ErrorMappingException.class);
-
-        // disable recursive mapping
-        virtualUserTable.setRecursiveMapping(false);
-        assertThat(virtualUserTable.getResolvedMappings(user1, domain1)).describedAs("Not recursive mapped")
-            .containsExactly(Mapping.address(user2 + "@" + domain2.asString()));
     }
 
     @Test
diff --git a/server/data/data-library/src/test/java/org/apache/james/rrt/lib/RewriteTablesStepdefs.java b/server/data/data-library/src/test/java/org/apache/james/rrt/lib/RewriteTablesStepdefs.java
index 3b3d80e..23d7684 100644
--- a/server/data/data-library/src/test/java/org/apache/james/rrt/lib/RewriteTablesStepdefs.java
+++ b/server/data/data-library/src/test/java/org/apache/james/rrt/lib/RewriteTablesStepdefs.java
@@ -22,9 +22,11 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import java.util.List;
+import java.util.function.Supplier;
 
 import org.apache.james.core.Domain;
 import org.apache.james.rrt.api.RecipientRewriteTable.ErrorMappingException;
+import org.apache.james.rrt.api.RecipientRewriteTableConfiguration;
 import org.apache.james.rrt.api.RecipientRewriteTableException;
 
 import cucumber.api.java.en.Given;
@@ -33,9 +35,16 @@ import cucumber.api.java.en.When;
 
 public class RewriteTablesStepdefs {
 
-    public AbstractRecipientRewriteTable rewriteTable;
+    private Supplier<AbstractRecipientRewriteTable> recipientRewriteTableSupplier;
+    private AbstractRecipientRewriteTable rewriteTable;
     private Exception exception;
 
+    public void setUp(Supplier<AbstractRecipientRewriteTable> recipientRewriteTableSupplier) {
+        this.recipientRewriteTableSupplier = recipientRewriteTableSupplier;
+        this.rewriteTable = this.recipientRewriteTableSupplier.get();
+        this.rewriteTable.setConfiguration(new RecipientRewriteTableConfiguration(true, 10));
+    }
+
     @Given("store \"([^\"]*)\" regexp mapping for user \"([^\"]*)\" at domain \"([^\"]*)\"")
     public void storeRegexpMappingForUserAtDomain(String regexp, String user, String domain) throws Throwable {
         MappingSource source = MappingSource.fromUser(user, domain);
@@ -98,12 +107,13 @@ public class RewriteTablesStepdefs {
 
     @Given("recursive mapping is disable")
     public void disableRecursiveMapping() {
-        rewriteTable.setRecursiveMapping(false);
+        this.rewriteTable = this.recipientRewriteTableSupplier.get();
+        this.rewriteTable.setConfiguration(new RecipientRewriteTableConfiguration(false, 0));
     }
 
     @Given("recursive mapping is enable")
     public void enableRecursiveMapping() {
-        rewriteTable.setRecursiveMapping(true);
+        // default case
     }
 
     @When("user \"([^\"]*)\" at domain \"([^\"]*)\" removes a regexp mapping \"([^\"]*)\"")
diff --git a/server/data/data-library/src/test/resources/cucumber/rewrite_tables.feature b/server/data/data-library/src/test/resources/cucumber/rewrite_tables.feature
index 9e1a367..c4367de 100644
--- a/server/data/data-library/src/test/resources/cucumber/rewrite_tables.feature
+++ b/server/data/data-library/src/test/resources/cucumber/rewrite_tables.feature
@@ -107,13 +107,13 @@ Feature: Rewrite Tables tests
     Given recursive mapping is disable
     Given store "test@localhost" address mapping as wildcard for domain "localhost"
     And store "mine@localhost" address mapping for user "user" at domain "localhost"
-    Then mappings for user "user" at domain "localhost" should contain only "mine@localhost"
+    Then retrieving mappings for user "user" at domain "localhost" should raise an ErrorMappingException with message "554 Too many mappings to process"
 
   Scenario: direct mapping should override address mapping as wildcard (reverse insertion order)
     Given recursive mapping is disable
     Given store "mine@localhost" address mapping for user "user" at domain "localhost"
     And store "test@localhost" address mapping as wildcard for domain "localhost"
-    Then mappings for user "user" at domain "localhost" should contain only "mine@localhost"
+    Then retrieving mappings for user "user" at domain "localhost" should raise an ErrorMappingException with message "554 Too many mappings to process"
 
   Scenario: direct mapping should not override address mapping as wildcard when other user
     Given store "test@localhost" address mapping as wildcard for domain "localhost"
@@ -188,11 +188,11 @@ Feature: Rewrite Tables tests
 
 # Recursive mapping
 
-  Scenario: direct mapping should be returned when recursive mapping is disable
+  Scenario: direct mapping should throw when recursive mapping is disable
     Given recursive mapping is disable
     And store "user2@domain2" address mapping for user "user1" at domain "domain1"
     And store "user3@domain3" address mapping for user "user2" at domain "domain2"
-    Then mappings for user "user1" at domain "domain1" should contain only "user2@domain2"
+    Then retrieving mappings for user "user1" at domain "localhost1" should raise an ErrorMappingException with message "554 Too many mappings to process"
 
   Scenario: recursive mapping should work when two levels
     Given recursive mapping is enable
diff --git a/server/data/data-memory/src/test/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableImplTest.java b/server/data/data-memory/src/test/java/org/apache/james/rrt/lib/AliasReverseResolverImplTest.java
similarity index 78%
rename from server/data/data-memory/src/test/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableImplTest.java
rename to server/data/data-memory/src/test/java/org/apache/james/rrt/lib/AliasReverseResolverImplTest.java
index fe0d17b..7230ee5 100644
--- a/server/data/data-memory/src/test/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableImplTest.java
+++ b/server/data/data-memory/src/test/java/org/apache/james/rrt/lib/AliasReverseResolverImplTest.java
@@ -19,6 +19,8 @@
 
 package org.apache.james.rrt.lib;
 
+import static org.apache.james.rrt.api.RecipientRewriteTableConfiguration.DEFAULT_ENABLED_MAPPING_LIMIT;
+import static org.apache.james.rrt.api.RecipientRewriteTableConfiguration.RECURSIVE_MAPPING_ENABLE;
 import static org.mockito.Mockito.mock;
 
 import org.apache.james.core.Domain;
@@ -26,14 +28,15 @@ import org.apache.james.core.Username;
 import org.apache.james.dnsservice.api.DNSService;
 import org.apache.james.domainlist.lib.DomainListConfiguration;
 import org.apache.james.domainlist.memory.MemoryDomainList;
-import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
+import org.apache.james.rrt.api.AliasReverseResolver;
+import org.apache.james.rrt.api.RecipientRewriteTableConfiguration;
 import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
 import org.junit.jupiter.api.BeforeEach;
 
-public class ReverseRecipientRewriteTableImplTest implements ReverseRecipientRewriteTableContract {
+public class AliasReverseResolverImplTest implements AliasReverseResolverContract {
 
     AbstractRecipientRewriteTable recipientRewriteTable;
-    ReverseRecipientRewriteTableImpl reverseRecipientRewriteTable;
+    AliasReverseResolverImpl aliasReverseResolver;
 
     @BeforeEach
     void setup() throws Exception {
@@ -47,13 +50,14 @@ public class ReverseRecipientRewriteTableImplTest implements ReverseRecipientRew
         domainList.addDomain(DOMAIN);
         domainList.addDomain(OTHER_DOMAIN);
         recipientRewriteTable.setDomainList(domainList);
+        recipientRewriteTable.setConfiguration(new RecipientRewriteTableConfiguration(RECURSIVE_MAPPING_ENABLE, DEFAULT_ENABLED_MAPPING_LIMIT));
 
-        this.reverseRecipientRewriteTable = new ReverseRecipientRewriteTableImpl(recipientRewriteTable);
+        this.aliasReverseResolver = new AliasReverseResolverImpl(recipientRewriteTable);
     }
 
     @Override
-    public ReverseRecipientRewriteTable reverseRecipientRewriteTable() {
-        return reverseRecipientRewriteTable;
+    public AliasReverseResolver aliasReverseResolver() {
+        return aliasReverseResolver;
     }
 
     @Override
diff --git a/server/data/data-memory/src/test/java/org/apache/james/rrt/lib/CanSendFromImplTest.java b/server/data/data-memory/src/test/java/org/apache/james/rrt/lib/CanSendFromImplTest.java
index 47863a1..99265c1 100644
--- a/server/data/data-memory/src/test/java/org/apache/james/rrt/lib/CanSendFromImplTest.java
+++ b/server/data/data-memory/src/test/java/org/apache/james/rrt/lib/CanSendFromImplTest.java
@@ -18,6 +18,8 @@
  ****************************************************************/
 package org.apache.james.rrt.lib;
 
+import static org.apache.james.rrt.api.RecipientRewriteTableConfiguration.DEFAULT_ENABLED_MAPPING_LIMIT;
+import static org.apache.james.rrt.api.RecipientRewriteTableConfiguration.RECURSIVE_MAPPING_ENABLE;
 import static org.mockito.Mockito.mock;
 
 import org.apache.james.core.Domain;
@@ -26,7 +28,8 @@ import org.apache.james.dnsservice.api.DNSService;
 import org.apache.james.domainlist.lib.DomainListConfiguration;
 import org.apache.james.domainlist.memory.MemoryDomainList;
 import org.apache.james.rrt.api.CanSendFrom;
-import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
+import org.apache.james.rrt.api.AliasReverseResolver;
+import org.apache.james.rrt.api.RecipientRewriteTableConfiguration;
 import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
 import org.junit.jupiter.api.BeforeEach;
 
@@ -47,9 +50,10 @@ public class CanSendFromImplTest implements CanSendFromContract {
         domainList.addDomain(DOMAIN);
         domainList.addDomain(OTHER_DOMAIN);
         recipientRewriteTable.setDomainList(domainList);
+        recipientRewriteTable.setConfiguration(new RecipientRewriteTableConfiguration(RECURSIVE_MAPPING_ENABLE, DEFAULT_ENABLED_MAPPING_LIMIT));
 
-        ReverseRecipientRewriteTable reverseRecipientRewriteTable = new ReverseRecipientRewriteTableImpl(recipientRewriteTable);
-        canSendFrom = new CanSendFromImpl(recipientRewriteTable, reverseRecipientRewriteTable);
+        AliasReverseResolver aliasReverseResolver = new AliasReverseResolverImpl(recipientRewriteTable);
+        canSendFrom = new CanSendFromImpl(recipientRewriteTable, aliasReverseResolver);
     }
 
     @Override
diff --git a/server/data/data-memory/src/test/java/org/apache/james/rrt/memory/InMemoryStepdefs.java b/server/data/data-memory/src/test/java/org/apache/james/rrt/memory/InMemoryStepdefs.java
index c3176a8..a2c36da 100644
--- a/server/data/data-memory/src/test/java/org/apache/james/rrt/memory/InMemoryStepdefs.java
+++ b/server/data/data-memory/src/test/java/org/apache/james/rrt/memory/InMemoryStepdefs.java
@@ -19,11 +19,11 @@
 
 package org.apache.james.rrt.memory;
 
-import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
 import org.apache.james.rrt.lib.AbstractRecipientRewriteTable;
 import org.apache.james.rrt.lib.RecipientRewriteTableFixture;
 import org.apache.james.rrt.lib.RewriteTablesStepdefs;
 
+import com.github.fge.lambdas.Throwing;
 import cucumber.api.java.Before;
 
 public class InMemoryStepdefs {
@@ -36,12 +36,11 @@ public class InMemoryStepdefs {
 
     @Before
     public void setup() throws Throwable {
-        mainStepdefs.rewriteTable = getRecipientRewriteTable(); 
+        mainStepdefs.setUp(Throwing.supplier(this::getRecipientRewriteTable).sneakyThrow());
     }
 
     private AbstractRecipientRewriteTable getRecipientRewriteTable() throws Exception {
         MemoryRecipientRewriteTable rrt = new MemoryRecipientRewriteTable();
-        rrt.configure(new BaseHierarchicalConfiguration());
         rrt.setDomainList(RecipientRewriteTableFixture.domainListForCucumberTests());
         return rrt;
     }
diff --git a/server/data/data-memory/src/test/java/org/apache/james/rrt/memory/MemoryRecipientRewriteTableTest.java b/server/data/data-memory/src/test/java/org/apache/james/rrt/memory/MemoryRecipientRewriteTableTest.java
index da7853f..7725797 100644
--- a/server/data/data-memory/src/test/java/org/apache/james/rrt/memory/MemoryRecipientRewriteTableTest.java
+++ b/server/data/data-memory/src/test/java/org/apache/james/rrt/memory/MemoryRecipientRewriteTableTest.java
@@ -19,7 +19,6 @@
 
 package org.apache.james.rrt.memory;
 
-import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
 import org.apache.james.rrt.lib.AbstractRecipientRewriteTable;
 import org.apache.james.rrt.lib.AbstractRecipientRewriteTableTest;
 import org.junit.After;
@@ -40,9 +39,7 @@ public class MemoryRecipientRewriteTableTest extends AbstractRecipientRewriteTab
     }
 
     @Override
-    protected AbstractRecipientRewriteTable getRecipientRewriteTable() throws Exception {
-        AbstractRecipientRewriteTable rrt = new MemoryRecipientRewriteTable();
-        rrt.configure(new BaseHierarchicalConfiguration());
-        return rrt;
+    protected AbstractRecipientRewriteTable getRecipientRewriteTable() {
+        return new MemoryRecipientRewriteTable();
     }
 }
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessorTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessorTest.java
index 6740ff1..a4a25fc 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessorTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessorTest.java
@@ -74,11 +74,11 @@ import org.apache.james.mailbox.model.TestMessageId;
 import org.apache.james.metrics.tests.RecordingMetricFactory;
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.rrt.api.RecipientRewriteTableException;
-import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
+import org.apache.james.rrt.api.AliasReverseResolver;
+import org.apache.james.rrt.lib.AliasReverseResolverImpl;
 import org.apache.james.rrt.lib.Mapping;
 import org.apache.james.rrt.lib.MappingSource;
 import org.apache.james.rrt.lib.CanSendFromImpl;
-import org.apache.james.rrt.lib.ReverseRecipientRewriteTableImpl;
 import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
 import org.apache.james.util.OptionalUtils;
 import org.apache.james.util.html.HtmlTextExtractor;
@@ -156,8 +156,8 @@ public class SetMessagesCreationProcessorTest {
         domainList.addDomain(Domain.of("example.com"));
         domainList.addDomain(Domain.of("other.org"));
         recipientRewriteTable.setDomainList(domainList);
-        ReverseRecipientRewriteTable reverseRecipientRewriteTable = new ReverseRecipientRewriteTableImpl(recipientRewriteTable);
-        canSendFrom = new CanSendFromImpl(recipientRewriteTable, reverseRecipientRewriteTable);
+        AliasReverseResolver aliasReverseResolver = new AliasReverseResolverImpl(recipientRewriteTable);
+        canSendFrom = new CanSendFromImpl(recipientRewriteTable, aliasReverseResolver);
         messageFullViewFactory = new MessageFullViewFactory(blobManager, messageContentExtractor, htmlTextExtractor,
             messageIdManager,
             new MemoryMessageFastViewProjection(new RecordingMetricFactory()));
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java
index 4b8f472..e3c9bf8 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java
@@ -67,10 +67,10 @@ import org.apache.james.mailbox.model.TestMessageId;
 import org.apache.james.metrics.tests.RecordingMetricFactory;
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.rrt.api.RecipientRewriteTableException;
-import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
+import org.apache.james.rrt.api.AliasReverseResolver;
+import org.apache.james.rrt.lib.AliasReverseResolverImpl;
 import org.apache.james.rrt.lib.CanSendFromImpl;
 import org.apache.james.rrt.lib.MappingSource;
-import org.apache.james.rrt.lib.ReverseRecipientRewriteTableImpl;
 import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
 import org.apache.james.util.OptionalUtils;
 import org.apache.james.util.html.HtmlTextExtractor;
@@ -169,8 +169,8 @@ public class SetMessagesUpdateProcessorTest {
         domainList.addDomain(Domain.of("example.com"));
         domainList.addDomain(Domain.of("other.org"));
         recipientRewriteTable.setDomainList(domainList);
-        ReverseRecipientRewriteTable reverseRecipientRewriteTable = new ReverseRecipientRewriteTableImpl(recipientRewriteTable);
-        canSendFrom = new CanSendFromImpl(recipientRewriteTable, reverseRecipientRewriteTable);
+        AliasReverseResolver aliasReverseResolver = new AliasReverseResolverImpl(recipientRewriteTable);
+        canSendFrom = new CanSendFromImpl(recipientRewriteTable, aliasReverseResolver);
         mockedMailSpool = mock(MailSpool.class);
         mockedMailboxManager = mock(MailboxManager.class);
         mockedMailboxIdFactory = mock(MailboxId.Factory.class);
diff --git a/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPServerTest.java b/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPServerTest.java
index c471f72..fbfb0a0 100644
--- a/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPServerTest.java
+++ b/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPServerTest.java
@@ -70,12 +70,12 @@ import org.apache.james.protocols.netty.AbstractChannelPipelineFactory;
 import org.apache.james.queue.api.MailQueueFactory;
 import org.apache.james.queue.api.RawMailQueueItemDecoratorFactory;
 import org.apache.james.queue.memory.MemoryMailQueueFactory;
+import org.apache.james.rrt.api.AliasReverseResolver;
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.rrt.api.RecipientRewriteTable;
-import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
+import org.apache.james.rrt.lib.AliasReverseResolverImpl;
 import org.apache.james.rrt.lib.CanSendFromImpl;
 import org.apache.james.rrt.lib.MappingSource;
-import org.apache.james.rrt.lib.ReverseRecipientRewriteTableImpl;
 import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
 import org.apache.james.server.core.configuration.Configuration;
 import org.apache.james.server.core.filesystem.FileSystemImpl;
@@ -200,7 +200,7 @@ public class SMTPServerTest {
     protected MemoryMailQueueFactory queueFactory;
     protected MemoryMailQueueFactory.MemoryMailQueue queue;
     protected MemoryRecipientRewriteTable rewriteTable;
-    private ReverseRecipientRewriteTable reverseRewriteTable;
+    private AliasReverseResolver aliasReverseResolver;
     protected CanSendFrom canSendFrom;
 
     private SMTPServer smtpServer;
@@ -279,8 +279,8 @@ public class SMTPServerTest {
         dnsServer = new AlterableDNSServer();
 
         rewriteTable = new MemoryRecipientRewriteTable();
-        reverseRewriteTable = new ReverseRecipientRewriteTableImpl(rewriteTable);
-        canSendFrom = new CanSendFromImpl(rewriteTable, reverseRewriteTable);
+        aliasReverseResolver = new AliasReverseResolverImpl(rewriteTable);
+        canSendFrom = new CanSendFromImpl(rewriteTable, aliasReverseResolver);
         queueFactory = new MemoryMailQueueFactory(new RawMailQueueItemDecoratorFactory());
         queue = queueFactory.createQueue(MailQueueFactory.SPOOL);
 
diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/UserRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/UserRoutesTest.java
index d45f79e..d876ce2 100644
--- a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/UserRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/UserRoutesTest.java
@@ -40,13 +40,13 @@ import org.apache.james.core.Domain;
 import org.apache.james.core.Username;
 import org.apache.james.domainlist.api.DomainListException;
 import org.apache.james.domainlist.api.mock.SimpleDomainList;
+import org.apache.james.rrt.api.AliasReverseResolver;
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.rrt.api.RecipientRewriteTable;
 import org.apache.james.rrt.api.RecipientRewriteTableException;
-import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.rrt.lib.CanSendFromImpl;
 import org.apache.james.rrt.lib.MappingSource;
-import org.apache.james.rrt.lib.ReverseRecipientRewriteTableImpl;
+import org.apache.james.rrt.lib.AliasReverseResolverImpl;
 import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
 import org.apache.james.user.api.UsersRepository;
 import org.apache.james.user.api.UsersRepositoryException;
@@ -100,7 +100,7 @@ class UserRoutesTest {
         final MemoryUsersRepository usersRepository;
         final SimpleDomainList domainList;
         final MemoryRecipientRewriteTable recipientRewriteTable;
-        final ReverseRecipientRewriteTable reverseRecipientRewriteTable;
+        final AliasReverseResolver aliasReverseResolver;
         final CanSendFrom canSendFrom;
 
         WebAdminServer webAdminServer;
@@ -110,8 +110,8 @@ class UserRoutesTest {
             this.domainList = domainList;
             this.recipientRewriteTable = new MemoryRecipientRewriteTable();
             this.recipientRewriteTable.setDomainList(domainList);
-            this.reverseRecipientRewriteTable = new ReverseRecipientRewriteTableImpl(recipientRewriteTable);
-            this.canSendFrom = new CanSendFromImpl(recipientRewriteTable, reverseRecipientRewriteTable);
+            this.aliasReverseResolver = new AliasReverseResolverImpl(recipientRewriteTable);
+            this.canSendFrom = new CanSendFromImpl(recipientRewriteTable, aliasReverseResolver);
         }
 
         @Override


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


[james-project] 20/21: JAMES-3067 Recursively find allowed From headers

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

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

commit d31b8bfc0ec68b19b58acd14eb0e9690b8ef80a7
Author: Gautier DI FOLCO <gd...@linagora.com>
AuthorDate: Tue Feb 18 11:58:02 2020 +0100

    JAMES-3067 Recursively find allowed From headers
---
 .../data/CassandraRecipientRewriteTableModule.java |   4 +
 .../data/JPARecipientRewriteTableModule.java       |   4 +
 .../james/modules/data/MemoryDataModule.java       |   5 +
 .../META-INF/org/apache/james/spring-server.xml    |   1 +
 .../java/org/apache/james/util/StreamUtils.java    |  68 +++++++++
 .../org/apache/james/util/StreamUtilsTest.java     |  70 +++++++++
 .../james/rrt/api/RecipientRewriteTable.java       |   2 +
 .../rrt/api/ReverseRecipientRewriteTable.java}     |  42 +-----
 .../apache/james/rrt/lib/CanSendFromContract.java  | 147 +++++++++++++++---
 .../lib/ReverseRecipientRewriteTableContract.java  | 167 +++++++++++++++++++++
 .../rrt/lib/AbstractRecipientRewriteTable.java     |   9 ++
 .../org/apache/james/rrt/lib/CanSendFromImpl.java  |  48 ++----
 .../rrt/lib/ReverseRecipientRewriteTableImpl.java  | 105 +++++++++++++
 .../apache/james/rrt/lib/CanSendFromImplTest.java  |   4 +-
 ...a => ReverseRecipientRewriteTableImplTest.java} |  17 ++-
 .../methods/SetMessagesCreationProcessorTest.java  |   5 +-
 .../methods/SetMessagesUpdateProcessorTest.java    |   5 +-
 .../apache/james/smtpserver/SMTPServerTest.java    |   6 +-
 .../james/webadmin/routes/UserRoutesTest.java      |   6 +-
 19 files changed, 609 insertions(+), 106 deletions(-)

diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/data/CassandraRecipientRewriteTableModule.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/data/CassandraRecipientRewriteTableModule.java
index 5a1836f..4eaccfc 100644
--- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/data/CassandraRecipientRewriteTableModule.java
+++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/data/CassandraRecipientRewriteTableModule.java
@@ -21,11 +21,13 @@ package org.apache.james.modules.data;
 import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.rrt.cassandra.CassandraMappingsSourcesDAO;
 import org.apache.james.rrt.cassandra.CassandraRRTModule;
 import org.apache.james.rrt.cassandra.CassandraRecipientRewriteTable;
 import org.apache.james.rrt.cassandra.CassandraRecipientRewriteTableDAO;
 import org.apache.james.rrt.lib.CanSendFromImpl;
+import org.apache.james.rrt.lib.ReverseRecipientRewriteTableImpl;
 import org.apache.james.server.core.configuration.ConfigurationProvider;
 import org.apache.james.utils.InitializationOperation;
 import org.apache.james.utils.InitilizationOperationBuilder;
@@ -42,6 +44,8 @@ public class CassandraRecipientRewriteTableModule extends AbstractModule {
         bind(CassandraRecipientRewriteTableDAO.class).in(Scopes.SINGLETON);
         bind(CassandraMappingsSourcesDAO.class).in(Scopes.SINGLETON);
         bind(RecipientRewriteTable.class).to(CassandraRecipientRewriteTable.class);
+        bind(ReverseRecipientRewriteTableImpl.class).in(Scopes.SINGLETON);
+        bind(ReverseRecipientRewriteTable.class).to(ReverseRecipientRewriteTableImpl.class);
         bind(CanSendFromImpl.class).in(Scopes.SINGLETON);
         bind(CanSendFrom.class).to(CanSendFromImpl.class);
         Multibinder<CassandraModule> cassandraDataDefinitions = Multibinder.newSetBinder(binder(), CassandraModule.class);
diff --git a/server/container/guice/jpa-common-guice/src/main/java/org/apache/james/modules/data/JPARecipientRewriteTableModule.java b/server/container/guice/jpa-common-guice/src/main/java/org/apache/james/modules/data/JPARecipientRewriteTableModule.java
index f22eb63..a608538 100644
--- a/server/container/guice/jpa-common-guice/src/main/java/org/apache/james/modules/data/JPARecipientRewriteTableModule.java
+++ b/server/container/guice/jpa-common-guice/src/main/java/org/apache/james/modules/data/JPARecipientRewriteTableModule.java
@@ -20,8 +20,10 @@ package org.apache.james.modules.data;
 
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.rrt.jpa.JPARecipientRewriteTable;
 import org.apache.james.rrt.lib.CanSendFromImpl;
+import org.apache.james.rrt.lib.ReverseRecipientRewriteTableImpl;
 import org.apache.james.server.core.configuration.ConfigurationProvider;
 import org.apache.james.utils.InitializationOperation;
 import org.apache.james.utils.InitilizationOperationBuilder;
@@ -35,6 +37,8 @@ public class JPARecipientRewriteTableModule extends AbstractModule {
     public void configure() {
         bind(JPARecipientRewriteTable.class).in(Scopes.SINGLETON);
         bind(RecipientRewriteTable.class).to(JPARecipientRewriteTable.class);
+        bind(ReverseRecipientRewriteTableImpl.class).in(Scopes.SINGLETON);
+        bind(ReverseRecipientRewriteTable.class).to(ReverseRecipientRewriteTableImpl.class);
         bind(CanSendFromImpl.class).in(Scopes.SINGLETON);
         bind(CanSendFrom.class).to(CanSendFromImpl.class);
     }
diff --git a/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/data/MemoryDataModule.java b/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/data/MemoryDataModule.java
index d78baca..d33c1ea 100644
--- a/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/data/MemoryDataModule.java
+++ b/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/data/MemoryDataModule.java
@@ -34,7 +34,9 @@ import org.apache.james.mailrepository.memory.MemoryMailRepositoryUrlStore;
 import org.apache.james.modules.server.MailStoreRepositoryModule;
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.rrt.lib.CanSendFromImpl;
+import org.apache.james.rrt.lib.ReverseRecipientRewriteTableImpl;
 import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
 import org.apache.james.server.core.configuration.ConfigurationProvider;
 import org.apache.james.user.api.UsersRepository;
@@ -68,6 +70,9 @@ public class MemoryDataModule extends AbstractModule {
         bind(MemoryRecipientRewriteTable.class).in(Scopes.SINGLETON);
         bind(RecipientRewriteTable.class).to(MemoryRecipientRewriteTable.class);
 
+        bind(ReverseRecipientRewriteTableImpl.class).in(Scopes.SINGLETON);
+        bind(ReverseRecipientRewriteTable.class).to(ReverseRecipientRewriteTableImpl.class);
+
         bind(CanSendFromImpl.class).in(Scopes.SINGLETON);
         bind(CanSendFrom.class).to(CanSendFromImpl.class);
 
diff --git a/server/container/spring/src/main/resources/META-INF/org/apache/james/spring-server.xml b/server/container/spring/src/main/resources/META-INF/org/apache/james/spring-server.xml
index 46b1f29..8a37ab5 100644
--- a/server/container/spring/src/main/resources/META-INF/org/apache/james/spring-server.xml
+++ b/server/container/spring/src/main/resources/META-INF/org/apache/james/spring-server.xml
@@ -326,5 +326,6 @@
 
     <bean id="jspfLogger" class="org.apache.james.smtpserver.fastfail.SPFHandler.SPFLogger"/>
 
+    <bean id="reverserecipientrewritetable" class="org.apache.james.rrt.lib.ReverseRecipientRewriteTableImpl" />
     <bean id="cansendfrom" class="org.apache.james.rrt.lib.CanSendFromImpl" />
 </beans>
diff --git a/server/container/util/src/main/java/org/apache/james/util/StreamUtils.java b/server/container/util/src/main/java/org/apache/james/util/StreamUtils.java
index ccdffd2..aea59a1 100644
--- a/server/container/util/src/main/java/org/apache/james/util/StreamUtils.java
+++ b/server/container/util/src/main/java/org/apache/james/util/StreamUtils.java
@@ -21,12 +21,21 @@ package org.apache.james.util;
 
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 import java.util.Optional;
+import java.util.Spliterator;
+import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import com.github.steveash.guavate.Guavate;
+import com.google.common.base.Preconditions;
 
 public class StreamUtils {
 
+    private static final boolean PARALLEL = true;
+
     @SafeVarargs
     public static <T> Stream<T> ofNullables(T... array) {
         return ofNullable(array);
@@ -54,4 +63,63 @@ public class StreamUtils {
     public static <T> Stream<T> flatten(Stream<T>... streams) {
         return flatten(Arrays.stream(streams));
     }
+
+    public static <T> Stream<T> unfold(T seed, Function<T, Optional<T>> generator) {
+        return StreamSupport.stream(new UnfoldSpliterator(seed, generator), !PARALLEL);
+    }
+
+    public static <T> Stream<T> iterate(T seed, Long limit, Function<T, Stream<T>> generator) {
+        Preconditions.checkArgument(limit >= 0, "StreamUtils.iterate have a given limit ok '{}', while it should not be negative", limit);
+        return StreamUtils.unfold(Arrays.asList(seed), conservativeGenerator(generator))
+            .limit(limit + 1)
+            .flatMap(List::stream);
+    }
+
+    private static <T> Function<List<T>, Optional<List<T>>> conservativeGenerator(Function<T, Stream<T>> generator) {
+        return previous -> {
+            List<T> generated = previous.stream()
+                .flatMap(generator)
+                .collect(Guavate.toImmutableList());
+
+            if (generated.isEmpty()) {
+                return Optional.empty();
+            } else {
+                return Optional.of(generated);
+            }
+        };
+    }
+
+    private static class UnfoldSpliterator<T> implements Spliterator<T> {
+
+        private static final Spliterator<?> NOT_ABLE_TO_SPLIT_SPLITERATOR = null;
+        private Optional<T> current;
+        private final Function<T, Optional<T>> generator;
+
+        private UnfoldSpliterator(T seed, Function<T, Optional<T>> generator) {
+            this.current = Optional.of(seed);
+            this.generator = generator;
+        }
+
+        @Override
+        public boolean tryAdvance(Consumer<? super T> action) {
+            current.ifPresent(action);
+            current = current.flatMap(generator);
+            return current.isPresent();
+        }
+
+        @Override
+        public Spliterator<T> trySplit() {
+            return (Spliterator<T>) NOT_ABLE_TO_SPLIT_SPLITERATOR;
+        }
+
+        @Override
+        public long estimateSize() {
+            return Long.MAX_VALUE;
+        }
+
+        @Override
+        public int characteristics() {
+            return Spliterator.IMMUTABLE & Spliterator.NONNULL & Spliterator.ORDERED;
+        }
+    }
 }
diff --git a/server/container/util/src/test/java/org/apache/james/util/StreamUtilsTest.java b/server/container/util/src/test/java/org/apache/james/util/StreamUtilsTest.java
index e08ebea..67f8527 100644
--- a/server/container/util/src/test/java/org/apache/james/util/StreamUtilsTest.java
+++ b/server/container/util/src/test/java/org/apache/james/util/StreamUtilsTest.java
@@ -20,8 +20,11 @@
 package org.apache.james.util;
 
 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.Optional;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Stream;
 
 import org.junit.jupiter.api.Test;
@@ -112,4 +115,71 @@ class StreamUtilsTest {
             .collect(Guavate.toImmutableList()))
             .containsExactly(1, 2);
     }
+
+    @Test
+    void unfoldShouldGenerateAnFiniteStream() {
+        Stream<Integer> unfolded = StreamUtils.unfold(1, i -> {
+            if (i < 10) {
+                return Optional.of(i + 1);
+            } else {
+                return Optional.empty();
+            }
+        });
+
+        assertThat(unfolded.collect(Guavate.toImmutableList()))
+            .contains(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+    }
+
+    @Test
+    void unfoldShouldGenerateALazyInfiniteStream() {
+        AtomicInteger counter = new AtomicInteger(0);
+        Stream<Integer> unfolded = StreamUtils.unfold(1, i -> {
+            counter.incrementAndGet();
+            return Optional.of(i + 1);
+        });
+
+        assertThat(unfolded.limit(10).collect(Guavate.toImmutableList()))
+            .contains(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+
+        assertThat(counter.get())
+            .isEqualTo(10);
+    }
+
+    @Test
+    void unfoldShouldHaveAtLeastTheSeed() {
+        Stream<Integer> unfolded = StreamUtils.unfold(1, i -> Optional.empty());
+
+        assertThat(unfolded.collect(Guavate.toImmutableList()))
+            .contains(1);
+    }
+
+    @Test
+    void iterateWithANegativeLimitShouldThrow() {
+        assertThatCode(() -> StreamUtils.iterate(1, (long) -1, Stream::of))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    void iterateWithZeroLimitShouldHaveOnlyTheSeed() {
+        Stream<Integer> generated = StreamUtils.iterate(1, (long) 0, Stream::of);
+
+        assertThat(generated.collect(Guavate.toImmutableList()))
+            .containsOnly(1);
+    }
+
+    @Test
+    void iterateWithEmptyGeneratorShouldHaveOnlyTheSeed() {
+        Stream<Integer> generated = StreamUtils.iterate(1, (long) 10, i -> Stream.of());
+
+        assertThat(generated.collect(Guavate.toImmutableList()))
+            .containsOnly(1);
+    }
+
+    @Test
+    void iterateWithGeneratorShouldHaveOnlyTheLimitedElements() {
+        Stream<Integer> generated = StreamUtils.iterate(1, (long) 5, i -> Stream.of(i + 1));
+
+        assertThat(generated.collect(Guavate.toImmutableList()))
+            .containsOnly(1, 2, 3, 4, 5, 6);
+    }
 }
diff --git a/server/data/data-api/src/main/java/org/apache/james/rrt/api/RecipientRewriteTable.java b/server/data/data-api/src/main/java/org/apache/james/rrt/api/RecipientRewriteTable.java
index e3ea519..ece8f55 100644
--- a/server/data/data-api/src/main/java/org/apache/james/rrt/api/RecipientRewriteTable.java
+++ b/server/data/data-api/src/main/java/org/apache/james/rrt/api/RecipientRewriteTable.java
@@ -122,6 +122,8 @@ public interface RecipientRewriteTable {
      */
     Map<MappingSource, Mappings> getAllMappings() throws RecipientRewriteTableException;
 
+    int getMappingLimit();
+
     default Stream<MappingSource> listSources(Mapping mapping) throws RecipientRewriteTableException {
         Preconditions.checkArgument(listSourcesSupportedType.contains(mapping.getType()),
             "Not supported mapping of type %s", mapping.getType());
diff --git a/server/container/util/src/main/java/org/apache/james/util/StreamUtils.java b/server/data/data-api/src/main/java/org/apache/james/rrt/api/ReverseRecipientRewriteTable.java
similarity index 53%
copy from server/container/util/src/main/java/org/apache/james/util/StreamUtils.java
copy to server/data/data-api/src/main/java/org/apache/james/rrt/api/ReverseRecipientRewriteTable.java
index ccdffd2..3f17aff 100644
--- a/server/container/util/src/main/java/org/apache/james/util/StreamUtils.java
+++ b/server/data/data-api/src/main/java/org/apache/james/rrt/api/ReverseRecipientRewriteTable.java
@@ -7,7 +7,7 @@
  * "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                 *
+ * 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  *
@@ -15,43 +15,15 @@
  * KIND, either express or implied.  See the License for the    *
  * specific language governing permissions and limitations      *
  * under the License.                                           *
- ****************************************************************/
+ ***************************************************************/
 
-package org.apache.james.util;
+package org.apache.james.rrt.api;
 
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Optional;
-import java.util.function.Function;
 import java.util.stream.Stream;
 
-public class StreamUtils {
+import org.apache.james.core.MailAddress;
+import org.apache.james.core.Username;
 
-    @SafeVarargs
-    public static <T> Stream<T> ofNullables(T... array) {
-        return ofNullable(array);
-    }
-
-    public static <T> Stream<T> ofNullable(T[] array) {
-        return ofOptional(Optional.ofNullable(array));
-    }
-
-    public static <T> Stream<T> ofOptional(Optional<T[]> array) {
-        return array
-            .map(Arrays::stream)
-            .orElse(Stream.empty());
-    }
-
-    public static <T> Stream<T> flatten(Collection<Stream<T>> streams) {
-        return flatten(streams.stream());
-    }
-
-    public static <T> Stream<T> flatten(Stream<Stream<T>> streams) {
-        return streams.flatMap(Function.identity());
-    }
-
-    @SafeVarargs
-    public static <T> Stream<T> flatten(Stream<T>... streams) {
-        return flatten(Arrays.stream(streams));
-    }
+public interface ReverseRecipientRewriteTable {
+    Stream<MailAddress> listAddresses(Username user) throws RecipientRewriteTable.ErrorMappingException, RecipientRewriteTableException;
 }
diff --git a/server/data/data-api/src/test/java/org/apache/james/rrt/lib/CanSendFromContract.java b/server/data/data-api/src/test/java/org/apache/james/rrt/lib/CanSendFromContract.java
index 13df95e..0a0201d 100644
--- a/server/data/data-api/src/test/java/org/apache/james/rrt/lib/CanSendFromContract.java
+++ b/server/data/data-api/src/test/java/org/apache/james/rrt/lib/CanSendFromContract.java
@@ -20,13 +20,17 @@ package org.apache.james.rrt.lib;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
+import java.util.Optional;
+import java.util.stream.IntStream;
+
 import org.apache.james.core.Domain;
 import org.apache.james.core.Username;
 import org.apache.james.rrt.api.CanSendFrom;
 
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 
+import com.github.fge.lambdas.Throwing;
+
 public interface CanSendFromContract {
 
     Domain DOMAIN = Domain.of("example.com");
@@ -43,6 +47,28 @@ public interface CanSendFromContract {
 
     void addGroupMapping(String group, Username user) throws Exception;
 
+    @FunctionalInterface
+    interface RequireUserName {
+        void to(Username user) throws Exception;
+    }
+
+    @FunctionalInterface
+    interface RequireDomain {
+        void to(Domain domain) throws Exception;
+    }
+
+    default RequireUserName redirectUser(Username alias) {
+        return user -> addAliasMapping(alias, user);
+    }
+
+    default RequireDomain redirectDomain(Domain alias) {
+        return domain -> addDomainMapping(alias, domain);
+    }
+
+    default RequireUserName redirectGroup(String group) {
+        return user -> addGroupMapping(group, user);
+    }
+
     @Test
     default void userCanSendFromShouldBeFalseWhenSenderIsNotTheUser() {
         assertThat(canSendFrom().userCanSendFrom(USER, OTHER_USER)).isFalse();
@@ -55,14 +81,14 @@ public interface CanSendFromContract {
 
     @Test
     default void userCanSendFromShouldBeFalseWhenSenderIsAnAliasOfAnotherUser() throws Exception {
-        addAliasMapping(USER_ALIAS, OTHER_USER);
+        redirectUser(USER_ALIAS).to(OTHER_USER);
 
         assertThat(canSendFrom().userCanSendFrom(USER, USER_ALIAS)).isFalse();
     }
 
     @Test
     default void userCanSendFromShouldBeTrueWhenSenderIsAnAliasOfTheUser() throws Exception {
-        addAliasMapping(USER_ALIAS, USER);
+        redirectUser(USER_ALIAS).to(USER);
 
         assertThat(canSendFrom().userCanSendFrom(USER, USER_ALIAS)).isTrue();
     }
@@ -70,17 +96,47 @@ public interface CanSendFromContract {
     @Test
     default void userCanSendFromShouldBeTrueWhenSenderIsAnAliasOfAnAliasOfTheUser() throws Exception {
         Username userAliasBis = Username.of("aliasbis@" + DOMAIN.asString());
-        addAliasMapping(userAliasBis, USER_ALIAS);
-        addAliasMapping(USER_ALIAS, USER);
+        redirectUser(userAliasBis).to(USER_ALIAS);
+        redirectUser(USER_ALIAS).to(USER);
+
+        assertThat(canSendFrom().userCanSendFrom(USER, userAliasBis)).isTrue();
+    }
+
+    @Test
+    default void userCanSendFromShouldBeFalseWhenSenderIsAnAliasOfAnAliasOfAnAliasOfTheUser() throws Exception {
+        Username userAliasBis = Username.of("aliasbis@" + DOMAIN.asString());
+        Username userAliasTer = Username.of("aliaster@" + DOMAIN.asString());
+        redirectUser(userAliasTer).to(userAliasBis);
+        redirectUser(userAliasBis).to(USER_ALIAS);
+        redirectUser(USER_ALIAS).to(USER);
+
+        assertThat(canSendFrom().userCanSendFrom(USER, userAliasTer)).isTrue();
+    }
+
+    @Test
+    default void userCanSendFromShouldBeTrueWhenSenderIsAnAliasOfAnAliasInAnotherDomainOfTheUser() throws Exception {
+        Username userAlias = Username.of("aliasbis@" + OTHER_DOMAIN.asString());
+        Username userAliasBis = Username.of("aliaster@" + OTHER_DOMAIN.asString());
+        redirectUser(userAliasBis).to(userAlias);
+        redirectUser(userAlias).to(USER);
+
+        assertThat(canSendFrom().userCanSendFrom(USER, userAliasBis)).isTrue();
+    }
+
+    @Test
+    default void userCanSendFromShouldBeTrueWhenSenderIsAnAliasInAnotherDomainOfAnAliasOfTheUser() throws Exception {
+        Username userAliasBis = Username.of("aliasbis@" + OTHER_DOMAIN.asString());
+        redirectUser(userAliasBis).to(USER_ALIAS);
+        redirectUser(USER_ALIAS).to(USER);
 
         assertThat(canSendFrom().userCanSendFrom(USER, userAliasBis)).isTrue();
     }
 
     @Test
     default void userCanSendFromShouldBeTrueWhenSenderIsAnAliasOfTheDomainUser() throws Exception {
-        Username fromUser = Username.of(USER.getLocalPart() + "@" + OTHER_DOMAIN.asString());
+        Username fromUser = USER.withOtherDomain(Optional.of(OTHER_DOMAIN));
 
-        addDomainMapping(OTHER_DOMAIN, DOMAIN);
+        redirectDomain(OTHER_DOMAIN).to(DOMAIN);
 
         assertThat(canSendFrom().userCanSendFrom(USER, fromUser)).isTrue();
     }
@@ -89,7 +145,7 @@ public interface CanSendFromContract {
     default void userCanSendFromShouldBeFalseWhenWhenSenderIsAnAliasOfTheUserFromAGroupAlias() throws Exception {
         Username fromGroup = Username.of("group@example.com");
 
-        addGroupMapping("group@example.com", USER);
+        redirectGroup("group@example.com").to(USER);
 
         assertThat(canSendFrom().userCanSendFrom(USER, fromGroup)).isFalse();
 
@@ -103,25 +159,24 @@ public interface CanSendFromContract {
 
     @Test
     default void allValidFromAddressesShouldContainOnlyUserAddressWhenUserHasNoAliasAndAnotherUserHasOne() throws Exception {
-        addAliasMapping(USER_ALIAS, OTHER_USER);
+        redirectUser(USER_ALIAS).to(OTHER_USER);
         assertThat(canSendFrom().allValidFromAddressesForUser(USER))
             .containsExactly(USER.asMailAddress());
     }
 
     @Test
     default void allValidFromAddressesShouldContainUserAddressAndAnAliasOfTheUser() throws Exception {
-        addAliasMapping(USER_ALIAS, USER);
+        redirectUser(USER_ALIAS).to(USER);
 
         assertThat(canSendFrom().allValidFromAddressesForUser(USER))
             .containsExactlyInAnyOrder(USER.asMailAddress(), USER_ALIAS.asMailAddress());
     }
 
     @Test
-    @Disabled("Recursive aliases are not supported yet")
     default void allValidFromAddressesFromShouldBeTrueWhenSenderIsAnAliasOfAnAliasOfTheUser() throws Exception {
         Username userAliasBis = Username.of("aliasbis@" + DOMAIN.asString());
-        addAliasMapping(userAliasBis, USER_ALIAS);
-        addAliasMapping(USER_ALIAS, USER);
+        redirectUser(userAliasBis).to(USER_ALIAS);
+        redirectUser(USER_ALIAS).to(USER);
 
         assertThat(canSendFrom().allValidFromAddressesForUser(USER))
             .containsExactlyInAnyOrder(USER.asMailAddress(), USER_ALIAS.asMailAddress(), userAliasBis.asMailAddress());
@@ -129,9 +184,9 @@ public interface CanSendFromContract {
 
     @Test
     default void allValidFromAddressesShouldContainUserAddressAndAnAliasOfTheDomainUser() throws Exception {
-        Username fromUser = Username.of(USER.getLocalPart() + "@" + OTHER_DOMAIN.asString());
+        Username fromUser = USER.withOtherDomain(Optional.of(OTHER_DOMAIN));
 
-        addDomainMapping(OTHER_DOMAIN, DOMAIN);
+        redirectDomain(OTHER_DOMAIN).to(DOMAIN);
 
         assertThat(canSendFrom().allValidFromAddressesForUser(USER))
             .containsExactlyInAnyOrder(USER.asMailAddress(), fromUser.asMailAddress());
@@ -139,14 +194,66 @@ public interface CanSendFromContract {
 
     @Test
     default void allValidFromAddressesShouldContainUserAddressAndAnAliasOfTheDomainUserFromAnotherDomain() throws Exception {
-        Username userAliasOtherDomain = Username.of(USER_ALIAS.getLocalPart() + "@" + OTHER_DOMAIN.asString());
+        Username userAliasOtherDomain = USER_ALIAS.withOtherDomain(Optional.of(OTHER_DOMAIN));
 
-        addDomainMapping(OTHER_DOMAIN, DOMAIN);
-        addAliasMapping(userAliasOtherDomain, USER);
+        redirectDomain(OTHER_DOMAIN).to(DOMAIN);
+        redirectUser(userAliasOtherDomain).to(USER);
 
-        Username userAliasMainDomain = Username.of(USER_ALIAS.getLocalPart() + "@" + DOMAIN.asString());
-        Username userOtherDomain = Username.of(USER.getLocalPart() + "@" + OTHER_DOMAIN.asString());
+        Username userAliasMainDomain = USER_ALIAS.withOtherDomain(Optional.of(DOMAIN));
+        Username userOtherDomain = USER.withOtherDomain(Optional.of(OTHER_DOMAIN));
         assertThat(canSendFrom().allValidFromAddressesForUser(USER))
             .containsExactlyInAnyOrder(USER.asMailAddress(), userAliasOtherDomain.asMailAddress(), userAliasMainDomain.asMailAddress(), userOtherDomain.asMailAddress());
     }
+
+    @Test
+    default void allValidFromAddressesShouldContainASendersAliasOfAnAliasOfTheUser() throws Exception {
+        Username userAliasBis = Username.of("aliasbis@" + DOMAIN.asString());
+        redirectUser(userAliasBis).to(USER_ALIAS);
+        redirectUser(USER_ALIAS).to(USER);
+
+        assertThat(canSendFrom().userCanSendFrom(USER, userAliasBis)).isTrue();
+    }
+
+    @Test
+    default void allValidFromAddressesShouldNotContainAliasesRequiringMoreThanTenRecursionSteps() throws Exception {
+        int recursionLevel = 10;
+        IntStream.range(0, recursionLevel)
+            .forEach(Throwing.intConsumer(aliasNumber -> {
+                Username userAliasFrom = Username.of("alias" + aliasNumber + "@" + DOMAIN.asString());
+                Username userAliasTo;
+                if (aliasNumber == 0) {
+                    userAliasTo = USER_ALIAS;
+                } else {
+                    userAliasTo = Username.of("alias" + (aliasNumber - 1) + "@" + DOMAIN.asString());
+                }
+                redirectUser(userAliasFrom).to(userAliasTo);
+            }).sneakyThrow());
+
+        Username userAliasExcluded = Username.of("alias" + (recursionLevel - 1) + "@" + DOMAIN.asString());
+        redirectUser(USER_ALIAS).to(USER);
+
+        assertThat(canSendFrom().allValidFromAddressesForUser(USER))
+            .doesNotContain(userAliasExcluded.asMailAddress());
+    }
+
+    @Test
+    default void allValidFromAddressesShouldContainASendersAliasOfAnAliasInAnotherDomainOfTheUser() throws Exception {
+        Username userAlias = Username.of("aliasbis@" + OTHER_DOMAIN.asString());
+        Username userAliasBis = Username.of("aliaster@" + OTHER_DOMAIN.asString());
+        redirectUser(userAliasBis).to(userAlias);
+        redirectUser(userAlias).to(USER);
+
+        assertThat(canSendFrom().allValidFromAddressesForUser(USER))
+            .contains(userAliasBis.asMailAddress());
+    }
+
+    @Test
+    default void allValidFromAddressesShouldContainAnUserAliasFollowingADomainAliasResolution() throws Exception {
+        Username userAliasBis = Username.of("aliasbis@" + OTHER_DOMAIN.asString());
+        redirectUser(userAliasBis).to(USER_ALIAS);
+        redirectUser(USER_ALIAS).to(USER);
+
+        assertThat(canSendFrom().allValidFromAddressesForUser(USER))
+            .contains(userAliasBis.asMailAddress());
+    }
 }
diff --git a/server/data/data-api/src/test/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableContract.java b/server/data/data-api/src/test/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableContract.java
new file mode 100644
index 0000000..edf4372
--- /dev/null
+++ b/server/data/data-api/src/test/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableContract.java
@@ -0,0 +1,167 @@
+/** *************************************************************
+ * 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.rrt.lib;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Optional;
+import java.util.stream.IntStream;
+
+import org.apache.james.core.Domain;
+import org.apache.james.core.Username;
+import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
+import org.junit.jupiter.api.Test;
+
+import com.github.fge.lambdas.Throwing;
+
+public interface ReverseRecipientRewriteTableContract {
+
+    Domain DOMAIN = Domain.of("example.com");
+    Domain OTHER_DOMAIN = Domain.of("other.org");
+    Username USER = Username.of("user@example.com");
+    Username USER_ALIAS = Username.of("alias@example.com");
+    Username OTHER_USER = Username.of("other@example.com");
+
+    ReverseRecipientRewriteTable reverseRecipientRewriteTable();
+
+    void addAliasMapping(Username alias, Username user) throws Exception;
+
+    void addDomainMapping(Domain alias, Domain domain) throws Exception;
+
+    void addGroupMapping(String group, Username user) throws Exception;
+
+    @FunctionalInterface
+    interface RequireUserName {
+        void to(Username user) throws Exception;
+    }
+
+    @FunctionalInterface
+    interface RequireDomain {
+        void to(Domain domain) throws Exception;
+    }
+
+    default CanSendFromContract.RequireUserName redirectUser(Username alias) {
+        return user -> addAliasMapping(alias, user);
+    }
+
+    default CanSendFromContract.RequireDomain redirectDomain(Domain alias) {
+        return domain -> addDomainMapping(alias, domain);
+    }
+
+    default CanSendFromContract.RequireUserName redirectGroup(String group) {
+        return user -> addGroupMapping(group, user);
+    }
+    @Test
+    default void listAddressesShouldContainOnlyUserAddressWhenUserHasNoAlias() throws Exception {
+        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+            .containsExactly(USER.asMailAddress());
+    }
+
+    @Test
+    default void listAddressesShouldContainOnlyUserAddressWhenUserHasNoAliasAndAnotherUserHasOne() throws Exception {
+        redirectUser(USER_ALIAS).to(OTHER_USER);
+        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+            .containsExactly(USER.asMailAddress());
+    }
+
+    @Test
+    default void listAddressesShouldContainUserAddressAndAnAliasOfTheUser() throws Exception {
+        redirectUser(USER_ALIAS).to(USER);
+
+        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+            .containsExactlyInAnyOrder(USER.asMailAddress(), USER_ALIAS.asMailAddress());
+    }
+
+    @Test
+    default void listAddressesFromShouldBeTrueWhenSenderIsAnAliasOfAnAliasOfTheUser() throws Exception {
+        Username userAliasBis = Username.of("aliasbis@" + DOMAIN.asString());
+        redirectUser(userAliasBis).to(USER_ALIAS);
+        redirectUser(USER_ALIAS).to(USER);
+
+        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+            .containsExactlyInAnyOrder(USER.asMailAddress(), USER_ALIAS.asMailAddress(), userAliasBis.asMailAddress());
+    }
+
+    @Test
+    default void listAddressesShouldContainUserAddressAndAnAliasOfTheDomainUser() throws Exception {
+        Username fromUser = USER.withOtherDomain(Optional.of(OTHER_DOMAIN));
+
+        redirectDomain(OTHER_DOMAIN).to(DOMAIN);
+
+        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+            .containsExactlyInAnyOrder(USER.asMailAddress(), fromUser.asMailAddress());
+    }
+
+    @Test
+    default void listAddressesShouldContainUserAddressAndAnAliasOfTheDomainUserFromAnotherDomain() throws Exception {
+        Username userAliasOtherDomain = USER_ALIAS.withOtherDomain(Optional.of(OTHER_DOMAIN));
+
+        redirectDomain(OTHER_DOMAIN).to(DOMAIN);
+        redirectUser(userAliasOtherDomain).to(USER);
+
+        Username userAliasMainDomain = USER_ALIAS.withOtherDomain(Optional.of(DOMAIN));
+        Username userOtherDomain = USER.withOtherDomain(Optional.of(OTHER_DOMAIN));
+        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+            .containsExactlyInAnyOrder(USER.asMailAddress(), userAliasOtherDomain.asMailAddress(), userAliasMainDomain.asMailAddress(), userOtherDomain.asMailAddress());
+    }
+
+    @Test
+    default void listAddressesShouldNotContainAliasesRequiringMoreThanTenRecursionSteps() throws Exception {
+        int recursionLevel = 10;
+        IntStream.range(0, recursionLevel)
+            .forEach(Throwing.intConsumer(aliasNumber -> {
+                Username userAliasFrom = Username.of("alias" + aliasNumber + "@" + DOMAIN.asString());
+                Username userAliasTo;
+                if (aliasNumber == 0) {
+                    userAliasTo = USER_ALIAS;
+                } else {
+                    userAliasTo = Username.of("alias" + (aliasNumber - 1) + "@" + DOMAIN.asString());
+                }
+                redirectUser(userAliasFrom).to(userAliasTo);
+            }).sneakyThrow());
+
+        Username userAliasExcluded = Username.of("alias" + (recursionLevel - 1) + "@" + DOMAIN.asString());
+        redirectUser(USER_ALIAS).to(USER);
+
+        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+            .doesNotContain(userAliasExcluded.asMailAddress());
+    }
+
+    @Test
+    default void listAddressesShouldContainASendersAliasOfAnAliasInAnotherDomainOfTheUser() throws Exception {
+        Username userAlias = Username.of("aliasbis@" + OTHER_DOMAIN.asString());
+        Username userAliasBis = Username.of("aliaster@" + OTHER_DOMAIN.asString());
+        redirectUser(userAliasBis).to(userAlias);
+        redirectUser(userAlias).to(USER);
+
+        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+            .contains(userAliasBis.asMailAddress());
+    }
+
+    @Test
+    default void listAddressesShouldContainAnUserAliasFollowingADomainAliasResolution() throws Exception {
+        Username userAliasBis = Username.of("aliasbis@" + OTHER_DOMAIN.asString());
+        redirectUser(userAliasBis).to(USER_ALIAS);
+        redirectUser(USER_ALIAS).to(USER);
+
+        assertThat(reverseRecipientRewriteTable().listAddresses(USER))
+            .contains(userAliasBis.asMailAddress());
+    }
+}
diff --git a/server/data/data-library/src/main/java/org/apache/james/rrt/lib/AbstractRecipientRewriteTable.java b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/AbstractRecipientRewriteTable.java
index 260d5f6..cafc427 100644
--- a/server/data/data-library/src/main/java/org/apache/james/rrt/lib/AbstractRecipientRewriteTable.java
+++ b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/AbstractRecipientRewriteTable.java
@@ -60,6 +60,15 @@ public abstract class AbstractRecipientRewriteTable implements RecipientRewriteT
 
     private DomainList domainList;
 
+    @Override
+    public int getMappingLimit() {
+        if (recursive) {
+            return mappingLimit;
+        } else {
+            return 0;
+        }
+    }
+
     @Inject
     public void setDomainList(DomainList domainList) {
         this.domainList = domainList;
diff --git a/server/data/data-library/src/main/java/org/apache/james/rrt/lib/CanSendFromImpl.java b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/CanSendFromImpl.java
index 930cc58..b2bedc8 100644
--- a/server/data/data-library/src/main/java/org/apache/james/rrt/lib/CanSendFromImpl.java
+++ b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/CanSendFromImpl.java
@@ -23,7 +23,6 @@ import static org.apache.james.rrt.lib.Mapping.Type.Domain;
 
 import java.util.EnumSet;
 import java.util.List;
-import java.util.Optional;
 import java.util.stream.Stream;
 
 import javax.inject.Inject;
@@ -34,19 +33,24 @@ import org.apache.james.core.Username;
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.rrt.api.RecipientRewriteTable;
 import org.apache.james.rrt.api.RecipientRewriteTableException;
+import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.util.OptionalUtils;
 
-import com.github.fge.lambdas.Throwing;
-import com.github.steveash.guavate.Guavate;
-
 public class CanSendFromImpl implements CanSendFrom {
 
+    @FunctionalInterface
+    interface DomainFetcher {
+        List<Domain> fetch(Username user);
+    }
+
     public static final EnumSet<Mapping.Type> ALIAS_TYPES_ACCEPTED_IN_FROM = EnumSet.of(Alias, Domain);
     private final RecipientRewriteTable recipientRewriteTable;
+    private final ReverseRecipientRewriteTable reverseRecipientRewriteTable;
 
     @Inject
-    public CanSendFromImpl(RecipientRewriteTable recipientRewriteTable) {
+    public CanSendFromImpl(RecipientRewriteTable recipientRewriteTable, ReverseRecipientRewriteTable reverseRecipientRewriteTable) {
         this.recipientRewriteTable = recipientRewriteTable;
+        this.reverseRecipientRewriteTable = reverseRecipientRewriteTable;
     }
 
     @Override
@@ -60,13 +64,7 @@ public class CanSendFromImpl implements CanSendFrom {
 
     @Override
     public Stream<MailAddress> allValidFromAddressesForUser(Username user) throws RecipientRewriteTable.ErrorMappingException, RecipientRewriteTableException {
-        List<Domain> domains = relatedDomains(user).collect(Guavate.toImmutableList());
-
-        return relatedAliases(user)
-            .flatMap(allowedUser -> domains.stream()
-                .map(Optional::of)
-                .map(allowedUser::withOtherDomain)
-                .map(Throwing.function(Username::asMailAddress).sneakyThrow()));
+        return reverseRecipientRewriteTable.listAddresses(user);
     }
 
     private boolean emailIsAnAliasOfTheConnectedUser(Username connectedUser, Username fromUser) throws RecipientRewriteTable.ErrorMappingException, RecipientRewriteTableException {
@@ -78,30 +76,4 @@ public class CanSendFromImpl implements CanSendFrom {
             .map(Username::fromMailAddress)
             .anyMatch(alias -> alias.equals(connectedUser));
     }
-
-    private Stream<Username> relatedAliases(Username user) throws RecipientRewriteTableException {
-        return Stream.concat(
-            Stream.of(user),
-            recipientRewriteTable
-                .listSources(Mapping.alias(user.asString()))
-                .map(MappingSource::asUsername)
-                .flatMap(OptionalUtils::toStream)
-        );
-    }
-
-    private Stream<Domain> relatedDomains(Username user) {
-        return user.getDomainPart()
-            .map(Throwing.function(this::fetchDomains).sneakyThrow())
-            .orElseGet(Stream::empty);
-    }
-
-    private Stream<Domain> fetchDomains(Domain domain) throws RecipientRewriteTableException {
-        return Stream.concat(
-          Stream.of(domain),
-          recipientRewriteTable
-              .listSources(Mapping.domain(domain))
-              .map(MappingSource::asDomain)
-              .flatMap(OptionalUtils::toStream)
-        );
-    }
 }
diff --git a/server/data/data-library/src/main/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableImpl.java b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableImpl.java
new file mode 100644
index 0000000..419a69b
--- /dev/null
+++ b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableImpl.java
@@ -0,0 +1,105 @@
+/****************************************************************
+ * 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.rrt.lib;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import javax.inject.Inject;
+
+import org.apache.james.core.Domain;
+import org.apache.james.core.MailAddress;
+import org.apache.james.core.Username;
+import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.RecipientRewriteTableException;
+import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
+import org.apache.james.util.OptionalUtils;
+import org.apache.james.util.StreamUtils;
+
+import com.github.fge.lambdas.Throwing;
+import com.github.steveash.guavate.Guavate;
+
+public class ReverseRecipientRewriteTableImpl implements ReverseRecipientRewriteTable {
+    private final RecipientRewriteTable recipientRewriteTable;
+
+    @Inject
+    public ReverseRecipientRewriteTableImpl(RecipientRewriteTable recipientRewriteTable) {
+        this.recipientRewriteTable = recipientRewriteTable;
+    }
+
+    @Override
+    public Stream<MailAddress> listAddresses(Username user) throws RecipientRewriteTable.ErrorMappingException, RecipientRewriteTableException {
+        CanSendFromImpl.DomainFetcher domains = domainFetcher(user);
+
+        return relatedAliases(user)
+            .flatMap(allowedUser -> domains.fetch(allowedUser)
+                .stream()
+                .map(Optional::of)
+                .map(allowedUser::withOtherDomain)
+                .map(Throwing.function(Username::asMailAddress).sneakyThrow()))
+            .distinct();
+    }
+
+    private Stream<Username> relatedAliases(Username user) {
+        return StreamUtils.iterate(
+            user,
+            (long) recipientRewriteTable.getMappingLimit(),
+            Throwing.<Username, Stream<Username>>function(targetUser ->
+                recipientRewriteTable
+                    .listSources(Mapping.alias(targetUser.asString()))
+                    .map(MappingSource::asUsername)
+                    .flatMap(OptionalUtils::toStream)).sneakyThrow()
+        );
+    }
+
+    private CanSendFromImpl.DomainFetcher domainFetcher(Username user) {
+        HashMap<Domain, List<Domain>> fetchedDomains = new HashMap<>();
+        List<Domain> userDomains = relatedDomains(user).collect(Guavate.toImmutableList());
+        user.getDomainPart().ifPresent(domain -> fetchedDomains.put(domain, userDomains));
+        Function<Domain, List<Domain>> computeDomain = givenDomain -> Stream.concat(userDomains.stream(), fetchDomains(givenDomain)).collect(Guavate.toImmutableList());
+        return givenUsername ->
+            givenUsername
+                .getDomainPart()
+                .map(domain -> fetchedDomains.computeIfAbsent(domain, computeDomain))
+                .orElseGet(Arrays::asList);
+    }
+
+    private Stream<Domain> relatedDomains(Username user) {
+        return user.getDomainPart()
+            .map(this::fetchDomains)
+            .orElseGet(Stream::empty);
+    }
+
+    private Stream<Domain> fetchDomains(Domain domain) {
+        return StreamUtils.iterate(
+            domain,
+            (long) recipientRewriteTable.getMappingLimit(),
+            Throwing.<Domain, Stream<Domain>>function(targetDomain ->
+                recipientRewriteTable
+                    .listSources(Mapping.domain(targetDomain))
+                    .map(MappingSource::asDomain)
+                    .flatMap(OptionalUtils::toStream)).sneakyThrow()
+        );
+    }
+}
diff --git a/server/data/data-memory/src/test/java/org/apache/james/rrt/lib/CanSendFromImplTest.java b/server/data/data-memory/src/test/java/org/apache/james/rrt/lib/CanSendFromImplTest.java
index a38467a..47863a1 100644
--- a/server/data/data-memory/src/test/java/org/apache/james/rrt/lib/CanSendFromImplTest.java
+++ b/server/data/data-memory/src/test/java/org/apache/james/rrt/lib/CanSendFromImplTest.java
@@ -26,6 +26,7 @@ import org.apache.james.dnsservice.api.DNSService;
 import org.apache.james.domainlist.lib.DomainListConfiguration;
 import org.apache.james.domainlist.memory.MemoryDomainList;
 import org.apache.james.rrt.api.CanSendFrom;
+import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
 import org.junit.jupiter.api.BeforeEach;
 
@@ -47,7 +48,8 @@ public class CanSendFromImplTest implements CanSendFromContract {
         domainList.addDomain(OTHER_DOMAIN);
         recipientRewriteTable.setDomainList(domainList);
 
-        canSendFrom = new CanSendFromImpl(recipientRewriteTable);
+        ReverseRecipientRewriteTable reverseRecipientRewriteTable = new ReverseRecipientRewriteTableImpl(recipientRewriteTable);
+        canSendFrom = new CanSendFromImpl(recipientRewriteTable, reverseRecipientRewriteTable);
     }
 
     @Override
diff --git a/server/data/data-memory/src/test/java/org/apache/james/rrt/lib/CanSendFromImplTest.java b/server/data/data-memory/src/test/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableImplTest.java
similarity index 84%
copy from server/data/data-memory/src/test/java/org/apache/james/rrt/lib/CanSendFromImplTest.java
copy to server/data/data-memory/src/test/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableImplTest.java
index a38467a..fe0d17b 100644
--- a/server/data/data-memory/src/test/java/org/apache/james/rrt/lib/CanSendFromImplTest.java
+++ b/server/data/data-memory/src/test/java/org/apache/james/rrt/lib/ReverseRecipientRewriteTableImplTest.java
@@ -7,7 +7,7 @@
  * "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                 *
+ * 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  *
@@ -15,7 +15,8 @@
  * KIND, either express or implied.  See the License for the    *
  * specific language governing permissions and limitations      *
  * under the License.                                           *
- ****************************************************************/
+ ***************************************************************/
+
 package org.apache.james.rrt.lib;
 
 import static org.mockito.Mockito.mock;
@@ -25,14 +26,14 @@ import org.apache.james.core.Username;
 import org.apache.james.dnsservice.api.DNSService;
 import org.apache.james.domainlist.lib.DomainListConfiguration;
 import org.apache.james.domainlist.memory.MemoryDomainList;
-import org.apache.james.rrt.api.CanSendFrom;
+import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
 import org.junit.jupiter.api.BeforeEach;
 
-public class CanSendFromImplTest implements CanSendFromContract {
+public class ReverseRecipientRewriteTableImplTest implements ReverseRecipientRewriteTableContract {
 
     AbstractRecipientRewriteTable recipientRewriteTable;
-    CanSendFrom canSendFrom;
+    ReverseRecipientRewriteTableImpl reverseRecipientRewriteTable;
 
     @BeforeEach
     void setup() throws Exception {
@@ -47,12 +48,12 @@ public class CanSendFromImplTest implements CanSendFromContract {
         domainList.addDomain(OTHER_DOMAIN);
         recipientRewriteTable.setDomainList(domainList);
 
-        canSendFrom = new CanSendFromImpl(recipientRewriteTable);
+        this.reverseRecipientRewriteTable = new ReverseRecipientRewriteTableImpl(recipientRewriteTable);
     }
 
     @Override
-    public CanSendFrom canSendFrom() {
-        return canSendFrom;
+    public ReverseRecipientRewriteTable reverseRecipientRewriteTable() {
+        return reverseRecipientRewriteTable;
     }
 
     @Override
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessorTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessorTest.java
index ade1101..6740ff1 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessorTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessorTest.java
@@ -74,9 +74,11 @@ import org.apache.james.mailbox.model.TestMessageId;
 import org.apache.james.metrics.tests.RecordingMetricFactory;
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.rrt.api.RecipientRewriteTableException;
+import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.rrt.lib.Mapping;
 import org.apache.james.rrt.lib.MappingSource;
 import org.apache.james.rrt.lib.CanSendFromImpl;
+import org.apache.james.rrt.lib.ReverseRecipientRewriteTableImpl;
 import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
 import org.apache.james.util.OptionalUtils;
 import org.apache.james.util.html.HtmlTextExtractor;
@@ -154,7 +156,8 @@ public class SetMessagesCreationProcessorTest {
         domainList.addDomain(Domain.of("example.com"));
         domainList.addDomain(Domain.of("other.org"));
         recipientRewriteTable.setDomainList(domainList);
-        canSendFrom = new CanSendFromImpl(recipientRewriteTable);
+        ReverseRecipientRewriteTable reverseRecipientRewriteTable = new ReverseRecipientRewriteTableImpl(recipientRewriteTable);
+        canSendFrom = new CanSendFromImpl(recipientRewriteTable, reverseRecipientRewriteTable);
         messageFullViewFactory = new MessageFullViewFactory(blobManager, messageContentExtractor, htmlTextExtractor,
             messageIdManager,
             new MemoryMessageFastViewProjection(new RecordingMetricFactory()));
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java
index 9b6f88a..4b8f472 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java
@@ -67,8 +67,10 @@ import org.apache.james.mailbox.model.TestMessageId;
 import org.apache.james.metrics.tests.RecordingMetricFactory;
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.rrt.api.RecipientRewriteTableException;
+import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.rrt.lib.CanSendFromImpl;
 import org.apache.james.rrt.lib.MappingSource;
+import org.apache.james.rrt.lib.ReverseRecipientRewriteTableImpl;
 import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
 import org.apache.james.util.OptionalUtils;
 import org.apache.james.util.html.HtmlTextExtractor;
@@ -167,7 +169,8 @@ public class SetMessagesUpdateProcessorTest {
         domainList.addDomain(Domain.of("example.com"));
         domainList.addDomain(Domain.of("other.org"));
         recipientRewriteTable.setDomainList(domainList);
-        canSendFrom = new CanSendFromImpl(recipientRewriteTable);
+        ReverseRecipientRewriteTable reverseRecipientRewriteTable = new ReverseRecipientRewriteTableImpl(recipientRewriteTable);
+        canSendFrom = new CanSendFromImpl(recipientRewriteTable, reverseRecipientRewriteTable);
         mockedMailSpool = mock(MailSpool.class);
         mockedMailboxManager = mock(MailboxManager.class);
         mockedMailboxIdFactory = mock(MailboxId.Factory.class);
diff --git a/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPServerTest.java b/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPServerTest.java
index 9e5278a..c471f72 100644
--- a/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPServerTest.java
+++ b/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/SMTPServerTest.java
@@ -72,8 +72,10 @@ import org.apache.james.queue.api.RawMailQueueItemDecoratorFactory;
 import org.apache.james.queue.memory.MemoryMailQueueFactory;
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.rrt.lib.CanSendFromImpl;
 import org.apache.james.rrt.lib.MappingSource;
+import org.apache.james.rrt.lib.ReverseRecipientRewriteTableImpl;
 import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
 import org.apache.james.server.core.configuration.Configuration;
 import org.apache.james.server.core.filesystem.FileSystemImpl;
@@ -198,6 +200,7 @@ public class SMTPServerTest {
     protected MemoryMailQueueFactory queueFactory;
     protected MemoryMailQueueFactory.MemoryMailQueue queue;
     protected MemoryRecipientRewriteTable rewriteTable;
+    private ReverseRecipientRewriteTable reverseRewriteTable;
     protected CanSendFrom canSendFrom;
 
     private SMTPServer smtpServer;
@@ -276,7 +279,8 @@ public class SMTPServerTest {
         dnsServer = new AlterableDNSServer();
 
         rewriteTable = new MemoryRecipientRewriteTable();
-        canSendFrom = new CanSendFromImpl(rewriteTable);
+        reverseRewriteTable = new ReverseRecipientRewriteTableImpl(rewriteTable);
+        canSendFrom = new CanSendFromImpl(rewriteTable, reverseRewriteTable);
         queueFactory = new MemoryMailQueueFactory(new RawMailQueueItemDecoratorFactory());
         queue = queueFactory.createQueue(MailQueueFactory.SPOOL);
 
diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/UserRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/UserRoutesTest.java
index 7936d4f..d45f79e 100644
--- a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/UserRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/UserRoutesTest.java
@@ -43,8 +43,10 @@ import org.apache.james.domainlist.api.mock.SimpleDomainList;
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.rrt.api.RecipientRewriteTable;
 import org.apache.james.rrt.api.RecipientRewriteTableException;
+import org.apache.james.rrt.api.ReverseRecipientRewriteTable;
 import org.apache.james.rrt.lib.CanSendFromImpl;
 import org.apache.james.rrt.lib.MappingSource;
+import org.apache.james.rrt.lib.ReverseRecipientRewriteTableImpl;
 import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
 import org.apache.james.user.api.UsersRepository;
 import org.apache.james.user.api.UsersRepositoryException;
@@ -98,6 +100,7 @@ class UserRoutesTest {
         final MemoryUsersRepository usersRepository;
         final SimpleDomainList domainList;
         final MemoryRecipientRewriteTable recipientRewriteTable;
+        final ReverseRecipientRewriteTable reverseRecipientRewriteTable;
         final CanSendFrom canSendFrom;
 
         WebAdminServer webAdminServer;
@@ -107,7 +110,8 @@ class UserRoutesTest {
             this.domainList = domainList;
             this.recipientRewriteTable = new MemoryRecipientRewriteTable();
             this.recipientRewriteTable.setDomainList(domainList);
-            this.canSendFrom = new CanSendFromImpl(recipientRewriteTable);
+            this.reverseRecipientRewriteTable = new ReverseRecipientRewriteTableImpl(recipientRewriteTable);
+            this.canSendFrom = new CanSendFromImpl(recipientRewriteTable, reverseRecipientRewriteTable);
         }
 
         @Override


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


[james-project] 01/21: JAMES-2897 Set ConsistencyLevel to QUORUM at the request level where needed

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

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

commit 27fda4a92a151f8b7d4255dcd659d43217cac79a
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Mar 5 18:10:01 2020 +0700

    JAMES-2897 Set ConsistencyLevel to QUORUM at the request level where needed
    
    Finding a mailbox and a message defaulted to ALL consistency level instead of the
    expected QUORUM value.
    
    This commit enforce QUORUM to be used for all cassandra reads of both of these operations
    not being set at consistency level SERIAL.
---
 .../apache/james/mailbox/cassandra/mail/CassandraAttachmentDAOV2.java | 4 +++-
 .../org/apache/james/mailbox/cassandra/mail/CassandraMailboxDAO.java  | 4 +++-
 .../james/mailbox/cassandra/mail/CassandraMailboxPathDAOImpl.java     | 4 +++-
 .../org/apache/james/mailbox/cassandra/mail/CassandraMessageDAO.java  | 4 +++-
 4 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentDAOV2.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentDAOV2.java
index a044e1f..5a1ee5f 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentDAOV2.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentDAOV2.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.mailbox.cassandra.mail;
 
+import static com.datastax.driver.core.ConsistencyLevel.QUORUM;
 import static com.datastax.driver.core.querybuilder.QueryBuilder.bindMarker;
 import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
 import static com.datastax.driver.core.querybuilder.QueryBuilder.insertInto;
@@ -154,7 +155,8 @@ public class CassandraAttachmentDAOV2 {
         Preconditions.checkArgument(attachmentId != null);
         return cassandraAsyncExecutor.executeSingleRow(
             selectStatement.bind()
-                .setUUID(ID_AS_UUID, attachmentId.asUUID()))
+                .setUUID(ID_AS_UUID, attachmentId.asUUID())
+                .setConsistencyLevel(QUORUM))
             .map(row -> CassandraAttachmentDAOV2.fromRow(row, blobIdFactory));
     }
 
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxDAO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxDAO.java
index e5c097c..02a7b13 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxDAO.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxDAO.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.mailbox.cassandra.mail;
 
+import static com.datastax.driver.core.ConsistencyLevel.QUORUM;
 import static com.datastax.driver.core.querybuilder.QueryBuilder.bindMarker;
 import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
 import static com.datastax.driver.core.querybuilder.QueryBuilder.insertInto;
@@ -135,7 +136,8 @@ public class CassandraMailboxDAO {
 
     public Mono<Mailbox> retrieveMailbox(CassandraId mailboxId) {
         return executor.executeSingleRow(readStatement.bind()
-            .setUUID(ID, mailboxId.asUuid()))
+            .setUUID(ID, mailboxId.asUuid())
+            .setConsistencyLevel(QUORUM))
             .map(row -> mailboxFromRow(row, mailboxId));
     }
 
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathDAOImpl.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathDAOImpl.java
index 50093ff..df210a0 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathDAOImpl.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxPathDAOImpl.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.mailbox.cassandra.mail;
 
+import static com.datastax.driver.core.ConsistencyLevel.QUORUM;
 import static com.datastax.driver.core.querybuilder.QueryBuilder.bindMarker;
 import static com.datastax.driver.core.querybuilder.QueryBuilder.count;
 import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
@@ -127,7 +128,8 @@ public class CassandraMailboxPathDAOImpl {
         return cassandraAsyncExecutor.executeSingleRow(
             select.bind()
                 .setUDTValue(NAMESPACE_AND_USER, mailboxBaseTupleUtil.createMailboxBaseUDT(mailboxPath.getNamespace(), mailboxPath.getUser()))
-                .setString(MAILBOX_NAME, mailboxPath.getName()))
+                .setString(MAILBOX_NAME, mailboxPath.getName())
+                .setConsistencyLevel(QUORUM))
             .map(this::fromRowToCassandraIdAndPath)
             .map(FunctionalUtils.toFunction(this::logGhostMailboxSuccess))
             .switchIfEmpty(ReactorUtils.executeAndEmpty(() -> logGhostMailboxFailure(mailboxPath)));
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAO.java
index 0bfce81..621a685 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAO.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAO.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.mailbox.cassandra.mail;
 
+import static com.datastax.driver.core.ConsistencyLevel.QUORUM;
 import static com.datastax.driver.core.querybuilder.QueryBuilder.bindMarker;
 import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
 import static com.datastax.driver.core.querybuilder.QueryBuilder.insertInto;
@@ -248,7 +249,8 @@ public class CassandraMessageDAO {
 
         return cassandraAsyncExecutor.execute(retrieveSelect(fetchType)
             .bind()
-            .setUUID(MESSAGE_ID, cassandraMessageId.get()));
+            .setUUID(MESSAGE_ID, cassandraMessageId.get())
+            .setConsistencyLevel(QUORUM));
     }
 
     private Mono<MessageResult>


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


[james-project] 14/21: JAMES-3058 WebAdmin documentation: solving mailbox inconsistencies

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

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

commit 548c6a7772b3348dc743a51590de65fb3b3c706a
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Feb 14 15:00:27 2020 +0700

    JAMES-3058 WebAdmin documentation: solving mailbox inconsistencies
---
 src/site/markdown/server/manage-webadmin.md | 48 ++++++++++++++++++++++++++++-
 1 file changed, 47 insertions(+), 1 deletion(-)

diff --git a/src/site/markdown/server/manage-webadmin.md b/src/site/markdown/server/manage-webadmin.md
index dcc35c6..51325d6 100644
--- a/src/site/markdown/server/manage-webadmin.md
+++ b/src/site/markdown/server/manage-webadmin.md
@@ -391,7 +391,53 @@ Response codes:
 
 The kind of task scheduled depends on the action parameter. See below for details.
 
-### Recomputing Global JMAP fast message view projection
+#### Fixing mailboxes inconsistencies
+
+This task is only available on top of Guice Cassandra products.
+
+```
+curl -XPOST /mailboxes?task=SolveInconsistencies
+```
+
+Will schedule a task for fixing inconsistencies for the mailbox deduplicated object stored in Cassandra.
+
+[More details about endpoints returning a task](#Endpoints_returning_a_task).
+
+The scheduled task will have the following type `solve-mailbox-inconsistencies` and the following `additionalInformation`:
+
+```
+{
+  "type":"solve-mailbox-inconsistencies",
+  "processedMailboxEntries": 3,
+  "processedMailboxPathEntries": 3,
+  "fixedInconsistencies": 2,
+  "errors": 1,
+  "conflictingEntries":[{
+    "mailboxDaoEntry":{
+      "mailboxPath":"#private:user:mailboxName",
+      "mailboxId":"464765a0-e4e7-11e4-aba4-710c1de3782b"
+    }," +
+    "mailboxPathDaoEntry":{
+      "mailboxPath":"#private:user:mailboxName2",
+      "mailboxId":"464765a0-e4e7-11e4-aba4-710c1de3782b"
+    }
+  }]
+}
+```
+
+Note that conflicting entry inconsistencies will not be fixed and will require to explicitly use 
+[ghost mailbox](#correcting-ghost-mailbox) endpoint in order to merge the conflicting mailboxes and prevent any message
+loss.
+
+**WARNING**: this task can cancel concurrently running legitimate user operations upon dirty read. As such this task 
+should be run offline. 
+
+A dirty read is when data is read between the two writes of the denormalization operations (no isolation).
+
+In order to ensure being offline, stop the traffic on SMTP, JMAP and IMAP ports, for example via re-configuration or 
+firewall rules.
+
+#### Recomputing Global JMAP fast message view projection
 
 This action is only available for backends supporting JMAP protocol.
 


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


[james-project] 08/21: JAMES-3078 ADR for Reactor-netty adoption for JMAP server implementation

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

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

commit 5322274e4d787570840370a76fd2047c68157bd1
Author: Rene Cordier <rc...@linagora.com>
AuthorDate: Fri Feb 28 11:06:27 2020 +0700

    JAMES-3078 ADR for Reactor-netty adoption for JMAP server implementation
---
 src/adr/0019-reactor-netty-adoption.md | 41 ++++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/src/adr/0019-reactor-netty-adoption.md b/src/adr/0019-reactor-netty-adoption.md
new file mode 100644
index 0000000..3f78056
--- /dev/null
+++ b/src/adr/0019-reactor-netty-adoption.md
@@ -0,0 +1,41 @@
+# 19. Reactor-netty adoption for JMAP server implementation
+
+Date: 2020-02-28
+
+## Status
+
+Accepted (lazy consensus)
+
+## Context
+
+After adopting the last specifications of JMAP (see 
+[new JMAP specifications adoption ADR](https://github.com/apache/james-project/blob/master/src/adr/0018-jmap-new-specs.md)), 
+it was agreed that we need to be able to serve both `jmap-draft` and the new `jmap` with a reactive server. 
+
+The current outdated implementation of JMAP in James is currently using a non-reactive [Jetty server](https://www.eclipse.org/jetty/).
+
+There are many possible candidates as reactive servers. Among the most popular ones for Java:
+
+* [Spring](https://spring.io)
+* [Reactor-netty](https://github.com/reactor/reactor-netty)
+* [Akka HTTP](https://doc.akka.io/docs/akka-http/current/introduction.html)
+* ...
+
+## Decision
+
+We decide to use `reactor-netty` for the following reasons:
+
+* It's a reactive server
+* It's using [Reactor](https://projectreactor.io/), which is the same technology that we use in the rest of our codebase
+* Implementing JMAP does not require high level HTTP server features
+
+## Consequences
+
+* Porting current `jmap-draft` to use a `reactor-netty` server instead of a Jetty server
+* The `reactor-netty` server should serve as well the new `jmap` implementation
+* We will be able to refactor and get end-to-end reactive operations for JMAP, unlocking performance gains
+
+## References
+
+* JIRA: [JAMES-3078](https://issues.apache.org/jira/browse/JAMES-3078)
+* JMAP new specifications adoption ADR: https://github.com/apache/james-project/blob/master/src/adr/0018-jmap-new-specs.md
\ No newline at end of file


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


[james-project] 16/21: JAMES-3058 ReIndexing: product disclaimer

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

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

commit ee2a84a2b6a41dbff8ac22031fcdc6e25f087b30
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Feb 14 15:04:19 2020 +0700

    JAMES-3058 ReIndexing: product disclaimer
    
    It should be clear that ReIndexing webAdmin endpoints do not exists on
    top of Guice Memory product.
---
 src/site/markdown/server/manage-webadmin.md | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/src/site/markdown/server/manage-webadmin.md b/src/site/markdown/server/manage-webadmin.md
index 51325d6..33d7a16 100644
--- a/src/site/markdown/server/manage-webadmin.md
+++ b/src/site/markdown/server/manage-webadmin.md
@@ -476,6 +476,9 @@ Response codes:
 
 #### ReIndexing action
 
+These tasks are only available on top of Guice Cassandra products or Guice JPA products. They are not part of Memory
+Guice product. 
+
 Be also aware of the limits of this API:
 
 Warning: During the re-indexing, the result of search operations might be altered.
@@ -536,6 +539,9 @@ The scheduled task will have the following type `error-recovery-indexation` and
 
 #### ReIndexing a mailbox mails
 
+This task is only available on top of Guice Cassandra products or Guice JPA products. It is not part of Memory
+Guice product. 
+
 ```
 curl -XPOST http://ip:port/mailboxes/{mailboxId}?task=reIndex
 ```
@@ -574,6 +580,9 @@ concurrent changes done during the reIndexing might be ignored.
 
 #### ReIndexing a single mail
 
+This task is only available on top of Guice Cassandra products or Guice JPA products. It is not part of Memory
+Guice product.
+
 ```
 curl -XPOST http://ip:port/mailboxes/{mailboxId}/uid/{uid}?task=reIndex
 ```
@@ -606,6 +615,9 @@ Warning: Canceling this task should be considered unsafe as it will leave the cu
 
 ### ReIndexing a single mail by messageId
 
+This task is only available on top of Guice Cassandra products or Guice JPA products. It is not part of Memory
+Guice product.
+
 ```
 curl -XPOST http://ip:port/messages/{messageId}?task=reIndex
 ```


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