Repository: james-project
Updated Branches:
  refs/heads/master e745d958b -> 829074f99


JAMES-2649 Add a module for alias sources Cassandra projection consistency and 
tests


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/dbb95982
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/dbb95982
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/dbb95982

Branch: refs/heads/master
Commit: dbb95982c7b67336394077992419ec64016b8b35
Parents: e745d95
Author: Rene Cordier <[email protected]>
Authored: Fri Jan 25 15:38:17 2019 +0700
Committer: Benoit Tellier <[email protected]>
Committed: Fri Feb 1 15:32:32 2019 +0700

----------------------------------------------------------------------
 pom.xml                                         |   5 +
 server/container/guice/cassandra-guice/pom.xml  |   4 +
 .../apache/james/CassandraJamesServerMain.java  |   2 +
 server/container/guice/pom.xml                  |   6 +
 .../protocols/webadmin-cassandra-data/pom.xml   |  53 +++++
 .../server/CassandraDataRoutesModules.java      |  40 ++++
 .../cassandra/CassandraMappingsSourcesDAO.java  |  11 +
 .../migration/MappingsSourcesMigration.java     |   1 -
 .../CassandraMappingsSourcesDAOTest.java        |  15 +-
 .../protocols/webadmin-integration-test/pom.xml |   5 +
 .../integration/UnauthorizedEndpointsTest.java  |   4 +-
 .../WebAdminServerIntegrationTest.java          |  36 ++++
 server/protocols/webadmin/pom.xml               |   1 +
 .../webadmin/webadmin-cassandra-data/pom.xml    |  88 ++++++++
 .../james/webadmin/dto/ActionMappings.java      |  36 ++++
 .../routes/CassandraMappingsRoutes.java         | 106 ++++++++++
 .../service/CassandraMappingsService.java       |  52 +++++
 ...ssandraMappingsSolveInconsistenciesTask.java |  56 +++++
 .../james/webadmin/dto/ActionMappingsTest.java  |  49 +++++
 .../routes/CassandraMappingsRoutesTest.java     | 208 +++++++++++++++++++
 .../apache/james/webadmin/WebAdminServer.java   |  11 +
 .../james/webadmin/routes/ErrorRoutes.java      |  15 +-
 .../james/webadmin/routes/ErrorRoutesTest.java  |  15 +-
 .../webadmin/routes/SieveScriptRoutesTest.java  |   4 +-
 src/site/markdown/server/manage-webadmin.md     |  34 +++
 25 files changed, 846 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 1bc0311..f5d0c86 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1621,6 +1621,11 @@
             </dependency>
             <dependency>
                 <groupId>${james.groupId}</groupId>
+                <artifactId>james-server-webadmin-cassandra-data</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>${james.groupId}</groupId>
                 <artifactId>james-server-webadmin-core</artifactId>
                 <version>${project.version}</version>
             </dependency>

http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/container/guice/cassandra-guice/pom.xml
----------------------------------------------------------------------
diff --git a/server/container/guice/cassandra-guice/pom.xml 
b/server/container/guice/cassandra-guice/pom.xml
index 81df22e..1c9b572 100644
--- a/server/container/guice/cassandra-guice/pom.xml
+++ b/server/container/guice/cassandra-guice/pom.xml
@@ -188,6 +188,10 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-guice-webadmin-cassandra-data</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>james-server-guice-webadmin-data</artifactId>
         </dependency>
         <dependency>

http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/container/guice/cassandra-guice/src/main/java/org/apache/james/CassandraJamesServerMain.java
----------------------------------------------------------------------
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 697c7d7..db1b48e 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
@@ -44,6 +44,7 @@ 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.DLPRoutesModule;
 import org.apache.james.modules.server.DataRoutesModules;
@@ -67,6 +68,7 @@ public class CassandraJamesServerMain {
 
     public static final Module WEBADMIN = Modules.combine(
         new CassandraRoutesModule(),
+        new CassandraDataRoutesModules(),
         new DataRoutesModules(),
         new MailboxRoutesModule(),
         new MailQueueRoutesModule(),

http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/container/guice/pom.xml
----------------------------------------------------------------------
diff --git a/server/container/guice/pom.xml b/server/container/guice/pom.xml
index b4e4e3e..2e588e5 100644
--- a/server/container/guice/pom.xml
+++ b/server/container/guice/pom.xml
@@ -64,6 +64,7 @@
         <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-mailbox</module>
         <module>protocols/webadmin-mailqueue</module>
@@ -170,6 +171,11 @@
             </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>

http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/container/guice/protocols/webadmin-cassandra-data/pom.xml
----------------------------------------------------------------------
diff --git a/server/container/guice/protocols/webadmin-cassandra-data/pom.xml 
b/server/container/guice/protocols/webadmin-cassandra-data/pom.xml
new file mode 100644
index 0000000..f479e02
--- /dev/null
+++ b/server/container/guice/protocols/webadmin-cassandra-data/pom.xml
@@ -0,0 +1,53 @@
+<?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.3.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-guice-configuration</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-webadmin-cassandra-data</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.inject</groupId>
+            <artifactId>guice</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.inject.extensions</groupId>
+            <artifactId>guice-multibindings</artifactId>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/container/guice/protocols/webadmin-cassandra-data/src/main/java/org/apache/james/modules/server/CassandraDataRoutesModules.java
----------------------------------------------------------------------
diff --git 
a/server/container/guice/protocols/webadmin-cassandra-data/src/main/java/org/apache/james/modules/server/CassandraDataRoutesModules.java
 
b/server/container/guice/protocols/webadmin-cassandra-data/src/main/java/org/apache/james/modules/server/CassandraDataRoutesModules.java
new file mode 100644
index 0000000..8c2b4ee
--- /dev/null
+++ 
b/server/container/guice/protocols/webadmin-cassandra-data/src/main/java/org/apache/james/modules/server/CassandraDataRoutesModules.java
@@ -0,0 +1,40 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.modules.server;
+
+import org.apache.james.webadmin.Routes;
+import org.apache.james.webadmin.routes.CassandraMappingsRoutes;
+import org.apache.james.webadmin.service.CassandraMappingsService;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Scopes;
+import com.google.inject.multibindings.Multibinder;
+
+public class CassandraDataRoutesModules 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);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/data/data-cassandra/src/main/java/org/apache/james/rrt/cassandra/CassandraMappingsSourcesDAO.java
----------------------------------------------------------------------
diff --git 
a/server/data/data-cassandra/src/main/java/org/apache/james/rrt/cassandra/CassandraMappingsSourcesDAO.java
 
b/server/data/data-cassandra/src/main/java/org/apache/james/rrt/cassandra/CassandraMappingsSourcesDAO.java
index 6ff3072..d7d5fb3 100644
--- 
a/server/data/data-cassandra/src/main/java/org/apache/james/rrt/cassandra/CassandraMappingsSourcesDAO.java
+++ 
b/server/data/data-cassandra/src/main/java/org/apache/james/rrt/cassandra/CassandraMappingsSourcesDAO.java
@@ -24,6 +24,7 @@ import static 
com.datastax.driver.core.querybuilder.QueryBuilder.delete;
 import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
 import static com.datastax.driver.core.querybuilder.QueryBuilder.insertInto;
 import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.truncate;
 import static 
org.apache.james.rrt.cassandra.tables.CassandraMappingsSourcesTable.MAPPING_TYPE;
 import static 
org.apache.james.rrt.cassandra.tables.CassandraMappingsSourcesTable.MAPPING_VALUE;
 import static 
org.apache.james.rrt.cassandra.tables.CassandraMappingsSourcesTable.SOURCE;
@@ -46,6 +47,7 @@ public class CassandraMappingsSourcesDAO {
     private final PreparedStatement insertStatement;
     private final PreparedStatement deleteStatement;
     private final PreparedStatement retrieveSourcesStatement;
+    private final PreparedStatement truncateStatement;
 
     @Inject
     public CassandraMappingsSourcesDAO(Session session) {
@@ -53,6 +55,7 @@ public class CassandraMappingsSourcesDAO {
         this.insertStatement = prepareInsertStatement(session);
         this.deleteStatement = prepareDelete(session);
         this.retrieveSourcesStatement = 
prepareRetrieveSourcesStatement(session);
+        this.truncateStatement = prepareTruncateStatement(session);
     }
 
     private PreparedStatement prepareInsertStatement(Session session) {
@@ -77,6 +80,10 @@ public class CassandraMappingsSourcesDAO {
             .and(eq(MAPPING_VALUE, bindMarker(MAPPING_VALUE))));
     }
 
+    private PreparedStatement prepareTruncateStatement(Session session) {
+        return session.prepare(truncate(TABLE_NAME));
+    }
+
     public Mono<Void> addMapping(Mapping mapping, MappingSource source) {
         return executor.executeVoidReactor(insertStatement.bind()
             .setString(MAPPING_TYPE, mapping.getType().asPrefix())
@@ -98,4 +105,8 @@ public class CassandraMappingsSourcesDAO {
             .flatMapMany(Flux::fromIterable)
             .map(row -> MappingSource.parse(row.getString(SOURCE)));
     }
+
+    public Mono<Void> removeAllData() {
+        return executor.executeVoidReactor(truncateStatement.bind());
+    }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/data/data-cassandra/src/main/java/org/apache/james/rrt/cassandra/migration/MappingsSourcesMigration.java
----------------------------------------------------------------------
diff --git 
a/server/data/data-cassandra/src/main/java/org/apache/james/rrt/cassandra/migration/MappingsSourcesMigration.java
 
b/server/data/data-cassandra/src/main/java/org/apache/james/rrt/cassandra/migration/MappingsSourcesMigration.java
index 226add2..1e612bc 100644
--- 
a/server/data/data-cassandra/src/main/java/org/apache/james/rrt/cassandra/migration/MappingsSourcesMigration.java
+++ 
b/server/data/data-cassandra/src/main/java/org/apache/james/rrt/cassandra/migration/MappingsSourcesMigration.java
@@ -45,7 +45,6 @@ public class MappingsSourcesMigration implements Migration {
         this.cassandraMappingsSourcesDAO = cassandraMappingsSourcesDAO;
     }
 
-
     @Override
     public Result run() {
         return cassandraRecipientRewriteTableDAO.getAllMappings()

http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/data/data-cassandra/src/test/java/org/apache/james/rrt/cassandra/CassandraMappingsSourcesDAOTest.java
----------------------------------------------------------------------
diff --git 
a/server/data/data-cassandra/src/test/java/org/apache/james/rrt/cassandra/CassandraMappingsSourcesDAOTest.java
 
b/server/data/data-cassandra/src/test/java/org/apache/james/rrt/cassandra/CassandraMappingsSourcesDAOTest.java
index b277a4e..045eb1b 100644
--- 
a/server/data/data-cassandra/src/test/java/org/apache/james/rrt/cassandra/CassandraMappingsSourcesDAOTest.java
+++ 
b/server/data/data-cassandra/src/test/java/org/apache/james/rrt/cassandra/CassandraMappingsSourcesDAOTest.java
@@ -34,6 +34,7 @@ class CassandraMappingsSourcesDAOTest {
     private static final String USER = "test";
     private static final String ADDRESS = "test@domain";
     private static final MappingSource SOURCE = MappingSource.fromUser(USER, 
Domain.LOCALHOST);
+    private static final MappingSource SOURCE_2 = 
MappingSource.fromUser("bob", Domain.LOCALHOST);
     private static final Mapping MAPPING = Mapping.alias(ADDRESS);
 
     @RegisterExtension
@@ -69,11 +70,19 @@ class CassandraMappingsSourcesDAOTest {
 
     @Test
     void retrieveSourcesShouldReturnMultipleStoredMappingSourcesForMapping() {
-        MappingSource source2 = MappingSource.fromUser("bob", 
Domain.LOCALHOST);
+        dao.addMapping(MAPPING, SOURCE).block();
+        dao.addMapping(MAPPING, SOURCE_2).block();
+
+        
assertThat(dao.retrieveSources(MAPPING).collectList().block()).containsOnly(SOURCE,
 SOURCE_2);
+    }
 
+    @Test
+    void retrieveSourcesShouldReturnEmptyAfterTruncateData() {
         dao.addMapping(MAPPING, SOURCE).block();
-        dao.addMapping(MAPPING, source2).block();
+        dao.addMapping(MAPPING, SOURCE_2).block();
+
+        dao.removeAllData().block();
 
-        
assertThat(dao.retrieveSources(MAPPING).collectList().block()).containsOnly(SOURCE,
 source2);
+        
assertThat(dao.retrieveSources(MAPPING).collectList().block()).isEmpty();
     }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin-integration-test/pom.xml
----------------------------------------------------------------------
diff --git a/server/protocols/webadmin-integration-test/pom.xml 
b/server/protocols/webadmin-integration-test/pom.xml
index a585ffb..15f0ea1 100644
--- a/server/protocols/webadmin-integration-test/pom.xml
+++ b/server/protocols/webadmin-integration-test/pom.xml
@@ -88,6 +88,11 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-webadmin-cassandra-data</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>james-server-webadmin-data</artifactId>
             <scope>test</scope>
         </dependency>

http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/UnauthorizedEndpointsTest.java
----------------------------------------------------------------------
diff --git 
a/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/UnauthorizedEndpointsTest.java
 
b/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/UnauthorizedEndpointsTest.java
index 14abe53..7c49324 100644
--- 
a/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/UnauthorizedEndpointsTest.java
+++ 
b/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/UnauthorizedEndpointsTest.java
@@ -25,6 +25,7 @@ import org.apache.james.GuiceJamesServer;
 import org.apache.james.utils.WebAdminGuiceProbe;
 import org.apache.james.webadmin.WebAdminUtils;
 import org.apache.james.webadmin.routes.AliasRoutes;
+import org.apache.james.webadmin.routes.CassandraMappingsRoutes;
 import org.apache.james.webadmin.routes.CassandraMigrationRoutes;
 import org.apache.james.webadmin.routes.DLPConfigurationRoutes;
 import org.apache.james.webadmin.routes.DomainMappingsRoutes;
@@ -107,7 +108,8 @@ class UnauthorizedEndpointsTest {
     @ParameterizedTest
     @ValueSource(strings = {
             CassandraMigrationRoutes.VERSION_BASE + "/upgrade",
-            CassandraMigrationRoutes.VERSION_BASE + "/upgrade/latest"
+            CassandraMigrationRoutes.VERSION_BASE + "/upgrade/latest",
+            CassandraMappingsRoutes.ROOT_PATH
     })
     void checkUrlProtectionOnPost(String url) {
         when()

http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/WebAdminServerIntegrationTest.java
----------------------------------------------------------------------
diff --git 
a/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/WebAdminServerIntegrationTest.java
 
b/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/WebAdminServerIntegrationTest.java
index 153a76e..85a68c5 100644
--- 
a/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/WebAdminServerIntegrationTest.java
+++ 
b/server/protocols/webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/WebAdminServerIntegrationTest.java
@@ -25,6 +25,7 @@ import static io.restassured.RestAssured.with;
 import static org.apache.james.webadmin.Constants.JSON_CONTENT_TYPE;
 import static org.apache.james.webadmin.Constants.SEPARATOR;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.CoreMatchers.hasItems;
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.is;
@@ -41,12 +42,14 @@ import org.apache.james.utils.DataProbeImpl;
 import org.apache.james.utils.WebAdminGuiceProbe;
 import org.apache.james.webadmin.WebAdminUtils;
 import org.apache.james.webadmin.routes.AliasRoutes;
+import org.apache.james.webadmin.routes.CassandraMappingsRoutes;
 import org.apache.james.webadmin.routes.DomainsRoutes;
 import org.apache.james.webadmin.routes.ForwardRoutes;
 import org.apache.james.webadmin.routes.GroupsRoutes;
 import org.apache.james.webadmin.routes.HealthCheckRoutes;
 import org.apache.james.webadmin.routes.MailQueueRoutes;
 import org.apache.james.webadmin.routes.MailRepositoriesRoutes;
+import org.apache.james.webadmin.routes.TasksRoutes;
 import org.apache.james.webadmin.routes.UserMailboxesRoutes;
 import org.apache.james.webadmin.routes.UserRoutes;
 import org.apache.james.webadmin.swagger.routes.SwaggerRoutes;
@@ -58,6 +61,7 @@ import org.junit.Rule;
 import org.junit.Test;
 
 import io.restassured.RestAssured;
+import io.restassured.http.ContentType;
 
 public class WebAdminServerIntegrationTest {
 
@@ -361,6 +365,7 @@ public class WebAdminServerIntegrationTest {
             .body(containsString("\"tags\":[\"Address Forwards\"]"))
             .body(containsString("\"tags\":[\"Address Aliases\"]"))
             .body(containsString("\"tags\":[\"Address Groups\"]"))
+            .body(containsString("\"tags\":[\"Cassandra Mappings 
Operations\"]"))
             .body(containsString("{\"name\":\"ReIndexing (mailboxes)\"}"))
             .body(containsString("{\"name\":\"MessageIdReIndexing\"}"));
     }
@@ -372,4 +377,35 @@ public class WebAdminServerIntegrationTest {
         .then()
             .statusCode(HttpStatus.OK_200);
     }
+
+    @Test
+    public void 
cassandraMappingsEndpointShouldKeepDataConsistencyWhenDataValid() {
+        String alias1 = "[email protected]";
+        String alias2 = "[email protected]";
+
+        with()
+            .put(AliasRoutes.ROOT_PATH + SEPARATOR + USERNAME + "/sources/" + 
alias1);
+        with()
+            .put(AliasRoutes.ROOT_PATH + SEPARATOR + USERNAME + "/sources/" + 
alias2);
+
+        String taskId = with()
+            .queryParam("action", "SolveInconsistencies")
+            .post(CassandraMappingsRoutes.ROOT_PATH)
+            .jsonPath()
+            .get("taskId");
+
+        given()
+            .basePath(TasksRoutes.BASE)
+        .when()
+            .get(taskId + "/await")
+        .then()
+            .body("status", is("completed"));
+
+        when()
+            .get(AliasRoutes.ROOT_PATH + SEPARATOR + USERNAME)
+        .then()
+            .contentType(ContentType.JSON)
+        .statusCode(HttpStatus.OK_200)
+            .body("source", hasItems(alias1, alias2));
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin/pom.xml
----------------------------------------------------------------------
diff --git a/server/protocols/webadmin/pom.xml 
b/server/protocols/webadmin/pom.xml
index a9725ea..3655376 100644
--- a/server/protocols/webadmin/pom.xml
+++ b/server/protocols/webadmin/pom.xml
@@ -34,6 +34,7 @@
 
     <modules>
         <module>webadmin-cassandra</module>
+        <module>webadmin-cassandra-data</module>
         <module>webadmin-core</module>
         <module>webadmin-data</module>
         <module>webadmin-mailbox</module>

http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin/webadmin-cassandra-data/pom.xml
----------------------------------------------------------------------
diff --git a/server/protocols/webadmin/webadmin-cassandra-data/pom.xml 
b/server/protocols/webadmin/webadmin-cassandra-data/pom.xml
new file mode 100644
index 0000000..47383d3
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-cassandra-data/pom.xml
@@ -0,0 +1,88 @@
+<?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</artifactId>
+        <version>3.3.0-SNAPSHOT</version>
+        <relativePath>../../../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>james-server-webadmin-cassandra-data</artifactId>
+    <packaging>jar</packaging>
+
+    <name>Apache James :: Server :: Web Admin :: Cassandra :: Data</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>apache-james-backends-cassandra</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>apache-james-backends-cassandra</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-data-cassandra</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-webadmin-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-webadmin-core</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
+            <artifactId>metrics-logger</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.rest-assured</groupId>
+            <artifactId>rest-assured</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>testcontainers</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/dto/ActionMappings.java
----------------------------------------------------------------------
diff --git 
a/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/dto/ActionMappings.java
 
b/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/dto/ActionMappings.java
new file mode 100644
index 0000000..7ca05b1
--- /dev/null
+++ 
b/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/dto/ActionMappings.java
@@ -0,0 +1,36 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.webadmin.dto;
+
+import java.util.Arrays;
+
+import com.google.common.base.Preconditions;
+
+public enum ActionMappings {
+    SolveInconsistencies;
+
+    public static ActionMappings parse(String action) {
+        Preconditions.checkArgument(action != null, "'action' url parameter is 
mandatory");
+        return Arrays.stream(ActionMappings.values())
+            .filter(element -> element.toString().equalsIgnoreCase(action))
+            .findAny()
+            .orElseThrow(() -> new IllegalArgumentException("'" + action + "' 
is not a valid action query parameter"));
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/routes/CassandraMappingsRoutes.java
----------------------------------------------------------------------
diff --git 
a/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/routes/CassandraMappingsRoutes.java
 
b/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/routes/CassandraMappingsRoutes.java
new file mode 100644
index 0000000..15c3d4e
--- /dev/null
+++ 
b/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/routes/CassandraMappingsRoutes.java
@@ -0,0 +1,106 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.webadmin.routes;
+
+import javax.inject.Inject;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+import org.apache.james.task.Task;
+import org.apache.james.task.TaskId;
+import org.apache.james.task.TaskManager;
+import org.apache.james.webadmin.Constants;
+import org.apache.james.webadmin.Routes;
+import org.apache.james.webadmin.dto.ActionMappings;
+import org.apache.james.webadmin.dto.TaskIdDto;
+import org.apache.james.webadmin.service.CassandraMappingsService;
+import org.apache.james.webadmin.utils.JsonTransformer;
+import org.eclipse.jetty.http.HttpStatus;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import io.swagger.annotations.ResponseHeader;
+import spark.Request;
+import spark.Response;
+import spark.Service;
+
+@Api(tags = "Cassandra Mappings Operations")
+@Path(CassandraMappingsRoutes.ROOT_PATH)
+@Produces(Constants.JSON_CONTENT_TYPE)
+public class CassandraMappingsRoutes implements Routes {
+    public static final String ROOT_PATH = "cassandra/mappings";
+
+    private final CassandraMappingsService cassandraMappingsService;
+    private final TaskManager taskManager;
+    private final JsonTransformer jsonTransformer;
+
+    private static final String INVALID_ACTION_ARGUMENT_REQUEST = "Invalid 
action argument for performing operation on mappings data";
+    private static final String ACTION_REQUEST_CAN_NOT_BE_DONE = "The action 
requested for performing operation on mappings data cannot be performed";
+
+    @Inject
+    CassandraMappingsRoutes(CassandraMappingsService cassandraMappingsService, 
TaskManager taskManager, JsonTransformer jsonTransformer) {
+        this.cassandraMappingsService = cassandraMappingsService;
+        this.taskManager = taskManager;
+        this.jsonTransformer = jsonTransformer;
+    }
+
+    @Override
+    public String getBasePath() {
+        return ROOT_PATH;
+    }
+
+    @Override
+    public void define(Service service) {
+        service.post(ROOT_PATH, this::performActionOnMappings, 
jsonTransformer);
+    }
+
+    @POST
+    @Path(ROOT_PATH)
+    @ApiOperation(value = "Performing operations on cassandra data mappings")
+    @ApiImplicitParams({
+        @ApiImplicitParam(
+            required = true,
+            dataType = "String",
+            name = "action",
+            paramType = "query",
+            example = "?action=SolveInconsistencies",
+            value = "Specify the action to perform on mappings. For now only 
'SolveInconsistencies' is supported as an action, "
+                + "and its purpose is to clean 'mappings_sources' projection 
table and repopulate it."),
+    })
+    @ApiResponses(value = {
+        @ApiResponse(code = HttpStatus.CREATED_201, message = "The taskId of 
the given scheduled task", response = TaskIdDto.class,
+            responseHeaders = {
+                @ResponseHeader(name = "Location", description = "URL of the 
resource associated with the scheduled task")
+            }),
+        @ApiResponse(code = HttpStatus.BAD_REQUEST_400, message = 
INVALID_ACTION_ARGUMENT_REQUEST),
+        @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500, message = 
ACTION_REQUEST_CAN_NOT_BE_DONE)
+    })
+    public TaskIdDto performActionOnMappings(Request request, Response 
response) {
+        ActionMappings action = 
ActionMappings.parse(request.queryParams("action"));
+        Task task = cassandraMappingsService.createActionTask(action);
+        TaskId taskId = taskManager.submit(task);
+        return TaskIdDto.respond(response, taskId);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/service/CassandraMappingsService.java
----------------------------------------------------------------------
diff --git 
a/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/service/CassandraMappingsService.java
 
b/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/service/CassandraMappingsService.java
new file mode 100644
index 0000000..e1733ef
--- /dev/null
+++ 
b/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/service/CassandraMappingsService.java
@@ -0,0 +1,52 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.webadmin.service;
+
+import javax.inject.Inject;
+
+import org.apache.james.rrt.cassandra.CassandraMappingsSourcesDAO;
+import org.apache.james.rrt.cassandra.migration.MappingsSourcesMigration;
+import org.apache.james.task.Task;
+import org.apache.james.webadmin.dto.ActionMappings;
+
+public class CassandraMappingsService {
+    private final MappingsSourcesMigration mappingsSourcesMigration;
+    private final CassandraMappingsSourcesDAO cassandraMappingsSourcesDAO;
+
+    @Inject
+    public CassandraMappingsService(MappingsSourcesMigration 
mappingsSourcesMigration,
+                                    CassandraMappingsSourcesDAO 
cassandraMappingsSourcesDAO) {
+        this.mappingsSourcesMigration = mappingsSourcesMigration;
+        this.cassandraMappingsSourcesDAO = cassandraMappingsSourcesDAO;
+    }
+
+    public Task createActionTask(ActionMappings action) {
+        switch (action) {
+            case SolveInconsistencies:
+                return solveMappingsSourcesInconsistencies();
+            default:
+                throw new IllegalArgumentException(action + " is not a 
supported action");
+        }
+    }
+
+    private Task solveMappingsSourcesInconsistencies() {
+        return new 
CassandraMappingsSolveInconsistenciesTask(mappingsSourcesMigration, 
cassandraMappingsSourcesDAO);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/service/CassandraMappingsSolveInconsistenciesTask.java
----------------------------------------------------------------------
diff --git 
a/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/service/CassandraMappingsSolveInconsistenciesTask.java
 
b/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/service/CassandraMappingsSolveInconsistenciesTask.java
new file mode 100644
index 0000000..14a1c47
--- /dev/null
+++ 
b/server/protocols/webadmin/webadmin-cassandra-data/src/main/java/org/apache/james/webadmin/service/CassandraMappingsSolveInconsistenciesTask.java
@@ -0,0 +1,56 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.webadmin.service;
+
+import javax.inject.Inject;
+
+import org.apache.james.rrt.cassandra.CassandraMappingsSourcesDAO;
+import org.apache.james.rrt.cassandra.migration.MappingsSourcesMigration;
+import org.apache.james.task.Task;
+
+import reactor.core.publisher.Mono;
+
+public class CassandraMappingsSolveInconsistenciesTask implements Task {
+    public static final String TYPE = "cassandraMappingsSolveInconsistencies";
+
+    private final MappingsSourcesMigration mappingsSourcesMigration;
+    private final CassandraMappingsSourcesDAO cassandraMappingsSourcesDAO;
+
+    @Inject
+    CassandraMappingsSolveInconsistenciesTask(MappingsSourcesMigration 
mappingsSourcesMigration,
+                                              CassandraMappingsSourcesDAO 
cassandraMappingsSourcesDAO) {
+        this.mappingsSourcesMigration = mappingsSourcesMigration;
+        this.cassandraMappingsSourcesDAO = cassandraMappingsSourcesDAO;
+    }
+
+    @Override
+    public Result run() {
+        return cassandraMappingsSourcesDAO.removeAllData()
+            .doOnError(e -> LOGGER.error("Error while cleaning up data in 
mappings sources projection table"))
+            .then(Mono.fromCallable(mappingsSourcesMigration::run))
+            .onErrorResume(e -> Mono.just(Result.PARTIAL))
+            .block();
+    }
+
+    @Override
+    public String type() {
+        return TYPE;
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin/webadmin-cassandra-data/src/test/java/org/apache/james/webadmin/dto/ActionMappingsTest.java
----------------------------------------------------------------------
diff --git 
a/server/protocols/webadmin/webadmin-cassandra-data/src/test/java/org/apache/james/webadmin/dto/ActionMappingsTest.java
 
b/server/protocols/webadmin/webadmin-cassandra-data/src/test/java/org/apache/james/webadmin/dto/ActionMappingsTest.java
new file mode 100644
index 0000000..3cfe691
--- /dev/null
+++ 
b/server/protocols/webadmin/webadmin-cassandra-data/src/test/java/org/apache/james/webadmin/dto/ActionMappingsTest.java
@@ -0,0 +1,49 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.webadmin.dto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.junit.jupiter.api.Test;
+
+class ActionMappingsTest {
+    private static final String ACTION = "SolveInconsistencies";
+
+    @Test
+    void parseShouldSucceedWithCorrectActionMappingsArgument() {
+        
assertThat(ActionMappings.parse(ACTION)).isEqualTo(ActionMappings.SolveInconsistencies);
+    }
+
+    @Test
+    void parseShouldFailWithIncorrectActionMappingsArgument() {
+        assertThatThrownBy(() -> ActionMappings.parse("incorrect-action"))
+            .isInstanceOf(IllegalArgumentException.class)
+            .hasMessage("'incorrect-action' is not a valid action query 
parameter");
+    }
+
+    @Test
+    void parseShouldFailWithMissingActionMappingsArgument() {
+        assertThatThrownBy(() -> ActionMappings.parse(null))
+            .isInstanceOf(IllegalArgumentException.class)
+            .hasMessage("'action' url parameter is mandatory");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin/webadmin-cassandra-data/src/test/java/org/apache/james/webadmin/routes/CassandraMappingsRoutesTest.java
----------------------------------------------------------------------
diff --git 
a/server/protocols/webadmin/webadmin-cassandra-data/src/test/java/org/apache/james/webadmin/routes/CassandraMappingsRoutesTest.java
 
b/server/protocols/webadmin/webadmin-cassandra-data/src/test/java/org/apache/james/webadmin/routes/CassandraMappingsRoutesTest.java
new file mode 100644
index 0000000..fedea9c
--- /dev/null
+++ 
b/server/protocols/webadmin/webadmin-cassandra-data/src/test/java/org/apache/james/webadmin/routes/CassandraMappingsRoutesTest.java
@@ -0,0 +1,208 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.webadmin.routes;
+
+import static io.restassured.RestAssured.given;
+import static io.restassured.RestAssured.when;
+import static io.restassured.RestAssured.with;
+import static org.apache.james.webadmin.WebAdminServer.NO_CONFIGURATION;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.james.backends.cassandra.CassandraCluster;
+import org.apache.james.backends.cassandra.CassandraClusterExtension;
+import org.apache.james.backends.cassandra.utils.CassandraUtils;
+import org.apache.james.core.Domain;
+import org.apache.james.metrics.logger.DefaultMetricFactory;
+import org.apache.james.rrt.cassandra.CassandraMappingsSourcesDAO;
+import org.apache.james.rrt.cassandra.CassandraRRTModule;
+import org.apache.james.rrt.cassandra.CassandraRecipientRewriteTableDAO;
+import org.apache.james.rrt.cassandra.migration.MappingsSourcesMigration;
+import org.apache.james.rrt.lib.Mapping;
+import org.apache.james.rrt.lib.MappingSource;
+import org.apache.james.task.MemoryTaskManager;
+import org.apache.james.webadmin.WebAdminServer;
+import org.apache.james.webadmin.WebAdminUtils;
+import org.apache.james.webadmin.service.CassandraMappingsService;
+import 
org.apache.james.webadmin.service.CassandraMappingsSolveInconsistenciesTask;
+import org.apache.james.webadmin.utils.ErrorResponder;
+import org.apache.james.webadmin.utils.JsonTransformer;
+import org.eclipse.jetty.http.HttpStatus;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.restassured.RestAssured;
+
+class CassandraMappingsRoutesTest {
+    private static final String MAPPINGS_ACTION = "SolveInconsistencies";
+    private static final MappingSource SOURCE_1 = 
MappingSource.fromUser("bob", Domain.LOCALHOST);
+    private static final MappingSource SOURCE_2 = 
MappingSource.fromUser("alice", Domain.LOCALHOST);
+    private static final Mapping MAPPING = Mapping.alias("bob-alias@domain");
+
+    private WebAdminServer webAdminServer;
+
+    private MappingsSourcesMigration mappingsSourcesMigration;
+    private CassandraRecipientRewriteTableDAO 
cassandraRecipientRewriteTableDAO;
+    private CassandraMappingsSourcesDAO cassandraMappingsSourcesDAO;
+    private MemoryTaskManager taskManager;
+
+    @RegisterExtension
+    static CassandraClusterExtension cassandraCluster = new 
CassandraClusterExtension(CassandraRRTModule.MODULE);
+
+    @BeforeEach
+    void setUp(CassandraCluster cassandra) throws ConfigurationException {
+        cassandraRecipientRewriteTableDAO = new 
CassandraRecipientRewriteTableDAO(cassandra.getConf(), 
CassandraUtils.WITH_DEFAULT_CONFIGURATION);
+        cassandraMappingsSourcesDAO = new 
CassandraMappingsSourcesDAO(cassandra.getConf());
+        mappingsSourcesMigration = new 
MappingsSourcesMigration(cassandraRecipientRewriteTableDAO, 
cassandraMappingsSourcesDAO);
+
+        CassandraMappingsService cassandraMappingsService = new 
CassandraMappingsService(mappingsSourcesMigration, cassandraMappingsSourcesDAO);
+
+        JsonTransformer jsonTransformer = new JsonTransformer();
+        taskManager = new MemoryTaskManager();
+        webAdminServer = WebAdminUtils.createWebAdminServer(
+            new DefaultMetricFactory(),
+            new CassandraMappingsRoutes(cassandraMappingsService, taskManager, 
jsonTransformer),
+            new TasksRoutes(taskManager, jsonTransformer));
+
+        webAdminServer.configure(NO_CONFIGURATION);
+        webAdminServer.await();
+
+        RestAssured.requestSpecification = 
WebAdminUtils.buildRequestSpecification(webAdminServer)
+            .setBasePath(CassandraMappingsRoutes.ROOT_PATH)
+            .build();
+    }
+
+    @AfterEach
+    void tearDown() {
+        webAdminServer.destroy();
+        taskManager.stop();
+    }
+
+    @Test
+    void 
postMappingsActionWithSolvedInconsistenciesQueryParamShouldCreateATask() {
+        given()
+            .queryParam("action", MAPPINGS_ACTION)
+        .when()
+            .post()
+        .then()
+            .statusCode(HttpStatus.CREATED_201)
+            .header("Location", is(notNullValue()))
+            .body("taskId", is(notNullValue()));
+    }
+
+    @Test
+    void 
postMappingsActionWithSolvedInconsistenciesQueryParamShouldHaveSuccessfulCompletedTask()
 {
+        String taskId = with()
+            .queryParam("action", MAPPINGS_ACTION)
+            .post()
+            .jsonPath()
+            .get("taskId");
+
+        given()
+            .basePath(TasksRoutes.BASE)
+        .when()
+            .get(taskId + "/await")
+        .then()
+            .body("status", is("completed"))
+            .body("taskId", is(taskId))
+            .body("type", is(CassandraMappingsSolveInconsistenciesTask.TYPE))
+            .body("startedDate", is(notNullValue()))
+            .body("submitDate", is(notNullValue()))
+            .body("completedDate", is(notNullValue()));
+    }
+
+    @Test
+    void postMappingsActionShouldRejectInvalidActions() {
+        given()
+            .queryParam("action", "invalid-action")
+        .when()
+            .post()
+        .then()
+            .statusCode(HttpStatus.BAD_REQUEST_400)
+            .body("statusCode", is(400))
+            .body("type", 
is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType()))
+            .body("message", is("Invalid arguments supplied in the user 
request"))
+            .body("details", is("'invalid-action' is not a valid action query 
parameter"));
+    }
+
+    @Test
+    void postMappingsActionShouldRequireAction() {
+        when()
+            .post()
+        .then()
+            .statusCode(HttpStatus.BAD_REQUEST_400)
+            .body("statusCode", is(400))
+            .body("type", 
is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType()))
+            .body("message", is("Invalid arguments supplied in the user 
request"))
+            .body("details", is("'action' url parameter is mandatory"));
+    }
+
+    @Test
+    void postMappingsActionShouldResolveRRTInconsistencies() {
+        cassandraRecipientRewriteTableDAO.addMapping(SOURCE_1, 
MAPPING).block();
+        cassandraRecipientRewriteTableDAO.addMapping(SOURCE_2, 
MAPPING).block();
+
+        cassandraMappingsSourcesDAO.addMapping(MAPPING, SOURCE_1).block();
+
+        String taskId = with()
+            .queryParam("action", MAPPINGS_ACTION)
+            .post()
+            .jsonPath()
+            .get("taskId");
+
+        given()
+            .basePath(TasksRoutes.BASE)
+        .when()
+            .get(taskId + "/await")
+        .then()
+            .body("status", is("completed"));
+
+        
assertThat(cassandraMappingsSourcesDAO.retrieveSources(MAPPING).collectList().block())
+            .containsOnly(SOURCE_1, SOURCE_2);
+    }
+
+    @Test
+    void postMappingsActionShouldResolveMappingsSourcesInconsistencies() {
+        cassandraRecipientRewriteTableDAO.addMapping(SOURCE_1, 
MAPPING).block();
+
+        cassandraMappingsSourcesDAO.addMapping(MAPPING, SOURCE_1).block();
+        cassandraMappingsSourcesDAO.addMapping(MAPPING, SOURCE_2).block();
+
+        String taskId = with()
+            .queryParam("action", MAPPINGS_ACTION)
+            .post()
+            .jsonPath()
+            .get("taskId");
+
+        given()
+            .basePath(TasksRoutes.BASE)
+        .when()
+            .get(taskId + "/await")
+        .then()
+            .body("status", is("completed"));
+
+        
assertThat(cassandraMappingsSourcesDAO.retrieveSources(MAPPING).collectList().block())
+            .containsOnly(SOURCE_1);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/WebAdminServer.java
----------------------------------------------------------------------
diff --git 
a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/WebAdminServer.java
 
b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/WebAdminServer.java
index 595db70..1080cf3 100644
--- 
a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/WebAdminServer.java
+++ 
b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/WebAdminServer.java
@@ -154,6 +154,17 @@ public class WebAdminServer implements Configurable {
                 .cause(ex)
                 .asString());
         });
+
+        service.exception(IllegalArgumentException.class, (ex, req, res) -> {
+            LOGGER.info("Invalid arguments supplied in the user request", ex);
+            res.status(BAD_REQUEST_400);
+            res.body(ErrorResponder.builder()
+                .statusCode(BAD_REQUEST_400)
+                .type(INVALID_ARGUMENT)
+                .message("Invalid arguments supplied in the user request")
+                .cause(ex)
+                .asString());
+        });
     }
 
     @PreDestroy

http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/ErrorRoutes.java
----------------------------------------------------------------------
diff --git 
a/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/ErrorRoutes.java
 
b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/ErrorRoutes.java
index a2563b9..35876e5 100644
--- 
a/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/ErrorRoutes.java
+++ 
b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/ErrorRoutes.java
@@ -26,14 +26,16 @@ import spark.Service;
 
 public class ErrorRoutes implements Routes {
 
-    public static final String BASE_URL = "/errors/";
-    public static final String INTERNAL_SERVER_ERROR = "internalServerError";
-    public static final String JSON_EXTRACT_EXCEPTION = "jsonExtractException";
+    static final String BASE_URL = "/errors/";
+    static final String INTERNAL_SERVER_ERROR = "internalServerError";
+    static final String JSON_EXTRACT_EXCEPTION = "jsonExtractException";
+    static final String INVALID_ARGUMENT_EXCEPTION = 
"illegalArgumentException";
 
     @Override
     public void define(Service service) {
         defineInternalError(service);
         defineJsonExtractException(service);
+        defineIllegalArgumentException(service);
     }
 
     @Override
@@ -52,4 +54,11 @@ public class ErrorRoutes implements Routes {
         service.get(BASE_URL + JSON_EXTRACT_EXCEPTION,
             (req, res) -> new JsonExtractor<>(Long.class).parse("a non valid 
JSON"));
     }
+
+    private void defineIllegalArgumentException(Service service) {
+        service.get(BASE_URL + INVALID_ARGUMENT_EXCEPTION,
+            (req, res) -> {
+                throw new IllegalArgumentException("Argument is non valid");
+            });
+    }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/ErrorRoutesTest.java
----------------------------------------------------------------------
diff --git 
a/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/ErrorRoutesTest.java
 
b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/ErrorRoutesTest.java
index f8896f9..bc7918c 100644
--- 
a/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/ErrorRoutesTest.java
+++ 
b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/routes/ErrorRoutesTest.java
@@ -22,6 +22,7 @@ package org.apache.james.webadmin.routes;
 import static io.restassured.RestAssured.when;
 import static org.apache.james.webadmin.WebAdminServer.NO_CONFIGURATION;
 import static 
org.apache.james.webadmin.routes.ErrorRoutes.INTERNAL_SERVER_ERROR;
+import static 
org.apache.james.webadmin.routes.ErrorRoutes.INVALID_ARGUMENT_EXCEPTION;
 import static 
org.apache.james.webadmin.routes.ErrorRoutes.JSON_EXTRACT_EXCEPTION;
 import static 
org.apache.james.webadmin.utils.ErrorResponder.ErrorType.INVALID_ARGUMENT;
 import static 
org.apache.james.webadmin.utils.ErrorResponder.ErrorType.SERVER_ERROR;
@@ -88,7 +89,7 @@ public class ErrorRoutesTest {
     }
 
     @Test
-    public void defineJsonExtractExceptionShouldReturnBadRequestJsonFormat() 
throws InterruptedException {
+    public void defineJsonExtractExceptionShouldReturnBadRequestJsonFormat() {
         when()
             .get(JSON_EXTRACT_EXCEPTION)
         .then()
@@ -98,4 +99,16 @@ public class ErrorRoutesTest {
             .body("message", equalTo("JSON payload of the request is not 
valid"))
             .body("details", containsString("Unrecognized token 'a': was 
expecting ('true', 'false' or 'null')"));
     }
+
+    @Test
+    public void defineIllegalExceptionShouldReturnBadRequestJsonFormat() {
+        when()
+            .get(INVALID_ARGUMENT_EXCEPTION)
+        .then()
+            .statusCode(BAD_REQUEST_400)
+            .body("statusCode", equalTo(BAD_REQUEST_400))
+            .body("type", equalTo(INVALID_ARGUMENT.getType()))
+            .body("message", equalTo("Invalid arguments supplied in the user 
request"))
+            .body("details", containsString("Argument is non valid"));
+    }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/SieveScriptRoutesTest.java
----------------------------------------------------------------------
diff --git 
a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/SieveScriptRoutesTest.java
 
b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/SieveScriptRoutesTest.java
index 5c3fb28..25f26ad 100644
--- 
a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/SieveScriptRoutesTest.java
+++ 
b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/SieveScriptRoutesTest.java
@@ -166,14 +166,14 @@ public class SieveScriptRoutesTest {
     }
 
     @Test
-    public void 
defineAddActiveSieveScriptShouldReturnInternalErrorWhenScriptIsNotSet() {
+    public void 
defineAddActiveSieveScriptShouldReturnBadRequestWhenScriptIsNotSet() {
         given()
             .pathParam("userName", "userA")
             .pathParam("scriptName", "scriptA")
         .when()
             .put("sieve/{userName}/scripts/{scriptName}")
         .then()
-            .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500);
+            .statusCode(HttpStatus.BAD_REQUEST_400);
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/james-project/blob/dbb95982/src/site/markdown/server/manage-webadmin.md
----------------------------------------------------------------------
diff --git a/src/site/markdown/server/manage-webadmin.md 
b/src/site/markdown/server/manage-webadmin.md
index 65d28d6..6ee80a8 100644
--- a/src/site/markdown/server/manage-webadmin.md
+++ b/src/site/markdown/server/manage-webadmin.md
@@ -44,6 +44,7 @@ as exposed above). To avoid information duplication, this is 
ommited on endpoint
  - [Administrating Sieve quotas](#Administrating_Sieve_quotas)
  - [ReIndexing](#ReIndexing)
  - [Task management](#Task_management)
+ - [Cassandra extra operations](#Cassandra_extra_operations)
 
 ## HealthCheck
 
@@ -2515,3 +2516,36 @@ Response codes:
 
  - 200: A list of corresponding tasks is returned
  - 400: Invalid status value
+
+## Cassandra extra operations
+
+Some webadmin features to manage some extra operations on Cassandra tables, 
like solving inconsistencies on projection tables.
+Such inconsistencies can be for example created by a fail of the DAO to add a 
mapping into 'mappings_sources`, while it was successful
+regarding the `rrt` table.
+
+ - [Operations on mappings sources](#Operations_on_mappings_sources)
+
+### Operations on mappings sources
+
+You can do a series of action on `mappings_sources` projection table :
+
+```
+curl -XPOST /cassandra/mappings?action=[ACTION]
+```
+
+Will return the taskId corresponding to the related task. Actions supported so 
far are :
+
+ - SolveInconsistencies : cleans up first all the mappings in 
`mappings_sources` index and then repopulate it correctly. In the meantime,
+listing sources of a mapping might create temporary inconsistencies during the 
process.
+
+For example :
+
+```
+curl -XPOST /cassandra/mappings?action=SolveInconsistencies
+```
+
+Response codes :
+
+ - 201: the taskId of the created task
+ - 400: Invalid action argument for performing operation on mappings data
+


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to