MAILBOX-304 Provides a new version of AttachmentDao relying on Blobs storage


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

Branch: refs/heads/master
Commit: 7c8dc3d3a27a2926608e71ecf600e7e87a7f94dd
Parents: b8303be
Author: benwa <btell...@linagora.com>
Authored: Wed Sep 6 17:35:34 2017 +0700
Committer: benwa <btell...@linagora.com>
Committed: Wed Sep 6 17:55:52 2017 +0700

----------------------------------------------------------------------
 .../CassandraMailboxSessionMapperFactory.java   |  52 +++---
 .../mail/CassandraAttachmentDAOV2.java          | 132 ++++++++++++++
 .../mail/CassandraAttachmentMapper.java         |  44 +++--
 .../modules/CassandraAttachmentModule.java      |  13 +-
 .../table/CassandraAttachmentV2Table.java       |  31 ++++
 .../CassandraMailboxManagerProvider.java        |   2 +-
 .../CassandraSubscriptionManagerTest.java       |   3 +
 .../cassandra/CassandraTestSystemFixture.java   |   2 +-
 .../mail/CassandraAttachmentDAOV2Test.java      |  88 ++++++++++
 .../CassandraAttachmentFallbackTestTest.java    | 174 +++++++++++++++++++
 .../CassandraMailboxManagerAttachmentTest.java  |   2 +-
 .../cassandra/mail/CassandraMapperProvider.java |   2 +-
 .../cassandra/host/CassandraHostSystem.java     |   2 +-
 13 files changed, 499 insertions(+), 48 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/7c8dc3d3/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxSessionMapperFactory.java
----------------------------------------------------------------------
diff --git 
a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxSessionMapperFactory.java
 
b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxSessionMapperFactory.java
index 2db0e01..164a179 100644
--- 
a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxSessionMapperFactory.java
+++ 
b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxSessionMapperFactory.java
@@ -29,6 +29,7 @@ import 
org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
 import org.apache.james.mailbox.cassandra.mail.CassandraAnnotationMapper;
 import org.apache.james.mailbox.cassandra.mail.CassandraApplicableFlagDAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraAttachmentDAO;
+import org.apache.james.mailbox.cassandra.mail.CassandraAttachmentDAOV2;
 import org.apache.james.mailbox.cassandra.mail.CassandraAttachmentMapper;
 import org.apache.james.mailbox.cassandra.mail.CassandraBlobsDAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraDeletedMessageDAO;
@@ -61,9 +62,32 @@ import com.datastax.driver.core.Session;
 
 /**
  * Cassandra implementation of {@link MailboxSessionMapperFactory}
- * 
  */
 public class CassandraMailboxSessionMapperFactory extends 
MailboxSessionMapperFactory {
+
+    public static CassandraMailboxSessionMapperFactory forTests(Session 
session, CassandraTypesProvider typesProvider,
+                                                                
CassandraMessageId.Factory factory) {
+        CassandraBlobsDAO cassandraBlobsDAO = new CassandraBlobsDAO(session);
+        return new CassandraMailboxSessionMapperFactory(
+            new CassandraUidProvider(session),
+            new CassandraModSeqProvider(session),
+            session,
+            new CassandraMessageDAO(session, typesProvider, cassandraBlobsDAO),
+            new CassandraMessageIdDAO(session, factory),
+            new CassandraMessageIdToImapUidDAO(session, factory),
+            new CassandraMailboxCounterDAO(session),
+            new CassandraMailboxRecentsDAO(session),
+            new CassandraMailboxDAO(session, typesProvider),
+            new CassandraMailboxPathDAO(session, typesProvider),
+            new CassandraFirstUnseenDAO(session),
+            new CassandraApplicableFlagDAO(session),
+            new CassandraAttachmentDAO(session),
+            new CassandraAttachmentDAOV2(session, cassandraBlobsDAO),
+            new CassandraDeletedMessageDAO(session),
+            CassandraUtils.WITH_DEFAULT_CONFIGURATION,
+            CassandraConfiguration.DEFAULT_CONFIGURATION);
+    }
+
     private final Session session;
     private final CassandraUidProvider uidProvider;
     private final CassandraModSeqProvider modSeqProvider;
@@ -78,6 +102,7 @@ public class CassandraMailboxSessionMapperFactory extends 
MailboxSessionMapperFa
     private final CassandraFirstUnseenDAO firstUnseenDAO;
     private final CassandraApplicableFlagDAO applicableFlagDAO;
     private final CassandraAttachmentDAO attachmentDAO;
+    private final CassandraAttachmentDAOV2 attachmentDAOV2;
     private CassandraUtils cassandraUtils;
     private CassandraConfiguration cassandraConfiguration;
     private final CassandraDeletedMessageDAO deletedMessageDAO;
@@ -88,7 +113,7 @@ public class CassandraMailboxSessionMapperFactory extends 
MailboxSessionMapperFa
                                                 CassandraMessageIdDAO 
messageIdDAO, CassandraMessageIdToImapUidDAO imapUidDAO,
                                                 CassandraMailboxCounterDAO 
mailboxCounterDAO, CassandraMailboxRecentsDAO mailboxRecentsDAO, 
CassandraMailboxDAO mailboxDAO,
                                                 CassandraMailboxPathDAO 
mailboxPathDAO, CassandraFirstUnseenDAO firstUnseenDAO, 
CassandraApplicableFlagDAO applicableFlagDAO,
-                                                CassandraAttachmentDAO 
attachmentDAO, CassandraDeletedMessageDAO deletedMessageDAO, CassandraUtils 
cassandraUtils,
+                                                CassandraAttachmentDAO 
attachmentDAO, CassandraAttachmentDAOV2 attachmentDAOV2, 
CassandraDeletedMessageDAO deletedMessageDAO, CassandraUtils cassandraUtils,
                                                 CassandraConfiguration 
cassandraConfiguration) {
         this.uidProvider = uidProvider;
         this.modSeqProvider = modSeqProvider;
@@ -102,6 +127,7 @@ public class CassandraMailboxSessionMapperFactory extends 
MailboxSessionMapperFa
         this.mailboxPathDAO = mailboxPathDAO;
         this.firstUnseenDAO = firstUnseenDAO;
         this.attachmentDAO = attachmentDAO;
+        this.attachmentDAOV2 = attachmentDAOV2;
         this.deletedMessageDAO = deletedMessageDAO;
         this.applicableFlagDAO = applicableFlagDAO;
         this.cassandraUtils = cassandraUtils;
@@ -114,26 +140,6 @@ public class CassandraMailboxSessionMapperFactory extends 
MailboxSessionMapperFa
             deletedMessageDAO);
     }
 
-    public CassandraMailboxSessionMapperFactory(Session session, 
CassandraTypesProvider typesProvider,
-                                                CassandraMessageId.Factory 
factory) {
-        this(new CassandraUidProvider(session),
-            new CassandraModSeqProvider(session),
-            session,
-            new CassandraMessageDAO(session, typesProvider, new 
CassandraBlobsDAO(session)),
-            new CassandraMessageIdDAO(session, factory),
-            new CassandraMessageIdToImapUidDAO(session, factory),
-            new CassandraMailboxCounterDAO(session),
-            new CassandraMailboxRecentsDAO(session),
-            new CassandraMailboxDAO(session, typesProvider),
-            new CassandraMailboxPathDAO(session, typesProvider),
-            new CassandraFirstUnseenDAO(session),
-            new CassandraApplicableFlagDAO(session),
-            new CassandraAttachmentDAO(session),
-            new CassandraDeletedMessageDAO(session),
-            CassandraUtils.WITH_DEFAULT_CONFIGURATION,
-            CassandraConfiguration.DEFAULT_CONFIGURATION);
-    }
-
     @Override
     public CassandraMessageMapper createMessageMapper(MailboxSession 
mailboxSession) {
         return new CassandraMessageMapper(
@@ -168,7 +174,7 @@ public class CassandraMailboxSessionMapperFactory extends 
MailboxSessionMapperFa
 
     @Override
     public AttachmentMapper createAttachmentMapper(MailboxSession 
mailboxSession) {
-        return new CassandraAttachmentMapper(attachmentDAO);
+        return new CassandraAttachmentMapper(attachmentDAO, attachmentDAOV2);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/james-project/blob/7c8dc3d3/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentDAOV2.java
----------------------------------------------------------------------
diff --git 
a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentDAOV2.java
 
b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentDAOV2.java
new file mode 100644
index 0000000..be8bebf
--- /dev/null
+++ 
b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentDAOV2.java
@@ -0,0 +1,132 @@
+/****************************************************************
+ * 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.mailbox.cassandra.mail;
+
+import static com.datastax.driver.core.querybuilder.QueryBuilder.bindMarker;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.insertInto;
+import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
+import static 
org.apache.james.mailbox.cassandra.table.CassandraAttachmentV2Table.BLOB_ID;
+import static 
org.apache.james.mailbox.cassandra.table.CassandraAttachmentV2Table.FIELDS;
+import static 
org.apache.james.mailbox.cassandra.table.CassandraAttachmentV2Table.ID;
+import static 
org.apache.james.mailbox.cassandra.table.CassandraAttachmentV2Table.SIZE;
+import static 
org.apache.james.mailbox.cassandra.table.CassandraAttachmentV2Table.TABLE_NAME;
+import static 
org.apache.james.mailbox.cassandra.table.CassandraAttachmentV2Table.TYPE;
+
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+
+import javax.inject.Inject;
+
+import org.apache.james.backends.cassandra.utils.CassandraAsyncExecutor;
+import org.apache.james.mailbox.cassandra.ids.BlobId;
+import org.apache.james.mailbox.cassandra.table.CassandraAttachmentTable;
+import org.apache.james.mailbox.model.Attachment;
+import org.apache.james.mailbox.model.AttachmentId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.datastax.driver.core.PreparedStatement;
+import com.datastax.driver.core.Row;
+import com.datastax.driver.core.Session;
+import com.google.common.base.Preconditions;
+
+public class CassandraAttachmentDAOV2 {
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(CassandraAttachmentMapper.class);
+    private static final boolean NO_LOG_IF_EMPTY = false;
+
+    private final CassandraAsyncExecutor cassandraAsyncExecutor;
+    private final PreparedStatement insertStatement;
+    private final CassandraBlobsDAO blobsDAO;
+    private final PreparedStatement selectStatement;
+
+    @Inject
+    public CassandraAttachmentDAOV2(Session session, CassandraBlobsDAO 
blobsDAO) {
+        this.cassandraAsyncExecutor = new CassandraAsyncExecutor(session);
+        this.blobsDAO = blobsDAO;
+
+        this.selectStatement = prepareSelect(session);
+        this.insertStatement = prepareInsert(session);
+    }
+
+    private PreparedStatement prepareInsert(Session session) {
+        return session.prepare(
+            insertInto(TABLE_NAME)
+                .value(ID, bindMarker(ID))
+                .value(BLOB_ID, bindMarker(BLOB_ID))
+                .value(TYPE, bindMarker(TYPE))
+                .value(SIZE, bindMarker(SIZE)));
+    }
+
+    private PreparedStatement prepareSelect(Session session) {
+        return session.prepare(select(FIELDS)
+            .from(TABLE_NAME)
+            .where(eq(ID, bindMarker(ID))));
+    }
+
+    public CompletableFuture<Optional<Attachment>> getAttachment(AttachmentId 
attachmentId) {
+        return getAttachment(attachmentId, NO_LOG_IF_EMPTY);
+    }
+
+    public CompletableFuture<Optional<Attachment>> getAttachment(AttachmentId 
attachmentId, boolean logIfEmpty) {
+        Preconditions.checkArgument(attachmentId != null);
+        return cassandraAsyncExecutor.executeSingleRow(
+            selectStatement.bind()
+                .setString(CassandraAttachmentTable.ID, attachmentId.getId()))
+            .thenCompose(this::attachment)
+            .thenApply(optional -> logNotFound(attachmentId, logIfEmpty, 
optional));
+    }
+
+    private Optional<Attachment> logNotFound(AttachmentId attachmentId, 
boolean logIfEmpty, Optional<Attachment> optional) {
+        if (!optional.isPresent() && logIfEmpty) {
+            LOGGER.warn("Failed retrieving attachment {}", attachmentId);
+        }
+        return optional;
+    }
+
+    public CompletableFuture<Void> storeAttachment(Attachment attachment) {
+        return blobsDAO.save(attachment.getBytes())
+            .thenApply(Optional::get) // attachment payload is never null
+            .thenApply(blobId ->
+                insertStatement.bind()
+                    .setString(ID, attachment.getAttachmentId().getId())
+                    .setLong(SIZE, attachment.getSize())
+                    .setString(TYPE, attachment.getType())
+                    .setString(BLOB_ID, blobId.getId()))
+            .thenCompose(cassandraAsyncExecutor::executeVoid);
+    }
+
+    private CompletableFuture<Optional<Attachment>> attachment(Optional<Row> 
rowOptional) {
+        if (rowOptional.isPresent()) {
+            return attachment(rowOptional.get())
+                .thenApply(Optional::of);
+        }
+        return CompletableFuture.completedFuture(Optional.empty());
+    }
+
+    private CompletableFuture<Attachment> attachment(Row row) {
+        return blobsDAO.read(BlobId.from(row.getString(BLOB_ID)))
+            .thenApply(bytes -> Attachment.builder()
+                .attachmentId(AttachmentId.from(row.getString(ID)))
+                .bytes(bytes)
+                .type(row.getString(TYPE))
+                .build());
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/7c8dc3d3/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentMapper.java
----------------------------------------------------------------------
diff --git 
a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentMapper.java
 
b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentMapper.java
index 0a346e8..a0cdc71 100644
--- 
a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentMapper.java
+++ 
b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentMapper.java
@@ -19,11 +19,11 @@
 
 package org.apache.james.mailbox.cassandra.mail;
 
-import java.io.IOException;
 import java.util.Collection;
 import java.util.List;
 import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
 import java.util.stream.Stream;
 
 import javax.inject.Inject;
@@ -36,8 +36,6 @@ import org.apache.james.mailbox.store.mail.AttachmentMapper;
 import org.apache.james.util.FluentFutureStream;
 import org.apache.james.util.OptionalUtils;
 
-import com.github.fge.lambdas.Throwing;
-import com.github.fge.lambdas.ThrownByLambdaException;
 import com.github.steveash.guavate.Guavate;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
@@ -45,12 +43,15 @@ import com.google.common.collect.ImmutableList;
 public class CassandraAttachmentMapper implements AttachmentMapper {
 
     private static final boolean LOG_IF_EMPTY = true;
+    private static final boolean NO_LOG_IF_EMPTY = !LOG_IF_EMPTY;
 
     private final CassandraAttachmentDAO attachmentDAO;
+    private final CassandraAttachmentDAOV2 attachmentDAOV2;
 
     @Inject
-    public CassandraAttachmentMapper(CassandraAttachmentDAO attachmentDAO) {
+    public CassandraAttachmentMapper(CassandraAttachmentDAO attachmentDAO, 
CassandraAttachmentDAOV2 attachmentDAOV2) {
         this.attachmentDAO = attachmentDAO;
+        this.attachmentDAOV2 = attachmentDAOV2;
     }
 
     @Override
@@ -65,7 +66,8 @@ public class CassandraAttachmentMapper implements 
AttachmentMapper {
     @Override
     public Attachment getAttachment(AttachmentId attachmentId) throws 
AttachmentNotFoundException {
         Preconditions.checkArgument(attachmentId != null);
-        return attachmentDAO.getAttachment(attachmentId)
+        return attachmentDAOV2.getAttachment(attachmentId)
+            .thenCompose(v2Value -> fallbackToV1(attachmentId, v2Value))
             .join()
             .orElseThrow(() -> new 
AttachmentNotFoundException(attachmentId.getId()));
     }
@@ -81,7 +83,8 @@ public class CassandraAttachmentMapper implements 
AttachmentMapper {
         Stream<CompletableFuture<Optional<Attachment>>> attachments = 
attachmentIds
                 .stream()
                 .distinct()
-                .map(id -> attachmentDAO.getAttachment(id, LOG_IF_EMPTY));
+                .map(id -> attachmentDAOV2.getAttachment(id, LOG_IF_EMPTY)
+                    .thenCompose(v2Value -> fallbackToV1(id, v2Value, 
LOG_IF_EMPTY)));
 
         return FluentFutureStream
             .of(attachments)
@@ -89,24 +92,27 @@ public class CassandraAttachmentMapper implements 
AttachmentMapper {
             .collect(Guavate.toImmutableList());
     }
 
+    private CompletionStage<Optional<Attachment>> fallbackToV1(AttachmentId 
attachmentId, Optional<Attachment> v2Value) {
+        return fallbackToV1(attachmentId, v2Value, NO_LOG_IF_EMPTY);
+    }
+
+    private CompletionStage<Optional<Attachment>> fallbackToV1(AttachmentId 
attachmentId, Optional<Attachment> v2Value, boolean logIfEmpty) {
+        if (v2Value.isPresent()) {
+            return CompletableFuture.completedFuture(v2Value);
+        }
+        return attachmentDAO.getAttachment(attachmentId, logIfEmpty);
+    }
+
     @Override
     public void storeAttachment(Attachment attachment) throws MailboxException 
{
-        try {
-            attachmentDAO.storeAttachment(attachment).join();
-        } catch (IOException e) {
-            throw new MailboxException(e.getMessage(), e);
-        }
+        attachmentDAOV2.storeAttachment(attachment).join();
     }
 
     @Override
     public void storeAttachments(Collection<Attachment> attachments) throws 
MailboxException {
-        try {
-            FluentFutureStream.of(
-                attachments.stream()
-                    .map(Throwing.function(attachmentDAO::storeAttachment)))
-                .join();
-        } catch (ThrownByLambdaException e) {
-            throw new MailboxException(e.getCause().getMessage(), 
e.getCause());
-        }
+        FluentFutureStream.of(
+            attachments.stream()
+                .map(attachmentDAOV2::storeAttachment))
+            .join();
     }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/7c8dc3d3/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraAttachmentModule.java
----------------------------------------------------------------------
diff --git 
a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraAttachmentModule.java
 
b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraAttachmentModule.java
index d576f91..ecd9d64 100644
--- 
a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraAttachmentModule.java
+++ 
b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraAttachmentModule.java
@@ -29,6 +29,7 @@ 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.mailbox.cassandra.table.CassandraAttachmentTable;
+import org.apache.james.mailbox.cassandra.table.CassandraAttachmentV2Table;
 
 import com.datastax.driver.core.schemabuilder.SchemaBuilder;
 import com.google.common.collect.ImmutableList;
@@ -48,7 +49,17 @@ public class CassandraAttachmentModule implements 
CassandraModule {
                     .addColumn(CassandraAttachmentTable.TYPE, text())
                     .addColumn(CassandraAttachmentTable.SIZE, bigint())
                     .withOptions()
-                    .comment("Holds attachment for fast attachment 
retrieval")));
+                    .comment("Holds attachment for fast attachment 
retrieval")),
+            new CassandraTable(CassandraAttachmentV2Table.TABLE_NAME,
+                
SchemaBuilder.createTable(CassandraAttachmentV2Table.TABLE_NAME)
+                    .ifNotExists()
+                    .addPartitionKey(CassandraAttachmentV2Table.ID, text())
+                    .addColumn(CassandraAttachmentV2Table.BLOB_ID, text())
+                    .addColumn(CassandraAttachmentV2Table.TYPE, text())
+                    .addColumn(CassandraAttachmentV2Table.SIZE, bigint())
+                    .withOptions()
+                    .comment("Holds attachment for fast attachment retrieval. 
Content of messages is stored" +
+                        "in `blobs` and `blobparts` tables.")));
         types = ImmutableList.of();
     }
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/7c8dc3d3/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraAttachmentV2Table.java
----------------------------------------------------------------------
diff --git 
a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraAttachmentV2Table.java
 
b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraAttachmentV2Table.java
new file mode 100644
index 0000000..9b3daec
--- /dev/null
+++ 
b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraAttachmentV2Table.java
@@ -0,0 +1,31 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mailbox.cassandra.table;
+
+public interface CassandraAttachmentV2Table {
+
+    String TABLE_NAME = "attachmentV2";
+    String ID = "id";
+    String BLOB_ID = "blobId";
+    String TYPE = "type";
+    String SIZE = "size";
+    String[] FIELDS = { ID, BLOB_ID, TYPE, SIZE };
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/7c8dc3d3/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxManagerProvider.java
----------------------------------------------------------------------
diff --git 
a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxManagerProvider.java
 
b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxManagerProvider.java
index 9baf216..06c04a9 100644
--- 
a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxManagerProvider.java
+++ 
b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxManagerProvider.java
@@ -41,7 +41,7 @@ public class CassandraMailboxManagerProvider {
     public static CassandraMailboxManager provideMailboxManager(Session 
session, CassandraTypesProvider cassandraTypesProvider) {
         CassandraMessageId.Factory messageIdFactory = new 
CassandraMessageId.Factory();
 
-        CassandraMailboxSessionMapperFactory mapperFactory = new 
CassandraMailboxSessionMapperFactory(
+        CassandraMailboxSessionMapperFactory mapperFactory = 
CassandraMailboxSessionMapperFactory.forTests(
             session,
             cassandraTypesProvider,
             messageIdFactory);

http://git-wip-us.apache.org/repos/asf/james-project/blob/7c8dc3d3/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraSubscriptionManagerTest.java
----------------------------------------------------------------------
diff --git 
a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraSubscriptionManagerTest.java
 
b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraSubscriptionManagerTest.java
index 8f8139c..c9c02ef 100644
--- 
a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraSubscriptionManagerTest.java
+++ 
b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraSubscriptionManagerTest.java
@@ -28,6 +28,7 @@ import 
org.apache.james.mailbox.AbstractSubscriptionManagerTest;
 import org.apache.james.mailbox.SubscriptionManager;
 import org.apache.james.mailbox.cassandra.mail.CassandraApplicableFlagDAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraAttachmentDAO;
+import org.apache.james.mailbox.cassandra.mail.CassandraAttachmentDAOV2;
 import org.apache.james.mailbox.cassandra.mail.CassandraDeletedMessageDAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraFirstUnseenDAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraMailboxCounterDAO;
@@ -87,6 +88,7 @@ public class CassandraSubscriptionManagerTest extends 
AbstractSubscriptionManage
         CassandraApplicableFlagDAO applicableFlagDAO = null;
         CassandraAttachmentDAO attachmentDAO = null;
         CassandraDeletedMessageDAO deletedMessageDAO = null;
+        CassandraAttachmentDAOV2 attachmentDAOV2 = null;
         return new CassandraSubscriptionManager(
             new CassandraMailboxSessionMapperFactory(
                 new CassandraUidProvider(cassandra.getConf()),
@@ -102,6 +104,7 @@ public class CassandraSubscriptionManagerTest extends 
AbstractSubscriptionManage
                 firstUnseenDAO,
                 applicableFlagDAO,
                 attachmentDAO,
+                attachmentDAOV2,
                 deletedMessageDAO,
                 CassandraUtils.WITH_DEFAULT_CONFIGURATION,
                 CassandraConfiguration.DEFAULT_CONFIGURATION));

http://git-wip-us.apache.org/repos/asf/james-project/blob/7c8dc3d3/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraTestSystemFixture.java
----------------------------------------------------------------------
diff --git 
a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraTestSystemFixture.java
 
b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraTestSystemFixture.java
index 3606d81..c8d7580 100644
--- 
a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraTestSystemFixture.java
+++ 
b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraTestSystemFixture.java
@@ -44,7 +44,7 @@ public class CassandraTestSystemFixture {
     public static CassandraMailboxSessionMapperFactory 
createMapperFactory(CassandraCluster cassandra) {
         CassandraMessageId.Factory messageIdFactory = new 
CassandraMessageId.Factory();
 
-        return new CassandraMailboxSessionMapperFactory(
+        return CassandraMailboxSessionMapperFactory.forTests(
             cassandra.getConf(),
             cassandra.getTypesProvider(),
             messageIdFactory);

http://git-wip-us.apache.org/repos/asf/james-project/blob/7c8dc3d3/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentDAOV2Test.java
----------------------------------------------------------------------
diff --git 
a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentDAOV2Test.java
 
b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentDAOV2Test.java
new file mode 100644
index 0000000..130ef19
--- /dev/null
+++ 
b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentDAOV2Test.java
@@ -0,0 +1,88 @@
+/****************************************************************
+ * 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.mailbox.cassandra.mail;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Optional;
+
+import org.apache.james.backends.cassandra.CassandraCluster;
+import org.apache.james.backends.cassandra.DockerCassandraRule;
+import org.apache.james.backends.cassandra.init.CassandraModuleComposite;
+import org.apache.james.mailbox.cassandra.modules.CassandraAttachmentModule;
+import org.apache.james.mailbox.cassandra.modules.CassandraBlobModule;
+import org.apache.james.mailbox.model.Attachment;
+import org.apache.james.mailbox.model.AttachmentId;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+public class CassandraAttachmentDAOV2Test {
+    public static final AttachmentId ATTACHMENT_ID = AttachmentId.from("id1");
+
+    @ClassRule
+    public static DockerCassandraRule cassandraServer = new 
DockerCassandraRule();
+
+    private CassandraCluster cassandra;
+
+    private CassandraAttachmentDAOV2 testee;
+
+    @Before
+    public void setUp() throws Exception {
+        CassandraModuleComposite compositeModule = new 
CassandraModuleComposite(
+            new CassandraAttachmentModule(),
+            new CassandraBlobModule());
+
+        cassandra = CassandraCluster.create(
+            compositeModule,
+            cassandraServer.getIp(),
+            cassandraServer.getBindingPort());
+
+        testee = new CassandraAttachmentDAOV2(cassandra.getConf(), new 
CassandraBlobsDAO(cassandra.getConf()));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        cassandra.close();
+    }
+
+    @Test
+    public void getAttachmentShouldReturnEmptyWhenAbsent() {
+        Optional<Attachment> attachment = 
testee.getAttachment(ATTACHMENT_ID).join();
+
+        assertThat(attachment).isEmpty();
+    }
+
+    @Test
+    public void getAttachmentShouldReturnAttachmentWhenStored() throws 
Exception {
+        Attachment attachment = Attachment.builder()
+            .attachmentId(ATTACHMENT_ID)
+            .type("application/json")
+            
.bytes("{\"property\":`\"value\"}".getBytes(StandardCharsets.UTF_8))
+            .build();
+        testee.storeAttachment(attachment).join();
+
+        Optional<Attachment> actual = 
testee.getAttachment(ATTACHMENT_ID).join();
+
+        assertThat(actual).contains(attachment);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/7c8dc3d3/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentFallbackTestTest.java
----------------------------------------------------------------------
diff --git 
a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentFallbackTestTest.java
 
b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentFallbackTestTest.java
new file mode 100644
index 0000000..45f03e2
--- /dev/null
+++ 
b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraAttachmentFallbackTestTest.java
@@ -0,0 +1,174 @@
+/****************************************************************
+ * 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.mailbox.cassandra.mail;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.nio.charset.StandardCharsets;
+
+import org.apache.james.backends.cassandra.CassandraCluster;
+import org.apache.james.backends.cassandra.DockerCassandraRule;
+import org.apache.james.backends.cassandra.init.CassandraModuleComposite;
+import org.apache.james.mailbox.cassandra.modules.CassandraAttachmentModule;
+import org.apache.james.mailbox.cassandra.modules.CassandraBlobModule;
+import org.apache.james.mailbox.exception.AttachmentNotFoundException;
+import org.apache.james.mailbox.model.Attachment;
+import org.apache.james.mailbox.model.AttachmentId;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.testcontainers.shaded.com.google.common.collect.ImmutableList;
+
+public class CassandraAttachmentFallbackTestTest {
+    public static final AttachmentId ATTACHMENT_ID_1 = 
AttachmentId.from("id1");
+    public static final AttachmentId ATTACHMENT_ID_2 = 
AttachmentId.from("id2");
+
+    @ClassRule
+    public static DockerCassandraRule cassandraServer = new 
DockerCassandraRule();
+
+    private CassandraCluster cassandra;
+
+    private CassandraAttachmentDAOV2 attachmentDAOV2;
+    private CassandraAttachmentDAO attachmentDAO;
+    private CassandraAttachmentMapper attachmentMapper;
+
+    @Before
+    public void setUp() throws Exception {
+        CassandraModuleComposite compositeModule = new 
CassandraModuleComposite(
+            new CassandraAttachmentModule(),
+            new CassandraBlobModule());
+
+        cassandra = CassandraCluster.create(
+            compositeModule,
+            cassandraServer.getIp(),
+            cassandraServer.getBindingPort());
+
+        attachmentDAOV2 = new CassandraAttachmentDAOV2(cassandra.getConf(), 
new CassandraBlobsDAO(cassandra.getConf()));
+        attachmentDAO = new CassandraAttachmentDAO(cassandra.getConf());
+        attachmentMapper = new CassandraAttachmentMapper(attachmentDAO, 
attachmentDAOV2);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        cassandra.close();
+    }
+
+    @Test
+    public void getAttachmentShouldThrowWhenAbsentFromV1AndV2() throws 
Exception {
+        assertThatThrownBy(() -> 
attachmentMapper.getAttachment(ATTACHMENT_ID_1))
+            .isInstanceOf(AttachmentNotFoundException.class);
+    }
+
+    @Test
+    public void getAttachmentsShouldReturnEmptyWhenAbsentFromV1AndV2() throws 
Exception {
+        
assertThat(attachmentMapper.getAttachments(ImmutableList.of(ATTACHMENT_ID_1)))
+            .isEmpty();
+    }
+
+    @Test
+    public void getAttachmentShouldReturnV2WhenPresentInV1AndV2() throws 
Exception {
+        Attachment attachment = Attachment.builder()
+            .attachmentId(ATTACHMENT_ID_1)
+            .type("application/json")
+            
.bytes("{\"property\":`\"value\"}".getBytes(StandardCharsets.UTF_8))
+            .build();
+        Attachment otherAttachment = Attachment.builder()
+            .attachmentId(ATTACHMENT_ID_1)
+            .type("application/json")
+            
.bytes("{\"property\":`\"different\"}".getBytes(StandardCharsets.UTF_8))
+            .build();
+
+        attachmentDAOV2.storeAttachment(attachment).join();
+        attachmentDAO.storeAttachment(otherAttachment).join();
+
+        assertThat(attachmentMapper.getAttachment(ATTACHMENT_ID_1))
+            .isEqualTo(attachment);
+    }
+
+    @Test
+    public void getAttachmentShouldReturnV1WhenV2Absent() throws Exception {
+        Attachment attachment = Attachment.builder()
+            .attachmentId(ATTACHMENT_ID_1)
+            .type("application/json")
+            
.bytes("{\"property\":`\"value\"}".getBytes(StandardCharsets.UTF_8))
+            .build();
+
+        attachmentDAO.storeAttachment(attachment).join();
+
+        assertThat(attachmentMapper.getAttachment(ATTACHMENT_ID_1))
+            .isEqualTo(attachment);
+    }
+
+    @Test
+    public void getAttachmentsShouldReturnV2WhenV2AndV1() throws Exception {
+        Attachment attachment = Attachment.builder()
+            .attachmentId(ATTACHMENT_ID_1)
+            .type("application/json")
+            
.bytes("{\"property\":`\"value\"}".getBytes(StandardCharsets.UTF_8))
+            .build();
+        Attachment otherAttachment = Attachment.builder()
+            .attachmentId(ATTACHMENT_ID_1)
+            .type("application/json")
+            
.bytes("{\"property\":`\"different\"}".getBytes(StandardCharsets.UTF_8))
+            .build();
+
+        attachmentDAOV2.storeAttachment(attachment).join();
+        attachmentDAO.storeAttachment(otherAttachment).join();
+
+        
assertThat(attachmentMapper.getAttachments(ImmutableList.of(ATTACHMENT_ID_1)))
+            .containsExactly(attachment);
+    }
+
+    @Test
+    public void getAttachmentsShouldReturnV1WhenV2Absent() throws Exception {
+        Attachment attachment = Attachment.builder()
+            .attachmentId(ATTACHMENT_ID_1)
+            .type("application/json")
+            
.bytes("{\"property\":`\"value\"}".getBytes(StandardCharsets.UTF_8))
+            .build();
+
+        attachmentDAO.storeAttachment(attachment).join();
+
+        
assertThat(attachmentMapper.getAttachments(ImmutableList.of(ATTACHMENT_ID_1)))
+            .containsExactly(attachment);
+    }
+
+    @Test
+    public void getAttachmentsShouldCombineElementsFromV1AndV2() throws 
Exception {
+        Attachment attachment = Attachment.builder()
+            .attachmentId(ATTACHMENT_ID_1)
+            .type("application/json")
+            
.bytes("{\"property\":`\"value\"}".getBytes(StandardCharsets.UTF_8))
+            .build();
+        Attachment otherAttachment = Attachment.builder()
+            .attachmentId(ATTACHMENT_ID_2)
+            .type("application/json")
+            
.bytes("{\"property\":`\"different\"}".getBytes(StandardCharsets.UTF_8))
+            .build();
+
+        attachmentDAOV2.storeAttachment(attachment).join();
+        attachmentDAO.storeAttachment(otherAttachment).join();
+
+        
assertThat(attachmentMapper.getAttachments(ImmutableList.of(ATTACHMENT_ID_1, 
ATTACHMENT_ID_2)))
+            .containsExactly(attachment, otherAttachment);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/7c8dc3d3/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxManagerAttachmentTest.java
----------------------------------------------------------------------
diff --git 
a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxManagerAttachmentTest.java
 
b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxManagerAttachmentTest.java
index c196a0f..2c87057 100644
--- 
a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxManagerAttachmentTest.java
+++ 
b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxManagerAttachmentTest.java
@@ -91,7 +91,7 @@ public class CassandraMailboxManagerAttachmentTest extends 
AbstractMailboxManage
     private void initSystemUnderTest() throws Exception {
         CassandraMessageId.Factory messageIdFactory = new 
CassandraMessageId.Factory();
 
-        mailboxSessionMapperFactory = new CassandraMailboxSessionMapperFactory(
+        mailboxSessionMapperFactory = 
CassandraMailboxSessionMapperFactory.forTests(
             cassandra.getConf(),
             cassandra.getTypesProvider(),
             messageIdFactory);

http://git-wip-us.apache.org/repos/asf/james-project/blob/7c8dc3d3/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMapperProvider.java
----------------------------------------------------------------------
diff --git 
a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMapperProvider.java
 
b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMapperProvider.java
index 245acbf..59c1dd1 100644
--- 
a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMapperProvider.java
+++ 
b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMapperProvider.java
@@ -78,7 +78,7 @@ public class CassandraMapperProvider implements 
MapperProvider {
     }
 
     private CassandraMailboxSessionMapperFactory createMapperFactory() {
-        return new CassandraMailboxSessionMapperFactory(cassandra.getConf(),
+        return 
CassandraMailboxSessionMapperFactory.forTests(cassandra.getConf(),
             cassandra.getTypesProvider(),
             new CassandraMessageId.Factory());
     }

http://git-wip-us.apache.org/repos/asf/james-project/blob/7c8dc3d3/mpt/impl/imap-mailbox/cassandra/src/test/java/org/apache/james/mpt/imapmailbox/cassandra/host/CassandraHostSystem.java
----------------------------------------------------------------------
diff --git 
a/mpt/impl/imap-mailbox/cassandra/src/test/java/org/apache/james/mpt/imapmailbox/cassandra/host/CassandraHostSystem.java
 
b/mpt/impl/imap-mailbox/cassandra/src/test/java/org/apache/james/mpt/imapmailbox/cassandra/host/CassandraHostSystem.java
index e0c2468..c8aa4a5 100644
--- 
a/mpt/impl/imap-mailbox/cassandra/src/test/java/org/apache/james/mpt/imapmailbox/cassandra/host/CassandraHostSystem.java
+++ 
b/mpt/impl/imap-mailbox/cassandra/src/test/java/org/apache/james/mpt/imapmailbox/cassandra/host/CassandraHostSystem.java
@@ -102,7 +102,7 @@ public class CassandraHostSystem extends 
JamesImapHostSystem {
         cassandra = CassandraCluster.create(mailboxModule, cassandraHost, 
cassandraPort);
         com.datastax.driver.core.Session session = cassandra.getConf();
         CassandraMessageId.Factory messageIdFactory = new 
CassandraMessageId.Factory();
-        CassandraMailboxSessionMapperFactory mapperFactory = new 
CassandraMailboxSessionMapperFactory(
+        CassandraMailboxSessionMapperFactory mapperFactory = 
CassandraMailboxSessionMapperFactory.forTests(
             cassandra.getConf(),
             cassandra.getTypesProvider(),
             messageIdFactory);


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

Reply via email to