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 ro...@apache.org on 2018/12/19 08:24:38 UTC

[1/8] james-project git commit: JAMES-2618 Write SchemaVersion after Types and Tables creation

Repository: james-project
Updated Branches:
  refs/heads/master c739d3aec -> 3b223d537


JAMES-2618 Write SchemaVersion after Types and Tables creation


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

Branch: refs/heads/master
Commit: d796a1d3c103683c2096313805745c78f6d5ef64
Parents: c739d3a
Author: Gautier DI FOLCO <gd...@linagora.com>
Authored: Tue Dec 4 18:46:46 2018 +0100
Committer: Raphael Ouazana <ra...@linagora.com>
Committed: Wed Dec 19 09:23:43 2018 +0100

----------------------------------------------------------------------
 backends-common/cassandra/pom.xml               |   5 +
 .../cassandra/components/CassandraTable.java    |  55 ++++++-
 .../cassandra/components/CassandraType.java     |  50 +++++-
 .../cassandra/init/CassandraTableManager.java   |  15 +-
 .../cassandra/init/CassandraTypesCreator.java   |  14 +-
 .../SessionWithInitializedTablesFactory.java    |  29 +++-
 .../components/CassandraTableTest.java          | 103 ++++++++++++
 .../cassandra/components/CassandraTypeTest.java | 104 +++++++++++++
 .../init/CassandraTableManagerTest.java         | 104 +++++++++++++
 .../init/CassandraTypeProviderTest.java         |   5 +-
 .../init/CassandraTypesCreatorTest.java         | 112 +++++++++++++
 ...SessionWithInitializedTablesFactoryTest.java | 156 +++++++++++++++++++
 .../cassandra/mail/MailboxAggregateModule.java  |   2 +
 .../CassandraVacationRepositoryTest.java        |   6 +-
 .../cassandra/CassandraMailRepositoryTest.java  |   2 +
 ...abbitMQMailQueueConfigurationChangeTest.java |   2 +
 16 files changed, 741 insertions(+), 23 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/d796a1d3/backends-common/cassandra/pom.xml
----------------------------------------------------------------------
diff --git a/backends-common/cassandra/pom.xml b/backends-common/cassandra/pom.xml
index 6b35ab1..bcac1fc 100644
--- a/backends-common/cassandra/pom.xml
+++ b/backends-common/cassandra/pom.xml
@@ -122,6 +122,11 @@
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-params</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>org.junit.platform</groupId>
             <artifactId>junit-platform-launcher</artifactId>
             <scope>test</scope>

http://git-wip-us.apache.org/repos/asf/james-project/blob/d796a1d3/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/components/CassandraTable.java
----------------------------------------------------------------------
diff --git a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/components/CassandraTable.java b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/components/CassandraTable.java
index cb9100a..ed2d53b 100644
--- a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/components/CassandraTable.java
+++ b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/components/CassandraTable.java
@@ -19,9 +19,27 @@
 
 package org.apache.james.backends.cassandra.components;
 
+import java.util.Objects;
+
+import com.datastax.driver.core.KeyspaceMetadata;
+import com.datastax.driver.core.Session;
 import com.datastax.driver.core.Statement;
+import com.google.common.base.MoreObjects;
 
 public class CassandraTable {
+    public enum InitializationStatus {
+        ALREADY_DONE,
+        PARTIAL,
+        FULL;
+
+        public InitializationStatus reduce(InitializationStatus other) {
+            if (this == other) {
+                return this;
+            }
+
+            return PARTIAL;
+        }
+    }
 
     private final Statement createStatement;
     private final String name;
@@ -31,11 +49,40 @@ public class CassandraTable {
         this.name = name;
     }
 
-    public Statement getCreateStatement() {
-        return createStatement;
-    }
-
     public String getName() {
         return name;
     }
+
+    public InitializationStatus initialize(KeyspaceMetadata keyspaceMetadata, Session session) {
+        if (keyspaceMetadata.getTable(name) != null) {
+            return InitializationStatus.ALREADY_DONE;
+        }
+
+        session.execute(createStatement);
+        return InitializationStatus.FULL;
+    }
+
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof CassandraTable) {
+            CassandraTable that = (CassandraTable) o;
+
+            return Objects.equals(this.name, that.name)
+                    && Objects.equals(this.createStatement, that.createStatement);
+        }
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(name, createStatement);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("name", name)
+                .add("createStatement", createStatement)
+                .toString();
+    }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/d796a1d3/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/components/CassandraType.java
----------------------------------------------------------------------
diff --git a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/components/CassandraType.java b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/components/CassandraType.java
index 2b41d72..082eff2 100644
--- a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/components/CassandraType.java
+++ b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/components/CassandraType.java
@@ -19,9 +19,27 @@
 
 package org.apache.james.backends.cassandra.components;
 
+import java.util.Objects;
+
+import com.datastax.driver.core.KeyspaceMetadata;
+import com.datastax.driver.core.Session;
 import com.datastax.driver.core.schemabuilder.CreateType;
+import com.google.common.base.MoreObjects;
 
 public class CassandraType {
+    public enum InitializationStatus {
+        ALREADY_DONE,
+        PARTIAL,
+        FULL;
+
+         public InitializationStatus reduce(InitializationStatus other) {
+             if (this == other) {
+                 return this;
+             }
+
+             return PARTIAL;
+        }
+    }
 
     private final String name;
     private final CreateType createStatement;
@@ -35,8 +53,36 @@ public class CassandraType {
         return name;
     }
 
-    public CreateType getCreateStatement() {
-        return createStatement;
+    public InitializationStatus initialize(KeyspaceMetadata keyspaceMetadata, Session session) {
+        if (keyspaceMetadata.getUserType(name) != null) {
+            return InitializationStatus.ALREADY_DONE;
+        }
+
+        session.execute(createStatement);
+        return InitializationStatus.FULL;
     }
 
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof CassandraType) {
+            CassandraType that = (CassandraType) o;
+
+            return Objects.equals(this.name, that.name)
+                    && Objects.equals(this.createStatement, that.createStatement);
+        }
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(name, createStatement);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("name", name)
+                .add("createStatement", createStatement)
+                .toString();
+    }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/d796a1d3/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/CassandraTableManager.java
----------------------------------------------------------------------
diff --git a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/CassandraTableManager.java b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/CassandraTableManager.java
index f406b5c..3317138 100644
--- a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/CassandraTableManager.java
+++ b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/CassandraTableManager.java
@@ -23,6 +23,7 @@ import javax.inject.Inject;
 
 import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.backends.cassandra.components.CassandraTable;
+import org.apache.james.backends.cassandra.components.CassandraTable.InitializationStatus;
 import org.apache.james.backends.cassandra.utils.CassandraAsyncExecutor;
 
 import com.datastax.driver.core.KeyspaceMetadata;
@@ -43,17 +44,16 @@ public class CassandraTableManager {
         this.module = module;
     }
 
-    public CassandraTableManager ensureAllTables() {
+    public InitializationStatus initializeTables() {
         KeyspaceMetadata keyspaceMetadata = session.getCluster()
             .getMetadata()
             .getKeyspace(session.getLoggedKeyspace());
 
-        module.moduleTables()
-            .stream()
-            .filter(table -> keyspaceMetadata.getTable(table.getName()) == null)
-            .forEach(table -> session.execute(table.getCreateStatement()));
-
-        return this;
+        return module.moduleTables()
+                .stream()
+                .map(table -> table.initialize(keyspaceMetadata, session))
+                .reduce((left, right) -> left.reduce(right))
+                .orElse(InitializationStatus.ALREADY_DONE);
     }
 
     public void clearAllTables() {
@@ -75,5 +75,4 @@ public class CassandraTableManager {
                 .filter(resultSet -> !resultSet.isExhausted())
                 .flatMap(ignored -> Mono.fromFuture(executor.execute(QueryBuilder.truncate(name))));
     }
-
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/d796a1d3/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/CassandraTypesCreator.java
----------------------------------------------------------------------
diff --git a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/CassandraTypesCreator.java b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/CassandraTypesCreator.java
index 27179bc..8c9a0aa 100644
--- a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/CassandraTypesCreator.java
+++ b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/CassandraTypesCreator.java
@@ -21,7 +21,9 @@ package org.apache.james.backends.cassandra.init;
 
 import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.backends.cassandra.components.CassandraType;
+import org.apache.james.backends.cassandra.components.CassandraType.InitializationStatus;
 
+import com.datastax.driver.core.KeyspaceMetadata;
 import com.datastax.driver.core.Session;
 import com.google.common.collect.ImmutableList;
 
@@ -34,8 +36,14 @@ public class CassandraTypesCreator {
         this.session = session;
     }
 
-    public void initializeTypes() {
-        types.forEach((type) -> session.execute(type.getCreateStatement()));
-    }
+    public InitializationStatus initializeTypes() {
+        KeyspaceMetadata keyspaceMetadata = session.getCluster()
+                .getMetadata()
+                .getKeyspace(session.getLoggedKeyspace());
 
+        return types.stream()
+                .map(type -> type.initialize(keyspaceMetadata, session))
+                .reduce((left, right) -> left.reduce(right))
+                .orElse(InitializationStatus.ALREADY_DONE);
+    }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/d796a1d3/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/SessionWithInitializedTablesFactory.java
----------------------------------------------------------------------
diff --git a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/SessionWithInitializedTablesFactory.java b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/SessionWithInitializedTablesFactory.java
index d87b41b..b5213d7 100644
--- a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/SessionWithInitializedTablesFactory.java
+++ b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/init/SessionWithInitializedTablesFactory.java
@@ -19,13 +19,19 @@
 
 package org.apache.james.backends.cassandra.init;
 
+import java.util.stream.Stream;
 import javax.annotation.PreDestroy;
 import javax.inject.Inject;
 import javax.inject.Provider;
 import javax.inject.Singleton;
 
 import org.apache.james.backends.cassandra.components.CassandraModule;
+import org.apache.james.backends.cassandra.components.CassandraTable;
+import org.apache.james.backends.cassandra.components.CassandraType;
 import org.apache.james.backends.cassandra.init.configuration.ClusterConfiguration;
+import org.apache.james.backends.cassandra.utils.CassandraUtils;
+import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionDAO;
+import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionManager;
 
 import com.datastax.driver.core.Cluster;
 import com.datastax.driver.core.Session;
@@ -44,10 +50,10 @@ public class SessionWithInitializedTablesFactory implements Provider<Session> {
     private Session createSession(Cluster cluster, String keyspace) {
         Session session = cluster.connect(keyspace);
         try {
-            new CassandraTypesCreator(module, session)
-                .initializeTypes();
-            new CassandraTableManager(module, session)
-                .ensureAllTables();
+            if (allOperationsAreFullyPerformed(session)) {
+                new CassandraSchemaVersionDAO(session, CassandraUtils.WITH_DEFAULT_CONFIGURATION)
+                        .updateVersion(CassandraSchemaVersionManager.MAX_VERSION);
+            }
             return session;
         } catch (Exception e) {
             session.close();
@@ -55,6 +61,21 @@ public class SessionWithInitializedTablesFactory implements Provider<Session> {
         }
     }
 
+    private boolean allOperationsAreFullyPerformed(Session session) {
+        Stream<Boolean> operations = Stream.of(createTypes(session), createTables(session));
+        return operations.allMatch(updated -> updated);
+    }
+
+    private boolean createTypes(Session session) {
+        return new CassandraTypesCreator(module, session)
+                .initializeTypes() == CassandraType.InitializationStatus.FULL;
+    }
+
+    private boolean createTables(Session session) {
+        return new CassandraTableManager(module, session)
+            .initializeTables() == CassandraTable.InitializationStatus.FULL;
+    }
+
     @Override
     public Session get() {
         return session;

http://git-wip-us.apache.org/repos/asf/james-project/blob/d796a1d3/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/components/CassandraTableTest.java
----------------------------------------------------------------------
diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/components/CassandraTableTest.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/components/CassandraTableTest.java
new file mode 100644
index 0000000..a0d264f
--- /dev/null
+++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/components/CassandraTableTest.java
@@ -0,0 +1,103 @@
+/****************************************************************
+ * 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.backends.cassandra.components;
+
+import static org.apache.james.backends.cassandra.components.CassandraTable.InitializationStatus.ALREADY_DONE;
+import static org.apache.james.backends.cassandra.components.CassandraTable.InitializationStatus.FULL;
+import static org.apache.james.backends.cassandra.components.CassandraTable.InitializationStatus.PARTIAL;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.stream.Stream;
+
+import org.apache.james.backends.cassandra.components.CassandraTable.InitializationStatus;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import com.datastax.driver.core.KeyspaceMetadata;
+import com.datastax.driver.core.Session;
+import com.datastax.driver.core.Statement;
+import com.datastax.driver.core.TableMetadata;
+import com.datastax.driver.core.schemabuilder.SchemaBuilder;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+class CassandraTableTest {
+    private static final String NAME = "tableName";
+    private static final Statement STATEMENT = SchemaBuilder.createTable(NAME);
+    private static final CassandraTable TABLE = new CassandraTable(NAME, STATEMENT);
+
+    @Test
+    void shouldRespectBeanContract() {
+        EqualsVerifier.forClass(CassandraTable.class)
+                .verify();
+    }
+
+    @Test
+    void initializeShouldExecuteCreateStatementAndReturnFullWhenTableDoesNotExist() {
+        KeyspaceMetadata keyspace = mock(KeyspaceMetadata.class);
+        when(keyspace.getTable(NAME)).thenReturn(null);
+        Session session = mock(Session.class);
+
+        assertThat(TABLE.initialize(keyspace, session))
+                .isEqualByComparingTo(FULL);
+
+        verify(keyspace).getTable(NAME);
+        verify(session).execute(STATEMENT);
+    }
+
+    @Test
+    void initializeShouldExecuteReturnAlreadyDoneWhenTableExists() {
+        KeyspaceMetadata keyspace = mock(KeyspaceMetadata.class);
+        when(keyspace.getTable(NAME)).thenReturn(mock(TableMetadata.class));
+        Session session = mock(Session.class);
+
+        assertThat(TABLE.initialize(keyspace, session))
+                .isEqualByComparingTo(ALREADY_DONE);
+
+        verify(keyspace).getTable(NAME);
+        verify(session, never()).execute(STATEMENT);
+    }
+
+    @ParameterizedTest
+    @MethodSource
+    void initializationStatusReduceShouldFallIntoTheRightState(InitializationStatus left, InitializationStatus right, InitializationStatus expectedResult) {
+        assertThat(left.reduce(right)).isEqualByComparingTo(expectedResult);
+    }
+
+    private static Stream<Arguments> initializationStatusReduceShouldFallIntoTheRightState() {
+        return Stream.of(
+                Arguments.of(ALREADY_DONE, ALREADY_DONE, ALREADY_DONE),
+                Arguments.of(ALREADY_DONE, PARTIAL, PARTIAL),
+                Arguments.of(ALREADY_DONE, FULL, PARTIAL),
+                Arguments.of(PARTIAL, PARTIAL, PARTIAL),
+                Arguments.of(PARTIAL, PARTIAL, PARTIAL),
+                Arguments.of(PARTIAL, FULL, PARTIAL),
+                Arguments.of(FULL, ALREADY_DONE, PARTIAL),
+                Arguments.of(FULL, PARTIAL, PARTIAL),
+                Arguments.of(FULL, FULL, FULL)
+        );
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/d796a1d3/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/components/CassandraTypeTest.java
----------------------------------------------------------------------
diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/components/CassandraTypeTest.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/components/CassandraTypeTest.java
new file mode 100644
index 0000000..7973c83
--- /dev/null
+++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/components/CassandraTypeTest.java
@@ -0,0 +1,104 @@
+/****************************************************************
+ * 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.backends.cassandra.components;
+
+import static org.apache.james.backends.cassandra.components.CassandraType.InitializationStatus.ALREADY_DONE;
+import static org.apache.james.backends.cassandra.components.CassandraType.InitializationStatus.FULL;
+import static org.apache.james.backends.cassandra.components.CassandraType.InitializationStatus.PARTIAL;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.stream.Stream;
+
+import org.apache.james.backends.cassandra.components.CassandraType.InitializationStatus;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import com.datastax.driver.core.KeyspaceMetadata;
+import com.datastax.driver.core.Session;
+import com.datastax.driver.core.UserType;
+import com.datastax.driver.core.schemabuilder.CreateType;
+import com.datastax.driver.core.schemabuilder.SchemaBuilder;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+class CassandraTypeTest {
+    private static final String NAME = "typeName";
+    private static final CreateType STATEMENT = SchemaBuilder.createType(NAME);
+    private static final CassandraType TYPE = new CassandraType(NAME, STATEMENT);
+
+    @Test
+    void shouldRespectBeanContract() {
+        EqualsVerifier.forClass(CassandraType.class)
+                .withPrefabValues(CreateType.class, SchemaBuilder.createType("name1"), SchemaBuilder.createType("name2"))
+                .verify();
+    }
+
+    @Test
+    void initializeShouldExecuteCreateStatementAndReturnFullWhenTypeDoesNotExist() {
+        KeyspaceMetadata keyspace = mock(KeyspaceMetadata.class);
+        when(keyspace.getUserType(NAME)).thenReturn(null);
+        Session session = mock(Session.class);
+
+        assertThat(TYPE.initialize(keyspace, session))
+                .isEqualByComparingTo(FULL);
+
+        verify(keyspace).getUserType(NAME);
+        verify(session).execute(STATEMENT);
+    }
+
+    @Test
+    void initializeShouldReturnAlreadyDoneWhenTypeExists() {
+        KeyspaceMetadata keyspace = mock(KeyspaceMetadata.class);
+        when(keyspace.getUserType(NAME)).thenReturn(mock(UserType.class));
+        Session session = mock(Session.class);
+
+        assertThat(TYPE.initialize(keyspace, session))
+                .isEqualByComparingTo(ALREADY_DONE);
+
+        verify(keyspace).getUserType(NAME);
+        verify(session, never()).execute(STATEMENT);
+    }
+
+    @ParameterizedTest
+    @MethodSource
+    void initializationStatusReduceShouldFallIntoTheRightState(InitializationStatus left, InitializationStatus right, InitializationStatus expectedResult) {
+        assertThat(left.reduce(right)).isEqualByComparingTo(expectedResult);
+    }
+
+    static Stream<Arguments> initializationStatusReduceShouldFallIntoTheRightState() {
+        return Stream.of(
+                Arguments.of(ALREADY_DONE, ALREADY_DONE, ALREADY_DONE),
+                Arguments.of(ALREADY_DONE, PARTIAL, PARTIAL),
+                Arguments.of(ALREADY_DONE, FULL, PARTIAL),
+                Arguments.of(PARTIAL, PARTIAL, PARTIAL),
+                Arguments.of(PARTIAL, PARTIAL, PARTIAL),
+                Arguments.of(PARTIAL, FULL, PARTIAL),
+                Arguments.of(FULL, ALREADY_DONE, PARTIAL),
+                Arguments.of(FULL, PARTIAL, PARTIAL),
+                Arguments.of(FULL, FULL, FULL)
+        );
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/d796a1d3/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/CassandraTableManagerTest.java
----------------------------------------------------------------------
diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/CassandraTableManagerTest.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/CassandraTableManagerTest.java
new file mode 100644
index 0000000..2b5206b
--- /dev/null
+++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/CassandraTableManagerTest.java
@@ -0,0 +1,104 @@
+/****************************************************************
+ * 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.backends.cassandra.init;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+import org.apache.james.backends.cassandra.CassandraCluster;
+import org.apache.james.backends.cassandra.CassandraClusterExtension;
+import org.apache.james.backends.cassandra.components.CassandraModule;
+import org.apache.james.backends.cassandra.components.CassandraTable;
+import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
+import org.apache.james.backends.cassandra.versions.table.CassandraSchemaVersionTable;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.datastax.driver.core.DataType;
+import com.datastax.driver.core.querybuilder.QueryBuilder;
+import com.datastax.driver.core.schemabuilder.SchemaBuilder;
+
+class CassandraTableManagerTest {
+    private static final String TABLE_NAME = "tablename";
+
+    public static final CassandraModule MODULE = CassandraModule.aggregateModules(
+            CassandraSchemaVersionModule.MODULE,
+            CassandraModule.table(TABLE_NAME)
+                .comment("Testing table")
+                .statement(statement -> statement
+                        .addPartitionKey("id", DataType.timeuuid())
+                        .addClusteringColumn("clustering", DataType.bigint()))
+                .build());
+
+    @RegisterExtension
+    static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(MODULE);
+
+    private CassandraCluster cassandra;
+
+    @BeforeEach
+    void setUp(CassandraCluster cassandra) {
+        this.cassandra = cassandra;
+    }
+
+    @Test
+    void describeShouldNotReturnNullNorFailWhenTableIsDefined() {
+        ensureTableExistence(TABLE_NAME);
+    }
+
+    @Test
+    void initializeTableShouldCreateAllTheTables() {
+        cassandra.getConf().execute(SchemaBuilder.dropTable(TABLE_NAME));
+        cassandra.getConf().execute(SchemaBuilder.dropTable(CassandraSchemaVersionTable.TABLE_NAME));
+
+        assertThat(new CassandraTableManager(MODULE, cassandra.getConf()).initializeTables())
+                .isEqualByComparingTo(CassandraTable.InitializationStatus.FULL);
+
+        ensureTableExistence(TABLE_NAME);
+    }
+
+    @Test
+    void initializeTableShouldCreateAllTheMissingTable() {
+        cassandra.getConf().execute(SchemaBuilder.dropTable(TABLE_NAME));
+
+        assertThat(new CassandraTableManager(MODULE, cassandra.getConf()).initializeTables())
+                .isEqualByComparingTo(CassandraTable.InitializationStatus.PARTIAL);
+
+        ensureTableExistence(TABLE_NAME);
+    }
+
+    @Test
+    void initializeTableShouldNotPerformIfCalledASecondTime() {
+        assertThat(new CassandraTableManager(MODULE, cassandra.getConf()).initializeTables())
+                .isEqualByComparingTo(CassandraTable.InitializationStatus.ALREADY_DONE);
+    }
+
+    @Test
+    void initializeTableShouldNotFailIfCalledASecondTime() {
+        new CassandraTableManager(MODULE, cassandra.getConf()).initializeTables();
+
+        ensureTableExistence(TABLE_NAME);
+    }
+
+    private void ensureTableExistence(String tableName) {
+        assertThatCode(() -> cassandra.getConf().execute(QueryBuilder.select().from(tableName).limit(1)))
+            .doesNotThrowAnyException();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/d796a1d3/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/CassandraTypeProviderTest.java
----------------------------------------------------------------------
diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/CassandraTypeProviderTest.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/CassandraTypeProviderTest.java
index 1bf149c..e2e6f21 100644
--- a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/CassandraTypeProviderTest.java
+++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/CassandraTypeProviderTest.java
@@ -25,6 +25,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 import org.apache.james.backends.cassandra.CassandraCluster;
 import org.apache.james.backends.cassandra.CassandraClusterExtension;
 import org.apache.james.backends.cassandra.components.CassandraModule;
+import org.apache.james.backends.cassandra.components.CassandraType;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
@@ -59,7 +60,9 @@ class CassandraTypeProviderTest {
     void initializeTypesShouldCreateTheTypes() {
         cassandra.getConf().execute(SchemaBuilder.dropType(TYPE_NAME));
 
-        new CassandraTypesCreator(MODULE, cassandra.getConf()).initializeTypes();
+        assertThat(new CassandraTypesCreator(MODULE, cassandra.getConf()).initializeTypes())
+            .isEqualByComparingTo(CassandraType.InitializationStatus.FULL);
+
         CassandraTypesProvider cassandraTypesProviderTest = new CassandraTypesProvider(MODULE, cassandra.getConf());
         assertThat(cassandraTypesProviderTest.getDefinedUserType(TYPE_NAME))
             .isNotNull();

http://git-wip-us.apache.org/repos/asf/james-project/blob/d796a1d3/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/CassandraTypesCreatorTest.java
----------------------------------------------------------------------
diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/CassandraTypesCreatorTest.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/CassandraTypesCreatorTest.java
new file mode 100644
index 0000000..9db2f8c
--- /dev/null
+++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/CassandraTypesCreatorTest.java
@@ -0,0 +1,112 @@
+/****************************************************************
+ * 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.backends.cassandra.init;
+
+import static com.datastax.driver.core.DataType.text;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.james.backends.cassandra.CassandraCluster;
+import org.apache.james.backends.cassandra.CassandraClusterExtension;
+import org.apache.james.backends.cassandra.components.CassandraModule;
+import org.apache.james.backends.cassandra.components.CassandraType;
+import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.datastax.driver.core.schemabuilder.SchemaBuilder;
+
+class CassandraTypesCreatorTest {
+    private static final String TYPE_NAME_1 = "typename1";
+    private static final String PROPERTY_1 = "property1";
+    private static final String TYPE_NAME_2 = "typename2";
+    private static final String PROPERTY_2 = "property2";
+
+    public static final CassandraModule MODULE = CassandraModule.aggregateModules(
+            CassandraSchemaVersionModule.MODULE,
+            CassandraModule.type(TYPE_NAME_1)
+                .statement(statement -> statement.addColumn(PROPERTY_1, text()))
+                .build(),
+            CassandraModule.type(TYPE_NAME_2)
+                .statement(statement -> statement.addColumn(PROPERTY_2, text()))
+                .build());
+
+    @RegisterExtension
+    static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(MODULE);
+
+    private CassandraCluster cassandra;
+
+    @BeforeEach
+    void setUp(CassandraCluster cassandra) {
+        this.cassandra = cassandra;
+        cassandra.getTypesProvider();
+    }
+
+    @Test
+    void getDefinedUserTypeShouldNotReturnNullNorFailWhenTypeIsDefined() {
+        assertThat(cassandra.getTypesProvider().getDefinedUserType(TYPE_NAME_1))
+                .isNotNull();
+        assertThat(cassandra.getTypesProvider().getDefinedUserType(TYPE_NAME_2))
+                .isNotNull();
+    }
+
+    @Test
+    void initializeTypesShouldCreateTheAllTypes() {
+        cassandra.getConf().execute(SchemaBuilder.dropType(TYPE_NAME_1));
+        cassandra.getConf().execute(SchemaBuilder.dropType(TYPE_NAME_2));
+
+        assertThat(new CassandraTypesCreator(MODULE, cassandra.getConf()).initializeTypes())
+                .isEqualByComparingTo(CassandraType.InitializationStatus.FULL);
+
+        CassandraTypesProvider cassandraTypesProviderTest = new CassandraTypesProvider(MODULE, cassandra.getConf());
+        assertThat(cassandraTypesProviderTest.getDefinedUserType(TYPE_NAME_1))
+                .isNotNull();
+        assertThat(cassandraTypesProviderTest.getDefinedUserType(TYPE_NAME_2))
+                .isNotNull();
+    }
+
+    @Test
+    void initializeTypesShouldCreateTheMissingType() {
+        cassandra.getConf().execute(SchemaBuilder.dropType(TYPE_NAME_1));
+
+        assertThat(new CassandraTypesCreator(MODULE, cassandra.getConf()).initializeTypes())
+                .isEqualByComparingTo(CassandraType.InitializationStatus.PARTIAL);
+
+        CassandraTypesProvider cassandraTypesProviderTest = new CassandraTypesProvider(MODULE, cassandra.getConf());
+        assertThat(cassandraTypesProviderTest.getDefinedUserType(TYPE_NAME_1))
+                .isNotNull();
+    }
+
+    @Test
+    void initializeTypesShouldNotPerformIfCalledASecondTime() {
+        assertThat(new CassandraTypesCreator(MODULE, cassandra.getConf()).initializeTypes())
+                .isEqualByComparingTo(CassandraType.InitializationStatus.ALREADY_DONE);
+    }
+
+    @Test
+    void initializeTypesShouldNotFailIfCalledASecondTime() {
+        new CassandraTypesCreator(MODULE, cassandra.getConf()).initializeTypes();
+
+        assertThat(cassandra.getTypesProvider().getDefinedUserType(TYPE_NAME_1))
+                .isNotNull();
+        assertThat(cassandra.getTypesProvider().getDefinedUserType(TYPE_NAME_2))
+                .isNotNull();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/d796a1d3/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/SessionWithInitializedTablesFactoryTest.java
----------------------------------------------------------------------
diff --git a/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/SessionWithInitializedTablesFactoryTest.java b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/SessionWithInitializedTablesFactoryTest.java
new file mode 100644
index 0000000..631a027
--- /dev/null
+++ b/backends-common/cassandra/src/test/java/org/apache/james/backends/cassandra/init/SessionWithInitializedTablesFactoryTest.java
@@ -0,0 +1,156 @@
+/****************************************************************
+ * 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.backends.cassandra.init;
+
+import static com.datastax.driver.core.DataType.text;
+import static org.apache.james.backends.cassandra.CassandraCluster.KEYSPACE;
+import static org.apache.james.backends.cassandra.versions.CassandraSchemaVersionManager.MAX_VERSION;
+import static org.apache.james.backends.cassandra.versions.CassandraSchemaVersionManager.MIN_VERSION;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.function.Supplier;
+
+import org.apache.james.backends.cassandra.DockerCassandraExtension;
+import org.apache.james.backends.cassandra.components.CassandraModule;
+import org.apache.james.backends.cassandra.init.configuration.ClusterConfiguration;
+import org.apache.james.backends.cassandra.utils.CassandraUtils;
+import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionDAO;
+import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionManager;
+import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
+import org.apache.james.util.Host;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import com.datastax.driver.core.Cluster;
+import com.datastax.driver.core.DataType;
+import com.datastax.driver.core.Session;
+import com.datastax.driver.core.schemabuilder.SchemaBuilder;
+
+@ExtendWith(DockerCassandraExtension.class)
+class SessionWithInitializedTablesFactoryTest {
+    private static final String TABLE_NAME = "tablename";
+    private static final String TYPE_NAME = "typename";
+    private static final String PROPERTY = "property";
+
+    public static final CassandraModule MODULE = CassandraModule.aggregateModules(
+            CassandraSchemaVersionModule.MODULE,
+            CassandraModule.table(TABLE_NAME)
+                    .comment("Testing table")
+                    .statement(statement -> statement
+                            .addPartitionKey("id", DataType.timeuuid())
+                            .addClusteringColumn("clustering", DataType.bigint()))
+                    .build(),
+            CassandraModule.type(TYPE_NAME)
+                    .statement(statement -> statement.addColumn(PROPERTY, text()))
+                    .build());
+
+    private Supplier<Session> testee;
+
+    @BeforeEach
+    void setUp(DockerCassandraExtension.DockerCassandra cassandraServer) {
+        this.testee = createSession(cassandraServer);
+    }
+
+    @AfterEach
+    void tearDown() {
+        cleanCassandra(testee.get());
+    }
+
+    @BeforeAll
+    @AfterAll
+    static void stabilizeCassandra(DockerCassandraExtension.DockerCassandra cassandraServer) {
+        cleanCassandra(createSession(cassandraServer).get());
+    }
+
+    @Test
+    void createSessionShouldSetTheLatestSchemaVersionWhenCreatingTypesAndTables() {
+        assertThat(versionManager(testee.get()).computeVersion())
+                .isEqualTo(MAX_VERSION);
+    }
+
+    @Test
+    void createSessionShouldKeepTheSetSchemaVersionWhenTypesAndTablesHaveNotChanged() {
+        Session session = testee.get();
+        assertThat(versionManager(session).computeVersion())
+                .isEqualTo(MAX_VERSION);
+
+        new CassandraTableManager(MODULE, session).clearAllTables();
+        versionManagerDAO(session).updateVersion(MIN_VERSION);
+        assertThat(versionManager(session).computeVersion())
+                .isEqualTo(MIN_VERSION);
+
+        assertThat(versionManager(testee.get()).computeVersion())
+                .isEqualTo(MIN_VERSION);
+    }
+
+    @Test
+    void createSessionShouldKeepTheSetSchemaVersionWhenTypesAndTablesHavePartiallyChanged() {
+        Session session = testee.get();
+        assertThat(versionManager(session).computeVersion())
+                .isEqualTo(MAX_VERSION);
+
+        new CassandraTableManager(MODULE, session).clearAllTables();
+        versionManagerDAO(session).updateVersion(MIN_VERSION);
+        assertThat(versionManager(session).computeVersion())
+                .isEqualTo(MIN_VERSION);
+        session.execute(SchemaBuilder.dropTable(TABLE_NAME));
+        session.execute(SchemaBuilder.dropType(TYPE_NAME));
+
+        assertThat(versionManager(testee.get()).computeVersion())
+                .isEqualTo(MIN_VERSION);
+    }
+
+    private static Supplier<Session> createSession(DockerCassandraExtension.DockerCassandra cassandraServer) {
+        Host host = cassandraServer.getHost();
+        Cluster cluster = ClusterBuilder.builder()
+                .host(host.getHostName())
+                .port(host.getPort())
+                .build();
+        return () -> new SessionWithInitializedTablesFactory(
+                ClusterConfiguration.builder()
+                        .host(host)
+                        .keyspace(KEYSPACE)
+                        .replicationFactor(1)
+                        .build(),
+                ClusterWithKeyspaceCreatedFactory
+                        .config(cluster, KEYSPACE)
+                        .replicationFactor(1)
+                        .disableDurableWrites()
+                        .clusterWithInitializedKeyspace(),
+                MODULE).get();
+    }
+
+    private static void cleanCassandra(Session session) {
+        MODULE.moduleTables().forEach(table -> session.execute(SchemaBuilder.dropTable(table.getName())));
+        MODULE.moduleTypes().forEach(type -> session.execute(SchemaBuilder.dropType(type.getName())));
+    }
+
+    private CassandraSchemaVersionManager versionManager(Session session) {
+        return new CassandraSchemaVersionManager(versionManagerDAO(session));
+    }
+
+    private CassandraSchemaVersionDAO versionManagerDAO(Session session) {
+        return new CassandraSchemaVersionDAO(session, CassandraUtils.WITH_DEFAULT_CONFIGURATION);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/d796a1d3/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/MailboxAggregateModule.java
----------------------------------------------------------------------
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/MailboxAggregateModule.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/MailboxAggregateModule.java
index 841a785..dd1fd57 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/MailboxAggregateModule.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/MailboxAggregateModule.java
@@ -20,6 +20,7 @@
 package org.apache.james.mailbox.cassandra.mail;
 
 import org.apache.james.backends.cassandra.components.CassandraModule;
+import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
 import org.apache.james.blob.cassandra.CassandraBlobModule;
 import org.apache.james.mailbox.cassandra.modules.CassandraAclModule;
 import org.apache.james.mailbox.cassandra.modules.CassandraAnnotationModule;
@@ -38,6 +39,7 @@ import org.apache.james.mailbox.cassandra.modules.CassandraUidModule;
 
 public interface MailboxAggregateModule {
     CassandraModule MODULE = CassandraModule.aggregateModules(
+            CassandraSchemaVersionModule.MODULE,
             CassandraAclModule.MODULE,
             CassandraMailboxModule.MODULE,
             CassandraMessageModule.MODULE,

http://git-wip-us.apache.org/repos/asf/james-project/blob/d796a1d3/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/vacation/CassandraVacationRepositoryTest.java
----------------------------------------------------------------------
diff --git a/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/vacation/CassandraVacationRepositoryTest.java b/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/vacation/CassandraVacationRepositoryTest.java
index 7db419b..caf644e 100644
--- a/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/vacation/CassandraVacationRepositoryTest.java
+++ b/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/vacation/CassandraVacationRepositoryTest.java
@@ -23,6 +23,7 @@ import org.apache.james.backends.cassandra.CassandraCluster;
 import org.apache.james.backends.cassandra.DockerCassandraRule;
 import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.backends.cassandra.init.CassandraZonedDateTimeModule;
+import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
 import org.apache.james.jmap.api.vacation.AbstractVacationRepositoryTest;
 import org.apache.james.jmap.api.vacation.VacationRepository;
 import org.junit.After;
@@ -39,7 +40,10 @@ public class CassandraVacationRepositoryTest extends AbstractVacationRepositoryT
 
     @BeforeClass
     public static void setUpClass() {
-        CassandraModule module = CassandraModule.aggregateModules(CassandraVacationModule.MODULE, CassandraZonedDateTimeModule.MODULE);
+        CassandraModule module = CassandraModule.aggregateModules(
+                CassandraSchemaVersionModule.MODULE,
+                CassandraVacationModule.MODULE,
+                CassandraZonedDateTimeModule.MODULE);
         cassandra = CassandraCluster.create(module, cassandraServer.getHost());
     }
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/d796a1d3/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryTest.java
----------------------------------------------------------------------
diff --git a/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryTest.java b/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryTest.java
index d83677e..b0c059e 100644
--- a/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryTest.java
+++ b/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryTest.java
@@ -23,6 +23,7 @@ import org.apache.james.backends.cassandra.CassandraCluster;
 import org.apache.james.backends.cassandra.CassandraClusterExtension;
 import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.backends.cassandra.utils.CassandraUtils;
+import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
 import org.apache.james.blob.api.HashBlobId;
 import org.apache.james.blob.cassandra.CassandraBlobModule;
 import org.apache.james.blob.cassandra.CassandraBlobsDAO;
@@ -42,6 +43,7 @@ class CassandraMailRepositoryTest implements MailRepositoryContract {
     @RegisterExtension
     static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(
         CassandraModule.aggregateModules(
+            CassandraSchemaVersionModule.MODULE,
             CassandraMailRepositoryModule.MODULE,
             CassandraBlobModule.MODULE));
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/d796a1d3/server/queue/queue-rabbitmq/src/test/java/org/apache/james/queue/rabbitmq/RabbitMQMailQueueConfigurationChangeTest.java
----------------------------------------------------------------------
diff --git a/server/queue/queue-rabbitmq/src/test/java/org/apache/james/queue/rabbitmq/RabbitMQMailQueueConfigurationChangeTest.java b/server/queue/queue-rabbitmq/src/test/java/org/apache/james/queue/rabbitmq/RabbitMQMailQueueConfigurationChangeTest.java
index d0acd5d..4ae8328 100644
--- a/server/queue/queue-rabbitmq/src/test/java/org/apache/james/queue/rabbitmq/RabbitMQMailQueueConfigurationChangeTest.java
+++ b/server/queue/queue-rabbitmq/src/test/java/org/apache/james/queue/rabbitmq/RabbitMQMailQueueConfigurationChangeTest.java
@@ -39,6 +39,7 @@ import org.apache.james.backends.cassandra.CassandraCluster;
 import org.apache.james.backends.cassandra.CassandraClusterExtension;
 import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration;
+import org.apache.james.backends.cassandra.versions.CassandraSchemaVersionModule;
 import org.apache.james.blob.api.HashBlobId;
 import org.apache.james.blob.cassandra.CassandraBlobModule;
 import org.apache.james.blob.cassandra.CassandraBlobsDAO;
@@ -79,6 +80,7 @@ class RabbitMQMailQueueConfigurationChangeTest {
 
     @RegisterExtension
     static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(CassandraModule.aggregateModules(
+        CassandraSchemaVersionModule.MODULE,
         CassandraBlobModule.MODULE,
         CassandraMailQueueViewModule.MODULE,
         CassandraEventStoreModule.MODULE));


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


[3/8] james-project git commit: JAMES-2608 Cassandra doesn't support multiple values in PerRecipientHeaders for a given recipient

Posted by ro...@apache.org.
JAMES-2608 Cassandra doesn't support multiple values in PerRecipientHeaders for a given recipient


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

Branch: refs/heads/master
Commit: d9118f6d7d648121d1ac0e99527f0519562d197b
Parents: d796a1d
Author: Matthieu Baechler <ma...@apache.org>
Authored: Mon Nov 26 16:53:54 2018 +0100
Committer: Raphael Ouazana <ra...@linagora.com>
Committed: Wed Dec 19 09:24:12 2018 +0100

----------------------------------------------------------------------
 .../mailrepository/MailRepositoryContract.java     | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/d9118f6d/server/mailrepository/mailrepository-api/src/test/java/org/apache/james/mailrepository/MailRepositoryContract.java
----------------------------------------------------------------------
diff --git a/server/mailrepository/mailrepository-api/src/test/java/org/apache/james/mailrepository/MailRepositoryContract.java b/server/mailrepository/mailrepository-api/src/test/java/org/apache/james/mailrepository/MailRepositoryContract.java
index d923050..e106015 100644
--- a/server/mailrepository/mailrepository-api/src/test/java/org/apache/james/mailrepository/MailRepositoryContract.java
+++ b/server/mailrepository/mailrepository-api/src/test/java/org/apache/james/mailrepository/MailRepositoryContract.java
@@ -46,6 +46,7 @@ import org.apache.mailet.Mail;
 import org.apache.mailet.PerRecipientHeaders;
 import org.apache.mailet.base.MailAddressFixture;
 import org.apache.mailet.base.test.FakeMail;
+import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.RepeatedTest;
 import org.junit.jupiter.api.Test;
 
@@ -419,6 +420,22 @@ public interface MailRepositoryContract {
         assertThat(testee.retrieve(MAIL_1)).satisfies(actual -> checkMailEquality(actual, mail));
     }
 
+
+    @Disabled("JAMES-2608")
+    @Test
+    default void storingMessageWithPerRecipientHeadersShouldAllowMultipleHeadersPerUser() throws Exception {
+
+        MailRepository testee = retrieveRepository();
+        Mail mail = createMail(MAIL_1);
+        MailAddress recipient1 = new MailAddress("rec1@domain.com");
+        mail.addSpecificHeaderForRecipient(PerRecipientHeaders.Header.builder().name("foo").value("bar").build(), recipient1);
+        mail.addSpecificHeaderForRecipient(PerRecipientHeaders.Header.builder().name("fizz").value("buzz").build(), recipient1);
+        testee.store(mail);
+
+        assertThat(testee.list()).hasSize(1).containsOnly(MAIL_1);
+        assertThat(testee.retrieve(MAIL_1)).satisfies(actual -> checkMailEquality(actual, mail));
+    }
+
     @RepeatedTest(100)
     default void storingAndRemovingMessagesConcurrentlyShouldLeadToConsistentResult() throws Exception {
         MailRepository testee = retrieveRepository();


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


[6/8] james-project git commit: JAMES-2608 implement CassandraMailRepositoryMailDaoV2 to handle several headers for each user

Posted by ro...@apache.org.
JAMES-2608 implement CassandraMailRepositoryMailDaoV2 to handle several headers for each user


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

Branch: refs/heads/master
Commit: d0f973b1071bf4ae43a71dd5b61444ba0904be89
Parents: 7a2e552
Author: Matthieu Baechler <ma...@apache.org>
Authored: Wed Nov 28 18:41:07 2018 +0100
Committer: Raphael Ouazana <ra...@linagora.com>
Committed: Wed Dec 19 09:24:12 2018 +0100

----------------------------------------------------------------------
 .../CassandraMailRepositoryMailDaoV2.java       | 277 +++++++++++++++++++
 .../CassandraMailRepositoryModule.java          |  23 ++
 .../cassandra/MailRepositoryTableV2.java        |  49 ++++
 .../CassandraMailRepositoryMailDAOTest.java     |  16 ++
 4 files changed, 365 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/d0f973b1/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDaoV2.java
----------------------------------------------------------------------
diff --git a/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDaoV2.java b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDaoV2.java
new file mode 100644
index 0000000..10b44b1
--- /dev/null
+++ b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDaoV2.java
@@ -0,0 +1,277 @@
+/****************************************************************
+ * 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.mailrepository.cassandra;
+
+import static com.datastax.driver.core.DataType.text;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.bindMarker;
+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 org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.ATTRIBUTES;
+import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.BODY_BLOB_ID;
+import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.CONTENT_TABLE_NAME;
+import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.ERROR_MESSAGE;
+import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.HEADER_BLOB_ID;
+import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.HeaderEntrty.HEADER_NAME_INDEX;
+import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.HeaderEntrty.HEADER_VALUE_INDEX;
+import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.HeaderEntrty.USER_INDEX;
+import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.LAST_UPDATED;
+import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.MAIL_KEY;
+import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.MAIL_PROPERTIES;
+import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.MESSAGE_SIZE;
+import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.PER_RECIPIENT_SPECIFIC_HEADERS;
+import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.RECIPIENTS;
+import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.REMOTE_ADDR;
+import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.REMOTE_HOST;
+import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.REPOSITORY_NAME;
+import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.SENDER;
+import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.STATE;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+
+import javax.inject.Inject;
+import javax.mail.MessagingException;
+import javax.mail.internet.AddressException;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.james.backends.cassandra.utils.CassandraAsyncExecutor;
+import org.apache.james.blob.api.BlobId;
+import org.apache.james.core.MailAddress;
+import org.apache.james.mailrepository.api.MailKey;
+import org.apache.james.mailrepository.api.MailRepositoryUrl;
+import org.apache.james.server.core.MailImpl;
+import org.apache.james.util.streams.Iterators;
+import org.apache.mailet.Mail;
+import org.apache.mailet.PerRecipientHeaders;
+import org.apache.mailet.PerRecipientHeaders.Header;
+
+import com.datastax.driver.core.PreparedStatement;
+import com.datastax.driver.core.Row;
+import com.datastax.driver.core.Session;
+import com.datastax.driver.core.TupleType;
+import com.datastax.driver.core.TupleValue;
+import com.github.fge.lambdas.Throwing;
+import com.github.steveash.guavate.Guavate;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class CassandraMailRepositoryMailDaoV2 implements CassandraMailRepositoryMailDaoAPI {
+
+    private final CassandraAsyncExecutor executor;
+    private final PreparedStatement insertMail;
+    private final PreparedStatement deleteMail;
+    private final PreparedStatement selectMail;
+    private final BlobId.Factory blobIdFactory;
+    private final TupleType userHeaderNameHeaderValueTriple;
+
+    @Inject
+    @VisibleForTesting
+    CassandraMailRepositoryMailDaoV2(Session session, BlobId.Factory blobIdFactory) {
+        this.executor = new CassandraAsyncExecutor(session);
+
+        this.insertMail = prepareInsert(session);
+        this.deleteMail = prepareDelete(session);
+        this.selectMail = prepareSelect(session);
+        this.blobIdFactory = blobIdFactory;
+        this.userHeaderNameHeaderValueTriple = session.getCluster().getMetadata().newTupleType(text(), text(), text());
+    }
+
+    private PreparedStatement prepareDelete(Session session) {
+        return session.prepare(delete()
+            .from(CONTENT_TABLE_NAME)
+            .where(eq(REPOSITORY_NAME, bindMarker(REPOSITORY_NAME)))
+            .and(eq(MAIL_KEY, bindMarker(MAIL_KEY))));
+    }
+
+    private PreparedStatement prepareInsert(Session session) {
+        return session.prepare(insertInto(CONTENT_TABLE_NAME)
+            .value(REPOSITORY_NAME, bindMarker(REPOSITORY_NAME))
+            .value(MAIL_KEY, bindMarker(MAIL_KEY))
+            .value(MESSAGE_SIZE, bindMarker(MESSAGE_SIZE))
+            .value(STATE, bindMarker(STATE))
+            .value(SENDER, bindMarker(SENDER))
+            .value(RECIPIENTS, bindMarker(RECIPIENTS))
+            .value(ATTRIBUTES, bindMarker(ATTRIBUTES))
+            .value(ERROR_MESSAGE, bindMarker(ERROR_MESSAGE))
+            .value(REMOTE_ADDR, bindMarker(REMOTE_ADDR))
+            .value(REMOTE_HOST, bindMarker(REMOTE_HOST))
+            .value(LAST_UPDATED, bindMarker(LAST_UPDATED))
+            .value(HEADER_BLOB_ID, bindMarker(HEADER_BLOB_ID))
+            .value(BODY_BLOB_ID, bindMarker(BODY_BLOB_ID))
+            .value(PER_RECIPIENT_SPECIFIC_HEADERS, bindMarker(PER_RECIPIENT_SPECIFIC_HEADERS)));
+    }
+
+    private PreparedStatement prepareSelect(Session session) {
+        return session.prepare(
+            select(MAIL_PROPERTIES)
+                .from(CONTENT_TABLE_NAME)
+                .where(eq(REPOSITORY_NAME, bindMarker(REPOSITORY_NAME)))
+                .and(eq(MAIL_KEY, bindMarker(MAIL_KEY))));
+    }
+
+    public CompletableFuture<Void> store(MailRepositoryUrl url, Mail mail, BlobId headerId, BlobId bodyId) throws MessagingException {
+        return executor.executeVoid(insertMail.bind()
+            .setString(REPOSITORY_NAME, url.asString())
+            .setString(MAIL_KEY, mail.getName())
+            .setString(HEADER_BLOB_ID, headerId.asString())
+            .setString(BODY_BLOB_ID, bodyId.asString())
+            .setString(STATE, mail.getState())
+            .setString(SENDER, mail.getMaybeSender().asString(null))
+            .setList(RECIPIENTS, asStringList(mail.getRecipients()))
+            .setString(ERROR_MESSAGE, mail.getErrorMessage())
+            .setString(REMOTE_ADDR, mail.getRemoteAddr())
+            .setString(REMOTE_HOST, mail.getRemoteHost())
+            .setLong(MESSAGE_SIZE, mail.getMessageSize())
+            .setTimestamp(LAST_UPDATED, mail.getLastUpdated())
+            .setMap(ATTRIBUTES, toRawAttributeMap(mail))
+            .setList(PER_RECIPIENT_SPECIFIC_HEADERS, toTupleList(mail.getPerRecipientSpecificHeaders()))
+        );
+    }
+
+    public CompletableFuture<Void> remove(MailRepositoryUrl url, MailKey key) {
+        return executor.executeVoid(deleteMail.bind()
+            .setString(REPOSITORY_NAME, url.asString())
+            .setString(MAIL_KEY, key.asString()));
+    }
+
+    public CompletableFuture<Optional<MailDTO>> read(MailRepositoryUrl url, MailKey key) {
+        return executor.executeSingleRow(selectMail.bind()
+            .setString(REPOSITORY_NAME, url.asString())
+            .setString(MAIL_KEY, key.asString()))
+            .thenApply(rowOptional -> rowOptional.map(this::toMail));
+    }
+
+    private MailDTO toMail(Row row) {
+        MailAddress sender = Optional.ofNullable(row.getString(SENDER))
+            .map(MailAddress::getMailSender)
+            .orElse(null);
+        List<MailAddress> recipients = row.getList(RECIPIENTS, String.class)
+            .stream()
+            .map(Throwing.function(MailAddress::new))
+            .collect(Guavate.toImmutableList());
+        String state = row.getString(STATE);
+        String remoteAddr = row.getString(REMOTE_ADDR);
+        String remoteHost = row.getString(REMOTE_HOST);
+        String errorMessage = row.getString(ERROR_MESSAGE);
+        String name = row.getString(MAIL_KEY);
+        Date lastUpdated = row.getTimestamp(LAST_UPDATED);
+        Map<String, ByteBuffer> rawAttributes = row.getMap(ATTRIBUTES, String.class, ByteBuffer.class);
+        PerRecipientHeaders perRecipientHeaders = fromList(row.getList(PER_RECIPIENT_SPECIFIC_HEADERS, TupleValue.class));
+
+        MailImpl.Builder mailBuilder = MailImpl.builder()
+            .name(name)
+            .sender(sender)
+            .recipients(recipients)
+            .lastUpdated(lastUpdated)
+            .errorMessage(errorMessage)
+            .remoteHost(remoteHost)
+            .remoteAddr(remoteAddr)
+            .state(state)
+            .addAllHeadersForRecipients(perRecipientHeaders)
+            .attributes(toAttributes(rawAttributes));
+
+        return new MailDTO(mailBuilder,
+            blobIdFactory.from(row.getString(HEADER_BLOB_ID)),
+            blobIdFactory.from(row.getString(BODY_BLOB_ID)));
+    }
+
+    private Map<String, Serializable> toAttributes(Map<String, ByteBuffer> rowAttributes) {
+        return rowAttributes.entrySet()
+            .stream()
+            .map(entry -> Pair.of(entry.getKey(), fromByteBuffer(entry.getValue())))
+            .collect(Guavate.toImmutableMap(Pair::getLeft, Pair::getRight));
+    }
+
+    private ImmutableList<String> asStringList(Collection<MailAddress> mailAddresses) {
+        return mailAddresses.stream().map(MailAddress::asString).collect(Guavate.toImmutableList());
+    }
+
+    private ImmutableMap<String, ByteBuffer> toRawAttributeMap(Mail mail) {
+        return Iterators.toStream(mail.getAttributeNames())
+            .map(name -> Pair.of(name, mail.getAttribute(name)))
+            .map(pair -> Pair.of(pair.getLeft(), toByteBuffer(pair.getRight())))
+            .collect(Guavate.toImmutableMap(Pair::getLeft, Pair::getRight));
+    }
+
+    private ImmutableList<TupleValue> toTupleList(PerRecipientHeaders perRecipientHeaders) {
+        return perRecipientHeaders.getHeadersByRecipient()
+            .entries()
+            .stream()
+            .map(entry -> userHeaderNameHeaderValueTriple.newValue(entry.getKey().asString(), entry.getValue().getName(), entry.getValue().getValue()))
+            .collect(Guavate.toImmutableList());
+    }
+
+    private PerRecipientHeaders fromList(List<TupleValue> list) {
+        PerRecipientHeaders result = new PerRecipientHeaders();
+
+        list.forEach(tuple ->
+                result.addHeaderForRecipient(
+                    Header.builder()
+                        .name(tuple.getString(HEADER_NAME_INDEX))
+                        .value(tuple.getString(HEADER_VALUE_INDEX))
+                        .build(),
+                    toMailAddress(tuple.getString(USER_INDEX))));
+        return result;
+    }
+
+    private ByteBuffer toByteBuffer(Serializable serializable) {
+        try {
+            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+            new ObjectOutputStream(outputStream).writeObject(serializable);
+            return ByteBuffer.wrap(outputStream.toByteArray());
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private Serializable fromByteBuffer(ByteBuffer byteBuffer) {
+        try {
+            byte[] data = new byte[byteBuffer.remaining()];
+            byteBuffer.get(data);
+            ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(data));
+            return (Serializable) objectInputStream.readObject();
+        } catch (IOException | ClassNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private MailAddress toMailAddress(String rawValue) {
+        try {
+            return new MailAddress(rawValue);
+        } catch (AddressException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/d0f973b1/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryModule.java
----------------------------------------------------------------------
diff --git a/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryModule.java b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryModule.java
index cfbcdf4..f2c44f4 100644
--- a/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryModule.java
+++ b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryModule.java
@@ -30,6 +30,10 @@ import static com.datastax.driver.core.schemabuilder.SchemaBuilder.frozen;
 
 import org.apache.james.backends.cassandra.components.CassandraModule;
 
+import com.datastax.driver.core.CodecRegistry;
+import com.datastax.driver.core.ProtocolVersion;
+import com.datastax.driver.core.TupleType;
+
 public interface CassandraMailRepositoryModule {
     CassandraModule MODULE = CassandraModule.builder()
         .type(MailRepositoryTable.HEADER_TYPE)
@@ -64,5 +68,24 @@ public interface CassandraMailRepositoryModule {
             .addColumn(MailRepositoryTable.REMOTE_ADDR, text())
             .addColumn(MailRepositoryTable.LAST_UPDATED, timestamp())
             .addUDTMapColumn(MailRepositoryTable.PER_RECIPIENT_SPECIFIC_HEADERS, text(), frozen(MailRepositoryTable.HEADER_TYPE)))
+        .table(MailRepositoryTableV2.CONTENT_TABLE_NAME)
+        .comment("Stores the mails for a given repository. " +
+            "Content is stored with other blobs. " +
+            "This v2 version was introduced to support multiple headers for each user")
+        .statement(statement -> statement
+            .addPartitionKey(MailRepositoryTable.REPOSITORY_NAME, text())
+            .addPartitionKey(MailRepositoryTable.MAIL_KEY, text())
+            .addColumn(MailRepositoryTable.MESSAGE_SIZE, bigint())
+            .addColumn(MailRepositoryTable.STATE, text())
+            .addColumn(MailRepositoryTable.HEADER_BLOB_ID, text())
+            .addColumn(MailRepositoryTable.BODY_BLOB_ID, text())
+            .addColumn(MailRepositoryTable.ATTRIBUTES, map(text(), blob()))
+            .addColumn(MailRepositoryTable.ERROR_MESSAGE, text())
+            .addColumn(MailRepositoryTable.SENDER, text())
+            .addColumn(MailRepositoryTable.RECIPIENTS, list(text()))
+            .addColumn(MailRepositoryTable.REMOTE_HOST, text())
+            .addColumn(MailRepositoryTable.REMOTE_ADDR, text())
+            .addColumn(MailRepositoryTable.LAST_UPDATED, timestamp())
+            .addColumn(MailRepositoryTable.PER_RECIPIENT_SPECIFIC_HEADERS, list(TupleType.of(ProtocolVersion.NEWEST_SUPPORTED, CodecRegistry.DEFAULT_INSTANCE, text(), text(), text()))))
         .build();
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/d0f973b1/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/MailRepositoryTableV2.java
----------------------------------------------------------------------
diff --git a/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/MailRepositoryTableV2.java b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/MailRepositoryTableV2.java
new file mode 100644
index 0000000..54e2e45
--- /dev/null
+++ b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/MailRepositoryTableV2.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.mailrepository.cassandra;
+
+public interface MailRepositoryTableV2 {
+    String CONTENT_TABLE_NAME = "mailRepositoryContentV2";
+
+    String REPOSITORY_NAME = "name";
+
+    String MAIL_KEY = "mailKey";
+    String MESSAGE_SIZE = "messageSize";
+    String HEADER_BLOB_ID = "headerBlobId";
+    String BODY_BLOB_ID = "bodyBlobId";
+    String STATE = "state";
+    String SENDER = "sender";
+    String RECIPIENTS = "recipients";
+    String ATTRIBUTES = "attributes";
+    String ERROR_MESSAGE = "errorMessage";
+    String REMOTE_HOST = "remoteHost";
+    String REMOTE_ADDR = "remoteAddr";
+    String LAST_UPDATED = "lastUpdated";
+    String PER_RECIPIENT_SPECIFIC_HEADERS = "perRecipientSpecificHeaders";
+
+    interface HeaderEntrty {
+        int USER_INDEX = 0;
+        int HEADER_NAME_INDEX = 1;
+        int HEADER_VALUE_INDEX = 2;
+    }
+
+    String[] MAIL_PROPERTIES = { MAIL_KEY, MESSAGE_SIZE, STATE, SENDER, RECIPIENTS, ATTRIBUTES, ERROR_MESSAGE, REMOTE_ADDR,
+        REMOTE_HOST, LAST_UPDATED, PER_RECIPIENT_SPECIFIC_HEADERS, HEADER_BLOB_ID, BODY_BLOB_ID };
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/d0f973b1/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java
----------------------------------------------------------------------
diff --git a/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java b/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java
index 582a7ba..c02966a 100644
--- a/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java
+++ b/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java
@@ -171,4 +171,20 @@ class CassandraMailRepositoryMailDAOTest {
             return testee;
         }
     }
+
+    @Nested
+    class v2 extends TestSuite {
+
+        private CassandraMailRepositoryMailDaoV2 testee;
+
+        @BeforeEach
+        void setUp(CassandraCluster cassandra) {
+            testee = new CassandraMailRepositoryMailDaoV2(cassandra.getConf(), BLOB_ID_FACTORY);
+        }
+
+        @Override
+        CassandraMailRepositoryMailDaoAPI testee() {
+            return testee;
+        }
+    }
 }
\ 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


[7/8] james-project git commit: JAMES-2608 Store attributes as Json

Posted by ro...@apache.org.
JAMES-2608 Store attributes as Json


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

Branch: refs/heads/master
Commit: 3b223d537508d9a79aa50640cc23d87726508c29
Parents: 1ebe3fd
Author: Antoine Duprat <ad...@linagora.com>
Authored: Mon Dec 17 11:34:23 2018 +0100
Committer: Raphael Ouazana <ra...@linagora.com>
Committed: Wed Dec 19 09:24:12 2018 +0100

----------------------------------------------------------------------
 .../CassandraMailRepositoryMailDaoV2.java       |  47 ++----
 .../CassandraMailRepositoryModule.java          |   5 +-
 .../CassandraMailRepositoryMailDAOTest.java     | 163 +++++++++++++------
 3 files changed, 126 insertions(+), 89 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/3b223d53/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDaoV2.java
----------------------------------------------------------------------
diff --git a/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDaoV2.java b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDaoV2.java
index 156fa85..36e8e28 100644
--- a/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDaoV2.java
+++ b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDaoV2.java
@@ -44,13 +44,6 @@ import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.RE
 import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.SENDER;
 import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.STATE;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.Serializable;
-import java.nio.ByteBuffer;
 import java.util.Collection;
 import java.util.Date;
 import java.util.List;
@@ -69,7 +62,9 @@ import org.apache.james.core.MailAddress;
 import org.apache.james.mailrepository.api.MailKey;
 import org.apache.james.mailrepository.api.MailRepositoryUrl;
 import org.apache.james.server.core.MailImpl;
-import org.apache.james.util.streams.Iterators;
+import org.apache.mailet.Attribute;
+import org.apache.mailet.AttributeName;
+import org.apache.mailet.AttributeValue;
 import org.apache.mailet.Mail;
 import org.apache.mailet.PerRecipientHeaders;
 import org.apache.mailet.PerRecipientHeaders.Header;
@@ -183,7 +178,7 @@ public class CassandraMailRepositoryMailDaoV2 implements CassandraMailRepository
         String errorMessage = row.getString(ERROR_MESSAGE);
         String name = row.getString(MAIL_KEY);
         Date lastUpdated = row.getTimestamp(LAST_UPDATED);
-        Map<String, ByteBuffer> rawAttributes = row.getMap(ATTRIBUTES, String.class, ByteBuffer.class);
+        Map<String, String> rawAttributes = row.getMap(ATTRIBUTES, String.class, String.class);
         PerRecipientHeaders perRecipientHeaders = fromList(row.getList(PER_RECIPIENT_SPECIFIC_HEADERS, TupleValue.class));
 
         MailImpl.Builder mailBuilder = MailImpl.builder()
@@ -203,21 +198,20 @@ public class CassandraMailRepositoryMailDaoV2 implements CassandraMailRepository
             blobIdFactory.from(row.getString(BODY_BLOB_ID)));
     }
 
-    private Map<String, Serializable> toAttributes(Map<String, ByteBuffer> rowAttributes) {
+    private List<Attribute> toAttributes(Map<String, String> rowAttributes) {
         return rowAttributes.entrySet()
             .stream()
-            .map(entry -> Pair.of(entry.getKey(), fromByteBuffer(entry.getValue())))
-            .collect(Guavate.toImmutableMap(Pair::getLeft, Pair::getRight));
+            .map(Throwing.function(entry -> new Attribute(AttributeName.of(entry.getKey()), AttributeValue.fromJsonString(entry.getValue()))))
+            .collect(Guavate.toImmutableList());
     }
 
     private ImmutableList<String> asStringList(Collection<MailAddress> mailAddresses) {
         return mailAddresses.stream().map(MailAddress::asString).collect(Guavate.toImmutableList());
     }
 
-    private ImmutableMap<String, ByteBuffer> toRawAttributeMap(Mail mail) {
-        return Iterators.toStream(mail.getAttributeNames())
-            .map(name -> Pair.of(name, mail.getAttribute(name)))
-            .map(pair -> Pair.of(pair.getLeft(), toByteBuffer(pair.getRight())))
+    private ImmutableMap<String, String> toRawAttributeMap(Mail mail) {
+        return mail.attributes()
+            .map(attribute -> Pair.of(attribute.getName().asString(), toJson(attribute.getValue())))
             .collect(Guavate.toImmutableMap(Pair::getLeft, Pair::getRight));
     }
 
@@ -242,25 +236,8 @@ public class CassandraMailRepositoryMailDaoV2 implements CassandraMailRepository
         return result;
     }
 
-    private ByteBuffer toByteBuffer(Serializable serializable) {
-        try {
-            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-            new ObjectOutputStream(outputStream).writeObject(serializable);
-            return ByteBuffer.wrap(outputStream.toByteArray());
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private Serializable fromByteBuffer(ByteBuffer byteBuffer) {
-        try {
-            byte[] data = new byte[byteBuffer.remaining()];
-            byteBuffer.get(data);
-            ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(data));
-            return (Serializable) objectInputStream.readObject();
-        } catch (IOException | ClassNotFoundException e) {
-            throw new RuntimeException(e);
-        }
+    private String toJson(AttributeValue<?> attributeValue) {
+        return attributeValue.toJson().toString();
     }
 
     private MailAddress toMailAddress(String rawValue) {

http://git-wip-us.apache.org/repos/asf/james-project/blob/3b223d53/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryModule.java
----------------------------------------------------------------------
diff --git a/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryModule.java b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryModule.java
index f2c44f4..6e6c3cd 100644
--- a/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryModule.java
+++ b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryModule.java
@@ -71,7 +71,8 @@ public interface CassandraMailRepositoryModule {
         .table(MailRepositoryTableV2.CONTENT_TABLE_NAME)
         .comment("Stores the mails for a given repository. " +
             "Content is stored with other blobs. " +
-            "This v2 version was introduced to support multiple headers for each user")
+            "This v2 version was introduced to support multiple headers for each user. " +
+            "The attributes are store as Json introduced in Mailet API v3.2.")
         .statement(statement -> statement
             .addPartitionKey(MailRepositoryTable.REPOSITORY_NAME, text())
             .addPartitionKey(MailRepositoryTable.MAIL_KEY, text())
@@ -79,7 +80,7 @@ public interface CassandraMailRepositoryModule {
             .addColumn(MailRepositoryTable.STATE, text())
             .addColumn(MailRepositoryTable.HEADER_BLOB_ID, text())
             .addColumn(MailRepositoryTable.BODY_BLOB_ID, text())
-            .addColumn(MailRepositoryTable.ATTRIBUTES, map(text(), blob()))
+            .addColumn(MailRepositoryTable.ATTRIBUTES, map(text(), text()))
             .addColumn(MailRepositoryTable.ERROR_MESSAGE, text())
             .addColumn(MailRepositoryTable.SENDER, text())
             .addColumn(MailRepositoryTable.RECIPIENTS, list(text()))

http://git-wip-us.apache.org/repos/asf/james-project/blob/3b223d53/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java
----------------------------------------------------------------------
diff --git a/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java b/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java
index a50c69e..a04b8ed 100644
--- a/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java
+++ b/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java
@@ -22,6 +22,8 @@ package org.apache.james.mailrepository.cassandra;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.SoftAssertions.assertSoftly;
 
+import java.util.Collection;
+import java.util.List;
 import java.util.Optional;
 
 import javax.mail.MessagingException;
@@ -32,6 +34,9 @@ import org.apache.james.blob.api.BlobId;
 import org.apache.james.blob.api.TestBlobId;
 import org.apache.james.mailrepository.api.MailKey;
 import org.apache.james.mailrepository.api.MailRepositoryUrl;
+import org.apache.mailet.Attribute;
+import org.apache.mailet.AttributeName;
+import org.apache.mailet.AttributeValue;
 import org.apache.mailet.Mail;
 import org.apache.mailet.PerRecipientHeaders;
 import org.apache.mailet.base.MailAddressFixture;
@@ -57,58 +62,6 @@ class CassandraMailRepositoryMailDAOTest {
         abstract CassandraMailRepositoryMailDaoAPI testee();
 
         @Test
-        void readShouldReturnAllMailMetadata() throws Exception {
-            CassandraMailRepositoryMailDaoAPI testee = testee();
-
-            BlobId blobIdBody = BLOB_ID_FACTORY.from("blobHeader");
-            BlobId blobIdHeader = BLOB_ID_FACTORY.from("blobBody");
-            String errorMessage = "error message";
-            String state = "state";
-            String remoteAddr = "remoteAddr";
-            String remoteHost = "remoteHost";
-            PerRecipientHeaders.Header header = PerRecipientHeaders.Header.builder().name("headerName").value("headerValue").build();
-            String attributeName = "att1";
-            ImmutableList<String> attributeValue = ImmutableList.of("value1", "value2");
-
-            testee.store(URL,
-                FakeMail.builder()
-                    .name(KEY_1.asString())
-                    .sender(MailAddressFixture.SENDER)
-                    .recipients(MailAddressFixture.RECIPIENT1, MailAddressFixture.RECIPIENT2)
-                    .errorMessage(errorMessage)
-                    .state(state)
-                    .remoteAddr(remoteAddr)
-                    .remoteHost(remoteHost)
-                    .addHeaderForRecipient(header, MailAddressFixture.RECIPIENT1)
-                    .attribute(attributeName, attributeValue)
-                    .build(),
-                blobIdHeader,
-                blobIdBody)
-                .join();
-
-            CassandraMailRepositoryMailDAO.MailDTO mailDTO = testee.read(URL, KEY_1).join().get();
-
-            Mail partialMail = mailDTO.getMailBuilder().build();
-            assertSoftly(softly -> {
-                softly.assertThat(mailDTO.getBodyBlobId()).isEqualTo(blobIdBody);
-                softly.assertThat(mailDTO.getHeaderBlobId()).isEqualTo(blobIdHeader);
-                softly.assertThat(partialMail.getName()).isEqualTo(KEY_1.asString());
-                softly.assertThat(partialMail.getErrorMessage()).isEqualTo(errorMessage);
-                softly.assertThat(partialMail.getState()).isEqualTo(state);
-                softly.assertThat(partialMail.getRemoteAddr()).isEqualTo(remoteAddr);
-                softly.assertThat(partialMail.getRemoteHost()).isEqualTo(remoteHost);
-                softly.assertThat(partialMail.getAttributeNames()).containsOnly(attributeName);
-                softly.assertThat(partialMail.getAttribute(attributeName)).isEqualTo(attributeValue);
-                softly.assertThat(partialMail.getPerRecipientSpecificHeaders().getRecipientsWithSpecificHeaders())
-                    .containsOnly(MailAddressFixture.RECIPIENT1);
-                softly.assertThat(partialMail.getPerRecipientSpecificHeaders().getHeadersForRecipient(MailAddressFixture.RECIPIENT1))
-                    .containsOnly(header);
-                softly.assertThat(partialMail.getMaybeSender().asOptional()).contains(MailAddressFixture.SENDER);
-                softly.assertThat(partialMail.getRecipients()).containsOnly(MailAddressFixture.RECIPIENT1, MailAddressFixture.RECIPIENT2);
-            });
-        }
-
-        @Test
         void storeShouldAcceptMailWithOnlyName() throws Exception {
             CassandraMailRepositoryMailDaoAPI testee = testee();
             BlobId blobIdBody = BLOB_ID_FACTORY.from("blobHeader");
@@ -174,6 +127,58 @@ class CassandraMailRepositoryMailDAOTest {
         CassandraMailRepositoryMailDaoAPI testee() {
             return testee;
         }
+
+        @Test
+        void readShouldReturnAllMailMetadata() throws Exception {
+            CassandraMailRepositoryMailDaoAPI testee = testee();
+
+            BlobId blobIdBody = BLOB_ID_FACTORY.from("blobHeader");
+            BlobId blobIdHeader = BLOB_ID_FACTORY.from("blobBody");
+            String errorMessage = "error message";
+            String state = "state";
+            String remoteAddr = "remoteAddr";
+            String remoteHost = "remoteHost";
+            PerRecipientHeaders.Header header = PerRecipientHeaders.Header.builder().name("headerName").value("headerValue").build();
+            String attributeName = "att1";
+            ImmutableList<String> attributeValue = ImmutableList.of("value1", "value2");
+
+            testee.store(URL,
+                FakeMail.builder()
+                    .name(KEY_1.asString())
+                    .sender(MailAddressFixture.SENDER)
+                    .recipients(MailAddressFixture.RECIPIENT1, MailAddressFixture.RECIPIENT2)
+                    .errorMessage(errorMessage)
+                    .state(state)
+                    .remoteAddr(remoteAddr)
+                    .remoteHost(remoteHost)
+                    .addHeaderForRecipient(header, MailAddressFixture.RECIPIENT1)
+                    .attribute(attributeName, attributeValue)
+                    .build(),
+                blobIdHeader,
+                blobIdBody)
+                .join();
+
+            CassandraMailRepositoryMailDAO.MailDTO mailDTO = testee.read(URL, KEY_1).join().get();
+
+            Mail partialMail = mailDTO.getMailBuilder().build();
+            assertSoftly(softly -> {
+                softly.assertThat(mailDTO.getBodyBlobId()).isEqualTo(blobIdBody);
+                softly.assertThat(mailDTO.getHeaderBlobId()).isEqualTo(blobIdHeader);
+                softly.assertThat(partialMail.getName()).isEqualTo(KEY_1.asString());
+                softly.assertThat(partialMail.getErrorMessage()).isEqualTo(errorMessage);
+                softly.assertThat(partialMail.getState()).isEqualTo(state);
+                softly.assertThat(partialMail.getRemoteAddr()).isEqualTo(remoteAddr);
+                softly.assertThat(partialMail.getRemoteHost()).isEqualTo(remoteHost);
+                softly.assertThat(partialMail.getAttributeNames()).containsOnly(attributeName);
+                softly.assertThat(partialMail.getAttribute(attributeName)).isEqualTo(attributeValue);
+                softly.assertThat(partialMail.getPerRecipientSpecificHeaders().getRecipientsWithSpecificHeaders())
+                    .containsOnly(MailAddressFixture.RECIPIENT1);
+                softly.assertThat(partialMail.getPerRecipientSpecificHeaders().getHeadersForRecipient(MailAddressFixture.RECIPIENT1))
+                    .containsOnly(header);
+                softly.assertThat(partialMail.getMaybeSender().asOptional()).contains(MailAddressFixture.SENDER);
+                softly.assertThat(partialMail.getRecipients()).containsOnly(MailAddressFixture.RECIPIENT1, MailAddressFixture.RECIPIENT2);
+            });
+        }
     }
 
     @Nested
@@ -190,6 +195,60 @@ class CassandraMailRepositoryMailDAOTest {
         CassandraMailRepositoryMailDaoAPI testee() {
             return testee;
         }
+
+        @Test
+        void readShouldReturnAllMailMetadata() throws Exception {
+            CassandraMailRepositoryMailDaoAPI testee = testee();
+
+            BlobId blobIdBody = BLOB_ID_FACTORY.from("blobHeader");
+            BlobId blobIdHeader = BLOB_ID_FACTORY.from("blobBody");
+            String errorMessage = "error message";
+            String state = "state";
+            String remoteAddr = "remoteAddr";
+            String remoteHost = "remoteHost";
+            PerRecipientHeaders.Header header = PerRecipientHeaders.Header.builder().name("headerName").value("headerValue").build();
+            String attributeName = "att1";
+            List<AttributeValue<?>> attributeValue = ImmutableList.of(AttributeValue.of("value1"), AttributeValue.of("value2"));
+            Attribute attribute = new Attribute(AttributeName.of(attributeName), AttributeValue.of(attributeValue));
+            List<Attribute> attributes = ImmutableList.of(attribute);
+
+            testee.store(URL,
+                FakeMail.builder()
+                    .name(KEY_1.asString())
+                    .sender(MailAddressFixture.SENDER)
+                    .recipients(MailAddressFixture.RECIPIENT1, MailAddressFixture.RECIPIENT2)
+                    .errorMessage(errorMessage)
+                    .state(state)
+                    .remoteAddr(remoteAddr)
+                    .remoteHost(remoteHost)
+                    .addHeaderForRecipient(header, MailAddressFixture.RECIPIENT1)
+                    .attributes(attributes)
+                    .build(),
+                blobIdHeader,
+                blobIdBody)
+                .join();
+
+            CassandraMailRepositoryMailDAO.MailDTO mailDTO = testee.read(URL, KEY_1).join().get();
+
+            Mail partialMail = mailDTO.getMailBuilder().build();
+            assertSoftly(softly -> {
+                softly.assertThat(mailDTO.getBodyBlobId()).isEqualTo(blobIdBody);
+                softly.assertThat(mailDTO.getHeaderBlobId()).isEqualTo(blobIdHeader);
+                softly.assertThat(partialMail.getName()).isEqualTo(KEY_1.asString());
+                softly.assertThat(partialMail.getErrorMessage()).isEqualTo(errorMessage);
+                softly.assertThat(partialMail.getState()).isEqualTo(state);
+                softly.assertThat(partialMail.getRemoteAddr()).isEqualTo(remoteAddr);
+                softly.assertThat(partialMail.getRemoteHost()).isEqualTo(remoteHost);
+                softly.assertThat(partialMail.getAttributeNames()).containsOnly(attributeName);
+                softly.assertThat(partialMail.getAttribute(AttributeName.of(attributeName))).contains(attribute);
+                softly.assertThat(partialMail.getPerRecipientSpecificHeaders().getRecipientsWithSpecificHeaders())
+                    .containsOnly(MailAddressFixture.RECIPIENT1);
+                softly.assertThat(partialMail.getPerRecipientSpecificHeaders().getHeadersForRecipient(MailAddressFixture.RECIPIENT1))
+                    .containsOnly(header);
+                softly.assertThat(partialMail.getMaybeSender().asOptional()).contains(MailAddressFixture.SENDER);
+                softly.assertThat(partialMail.getRecipients()).containsOnly(MailAddressFixture.RECIPIENT1, MailAddressFixture.RECIPIENT2);
+            });
+        }
     }
 
     @Nested


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


[4/8] james-project git commit: JAMES-2608 introduce CassandraMailRepositoryMailDaoAPI to introduce a v2 implementation later

Posted by ro...@apache.org.
JAMES-2608 introduce CassandraMailRepositoryMailDaoAPI to introduce a v2 implementation later


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

Branch: refs/heads/master
Commit: 7a2e552f3575ad56def379850c1e38637c320771
Parents: d9118f6
Author: Matthieu Baechler <ma...@apache.org>
Authored: Wed Nov 28 18:37:59 2018 +0100
Committer: Raphael Ouazana <ra...@linagora.com>
Committed: Wed Dec 19 09:24:12 2018 +0100

----------------------------------------------------------------------
 .../cassandra/CassandraMailRepository.java      |   4 +-
 .../CassandraMailRepositoryMailDAO.java         |  52 +----
 .../CassandraMailRepositoryMailDaoAPI.java      |  81 +++++++
 .../CassandraMailRepositoryProvider.java        |   4 +-
 .../CassandraMailRepositoryMailDAOTest.java     | 212 ++++++++++---------
 ...ilRepositoryWithFakeImplementationsTest.java |  33 ++-
 6 files changed, 232 insertions(+), 154 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/7a2e552f/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepository.java
----------------------------------------------------------------------
diff --git a/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepository.java b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepository.java
index c62feba..6655798 100644
--- a/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepository.java
+++ b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepository.java
@@ -43,11 +43,11 @@ public class CassandraMailRepository implements MailRepository {
     private final MailRepositoryUrl url;
     private final CassandraMailRepositoryKeysDAO keysDAO;
     private final CassandraMailRepositoryCountDAO countDAO;
-    private final CassandraMailRepositoryMailDAO mailDAO;
+    private final CassandraMailRepositoryMailDaoAPI mailDAO;
     private final Store<MimeMessage, MimeMessagePartsId> mimeMessageStore;
 
     public CassandraMailRepository(MailRepositoryUrl url, CassandraMailRepositoryKeysDAO keysDAO,
-                                   CassandraMailRepositoryCountDAO countDAO, CassandraMailRepositoryMailDAO mailDAO,
+                                   CassandraMailRepositoryCountDAO countDAO, CassandraMailRepositoryMailDaoAPI mailDAO,
                                    Store<MimeMessage, MimeMessagePartsId> mimeMessageStore) {
         this.url = url;
         this.keysDAO = keysDAO;

http://git-wip-us.apache.org/repos/asf/james-project/blob/7a2e552f/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAO.java
----------------------------------------------------------------------
diff --git a/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAO.java b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAO.java
index 51bbe30..7c3596b 100644
--- a/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAO.java
+++ b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAO.java
@@ -55,7 +55,6 @@ import java.util.Collection;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 
@@ -82,10 +81,11 @@ import com.datastax.driver.core.Session;
 import com.datastax.driver.core.UDTValue;
 import com.github.fge.lambdas.Throwing;
 import com.github.steveash.guavate.Guavate;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
-public class CassandraMailRepositoryMailDAO {
+public class CassandraMailRepositoryMailDAO implements CassandraMailRepositoryMailDaoAPI {
 
     private final CassandraAsyncExecutor executor;
     private final PreparedStatement insertMail;
@@ -95,8 +95,9 @@ public class CassandraMailRepositoryMailDAO {
     private final CassandraTypesProvider cassandraTypesProvider;
 
     @Inject
-    public CassandraMailRepositoryMailDAO(Session session, BlobId.Factory blobIdFactory,
-                                          CassandraTypesProvider cassandraTypesProvider) {
+    @VisibleForTesting
+    CassandraMailRepositoryMailDAO(Session session, BlobId.Factory blobIdFactory,
+                                   CassandraTypesProvider cassandraTypesProvider) {
         this.executor = new CassandraAsyncExecutor(session);
 
         this.insertMail = prepareInsert(session);
@@ -139,6 +140,7 @@ public class CassandraMailRepositoryMailDAO {
                 .and(eq(MAIL_KEY, bindMarker(MAIL_KEY))));
     }
 
+    @Override
     public CompletableFuture<Void> store(MailRepositoryUrl url, Mail mail, BlobId headerId, BlobId bodyId) throws MessagingException {
         return executor.executeVoid(insertMail.bind()
             .setString(REPOSITORY_NAME, url.asString())
@@ -158,12 +160,14 @@ public class CassandraMailRepositoryMailDAO {
         );
     }
 
+    @Override
     public CompletableFuture<Void> remove(MailRepositoryUrl url, MailKey key) {
         return executor.executeVoid(deleteMail.bind()
             .setString(REPOSITORY_NAME, url.asString())
             .setString(MAIL_KEY, key.asString()));
     }
 
+    @Override
     public CompletableFuture<Optional<MailDTO>> read(MailRepositoryUrl url, MailKey key) {
         return executor.executeSingleRow(selectMail.bind()
             .setString(REPOSITORY_NAME, url.asString())
@@ -277,44 +281,4 @@ public class CassandraMailRepositoryMailDAO {
         }
     }
 
-    public class MailDTO {
-        private final MailImpl.Builder mailBuilder;
-        private final BlobId headerBlobId;
-        private final BlobId bodyBlobId;
-
-        public MailDTO(MailImpl.Builder mailBuilder, BlobId headerBlobId, BlobId bodyBlobId) {
-            this.mailBuilder = mailBuilder;
-            this.headerBlobId = headerBlobId;
-            this.bodyBlobId = bodyBlobId;
-        }
-
-        public MailImpl.Builder getMailBuilder() {
-            return mailBuilder;
-        }
-
-        public BlobId getHeaderBlobId() {
-            return headerBlobId;
-        }
-
-        public BlobId getBodyBlobId() {
-            return bodyBlobId;
-        }
-
-        @Override
-        public final boolean equals(Object o) {
-            if (o instanceof MailDTO) {
-                MailDTO mailDTO = (MailDTO) o;
-
-                return Objects.equals(this.mailBuilder.build(), mailDTO.mailBuilder.build())
-                    && Objects.equals(this.headerBlobId, mailDTO.headerBlobId)
-                    && Objects.equals(this.bodyBlobId, mailDTO.bodyBlobId);
-            }
-            return false;
-        }
-
-        @Override
-        public final int hashCode() {
-            return Objects.hash(mailBuilder.build(), headerBlobId, bodyBlobId);
-        }
-    }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/7a2e552f/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDaoAPI.java
----------------------------------------------------------------------
diff --git a/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDaoAPI.java b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDaoAPI.java
new file mode 100644
index 0000000..435bcf1
--- /dev/null
+++ b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDaoAPI.java
@@ -0,0 +1,81 @@
+/****************************************************************
+ * 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.mailrepository.cassandra;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+
+import javax.mail.MessagingException;
+
+import org.apache.james.blob.api.BlobId;
+import org.apache.james.mailrepository.api.MailKey;
+import org.apache.james.mailrepository.api.MailRepositoryUrl;
+import org.apache.james.server.core.MailImpl;
+import org.apache.mailet.Mail;
+
+public interface CassandraMailRepositoryMailDaoAPI {
+    CompletableFuture<Void> store(MailRepositoryUrl url, Mail mail, BlobId headerId, BlobId bodyId) throws MessagingException;
+
+    CompletableFuture<Void> remove(MailRepositoryUrl url, MailKey key);
+
+    CompletableFuture<Optional<MailDTO>> read(MailRepositoryUrl url, MailKey key);
+
+    class MailDTO {
+        private final MailImpl.Builder mailBuilder;
+        private final BlobId headerBlobId;
+        private final BlobId bodyBlobId;
+
+        public MailDTO(MailImpl.Builder mailBuilder, BlobId headerBlobId, BlobId bodyBlobId) {
+            this.mailBuilder = mailBuilder;
+            this.headerBlobId = headerBlobId;
+            this.bodyBlobId = bodyBlobId;
+        }
+
+        public MailImpl.Builder getMailBuilder() {
+            return mailBuilder;
+        }
+
+        public BlobId getHeaderBlobId() {
+            return headerBlobId;
+        }
+
+        public BlobId getBodyBlobId() {
+            return bodyBlobId;
+        }
+
+        @Override
+        public final boolean equals(Object o) {
+            if (o instanceof MailDTO) {
+                MailDTO mailDTO = (MailDTO) o;
+
+                return Objects.equals(this.mailBuilder.build(), mailDTO.mailBuilder.build())
+                    && Objects.equals(this.headerBlobId, mailDTO.headerBlobId)
+                    && Objects.equals(this.bodyBlobId, mailDTO.bodyBlobId);
+            }
+            return false;
+        }
+
+        @Override
+        public final int hashCode() {
+            return Objects.hash(mailBuilder.build(), headerBlobId, bodyBlobId);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/7a2e552f/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryProvider.java
----------------------------------------------------------------------
diff --git a/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryProvider.java b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryProvider.java
index d5c1077..410c46b 100644
--- a/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryProvider.java
+++ b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryProvider.java
@@ -29,12 +29,12 @@ import org.apache.james.mailrepository.api.MailRepositoryUrl;
 public class CassandraMailRepositoryProvider implements MailRepositoryProvider {
     private final CassandraMailRepositoryKeysDAO keysDAO;
     private final CassandraMailRepositoryCountDAO countDAO;
-    private final CassandraMailRepositoryMailDAO mailDAO;
+    private final CassandraMailRepositoryMailDaoAPI mailDAO;
     private final MimeMessageStore.Factory mimeMessageStoreFactory;
 
     @Inject
     public CassandraMailRepositoryProvider(CassandraMailRepositoryKeysDAO keysDAO, CassandraMailRepositoryCountDAO countDAO,
-                                           CassandraMailRepositoryMailDAO mailDAO, MimeMessageStore.Factory mimeMessageStoreFactory) {
+                                           CassandraMailRepositoryMailDaoAPI mailDAO, MimeMessageStore.Factory mimeMessageStoreFactory) {
         this.keysDAO = keysDAO;
         this.countDAO = countDAO;
         this.mailDAO = mailDAO;

http://git-wip-us.apache.org/repos/asf/james-project/blob/7a2e552f/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java
----------------------------------------------------------------------
diff --git a/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java b/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java
index de19dee..582a7ba 100644
--- a/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java
+++ b/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java
@@ -33,6 +33,7 @@ import org.apache.mailet.PerRecipientHeaders;
 import org.apache.mailet.base.MailAddressFixture;
 import org.apache.mailet.base.test.FakeMail;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
@@ -46,109 +47,128 @@ class CassandraMailRepositoryMailDAOTest {
     @RegisterExtension
     static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(CassandraMailRepositoryModule.MODULE);
 
-    CassandraMailRepositoryMailDAO testee;
 
-    @BeforeEach
-    void setUp(CassandraCluster cassandra) {
-        testee = new CassandraMailRepositoryMailDAO(cassandra.getConf(), BLOB_ID_FACTORY, cassandra.getTypesProvider());
-    }
-
-    @Test
-    void readShouldReturnEmptyWhenAbsent() {
-        assertThat(testee.read(URL, KEY_1).join())
-            .isEmpty();
-    }
-
-    @Test
-    void readShouldReturnAllMailMetadata() throws Exception {
-        BlobId blobIdBody = BLOB_ID_FACTORY.from("blobHeader");
-        BlobId blobIdHeader = BLOB_ID_FACTORY.from("blobBody");
-        String errorMessage = "error message";
-        String state = "state";
-        String remoteAddr = "remoteAddr";
-        String remoteHost = "remoteHost";
-        PerRecipientHeaders.Header header = PerRecipientHeaders.Header.builder().name("headerName").value("headerValue").build();
-        String attributeName = "att1";
-        ImmutableList<String> attributeValue = ImmutableList.of("value1", "value2");
-
-        testee.store(URL,
-            FakeMail.builder()
-                .name(KEY_1.asString())
-                .sender(MailAddressFixture.SENDER)
-                .recipients(MailAddressFixture.RECIPIENT1, MailAddressFixture.RECIPIENT2)
-                .errorMessage(errorMessage)
-                .state(state)
-                .remoteAddr(remoteAddr)
-                .remoteHost(remoteHost)
-                .addHeaderForRecipient(header, MailAddressFixture.RECIPIENT1)
-                .attribute(attributeName, attributeValue)
-                .build(),
-            blobIdHeader,
-            blobIdBody)
-            .join();
-
-        CassandraMailRepositoryMailDAO.MailDTO mailDTO = testee.read(URL, KEY_1).join().get();
-
-        Mail partialMail = mailDTO.getMailBuilder().build();
-        assertSoftly(softly -> {
-            softly.assertThat(mailDTO.getBodyBlobId()).isEqualTo(blobIdBody);
-            softly.assertThat(mailDTO.getHeaderBlobId()).isEqualTo(blobIdHeader);
-            softly.assertThat(partialMail.getName()).isEqualTo(KEY_1.asString());
-            softly.assertThat(partialMail.getErrorMessage()).isEqualTo(errorMessage);
-            softly.assertThat(partialMail.getState()).isEqualTo(state);
-            softly.assertThat(partialMail.getRemoteAddr()).isEqualTo(remoteAddr);
-            softly.assertThat(partialMail.getRemoteHost()).isEqualTo(remoteHost);
-            softly.assertThat(partialMail.getAttributeNames()).containsOnly(attributeName);
-            softly.assertThat(partialMail.getAttribute(attributeName)).isEqualTo(attributeValue);
-            softly.assertThat(partialMail.getPerRecipientSpecificHeaders().getRecipientsWithSpecificHeaders())
+    abstract class TestSuite {
+
+        abstract CassandraMailRepositoryMailDaoAPI testee();
+
+        @Test
+        void readShouldReturnAllMailMetadata() throws Exception {
+            CassandraMailRepositoryMailDaoAPI testee = testee();
+
+            BlobId blobIdBody = BLOB_ID_FACTORY.from("blobHeader");
+            BlobId blobIdHeader = BLOB_ID_FACTORY.from("blobBody");
+            String errorMessage = "error message";
+            String state = "state";
+            String remoteAddr = "remoteAddr";
+            String remoteHost = "remoteHost";
+            PerRecipientHeaders.Header header = PerRecipientHeaders.Header.builder().name("headerName").value("headerValue").build();
+            String attributeName = "att1";
+            ImmutableList<String> attributeValue = ImmutableList.of("value1", "value2");
+
+            testee.store(URL,
+                FakeMail.builder()
+                    .name(KEY_1.asString())
+                    .sender(MailAddressFixture.SENDER)
+                    .recipients(MailAddressFixture.RECIPIENT1, MailAddressFixture.RECIPIENT2)
+                    .errorMessage(errorMessage)
+                    .state(state)
+                    .remoteAddr(remoteAddr)
+                    .remoteHost(remoteHost)
+                    .addHeaderForRecipient(header, MailAddressFixture.RECIPIENT1)
+                    .attribute(attributeName, attributeValue)
+                    .build(),
+                blobIdHeader,
+                blobIdBody)
+                .join();
+
+            CassandraMailRepositoryMailDAO.MailDTO mailDTO = testee.read(URL, KEY_1).join().get();
+
+            Mail partialMail = mailDTO.getMailBuilder().build();
+            assertSoftly(softly -> {
+                softly.assertThat(mailDTO.getBodyBlobId()).isEqualTo(blobIdBody);
+                softly.assertThat(mailDTO.getHeaderBlobId()).isEqualTo(blobIdHeader);
+                softly.assertThat(partialMail.getName()).isEqualTo(KEY_1.asString());
+                softly.assertThat(partialMail.getErrorMessage()).isEqualTo(errorMessage);
+                softly.assertThat(partialMail.getState()).isEqualTo(state);
+                softly.assertThat(partialMail.getRemoteAddr()).isEqualTo(remoteAddr);
+                softly.assertThat(partialMail.getRemoteHost()).isEqualTo(remoteHost);
+                softly.assertThat(partialMail.getAttributeNames()).containsOnly(attributeName);
+                softly.assertThat(partialMail.getAttribute(attributeName)).isEqualTo(attributeValue);
+                softly.assertThat(partialMail.getPerRecipientSpecificHeaders().getRecipientsWithSpecificHeaders())
                     .containsOnly(MailAddressFixture.RECIPIENT1);
-            softly.assertThat(partialMail.getPerRecipientSpecificHeaders().getHeadersForRecipient(MailAddressFixture.RECIPIENT1))
+                softly.assertThat(partialMail.getPerRecipientSpecificHeaders().getHeadersForRecipient(MailAddressFixture.RECIPIENT1))
                     .containsOnly(header);
-            softly.assertThat(partialMail.getMaybeSender().asOptional()).contains(MailAddressFixture.SENDER);
-            softly.assertThat(partialMail.getRecipients()).containsOnly(MailAddressFixture.RECIPIENT1, MailAddressFixture.RECIPIENT2);
-        });
+                softly.assertThat(partialMail.getMaybeSender().asOptional()).contains(MailAddressFixture.SENDER);
+                softly.assertThat(partialMail.getRecipients()).containsOnly(MailAddressFixture.RECIPIENT1, MailAddressFixture.RECIPIENT2);
+            });
+        }
+
+        @Test
+        void storeShouldAcceptMailWithOnlyName() throws Exception {
+            CassandraMailRepositoryMailDaoAPI testee = testee();
+            BlobId blobIdBody = BLOB_ID_FACTORY.from("blobHeader");
+            BlobId blobIdHeader = BLOB_ID_FACTORY.from("blobBody");
+
+            testee.store(URL,
+                FakeMail.builder()
+                    .name(KEY_1.asString())
+                    .build(),
+                blobIdHeader,
+                blobIdBody)
+                .join();
+
+            CassandraMailRepositoryMailDAO.MailDTO mailDTO = testee.read(URL, KEY_1).join().get();
+
+            Mail partialMail = mailDTO.getMailBuilder().build();
+            assertSoftly(softly -> {
+                softly.assertThat(mailDTO.getBodyBlobId()).isEqualTo(blobIdBody);
+                softly.assertThat(mailDTO.getHeaderBlobId()).isEqualTo(blobIdHeader);
+                softly.assertThat(partialMail.getName()).isEqualTo(KEY_1.asString());
+            });
+        }
+
+        @Test
+        void removeShouldDeleteMailMetaData() throws Exception {
+            CassandraMailRepositoryMailDaoAPI testee = testee();
+            BlobId blobIdBody = BLOB_ID_FACTORY.from("blobHeader");
+            BlobId blobIdHeader = BLOB_ID_FACTORY.from("blobBody");
+
+            testee.store(URL,
+                FakeMail.builder()
+                    .name(KEY_1.asString())
+                    .build(),
+                blobIdHeader,
+                blobIdBody)
+                .join();
+
+            testee.remove(URL, KEY_1).join();
+
+            assertThat(testee.read(URL, KEY_1).join())
+                .isEmpty();
+        }
+
+
+        @Test
+        void readShouldReturnEmptyWhenAbsent() {
+            assertThat(testee().read(URL, KEY_1).join())
+                .isEmpty();
+        }
     }
 
-    @Test
-    void storeShouldAcceptMailWithOnlyName() throws Exception {
-        BlobId blobIdBody = BLOB_ID_FACTORY.from("blobHeader");
-        BlobId blobIdHeader = BLOB_ID_FACTORY.from("blobBody");
-
-        testee.store(URL,
-            FakeMail.builder()
-                .name(KEY_1.asString())
-                .build(),
-            blobIdHeader,
-            blobIdBody)
-            .join();
-
-        CassandraMailRepositoryMailDAO.MailDTO mailDTO = testee.read(URL, KEY_1).join().get();
-
-        Mail partialMail = mailDTO.getMailBuilder().build();
-        assertSoftly(softly -> {
-            softly.assertThat(mailDTO.getBodyBlobId()).isEqualTo(blobIdBody);
-            softly.assertThat(mailDTO.getHeaderBlobId()).isEqualTo(blobIdHeader);
-            softly.assertThat(partialMail.getName()).isEqualTo(KEY_1.asString());
-        });
-    }
-
-    @Test
-    void removeShouldDeleteMailMetaData() throws Exception {
-        BlobId blobIdBody = BLOB_ID_FACTORY.from("blobHeader");
-        BlobId blobIdHeader = BLOB_ID_FACTORY.from("blobBody");
+    @Nested
+    class v1 extends TestSuite {
 
-        testee.store(URL,
-            FakeMail.builder()
-                .name(KEY_1.asString())
-                .build(),
-            blobIdHeader,
-            blobIdBody)
-            .join();
+        private CassandraMailRepositoryMailDAO testee;
 
-        testee.remove(URL, KEY_1).join();
+        @BeforeEach
+        void setUp(CassandraCluster cassandra) {
+            testee = new CassandraMailRepositoryMailDAO(cassandra.getConf(), BLOB_ID_FACTORY, cassandra.getTypesProvider());
+        }
 
-        assertThat(testee.read(URL, KEY_1).join())
-            .isEmpty();
+        @Override
+        CassandraMailRepositoryMailDaoAPI testee() {
+            return testee;
+        }
     }
-
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/7a2e552f/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryWithFakeImplementationsTest.java
----------------------------------------------------------------------
diff --git a/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryWithFakeImplementationsTest.java b/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryWithFakeImplementationsTest.java
index ce56641..ca2eb2c 100644
--- a/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryWithFakeImplementationsTest.java
+++ b/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryWithFakeImplementationsTest.java
@@ -24,15 +24,14 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 
-import javax.mail.MessagingException;
 import javax.mail.internet.MimeMessage;
 
 import org.apache.james.backends.cassandra.CassandraCluster;
 import org.apache.james.backends.cassandra.CassandraClusterExtension;
 import org.apache.james.backends.cassandra.components.CassandraModule;
-import org.apache.james.backends.cassandra.init.CassandraTypesProvider;
 import org.apache.james.backends.cassandra.utils.CassandraUtils;
 import org.apache.james.blob.api.BlobId;
 import org.apache.james.blob.api.HashBlobId;
@@ -82,7 +81,7 @@ class CassandraMailRepositoryWithFakeImplementationsTest {
 
         @BeforeEach
         void setup(CassandraCluster cassandra) {
-            CassandraMailRepositoryMailDAO mailDAO = new CassandraMailRepositoryMailDAO(cassandra.getConf(), BLOB_ID_FACTORY, cassandra.getTypesProvider());
+            CassandraMailRepositoryMailDaoAPI mailDAO = new CassandraMailRepositoryMailDAO(cassandra.getConf(), BLOB_ID_FACTORY, cassandra.getTypesProvider());
             keysDAO = new CassandraMailRepositoryKeysDAO(cassandra.getConf(), CassandraUtils.WITH_DEFAULT_CONFIGURATION);
             CassandraMailRepositoryCountDAO countDAO = new CassandraMailRepositoryCountDAO(cassandra.getConf());
 
@@ -134,7 +133,7 @@ class CassandraMailRepositoryWithFakeImplementationsTest {
 
         @BeforeEach
         void setup(CassandraCluster cassandra) {
-            FailingMailDAO mailDAO = new FailingMailDAO(cassandra.getConf(), BLOB_ID_FACTORY, cassandra.getTypesProvider());
+            FailingMailDAO mailDAO = new FailingMailDAO();
             keysDAO = new CassandraMailRepositoryKeysDAO(cassandra.getConf(), CassandraUtils.WITH_DEFAULT_CONFIGURATION);
             CassandraMailRepositoryCountDAO countDAO = new CassandraMailRepositoryCountDAO(cassandra.getConf());
             CassandraBlobsDAO blobsDAO = new CassandraBlobsDAO(cassandra.getConf());
@@ -143,18 +142,32 @@ class CassandraMailRepositoryWithFakeImplementationsTest {
                     keysDAO, countDAO, mailDAO, MimeMessageStore.factory(blobsDAO).mimeMessageStore());
         }
 
-        class FailingMailDAO extends CassandraMailRepositoryMailDAO {
+        class FailingMailDAO implements CassandraMailRepositoryMailDaoAPI {
 
-            public FailingMailDAO(Session session, BlobId.Factory blobIdFactory, CassandraTypesProvider cassandraTypesProvider) {
-                super(session, blobIdFactory, cassandraTypesProvider);
+            FailingMailDAO() {
             }
 
             @Override
-            public CompletableFuture<Void> store(MailRepositoryUrl url, Mail mail, BlobId headerId, BlobId bodyId) throws MessagingException {
+            public CompletableFuture<Void> store(MailRepositoryUrl url, Mail mail, BlobId headerId, BlobId bodyId) {
                 return CompletableFuture.supplyAsync(() -> {
                     throw new RuntimeException("Expected failure while storing mail parts");
                 });
             }
+
+            @Override
+            public CompletableFuture<Void> remove(MailRepositoryUrl url, MailKey key) {
+                return CompletableFuture.supplyAsync(() -> {
+                    throw new RuntimeException("Expected failure while remeving mail parts");
+                });
+
+            }
+
+            @Override
+            public CompletableFuture<Optional<CassandraMailRepositoryMailDAO.MailDTO>> read(MailRepositoryUrl url, MailKey key) {
+                return CompletableFuture.supplyAsync(() -> {
+                    throw new RuntimeException("Expected failure while reading mail parts");
+                });
+            }
         }
 
         @Test
@@ -205,7 +218,7 @@ class CassandraMailRepositoryWithFakeImplementationsTest {
 
         @BeforeEach
         void setup(CassandraCluster cassandra) {
-            CassandraMailRepositoryMailDAO mailDAO = new CassandraMailRepositoryMailDAO(cassandra.getConf(), BLOB_ID_FACTORY, cassandra.getTypesProvider());
+            CassandraMailRepositoryMailDaoAPI mailDAO = new CassandraMailRepositoryMailDAO(cassandra.getConf(), BLOB_ID_FACTORY, cassandra.getTypesProvider());
             FailingKeysDAO keysDAO = new FailingKeysDAO(cassandra.getConf(), CassandraUtils.WITH_DEFAULT_CONFIGURATION);
             countDAO = new CassandraMailRepositoryCountDAO(cassandra.getConf());
             CassandraBlobsDAO blobsDAO = new CassandraBlobsDAO(cassandra.getConf());
@@ -216,7 +229,7 @@ class CassandraMailRepositoryWithFakeImplementationsTest {
 
         class FailingKeysDAO extends CassandraMailRepositoryKeysDAO {
 
-            public FailingKeysDAO(Session session, CassandraUtils cassandraUtils) {
+            FailingKeysDAO(Session session, CassandraUtils cassandraUtils) {
                 super(session, cassandraUtils);
             }
 


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


[8/8] james-project git commit: JAMES-2608 Remove MESSAGE_SIZE from MailRepositoryTableV2

Posted by ro...@apache.org.
JAMES-2608 Remove MESSAGE_SIZE from MailRepositoryTableV2


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

Branch: refs/heads/master
Commit: 1ebe3fde75d170129c5d5e90a68f2174c3d464df
Parents: 31dd1b2
Author: Antoine Duprat <ad...@linagora.com>
Authored: Thu Dec 13 14:26:18 2018 +0100
Committer: Raphael Ouazana <ra...@linagora.com>
Committed: Wed Dec 19 09:24:12 2018 +0100

----------------------------------------------------------------------
 .../cassandra/CassandraMailRepositoryMailDaoV2.java               | 3 ---
 .../james/mailrepository/cassandra/MailRepositoryTableV2.java     | 3 +--
 2 files changed, 1 insertion(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/1ebe3fde/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDaoV2.java
----------------------------------------------------------------------
diff --git a/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDaoV2.java b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDaoV2.java
index 10b44b1..156fa85 100644
--- a/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDaoV2.java
+++ b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDaoV2.java
@@ -36,7 +36,6 @@ import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.He
 import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.LAST_UPDATED;
 import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.MAIL_KEY;
 import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.MAIL_PROPERTIES;
-import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.MESSAGE_SIZE;
 import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.PER_RECIPIENT_SPECIFIC_HEADERS;
 import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.RECIPIENTS;
 import static org.apache.james.mailrepository.cassandra.MailRepositoryTableV2.REMOTE_ADDR;
@@ -118,7 +117,6 @@ public class CassandraMailRepositoryMailDaoV2 implements CassandraMailRepository
         return session.prepare(insertInto(CONTENT_TABLE_NAME)
             .value(REPOSITORY_NAME, bindMarker(REPOSITORY_NAME))
             .value(MAIL_KEY, bindMarker(MAIL_KEY))
-            .value(MESSAGE_SIZE, bindMarker(MESSAGE_SIZE))
             .value(STATE, bindMarker(STATE))
             .value(SENDER, bindMarker(SENDER))
             .value(RECIPIENTS, bindMarker(RECIPIENTS))
@@ -152,7 +150,6 @@ public class CassandraMailRepositoryMailDaoV2 implements CassandraMailRepository
             .setString(ERROR_MESSAGE, mail.getErrorMessage())
             .setString(REMOTE_ADDR, mail.getRemoteAddr())
             .setString(REMOTE_HOST, mail.getRemoteHost())
-            .setLong(MESSAGE_SIZE, mail.getMessageSize())
             .setTimestamp(LAST_UPDATED, mail.getLastUpdated())
             .setMap(ATTRIBUTES, toRawAttributeMap(mail))
             .setList(PER_RECIPIENT_SPECIFIC_HEADERS, toTupleList(mail.getPerRecipientSpecificHeaders()))

http://git-wip-us.apache.org/repos/asf/james-project/blob/1ebe3fde/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/MailRepositoryTableV2.java
----------------------------------------------------------------------
diff --git a/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/MailRepositoryTableV2.java b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/MailRepositoryTableV2.java
index 54e2e45..30b9ea5 100644
--- a/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/MailRepositoryTableV2.java
+++ b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/MailRepositoryTableV2.java
@@ -25,7 +25,6 @@ public interface MailRepositoryTableV2 {
     String REPOSITORY_NAME = "name";
 
     String MAIL_KEY = "mailKey";
-    String MESSAGE_SIZE = "messageSize";
     String HEADER_BLOB_ID = "headerBlobId";
     String BODY_BLOB_ID = "bodyBlobId";
     String STATE = "state";
@@ -44,6 +43,6 @@ public interface MailRepositoryTableV2 {
         int HEADER_VALUE_INDEX = 2;
     }
 
-    String[] MAIL_PROPERTIES = { MAIL_KEY, MESSAGE_SIZE, STATE, SENDER, RECIPIENTS, ATTRIBUTES, ERROR_MESSAGE, REMOTE_ADDR,
+    String[] MAIL_PROPERTIES = { MAIL_KEY, STATE, SENDER, RECIPIENTS, ATTRIBUTES, ERROR_MESSAGE, REMOTE_ADDR,
         REMOTE_HOST, LAST_UPDATED, PER_RECIPIENT_SPECIFIC_HEADERS, HEADER_BLOB_ID, BODY_BLOB_ID };
 }


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


[2/8] james-project git commit: JAMES-2608 finally bind MergingCassandraMailRepositoryMailDao and enable the broken test

Posted by ro...@apache.org.
JAMES-2608 finally bind MergingCassandraMailRepositoryMailDao and enable the broken test


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

Branch: refs/heads/master
Commit: 31dd1b251d2e98deb8758318aef8027f784473ef
Parents: 48b99bf
Author: Matthieu Baechler <ma...@apache.org>
Authored: Wed Nov 28 19:17:12 2018 +0100
Committer: Raphael Ouazana <ra...@linagora.com>
Committed: Wed Dec 19 09:24:12 2018 +0100

----------------------------------------------------------------------
 .../james/modules/data/CassandraMailRepositoryModule.java      | 6 ++++++
 .../apache/james/mailrepository/MailRepositoryContract.java    | 2 --
 .../mailrepository/cassandra/CassandraMailRepositoryTest.java  | 4 +++-
 3 files changed, 9 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/31dd1b25/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/data/CassandraMailRepositoryModule.java
----------------------------------------------------------------------
diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/data/CassandraMailRepositoryModule.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/data/CassandraMailRepositoryModule.java
index 176c39b..60fa72f 100644
--- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/data/CassandraMailRepositoryModule.java
+++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/data/CassandraMailRepositoryModule.java
@@ -25,9 +25,12 @@ import org.apache.james.mailrepository.api.MailRepositoryUrlStore;
 import org.apache.james.mailrepository.cassandra.CassandraMailRepositoryCountDAO;
 import org.apache.james.mailrepository.cassandra.CassandraMailRepositoryKeysDAO;
 import org.apache.james.mailrepository.cassandra.CassandraMailRepositoryMailDAO;
+import org.apache.james.mailrepository.cassandra.CassandraMailRepositoryMailDaoAPI;
+import org.apache.james.mailrepository.cassandra.CassandraMailRepositoryMailDaoV2;
 import org.apache.james.mailrepository.cassandra.CassandraMailRepositoryProvider;
 import org.apache.james.mailrepository.cassandra.CassandraMailRepositoryUrlModule;
 import org.apache.james.mailrepository.cassandra.CassandraMailRepositoryUrlStore;
+import org.apache.james.mailrepository.cassandra.MergingCassandraMailRepositoryMailDao;
 
 import com.google.inject.AbstractModule;
 import com.google.inject.Scopes;
@@ -40,7 +43,10 @@ public class CassandraMailRepositoryModule extends AbstractModule {
         bind(CassandraMailRepositoryKeysDAO.class).in(Scopes.SINGLETON);
         bind(CassandraMailRepositoryCountDAO.class).in(Scopes.SINGLETON);
         bind(CassandraMailRepositoryMailDAO.class).in(Scopes.SINGLETON);
+        bind(CassandraMailRepositoryMailDaoV2.class).in(Scopes.SINGLETON);
+        bind(MergingCassandraMailRepositoryMailDao.class).in(Scopes.SINGLETON);
 
+        bind(CassandraMailRepositoryMailDaoAPI.class).to(MergingCassandraMailRepositoryMailDao.class);
         bind(MailRepositoryUrlStore.class).to(CassandraMailRepositoryUrlStore.class);
 
         Multibinder<MailRepositoryProvider> multibinder = Multibinder.newSetBinder(binder(), MailRepositoryProvider.class);

http://git-wip-us.apache.org/repos/asf/james-project/blob/31dd1b25/server/mailrepository/mailrepository-api/src/test/java/org/apache/james/mailrepository/MailRepositoryContract.java
----------------------------------------------------------------------
diff --git a/server/mailrepository/mailrepository-api/src/test/java/org/apache/james/mailrepository/MailRepositoryContract.java b/server/mailrepository/mailrepository-api/src/test/java/org/apache/james/mailrepository/MailRepositoryContract.java
index e106015..ee74d10 100644
--- a/server/mailrepository/mailrepository-api/src/test/java/org/apache/james/mailrepository/MailRepositoryContract.java
+++ b/server/mailrepository/mailrepository-api/src/test/java/org/apache/james/mailrepository/MailRepositoryContract.java
@@ -46,7 +46,6 @@ import org.apache.mailet.Mail;
 import org.apache.mailet.PerRecipientHeaders;
 import org.apache.mailet.base.MailAddressFixture;
 import org.apache.mailet.base.test.FakeMail;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.RepeatedTest;
 import org.junit.jupiter.api.Test;
 
@@ -421,7 +420,6 @@ public interface MailRepositoryContract {
     }
 
 
-    @Disabled("JAMES-2608")
     @Test
     default void storingMessageWithPerRecipientHeadersShouldAllowMultipleHeadersPerUser() throws Exception {
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/31dd1b25/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryTest.java
----------------------------------------------------------------------
diff --git a/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryTest.java b/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryTest.java
index b0c059e..dbd39a3 100644
--- a/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryTest.java
+++ b/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryTest.java
@@ -52,7 +52,9 @@ class CassandraMailRepositoryTest implements MailRepositoryContract {
 
     @BeforeEach
     void setup(CassandraCluster cassandra) {
-        CassandraMailRepositoryMailDAO mailDAO = new CassandraMailRepositoryMailDAO(cassandra.getConf(), BLOB_ID_FACTORY, cassandra.getTypesProvider());
+        CassandraMailRepositoryMailDAO v1 = new CassandraMailRepositoryMailDAO(cassandra.getConf(), BLOB_ID_FACTORY, cassandra.getTypesProvider());
+        CassandraMailRepositoryMailDaoV2 v2 = new CassandraMailRepositoryMailDaoV2(cassandra.getConf(), BLOB_ID_FACTORY);
+        CassandraMailRepositoryMailDaoAPI mailDAO = new MergingCassandraMailRepositoryMailDao(v1, v2);
         CassandraMailRepositoryKeysDAO keysDAO = new CassandraMailRepositoryKeysDAO(cassandra.getConf(), CassandraUtils.WITH_DEFAULT_CONFIGURATION);
         CassandraMailRepositoryCountDAO countDAO = new CassandraMailRepositoryCountDAO(cassandra.getConf());
         CassandraBlobsDAO blobsDAO = new CassandraBlobsDAO(cassandra.getConf());


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


[5/8] james-project git commit: JAMES-2608 implement MergingCassandraMailRepositoryMailDao to handle on-the-fly data migration

Posted by ro...@apache.org.
JAMES-2608 implement MergingCassandraMailRepositoryMailDao to handle on-the-fly data migration


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

Branch: refs/heads/master
Commit: 48b99bf7b00862ee4e7fcef7b08b44c61fdcbc57
Parents: d0f973b
Author: Matthieu Baechler <ma...@apache.org>
Authored: Wed Nov 28 19:11:44 2018 +0100
Committer: Raphael Ouazana <ra...@linagora.com>
Committed: Wed Dec 19 09:24:12 2018 +0100

----------------------------------------------------------------------
 .../MergingCassandraMailRepositoryMailDao.java  |  64 +++++++++
 .../CassandraMailRepositoryMailDAOTest.java     | 135 ++++++++++++++++++-
 2 files changed, 197 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/48b99bf7/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/MergingCassandraMailRepositoryMailDao.java
----------------------------------------------------------------------
diff --git a/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/MergingCassandraMailRepositoryMailDao.java b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/MergingCassandraMailRepositoryMailDao.java
new file mode 100644
index 0000000..8b01a38
--- /dev/null
+++ b/server/mailrepository/mailrepository-cassandra/src/main/java/org/apache/james/mailrepository/cassandra/MergingCassandraMailRepositoryMailDao.java
@@ -0,0 +1,64 @@
+/****************************************************************
+ * 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.mailrepository.cassandra;
+
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+
+import javax.inject.Inject;
+import javax.mail.MessagingException;
+
+import org.apache.james.blob.api.BlobId;
+import org.apache.james.mailrepository.api.MailKey;
+import org.apache.james.mailrepository.api.MailRepositoryUrl;
+import org.apache.james.util.OptionalUtils;
+import org.apache.mailet.Mail;
+
+import com.google.common.annotations.VisibleForTesting;
+
+public class MergingCassandraMailRepositoryMailDao implements CassandraMailRepositoryMailDaoAPI {
+
+    private final CassandraMailRepositoryMailDAO v1;
+    private final CassandraMailRepositoryMailDaoV2 v2;
+
+    @Inject
+    @VisibleForTesting
+    MergingCassandraMailRepositoryMailDao(CassandraMailRepositoryMailDAO v1, CassandraMailRepositoryMailDaoV2 v2) {
+        this.v1 = v1;
+        this.v2 = v2;
+    }
+
+    @Override
+    public CompletableFuture<Void> store(MailRepositoryUrl url, Mail mail, BlobId headerId, BlobId bodyId) throws MessagingException {
+        return v2.store(url, mail, headerId, bodyId);
+    }
+
+    @Override
+    public CompletableFuture<Void> remove(MailRepositoryUrl url, MailKey key) {
+        return CompletableFuture.allOf(v1.remove(url, key), v2.remove(url, key));
+    }
+
+    @Override
+    public CompletableFuture<Optional<MailDTO>> read(MailRepositoryUrl url, MailKey key) {
+        return v2.read(url, key)
+            .thenCombine(v1.read(url, key),
+                (maybeV2Value, maybeV1Value) -> OptionalUtils.or(maybeV2Value, maybeV1Value));
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/48b99bf7/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java
----------------------------------------------------------------------
diff --git a/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java b/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java
index c02966a..a50c69e 100644
--- a/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java
+++ b/server/mailrepository/mailrepository-cassandra/src/test/java/org/apache/james/mailrepository/cassandra/CassandraMailRepositoryMailDAOTest.java
@@ -22,6 +22,10 @@ package org.apache.james.mailrepository.cassandra;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.SoftAssertions.assertSoftly;
 
+import java.util.Optional;
+
+import javax.mail.MessagingException;
+
 import org.apache.james.backends.cassandra.CassandraCluster;
 import org.apache.james.backends.cassandra.CassandraClusterExtension;
 import org.apache.james.blob.api.BlobId;
@@ -157,7 +161,7 @@ class CassandraMailRepositoryMailDAOTest {
     }
 
     @Nested
-    class v1 extends TestSuite {
+    class V1 extends TestSuite {
 
         private CassandraMailRepositoryMailDAO testee;
 
@@ -173,7 +177,7 @@ class CassandraMailRepositoryMailDAOTest {
     }
 
     @Nested
-    class v2 extends TestSuite {
+    class V2 extends TestSuite {
 
         private CassandraMailRepositoryMailDaoV2 testee;
 
@@ -187,4 +191,131 @@ class CassandraMailRepositoryMailDAOTest {
             return testee;
         }
     }
+
+    @Nested
+    class Merging extends TestSuite {
+
+        private MergingCassandraMailRepositoryMailDao testee;
+        private CassandraMailRepositoryMailDAO v1;
+        private CassandraMailRepositoryMailDaoV2 v2;
+
+        @BeforeEach
+        void setUp(CassandraCluster cassandra) {
+            v1 = new CassandraMailRepositoryMailDAO(cassandra.getConf(), BLOB_ID_FACTORY, cassandra.getTypesProvider());
+            v2 = new CassandraMailRepositoryMailDaoV2(cassandra.getConf(), BLOB_ID_FACTORY);
+            testee = new MergingCassandraMailRepositoryMailDao(v1, v2);
+        }
+
+        @Override
+        CassandraMailRepositoryMailDaoAPI testee() {
+            return testee;
+        }
+
+        @Test
+        void readShouldReturnV1Value() throws MessagingException {
+            BlobId blobIdBody = BLOB_ID_FACTORY.from("blobHeader");
+            BlobId blobIdHeader = BLOB_ID_FACTORY.from("blobBody");
+
+            v1.store(URL,
+                FakeMail.builder()
+                    .name(KEY_1.asString())
+                    .build(),
+                blobIdHeader,
+                blobIdBody)
+                .join();
+
+            CassandraMailRepositoryMailDaoAPI.MailDTO actual = testee.read(URL, KEY_1).join().get();
+            Mail partialMail = actual.getMailBuilder().build();
+            assertSoftly(softly -> {
+                softly.assertThat(actual.getBodyBlobId()).isEqualTo(blobIdBody);
+                softly.assertThat(actual.getHeaderBlobId()).isEqualTo(blobIdHeader);
+                softly.assertThat(partialMail.getName()).isEqualTo(KEY_1.asString());
+            });
+        }
+
+        @Test
+        void readShouldReturnV2Value() throws MessagingException {
+            BlobId blobIdBody = BLOB_ID_FACTORY.from("blobHeader");
+            BlobId blobIdHeader = BLOB_ID_FACTORY.from("blobBody");
+
+            v2.store(URL,
+                FakeMail.builder()
+                    .name(KEY_1.asString())
+                    .build(),
+                blobIdHeader,
+                blobIdBody)
+                .join();
+
+            CassandraMailRepositoryMailDaoAPI.MailDTO actual = testee.read(URL, KEY_1).join().get();
+            Mail partialMail = actual.getMailBuilder().build();
+            assertSoftly(softly -> {
+                softly.assertThat(actual.getBodyBlobId()).isEqualTo(blobIdBody);
+                softly.assertThat(actual.getHeaderBlobId()).isEqualTo(blobIdHeader);
+                softly.assertThat(partialMail.getName()).isEqualTo(KEY_1.asString());
+            });
+        }
+
+        @Test
+        void readShouldReturnV2ValueIfPresentInBoth() throws MessagingException {
+            BlobId blobIdBody1 = BLOB_ID_FACTORY.from("blobHeader");
+            BlobId blobIdBody2 = BLOB_ID_FACTORY.from("blobHeader2");
+            BlobId blobIdHeader1 = BLOB_ID_FACTORY.from("blobBody");
+            BlobId blobIdHeader2 = BLOB_ID_FACTORY.from("blobBody2");
+
+            v1.store(URL,
+                FakeMail.builder()
+                    .name(KEY_1.asString())
+                    .build(),
+                blobIdHeader1,
+                blobIdBody1)
+                .join();
+
+            v2.store(URL,
+                FakeMail.builder()
+                    .name(KEY_1.asString())
+                    .build(),
+                blobIdHeader2,
+                blobIdBody2)
+                .join();
+
+            CassandraMailRepositoryMailDaoAPI.MailDTO actual = testee.read(URL, KEY_1).join().get();
+            Mail partialMail = actual.getMailBuilder().build();
+            assertSoftly(softly -> {
+                softly.assertThat(actual.getBodyBlobId()).isEqualTo(blobIdBody2);
+                softly.assertThat(actual.getHeaderBlobId()).isEqualTo(blobIdHeader2);
+                softly.assertThat(partialMail.getName()).isEqualTo(KEY_1.asString());
+            });
+        }
+
+        @Test
+        void removeShouldRemoveInBOth() throws MessagingException {
+            BlobId blobIdBody1 = BLOB_ID_FACTORY.from("blobHeader");
+            BlobId blobIdBody2 = BLOB_ID_FACTORY.from("blobHeader2");
+            BlobId blobIdHeader1 = BLOB_ID_FACTORY.from("blobBody");
+            BlobId blobIdHeader2 = BLOB_ID_FACTORY.from("blobBody2");
+
+            v1.store(URL,
+                FakeMail.builder()
+                    .name(KEY_1.asString())
+                    .build(),
+                blobIdHeader1,
+                blobIdBody1)
+                .join();
+
+            v2.store(URL,
+                FakeMail.builder()
+                    .name(KEY_1.asString())
+                    .build(),
+                blobIdHeader2,
+                blobIdBody2)
+                .join();
+
+            testee.remove(URL, KEY_1).join();
+
+            Optional<CassandraMailRepositoryMailDaoAPI.MailDTO> v1Entry = v1.read(URL, KEY_1).join();
+            Optional<CassandraMailRepositoryMailDaoAPI.MailDTO> v2Entry = v2.read(URL, KEY_1).join();
+            assertThat(v1Entry).isEmpty();
+            assertThat(v2Entry).isEmpty();
+        }
+    }
 }
\ 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