You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by rc...@apache.org on 2020/10/26 02:55:03 UTC

[james-project] branch master updated (1060cb0 -> 920e575)

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

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


    from 1060cb0  [REFACTORING] Solve intelliJ warnings in CassandraMessageDAO
     new 2adacf4  JAMES-3350 Significantly fasten Cassandra backend integration tests
     new 123103b  JAMES-3430 Split PropertyBuilder in two
     new fcdb50b  JAMES-3430 Remove unused boundary property
     new 2b66fac  JAMES-3430 Implement CassandraMessageDAOV3
     new 518e6b2  JAMES-3430 Use CassandraMessageDAOV3 in mappers
     new 2284c51  JAMES-3430 Charset should be accessed via ContentDispositionParameters
     new 5ab6e6d  JAMES-3430 Provide migration for MessageV3 table
     new 3aa16fd  JAMES-3430 Document migration for MessageV3 table
     new 2d54e04  JAMES-3277 JMAP Rely on MessageManager::setFlags to optimize massive flags updates (Draft)
     new 2c2f2ea  JAMES-3433 BlobStore reads should have a StoragePolicy associated
     new 920e575  JAMES-3433 Ensure CachedBlobStore  is only queried for headers

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


Summary of changes:
 CHANGELOG.md                                       |   1 +
 .../versions/CassandraSchemaVersionManager.java    |   2 +-
 .../distributed/operate/cassandra-migration.adoc   |   2 +
 .../mailbox/backup/MailboxMessageFixture.java      |   6 +-
 .../CassandraMailboxSessionMapperFactory.java      |  10 +-
 .../mailbox/cassandra/DeleteMessageListener.java   |  14 +-
 .../cassandra/mail/CassandraAttachmentMapper.java  |   2 +-
 .../cassandra/mail/CassandraMessageDAO.java        |  55 ++++-
 ...aMessageDAO.java => CassandraMessageDAOV3.java} | 160 ++++++++----
 .../cassandra/mail/CassandraMessageIdMapper.java   |   9 +-
 .../cassandra/mail/CassandraMessageMapper.java     |  14 +-
 .../cassandra/mail/MessageRepresentation.java      |  34 ++-
 ...athV2Migration.java => MessageV3Migration.java} |  68 ++----
 ...ageV3MigrationTaskAdditionalInformationDTO.java |  47 ++--
 ...TaskDTO.java => MessageV3MigrationTaskDTO.java} |  22 +-
 .../cassandra/modules/CassandraMessageModule.java  |  27 +++
 ...geV2Table.java => CassandraMessageV3Table.java} |  19 +-
 .../cassandra/CassandraMailboxManagerTest.java     |   6 +-
 .../CassandraSubscriptionManagerTest.java          |   3 +
 .../cassandra/mail/CassandraMessageDAOTest.java    |   7 +-
 ...DAOTest.java => CassandraMessageDAOV3Test.java} |  15 +-
 .../mail/CassandraMessageIdMapperTest.java         |   2 +-
 .../cassandra/mail/CassandraMessageMapperTest.java |   4 +-
 ...> MessageV3MigrationTaskSerializationTest.java} |  16 +-
 .../mail/migration/MessageV3MigrationTest.java     | 157 ++++++++++++
 ...asticSearchListeningMessageSearchIndexTest.java |   2 +-
 .../json/MessageToElasticSearchJsonTest.java       |  26 +-
 .../model/openjpa/AbstractJPAMailboxMessage.java   |  24 +-
 .../james/mailbox/jpa/mail/MessageUtilsTest.java   |   2 +-
 .../mailbox/maildir/mail/model/MaildirMessage.java |  16 +-
 .../vault/blob/BlobStoreDeletedMessageVault.java   |   2 +-
 .../spamassassin/SpamAssassinListenerTest.java     |   2 +-
 .../james/mailbox/store/LazyMimeDescriptor.java    |  11 +-
 .../apache/james/mailbox/store/MessageFactory.java |   2 +-
 .../james/mailbox/store/StoreMessageManager.java   |   4 -
 .../store/mail/model/DelegatingMailboxMessage.java |   3 +-
 .../james/mailbox/store/mail/model/Message.java    |   5 +-
 .../mailbox/store/mail/model/StandardNames.java    |   6 -
 .../impl/{PropertyBuilder.java => Properties.java} | 268 +++------------------
 .../store/mail/model/impl/PropertyBuilder.java     | 193 +--------------
 .../mail/model/impl/SimpleMailboxMessage.java      |  32 +--
 .../store/mail/model/impl/SimpleMessage.java       |  15 +-
 .../apache/james/mailbox/store/MessageBuilder.java |   2 +-
 .../mailbox/store/MessageIdManagerTestSystem.java  |   2 +-
 .../StoreMailboxMessageResultIteratorTest.java     |   2 +-
 .../store/mail/model/ListMessageAssertTest.java    |   4 +-
 .../model/ListMessagePropertiesAssertTest.java     |   2 +-
 .../store/mail/model/MailboxMessageAssertTest.java |  12 +-
 .../store/mail/model/MessageIdMapperTest.java      |   2 +-
 .../store/mail/model/MessageMapperTest.java        |  15 +-
 .../mailbox/store/mail/model/MessageMoveTest.java  |   2 +-
 .../model/MessageWithAttachmentMapperTest.java     |   4 +-
 .../store/mail/model/MetadataMapAssertTest.java    |   2 +-
 .../mail/model/impl/SimpleMailboxMessageTest.java  |  24 +-
 .../mail/utils/ApplicableFlagCalculatorTest.java   |   2 +-
 .../ListeningMessageSearchIndexContract.java       |   2 +-
 .../tools/indexer/CassandraReIndexerImplTest.java  |   2 +-
 .../java/org/apache/james/blob/api/BlobStore.java  |   8 +
 .../java/org/apache/james/blob/api/BlobType.java   |  13 +-
 server/blob/blob-cassandra/pom.xml                 |   8 +-
 .../blob/cassandra/cache/CachedBlobStore.java      |  20 +-
 .../blob/cassandra/cache/CachedBlobStoreTest.java  | 106 +++++---
 .../main/java/org/apache/james/blob/api/Store.java |   2 +-
 .../export/file/LocalFileBlobExportMechanism.java  |   4 +-
 .../apache/james/blob/mail/MimeMessagePartsId.java |   7 +-
 .../modules/webadmin/CassandraRoutesModule.java    |   3 +
 .../org/apache/james/CassandraJmapTestRule.java    |   2 -
 .../james/CassandraRabbitMQAwsS3JmapTestRule.java  |   2 -
 .../java/org/apache/james/util/StreamUtils.java    |   6 +
 .../jmap/cassandra/cucumber/CassandraStepdefs.java |   2 -
 .../methods/integration/SetMessagesMethodTest.java |  72 +++++-
 .../cucumber/awss3/RabbitMQAwsS3Stepdefs.java      |   2 -
 .../draft/methods/SetMessagesUpdateProcessor.java  |  58 ++++-
 .../james/jmap/draft/model/UpdateMessagePatch.java |  23 ++
 .../methods/SetMessagesUpdateProcessorTest.java    |   4 +
 .../james/webadmin/routes/MailboxesRoutesTest.java |   4 +-
 .../webadmin/routes/UserMailboxesRoutesTest.java   |   2 +-
 src/site/xdoc/server/config-cassandra.xml          |  12 +
 .../linshare/LinshareBlobExportMechanism.java      |   4 +-
 upgrade-instructions.md                            |  26 ++
 80 files changed, 980 insertions(+), 804 deletions(-)
 copy mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/{CassandraMessageDAO.java => CassandraMessageDAOV3.java} (62%)
 copy mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/migration/{MailboxPathV2Migration.java => MessageV3Migration.java} (58%)
 copy server/protocols/webadmin/webadmin-cassandra/src/main/java/org/apache/james/webadmin/dto/WebAdminMigrationTaskAdditionalInformationDTO.java => mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/migration/MessageV3MigrationTaskAdditionalInformationDTO.java (56%)
 copy mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/migration/{MailboxPathV3MigrationTaskDTO.java => MessageV3MigrationTaskDTO.java} (64%)
 copy mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/{CassandraMessageV2Table.java => CassandraMessageV3Table.java} (72%)
 copy mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/{CassandraMessageDAOTest.java => CassandraMessageDAOV3Test.java} (94%)
 copy mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/migration/{MailboxPathV3MigrationTaskSerializationTest.java => MessageV3MigrationTaskSerializationTest.java} (70%)
 create mode 100644 mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/migration/MessageV3MigrationTest.java
 copy mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/{PropertyBuilder.java => Properties.java} (52%)


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


[james-project] 07/11: JAMES-3430 Provide migration for MessageV3 table

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

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

commit 5ab6e6d765a51193254ca1aab1d36a49d0279d0d
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Oct 20 16:54:33 2020 +0700

    JAMES-3430 Provide migration for MessageV3 table
---
 .../versions/CassandraSchemaVersionManager.java    |   2 +-
 .../cassandra/mail/CassandraMessageDAO.java        |  33 +++++
 .../cassandra/mail/CassandraMessageDAOV3.java      |  43 ++++++
 .../cassandra/mail/MessageRepresentation.java      |  20 ++-
 .../mail/migration/MessageV3Migration.java         | 119 ++++++++++++++++
 ...ageV3MigrationTaskAdditionalInformationDTO.java |  69 +++++++++
 .../mail/migration/MessageV3MigrationTaskDTO.java  |  59 ++++++++
 .../cassandra/mail/CassandraMessageDAOTest.java    |   1 +
 .../MessageV3MigrationTaskSerializationTest.java   |  52 +++++++
 .../mail/migration/MessageV3MigrationTest.java     | 157 +++++++++++++++++++++
 .../mailbox/store/mail/model/impl/Properties.java  |  17 +++
 .../modules/webadmin/CassandraRoutesModule.java    |   3 +
 12 files changed, 570 insertions(+), 5 deletions(-)

diff --git a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/versions/CassandraSchemaVersionManager.java b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/versions/CassandraSchemaVersionManager.java
index e38037a..29dae94 100644
--- a/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/versions/CassandraSchemaVersionManager.java
+++ b/backends-common/cassandra/src/main/java/org/apache/james/backends/cassandra/versions/CassandraSchemaVersionManager.java
@@ -36,7 +36,7 @@ import reactor.core.publisher.Mono;
 
 public class CassandraSchemaVersionManager {
     public static final SchemaVersion MIN_VERSION = new SchemaVersion(5);
-    public static final SchemaVersion MAX_VERSION = new SchemaVersion(8);
+    public static final SchemaVersion MAX_VERSION = new SchemaVersion(9);
     public static final SchemaVersion DEFAULT_VERSION = MIN_VERSION;
 
     private static final Logger LOGGER = LoggerFactory.getLogger(CassandraSchemaVersionManager.class);
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAO.java
index 632af57..8d97ff5 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAO.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAO.java
@@ -78,6 +78,7 @@ import com.github.steveash.guavate.Guavate;
 import com.google.common.collect.ImmutableList;
 import com.google.common.primitives.Bytes;
 
+import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 import reactor.util.function.Tuple2;
 
@@ -92,7 +93,9 @@ public class CassandraMessageDAO {
     private final PreparedStatement insert;
     private final PreparedStatement delete;
     private final PreparedStatement select;
+    private final PreparedStatement selectAll;
     private final Cid.CidParser cidParser;
+    private final CassandraMessageId.Factory messageIdFactory;
     private final ConsistencyLevel consistencyLevel;
 
     @Inject
@@ -100,8 +103,10 @@ public class CassandraMessageDAO {
                                CassandraTypesProvider typesProvider,
                                BlobStore blobStore,
                                BlobId.Factory blobIdFactory,
+                               CassandraMessageId.Factory messageIdFactory,
                                CassandraConsistenciesConfiguration consistenciesConfiguration) {
         this.cassandraAsyncExecutor = new CassandraAsyncExecutor(session);
+        this.messageIdFactory = messageIdFactory;
         this.consistencyLevel = consistenciesConfiguration.getRegular();
         this.typesProvider = typesProvider;
         this.blobStore = blobStore;
@@ -110,6 +115,7 @@ public class CassandraMessageDAO {
         this.insert = prepareInsert(session);
         this.delete = prepareDelete(session);
         this.select = prepareSelect(session);
+        this.selectAll = prepareSelectAll(session);
         this.cidParser = Cid.parser().relaxed();
     }
 
@@ -119,6 +125,11 @@ public class CassandraMessageDAO {
             .where(eq(MESSAGE_ID, bindMarker(MESSAGE_ID))));
     }
 
+    private PreparedStatement prepareSelectAll(Session session) {
+        return session.prepare(select()
+            .from(TABLE_NAME));
+    }
+
     private PreparedStatement prepareInsert(Session session) {
         return session.prepare(insertInto(TABLE_NAME)
             .value(MESSAGE_ID, bindMarker(MESSAGE_ID))
@@ -139,6 +150,11 @@ public class CassandraMessageDAO {
             .where(eq(MESSAGE_ID, bindMarker(MESSAGE_ID))));
     }
 
+    public Flux<MessageRepresentation> list() {
+        return cassandraAsyncExecutor.executeRows(selectAll.bind())
+            .map(this::message);
+    }
+
     public Mono<Void> save(MailboxMessage message) throws MailboxException {
         return saveContent(message)
             .flatMap(pair -> cassandraAsyncExecutor.executeVoid(boundWriteStatement(message, pair)));
@@ -244,6 +260,23 @@ public class CassandraMessageDAO {
                 bodyId));
     }
 
+    private MessageRepresentation message(Row row) {
+        BlobId headerId = retrieveBlobId(HEADER_CONTENT, row);
+        BlobId bodyId = retrieveBlobId(BODY_CONTENT, row);
+        CassandraMessageId messageId = messageIdFactory.of(row.getUUID(MESSAGE_ID));
+
+        return new MessageRepresentation(
+                messageId,
+                row.getTimestamp(INTERNAL_DATE),
+                row.getLong(FULL_CONTENT_OCTETS),
+                row.getInt(BODY_START_OCTET),
+                new SharedByteArrayInputStream(EMPTY_BYTE_ARRAY),
+                getProperties(row),
+                getAttachments(row).collect(Guavate.toImmutableList()),
+                headerId,
+                bodyId);
+    }
+
     private org.apache.james.mailbox.store.mail.model.impl.Properties getProperties(Row row) {
         PropertyBuilder property = new PropertyBuilder(
             row.getList(PROPERTIES, UDTValue.class).stream()
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3.java
index 2c6b5ae..8f349da 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3.java
@@ -159,6 +159,31 @@ public class CassandraMessageDAOV3 {
             .flatMap(pair -> cassandraAsyncExecutor.executeVoid(boundWriteStatement(message, pair)));
     }
 
+    public Mono<Void> save(MessageRepresentation message) {
+        CassandraMessageId messageId = (CassandraMessageId) message.getMessageId();
+        return cassandraAsyncExecutor.executeVoid(insert.bind()
+            .setUUID(MESSAGE_ID, messageId.get())
+            .setTimestamp(INTERNAL_DATE, message.getInternalDate())
+            .setInt(BODY_START_OCTET, message.getBodyStartOctet())
+            .setLong(FULL_CONTENT_OCTETS, message.getSize())
+            .setLong(BODY_OCTECTS, message.getSize() - message.getBodyStartOctet())
+            .setString(BODY_CONTENT, message.getBodyId().asString())
+            .setString(HEADER_CONTENT, message.getHeaderId().asString())
+            .setLong(TEXTUAL_LINE_COUNT, Optional.ofNullable(message.getProperties().getTextualLineCount()).orElse(DEFAULT_LONG_VALUE))
+            .setString(CONTENT_DESCRIPTION, message.getProperties().getContentDescription())
+            .setString(CONTENT_DISPOSITION_TYPE, message.getProperties().getContentDispositionType())
+            .setString(MEDIA_TYPE, message.getProperties().getMediaType())
+            .setString(SUB_TYPE, message.getProperties().getSubType())
+            .setString(CONTENT_ID, message.getProperties().getContentID())
+            .setString(CONTENT_MD5, message.getProperties().getContentMD5())
+            .setString(CONTENT_TRANSFER_ENCODING, message.getProperties().getContentTransferEncoding())
+            .setString(CONTENT_LOCATION, message.getProperties().getContentLocation())
+            .setList(CONTENT_LANGUAGE, message.getProperties().getContentLanguage())
+            .setMap(CONTENT_DISPOSITION_PARAMETERS, message.getProperties().getContentDispositionParameters())
+            .setMap(CONTENT_TYPE_PARAMETERS, message.getProperties().getContentTypeParameters())
+            .setList(ATTACHMENTS, buildAttachmentUdt(message.getAttachments())));
+    }
+
     private Mono<Tuple2<BlobId, BlobId>> saveContent(MailboxMessage message) throws MailboxException {
         try {
             byte[] headerContent = IOUtils.toByteArray(message.getHeaderContent());
@@ -204,6 +229,12 @@ public class CassandraMessageDAOV3 {
             .collect(Guavate.toImmutableList());
     }
 
+    private ImmutableList<UDTValue> buildAttachmentUdt(List<MessageAttachmentRepresentation> attachments) {
+        return attachments.stream()
+            .map(this::toUDT)
+            .collect(Guavate.toImmutableList());
+    }
+
     private UDTValue toUDT(MessageAttachmentMetadata messageAttachment) {
         UDTValue result = typesProvider.getDefinedUserType(ATTACHMENTS)
             .newValue()
@@ -216,6 +247,18 @@ public class CassandraMessageDAOV3 {
         return result;
     }
 
+    private UDTValue toUDT(MessageAttachmentRepresentation messageAttachment) {
+        UDTValue result = typesProvider.getDefinedUserType(ATTACHMENTS)
+            .newValue()
+            .setString(Attachments.ID, messageAttachment.getAttachmentId().getId())
+            .setBool(Attachments.IS_INLINE, messageAttachment.isInline());
+        messageAttachment.getName()
+            .ifPresent(name -> result.setString(Attachments.NAME, name));
+        messageAttachment.getCid()
+            .ifPresent(cid -> result.setString(Attachments.CID, cid.getValue()));
+        return result;
+    }
+
     public Mono<MessageRepresentation> retrieveMessage(ComposedMessageIdWithMetaData id, FetchType fetchType) {
         CassandraMessageId cassandraMessageId = (CassandraMessageId) id.getComposedMessageId().getMessageId();
         return retrieveMessage(cassandraMessageId, fetchType);
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/MessageRepresentation.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/MessageRepresentation.java
index 19fd945..06274e5 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/MessageRepresentation.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/MessageRepresentation.java
@@ -35,19 +35,19 @@ public class MessageRepresentation {
     private final MessageId messageId;
     private final Date internalDate;
     private final Long size;
-    private final Integer bodySize;
+    private final Integer bodyStartOctet;
     private final SharedByteArrayInputStream content;
     private final Properties properties;
     private final List<MessageAttachmentRepresentation> attachments;
     private final BlobId headerId;
     private final BlobId bodyId;
 
-    public MessageRepresentation(MessageId messageId, Date internalDate, Long size, Integer bodySize, SharedByteArrayInputStream content,
+    public MessageRepresentation(MessageId messageId, Date internalDate, Long size, Integer bodyStartOctet, SharedByteArrayInputStream content,
                                  Properties properties, List<MessageAttachmentRepresentation> attachments, BlobId headerId, BlobId bodyId) {
         this.messageId = messageId;
         this.internalDate = internalDate;
         this.size = size;
-        this.bodySize = bodySize;
+        this.bodyStartOctet = bodyStartOctet;
         this.content = content;
         this.properties = properties;
         this.attachments = attachments;
@@ -62,7 +62,7 @@ public class MessageRepresentation {
             .uid(metadata.getComposedMessageId().getUid())
             .modseq(metadata.getModSeq())
             .internalDate(internalDate)
-            .bodyStartOctet(bodySize)
+            .bodyStartOctet(bodyStartOctet)
             .size(size)
             .content(content)
             .flags(metadata.getFlags())
@@ -71,6 +71,18 @@ public class MessageRepresentation {
             .build();
     }
 
+    public Date getInternalDate() {
+        return internalDate;
+    }
+
+    public Long getSize() {
+        return size;
+    }
+
+    public Integer getBodyStartOctet() {
+        return bodyStartOctet;
+    }
+
     public MessageId getMessageId() {
         return messageId;
     }
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/migration/MessageV3Migration.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/migration/MessageV3Migration.java
new file mode 100644
index 0000000..2c37c4c
--- /dev/null
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/migration/MessageV3Migration.java
@@ -0,0 +1,119 @@
+/****************************************************************
+ * 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.migration;
+
+import java.time.Clock;
+import java.time.Instant;
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+import org.apache.james.backends.cassandra.migration.Migration;
+import org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
+import org.apache.james.mailbox.cassandra.mail.CassandraMessageDAO;
+import org.apache.james.mailbox.cassandra.mail.CassandraMessageDAOV3;
+import org.apache.james.mailbox.cassandra.mail.MessageRepresentation;
+import org.apache.james.task.Task;
+import org.apache.james.task.TaskExecutionDetails;
+import org.apache.james.task.TaskType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import reactor.core.publisher.Mono;
+
+public class MessageV3Migration implements Migration {
+    private static final int CONCURRENCY = 50;
+
+    static class MessageV3MigrationTask implements Task {
+        private final MessageV3Migration migration;
+
+        MessageV3MigrationTask(MessageV3Migration migration) {
+            this.migration = migration;
+        }
+
+        @Override
+        public Result run() throws InterruptedException {
+            return migration.runTask();
+        }
+
+        @Override
+        public TaskType type() {
+            return TYPE;
+        }
+
+        @Override
+        public Optional<TaskExecutionDetails.AdditionalInformation> details() {
+            return Optional.of(migration.getAdditionalInformation());
+        }
+    }
+
+    public static class AdditionalInformation implements TaskExecutionDetails.AdditionalInformation {
+        private final Instant timestamp;
+
+        public AdditionalInformation(Instant timestamp) {
+            this.timestamp = timestamp;
+        }
+
+        @Override
+        public Instant timestamp() {
+            return timestamp;
+        }
+    }
+
+    public static final Logger LOGGER = LoggerFactory.getLogger(MessageV3Migration.class);
+    public static final TaskType TYPE = TaskType.of("cassandra-message-v3-migration");
+    private final CassandraMessageDAO daoV2;
+    private final CassandraMessageDAOV3 daoV3;
+
+    @Inject
+    public MessageV3Migration(CassandraMessageDAO daoV2, CassandraMessageDAOV3 daoV3) {
+        this.daoV2 = daoV2;
+        this.daoV3 = daoV3;
+    }
+
+    @Override
+    public void apply() {
+        daoV2.list()
+            .flatMap(this::migrate, CONCURRENCY)
+            .doOnError(t -> LOGGER.error("Error while performing migration", t))
+            .blockLast();
+    }
+
+    private Mono<Void> migrate(MessageRepresentation messageRepresentation) {
+        return daoV3.save(messageRepresentation)
+            .then(daoV2.delete((CassandraMessageId) messageRepresentation.getMessageId()))
+            .onErrorResume(error -> handleErrorMigrate(messageRepresentation, error))
+            .then();
+    }
+
+    private Mono<Void> handleErrorMigrate(MessageRepresentation messageRepresentation, Throwable throwable) {
+        LOGGER.error("Error while performing migration for {}", messageRepresentation.getMessageId(), throwable);
+        return Mono.empty();
+    }
+
+    @Override
+    public Task asTask() {
+        return new MessageV3MigrationTask(this);
+    }
+
+    AdditionalInformation getAdditionalInformation() {
+        return new AdditionalInformation(Clock.systemUTC().instant());
+    }
+}
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/migration/MessageV3MigrationTaskAdditionalInformationDTO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/migration/MessageV3MigrationTaskAdditionalInformationDTO.java
new file mode 100644
index 0000000..c5609cc
--- /dev/null
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/migration/MessageV3MigrationTaskAdditionalInformationDTO.java
@@ -0,0 +1,69 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mailbox.cassandra.mail.migration;
+
+import java.time.Instant;
+
+import org.apache.james.json.DTOModule;
+import org.apache.james.server.task.json.dto.AdditionalInformationDTO;
+import org.apache.james.server.task.json.dto.AdditionalInformationDTOModule;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class MessageV3MigrationTaskAdditionalInformationDTO implements AdditionalInformationDTO {
+    private static MessageV3MigrationTaskAdditionalInformationDTO fromDomainObject(MessageV3Migration.AdditionalInformation additionalInformation, String type) {
+        return new MessageV3MigrationTaskAdditionalInformationDTO(
+            type,
+            additionalInformation.timestamp()
+        );
+    }
+
+    public static final AdditionalInformationDTOModule<MessageV3Migration.AdditionalInformation, MessageV3MigrationTaskAdditionalInformationDTO> MODULE =
+        DTOModule
+            .forDomainObject(MessageV3Migration.AdditionalInformation.class)
+            .convertToDTO(MessageV3MigrationTaskAdditionalInformationDTO.class)
+            .toDomainObjectConverter(MessageV3MigrationTaskAdditionalInformationDTO::toDomainObject)
+            .toDTOConverter(MessageV3MigrationTaskAdditionalInformationDTO::fromDomainObject)
+            .typeName(MessageV3Migration.TYPE.asString())
+            .withFactory(AdditionalInformationDTOModule::new);
+
+    private final String type;
+    private final Instant timestamp;
+
+    public MessageV3MigrationTaskAdditionalInformationDTO(@JsonProperty("type") String type,
+                                                          @JsonProperty("timestamp") Instant timestamp) {
+        this.type = type;
+        this.timestamp = timestamp;
+    }
+
+    @Override
+    public Instant getTimestamp() {
+        return timestamp;
+    }
+
+    @Override
+    public String getType() {
+        return type;
+    }
+
+    private MessageV3Migration.AdditionalInformation toDomainObject() {
+        return new MessageV3Migration.AdditionalInformation(timestamp);
+    }
+}
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/migration/MessageV3MigrationTaskDTO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/migration/MessageV3MigrationTaskDTO.java
new file mode 100644
index 0000000..87a1fec
--- /dev/null
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/migration/MessageV3MigrationTaskDTO.java
@@ -0,0 +1,59 @@
+/****************************************************************
+ * 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.migration;
+
+import java.util.function.Function;
+
+import org.apache.james.json.DTOModule;
+import org.apache.james.server.task.json.dto.TaskDTO;
+import org.apache.james.server.task.json.dto.TaskDTOModule;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class MessageV3MigrationTaskDTO implements TaskDTO {
+
+    private static MessageV3MigrationTaskDTO fromDomainObject(MessageV3Migration.MessageV3MigrationTask task, String type) {
+        return new MessageV3MigrationTaskDTO(type);
+    }
+
+    public static final Function<MessageV3Migration, TaskDTOModule<MessageV3Migration.MessageV3MigrationTask, MessageV3MigrationTaskDTO>> MODULE = (migration) ->
+        DTOModule
+            .forDomainObject(MessageV3Migration.MessageV3MigrationTask.class)
+            .convertToDTO(MessageV3MigrationTaskDTO.class)
+            .toDomainObjectConverter(dto -> dto.toDomainObject(migration))
+            .toDTOConverter(MessageV3MigrationTaskDTO::fromDomainObject)
+            .typeName(MessageV3Migration.TYPE.asString())
+            .withFactory(TaskDTOModule::new);
+
+    private final String type;
+
+    public MessageV3MigrationTaskDTO(@JsonProperty("type") String type) {
+        this.type = type;
+    }
+
+    @Override
+    public String getType() {
+        return type;
+    }
+
+    private MessageV3Migration.MessageV3MigrationTask toDomainObject(MessageV3Migration migration) {
+        return new MessageV3Migration.MessageV3MigrationTask(migration);
+    }
+}
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOTest.java
index bb52781..5f044db 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOTest.java
@@ -94,6 +94,7 @@ class CassandraMessageDAOTest {
             cassandra.getTypesProvider(),
             blobStore,
             blobIdFactory,
+            messageIdFactory,
             cassandraCluster.getCassandraConsistenciesConfiguration());
 
         messageIdWithMetadata = ComposedMessageIdWithMetaData.builder()
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/migration/MessageV3MigrationTaskSerializationTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/migration/MessageV3MigrationTaskSerializationTest.java
new file mode 100644
index 0000000..1799543
--- /dev/null
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/migration/MessageV3MigrationTaskSerializationTest.java
@@ -0,0 +1,52 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mailbox.cassandra.mail.migration;
+
+import static org.mockito.Mockito.mock;
+
+import java.time.Instant;
+
+import org.apache.james.JsonSerializationVerifier;
+import org.junit.jupiter.api.Test;
+
+class MessageV3MigrationTaskSerializationTest {
+    private static final Instant TIMESTAMP = Instant.parse("2018-11-13T12:00:55Z");
+    private static final MessageV3Migration MIGRATION = mock(MessageV3Migration.class);
+    private static final MessageV3Migration.MessageV3MigrationTask TASK = new MessageV3Migration.MessageV3MigrationTask(MIGRATION);
+    private static final String SERIALIZED_TASK = "{\"type\": \"cassandra-message-v3-migration\"}";
+    private static final MessageV3Migration.AdditionalInformation DETAILS = new MessageV3Migration.AdditionalInformation(TIMESTAMP);
+    private static final String SERIALIZED_ADDITIONAL_INFORMATION = "{\"type\": \"cassandra-message-v3-migration\", \"timestamp\":\"2018-11-13T12:00:55Z\"}";
+
+    @Test
+    void taskShouldBeSerializable() throws Exception {
+        JsonSerializationVerifier.dtoModule(MessageV3MigrationTaskDTO.MODULE.apply(MIGRATION))
+            .bean(TASK)
+            .json(SERIALIZED_TASK)
+            .verify();
+    }
+
+    @Test
+    void additionalInformationShouldBeSerializable() throws Exception {
+        JsonSerializationVerifier.dtoModule(MessageV3MigrationTaskAdditionalInformationDTO.MODULE)
+            .bean(DETAILS)
+            .json(SERIALIZED_ADDITIONAL_INFORMATION)
+            .verify();
+    }
+}
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/migration/MessageV3MigrationTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/migration/MessageV3MigrationTest.java
new file mode 100644
index 0000000..51d9d31
--- /dev/null
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/migration/MessageV3MigrationTest.java
@@ -0,0 +1,157 @@
+/****************************************************************
+ * 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.migration;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.List;
+
+import javax.mail.Flags;
+import javax.mail.util.SharedByteArrayInputStream;
+
+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.versions.CassandraSchemaVersionModule;
+import org.apache.james.blob.api.BlobStore;
+import org.apache.james.blob.api.HashBlobId;
+import org.apache.james.blob.cassandra.CassandraBlobModule;
+import org.apache.james.blob.cassandra.CassandraBlobStoreFactory;
+import org.apache.james.mailbox.MessageUid;
+import org.apache.james.mailbox.cassandra.ids.CassandraId;
+import org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
+import org.apache.james.mailbox.cassandra.mail.CassandraMessageDAO;
+import org.apache.james.mailbox.cassandra.mail.CassandraMessageDAOV3;
+import org.apache.james.mailbox.cassandra.mail.MessageRepresentation;
+import org.apache.james.mailbox.cassandra.modules.CassandraMessageModule;
+import org.apache.james.mailbox.model.MessageAttachmentMetadata;
+import org.apache.james.mailbox.model.MessageId;
+import org.apache.james.mailbox.store.mail.MessageMapper;
+import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder;
+import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.google.common.collect.ImmutableList;
+
+class MessageV3MigrationTest {
+    private static final int BODY_START = 16;
+    private static final CassandraId MAILBOX_ID = CassandraId.timeBased();
+    private static final String CONTENT = "Subject: Test7 \n\nBody7\n.\n";
+    private static final MessageUid messageUid = MessageUid.of(1);
+    private static final List<MessageAttachmentMetadata> NO_ATTACHMENT = ImmutableList.of();
+
+    public static final CassandraModule MODULES = CassandraModule.aggregateModules(
+        CassandraMessageModule.MODULE,
+        CassandraBlobModule.MODULE,
+        CassandraSchemaVersionModule.MODULE);
+
+    @RegisterExtension
+    static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(MODULES);
+
+    private CassandraMessageDAO daoV2;
+    private CassandraMessageDAOV3 daoV3;
+    private CassandraMessageId.Factory messageIdFactory;
+
+    @BeforeEach
+    void setUp(CassandraCluster cassandra) {
+        BlobStore blobStore = CassandraBlobStoreFactory.forTesting(cassandra.getConf())
+            .passthrough();
+        HashBlobId.Factory blobIdFactory = new HashBlobId.Factory();
+        daoV2 = new CassandraMessageDAO(
+            cassandra.getConf(),
+            cassandra.getTypesProvider(),
+            blobStore,
+            blobIdFactory,
+            new CassandraMessageId.Factory(),
+            cassandraCluster.getCassandraConsistenciesConfiguration());
+        daoV3 = new CassandraMessageDAOV3(
+            cassandra.getConf(),
+            cassandra.getTypesProvider(),
+            blobStore,
+            blobIdFactory,
+            cassandraCluster.getCassandraConsistenciesConfiguration());
+        messageIdFactory = new CassandraMessageId.Factory();
+    }
+
+    @Test
+    void migrationTaskShouldMoveDataToMostRecentDao() throws Exception{
+        SimpleMailboxMessage message1 = createMessage(messageIdFactory.generate());
+        SimpleMailboxMessage message2 = createMessage(messageIdFactory.generate());
+        SimpleMailboxMessage message3 = createMessage(messageIdFactory.generate());
+        SimpleMailboxMessage message4 = createMessage(messageIdFactory.generate());
+
+        daoV2.save(message1).block();
+        daoV2.save(message2).block();
+        daoV2.save(message3).block();
+        daoV2.save(message4).block();
+
+        new MessageV3Migration(daoV2, daoV3).apply();
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(daoV3.retrieveMessage((CassandraMessageId) message1.getMessageId(), MessageMapper.FetchType.Metadata).block().getMessageId())
+                .isEqualTo(message1.getMessageId());
+            softly.assertThat(daoV3.retrieveMessage((CassandraMessageId) message2.getMessageId(), MessageMapper.FetchType.Metadata).block().getMessageId())
+                .isEqualTo(message2.getMessageId());
+            softly.assertThat(daoV3.retrieveMessage((CassandraMessageId) message3.getMessageId(), MessageMapper.FetchType.Metadata).block().getMessageId())
+                .isEqualTo(message3.getMessageId());
+            softly.assertThat(daoV3.retrieveMessage((CassandraMessageId) message4.getMessageId(), MessageMapper.FetchType.Metadata).block().getMessageId())
+                .isEqualTo(message4.getMessageId());
+
+            softly.assertThat(daoV2.list().collectList().block()).isEmpty();
+        });
+    }
+
+    @Test
+    void migrationTaskShouldPreserveMessageContent() throws Exception{
+        SimpleMailboxMessage message1 = createMessage(messageIdFactory.generate());
+        daoV2.save(message1).block();
+        MessageRepresentation original = daoV2.retrieveMessage((CassandraMessageId) message1.getMessageId(), MessageMapper.FetchType.Metadata).block();
+
+        new MessageV3Migration(daoV2, daoV3).apply();
+        MessageRepresentation migrated = daoV3.retrieveMessage((CassandraMessageId) message1.getMessageId(), MessageMapper.FetchType.Metadata).block();
+
+        int start = 0;
+        int end = -1;
+        assertThat(migrated).isEqualToComparingOnlyGivenFields(original, "messageId",
+            "internalDate", "size", "bodyStartOctet", "properties", "attachments", "headerId", "bodyId");
+        assertThat(migrated.getContent().newStream(start, end))
+            .hasSameContentAs(original.getContent().newStream(start, end));
+    }
+
+    private SimpleMailboxMessage createMessage(MessageId messageId) {
+        return SimpleMailboxMessage.builder()
+            .messageId(messageId)
+            .mailboxId(MAILBOX_ID)
+            .uid(messageUid)
+            .internalDate(new Date())
+            .bodyStartOctet(MessageV3MigrationTest.BODY_START)
+            .size(MessageV3MigrationTest.CONTENT.length())
+            .content(new SharedByteArrayInputStream(MessageV3MigrationTest.CONTENT.getBytes(StandardCharsets.UTF_8)))
+            .flags(new Flags())
+            .properties(new PropertyBuilder().build())
+            .addAttachments(NO_ATTACHMENT)
+            .build();
+    }
+}
\ No newline at end of file
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/Properties.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/Properties.java
index 5754b71..6b63d07 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/Properties.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/Properties.java
@@ -41,6 +41,7 @@ import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_SUB_T
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.SortedMap;
 import java.util.TreeMap;
 
@@ -222,6 +223,22 @@ public class Properties {
         return new ArrayList<>(properties);
     }
 
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof Properties) {
+            Properties that = (Properties) o;
+
+            return Objects.equals(this.textualLineCount, that.textualLineCount)
+                && Objects.equals(this.properties, that.properties);
+        }
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(textualLineCount, properties);
+    }
+
     /**
      * Constructs a <code>String</code> with all attributes
      * in name = value format.
diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/webadmin/CassandraRoutesModule.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/webadmin/CassandraRoutesModule.java
index 90e8911..ff0f783 100644
--- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/webadmin/CassandraRoutesModule.java
+++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/webadmin/CassandraRoutesModule.java
@@ -27,6 +27,7 @@ import org.apache.james.backends.cassandra.versions.SchemaTransition;
 import org.apache.james.backends.cassandra.versions.SchemaVersion;
 import org.apache.james.mailbox.cassandra.mail.migration.MailboxPathV2Migration;
 import org.apache.james.mailbox.cassandra.mail.migration.MailboxPathV3Migration;
+import org.apache.james.mailbox.cassandra.mail.migration.MessageV3Migration;
 import org.apache.james.rrt.cassandra.migration.MappingsSourcesMigration;
 import org.apache.james.webadmin.Routes;
 import org.apache.james.webadmin.routes.CassandraMailboxMergingRoutes;
@@ -42,6 +43,7 @@ public class CassandraRoutesModule extends AbstractModule {
     private static final SchemaTransition FROM_V5_TO_V6 = SchemaTransition.to(new SchemaVersion(6));
     private static final SchemaTransition FROM_V6_TO_V7 = SchemaTransition.to(new SchemaVersion(7));
     private static final SchemaTransition FROM_V7_TO_V8 = SchemaTransition.to(new SchemaVersion(8));
+    private static final SchemaTransition FROM_V8_TO_V9 = SchemaTransition.to(new SchemaVersion(9));
 
     @Override
     protected void configure() {
@@ -60,6 +62,7 @@ public class CassandraRoutesModule extends AbstractModule {
         allMigrationClazzBinder.addBinding(FROM_V5_TO_V6).to(MailboxPathV2Migration.class);
         allMigrationClazzBinder.addBinding(FROM_V6_TO_V7).to(MappingsSourcesMigration.class);
         allMigrationClazzBinder.addBinding(FROM_V7_TO_V8).to(MailboxPathV3Migration.class);
+        allMigrationClazzBinder.addBinding(FROM_V8_TO_V9).to(MessageV3Migration.class);
 
         bind(SchemaVersion.class)
             .annotatedWith(Names.named(CassandraMigrationService.LATEST_VERSION))


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


[james-project] 09/11: JAMES-3277 JMAP Rely on MessageManager::setFlags to optimize massive flags updates (Draft)

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

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

commit 2d54e04db52cb4df4f340c13050e7a2fd41385da
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Oct 21 11:55:19 2020 +0700

    JAMES-3277 JMAP Rely on MessageManager::setFlags to optimize massive flags updates (Draft)
    
    We notice that many slow traces at the JMAP level are flag updates, generating a high count of Cassandra queries.
    
    By grouping the update we enable to share modseq allocation, and counter updates, reducing drastically query count.
    
    Furthermore parallelism with a flag update stage is unlocked, significantly fasting the request up.
    
    Query count before: 10m+9 (6 message => 69 queries, 9 messages => 99 queries, 12 messages => 129 queries)
    Query count after: 4m+16 (6 message => 40 queries, 9 messages => 52 queries, 64 messages => 129 queries)
---
 .../java/org/apache/james/util/StreamUtils.java    |  6 ++
 .../methods/integration/SetMessagesMethodTest.java | 72 ++++++++++++++++++++--
 .../draft/methods/SetMessagesUpdateProcessor.java  | 58 ++++++++++++++++-
 .../james/jmap/draft/model/UpdateMessagePatch.java | 23 +++++++
 .../methods/SetMessagesUpdateProcessorTest.java    |  4 ++
 5 files changed, 154 insertions(+), 9 deletions(-)

diff --git a/server/container/util/src/main/java/org/apache/james/util/StreamUtils.java b/server/container/util/src/main/java/org/apache/james/util/StreamUtils.java
index 73452ad..6dc5db5 100644
--- a/server/container/util/src/main/java/org/apache/james/util/StreamUtils.java
+++ b/server/container/util/src/main/java/org/apache/james/util/StreamUtils.java
@@ -59,6 +59,12 @@ public class StreamUtils {
         return streams.flatMap(Function.identity());
     }
 
+    public static <T> boolean isSingleValued(Stream<T> stream) {
+        return stream.distinct()
+            .limit(2)
+            .count() == 1;
+    }
+
     @SafeVarargs
     public static <T> Stream<T> flatten(Stream<T>... streams) {
         return flatten(Arrays.stream(streams));
diff --git a/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/SetMessagesMethodTest.java b/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/SetMessagesMethodTest.java
index 988df1f..2fbed4fff 100644
--- a/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/SetMessagesMethodTest.java
+++ b/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/SetMessagesMethodTest.java
@@ -119,6 +119,7 @@ import org.apache.james.utils.SMTPMessageSender;
 import org.apache.james.utils.TestIMAPClient;
 import org.apache.mailet.Mail;
 import org.apache.mailet.base.test.FakeMail;
+import org.assertj.core.api.SoftAssertions;
 import org.awaitility.Duration;
 import org.hamcrest.Matcher;
 import org.hamcrest.Matchers;
@@ -144,12 +145,12 @@ import net.javacrumbs.jsonunit.core.internal.Options;
 public abstract class SetMessagesMethodTest {
     private static final String FORWARDED = "$Forwarded";
     private static final int _1MB = 1024 * 1024;
-    private static final Username USERNAME = Username.of("username@" + DOMAIN);
+    protected static final Username USERNAME = Username.of("username@" + DOMAIN);
     private static final String ALIAS_OF_USERNAME_MAIL = "alias@" + DOMAIN;
     private static final String GROUP_MAIL = "group@" + DOMAIN;
     private static final Username ALIAS_OF_USERNAME = Username.of(ALIAS_OF_USERNAME_MAIL);
     private static final String PASSWORD = "password";
-    private static final MailboxPath USER_MAILBOX = MailboxPath.forUser(USERNAME, "mailbox");
+    protected static final MailboxPath USER_MAILBOX = MailboxPath.forUser(USERNAME, "mailbox");
     private static final String NOT_UPDATED = ARGUMENTS + ".notUpdated";
     private static final int BIG_MESSAGE_SIZE = 20 * 1024 * 1024;
     public static final String OCTET_CONTENT_TYPE = "application/octet-stream";
@@ -161,11 +162,11 @@ public abstract class SetMessagesMethodTest {
 
     protected abstract MessageId randomMessageId();
 
-    private AccessToken accessToken;
-    private GuiceJamesServer jmapServer;
-    private MailboxProbe mailboxProbe;
+    protected AccessToken accessToken;
+    protected GuiceJamesServer jmapServer;
+    protected MailboxProbe mailboxProbe;
     private DataProbe dataProbe;
-    private MessageIdProbe messageProbe;
+    protected MessageIdProbe messageProbe;
     private ACLProbe aclProbe;
 
     @Before
@@ -424,6 +425,65 @@ public abstract class SetMessagesMethodTest {
     }
 
     @Test
+    public void massiveFlagUpdateShouldBeApplied() throws MailboxException {
+        mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
+
+        ComposedMessageId message1 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
+            new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
+        ComposedMessageId message2 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
+            new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
+        ComposedMessageId message3 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
+            new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags(Flag.SEEN));
+        ComposedMessageId message4 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
+            new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
+        ComposedMessageId message5 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
+            new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags(Flag.ANSWERED));
+        ComposedMessageId message6 = mailboxProbe.appendMessage(USERNAME.asString(), USER_MAILBOX,
+            new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(StandardCharsets.UTF_8)), new Date(), false, new Flags());
+
+        String serializedMessageId1 = message1.getMessageId().serialize();
+        String serializedMessageId2 = message2.getMessageId().serialize();
+        String serializedMessageId3 = message3.getMessageId().serialize();
+        String serializedMessageId4 = message4.getMessageId().serialize();
+        String serializedMessageId5 = message5.getMessageId().serialize();
+        String serializedMessageId6 = message6.getMessageId().serialize();
+
+        // When
+        given()
+            .header("Authorization", accessToken.asString())
+            .body(String.format("[[\"setMessages\", {\"update\": {" +
+                "  \"%s\" : { \"isUnread\" : false }, " +
+                "  \"%s\" : { \"isUnread\" : false }, " +
+                "  \"%s\" : { \"isUnread\" : false }, " +
+                "  \"%s\" : { \"isUnread\" : false }, " +
+                "  \"%s\" : { \"isUnread\" : false }, " +
+                "  \"%s\" : { \"isUnread\" : false } " +
+                "} }, \"#0\"]]", serializedMessageId1, serializedMessageId2, serializedMessageId3,
+                serializedMessageId4, serializedMessageId5, serializedMessageId6))
+        .when()
+            .post("/jmap")
+        // Then
+        .then()
+            .log().ifValidationFails().body(ARGUMENTS + ".updated", hasSize(6));
+
+        Flags flags1 = messageProbe.getMessages(message1.getMessageId(), USERNAME).iterator().next().getFlags();
+        Flags flags2 = messageProbe.getMessages(message1.getMessageId(), USERNAME).iterator().next().getFlags();
+        Flags flags3 = messageProbe.getMessages(message1.getMessageId(), USERNAME).iterator().next().getFlags();
+        Flags flags4 = messageProbe.getMessages(message1.getMessageId(), USERNAME).iterator().next().getFlags();
+        Flags flags5 = messageProbe.getMessages(message1.getMessageId(), USERNAME).iterator().next().getFlags();
+        Flags flags6 = messageProbe.getMessages(message1.getMessageId(), USERNAME).iterator().next().getFlags();
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(flags1).isEqualTo(new Flags(Flag.SEEN));
+            softly.assertThat(flags2).isEqualTo(new Flags(Flag.SEEN));
+            softly.assertThat(flags3).isEqualTo(new Flags(Flag.SEEN));
+            softly.assertThat(flags4).isEqualTo(new Flags(Flag.SEEN));
+            softly.assertThat(flags5).isEqualTo(new Flags(Flag.SEEN));
+            softly.assertThat(flags6).isEqualTo(new Flags(Flag.SEEN));
+        });
+    }
+
+    @Test
     public void setMessagesWithUpdateShouldReturnAnErrorWhenBothIsFlagAndKeywordsArePassed() throws MailboxException {
         mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME.asString(), "mailbox");
 
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessor.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessor.java
index 2c74d40..84d31e5 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessor.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessor.java
@@ -32,6 +32,7 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import javax.inject.Inject;
+import javax.mail.Flags;
 import javax.mail.MessagingException;
 import javax.mail.Session;
 import javax.mail.internet.MimeMessage;
@@ -47,6 +48,7 @@ import org.apache.james.jmap.draft.model.SetMessagesRequest;
 import org.apache.james.jmap.draft.model.SetMessagesResponse;
 import org.apache.james.jmap.draft.model.UpdateMessagePatch;
 import org.apache.james.jmap.draft.utils.KeywordsCombiner;
+import org.apache.james.mailbox.MailboxManager;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.MessageIdManager;
 import org.apache.james.mailbox.MessageManager;
@@ -61,11 +63,13 @@ import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.mailbox.model.MailboxId.Factory;
 import org.apache.james.mailbox.model.MessageId;
 import org.apache.james.mailbox.model.MessageMoves;
+import org.apache.james.mailbox.model.MessageRange;
 import org.apache.james.mailbox.model.MessageResult;
 import org.apache.james.metrics.api.MetricFactory;
 import org.apache.james.metrics.api.TimeMetric;
 import org.apache.james.rrt.api.CanSendFrom;
 import org.apache.james.server.core.MailImpl;
+import org.apache.james.util.StreamUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -85,6 +89,7 @@ public class SetMessagesUpdateProcessor implements SetMessagesProcessor {
 
     private final UpdateMessagePatchConverter updatePatchConverter;
     private final MessageIdManager messageIdManager;
+    private final MailboxManager mailboxManager;
     private final SystemMailboxesProvider systemMailboxesProvider;
     private final Factory mailboxIdFactory;
     private final MetricFactory metricFactory;
@@ -97,6 +102,7 @@ public class SetMessagesUpdateProcessor implements SetMessagesProcessor {
     @VisibleForTesting SetMessagesUpdateProcessor(
             UpdateMessagePatchConverter updatePatchConverter,
             MessageIdManager messageIdManager,
+            MailboxManager mailboxManager,
             SystemMailboxesProvider systemMailboxesProvider,
             Factory mailboxIdFactory,
             MessageSender messageSender,
@@ -105,6 +111,7 @@ public class SetMessagesUpdateProcessor implements SetMessagesProcessor {
             CanSendFrom canSendFrom) {
         this.updatePatchConverter = updatePatchConverter;
         this.messageIdManager = messageIdManager;
+        this.mailboxManager = mailboxManager;
         this.systemMailboxesProvider = systemMailboxesProvider;
         this.mailboxIdFactory = mailboxIdFactory;
         this.metricFactory = metricFactory;
@@ -144,14 +151,59 @@ public class SetMessagesUpdateProcessor implements SetMessagesProcessor {
             .collect(Guavate.toImmutableListMultimap(metaData -> metaData.getComposedMessageId().getMessageId()))
             .block();
 
-        patches.forEach((id, patch) -> {
+        if (isAMassiveFlagUpdate(patches, messages)) {
+            applyRangedFlagUpdate(patches, messages, responseBuilder, mailboxSession);
+        } else {
+            patches.forEach((id, patch) -> {
                 if (patch.isValid()) {
                     update(outboxes, id, patch, mailboxSession, responseBuilder, messages);
                 } else {
                     handleInvalidRequest(responseBuilder, id, patch.getValidationErrors(), patch);
                 }
-            }
-        );
+            });
+        }
+    }
+
+    private boolean isAMassiveFlagUpdate(Map<MessageId, UpdateMessagePatch> patches, Multimap<MessageId, ComposedMessageIdWithMetaData> messages) {
+        // The same patch, that only represents a flag update, is applied to messages within a single mailbox
+        return StreamUtils.isSingleValued(patches.values().stream())
+            && StreamUtils.isSingleValued(messages.values().stream().map(metaData -> metaData.getComposedMessageId().getMailboxId()))
+            && patches.values().iterator().next().isOnlyAFlagUpdate()
+            && messages.size() > 3;
+    }
+
+    private void applyRangedFlagUpdate(Map<MessageId, UpdateMessagePatch> patches, Multimap<MessageId, ComposedMessageIdWithMetaData> messages, SetMessagesResponse.Builder responseBuilder, MailboxSession mailboxSession) {
+        MailboxId mailboxId = messages.values()
+            .iterator()
+            .next()
+            .getComposedMessageId()
+            .getMailboxId();
+        UpdateMessagePatch patch = patches.values().iterator().next();
+        List<MessageRange> uidRanges = MessageRange.toRanges(messages.values().stream().map(metaData -> metaData.getComposedMessageId().getUid())
+            .distinct()
+            .collect(Guavate.toImmutableList()));
+
+        if (patch.isValid()) {
+            uidRanges.forEach(range -> {
+                ImmutableList<MessageId> messageIds = messages.entries()
+                    .stream()
+                    .filter(entry -> range.includes(entry.getValue().getComposedMessageId().getUid()))
+                    .map(Map.Entry::getKey)
+                    .distinct()
+                    .collect(Guavate.toImmutableList());
+                try {
+                    mailboxManager.getMailbox(mailboxId, mailboxSession)
+                        .setFlags(patch.applyToState(new Flags()), FlagsUpdateMode.REPLACE, range, mailboxSession);
+                    responseBuilder.updated(messageIds);
+                } catch (MailboxException e) {
+                    messageIds
+                        .forEach(messageId -> handleMessageUpdateException(messageId, responseBuilder, e));
+                }
+            });
+        } else {
+            messages.keySet()
+                .forEach(messageId -> handleInvalidRequest(responseBuilder, messageId, patch.getValidationErrors(), patch));
+        }
     }
 
     private void update(Set<MailboxId> outboxes, MessageId messageId, UpdateMessagePatch updateMessagePatch, MailboxSession mailboxSession,
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/UpdateMessagePatch.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/UpdateMessagePatch.java
index 59cb832..4aa3663 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/UpdateMessagePatch.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/UpdateMessagePatch.java
@@ -21,6 +21,7 @@ package org.apache.james.jmap.draft.model;
 
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 
@@ -139,6 +140,10 @@ public class UpdateMessagePatch {
         return !oldKeywords.isPresent() && !keywords.isPresent();
     }
 
+    public boolean isOnlyAFlagUpdate() {
+        return !mailboxIds.isPresent() && (oldKeywords.isPresent() || keywords.isPresent());
+    }
+
     public ImmutableList<ValidationResult> getValidationErrors() {
         return validationErrors;
     }
@@ -156,6 +161,24 @@ public class UpdateMessagePatch {
     }
 
     @Override
+    public final boolean equals(Object o) {
+        if (o instanceof UpdateMessagePatch) {
+            UpdateMessagePatch that = (UpdateMessagePatch) o;
+
+            return Objects.equals(this.mailboxIds, that.mailboxIds)
+                && Objects.equals(this.keywords, that.keywords)
+                && Objects.equals(this.oldKeywords, that.oldKeywords)
+                && Objects.equals(this.validationErrors, that.validationErrors);
+        }
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(mailboxIds, keywords, oldKeywords, validationErrors);
+    }
+
+    @Override
     public String toString() {
         return MoreObjects.toStringHelper(this)
             .add("mailboxIds", mailboxIds)
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java
index aa01dcd..b33f25c 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java
@@ -178,8 +178,10 @@ public class SetMessagesUpdateProcessorTest {
         referenceUpdater = new ReferenceUpdater(mockMessageIdManager, mockedMailboxManager);
 
         UpdateMessagePatchConverter updateMessagePatchConverter = null;
+        MailboxManager mailboxManager = null;
         sut = new SetMessagesUpdateProcessor(updateMessagePatchConverter,
             messageIdManager,
+            mailboxManager,
             fakeSystemMailboxesProvider,
             mockedMailboxIdFactory,
             messageSender,
@@ -234,8 +236,10 @@ public class SetMessagesUpdateProcessorTest {
                 .thenReturn(mockInvalidPatch);
 
 
+        MailboxManager mailboxManager = null;
         SetMessagesUpdateProcessor sut = new SetMessagesUpdateProcessor(mockConverter,
             mockMessageIdManager,
+            mailboxManager,
             fakeSystemMailboxesProvider,
             mockedMailboxIdFactory,
             messageSender,


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


[james-project] 01/11: JAMES-3350 Significantly fasten Cassandra backend integration tests

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

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

commit 2adacf4d3e6209b5f340c5421d2c7a28439948ed
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon Oct 19 14:44:59 2020 +0700

    JAMES-3350 Significantly fasten Cassandra backend integration tests
    
    There was a timeout connecting the ES metrics reporter causing execution of a single test taking 2 minutes instead of 53 seconds
---
 .../src/test/java/org/apache/james/CassandraJmapTestRule.java           | 2 --
 .../test/java/org/apache/james/CassandraRabbitMQAwsS3JmapTestRule.java  | 2 --
 .../org/apache/james/jmap/cassandra/cucumber/CassandraStepdefs.java     | 2 --
 .../james/jmap/rabbitmq/cucumber/awss3/RabbitMQAwsS3Stepdefs.java       | 2 --
 4 files changed, 8 deletions(-)

diff --git a/server/container/guice/cassandra-guice/src/test/java/org/apache/james/CassandraJmapTestRule.java b/server/container/guice/cassandra-guice/src/test/java/org/apache/james/CassandraJmapTestRule.java
index 6a34304..bb744a2 100644
--- a/server/container/guice/cassandra-guice/src/test/java/org/apache/james/CassandraJmapTestRule.java
+++ b/server/container/guice/cassandra-guice/src/test/java/org/apache/james/CassandraJmapTestRule.java
@@ -21,7 +21,6 @@ package org.apache.james;
 
 import java.io.IOException;
 
-import org.apache.james.modules.TestDockerESMetricReporterModule;
 import org.apache.james.modules.TestJMAPServerModule;
 import org.apache.james.webadmin.WebAdminConfiguration;
 import org.junit.rules.TemporaryFolder;
@@ -62,7 +61,6 @@ public class CassandraJmapTestRule implements TestRule {
 
         return CassandraJamesServerMain.createServer(configuration)
             .overrideWith(new TestJMAPServerModule())
-            .overrideWith(new TestDockerESMetricReporterModule(dockerElasticSearchRule.getDockerEs().getHttpHost()))
             .overrideWith(guiceModuleTestRule.getModule())
             .overrideWith((binder -> binder.bind(CleanupTasksPerformer.class).asEagerSingleton()))
             .overrideWith(binder -> binder.bind(WebAdminConfiguration.class).toInstance(WebAdminConfiguration.TEST_CONFIGURATION))
diff --git a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/CassandraRabbitMQAwsS3JmapTestRule.java b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/CassandraRabbitMQAwsS3JmapTestRule.java
index 6b86dbf..c553a01 100644
--- a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/CassandraRabbitMQAwsS3JmapTestRule.java
+++ b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/CassandraRabbitMQAwsS3JmapTestRule.java
@@ -22,7 +22,6 @@ package org.apache.james;
 import java.io.IOException;
 
 import org.apache.james.backends.rabbitmq.DockerRabbitMQSingleton;
-import org.apache.james.modules.TestDockerESMetricReporterModule;
 import org.apache.james.modules.TestJMAPServerModule;
 import org.apache.james.modules.TestRabbitMQModule;
 import org.apache.james.modules.blobstore.BlobStoreConfiguration;
@@ -73,7 +72,6 @@ public class CassandraRabbitMQAwsS3JmapTestRule implements TestRule {
             .overrideWith(new TestRabbitMQModule(DockerRabbitMQSingleton.SINGLETON))
             .overrideWith(new DockerAwsS3TestRule().getModule())
             .overrideWith(new TestJMAPServerModule())
-            .overrideWith(new TestDockerESMetricReporterModule(dockerElasticSearchRule.getDockerEs().getHttpHost()))
             .overrideWith(guiceModuleTestRule.getModule())
             .overrideWith((binder -> binder.bind(CleanupTasksPerformer.class).asEagerSingleton()))
             .overrideWith(binder -> binder.bind(WebAdminConfiguration.class).toInstance(WebAdminConfiguration.TEST_CONFIGURATION))
diff --git a/server/protocols/jmap-draft-integration-testing/cassandra-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/cassandra/cucumber/CassandraStepdefs.java b/server/protocols/jmap-draft-integration-testing/cassandra-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/cassandra/cucumber/CassandraStepdefs.java
index a9fdcab..7638f4f 100644
--- a/server/protocols/jmap-draft-integration-testing/cassandra-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/cassandra/cucumber/CassandraStepdefs.java
+++ b/server/protocols/jmap-draft-integration-testing/cassandra-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/cassandra/cucumber/CassandraStepdefs.java
@@ -36,7 +36,6 @@ import org.apache.james.jmap.draft.methods.integration.cucumber.MainStepdefs;
 import org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
 import org.apache.james.mailbox.extractor.TextExtractor;
 import org.apache.james.mailbox.store.extractor.DefaultTextExtractor;
-import org.apache.james.modules.TestDockerESMetricReporterModule;
 import org.apache.james.modules.TestJMAPServerModule;
 import org.apache.james.server.CassandraTruncateTableTask;
 import org.junit.rules.TemporaryFolder;
@@ -78,7 +77,6 @@ public class CassandraStepdefs {
 
         mainStepdefs.jmapServer = CassandraJamesServerMain.createServer(configuration)
             .overrideWith(new TestJMAPServerModule())
-            .overrideWith(new TestDockerESMetricReporterModule(elasticSearch.getDockerEs().getHttpHost()))
             .overrideWith(elasticSearch.getModule())
             .overrideWith(cassandraServer.getModule())
             .overrideWith(binder -> binder.bind(TextExtractor.class).to(DefaultTextExtractor.class))
diff --git a/server/protocols/jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/cucumber/awss3/RabbitMQAwsS3Stepdefs.java b/server/protocols/jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/cucumber/awss3/RabbitMQAwsS3Stepdefs.java
index c549a76..cb4eefe 100644
--- a/server/protocols/jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/cucumber/awss3/RabbitMQAwsS3Stepdefs.java
+++ b/server/protocols/jmap-draft-integration-testing/rabbitmq-jmap-draft-integration-testing/src/test/java/org/apache/james/jmap/rabbitmq/cucumber/awss3/RabbitMQAwsS3Stepdefs.java
@@ -33,7 +33,6 @@ import org.apache.james.jmap.draft.methods.integration.cucumber.ImapStepdefs;
 import org.apache.james.jmap.draft.methods.integration.cucumber.MainStepdefs;
 import org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
 import org.apache.james.modules.DockerRabbitMQRule;
-import org.apache.james.modules.TestDockerESMetricReporterModule;
 import org.apache.james.modules.TestJMAPServerModule;
 import org.apache.james.modules.TestRabbitMQModule;
 import org.apache.james.modules.blobstore.BlobStoreConfiguration;
@@ -86,7 +85,6 @@ public class RabbitMQAwsS3Stepdefs {
 
         mainStepdefs.jmapServer = CassandraRabbitMQJamesServerMain.createServer(configuration)
             .overrideWith(new TestJMAPServerModule())
-                .overrideWith(new TestDockerESMetricReporterModule(elasticSearch.getDockerEs().getHttpHost()))
                 .overrideWith(new TestRabbitMQModule(rabbitMQServer.dockerRabbitMQ()))
                 .overrideWith(awsS3Server.getModule())
                 .overrideWith(elasticSearch.getModule())


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


[james-project] 08/11: JAMES-3430 Document migration for MessageV3 table

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

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

commit 3aa16fd9a0f3b08935036d5d78af0bfc97a22cbb
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Oct 20 17:04:24 2020 +0700

    JAMES-3430 Document migration for MessageV3 table
---
 CHANGELOG.md                                       |  1 +
 .../distributed/operate/cassandra-migration.adoc   |  2 ++
 src/site/xdoc/server/config-cassandra.xml          | 12 ++++++++++
 upgrade-instructions.md                            | 26 ++++++++++++++++++++++
 4 files changed, 41 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 790db3d..596bcc0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
 - JAMES-3263 Optimize RecipientRewriteTable::getMappingsForType
 - JAMES-3176 Rewritte MDN parsing with Parboiled scala (avoid asm library dependency clash within the Distributed Server)
 - JAMES-3194 Rely on DTOConverter in TaskRoute
+- JAMES-3430 Restructure message properties storage within Cassandra Mailbox. See upgrade instructions.
 
 ### Deprecated
 - HybridBlobStore. This will be removed after 3.6.0 release. Introduced to fasten small blob access, its usage could be
diff --git a/docs/modules/servers/pages/distributed/operate/cassandra-migration.adoc b/docs/modules/servers/pages/distributed/operate/cassandra-migration.adoc
index 617ddc4..357553e 100644
--- a/docs/modules/servers/pages/distributed/operate/cassandra-migration.adoc
+++ b/docs/modules/servers/pages/distributed/operate/cassandra-migration.adoc
@@ -23,5 +23,7 @@ to mailboxPathV2 table relying on a composite primary key.
 handy for things like mail aliases (I want to list aliases rewritting things to bob). Without this projection table being available,
 (ie we rely on schema version 6 or less) such information is obtained through a full table scan, unoptimized. From schema version 7,
 the optimized projection can safely be used.
+* *From V7 to V8* : Add UID_VALIDITY to mailboxPath table in order not to mandate mailbox table reads.
+* *From V8 to V9* : Adopt a more compact representation for message properties.
 
 The xref:distributed/operate/guide.adoc#_updating_cassandra_schema_version[Operator guide] further details the migration process.
\ No newline at end of file
diff --git a/src/site/xdoc/server/config-cassandra.xml b/src/site/xdoc/server/config-cassandra.xml
index a8e2470..d779602 100644
--- a/src/site/xdoc/server/config-cassandra.xml
+++ b/src/site/xdoc/server/config-cassandra.xml
@@ -257,6 +257,18 @@
 
     </subsection>
 
+    <subsection name="From V7 to V8">
+
+      <p>Add UID_VALIDITY to mailboxPath table in order not to mandate mailbox table reads.</p>
+
+    </subsection>
+
+    <subsection name="From V8 to V9">
+
+      <p>Adopt a more compact representation for message properties.</p>
+
+    </subsection>
+
   </section>
 
 </body>
diff --git a/upgrade-instructions.md b/upgrade-instructions.md
index a040c31..3ba7aef 100644
--- a/upgrade-instructions.md
+++ b/upgrade-instructions.md
@@ -20,6 +20,32 @@ Change list:
  - [mailqueue.size.metricsEnabled now defaults to false](#mailqueuesizemetricsenabled-now-defaults-to-false)
  - [LDAP users repository connection pool now defaults to false](#ldap-users-repository-connection-pool-now-defaults-to-false)
  - [Swift support has been dropped](#swift-support-has-been-dropped)
+ - [Cassandra Schema update to V8](#cassandra-schema-update-to-v8)
+ - [Cassandra Schema update to V9](#cassandra-schema-update-to-v9)
+
+### Cassandra Schema update to V9
+
+Date 20/10/2020
+
+JIRA: https://issues.apache.org/jira/browse/JAMES-3430
+
+Concerned product: Distributed James
+
+Adopt a more compact representation for message properties. This improves performance of the Cassandra mailbox.
+
+In order to benefit from this work, you need to [upgrade to the latest schema version](https://github.com/apache/james-project/blob/master/src/site/markdown/server/manage-webadmin.md#upgrading-to-the-latest-version).
+
+### Cassandra Schema update to V8
+
+Date 13/10/2020
+
+JIRA: https://issues.apache.org/jira/browse/JAMES-3409
+
+Concerned product: Distributed James
+
+Add UID_VALIDITY to mailboxPath table in order not to mandate mailbox table reads. This improves performance of the Cassandra mailbox.
+
+In order to benefit from this work, you need to [upgrade to the latest schema version](https://github.com/apache/james-project/blob/master/src/site/markdown/server/manage-webadmin.md#upgrading-to-the-latest-version).
 
 ### Swift support has been dropped
 


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


[james-project] 10/11: JAMES-3433 BlobStore reads should have a StoragePolicy associated

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

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

commit 2c2f2ea125ffe6d300487352aa0d34c7e9fe7213
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Oct 22 08:45:00 2020 +0700

    JAMES-3433 BlobStore reads should have a StoragePolicy associated
    
    Cached blob store only attempts cache reads when HIGH_PERFORMANCE in being used.
---
 .../cassandra/mail/CassandraAttachmentMapper.java  |  2 +-
 .../cassandra/mail/CassandraMessageDAO.java        | 12 ++--
 .../cassandra/mail/CassandraMessageDAOV3.java      | 12 ++--
 .../vault/blob/BlobStoreDeletedMessageVault.java   |  2 +-
 .../java/org/apache/james/blob/api/BlobStore.java  |  8 +++
 .../java/org/apache/james/blob/api/BlobType.java   | 13 +++-
 .../blob/cassandra/cache/CachedBlobStore.java      | 20 +++++-
 .../blob/cassandra/cache/CachedBlobStoreTest.java  | 71 +++++++++++-----------
 .../main/java/org/apache/james/blob/api/Store.java |  2 +-
 .../export/file/LocalFileBlobExportMechanism.java  |  4 +-
 .../apache/james/blob/mail/MimeMessagePartsId.java |  7 ++-
 .../linshare/LinshareBlobExportMechanism.java      |  4 +-
 12 files changed, 97 insertions(+), 60 deletions(-)

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 dd6edec..9db614a 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
@@ -96,7 +96,7 @@ public class CassandraAttachmentMapper implements AttachmentMapper {
     @Override
     public InputStream loadAttachmentContent(AttachmentId attachmentId) throws AttachmentNotFoundException, IOException {
         return attachmentDAOV2.getAttachment(attachmentId)
-            .map(daoAttachment -> blobStore.read(blobStore.getDefaultBucketName(), daoAttachment.getBlobId()))
+            .map(daoAttachment -> blobStore.read(blobStore.getDefaultBucketName(), daoAttachment.getBlobId(), LOW_COST))
             .blockOptional()
             .orElseThrow(() -> new AttachmentNotFoundException(attachmentId.toString()));
     }
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAO.java
index 8d97ff5..98b22d6 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAO.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAO.java
@@ -319,9 +319,9 @@ public class CassandraMessageDAO {
             case Full:
                 return getFullContent(headerId, bodyId);
             case Headers:
-                return getContent(headerId);
+                return getContent(headerId, SIZE_BASED);
             case Body:
-                return getContent(bodyId)
+                return getContent(bodyId, LOW_COST)
                     .map(data -> Bytes.concat(new byte[bodyStartOctet], data));
             case Metadata:
                 return Mono.just(EMPTY_BYTE_ARRAY);
@@ -331,12 +331,12 @@ public class CassandraMessageDAO {
     }
 
     private Mono<byte[]> getFullContent(BlobId headerId, BlobId bodyId) {
-        return getContent(headerId)
-            .zipWith(getContent(bodyId), Bytes::concat);
+        return getContent(headerId, SIZE_BASED)
+            .zipWith(getContent(bodyId, LOW_COST), Bytes::concat);
     }
 
-    private Mono<byte[]> getContent(BlobId blobId) {
-        return Mono.from(blobStore.readBytes(blobStore.getDefaultBucketName(), blobId));
+    private Mono<byte[]> getContent(BlobId blobId, BlobStore.StoragePolicy storagePolicy) {
+        return Mono.from(blobStore.readBytes(blobStore.getDefaultBucketName(), blobId, storagePolicy));
     }
 
     private BlobId retrieveBlobId(String field, Row row) {
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3.java
index 8f349da..c665a85 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3.java
@@ -346,9 +346,9 @@ public class CassandraMessageDAOV3 {
             case Full:
                 return getFullContent(headerId, bodyId);
             case Headers:
-                return getContent(headerId);
+                return getContent(headerId, SIZE_BASED);
             case Body:
-                return getContent(bodyId)
+                return getContent(bodyId, LOW_COST)
                     .map(data -> Bytes.concat(new byte[bodyStartOctet], data));
             case Metadata:
                 return Mono.just(EMPTY_BYTE_ARRAY);
@@ -358,12 +358,12 @@ public class CassandraMessageDAOV3 {
     }
 
     private Mono<byte[]> getFullContent(BlobId headerId, BlobId bodyId) {
-        return getContent(headerId)
-            .zipWith(getContent(bodyId), Bytes::concat);
+        return getContent(headerId, SIZE_BASED)
+            .zipWith(getContent(bodyId, LOW_COST), Bytes::concat);
     }
 
-    private Mono<byte[]> getContent(BlobId blobId) {
-        return Mono.from(blobStore.readBytes(blobStore.getDefaultBucketName(), blobId));
+    private Mono<byte[]> getContent(BlobId blobId, BlobStore.StoragePolicy storagePolicy) {
+        return Mono.from(blobStore.readBytes(blobStore.getDefaultBucketName(), blobId, storagePolicy));
     }
 
     private BlobId retrieveBlobId(String field, Row row) {
diff --git a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/blob/BlobStoreDeletedMessageVault.java b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/blob/BlobStoreDeletedMessageVault.java
index d61247b..e10cd2b 100644
--- a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/blob/BlobStoreDeletedMessageVault.java
+++ b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/blob/BlobStoreDeletedMessageVault.java
@@ -122,7 +122,7 @@ public class BlobStoreDeletedMessageVault implements DeletedMessageVault {
     }
 
     private Mono<InputStream> loadMimeMessage(StorageInformation storageInformation, Username username, MessageId messageId) {
-        return Mono.fromSupplier(() -> blobStore.read(storageInformation.getBucketName(), storageInformation.getBlobId()))
+        return Mono.fromSupplier(() -> blobStore.read(storageInformation.getBucketName(), storageInformation.getBlobId(), LOW_COST))
             .onErrorResume(
                 ObjectNotFoundException.class,
                 ex -> Mono.error(new DeletedMessageContentNotFoundException(username, messageId)));
diff --git a/server/blob/blob-api/src/main/java/org/apache/james/blob/api/BlobStore.java b/server/blob/blob-api/src/main/java/org/apache/james/blob/api/BlobStore.java
index a887a32..dc4abef 100644
--- a/server/blob/blob-api/src/main/java/org/apache/james/blob/api/BlobStore.java
+++ b/server/blob/blob-api/src/main/java/org/apache/james/blob/api/BlobStore.java
@@ -43,6 +43,14 @@ public interface BlobStore {
 
     InputStream read(BucketName bucketName, BlobId blobId);
 
+    default Publisher<byte[]> readBytes(BucketName bucketName, BlobId blobId, StoragePolicy storagePolicy) {
+       return readBytes(bucketName, blobId);
+    }
+
+    default InputStream read(BucketName bucketName, BlobId blobId, StoragePolicy storagePolicy) {
+        return read(bucketName, blobId);
+    }
+
     BucketName getDefaultBucketName();
 
     Publisher<Void> deleteBucket(BucketName bucketName);
diff --git a/server/blob/blob-api/src/main/java/org/apache/james/blob/api/BlobType.java b/server/blob/blob-api/src/main/java/org/apache/james/blob/api/BlobType.java
index e068c68..ecc0d61 100644
--- a/server/blob/blob-api/src/main/java/org/apache/james/blob/api/BlobType.java
+++ b/server/blob/blob-api/src/main/java/org/apache/james/blob/api/BlobType.java
@@ -23,27 +23,34 @@ import java.util.Objects;
 
 public class BlobType {
     private final String name;
+    private final BlobStore.StoragePolicy storagePolicy;
 
-    public BlobType(String name) {
+    public BlobType(String name, BlobStore.StoragePolicy storagePolicy) {
         this.name = name;
+        this.storagePolicy = storagePolicy;
     }
 
     public String getName() {
         return name;
     }
 
+    public BlobStore.StoragePolicy getStoragePolicy() {
+        return storagePolicy;
+    }
+
     @Override
     public final boolean equals(Object o) {
         if (o instanceof BlobType) {
             BlobType blobType = (BlobType) o;
 
-            return Objects.equals(this.name, blobType.name);
+            return Objects.equals(this.name, blobType.name)
+                && Objects.equals(this.storagePolicy, blobType.storagePolicy);
         }
         return false;
     }
 
     @Override
     public final int hashCode() {
-        return Objects.hash(name);
+        return Objects.hash(name, storagePolicy);
     }
 }
diff --git a/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/cache/CachedBlobStore.java b/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/cache/CachedBlobStore.java
index 0e998c0..87fdfa5 100644
--- a/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/cache/CachedBlobStore.java
+++ b/server/blob/blob-cassandra/src/main/java/org/apache/james/blob/cassandra/cache/CachedBlobStore.java
@@ -132,7 +132,10 @@ public class CachedBlobStore implements BlobStore {
     }
 
     @Override
-    public InputStream read(BucketName bucketName, BlobId blobId) throws ObjectStoreIOException, ObjectNotFoundException {
+    public InputStream read(BucketName bucketName, BlobId blobId, StoragePolicy storagePolicy) throws ObjectStoreIOException, ObjectNotFoundException {
+        if (storagePolicy == LOW_COST) {
+            return backend.read(bucketName, blobId);
+        }
         return Mono.just(bucketName)
             .filter(getDefaultBucketName()::equals)
             .flatMap(defaultBucket -> readInDefaultBucket(bucketName, blobId))
@@ -152,13 +155,26 @@ public class CachedBlobStore implements BlobStore {
     }
 
     @Override
-    public Mono<byte[]> readBytes(BucketName bucketName, BlobId blobId) {
+    public Mono<byte[]> readBytes(BucketName bucketName, BlobId blobId, StoragePolicy storagePolicy) {
+        if (storagePolicy == LOW_COST) {
+            return readBytesFromBackend(bucketName, blobId);
+        }
         if (getDefaultBucketName().equals(bucketName)) {
             return readBytesInDefaultBucket(bucketName, blobId);
         }
         return readBytesFromBackend(bucketName, blobId);
     }
 
+    @Override
+    public Publisher<byte[]> readBytes(BucketName bucketName, BlobId blobId) {
+        return readBytes(bucketName, blobId, LOW_COST);
+    }
+
+    @Override
+    public InputStream read(BucketName bucketName, BlobId blobId) {
+        return read(bucketName, blobId, LOW_COST);
+    }
+
     private Mono<byte[]> readBytesInDefaultBucket(BucketName bucketName, BlobId blobId) {
         return readFromCache(blobId).switchIfEmpty(
             readBytesFromBackend(bucketName, blobId)
diff --git a/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/cache/CachedBlobStoreTest.java b/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/cache/CachedBlobStoreTest.java
index bf0a405..580f910 100644
--- a/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/cache/CachedBlobStoreTest.java
+++ b/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/cache/CachedBlobStoreTest.java
@@ -216,7 +216,7 @@ public class CachedBlobStoreTest implements BlobStoreContract {
 
         SoftAssertions.assertSoftly(soflty -> {
             soflty.assertThat(Mono.from(cache.read(blobId)).blockOptional()).isEmpty();
-            soflty.assertThat(new ByteArrayInputStream(Mono.from(testee().readBytes(DEFAULT_BUCKETNAME, blobId)).block()))
+            soflty.assertThat(new ByteArrayInputStream(Mono.from(testee().readBytes(DEFAULT_BUCKETNAME, blobId, HIGH_PERFORMANCE)).block()))
                 .hasSameContentAs(new ByteArrayInputStream(APPROXIMATELY_FIVE_KILOBYTES));
             soflty.assertThat(new ByteArrayInputStream(Mono.from(cache.read(blobId)).block()))
                 .hasSameContentAs(new ByteArrayInputStream(APPROXIMATELY_FIVE_KILOBYTES));
@@ -229,7 +229,7 @@ public class CachedBlobStoreTest implements BlobStoreContract {
 
         SoftAssertions.assertSoftly(soflty -> {
             soflty.assertThat(Mono.from(cache.read(blobId)).blockOptional()).isEmpty();
-            soflty.assertThat(testee().read(DEFAULT_BUCKETNAME, blobId))
+            soflty.assertThat(testee().read(DEFAULT_BUCKETNAME, blobId, HIGH_PERFORMANCE))
                 .hasSameContentAs(new ByteArrayInputStream(APPROXIMATELY_FIVE_KILOBYTES));
             soflty.assertThat(new ByteArrayInputStream(Mono.from(cache.read(blobId)).block()))
                 .hasSameContentAs(new ByteArrayInputStream(APPROXIMATELY_FIVE_KILOBYTES));
@@ -242,7 +242,7 @@ public class CachedBlobStoreTest implements BlobStoreContract {
 
         SoftAssertions.assertSoftly(soflty -> {
             soflty.assertThat(Mono.from(cache.read(blobId)).blockOptional()).isEmpty();
-            soflty.assertThat(new ByteArrayInputStream(Mono.from(testee().readBytes(TEST_BUCKETNAME, blobId)).block()))
+            soflty.assertThat(new ByteArrayInputStream(Mono.from(testee().readBytes(TEST_BUCKETNAME, blobId, HIGH_PERFORMANCE)).block()))
                 .hasSameContentAs(new ByteArrayInputStream(APPROXIMATELY_FIVE_KILOBYTES));
             soflty.assertThat(Mono.from(cache.read(blobId)).blockOptional()).isEmpty();
         });
@@ -254,7 +254,7 @@ public class CachedBlobStoreTest implements BlobStoreContract {
 
         SoftAssertions.assertSoftly(soflty -> {
             soflty.assertThat(Mono.from(cache.read(blobId)).blockOptional()).isEmpty();
-            soflty.assertThat(testee().read(TEST_BUCKETNAME, blobId))
+            soflty.assertThat(testee().read(TEST_BUCKETNAME, blobId, HIGH_PERFORMANCE))
                 .hasSameContentAs(new ByteArrayInputStream(APPROXIMATELY_FIVE_KILOBYTES));
             soflty.assertThat(Mono.from(cache.read(blobId)).blockOptional()).isEmpty();
         });
@@ -266,7 +266,7 @@ public class CachedBlobStoreTest implements BlobStoreContract {
 
         SoftAssertions.assertSoftly(soflty -> {
             soflty.assertThat(Mono.from(cache.read(blobId)).blockOptional()).isEmpty();
-            soflty.assertThat(new ByteArrayInputStream(Mono.from(testee().readBytes(DEFAULT_BUCKETNAME, blobId)).block()))
+            soflty.assertThat(new ByteArrayInputStream(Mono.from(testee().readBytes(DEFAULT_BUCKETNAME, blobId, HIGH_PERFORMANCE)).block()))
                 .hasSameContentAs(new ByteArrayInputStream(TWELVE_MEGABYTES));
             soflty.assertThat(Mono.from(cache.read(blobId)).blockOptional()).isEmpty();
         });
@@ -278,7 +278,7 @@ public class CachedBlobStoreTest implements BlobStoreContract {
 
         SoftAssertions.assertSoftly(soflty -> {
             soflty.assertThat(Mono.from(cache.read(blobId)).blockOptional()).isEmpty();
-            soflty.assertThat(testee().read(DEFAULT_BUCKETNAME, blobId))
+            soflty.assertThat(testee().read(DEFAULT_BUCKETNAME, blobId, HIGH_PERFORMANCE))
                 .hasSameContentAs(new ByteArrayInputStream(TWELVE_MEGABYTES));
             soflty.assertThat(Mono.from(cache.read(blobId)).blockOptional()).isEmpty();
         });
@@ -294,7 +294,7 @@ public class CachedBlobStoreTest implements BlobStoreContract {
 
         Mono.from(cache.read(blobId)).block();
 
-        assertThat(testee().read(DEFAULT_BUCKETNAME, blobId))
+        assertThat(testee().read(DEFAULT_BUCKETNAME, blobId, HIGH_PERFORMANCE))
             .hasSameContentAs(new ByteArrayInputStream(APPROXIMATELY_FIVE_KILOBYTES));
     }
 
@@ -304,8 +304,8 @@ public class CachedBlobStoreTest implements BlobStoreContract {
         void readBlobStoreCacheWithNoneDefaultBucketNameShouldNotImpact() {
             BlobId blobId = Mono.from(testee.save(TEST_BUCKETNAME, APPROXIMATELY_FIVE_KILOBYTES, SIZE_BASED)).block();
 
-            testee.read(TEST_BUCKETNAME, blobId);
-            testee.read(TEST_BUCKETNAME, blobId);
+            testee.read(TEST_BUCKETNAME, blobId, HIGH_PERFORMANCE);
+            testee.read(TEST_BUCKETNAME, blobId, HIGH_PERFORMANCE);
 
             SoftAssertions.assertSoftly(soflty -> {
                 soflty.assertThat(metricFactory.countFor(BLOBSTORE_CACHED_MISS_COUNT_METRIC_NAME))
@@ -324,8 +324,8 @@ public class CachedBlobStoreTest implements BlobStoreContract {
         void readBlobStoreWithNoneDefaultBucketNameShouldRecordByBackendLatency() {
             BlobId blobId = Mono.from(testee.save(TEST_BUCKETNAME, APPROXIMATELY_FIVE_KILOBYTES, SIZE_BASED)).block();
 
-            testee.read(TEST_BUCKETNAME, blobId);
-            testee.read(TEST_BUCKETNAME, blobId);
+            testee.read(TEST_BUCKETNAME, blobId, HIGH_PERFORMANCE);
+            testee.read(TEST_BUCKETNAME, blobId, HIGH_PERFORMANCE);
 
             SoftAssertions.assertSoftly(soflty ->
                 soflty.assertThat(metricFactory.executionTimesFor(BLOBSTORE_BACKEND_LATENCY_METRIC_NAME))
@@ -337,9 +337,8 @@ public class CachedBlobStoreTest implements BlobStoreContract {
         void readBytesWithNoneDefaultBucketNameShouldNotImpact() {
             BlobId blobId = Mono.from(testee.save(TEST_BUCKETNAME, APPROXIMATELY_FIVE_KILOBYTES, SIZE_BASED)).block();
 
-            Mono.from(testee.readBytes(TEST_BUCKETNAME, blobId)).block();
-            Mono.from(testee.readBytes(TEST_BUCKETNAME, blobId)).block();
-
+            Mono.from(testee.readBytes(TEST_BUCKETNAME, blobId, HIGH_PERFORMANCE)).block();
+            Mono.from(testee.readBytes(TEST_BUCKETNAME, blobId, HIGH_PERFORMANCE)).block();
 
             SoftAssertions.assertSoftly(soflty -> {
                 assertThat(metricFactory.countFor(BLOBSTORE_CACHED_MISS_COUNT_METRIC_NAME))
@@ -361,8 +360,8 @@ public class CachedBlobStoreTest implements BlobStoreContract {
         void readBytesWithNoneDefaultBucketNameShouldPublishBackendTimerMetrics() {
             BlobId blobId = Mono.from(testee.save(TEST_BUCKETNAME, APPROXIMATELY_FIVE_KILOBYTES, SIZE_BASED)).block();
 
-            Mono.from(testee.readBytes(TEST_BUCKETNAME, blobId)).block();
-            Mono.from(testee.readBytes(TEST_BUCKETNAME, blobId)).block();
+            Mono.from(testee.readBytes(TEST_BUCKETNAME, blobId, HIGH_PERFORMANCE)).block();
+            Mono.from(testee.readBytes(TEST_BUCKETNAME, blobId, HIGH_PERFORMANCE)).block();
 
             SoftAssertions.assertSoftly(soflty ->
                 soflty.assertThat(metricFactory.executionTimesFor(BLOBSTORE_BACKEND_LATENCY_METRIC_NAME))
@@ -374,8 +373,8 @@ public class CachedBlobStoreTest implements BlobStoreContract {
         void readBlobStoreCacheShouldPublishTimerMetrics() {
             BlobId blobId = Mono.from(testee.save(DEFAULT_BUCKETNAME, APPROXIMATELY_FIVE_KILOBYTES, SIZE_BASED)).block();
 
-            testee.read(DEFAULT_BUCKETNAME, blobId);
-            testee.read(DEFAULT_BUCKETNAME, blobId);
+            testee.read(DEFAULT_BUCKETNAME, blobId, HIGH_PERFORMANCE);
+            testee.read(DEFAULT_BUCKETNAME, blobId, HIGH_PERFORMANCE);
 
             SoftAssertions.assertSoftly(soflty -> {
                 soflty.assertThat(metricFactory.executionTimesFor(BLOBSTORE_CACHED_LATENCY_METRIC_NAME))
@@ -388,8 +387,8 @@ public class CachedBlobStoreTest implements BlobStoreContract {
         void readBytesCacheShouldPublishTimerMetrics() {
             BlobId blobId = Mono.from(testee.save(DEFAULT_BUCKETNAME, APPROXIMATELY_FIVE_KILOBYTES, SIZE_BASED)).block();
 
-            Mono.from(testee.readBytes(DEFAULT_BUCKETNAME, blobId)).block();
-            Mono.from(testee.readBytes(DEFAULT_BUCKETNAME, blobId)).block();
+            Mono.from(testee.readBytes(DEFAULT_BUCKETNAME, blobId, HIGH_PERFORMANCE)).block();
+            Mono.from(testee.readBytes(DEFAULT_BUCKETNAME, blobId, HIGH_PERFORMANCE)).block();
 
             SoftAssertions.assertSoftly(soflty -> {
                 soflty.assertThat(metricFactory.executionTimesFor(BLOBSTORE_CACHED_LATENCY_METRIC_NAME))
@@ -405,8 +404,8 @@ public class CachedBlobStoreTest implements BlobStoreContract {
         void readBytesShouldPublishBackendTimerMetricsForBigBlobs() {
             BlobId blobId = Mono.from(backend.save(DEFAULT_BUCKETNAME, ELEVEN_KILOBYTES, SIZE_BASED)).block();
 
-            Mono.from(testee.readBytes(DEFAULT_BUCKETNAME, blobId)).block();
-            Mono.from(testee.readBytes(DEFAULT_BUCKETNAME, blobId)).block();
+            Mono.from(testee.readBytes(DEFAULT_BUCKETNAME, blobId, HIGH_PERFORMANCE)).block();
+            Mono.from(testee.readBytes(DEFAULT_BUCKETNAME, blobId, HIGH_PERFORMANCE)).block();
 
             SoftAssertions.assertSoftly(soflty ->
                 soflty.assertThat(metricFactory.executionTimesFor(BLOBSTORE_BACKEND_LATENCY_METRIC_NAME))
@@ -418,8 +417,8 @@ public class CachedBlobStoreTest implements BlobStoreContract {
         void readInputStreamShouldPublishBackendTimerForBigBlobs() {
             BlobId blobId = Mono.from(backend.save(DEFAULT_BUCKETNAME, ELEVEN_KILOBYTES, SIZE_BASED)).block();
 
-            testee.read(DEFAULT_BUCKETNAME, blobId);
-            testee.read(DEFAULT_BUCKETNAME, blobId);
+            testee.read(DEFAULT_BUCKETNAME, blobId, HIGH_PERFORMANCE);
+            testee.read(DEFAULT_BUCKETNAME, blobId, HIGH_PERFORMANCE);
 
             SoftAssertions.assertSoftly(soflty ->
                 soflty.assertThat(metricFactory.executionTimesFor(BLOBSTORE_BACKEND_LATENCY_METRIC_NAME))
@@ -431,8 +430,8 @@ public class CachedBlobStoreTest implements BlobStoreContract {
         void readBytesShouldNotIncreaseCacheCounterForBigBlobs() {
             BlobId blobId = Mono.from(backend.save(DEFAULT_BUCKETNAME, ELEVEN_KILOBYTES, SIZE_BASED)).block();
 
-            Mono.from(testee.readBytes(DEFAULT_BUCKETNAME, blobId)).block();
-            Mono.from(testee.readBytes(DEFAULT_BUCKETNAME, blobId)).block();
+            Mono.from(testee.readBytes(DEFAULT_BUCKETNAME, blobId, HIGH_PERFORMANCE)).block();
+            Mono.from(testee.readBytes(DEFAULT_BUCKETNAME, blobId, HIGH_PERFORMANCE)).block();
 
             SoftAssertions.assertSoftly(soflty -> {
                 soflty.assertThat(metricFactory.countFor(BLOBSTORE_CACHED_MISS_COUNT_METRIC_NAME))
@@ -466,7 +465,7 @@ public class CachedBlobStoreTest implements BlobStoreContract {
             BlobId blobId = Mono.from(testee.save(DEFAULT_BUCKETNAME, ELEVEN_KILOBYTES, SIZE_BASED)).block();
 
             Duration delay = Duration.ofMillis(500);
-            Mono.from(testee.readBytes(DEFAULT_BUCKETNAME, blobId))
+            Mono.from(testee.readBytes(DEFAULT_BUCKETNAME, blobId, HIGH_PERFORMANCE))
                 .then(Mono.delay(delay))
                 .repeat(2)
                 .blockLast();
@@ -481,7 +480,7 @@ public class CachedBlobStoreTest implements BlobStoreContract {
             BlobId blobId = Mono.from(testee.save(DEFAULT_BUCKETNAME, APPROXIMATELY_FIVE_KILOBYTES, SIZE_BASED)).block();
 
             Duration delay = Duration.ofMillis(500);
-            Mono.from(testee.readBytes(DEFAULT_BUCKETNAME, blobId))
+            Mono.from(testee.readBytes(DEFAULT_BUCKETNAME, blobId, HIGH_PERFORMANCE))
                 .then(Mono.delay(delay))
                 .repeat(2)
                 .blockLast();
@@ -495,8 +494,8 @@ public class CachedBlobStoreTest implements BlobStoreContract {
         void readBlobStoreCacheShouldCountWhenHit() {
             BlobId blobId = Mono.from(testee.save(DEFAULT_BUCKETNAME, APPROXIMATELY_FIVE_KILOBYTES, SIZE_BASED)).block();
 
-            testee.read(DEFAULT_BUCKETNAME, blobId);
-            testee.read(DEFAULT_BUCKETNAME, blobId);
+            testee.read(DEFAULT_BUCKETNAME, blobId, HIGH_PERFORMANCE);
+            testee.read(DEFAULT_BUCKETNAME, blobId, HIGH_PERFORMANCE);
 
             assertThat(metricFactory.countFor(BLOBSTORE_CACHED_HIT_COUNT_METRIC_NAME)).isEqualTo(2);
         }
@@ -505,8 +504,8 @@ public class CachedBlobStoreTest implements BlobStoreContract {
         void readBytesCacheShouldCountWhenHit() {
             BlobId blobId = Mono.from(testee.save(DEFAULT_BUCKETNAME, APPROXIMATELY_FIVE_KILOBYTES, SIZE_BASED)).block();
 
-            Mono.from(testee.readBytes(DEFAULT_BUCKETNAME, blobId)).block();
-            Mono.from(testee.readBytes(DEFAULT_BUCKETNAME, blobId)).block();
+            Mono.from(testee.readBytes(DEFAULT_BUCKETNAME, blobId, HIGH_PERFORMANCE)).block();
+            Mono.from(testee.readBytes(DEFAULT_BUCKETNAME, blobId, HIGH_PERFORMANCE)).block();
 
             assertThat(metricFactory.countFor(BLOBSTORE_CACHED_HIT_COUNT_METRIC_NAME)).isEqualTo(2);
         }
@@ -517,7 +516,7 @@ public class CachedBlobStoreTest implements BlobStoreContract {
             BlobId blobId = Mono.from(backend.save(DEFAULT_BUCKETNAME, APPROXIMATELY_FIVE_KILOBYTES, SIZE_BASED)).block();
 
             Mono.from(cache.remove(blobId)).block();
-            testee.read(DEFAULT_BUCKETNAME, blobId);
+            testee.read(DEFAULT_BUCKETNAME, blobId, HIGH_PERFORMANCE);
 
             assertThat(metricFactory.countFor(BLOBSTORE_CACHED_MISS_COUNT_METRIC_NAME)).isEqualTo(1);
         }
@@ -527,7 +526,7 @@ public class CachedBlobStoreTest implements BlobStoreContract {
             BlobId blobId = Mono.from(testee.save(DEFAULT_BUCKETNAME, APPROXIMATELY_FIVE_KILOBYTES, SIZE_BASED)).block();
 
             Mono.from(cache.remove(blobId)).block();
-            Mono.from(testee.readBytes(DEFAULT_BUCKETNAME, blobId)).block();
+            Mono.from(testee.readBytes(DEFAULT_BUCKETNAME, blobId, HIGH_PERFORMANCE)).block();
 
             assertThat(metricFactory.countFor(BLOBSTORE_CACHED_MISS_COUNT_METRIC_NAME)).isEqualTo(1);
         }
@@ -535,7 +534,7 @@ public class CachedBlobStoreTest implements BlobStoreContract {
         @Test
         void metricsShouldNotWorkExceptLatencyWhenReadNonExistingBlob() {
             SoftAssertions.assertSoftly(soflty -> {
-                soflty.assertThatThrownBy(() -> testee.read(DEFAULT_BUCKETNAME, new TestBlobId.Factory().randomId()))
+                soflty.assertThatThrownBy(() -> testee.read(DEFAULT_BUCKETNAME, new TestBlobId.Factory().randomId(), HIGH_PERFORMANCE))
                     .isInstanceOf(ObjectNotFoundException.class);
 
                 soflty.assertThat(metricFactory.countFor(BLOBSTORE_CACHED_MISS_COUNT_METRIC_NAME))
@@ -556,7 +555,7 @@ public class CachedBlobStoreTest implements BlobStoreContract {
         @Test
         void metricsShouldNotWorkExceptLatencyWhenReadNonExistingBlobAsBytes() {
             SoftAssertions.assertSoftly(soflty -> {
-                soflty.assertThatThrownBy(() -> Mono.from(testee.readBytes(DEFAULT_BUCKETNAME, new TestBlobId.Factory().randomId())).blockOptional())
+                soflty.assertThatThrownBy(() -> Mono.from(testee.readBytes(DEFAULT_BUCKETNAME, new TestBlobId.Factory().randomId(), HIGH_PERFORMANCE)).blockOptional())
                     .isInstanceOf(ObjectNotFoundException.class);
 
                 soflty.assertThat(metricFactory.countFor(BLOBSTORE_CACHED_MISS_COUNT_METRIC_NAME))
diff --git a/server/blob/blob-common/src/main/java/org/apache/james/blob/api/Store.java b/server/blob/blob-common/src/main/java/org/apache/james/blob/api/Store.java
index 8deba51..9c20cba 100644
--- a/server/blob/blob-common/src/main/java/org/apache/james/blob/api/Store.java
+++ b/server/blob/blob-common/src/main/java/org/apache/james/blob/api/Store.java
@@ -97,7 +97,7 @@ public interface Store<T, I> {
             return Flux.fromIterable(blobIds.asMap().entrySet())
                 .publishOn(Schedulers.elastic())
                 .flatMapSequential(
-                    entry -> Mono.from(blobStore.readBytes(blobStore.getDefaultBucketName(), entry.getValue()))
+                    entry -> Mono.from(blobStore.readBytes(blobStore.getDefaultBucketName(), entry.getValue(), entry.getKey().getStoragePolicy()))
                         .zipWith(Mono.just(entry.getKey())))
                 .map(entry -> Pair.of(entry.getT2(), entry.getT1()))
                 .collectList()
diff --git a/server/blob/blob-export-file/src/main/java/org/apache/james/blob/export/file/LocalFileBlobExportMechanism.java b/server/blob/blob-export-file/src/main/java/org/apache/james/blob/export/file/LocalFileBlobExportMechanism.java
index a2968ea..e6aec2b 100644
--- a/server/blob/blob-export-file/src/main/java/org/apache/james/blob/export/file/LocalFileBlobExportMechanism.java
+++ b/server/blob/blob-export-file/src/main/java/org/apache/james/blob/export/file/LocalFileBlobExportMechanism.java
@@ -19,6 +19,8 @@
 
 package org.apache.james.blob.export.file;
 
+import static org.apache.james.blob.api.BlobStore.StoragePolicy.LOW_COST;
+
 import java.io.File;
 import java.io.IOException;
 import java.net.UnknownHostException;
@@ -121,7 +123,7 @@ public class LocalFileBlobExportMechanism implements BlobExportMechanism {
             String fileName = ExportedFileNamesGenerator.generateFileName(fileCustomPrefix, blobId, fileExtension);
             String fileURL = configuration.exportDirectory + "/" + fileName;
             File file = fileSystem.getFile(fileURL);
-            FileUtils.copyToFile(blobStore.read(blobStore.getDefaultBucketName(), blobId), file);
+            FileUtils.copyToFile(blobStore.read(blobStore.getDefaultBucketName(), blobId, LOW_COST), file);
 
             return file.getAbsolutePath();
         } catch (IOException e) {
diff --git a/server/blob/mail-store/src/main/java/org/apache/james/blob/mail/MimeMessagePartsId.java b/server/blob/mail-store/src/main/java/org/apache/james/blob/mail/MimeMessagePartsId.java
index 936efe5..935a9b7 100644
--- a/server/blob/mail-store/src/main/java/org/apache/james/blob/mail/MimeMessagePartsId.java
+++ b/server/blob/mail-store/src/main/java/org/apache/james/blob/mail/MimeMessagePartsId.java
@@ -19,6 +19,9 @@
 
 package org.apache.james.blob.mail;
 
+import static org.apache.james.blob.api.BlobStore.StoragePolicy.LOW_COST;
+import static org.apache.james.blob.api.BlobStore.StoragePolicy.SIZE_BASED;
+
 import java.util.Map;
 import java.util.Objects;
 
@@ -75,8 +78,8 @@ public class MimeMessagePartsId implements BlobPartsId {
         }
     }
 
-    static final BlobType HEADER_BLOB_TYPE = new BlobType("mailHeader");
-    static final BlobType BODY_BLOB_TYPE = new BlobType("mailBody");
+    static final BlobType HEADER_BLOB_TYPE = new BlobType("mailHeader", SIZE_BASED);
+    static final BlobType BODY_BLOB_TYPE = new BlobType("mailBody", LOW_COST);
 
     private final BlobId headerBlobId;
     private final BlobId bodyBlobId;
diff --git a/third-party/linshare/src/main/java/org/apache/james/linshare/LinshareBlobExportMechanism.java b/third-party/linshare/src/main/java/org/apache/james/linshare/LinshareBlobExportMechanism.java
index 92c280a..7f39129 100644
--- a/third-party/linshare/src/main/java/org/apache/james/linshare/LinshareBlobExportMechanism.java
+++ b/third-party/linshare/src/main/java/org/apache/james/linshare/LinshareBlobExportMechanism.java
@@ -19,6 +19,8 @@
 
 package org.apache.james.linshare;
 
+import static org.apache.james.blob.api.BlobStore.StoragePolicy.LOW_COST;
+
 import java.io.File;
 import java.io.IOException;
 import java.util.Optional;
@@ -67,7 +69,7 @@ public class LinshareBlobExportMechanism implements BlobExportMechanism {
         String fileName = ExportedFileNamesGenerator.generateFileName(fileCustomPrefix, blobId, fileExtension);
         File tempFile = new File(tempDir, fileName);
         try {
-            FileUtils.copyInputStreamToFile(blobStore.read(blobStore.getDefaultBucketName(), blobId), tempFile);
+            FileUtils.copyInputStreamToFile(blobStore.read(blobStore.getDefaultBucketName(), blobId, LOW_COST), tempFile);
             uploadDocumentToTargetMail(mailAddress, tempFile);
         } finally {
             FileUtils.forceDelete(tempFile);


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


[james-project] 02/11: JAMES-3430 Split PropertyBuilder in two

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

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

commit 123103b5c5bf81ca18d01f151cb35922d669b789
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Oct 20 16:04:58 2020 +0700

    JAMES-3430 Split PropertyBuilder in two
    
        Stop building several time property builder
        Stop mixing read & writes on Property Builder
        Stop passing mutable data around
    
    This can be achieved by introducing a new, immutable Properties relying
    on PropertyBuilder to be built.
---
 .../mailbox/backup/MailboxMessageFixture.java      |   6 +-
 .../cassandra/mail/CassandraMessageDAO.java        |  10 +-
 .../cassandra/mail/MessageRepresentation.java      |  14 +-
 .../cassandra/mail/CassandraMessageDAOTest.java    |   6 +-
 ...asticSearchListeningMessageSearchIndexTest.java |   2 +-
 .../json/MessageToElasticSearchJsonTest.java       |  26 +--
 .../model/openjpa/AbstractJPAMailboxMessage.java   |  24 ++-
 .../james/mailbox/jpa/mail/MessageUtilsTest.java   |   2 +-
 .../mailbox/maildir/mail/model/MaildirMessage.java |  12 +-
 .../spamassassin/SpamAssassinListenerTest.java     |   2 +-
 .../james/mailbox/store/LazyMimeDescriptor.java    |  11 +-
 .../apache/james/mailbox/store/MessageFactory.java |   2 +-
 .../store/mail/model/DelegatingMailboxMessage.java |   3 +-
 .../james/mailbox/store/mail/model/Message.java    |   5 +-
 .../impl/{PropertyBuilder.java => Properties.java} | 230 ++-------------------
 .../store/mail/model/impl/PropertyBuilder.java     | 182 +---------------
 .../mail/model/impl/SimpleMailboxMessage.java      |  32 +--
 .../store/mail/model/impl/SimpleMessage.java       |  15 +-
 .../apache/james/mailbox/store/MessageBuilder.java |   2 +-
 .../mailbox/store/MessageIdManagerTestSystem.java  |   2 +-
 .../StoreMailboxMessageResultIteratorTest.java     |   2 +-
 .../store/mail/model/ListMessageAssertTest.java    |   4 +-
 .../model/ListMessagePropertiesAssertTest.java     |   2 +-
 .../store/mail/model/MailboxMessageAssertTest.java |  12 +-
 .../store/mail/model/MessageIdMapperTest.java      |   2 +-
 .../store/mail/model/MessageMapperTest.java        |  15 +-
 .../mailbox/store/mail/model/MessageMoveTest.java  |   2 +-
 .../model/MessageWithAttachmentMapperTest.java     |   4 +-
 .../store/mail/model/MetadataMapAssertTest.java    |   2 +-
 .../mail/model/impl/SimpleMailboxMessageTest.java  |  24 +--
 .../mail/utils/ApplicableFlagCalculatorTest.java   |   2 +-
 .../ListeningMessageSearchIndexContract.java       |   2 +-
 .../james/webadmin/routes/MailboxesRoutesTest.java |   4 +-
 .../webadmin/routes/UserMailboxesRoutesTest.java   |   2 +-
 34 files changed, 156 insertions(+), 511 deletions(-)

diff --git a/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/MailboxMessageFixture.java b/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/MailboxMessageFixture.java
index 50f8a76..e85e0cc 100644
--- a/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/MailboxMessageFixture.java
+++ b/mailbox/backup/src/test/java/org/apache/james/mailbox/backup/MailboxMessageFixture.java
@@ -130,7 +130,7 @@ public interface MailboxMessageFixture {
         .internalDate(new Date(DATE_1.toEpochSecond()))
         .bodyStartOctet(0)
         .flags(flags1)
-        .propertyBuilder(new PropertyBuilder())
+        .properties(new PropertyBuilder())
         .mailboxId(MAILBOX_ID_1)
         .build();
 
@@ -142,7 +142,7 @@ public interface MailboxMessageFixture {
         .internalDate(new Date(DATE_1.toEpochSecond()))
         .bodyStartOctet(0)
         .flags(flags1)
-        .propertyBuilder(new PropertyBuilder())
+        .properties(new PropertyBuilder())
         .mailboxId(MAILBOX_ID_11)
         .build();
 
@@ -154,7 +154,7 @@ public interface MailboxMessageFixture {
         .internalDate(new Date(DATE_2.toEpochSecond()))
         .bodyStartOctet(0)
         .flags(new Flags())
-        .propertyBuilder(new PropertyBuilder())
+        .properties(new PropertyBuilder())
         .mailboxId(MAILBOX_ID_1)
         .build();
 }
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAO.java
index a321551..632af57 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAO.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAO.java
@@ -192,7 +192,9 @@ public class CassandraMessageDAO {
     }
 
     private List<UDTValue> buildPropertiesUdt(MailboxMessage message) {
-        return message.getProperties().stream()
+        return message.getProperties()
+            .toProperties()
+            .stream()
             .map(property -> typesProvider.getDefinedUserType(PROPERTIES)
                 .newValue()
                 .setString(Properties.NAMESPACE, property.getNamespace())
@@ -236,19 +238,19 @@ public class CassandraMessageDAO {
                 row.getLong(FULL_CONTENT_OCTETS),
                 row.getInt(BODY_START_OCTET),
                 new SharedByteArrayInputStream(content),
-                getPropertyBuilder(row),
+                getProperties(row),
                 getAttachments(row).collect(Guavate.toImmutableList()),
                 headerId,
                 bodyId));
     }
 
-    private PropertyBuilder getPropertyBuilder(Row row) {
+    private org.apache.james.mailbox.store.mail.model.impl.Properties getProperties(Row row) {
         PropertyBuilder property = new PropertyBuilder(
             row.getList(PROPERTIES, UDTValue.class).stream()
                 .map(this::toProperty)
                 .collect(Collectors.toList()));
         property.setTextualLineCount(row.getLong(TEXTUAL_LINE_COUNT));
-        return property;
+        return property.build();
     }
 
     private Property toProperty(UDTValue udtValue) {
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/MessageRepresentation.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/MessageRepresentation.java
index 7ac496b..19fd945 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/MessageRepresentation.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/MessageRepresentation.java
@@ -28,7 +28,7 @@ import org.apache.james.blob.api.BlobId;
 import org.apache.james.mailbox.model.ComposedMessageIdWithMetaData;
 import org.apache.james.mailbox.model.MessageAttachmentMetadata;
 import org.apache.james.mailbox.model.MessageId;
-import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder;
+import org.apache.james.mailbox.store.mail.model.impl.Properties;
 import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage;
 
 public class MessageRepresentation {
@@ -37,19 +37,19 @@ public class MessageRepresentation {
     private final Long size;
     private final Integer bodySize;
     private final SharedByteArrayInputStream content;
-    private final PropertyBuilder propertyBuilder;
+    private final Properties properties;
     private final List<MessageAttachmentRepresentation> attachments;
     private final BlobId headerId;
     private final BlobId bodyId;
 
     public MessageRepresentation(MessageId messageId, Date internalDate, Long size, Integer bodySize, SharedByteArrayInputStream content,
-                                 PropertyBuilder propertyBuilder, List<MessageAttachmentRepresentation> attachments, BlobId headerId, BlobId bodyId) {
+                                 Properties properties, List<MessageAttachmentRepresentation> attachments, BlobId headerId, BlobId bodyId) {
         this.messageId = messageId;
         this.internalDate = internalDate;
         this.size = size;
         this.bodySize = bodySize;
         this.content = content;
-        this.propertyBuilder = propertyBuilder;
+        this.properties = properties;
         this.attachments = attachments;
         this.headerId = headerId;
         this.bodyId = bodyId;
@@ -66,7 +66,7 @@ public class MessageRepresentation {
             .size(size)
             .content(content)
             .flags(metadata.getFlags())
-            .propertyBuilder(propertyBuilder)
+            .properties(properties)
             .addAttachments(attachments)
             .build();
     }
@@ -79,8 +79,8 @@ public class MessageRepresentation {
         return content;
     }
 
-    public PropertyBuilder getPropertyBuilder() {
-        return propertyBuilder;
+    public Properties getProperties() {
+        return properties;
     }
 
     public List<MessageAttachmentRepresentation> getAttachments() {
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOTest.java
index b255ac7..bb52781 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOTest.java
@@ -112,7 +112,7 @@ class CassandraMessageDAOTest {
         MessageRepresentation attachmentRepresentation =
             toMessage(testee.retrieveMessage(messageIdWithMetadata, MessageMapper.FetchType.Metadata));
 
-        assertThat(attachmentRepresentation.getPropertyBuilder().getTextualLineCount())
+        assertThat(attachmentRepresentation.getProperties().getTextualLineCount())
             .isEqualTo(0L);
     }
 
@@ -128,7 +128,7 @@ class CassandraMessageDAOTest {
         MessageRepresentation attachmentRepresentation =
             toMessage(testee.retrieveMessage(messageIdWithMetadata, MessageMapper.FetchType.Metadata));
 
-        assertThat(attachmentRepresentation.getPropertyBuilder().getTextualLineCount()).isEqualTo(textualLineCount);
+        assertThat(attachmentRepresentation.getProperties().getTextualLineCount()).isEqualTo(textualLineCount);
     }
 
     @Test
@@ -183,7 +183,7 @@ class CassandraMessageDAOTest {
             .size(content.length())
             .content(new SharedByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)))
             .flags(new Flags())
-            .propertyBuilder(propertyBuilder)
+            .properties(propertyBuilder.build())
             .addAttachments(attachments)
             .build();
     }
diff --git a/mailbox/elasticsearch/src/test/java/org/apache/james/mailbox/elasticsearch/events/ElasticSearchListeningMessageSearchIndexTest.java b/mailbox/elasticsearch/src/test/java/org/apache/james/mailbox/elasticsearch/events/ElasticSearchListeningMessageSearchIndexTest.java
index 3275897..1dc6d7e 100644
--- a/mailbox/elasticsearch/src/test/java/org/apache/james/mailbox/elasticsearch/events/ElasticSearchListeningMessageSearchIndexTest.java
+++ b/mailbox/elasticsearch/src/test/java/org/apache/james/mailbox/elasticsearch/events/ElasticSearchListeningMessageSearchIndexTest.java
@@ -109,7 +109,7 @@ class ElasticSearchListeningMessageSearchIndexTest {
         .internalDate(new Date(1433628000000L))
         .size(SIZE)
         .content(new SharedByteArrayInputStream("message".getBytes(StandardCharsets.UTF_8)))
-        .propertyBuilder(new PropertyBuilder())
+        .properties(new PropertyBuilder())
         .modseq(MOD_SEQ);
 
     static final SimpleMailboxMessage MESSAGE_1 = MESSAGE_BUILDER.messageId(MESSAGE_ID_1)
diff --git a/mailbox/elasticsearch/src/test/java/org/apache/james/mailbox/elasticsearch/json/MessageToElasticSearchJsonTest.java b/mailbox/elasticsearch/src/test/java/org/apache/james/mailbox/elasticsearch/json/MessageToElasticSearchJsonTest.java
index 7c7f5ba..93ec1ad 100644
--- a/mailbox/elasticsearch/src/test/java/org/apache/james/mailbox/elasticsearch/json/MessageToElasticSearchJsonTest.java
+++ b/mailbox/elasticsearch/src/test/java/org/apache/james/mailbox/elasticsearch/json/MessageToElasticSearchJsonTest.java
@@ -100,7 +100,7 @@ class MessageToElasticSearchJsonTest {
                 BODY_START_OCTET,
                 ClassLoaderUtils.getSystemResourceAsSharedStream("eml/spamMail.eml"),
                 new Flags(),
-                propertyBuilder,
+                propertyBuilder.build(),
                 MAILBOX_ID);
         spamMail.setUid(UID);
         spamMail.setModSeq(MOD_SEQ);
@@ -120,7 +120,7 @@ class MessageToElasticSearchJsonTest {
                 BODY_START_OCTET,
                 ClassLoaderUtils.getSystemResourceAsSharedStream("eml/invalidCharset.eml"),
                 new Flags(),
-                propertyBuilder,
+                propertyBuilder.build(),
                 MAILBOX_ID);
         spamMail.setUid(UID);
         spamMail.setModSeq(MOD_SEQ);
@@ -142,7 +142,7 @@ class MessageToElasticSearchJsonTest {
                 BODY_START_OCTET,
                 ClassLoaderUtils.getSystemResourceAsSharedStream("eml/htmlMail.eml"),
                 new FlagsBuilder().add(Flags.Flag.DELETED, Flags.Flag.SEEN).add("social", "pocket-money").build(),
-                propertyBuilder,
+                propertyBuilder.build(),
                 MAILBOX_ID);
         htmlMail.setModSeq(MOD_SEQ);
         htmlMail.setUid(UID);
@@ -162,7 +162,7 @@ class MessageToElasticSearchJsonTest {
                 BODY_START_OCTET,
                 ClassLoaderUtils.getSystemResourceAsSharedStream("eml/pgpSignedMail.eml"),
                 new FlagsBuilder().add(Flags.Flag.DELETED, Flags.Flag.SEEN).add("debian", "security").build(),
-                propertyBuilder,
+                propertyBuilder.build(),
                 MAILBOX_ID);
         pgpSignedMail.setModSeq(MOD_SEQ);
         pgpSignedMail.setUid(UID);
@@ -182,7 +182,7 @@ class MessageToElasticSearchJsonTest {
                 BODY_START_OCTET,
                 ClassLoaderUtils.getSystemResourceAsSharedStream("eml/mail.eml"),
                 new FlagsBuilder().add(Flags.Flag.DELETED, Flags.Flag.SEEN).add("debian", "security").build(),
-                propertyBuilder,
+                propertyBuilder.build(),
                 MAILBOX_ID);
         mail.setModSeq(MOD_SEQ);
         mail.setUid(UID);
@@ -203,7 +203,7 @@ class MessageToElasticSearchJsonTest {
                 BODY_START_OCTET,
                 ClassLoaderUtils.getSystemResourceAsSharedStream("eml/recursiveMail.eml"),
                 new FlagsBuilder().add(Flags.Flag.DELETED, Flags.Flag.SEEN).add("debian", "security").build(),
-                propertyBuilder,
+                propertyBuilder.build(),
                 MAILBOX_ID);
         recursiveMail.setModSeq(MOD_SEQ);
         recursiveMail.setUid(UID);
@@ -223,7 +223,7 @@ class MessageToElasticSearchJsonTest {
                 BODY_START_OCTET,
                 ClassLoaderUtils.getSystemResourceAsSharedStream("eml/recursiveMail.eml"),
                 new FlagsBuilder().add(Flags.Flag.DELETED, Flags.Flag.SEEN).add("debian", "security").build(),
-                propertyBuilder,
+                propertyBuilder.build(),
                 MAILBOX_ID);
         mailWithNoInternalDate.setModSeq(MOD_SEQ);
         mailWithNoInternalDate.setUid(UID);
@@ -242,7 +242,7 @@ class MessageToElasticSearchJsonTest {
                 BODY_START_OCTET,
                 ClassLoaderUtils.getSystemResourceAsSharedStream("eml/recursiveMail.eml"),
                 new FlagsBuilder().add(Flags.Flag.DELETED, Flags.Flag.SEEN).add("debian", "security").build(),
-                propertyBuilder,
+                propertyBuilder.build(),
                 MAILBOX_ID);
         mailWithNoInternalDate.setModSeq(MOD_SEQ);
         mailWithNoInternalDate.setUid(UID);
@@ -270,7 +270,7 @@ class MessageToElasticSearchJsonTest {
                 BODY_START_OCTET,
                 ClassLoaderUtils.getSystemResourceAsSharedStream("eml/recursiveMail.eml"),
                 new FlagsBuilder().add(Flags.Flag.DELETED, Flags.Flag.SEEN).add("debian", "security").build(),
-                propertyBuilder,
+                propertyBuilder.build(),
                 MAILBOX_ID);
         mailWithNoInternalDate.setModSeq(MOD_SEQ);
         mailWithNoInternalDate.setUid(UID);
@@ -299,7 +299,7 @@ class MessageToElasticSearchJsonTest {
             BODY_START_OCTET,
             ClassLoaderUtils.getSystemResourceAsSharedStream("eml/recursiveMail.eml"),
             new FlagsBuilder().add(Flags.Flag.DELETED, Flags.Flag.SEEN).add("debian", "security").build(),
-            propertyBuilder,
+            propertyBuilder.build(),
             null);
         mailWithNoMailboxId.setModSeq(MOD_SEQ);
         mailWithNoMailboxId.setUid(UID);
@@ -351,7 +351,7 @@ class MessageToElasticSearchJsonTest {
             BODY_START_OCTET,
             ClassLoaderUtils.getSystemResourceAsSharedStream("eml/nonTextual.eml"),
             new Flags(),
-            propertyBuilder,
+            propertyBuilder.build(),
             MAILBOX_ID);
         spamMail.setUid(UID);
         spamMail.setModSeq(MOD_SEQ);
@@ -371,7 +371,7 @@ class MessageToElasticSearchJsonTest {
             BODY_START_OCTET,
             ClassLoaderUtils.getSystemResourceAsSharedStream("eml/emailWithNonIndexableAttachment.eml"),
             new FlagsBuilder().add(Flags.Flag.DELETED, Flags.Flag.SEEN).add("debian", "security").build(),
-            propertyBuilder,
+            propertyBuilder.build(),
             MAILBOX_ID);
         message.setModSeq(MOD_SEQ);
         message.setUid(UID);
@@ -399,7 +399,7 @@ class MessageToElasticSearchJsonTest {
             BODY_START_OCTET,
             ClassLoaderUtils.getSystemResourceAsSharedStream("eml/emailWithNonIndexableAttachment.eml"),
             new FlagsBuilder().add(Flags.Flag.DELETED, Flags.Flag.SEEN).add("debian", "security").build(),
-            propertyBuilder,
+            propertyBuilder.build(),
             MAILBOX_ID);
         message.setModSeq(MOD_SEQ);
         message.setUid(UID);
diff --git a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/AbstractJPAMailboxMessage.java b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/AbstractJPAMailboxMessage.java
index 480989a..7f62ce3 100644
--- a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/AbstractJPAMailboxMessage.java
+++ b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/mail/model/openjpa/AbstractJPAMailboxMessage.java
@@ -61,6 +61,7 @@ import org.apache.james.mailbox.store.mail.model.FlagsFactory;
 import org.apache.james.mailbox.store.mail.model.MailboxMessage;
 import org.apache.james.mailbox.store.mail.model.Property;
 import org.apache.james.mailbox.store.mail.model.impl.MessageParser;
+import org.apache.james.mailbox.store.mail.model.impl.Properties;
 import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder;
 import org.apache.openjpa.persistence.jdbc.ElementJoinColumn;
 import org.apache.openjpa.persistence.jdbc.ElementJoinColumns;
@@ -254,13 +255,14 @@ public abstract class AbstractJPAMailboxMessage implements MailboxMessage {
         setFlags(flags);
         this.contentOctets = contentOctets;
         this.bodyStartOctet = bodyStartOctet;
-        this.textualLineCount = propertyBuilder.getTextualLineCount();
-        this.mediaType = propertyBuilder.getMediaType();
-        this.subType = propertyBuilder.getSubType();
-        final List<Property> properties = propertyBuilder.toProperties();
-        this.properties = new ArrayList<>(properties.size());
+        Properties properties = propertyBuilder.build();
+        this.textualLineCount = properties.getTextualLineCount();
+        this.mediaType = properties.getMediaType();
+        this.subType = properties.getSubType();
+        final List<Property> propertiesAsList = properties.toProperties();
+        this.properties = new ArrayList<>(propertiesAsList.size());
         int order = 0;
-        for (Property property : properties) {
+        for (Property property : propertiesAsList) {
             this.properties.add(new JPAProperty(property, order++));
         }
 
@@ -296,11 +298,10 @@ public abstract class AbstractJPAMailboxMessage implements MailboxMessage {
         this.bodyStartOctet = (int) (original.getFullContentOctets() - original.getBodyOctets());
         this.internalDate = original.getInternalDate();
 
-        PropertyBuilder pBuilder = new PropertyBuilder(original.getProperties());
         this.textualLineCount = original.getTextualLineCount();
         this.mediaType = original.getMediaType();
         this.subType = original.getSubType();
-        final List<Property> properties = pBuilder.toProperties();
+        final List<Property> properties = original.getProperties().toProperties();
         this.properties = new ArrayList<>(properties.size());
         int order = 0;
         for (Property property : properties) {
@@ -360,10 +361,11 @@ public abstract class AbstractJPAMailboxMessage implements MailboxMessage {
      * @return unmodifiable list of meta-data, not null
      */
     @Override
-    public List<Property> getProperties() {
-        return properties.stream()
+    public Properties getProperties() {
+        return new PropertyBuilder(properties.stream()
             .map(JPAProperty::toProperty)
-            .collect(Guavate.toImmutableList());
+            .collect(Guavate.toImmutableList()))
+            .build();
     }
 
     @Override
diff --git a/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/mail/MessageUtilsTest.java b/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/mail/MessageUtilsTest.java
index c58c204..afb4acc 100644
--- a/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/mail/MessageUtilsTest.java
+++ b/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/mail/MessageUtilsTest.java
@@ -63,7 +63,7 @@ class MessageUtilsTest {
         MockitoAnnotations.initMocks(this);
         messageUtils = new MessageUtils(uidProvider, modSeqProvider);
         message = new SimpleMailboxMessage(MESSAGE_ID, new Date(), CONTENT.length(), BODY_START,
-            new SharedByteArrayInputStream(CONTENT.getBytes()), new Flags(), new PropertyBuilder(), mailbox.getMailboxId());
+            new SharedByteArrayInputStream(CONTENT.getBytes()), new Flags(), new PropertyBuilder().build(), mailbox.getMailboxId());
     }
     
     @Test
diff --git a/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/MaildirMessage.java b/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/MaildirMessage.java
index 3861dd9..af337cb 100644
--- a/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/MaildirMessage.java
+++ b/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/MaildirMessage.java
@@ -37,8 +37,8 @@ import org.apache.james.mailbox.model.MessageId;
 import org.apache.james.mailbox.model.ParsedAttachment;
 import org.apache.james.mailbox.store.mail.model.DefaultMessageId;
 import org.apache.james.mailbox.store.mail.model.Message;
-import org.apache.james.mailbox.store.mail.model.Property;
 import org.apache.james.mailbox.store.mail.model.impl.MessageParser;
+import org.apache.james.mailbox.store.mail.model.impl.Properties;
 import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder;
 import org.apache.james.mailbox.store.streaming.CountingInputStream;
 import org.apache.james.mime4j.MimeException;
@@ -185,13 +185,13 @@ public class MaildirMessage implements Message {
     @Override
     public String getMediaType() {
         parseMessage();
-        return propertyBuilder.getMediaType();
+        return propertyBuilder.build().getMediaType();
     }
 
     @Override
     public String getSubType() {
         parseMessage();
-        return propertyBuilder.getSubType();
+        return propertyBuilder.build().getSubType();
     }
 
     @Override
@@ -221,13 +221,13 @@ public class MaildirMessage implements Message {
     @Override
     public Long getTextualLineCount() {
         parseMessage();
-        return propertyBuilder.getTextualLineCount();
+        return propertyBuilder.build().getTextualLineCount();
     }
 
     @Override
-    public List<Property> getProperties() {
+    public Properties getProperties() {
         parseMessage();
-        return propertyBuilder.toProperties();
+        return propertyBuilder.build();
     }
 
     @Override
diff --git a/mailbox/plugin/spamassassin/src/test/java/org/apache/james/mailbox/spamassassin/SpamAssassinListenerTest.java b/mailbox/plugin/spamassassin/src/test/java/org/apache/james/mailbox/spamassassin/SpamAssassinListenerTest.java
index e159800..c24ee8e 100644
--- a/mailbox/plugin/spamassassin/src/test/java/org/apache/james/mailbox/spamassassin/SpamAssassinListenerTest.java
+++ b/mailbox/plugin/spamassassin/src/test/java/org/apache/james/mailbox/spamassassin/SpamAssassinListenerTest.java
@@ -271,7 +271,7 @@ class SpamAssassinListenerTest {
         int bodyStartOctet = 25;
         byte[] content = "Subject: test\r\n\r\nBody\r\n".getBytes(StandardCharsets.UTF_8);
         SimpleMailboxMessage message = new SimpleMailboxMessage(MESSAGE_ID, new Date(),
-            size, bodyStartOctet, new SharedByteArrayInputStream(content), new Flags(), new PropertyBuilder(),
+            size, bodyStartOctet, new SharedByteArrayInputStream(content), new Flags(), new PropertyBuilder().build(),
             mailbox.getMailboxId());
         MessageMetaData messageMetaData = mapperFactory.createMessageMapper(null).add(mailbox, message);
         message.setUid(messageMetaData.getUid());
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/LazyMimeDescriptor.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/LazyMimeDescriptor.java
index e1d4ae2..20ad442 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/LazyMimeDescriptor.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/LazyMimeDescriptor.java
@@ -32,6 +32,7 @@ import org.apache.james.mailbox.model.Header;
 import org.apache.james.mailbox.model.MessageResult;
 import org.apache.james.mailbox.model.MimeDescriptor;
 import org.apache.james.mailbox.store.mail.model.Message;
+import org.apache.james.mailbox.store.mail.model.impl.Properties;
 import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder;
 
 /**
@@ -42,7 +43,7 @@ public class LazyMimeDescriptor implements MimeDescriptor {
 
     private final Message message;
     private final MessageResult result;
-    private PropertyBuilder pbuilder;
+    private Properties properties;
     
     public LazyMimeDescriptor(MessageResult result, Message message) {
         this.message = message;
@@ -151,11 +152,11 @@ public class LazyMimeDescriptor implements MimeDescriptor {
      * 
      * @return pbuilder
      */
-    private PropertyBuilder getPropertyBuilder() {
-        if (pbuilder == null) {
-            pbuilder = new PropertyBuilder(message.getProperties());
+    private Properties getPropertyBuilder() {
+        if (properties == null) {
+            properties = message.getProperties();
         }
-        return pbuilder;
+        return properties;
     }
     
 }
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageFactory.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageFactory.java
index 4a4a4f2..2a3006f 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageFactory.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageFactory.java
@@ -43,7 +43,7 @@ public interface MessageFactory<T extends MailboxMessage> {
         public SimpleMailboxMessage createMessage(MessageId messageId, Mailbox mailbox, Date internalDate, int size,
                                             int bodyStartOctet, SharedInputStream content, Flags flags,
                                             PropertyBuilder propertyBuilder, List<MessageAttachmentMetadata> attachments) {
-            return new SimpleMailboxMessage(messageId, internalDate, size, bodyStartOctet, content, flags, propertyBuilder,
+            return new SimpleMailboxMessage(messageId, internalDate, size, bodyStartOctet, content, flags, propertyBuilder.build(),
                 mailbox.getMailboxId(), attachments);
         }
     }
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/DelegatingMailboxMessage.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/DelegatingMailboxMessage.java
index cdd3ff2..6b5c5f9 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/DelegatingMailboxMessage.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/DelegatingMailboxMessage.java
@@ -27,6 +27,7 @@ import javax.mail.Flags;
 
 import org.apache.james.mailbox.model.MessageAttachmentMetadata;
 import org.apache.james.mailbox.model.MessageId;
+import org.apache.james.mailbox.store.mail.model.impl.Properties;
 
 public abstract class DelegatingMailboxMessage implements MailboxMessage {
 
@@ -77,7 +78,7 @@ public abstract class DelegatingMailboxMessage implements MailboxMessage {
     }
 
     @Override
-    public List<Property> getProperties() {
+    public Properties getProperties() {
         return message.getProperties();
     }
 
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/Message.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/Message.java
index 9cacbfc..fef05fb 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/Message.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/Message.java
@@ -25,6 +25,7 @@ import java.util.List;
 
 import org.apache.james.mailbox.model.MessageAttachmentMetadata;
 import org.apache.james.mailbox.model.MessageId;
+import org.apache.james.mailbox.store.mail.model.impl.Properties;
 
 public interface Message {
 
@@ -96,11 +97,11 @@ public interface Message {
     /**
      * Gets a read-only list of meta-data properties.
      * For properties with multiple values, this list will contain
-     * several enteries with the same namespace and local name.
+     * several entries with the same namespace and local name.
      *
      * @return unmodifiable list of meta-data, not null
      */
-    List<Property> getProperties();
+    Properties getProperties();
     
     /**
      * Return the list of attachments
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/PropertyBuilder.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/Properties.java
similarity index 57%
copy from mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/PropertyBuilder.java
copy to mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/Properties.java
index 1f53595..b8900a4 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/PropertyBuilder.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/Properties.java
@@ -42,7 +42,6 @@ import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_SUB_T
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.SortedMap;
 import java.util.TreeMap;
@@ -50,27 +49,16 @@ import java.util.TreeMap;
 import org.apache.james.mailbox.store.mail.model.Property;
 
 import com.github.steveash.guavate.Guavate;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
 
-/**
- * Builds properties
- */
-public class PropertyBuilder {
-    private static final int INITIAL_CAPACITY = 32;
-
+public class Properties {
     private Long textualLineCount;
-    private final List<Property> properties;
+    private final ImmutableList<Property> properties;
 
-    public PropertyBuilder(List<Property> props) {
-        textualLineCount = null;
-        properties = new ArrayList<>(props.size());
-        for (Property property:props) {
-            properties.add(new Property(property));
-        }
-    }
-    
-    public PropertyBuilder() {
-        textualLineCount = null;
-        properties = new ArrayList<>(INITIAL_CAPACITY);
+    public Properties(List<Property> props, Long textualLineCount) {
+        this.textualLineCount = textualLineCount;
+        this.properties = ImmutableList.copyOf(props);
     }
 
     /**
@@ -81,15 +69,6 @@ public class PropertyBuilder {
     public Long getTextualLineCount() {
         return textualLineCount;
     }
-
-    /**
-     * Aets the number of CRLF in a textual document.
-     * @param textualLineCount count when document is textual,
-     * null otherwise
-     */
-    public void setTextualLineCount(Long textualLineCount) {
-        this.textualLineCount = textualLineCount;
-    }
     
     /**
      * Gets the first value with the given name.
@@ -99,7 +78,7 @@ public class PropertyBuilder {
      * @return value, 
      * or null when no property has the given name and namespace
      */
-    public String getFirstValue(String namespace, String localName) {
+    private String getFirstValue(String namespace, String localName) {
         return properties.stream()
             .filter(property -> property.isNamed(namespace, localName))
             .findFirst()
@@ -113,48 +92,20 @@ public class PropertyBuilder {
      * @param localName not null
      * @return not null
      */
-    public List<String> getValues(String namespace, String localName) {
+    private List<String> getValues(String namespace, String localName) {
         return properties.stream()
             .filter(property -> property.isNamed(namespace, localName))
             .map(Property::getValue)
             .collect(Guavate.toImmutableList());
     }
-    
-    /**
-     * Sets a property allowing only a single value.
-     * @param namespace not null
-     * @param localName not null
-     * @param value null to remove property
-     */
-    public void setProperty(String namespace, String localName, String value) {
-        properties.removeIf(property -> property.isNamed(namespace, localName));
-        
-        if (value != null) {
-            properties.add(new Property(namespace, localName, value));
-        }
-    }
-    
-    /**
-     * Sets a multiple valued property.
-     * @param namespace not null
-     * @param localName not null
-     * @param values null to remove property
-     */
-    public void setProperty(String namespace, String localName, List<String> values) {
-        properties.removeIf(property -> property.isNamed(namespace, localName));
-        if (values != null) {
-            for (String value:values) {
-                properties.add(new Property(namespace, localName, value));
-            }
-        }
-    }
+
     
     /**
      * Maps properties in the given namespace.
      * @param namespace not null
      * @return values indexed by local name
      */
-    public SortedMap<String,String> getProperties(String namespace) {
+    private SortedMap<String,String> getProperties(String namespace) {
         final SortedMap<String, String> parameters = new TreeMap<>();
         for (Property property : properties) {
             if (property.isInSpace(namespace)) {
@@ -165,20 +116,6 @@ public class PropertyBuilder {
     }
     
     /**
-     * Sets properties in the given namespace from the map.
-     * Existing properties in the namespace will be removed.
-     * All local names will be converted to lower case.
-     * @param namespace not null
-     * @param valuesByLocalName not null
-     */
-    public void setProperties(String namespace, Map<String,String> valuesByLocalName) {
-        properties.removeIf(property -> property.isInSpace(namespace));
-        for (Map.Entry<String, String> valueByLocalName:valuesByLocalName.entrySet()) {
-            properties.add(new Property(namespace, valueByLocalName.getKey().toLowerCase(Locale.US), valueByLocalName.getValue()));
-        }
-    }
-    
-    /**
      * Gets the top level MIME content media type.
      * 
      * @return top level MIME content media type, or null if default
@@ -186,16 +123,6 @@ public class PropertyBuilder {
     public String getMediaType() {
         return getFirstValue(MIME_MIME_TYPE_SPACE, MIME_MEDIA_TYPE_NAME);
     }
-    
-    /**
-     * Sets the top level MIME content media type.
-     * 
-     * @param value top level MIME content media type, 
-     * or null to remove
-     */
-    public void setMediaType(String value) {
-        setProperty(MIME_MIME_TYPE_SPACE, MIME_MEDIA_TYPE_NAME, value);
-    }
 
     /**
      * Gets the MIME content subtype.
@@ -205,16 +132,7 @@ public class PropertyBuilder {
     public String getSubType() {
         return getFirstValue(MIME_MIME_TYPE_SPACE, MIME_SUB_TYPE_NAME);
     }
-    
-    /**
-     * Sets the MIME content subtype.
-     * 
-     * @param value the MIME content subtype, 
-     * or null to remove
-     */
-    public void setSubType(String value) {
-        setProperty(MIME_MIME_TYPE_SPACE, MIME_SUB_TYPE_NAME, value);
-    }
+
     
     /**
      * Gets the MIME Content-ID.
@@ -226,16 +144,6 @@ public class PropertyBuilder {
     }
     
     /**
-     * Sets MIME Content-ID.
-     * 
-     * @param value the MIME content subtype, 
-     * or null to remove
-     */
-    public void setContentID(String value) {
-        setProperty(MIME_CONTENT_ID_SPACE, MIME_CONTENT_ID_NAME, value);
-    }
-    
-    /**
      * Gets the MIME Content-Description.
      * 
      * @return the MIME Content-Description, 
@@ -246,16 +154,6 @@ public class PropertyBuilder {
     }
     
     /**
-     * Sets MIME Content-Description.
-     * 
-     * @param value the MIME Content-Description
-     * or null to remove
-     */
-    public void setContentDescription(String value) {
-        setProperty(MIME_CONTENT_DESCRIPTION_SPACE, MIME_CONTENT_DESCRIPTION_NAME, value);
-    }
-    
-    /**
      * Gets the MIME Content-Transfer-Encoding.
      * 
      * @return the MIME Content-Transfer-Encoding, 
@@ -264,16 +162,7 @@ public class PropertyBuilder {
     public String getContentTransferEncoding() {
         return getFirstValue(MIME_CONTENT_TRANSFER_ENCODING_SPACE, MIME_CONTENT_TRANSFER_ENCODING_NAME);
     }
-    
-    /**
-     * Sets MIME Content-Transfer-Encoding.
-     * 
-     * @param value the MIME Content-Transfer-Encoding
-     * or null to remove
-     */
-    public void setContentTransferEncoding(String value) {
-        setProperty(MIME_CONTENT_TRANSFER_ENCODING_SPACE, MIME_CONTENT_TRANSFER_ENCODING_NAME, value);
-    }
+
     
     /**
      * Gets the RFC2557 Content-Location.
@@ -286,26 +175,6 @@ public class PropertyBuilder {
     }
     
     /**
-     * Sets RFC2557 Content-Location.
-     * 
-     * @param value the RFC2557 Content-Location
-     * or null to remove
-     */
-    public void setContentLocation(String value) {
-        setProperty(MIME_CONTENT_LOCATION_SPACE, MIME_CONTENT_LOCATION_NAME, value);
-    }
-    
-    /**
-     * Sets RFC2183 Content-Disposition disposition-type.
-     * 
-     * @param value the RFC2183 Content-Disposition
-     * or null to remove
-     */
-    public void setContentDispositionType(String value) {
-        setProperty(MIME_CONTENT_DISPOSITION_SPACE, MIME_CONTENT_DISPOSITION_TYPE_NAME, value);
-    }
-    
-    /**
      * Gets the RFC2183 Content-Disposition disposition-type.
      * 
      * @return the RFC2183 Content-Disposition, 
@@ -324,15 +193,6 @@ public class PropertyBuilder {
     }
     
     /**
-     * Sets Content-Disposition parameters.
-     * Parameter names will be normalised to lower case.
-     * @param valuesByParameterName values indexed by parameter name
-     */
-    public void setContentDispositionParameters(Map<String,String> valuesByParameterName) {
-        setProperties(MIME_CONTENT_DISPOSITION_PARAMETER_SPACE, valuesByParameterName);
-    }
-    
-    /**
      * Gets RFC2045 Content-Type parameters.
      * @return parameter values indexed by lower case local names 
      */
@@ -341,15 +201,6 @@ public class PropertyBuilder {
     }
     
     /**
-     * Sets Content-Type parameters.
-     * Parameter names will be normalised to lower case.
-     * @param valuesByParameterName values indexed by parameter name
-     */
-    public void setContentTypeParameters(Map<String,String> valuesByParameterName) {
-        setProperties(MIME_CONTENT_TYPE_PARAMETER_SPACE, valuesByParameterName);
-    }
-    
-    /**
      * Gets the RFC1864 Content-MD5.
      * 
      * @return the RFC1864 Content-MD5, 
@@ -360,16 +211,6 @@ public class PropertyBuilder {
     }
     
     /**
-     * Sets RFC1864 Content-MD5.
-     * 
-     * @param value the RFC1864 Content-MD5
-     * or null to remove
-     */
-    public void setContentMD5(String value) {
-        setProperty(MIME_CONTENT_MD5_SPACE, MIME_CONTENT_MD5_NAME, value);
-    }
-    
-    /**
      * Gets the RFC2045 Content-Type "charset" parameter.
      * 
      * @return the RFC2045 Content-Type "charset" parameter, 
@@ -378,16 +219,7 @@ public class PropertyBuilder {
     public String getCharset() {
         return getFirstValue(MIME_CONTENT_TYPE_PARAMETER_SPACE, MIME_CONTENT_TYPE_PARAMETER_CHARSET_NAME);
     }
-    
-    /**
-     * Sets RFC2045 Content-Type "charset" parameter.
-     * 
-     * @param value the RFC2045 Content-Type "charset" parameter
-     * or null to remove
-     */
-    public void setCharset(String value) {
-        setProperty(MIME_CONTENT_TYPE_PARAMETER_SPACE, MIME_CONTENT_TYPE_PARAMETER_CHARSET_NAME, value);
-    }
+
     
     /**
      * Gets the RFC2045 Content-Type "boundary" parameter.
@@ -400,16 +232,6 @@ public class PropertyBuilder {
     }
     
     /**
-     * Sets RFC2045 Content-Type "boundary" parameter.
-     * 
-     * @param value the RFC2045 Content-Type "boundary" parameter
-     * or null to remove
-     */
-    public void setBoundary(String value) {
-        setProperty(MIME_CONTENT_TYPE_PARAMETER_SPACE, MIME_CONTENT_TYPE_PARAMETER_BOUNDARY_NAME, value);
-    }
-    
-    /**
      * Gets the RFC1766 Content-Language.
      * 
      * @return list of parsed langauge tags from the RFC1766 Content-Language, 
@@ -418,21 +240,7 @@ public class PropertyBuilder {
     public List<String> getContentLanguage() {
         return getValues(MIME_CONTENT_LANGUAGE_SPACE, MIME_CONTENT_LANGUAGE_NAME);
     }
-    
-    /**
-     * Sets RFC1766 Content-Language.
-     * 
-     * @param values list of parsed language tags from the RFC1766 Content-Language, 
-     * possibly empty
-     */
-    public void setContentLanguage(List<String> values) {
-        setProperty(MIME_CONTENT_LANGUAGE_SPACE, MIME_CONTENT_LANGUAGE_NAME, values);
-    }
-    
-    /**
-     * Builds a list of properties.
-     * @return not null
-     */
+
     public List<Property> toProperties() {
         return new ArrayList<>(properties);
     }
@@ -445,9 +253,9 @@ public class PropertyBuilder {
      * of this object.
      */
     public String toString() {
-        return "PropertyBuilder ( "
-        + " textualLineCount = " + this.textualLineCount
-        + " properties = " + this.properties
-        + " )";
+        return MoreObjects.toStringHelper(this)
+            .add("textualLineCount", textualLineCount)
+            .add("properties", properties)
+            .toString();
     }
 }
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/PropertyBuilder.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/PropertyBuilder.java
index 1f53595..0abf1ee 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/PropertyBuilder.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/PropertyBuilder.java
@@ -44,13 +44,9 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.SortedMap;
-import java.util.TreeMap;
 
 import org.apache.james.mailbox.store.mail.model.Property;
 
-import com.github.steveash.guavate.Guavate;
-
 /**
  * Builds properties
  */
@@ -90,35 +86,7 @@ public class PropertyBuilder {
     public void setTextualLineCount(Long textualLineCount) {
         this.textualLineCount = textualLineCount;
     }
-    
-    /**
-     * Gets the first value with the given name.
-     * Used to retrieve values with a single value.
-     * @param namespace not null
-     * @param localName not null
-     * @return value, 
-     * or null when no property has the given name and namespace
-     */
-    public String getFirstValue(String namespace, String localName) {
-        return properties.stream()
-            .filter(property -> property.isNamed(namespace, localName))
-            .findFirst()
-            .map(Property::getValue)
-            .orElse(null);
-    }
-    
-    /**
-     * Lists all values for a property.
-     * @param namespace not null
-     * @param localName not null
-     * @return not null
-     */
-    public List<String> getValues(String namespace, String localName) {
-        return properties.stream()
-            .filter(property -> property.isNamed(namespace, localName))
-            .map(Property::getValue)
-            .collect(Guavate.toImmutableList());
-    }
+
     
     /**
      * Sets a property allowing only a single value.
@@ -126,7 +94,7 @@ public class PropertyBuilder {
      * @param localName not null
      * @param value null to remove property
      */
-    public void setProperty(String namespace, String localName, String value) {
+    private void setProperty(String namespace, String localName, String value) {
         properties.removeIf(property -> property.isNamed(namespace, localName));
         
         if (value != null) {
@@ -140,7 +108,7 @@ public class PropertyBuilder {
      * @param localName not null
      * @param values null to remove property
      */
-    public void setProperty(String namespace, String localName, List<String> values) {
+    private void setProperty(String namespace, String localName, List<String> values) {
         properties.removeIf(property -> property.isNamed(namespace, localName));
         if (values != null) {
             for (String value:values) {
@@ -150,28 +118,13 @@ public class PropertyBuilder {
     }
     
     /**
-     * Maps properties in the given namespace.
-     * @param namespace not null
-     * @return values indexed by local name
-     */
-    public SortedMap<String,String> getProperties(String namespace) {
-        final SortedMap<String, String> parameters = new TreeMap<>();
-        for (Property property : properties) {
-            if (property.isInSpace(namespace)) {
-                parameters.put(property.getLocalName(), property.getValue());
-            }
-        }
-        return parameters;
-    }
-    
-    /**
      * Sets properties in the given namespace from the map.
      * Existing properties in the namespace will be removed.
      * All local names will be converted to lower case.
      * @param namespace not null
      * @param valuesByLocalName not null
      */
-    public void setProperties(String namespace, Map<String,String> valuesByLocalName) {
+    private void setProperties(String namespace, Map<String,String> valuesByLocalName) {
         properties.removeIf(property -> property.isInSpace(namespace));
         for (Map.Entry<String, String> valueByLocalName:valuesByLocalName.entrySet()) {
             properties.add(new Property(namespace, valueByLocalName.getKey().toLowerCase(Locale.US), valueByLocalName.getValue()));
@@ -179,15 +132,6 @@ public class PropertyBuilder {
     }
     
     /**
-     * Gets the top level MIME content media type.
-     * 
-     * @return top level MIME content media type, or null if default
-     */
-    public String getMediaType() {
-        return getFirstValue(MIME_MIME_TYPE_SPACE, MIME_MEDIA_TYPE_NAME);
-    }
-    
-    /**
      * Sets the top level MIME content media type.
      * 
      * @param value top level MIME content media type, 
@@ -196,15 +140,6 @@ public class PropertyBuilder {
     public void setMediaType(String value) {
         setProperty(MIME_MIME_TYPE_SPACE, MIME_MEDIA_TYPE_NAME, value);
     }
-
-    /**
-     * Gets the MIME content subtype.
-     * 
-     * @return the MIME content subtype, or null if default
-     */
-    public String getSubType() {
-        return getFirstValue(MIME_MIME_TYPE_SPACE, MIME_SUB_TYPE_NAME);
-    }
     
     /**
      * Sets the MIME content subtype.
@@ -217,15 +152,6 @@ public class PropertyBuilder {
     }
     
     /**
-     * Gets the MIME Content-ID.
-     * 
-     * @return the MIME content subtype, or null if default
-     */
-    public String getContentID() {
-        return getFirstValue(MIME_CONTENT_ID_SPACE, MIME_CONTENT_ID_NAME);
-    }
-    
-    /**
      * Sets MIME Content-ID.
      * 
      * @param value the MIME content subtype, 
@@ -236,16 +162,6 @@ public class PropertyBuilder {
     }
     
     /**
-     * Gets the MIME Content-Description.
-     * 
-     * @return the MIME Content-Description, 
-     * or null if this meta data is not present
-     */
-    public String getContentDescription() {
-        return getFirstValue(MIME_CONTENT_DESCRIPTION_SPACE, MIME_CONTENT_DESCRIPTION_NAME);
-    }
-    
-    /**
      * Sets MIME Content-Description.
      * 
      * @param value the MIME Content-Description
@@ -256,16 +172,6 @@ public class PropertyBuilder {
     }
     
     /**
-     * Gets the MIME Content-Transfer-Encoding.
-     * 
-     * @return the MIME Content-Transfer-Encoding, 
-     * or null if this meta data is not present
-     */
-    public String getContentTransferEncoding() {
-        return getFirstValue(MIME_CONTENT_TRANSFER_ENCODING_SPACE, MIME_CONTENT_TRANSFER_ENCODING_NAME);
-    }
-    
-    /**
      * Sets MIME Content-Transfer-Encoding.
      * 
      * @param value the MIME Content-Transfer-Encoding
@@ -276,16 +182,6 @@ public class PropertyBuilder {
     }
     
     /**
-     * Gets the RFC2557 Content-Location.
-     * 
-     * @return the RFC2557 Content-Location, 
-     * or null if this meta data is not present
-     */
-    public String getContentLocation() {
-        return getFirstValue(MIME_CONTENT_LOCATION_SPACE, MIME_CONTENT_LOCATION_NAME);
-    }
-    
-    /**
      * Sets RFC2557 Content-Location.
      * 
      * @param value the RFC2557 Content-Location
@@ -306,24 +202,6 @@ public class PropertyBuilder {
     }
     
     /**
-     * Gets the RFC2183 Content-Disposition disposition-type.
-     * 
-     * @return the RFC2183 Content-Disposition, 
-     * or null if this meta data is not present
-     */
-    public String getContentDispositionType() {
-        return getFirstValue(MIME_CONTENT_DISPOSITION_SPACE, MIME_CONTENT_DISPOSITION_TYPE_NAME);
-    }
-    
-    /**
-     * Gets RFC2183 Content-Disposition parameters.
-     * @return parameter values indexed by lower case local names 
-     */
-    public Map<String,String> getContentDispositionParameters() {
-        return getProperties(MIME_CONTENT_DISPOSITION_PARAMETER_SPACE);
-    }
-    
-    /**
      * Sets Content-Disposition parameters.
      * Parameter names will be normalised to lower case.
      * @param valuesByParameterName values indexed by parameter name
@@ -333,14 +211,6 @@ public class PropertyBuilder {
     }
     
     /**
-     * Gets RFC2045 Content-Type parameters.
-     * @return parameter values indexed by lower case local names 
-     */
-    public Map<String,String> getContentTypeParameters() {
-        return getProperties(MIME_CONTENT_TYPE_PARAMETER_SPACE);
-    }
-    
-    /**
      * Sets Content-Type parameters.
      * Parameter names will be normalised to lower case.
      * @param valuesByParameterName values indexed by parameter name
@@ -350,16 +220,6 @@ public class PropertyBuilder {
     }
     
     /**
-     * Gets the RFC1864 Content-MD5.
-     * 
-     * @return the RFC1864 Content-MD5, 
-     * or null if this meta data is not present
-     */
-    public String getContentMD5() {
-        return getFirstValue(MIME_CONTENT_MD5_SPACE, MIME_CONTENT_MD5_NAME);
-    }
-    
-    /**
      * Sets RFC1864 Content-MD5.
      * 
      * @param value the RFC1864 Content-MD5
@@ -370,16 +230,6 @@ public class PropertyBuilder {
     }
     
     /**
-     * Gets the RFC2045 Content-Type "charset" parameter.
-     * 
-     * @return the RFC2045 Content-Type "charset" parameter, 
-     * or null if this meta data is not present
-     */
-    public String getCharset() {
-        return getFirstValue(MIME_CONTENT_TYPE_PARAMETER_SPACE, MIME_CONTENT_TYPE_PARAMETER_CHARSET_NAME);
-    }
-    
-    /**
      * Sets RFC2045 Content-Type "charset" parameter.
      * 
      * @param value the RFC2045 Content-Type "charset" parameter
@@ -390,16 +240,6 @@ public class PropertyBuilder {
     }
     
     /**
-     * Gets the RFC2045 Content-Type "boundary" parameter.
-     * 
-     * @return the RFC2045 Content-Type "boundary" parameter, 
-     * or null if this meta data is not present
-     */
-    public String getBoundary() {
-        return getFirstValue(MIME_CONTENT_TYPE_PARAMETER_SPACE, MIME_CONTENT_TYPE_PARAMETER_BOUNDARY_NAME);
-    }
-    
-    /**
      * Sets RFC2045 Content-Type "boundary" parameter.
      * 
      * @param value the RFC2045 Content-Type "boundary" parameter
@@ -410,16 +250,6 @@ public class PropertyBuilder {
     }
     
     /**
-     * Gets the RFC1766 Content-Language.
-     * 
-     * @return list of parsed langauge tags from the RFC1766 Content-Language, 
-     * possibly empty
-     */
-    public List<String> getContentLanguage() {
-        return getValues(MIME_CONTENT_LANGUAGE_SPACE, MIME_CONTENT_LANGUAGE_NAME);
-    }
-    
-    /**
      * Sets RFC1766 Content-Language.
      * 
      * @param values list of parsed language tags from the RFC1766 Content-Language, 
@@ -437,6 +267,10 @@ public class PropertyBuilder {
         return new ArrayList<>(properties);
     }
 
+    public Properties build() {
+        return new Properties(properties, textualLineCount);
+    }
+
     /**
      * Constructs a <code>String</code> with all attributes
      * in name = value format.
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailboxMessage.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailboxMessage.java
index 32be330..acbe59f 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailboxMessage.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailboxMessage.java
@@ -60,7 +60,7 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage {
         private Integer bodyStartOctet;
         private SharedInputStream content;
         private Flags flags;
-        private PropertyBuilder propertyBuilder;
+        private Properties properties;
         private MailboxId mailboxId;
         private Optional<MessageUid> uid = Optional.empty();
         private Optional<ModSeq> modseq = Optional.empty();
@@ -108,8 +108,13 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage {
             return this;
         }
 
-        public Builder propertyBuilder(PropertyBuilder propertyBuilder) {
-            this.propertyBuilder = propertyBuilder;
+        public Builder properties(PropertyBuilder propertyBuilder) {
+            this.properties = propertyBuilder.build();
+            return this;
+        }
+
+        public Builder properties(Properties properties) {
+            this.properties = properties;
             return this;
         }
 
@@ -130,12 +135,12 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage {
             Preconditions.checkNotNull(bodyStartOctet, "bodyStartOctet is required");
             Preconditions.checkNotNull(content, "content is required");
             Preconditions.checkNotNull(flags, "flags is required");
-            Preconditions.checkNotNull(propertyBuilder, "propertyBuilder is required");
+            Preconditions.checkNotNull(properties, "properties is required");
             Preconditions.checkNotNull(mailboxId, "mailboxId is required");
 
             ImmutableList<MessageAttachmentMetadata> attachments = this.attachments.build();
             SimpleMailboxMessage simpleMailboxMessage = new SimpleMailboxMessage(messageId, internalDate, size,
-                bodyStartOctet, content, flags, propertyBuilder, mailboxId, attachments);
+                bodyStartOctet, content, flags, properties, mailboxId, attachments);
 
             uid.ifPresent(simpleMailboxMessage::setUid);
             modseq.ifPresent(simpleMailboxMessage::setModSeq);
@@ -154,8 +159,6 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage {
     }
 
     public static Builder fromWithoutAttachments(MailboxMessage original) throws MailboxException {
-        PropertyBuilder propertyBuilder = new PropertyBuilder(original.getProperties());
-        propertyBuilder.setTextualLineCount(original.getTextualLineCount());
         return builder()
             .bodyStartOctet(Ints.checkedCast(original.getFullContentOctets() - original.getBodyOctets()))
             .content(copyFullContent(original))
@@ -163,7 +166,7 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage {
             .internalDate(original.getInternalDate())
             .size(original.getFullContentOctets())
             .flags(original.createFlags())
-            .propertyBuilder(propertyBuilder);
+            .properties(original.getProperties());
     }
 
     public static SimpleMailboxMessage copyWithoutAttachments(MailboxId mailboxId, MailboxMessage original) throws MailboxException {
@@ -192,14 +195,13 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage {
 
     public SimpleMailboxMessage(MessageId messageId, Date internalDate, long size, int bodyStartOctet,
             SharedInputStream content, Flags flags,
-            PropertyBuilder propertyBuilder, MailboxId mailboxId, List<MessageAttachmentMetadata> attachments) {
+            Properties properties, MailboxId mailboxId, List<MessageAttachmentMetadata> attachments) {
         super(new SimpleMessage(
                 messageId,
-                content, size, internalDate, propertyBuilder.getSubType(),
-                propertyBuilder.getMediaType(),
+                content, size, internalDate,
                 bodyStartOctet,
-                propertyBuilder.getTextualLineCount(),
-                propertyBuilder.toProperties(),
+                properties.getTextualLineCount(),
+                properties,
                 attachments));
 
             setFlags(flags);
@@ -209,10 +211,10 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage {
 
     public SimpleMailboxMessage(MessageId messageId, Date internalDate, long size, int bodyStartOctet,
                                 SharedInputStream content, Flags flags,
-                                PropertyBuilder propertyBuilder, MailboxId mailboxId) {
+                                Properties properties, MailboxId mailboxId) {
         this(messageId, internalDate, size, bodyStartOctet,
                 content, flags,
-                propertyBuilder, mailboxId, ImmutableList.of());
+                properties, mailboxId, ImmutableList.of());
     }
 
     @Override
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMessage.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMessage.java
index 62a5b86..88356b0 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMessage.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMessage.java
@@ -28,25 +28,20 @@ import javax.mail.internet.SharedInputStream;
 import org.apache.james.mailbox.model.MessageAttachmentMetadata;
 import org.apache.james.mailbox.model.MessageId;
 import org.apache.james.mailbox.store.mail.model.Message;
-import org.apache.james.mailbox.store.mail.model.Property;
 
 public class SimpleMessage implements Message {
 
     private final MessageId messageId;
-    private final String subType;
-    private final String mediaType;
     private final SharedInputStream content;
     private final int bodyStartOctet;
     private final Date internalDate;
     private final long size;
     private final Long textualLineCount;
-    private final List<Property> properties;
+    private final Properties properties;
     private final List<MessageAttachmentMetadata> attachments;
 
-    public SimpleMessage(MessageId messageId, SharedInputStream content, long size, Date internalDate, String subType, String mediaType, int bodyStartOctet, Long textualLineCount, List<Property> properties, List<MessageAttachmentMetadata> attachments) {
+    public SimpleMessage(MessageId messageId, SharedInputStream content, long size, Date internalDate, int bodyStartOctet, Long textualLineCount, Properties properties, List<MessageAttachmentMetadata> attachments) {
         this.messageId = messageId;
-        this.subType = subType;
-        this.mediaType = mediaType;
         this.content = content;
         this.bodyStartOctet = bodyStartOctet;
         this.internalDate = internalDate;
@@ -73,12 +68,12 @@ public class SimpleMessage implements Message {
 
     @Override
     public String getMediaType() {
-        return mediaType;
+        return properties.getMediaType();
     }
 
     @Override
     public String getSubType() {
-        return subType;
+        return properties.getSubType();
     }
 
     @Override
@@ -116,7 +111,7 @@ public class SimpleMessage implements Message {
     }
 
     @Override
-    public List<Property> getProperties() {
+    public Properties getProperties() {
         return properties;
     }
 
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageBuilder.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageBuilder.java
index 9e593c5..065202f 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageBuilder.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageBuilder.java
@@ -81,7 +81,7 @@ public class MessageBuilder {
     public MailboxMessage build(MessageId messageId) throws Exception {
         byte[] headerContent = getHeaderContent();
         SimpleMailboxMessage mailboxMessage = new SimpleMailboxMessage(messageId, internalDate, size, headerContent.length,
-            new SharedByteArrayInputStream(Bytes.concat(headerContent, body)), flags, new PropertyBuilder(), mailboxId, NO_ATTACHMENTS);
+            new SharedByteArrayInputStream(Bytes.concat(headerContent, body)), flags, new PropertyBuilder().build(), mailboxId, NO_ATTACHMENTS);
         mailboxMessage.setUid(uid);
         return mailboxMessage;
     }
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageIdManagerTestSystem.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageIdManagerTestSystem.java
index 24ccc3c..7728ed3 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageIdManagerTestSystem.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageIdManagerTestSystem.java
@@ -114,7 +114,7 @@ public class MessageIdManagerTestSystem {
 
     private static MailboxMessage createMessage(MailboxId mailboxId, Flags flags, MessageId messageId, MessageUid uid) {
         MailboxMessage mailboxMessage = new SimpleMailboxMessage(messageId, new Date(), MESSAGE_CONTENT.length, 1256,
-            new SharedByteArrayInputStream(MESSAGE_CONTENT), flags, new PropertyBuilder(), mailboxId);
+            new SharedByteArrayInputStream(MESSAGE_CONTENT), flags, new PropertyBuilder().build(), mailboxId);
         mailboxMessage.setModSeq(MOD_SEQ);
         mailboxMessage.setUid(uid);
         return mailboxMessage;
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/StoreMailboxMessageResultIteratorTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/StoreMailboxMessageResultIteratorTest.java
index c20c807..d608338 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/StoreMailboxMessageResultIteratorTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/StoreMailboxMessageResultIteratorTest.java
@@ -98,7 +98,7 @@ class StoreMailboxMessageResultIteratorTest {
 
         private SimpleMailboxMessage createMessage(MessageUid uid) {
             SimpleMailboxMessage message = new SimpleMailboxMessage(new DefaultMessageId(), null, 0, 0, new SharedByteArrayInputStream(
-                    "".getBytes()), new Flags(), new PropertyBuilder(), TestId.of(1L));
+                    "".getBytes()), new Flags(), new PropertyBuilder().build(), TestId.of(1L));
             message.setUid(uid);
             return message;
         }
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/ListMessageAssertTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/ListMessageAssertTest.java
index 99318de..6d369bc 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/ListMessageAssertTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/ListMessageAssertTest.java
@@ -90,7 +90,7 @@ class ListMessageAssertTest {
     private MailboxMessage createMailboxMessage(MailboxId mailboxId, MessageId messageId, MessageUid uid, Date internalDate,
                                                 String content, int bodyStart, PropertyBuilder propertyBuilder) {
         SimpleMailboxMessage simpleMailboxMessage = new SimpleMailboxMessage(messageId, internalDate, content.length(),
-            bodyStart, new SharedByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)), new Flags(), propertyBuilder, mailboxId);
+            bodyStart, new SharedByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)), new Flags(), propertyBuilder.build(), mailboxId);
 
         simpleMailboxMessage.setUid(uid);
         simpleMailboxMessage.setModSeq(ModSeq.first());
@@ -103,7 +103,7 @@ class ListMessageAssertTest {
 
     private MailboxMessage createMessage(Mailbox mailbox, MessageId messageId, String content, int bodyStart, PropertyBuilder propertyBuilder) {
         SimpleMailboxMessage simpleMailboxMessage = new SimpleMailboxMessage(messageId, INTERNAL_DATE, content.length(),
-            bodyStart, new SharedByteArrayInputStream(content.getBytes()), new Flags(), propertyBuilder, mailbox.getMailboxId());
+            bodyStart, new SharedByteArrayInputStream(content.getBytes()), new Flags(), propertyBuilder.build(), mailbox.getMailboxId());
 
         simpleMailboxMessage.setUid(MESSAGE_UID);
         return simpleMailboxMessage;
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/ListMessagePropertiesAssertTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/ListMessagePropertiesAssertTest.java
index beef849..249283d 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/ListMessagePropertiesAssertTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/ListMessagePropertiesAssertTest.java
@@ -31,7 +31,7 @@ import com.google.common.collect.ImmutableList;
 
 class ListMessagePropertiesAssertTest {
     static final String OTHER_VALUE = "US-ASCII";
-    static final String OTHER_LOCAL_NAME = StandardNames.MIME_CONTENT_TYPE_PARAMETER_CHARSET_NAME;
+    static final String OTHER_LOCAL_NAME = StandardNames.MIME_CONTENT_MD5_NAME;
     static final String OTHER_NAMESPACE = StandardNames.MIME_CONTENT_TYPE_PARAMETER_SPACE;
     static final String VALUE = "7bit";
     static final String LOCAL_NAME = StandardNames.MIME_CONTENT_TRANSFER_ENCODING_NAME;
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MailboxMessageAssertTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MailboxMessageAssertTest.java
index 703eae0..02b0c8b 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MailboxMessageAssertTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MailboxMessageAssertTest.java
@@ -48,11 +48,11 @@ class MailboxMessageAssertTest {
         Date date = new Date();
 
         SimpleMailboxMessage message1 = new SimpleMailboxMessage(MESSAGE_ID, date, headerString.length() + bodyString.length(),
-            headerString.length(), new SharedByteArrayInputStream((headerString + bodyString).getBytes()), new Flags(), new PropertyBuilder(), MAILBOX_ID);
+            headerString.length(), new SharedByteArrayInputStream((headerString + bodyString).getBytes()), new Flags(), new PropertyBuilder().build(), MAILBOX_ID);
         message1.setUid(UID);
 
         SimpleMailboxMessage message2 = new SimpleMailboxMessage(MESSAGE_ID, date, headerString.length() + bodyString.length(),
-            headerString.length(), new SharedByteArrayInputStream((headerString + bodyString).getBytes()), new Flags(), new PropertyBuilder(), MAILBOX_ID);
+            headerString.length(), new SharedByteArrayInputStream((headerString + bodyString).getBytes()), new Flags(), new PropertyBuilder().build(), MAILBOX_ID);
         message2.setUid(UID);
 
         MessageAssert.assertThat(message1).isEqualTo(message2, MessageMapper.FetchType.Full);
@@ -65,12 +65,12 @@ class MailboxMessageAssertTest {
         Date date = new Date();
 
         SimpleMailboxMessage message1 = new SimpleMailboxMessage(MESSAGE_ID, date, headerString.length() + bodyString.length(),
-            headerString.length(), new SharedByteArrayInputStream((headerString + bodyString).getBytes()), new Flags(), new PropertyBuilder(), MAILBOX_ID);
+            headerString.length(), new SharedByteArrayInputStream((headerString + bodyString).getBytes()), new Flags(), new PropertyBuilder().build(), MAILBOX_ID);
         message1.setUid(UID);
 
         bodyString = "work\n.\n";
         SimpleMailboxMessage message2 = new SimpleMailboxMessage(MESSAGE_ID, date, headerString.length() + bodyString.length(),
-            headerString.length(), new SharedByteArrayInputStream((headerString + bodyString).getBytes()), new Flags(), new PropertyBuilder(), MAILBOX_ID);
+            headerString.length(), new SharedByteArrayInputStream((headerString + bodyString).getBytes()), new Flags(), new PropertyBuilder().build(), MAILBOX_ID);
         message2.setUid(UID);
 
         MessageAssert.assertThat(message1).isEqualTo(message2, MessageMapper.FetchType.Headers);
@@ -83,12 +83,12 @@ class MailboxMessageAssertTest {
         Date date = new Date();
 
         SimpleMailboxMessage message1 = new SimpleMailboxMessage(MESSAGE_ID, date, headerString.length() + bodyString.length(),
-            headerString.length(), new SharedByteArrayInputStream((headerString + bodyString).getBytes()), new Flags(), new PropertyBuilder(), MAILBOX_ID);
+            headerString.length(), new SharedByteArrayInputStream((headerString + bodyString).getBytes()), new Flags(), new PropertyBuilder().build(), MAILBOX_ID);
         message1.setUid(UID);
 
         bodyString = "work\n.\n";
         SimpleMailboxMessage message2 = new SimpleMailboxMessage(MESSAGE_ID, date, headerString.length() + bodyString.length(),
-            headerString.length(), new SharedByteArrayInputStream((headerString + bodyString).getBytes()), new Flags(), new PropertyBuilder(), MAILBOX_ID);
+            headerString.length(), new SharedByteArrayInputStream((headerString + bodyString).getBytes()), new Flags(), new PropertyBuilder().build(), MAILBOX_ID);
         message2.setUid(UID);
 
         assertThatThrownBy(() -> MessageAssert.assertThat(message1).isEqualTo(message2, MessageMapper.FetchType.Body))
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageIdMapperTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageIdMapperTest.java
index 3abb0c9..6f97388 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageIdMapperTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageIdMapperTest.java
@@ -992,7 +992,7 @@ public abstract class MessageIdMapperTest {
                 bodyStart, 
                 new SharedByteArrayInputStream(content.getBytes()), 
                 new Flags(), 
-                propertyBuilder, 
+                propertyBuilder.build(),
                 mailbox.getMailboxId());
     }
 }
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageMapperTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageMapperTest.java
index 9907c5e..0a59a33 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageMapperTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageMapperTest.java
@@ -800,27 +800,26 @@ public abstract class MessageMapperTest {
         propBuilder.setMediaType("text");
         propBuilder.setSubType("html");
         propBuilder.setTextualLineCount(2L);
-        propBuilder.setProperty(StandardNames.NAMESPACE_RFC_2045, StandardNames.MIME_CONTENT_TRANSFER_ENCODING_NAME, "7bit");
-        propBuilder.setProperty(StandardNames.MIME_CONTENT_TYPE_PARAMETER_SPACE, StandardNames.MIME_CONTENT_TYPE_PARAMETER_CHARSET_NAME, "US-ASCII");
+        propBuilder.setContentTransferEncoding("7bit");
+        propBuilder.setCharset("US-ASCII");
 
         MailboxMessage messageWithProperties = createMessage(benwaWorkMailbox, mapperProvider.generateMessageId(), "Subject: messagePropertiesShouldBeStored \n\nBody\n.\n", BODY_START, propBuilder);
         MessageMetaData messageMetaData = messageMapper.add(benwaInboxMailbox, messageWithProperties);
         MailboxMessage message = messageMapper.findInMailbox(benwaInboxMailbox, MessageRange.one(messageMetaData.getUid()), FetchType.Body, 1).next();
 
-        assertProperties(message.getProperties()).containsOnly(propBuilder.toProperties());
+        assertProperties(message.getProperties().toProperties()).containsOnly(propBuilder.toProperties());
     }
     
     @Test
     void messagePropertiesShouldBeStoredWhenDuplicateEntries() throws Exception {
         PropertyBuilder propBuilder = new PropertyBuilder();
-        propBuilder.setProperty(StandardNames.MIME_CONTENT_LANGUAGE_SPACE, StandardNames.MIME_CONTENT_LANGUAGE_NAME, "us");
-        propBuilder.setProperty(StandardNames.MIME_CONTENT_LANGUAGE_SPACE, StandardNames.MIME_CONTENT_LANGUAGE_NAME, "fr");
+        propBuilder.setContentLanguage(ImmutableList.of("us", "fr"));
 
         MailboxMessage messageWithProperties = createMessage(benwaWorkMailbox, mapperProvider.generateMessageId(), "Subject: messagePropertiesShouldBeStoredWhenDuplicateEntries \n\nBody\n.\n", BODY_START, propBuilder);
         MessageMetaData messageMetaData = messageMapper.add(benwaInboxMailbox, messageWithProperties);
         MailboxMessage message = messageMapper.findInMailbox(benwaInboxMailbox, MessageRange.one(messageMetaData.getUid()), FetchType.Body, 1).next();
 
-        assertProperties(message.getProperties()).containsOnly(propBuilder.toProperties());
+        assertProperties(message.getProperties().toProperties()).containsOnly(propBuilder.toProperties());
     }
 
     @Test
@@ -828,7 +827,7 @@ public abstract class MessageMapperTest {
         MailboxMessage messageWithProperties = createMessage(benwaWorkMailbox, mapperProvider.generateMessageId(), "Subject: messagePropertiesShouldBeStoredWhenNoProperty \n\nBody\n.\n", BODY_START, new PropertyBuilder());
         MessageMetaData messageMetaData = messageMapper.add(benwaInboxMailbox, messageWithProperties);
         MailboxMessage message = messageMapper.findInMailbox(benwaInboxMailbox, MessageRange.one(messageMetaData.getUid()), FetchType.Body, 1).next();
-        assertThat(message.getProperties()).isEmpty();
+        assertThat(message.getProperties().toProperties()).isEmpty();
     }
 
     @Test
@@ -1251,6 +1250,6 @@ public abstract class MessageMapperTest {
     }
     
     private MailboxMessage createMessage(Mailbox mailbox, MessageId messageId, String content, int bodyStart, PropertyBuilder propertyBuilder) {
-        return new SimpleMailboxMessage(messageId, new Date(), content.length(), bodyStart, new SharedByteArrayInputStream(content.getBytes()), new Flags(), propertyBuilder, mailbox.getMailboxId());
+        return new SimpleMailboxMessage(messageId, new Date(), content.length(), bodyStart, new SharedByteArrayInputStream(content.getBytes()), new Flags(), propertyBuilder.build(), mailbox.getMailboxId());
     }
 }
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageMoveTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageMoveTest.java
index a9b2182..d0260f5 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageMoveTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageMoveTest.java
@@ -143,6 +143,6 @@ public abstract class MessageMoveTest {
     }
     
     private MailboxMessage createMessage(Mailbox mailbox, MessageId messageId, String content, int bodyStart, PropertyBuilder propertyBuilder) {
-        return new SimpleMailboxMessage(messageId, new Date(), content.length(), bodyStart, new SharedByteArrayInputStream(content.getBytes()), new Flags(), propertyBuilder, mailbox.getMailboxId());
+        return new SimpleMailboxMessage(messageId, new Date(), content.length(), bodyStart, new SharedByteArrayInputStream(content.getBytes()), new Flags(), propertyBuilder.build(), mailbox.getMailboxId());
     }
 }
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageWithAttachmentMapperTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageWithAttachmentMapperTest.java
index 0c241b6..a08cb2b 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageWithAttachmentMapperTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageWithAttachmentMapperTest.java
@@ -193,10 +193,10 @@ public abstract class MessageWithAttachmentMapperTest {
     }
 
     private SimpleMailboxMessage createMessage(Mailbox mailbox, MessageId messageId, String content, int bodyStart, PropertyBuilder propertyBuilder, List<MessageAttachmentMetadata> attachments) {
-        return new SimpleMailboxMessage(messageId, new Date(), content.length(), bodyStart, new SharedByteArrayInputStream(content.getBytes()), new Flags(), propertyBuilder, mailbox.getMailboxId(), attachments);
+        return new SimpleMailboxMessage(messageId, new Date(), content.length(), bodyStart, new SharedByteArrayInputStream(content.getBytes()), new Flags(), propertyBuilder.build(), mailbox.getMailboxId(), attachments);
     }
 
     private SimpleMailboxMessage createMessage(Mailbox mailbox, MessageId messageId, String content, int bodyStart, PropertyBuilder propertyBuilder) {
-        return new SimpleMailboxMessage(messageId, new Date(), content.length(), bodyStart, new SharedByteArrayInputStream(content.getBytes()), new Flags(), propertyBuilder, mailbox.getMailboxId());
+        return new SimpleMailboxMessage(messageId, new Date(), content.length(), bodyStart, new SharedByteArrayInputStream(content.getBytes()), new Flags(), propertyBuilder.build(), mailbox.getMailboxId());
     }
 }
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MetadataMapAssertTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MetadataMapAssertTest.java
index af057a0..be33351 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MetadataMapAssertTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MetadataMapAssertTest.java
@@ -53,7 +53,7 @@ class MetadataMapAssertTest {
     @BeforeEach
     void setUp() {
         message1 = new SimpleMailboxMessage(MESSAGE_ID, DATE, HEADER_STRING.length() + BODY_STRING.length(),
-            HEADER_STRING.length(), new SharedByteArrayInputStream((HEADER_STRING + BODY_STRING).getBytes()), new Flags(), new PropertyBuilder(), MAILBOX_ID);
+            HEADER_STRING.length(), new SharedByteArrayInputStream((HEADER_STRING + BODY_STRING).getBytes()), new Flags(), new PropertyBuilder().build(), MAILBOX_ID);
         message1.setUid(UID);
         message1.setModSeq(MODSEQ);
     }
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailboxMessageTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailboxMessageTest.java
index 3b7a0d1..c3ed460 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailboxMessageTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailboxMessageTest.java
@@ -114,7 +114,7 @@ class SimpleMailboxMessageTest {
             BODY_START_OCTET,
             CONTENT_STREAM,
             new Flags(),
-            propertyBuilder,
+            propertyBuilder.build(),
             TEST_ID);
 
         SimpleMailboxMessage copy = SimpleMailboxMessage.copy(TestId.of(1337), original);
@@ -135,7 +135,7 @@ class SimpleMailboxMessageTest {
         return new SimpleMailboxMessage(new DefaultMessageId(), Calendar.getInstance().getTime(),
             content.length(), BODY_START_OCTET, new SharedByteArrayInputStream(
                     content.getBytes(MESSAGE_CHARSET)), new Flags(),
-            new PropertyBuilder(), TEST_ID);
+            new PropertyBuilder().build(), TEST_ID);
     }
 
     @Test
@@ -163,7 +163,7 @@ class SimpleMailboxMessageTest {
             .size(SIZE)
             .content(CONTENT_STREAM)
             .flags(new Flags())
-            .propertyBuilder(new PropertyBuilder())
+            .properties(new PropertyBuilder())
             .build();
     }
 
@@ -195,7 +195,7 @@ class SimpleMailboxMessageTest {
             .size(SIZE)
             .content(CONTENT_STREAM)
             .flags(flags)
-            .propertyBuilder(propertyBuilder)
+            .properties(propertyBuilder)
             .addAttachments(ImmutableList.of(messageAttachment))
             .build();
         String messageContent = IOUtils.toString(message.getFullContent(), StandardCharsets.UTF_8);
@@ -208,7 +208,7 @@ class SimpleMailboxMessageTest {
             soft.assertThat(message.getFullContentOctets()).isEqualTo(SIZE);
             soft.assertThat(messageContent).isEqualTo(MESSAGE_CONTENT);
             soft.assertThat(message.createFlags()).isEqualTo(flags);
-            soft.assertThat(message.getProperties()).isEqualTo(propertyBuilder.toProperties());
+            soft.assertThat(message.getProperties().toProperties()).isEqualTo(propertyBuilder.toProperties());
             soft.assertThat(message.getUid()).isEqualTo(uid);
             soft.assertThat(message.getModSeq()).isEqualTo(modseq);
             soft.assertThat(message.getAttachments()).containsOnly(messageAttachment);
@@ -225,7 +225,7 @@ class SimpleMailboxMessageTest {
                 .size(SIZE)
                 .content(CONTENT_STREAM)
                 .flags(new Flags())
-                .propertyBuilder(new PropertyBuilder())
+                .properties(new PropertyBuilder())
                 .build())
             .isInstanceOf(NullPointerException.class);
     }
@@ -240,7 +240,7 @@ class SimpleMailboxMessageTest {
                 .size(SIZE)
                 .content(CONTENT_STREAM)
                 .flags(new Flags())
-                .propertyBuilder(new PropertyBuilder())
+                .properties(new PropertyBuilder())
                 .build())
             .isInstanceOf(NullPointerException.class);
     }
@@ -254,7 +254,7 @@ class SimpleMailboxMessageTest {
                 .size(SIZE)
                 .content(CONTENT_STREAM)
                 .flags(new Flags())
-                .propertyBuilder(new PropertyBuilder())
+                .properties(new PropertyBuilder())
                 .build())
             .isInstanceOf(NullPointerException.class);
     }
@@ -269,7 +269,7 @@ class SimpleMailboxMessageTest {
                 .size(SIZE)
                 .content(CONTENT_STREAM)
                 .flags(new Flags())
-                .propertyBuilder(new PropertyBuilder())
+                .properties(new PropertyBuilder())
                 .build())
             .isInstanceOf(NullPointerException.class);
     }
@@ -284,7 +284,7 @@ class SimpleMailboxMessageTest {
                 .bodyStartOctet(BODY_START_OCTET)
                 .content(CONTENT_STREAM)
                 .flags(new Flags())
-                .propertyBuilder(new PropertyBuilder())
+                .properties(new PropertyBuilder())
                 .build())
             .isInstanceOf(NullPointerException.class);
     }
@@ -299,7 +299,7 @@ class SimpleMailboxMessageTest {
                 .bodyStartOctet(BODY_START_OCTET)
                 .size(SIZE)
                 .flags(new Flags())
-                .propertyBuilder(new PropertyBuilder())
+                .properties(new PropertyBuilder())
                 .build())
             .isInstanceOf(NullPointerException.class);
     }
@@ -314,7 +314,7 @@ class SimpleMailboxMessageTest {
                 .bodyStartOctet(BODY_START_OCTET)
                 .size(SIZE)
                 .content(CONTENT_STREAM)
-                .propertyBuilder(new PropertyBuilder())
+                .properties(new PropertyBuilder())
                 .build())
             .isInstanceOf(NullPointerException.class);
     }
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/utils/ApplicableFlagCalculatorTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/utils/ApplicableFlagCalculatorTest.java
index db11e31..1632ce3 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/utils/ApplicableFlagCalculatorTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/utils/ApplicableFlagCalculatorTest.java
@@ -105,7 +105,7 @@ class ApplicableFlagCalculatorTest {
         int bodyStart = 10;
 
         return new SimpleMailboxMessage(new DefaultMessageId(), new Date(), content.length(), bodyStart,
-            new SharedByteArrayInputStream(content.getBytes()), messageFlags, new PropertyBuilder(), TestId.of(1));
+            new SharedByteArrayInputStream(content.getBytes()), messageFlags, new PropertyBuilder().build(), TestId.of(1));
     }
 
     private Flags getDefaultApplicableFlag() {
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/search/ListeningMessageSearchIndexContract.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/search/ListeningMessageSearchIndexContract.java
index f9ab723..184dfcc 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/search/ListeningMessageSearchIndexContract.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/search/ListeningMessageSearchIndexContract.java
@@ -52,7 +52,7 @@ public interface ListeningMessageSearchIndexContract {
         .internalDate(new Date(1433628000000L))
         .size(SIZE)
         .content(new SharedByteArrayInputStream("message".getBytes(StandardCharsets.UTF_8)))
-        .propertyBuilder(new PropertyBuilder())
+        .properties(new PropertyBuilder())
         .modseq(MOD_SEQ);
     
     ListeningMessageSearchIndex testee();
diff --git a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/MailboxesRoutesTest.java b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/MailboxesRoutesTest.java
index b64e4ab..1bbbbdf 100644
--- a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/MailboxesRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/MailboxesRoutesTest.java
@@ -519,7 +519,7 @@ class MailboxesRoutesTest {
                     .internalDate(new Date(ZonedDateTime.parse("2018-02-15T15:54:02Z").toEpochSecond()))
                     .bodyStartOctet(0)
                     .flags(new Flags("myFlags"))
-                    .propertyBuilder(new PropertyBuilder())
+                    .properties(new PropertyBuilder())
                     .mailboxId(mailboxId)
                     .build();
 
@@ -938,7 +938,7 @@ class MailboxesRoutesTest {
                     .internalDate(new Date(ZonedDateTime.parse("2018-02-15T15:54:02Z").toEpochSecond()))
                     .bodyStartOctet(0)
                     .flags(new Flags("myFlags"))
-                    .propertyBuilder(new PropertyBuilder())
+                    .properties(new PropertyBuilder())
                     .mailboxId(mailboxId)
                     .build();
 
diff --git a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/UserMailboxesRoutesTest.java b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/UserMailboxesRoutesTest.java
index d96f4d8..fbd28e9 100644
--- a/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/UserMailboxesRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-mailbox/src/test/java/org/apache/james/webadmin/routes/UserMailboxesRoutesTest.java
@@ -1529,7 +1529,7 @@ class UserMailboxesRoutesTest {
                     .internalDate(new Date(ZonedDateTime.parse("2018-02-15T15:54:02Z").toEpochSecond()))
                     .bodyStartOctet(0)
                     .flags(new Flags("myFlags"))
-                    .propertyBuilder(new PropertyBuilder())
+                    .properties(new PropertyBuilder())
                     .mailboxId(mailboxId)
                     .build();
 


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


[james-project] 03/11: JAMES-3430 Remove unused boundary property

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

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

commit fcdb50b9afc388b42a96b6f0ddf54869b5d64532
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Oct 20 16:06:14 2020 +0700

    JAMES-3430 Remove unused boundary property
---
 .../james/mailbox/maildir/mail/model/MaildirMessage.java     |  4 ----
 .../org/apache/james/mailbox/store/StoreMessageManager.java  |  4 ----
 .../apache/james/mailbox/store/mail/model/StandardNames.java |  6 ------
 .../james/mailbox/store/mail/model/impl/Properties.java      | 12 ------------
 .../james/mailbox/store/mail/model/impl/PropertyBuilder.java | 11 -----------
 5 files changed, 37 deletions(-)

diff --git a/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/MaildirMessage.java b/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/MaildirMessage.java
index af337cb..9878e06 100644
--- a/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/MaildirMessage.java
+++ b/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/model/MaildirMessage.java
@@ -118,10 +118,6 @@ public class MaildirMessage implements Message {
                 propertyBuilder.setCharset(codeset);
             }
 
-            final String boundary = descriptor.getBoundary();
-            if (boundary != null) {
-                propertyBuilder.setBoundary(boundary);
-            }
             if ("text".equalsIgnoreCase(mediaType)) {
                 long lines = -1;
                 try (CountingInputStream bodyStream = new CountingInputStream(parser.getInputStream())) {
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java
index 53079f4..fb4f79d 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java
@@ -478,10 +478,6 @@ public class StoreMessageManager implements MessageManager {
         } else {
             propertyBuilder.setCharset(codeset);
         }
-        final String boundary = descriptor.getBoundary();
-        if (boundary != null) {
-            propertyBuilder.setBoundary(boundary);
-        }
         return propertyBuilder;
     }
 
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/StandardNames.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/StandardNames.java
index a50eaf5..32e5fee 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/StandardNames.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/StandardNames.java
@@ -94,12 +94,6 @@ public class StandardNames {
     public static final String MIME_CONTENT_TYPE_PARAMETER_CHARSET_NAME = "charset";
     
     /**
-     * Namespace for <a href='http://www.faqs.org/rfcs/rfc2045.html'>MIME</a> Content-Type
-     * "boundary" parameter.
-     */
-    public static final String MIME_CONTENT_TYPE_PARAMETER_BOUNDARY_NAME = "boundary";
-    
-    /**
      * Namespace for <a href='http://www.faqs.org/rfcs/rfc2045.html'>MIME</a> mime type properties.
      * A distinct namespace is required to distinguish these properties from the Content-Type
      * parameters.
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/Properties.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/Properties.java
index b8900a4..52d0406 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/Properties.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/Properties.java
@@ -33,7 +33,6 @@ import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTE
 import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_MD5_SPACE;
 import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_TRANSFER_ENCODING_NAME;
 import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_TRANSFER_ENCODING_SPACE;
-import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_TYPE_PARAMETER_BOUNDARY_NAME;
 import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_TYPE_PARAMETER_CHARSET_NAME;
 import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_TYPE_PARAMETER_SPACE;
 import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_MEDIA_TYPE_NAME;
@@ -219,17 +218,6 @@ public class Properties {
     public String getCharset() {
         return getFirstValue(MIME_CONTENT_TYPE_PARAMETER_SPACE, MIME_CONTENT_TYPE_PARAMETER_CHARSET_NAME);
     }
-
-    
-    /**
-     * Gets the RFC2045 Content-Type "boundary" parameter.
-     * 
-     * @return the RFC2045 Content-Type "boundary" parameter, 
-     * or null if this meta data is not present
-     */
-    public String getBoundary() {
-        return getFirstValue(MIME_CONTENT_TYPE_PARAMETER_SPACE, MIME_CONTENT_TYPE_PARAMETER_BOUNDARY_NAME);
-    }
     
     /**
      * Gets the RFC1766 Content-Language.
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/PropertyBuilder.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/PropertyBuilder.java
index 0abf1ee..74f5409 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/PropertyBuilder.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/PropertyBuilder.java
@@ -33,7 +33,6 @@ import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTE
 import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_MD5_SPACE;
 import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_TRANSFER_ENCODING_NAME;
 import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_TRANSFER_ENCODING_SPACE;
-import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_TYPE_PARAMETER_BOUNDARY_NAME;
 import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_TYPE_PARAMETER_CHARSET_NAME;
 import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_TYPE_PARAMETER_SPACE;
 import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_MEDIA_TYPE_NAME;
@@ -240,16 +239,6 @@ public class PropertyBuilder {
     }
     
     /**
-     * Sets RFC2045 Content-Type "boundary" parameter.
-     * 
-     * @param value the RFC2045 Content-Type "boundary" parameter
-     * or null to remove
-     */
-    public void setBoundary(String value) {
-        setProperty(MIME_CONTENT_TYPE_PARAMETER_SPACE, MIME_CONTENT_TYPE_PARAMETER_BOUNDARY_NAME, value);
-    }
-    
-    /**
      * Sets RFC1766 Content-Language.
      * 
      * @param values list of parsed language tags from the RFC1766 Content-Language, 


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


[james-project] 06/11: JAMES-3430 Charset should be accessed via ContentDispositionParameters

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

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

commit 2284c51005e32894ce4628c4580ebf16e49d311b
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Oct 20 16:15:26 2020 +0700

    JAMES-3430 Charset should be accessed via ContentDispositionParameters
---
 .../james/mailbox/store/mail/model/impl/Properties.java       | 11 -----------
 1 file changed, 11 deletions(-)

diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/Properties.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/Properties.java
index 52d0406..5754b71 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/Properties.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/Properties.java
@@ -33,7 +33,6 @@ import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTE
 import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_MD5_SPACE;
 import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_TRANSFER_ENCODING_NAME;
 import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_TRANSFER_ENCODING_SPACE;
-import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_TYPE_PARAMETER_CHARSET_NAME;
 import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_CONTENT_TYPE_PARAMETER_SPACE;
 import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_MEDIA_TYPE_NAME;
 import static org.apache.james.mailbox.store.mail.model.StandardNames.MIME_MIME_TYPE_SPACE;
@@ -210,16 +209,6 @@ public class Properties {
     }
     
     /**
-     * Gets the RFC2045 Content-Type "charset" parameter.
-     * 
-     * @return the RFC2045 Content-Type "charset" parameter, 
-     * or null if this meta data is not present
-     */
-    public String getCharset() {
-        return getFirstValue(MIME_CONTENT_TYPE_PARAMETER_SPACE, MIME_CONTENT_TYPE_PARAMETER_CHARSET_NAME);
-    }
-    
-    /**
      * Gets the RFC1766 Content-Language.
      * 
      * @return list of parsed langauge tags from the RFC1766 Content-Language, 


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


[james-project] 05/11: JAMES-3430 Use CassandraMessageDAOV3 in mappers

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

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

commit 518e6b27a7fb4dc880ebd722e0c70454baaeb87d
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Oct 20 15:25:05 2020 +0700

    JAMES-3430 Use CassandraMessageDAOV3 in mappers
    
    Fallback to CassandraMessageDAO
---
 .../CassandraMailboxSessionMapperFactory.java      | 10 +++++---
 .../mailbox/cassandra/DeleteMessageListener.java   | 14 +++++++---
 .../cassandra/mail/CassandraMessageDAOV3.java      | 30 +++++++++++-----------
 .../cassandra/mail/CassandraMessageIdMapper.java   |  9 ++++---
 .../cassandra/mail/CassandraMessageMapper.java     | 14 ++++++----
 .../cassandra/CassandraMailboxManagerTest.java     |  6 ++---
 .../CassandraSubscriptionManagerTest.java          |  3 +++
 .../mail/CassandraMessageIdMapperTest.java         |  2 +-
 .../cassandra/mail/CassandraMessageMapperTest.java |  4 +--
 .../tools/indexer/CassandraReIndexerImplTest.java  |  2 +-
 10 files changed, 57 insertions(+), 37 deletions(-)

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 f0e5609..26a8a04 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
@@ -44,6 +44,7 @@ import org.apache.james.mailbox.cassandra.mail.CassandraMailboxPathV2DAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraMailboxPathV3DAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraMailboxRecentsDAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraMessageDAO;
+import org.apache.james.mailbox.cassandra.mail.CassandraMessageDAOV3;
 import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdDAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdMapper;
 import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdToImapUidDAO;
@@ -75,6 +76,7 @@ public class CassandraMailboxSessionMapperFactory extends MailboxSessionMapperFa
     private final CassandraUidProvider uidProvider;
     private final CassandraModSeqProvider modSeqProvider;
     private final CassandraMessageDAO messageDAO;
+    private final CassandraMessageDAOV3 messageDAOV3;
     private final CassandraMessageIdDAO messageIdDAO;
     private final CassandraMessageIdToImapUidDAO imapUidDAO;
     private final CassandraMailboxCounterDAO mailboxCounterDAO;
@@ -101,7 +103,7 @@ public class CassandraMailboxSessionMapperFactory extends MailboxSessionMapperFa
     @Inject
     public CassandraMailboxSessionMapperFactory(CassandraUidProvider uidProvider, CassandraModSeqProvider modSeqProvider, Session session,
                                                 CassandraMessageDAO messageDAO,
-                                                CassandraMessageIdDAO messageIdDAO, CassandraMessageIdToImapUidDAO imapUidDAO,
+                                                CassandraMessageDAOV3 messageDAOV3, CassandraMessageIdDAO messageIdDAO, CassandraMessageIdToImapUidDAO imapUidDAO,
                                                 CassandraMailboxCounterDAO mailboxCounterDAO, CassandraMailboxRecentsDAO mailboxRecentsDAO, CassandraMailboxDAO mailboxDAO,
                                                 CassandraMailboxPathDAOImpl mailboxPathDAO, CassandraMailboxPathV2DAO mailboxPathV2DAO, CassandraMailboxPathV3DAO mailboxPathV3DAO, CassandraFirstUnseenDAO firstUnseenDAO, CassandraApplicableFlagDAO applicableFlagDAO,
                                                 CassandraAttachmentDAOV2 attachmentDAOV2, CassandraDeletedMessageDAO deletedMessageDAO,
@@ -115,6 +117,7 @@ public class CassandraMailboxSessionMapperFactory extends MailboxSessionMapperFa
         this.modSeqProvider = modSeqProvider;
         this.session = session;
         this.messageDAO = messageDAO;
+        this.messageDAOV3 = messageDAOV3;
         this.messageIdDAO = messageIdDAO;
         this.imapUidDAO = imapUidDAO;
         this.mailboxCounterDAO = mailboxCounterDAO;
@@ -151,6 +154,7 @@ public class CassandraMailboxSessionMapperFactory extends MailboxSessionMapperFa
                                           modSeqProvider,
                                           createAttachmentMapper(mailboxSession),
                                           messageDAO,
+                                          messageDAOV3,
                                           messageIdDAO,
                                           imapUidDAO,
                                           mailboxCounterDAO,
@@ -166,7 +170,7 @@ public class CassandraMailboxSessionMapperFactory extends MailboxSessionMapperFa
     public MessageIdMapper createMessageIdMapper(MailboxSession mailboxSession) {
         return new CassandraMessageIdMapper(getMailboxMapper(mailboxSession), mailboxDAO,
                 createAttachmentMapper(mailboxSession),
-                imapUidDAO, messageIdDAO, messageDAO, indexTableHandler, modSeqProvider,
+                imapUidDAO, messageIdDAO, messageDAO, messageDAOV3, indexTableHandler, modSeqProvider,
                 cassandraConfiguration);
     }
 
@@ -211,7 +215,7 @@ public class CassandraMailboxSessionMapperFactory extends MailboxSessionMapperFa
     }
 
     public DeleteMessageListener deleteMessageListener() {
-        return new DeleteMessageListener(imapUidDAO, messageIdDAO, messageDAO, attachmentDAOV2, ownerDAO,
+        return new DeleteMessageListener(imapUidDAO, messageIdDAO, messageDAO, messageDAOV3, attachmentDAOV2, ownerDAO,
             attachmentMessageIdDAO, aclMapper, userMailboxRightsDAO, applicableFlagDAO, firstUnseenDAO, deletedMessageDAO,
             mailboxCounterDAO, mailboxRecentsDAO, blobStore);
     }
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/DeleteMessageListener.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/DeleteMessageListener.java
index fa70727..fc37174 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/DeleteMessageListener.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/DeleteMessageListener.java
@@ -40,6 +40,7 @@ import org.apache.james.mailbox.cassandra.mail.CassandraFirstUnseenDAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraMailboxCounterDAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraMailboxRecentsDAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraMessageDAO;
+import org.apache.james.mailbox.cassandra.mail.CassandraMessageDAOV3;
 import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdDAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdToImapUidDAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraUserMailboxRightsDAO;
@@ -79,6 +80,7 @@ public class DeleteMessageListener implements MailboxListener.GroupMailboxListen
     private final CassandraMessageIdToImapUidDAO imapUidDAO;
     private final CassandraMessageIdDAO messageIdDAO;
     private final CassandraMessageDAO messageDAO;
+    private final CassandraMessageDAOV3 messageDAOV3;
     private final CassandraAttachmentDAOV2 attachmentDAO;
     private final CassandraAttachmentOwnerDAO ownerDAO;
     private final CassandraAttachmentMessageIdDAO attachmentMessageIdDAO;
@@ -93,13 +95,14 @@ public class DeleteMessageListener implements MailboxListener.GroupMailboxListen
 
     @Inject
     public DeleteMessageListener(CassandraMessageIdToImapUidDAO imapUidDAO, CassandraMessageIdDAO messageIdDAO, CassandraMessageDAO messageDAO,
-                                 CassandraAttachmentDAOV2 attachmentDAO, CassandraAttachmentOwnerDAO ownerDAO,
+                                 CassandraMessageDAOV3 messageDAOV3, CassandraAttachmentDAOV2 attachmentDAO, CassandraAttachmentOwnerDAO ownerDAO,
                                  CassandraAttachmentMessageIdDAO attachmentMessageIdDAO, CassandraACLMapper aclMapper,
                                  CassandraUserMailboxRightsDAO rightsDAO, CassandraApplicableFlagDAO applicableFlagDAO,
                                  CassandraFirstUnseenDAO firstUnseenDAO, CassandraDeletedMessageDAO deletedMessageDAO, CassandraMailboxCounterDAO counterDAO, CassandraMailboxRecentsDAO recentsDAO, BlobStore blobStore) {
         this.imapUidDAO = imapUidDAO;
         this.messageIdDAO = messageIdDAO;
         this.messageDAO = messageDAO;
+        this.messageDAOV3 = messageDAOV3;
         this.attachmentDAO = attachmentDAO;
         this.ownerDAO = ownerDAO;
         this.attachmentMessageIdDAO = attachmentMessageIdDAO;
@@ -180,7 +183,8 @@ public class DeleteMessageListener implements MailboxListener.GroupMailboxListen
                 .flatMap(message -> deleteUnreferencedAttachments(message).thenReturn(message))
                 .flatMap(this::deleteMessageBlobs)
                 .flatMap(this::deleteAttachmentMessageIds)
-                .then(messageDAO.delete(messageId)));
+                .then(messageDAO.delete(messageId))
+                .then(messageDAOV3.delete(messageId)));
     }
 
     private Mono<Void> handleMessageDeletionAsPartOfMailboxDeletion(CassandraMessageId messageId, CassandraId excludedId) {
@@ -190,7 +194,8 @@ public class DeleteMessageListener implements MailboxListener.GroupMailboxListen
                 .flatMap(message -> deleteUnreferencedAttachments(message).thenReturn(message))
                 .flatMap(this::deleteMessageBlobs)
                 .flatMap(this::deleteAttachmentMessageIds)
-                .then(messageDAO.delete(messageId)));
+                .then(messageDAO.delete(messageId))
+                .then(messageDAOV3.delete(messageId)));
     }
 
     private Mono<MessageRepresentation> deleteMessageBlobs(MessageRepresentation message) {
@@ -202,7 +207,8 @@ public class DeleteMessageListener implements MailboxListener.GroupMailboxListen
     }
 
     private Mono<MessageRepresentation> readMessage(CassandraMessageId id) {
-        return messageDAO.retrieveMessage(id, MessageMapper.FetchType.Metadata);
+        return messageDAOV3.retrieveMessage(id, MessageMapper.FetchType.Metadata)
+            .switchIfEmpty(messageDAO.retrieveMessage(id, MessageMapper.FetchType.Metadata));
     }
 
     private Mono<Void> deleteUnreferencedAttachments(MessageRepresentation message) {
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3.java
index dc1b1c0..2c6b5ae 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3.java
@@ -70,6 +70,7 @@ import org.apache.james.mailbox.model.ComposedMessageIdWithMetaData;
 import org.apache.james.mailbox.model.MessageAttachmentMetadata;
 import org.apache.james.mailbox.store.mail.MessageMapper.FetchType;
 import org.apache.james.mailbox.store.mail.model.MailboxMessage;
+import org.apache.james.mailbox.store.mail.model.impl.Properties;
 import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder;
 
 import com.datastax.driver.core.BoundStatement;
@@ -174,7 +175,6 @@ public class CassandraMessageDAOV3 {
 
     private BoundStatement boundWriteStatement(MailboxMessage message, Tuple2<BlobId, BlobId> pair) {
         CassandraMessageId messageId = (CassandraMessageId) message.getMessageId();
-        PropertyBuilder propertyBuilder = new PropertyBuilder(message.getProperties());
         return insert.bind()
             .setUUID(MESSAGE_ID, messageId.get())
             .setTimestamp(INTERNAL_DATE, message.getInternalDate())
@@ -184,17 +184,17 @@ public class CassandraMessageDAOV3 {
             .setString(BODY_CONTENT, pair.getT2().asString())
             .setString(HEADER_CONTENT, pair.getT1().asString())
             .setLong(TEXTUAL_LINE_COUNT, Optional.ofNullable(message.getTextualLineCount()).orElse(DEFAULT_LONG_VALUE))
-            .setString(CONTENT_DESCRIPTION, propertyBuilder.getContentDescription())
-            .setString(CONTENT_DISPOSITION_TYPE, propertyBuilder.getContentDispositionType())
-            .setString(MEDIA_TYPE, propertyBuilder.getMediaType())
-            .setString(SUB_TYPE, propertyBuilder.getSubType())
-            .setString(CONTENT_ID, propertyBuilder.getContentID())
-            .setString(CONTENT_MD5, propertyBuilder.getContentMD5())
-            .setString(CONTENT_TRANSFER_ENCODING, propertyBuilder.getContentTransferEncoding())
-            .setString(CONTENT_LOCATION, propertyBuilder.getContentLocation())
-            .setList(CONTENT_LANGUAGE, propertyBuilder.getContentLanguage())
-            .setMap(CONTENT_DISPOSITION_PARAMETERS, propertyBuilder.getContentDispositionParameters())
-            .setMap(CONTENT_TYPE_PARAMETERS, propertyBuilder.getContentTypeParameters())
+            .setString(CONTENT_DESCRIPTION, message.getProperties().getContentDescription())
+            .setString(CONTENT_DISPOSITION_TYPE, message.getProperties().getContentDispositionType())
+            .setString(MEDIA_TYPE, message.getProperties().getMediaType())
+            .setString(SUB_TYPE, message.getProperties().getSubType())
+            .setString(CONTENT_ID, message.getProperties().getContentID())
+            .setString(CONTENT_MD5, message.getProperties().getContentMD5())
+            .setString(CONTENT_TRANSFER_ENCODING, message.getProperties().getContentTransferEncoding())
+            .setString(CONTENT_LOCATION, message.getProperties().getContentLocation())
+            .setList(CONTENT_LANGUAGE, message.getProperties().getContentLanguage())
+            .setMap(CONTENT_DISPOSITION_PARAMETERS, message.getProperties().getContentDispositionParameters())
+            .setMap(CONTENT_TYPE_PARAMETERS, message.getProperties().getContentTypeParameters())
             .setList(ATTACHMENTS, buildAttachmentUdt(message));
     }
 
@@ -251,13 +251,13 @@ public class CassandraMessageDAOV3 {
                 row.getLong(FULL_CONTENT_OCTETS),
                 row.getInt(BODY_START_OCTET),
                 new SharedByteArrayInputStream(content),
-                getPropertyBuilder(row),
+                getProperties(row),
                 getAttachments(row).collect(Guavate.toImmutableList()),
                 headerId,
                 bodyId));
     }
 
-    private PropertyBuilder getPropertyBuilder(Row row) {
+    private Properties getProperties(Row row) {
         PropertyBuilder property = new PropertyBuilder();
         property.setContentDescription(row.getString(CONTENT_DESCRIPTION));
         property.setContentDispositionType(row.getString(CONTENT_DISPOSITION_TYPE));
@@ -271,7 +271,7 @@ public class CassandraMessageDAOV3 {
         property.setContentDispositionParameters(row.getMap(CONTENT_DISPOSITION_PARAMETERS, String.class, String.class));
         property.setContentTypeParameters(row.getMap(CONTENT_TYPE_PARAMETERS, String.class, String.class));
         property.setTextualLineCount(row.getLong(TEXTUAL_LINE_COUNT));
-        return property;
+        return property.build();
     }
 
     private Stream<MessageAttachmentRepresentation> getAttachments(Row row) {
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapper.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapper.java
index 7c09008..e9cc5eb 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapper.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapper.java
@@ -72,6 +72,7 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
     private final CassandraMessageIdToImapUidDAO imapUidDAO;
     private final CassandraMessageIdDAO messageIdDAO;
     private final CassandraMessageDAO messageDAO;
+    private final CassandraMessageDAOV3 messageDAOV3;
     private final CassandraIndexTableHandler indexTableHandler;
     private final ModSeqProvider modSeqProvider;
     private final AttachmentLoader attachmentLoader;
@@ -79,7 +80,7 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
 
     public CassandraMessageIdMapper(MailboxMapper mailboxMapper, CassandraMailboxDAO mailboxDAO, CassandraAttachmentMapper attachmentMapper,
                                     CassandraMessageIdToImapUidDAO imapUidDAO, CassandraMessageIdDAO messageIdDAO,
-                                    CassandraMessageDAO messageDAO, CassandraIndexTableHandler indexTableHandler,
+                                    CassandraMessageDAO messageDAO, CassandraMessageDAOV3 messageDAOV3, CassandraIndexTableHandler indexTableHandler,
                                     ModSeqProvider modSeqProvider, CassandraConfiguration cassandraConfiguration) {
 
         this.mailboxMapper = mailboxMapper;
@@ -87,6 +88,7 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
         this.imapUidDAO = imapUidDAO;
         this.messageIdDAO = messageIdDAO;
         this.messageDAO = messageDAO;
+        this.messageDAOV3 = messageDAOV3;
         this.indexTableHandler = indexTableHandler;
         this.modSeqProvider = modSeqProvider;
         this.attachmentLoader = new AttachmentLoader(attachmentMapper);
@@ -104,7 +106,8 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
     public Flux<MailboxMessage> findReactive(Collection<MessageId> messageIds, FetchType fetchType) {
         return Flux.fromIterable(messageIds)
             .flatMap(messageId -> imapUidDAO.retrieve((CassandraMessageId) messageId, Optional.empty()), cassandraConfiguration.getMessageReadChunkSize())
-            .flatMap(composedMessageId -> messageDAO.retrieveMessage(composedMessageId, fetchType)
+            .flatMap(composedMessageId -> messageDAOV3.retrieveMessage(composedMessageId, fetchType)
+                .switchIfEmpty(messageDAO.retrieveMessage(composedMessageId, fetchType))
                 .map(messageRepresentation -> Pair.of(composedMessageId, messageRepresentation)), cassandraConfiguration.getMessageReadChunkSize())
             .flatMap(messageRepresentation -> attachmentLoader.addAttachmentToMessage(messageRepresentation, fetchType), cassandraConfiguration.getMessageReadChunkSize())
             .groupBy(MailboxMessage::getMailboxId)
@@ -142,7 +145,7 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
         CassandraId mailboxId = (CassandraId) mailboxMessage.getMailboxId();
         MailboxReactorUtils.block(mailboxMapper.findMailboxById(mailboxId)
             .switchIfEmpty(Mono.error(() -> new MailboxNotFoundException(mailboxId)))
-            .then(messageDAO.save(mailboxMessage))
+            .then(messageDAOV3.save(mailboxMessage))
             .thenEmpty(saveMessageMetadata(mailboxMessage, mailboxId)));
     }
 
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
index df29d11..84841ac 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapper.java
@@ -78,6 +78,7 @@ public class CassandraMessageMapper implements MessageMapper {
     private final CassandraModSeqProvider modSeqProvider;
     private final CassandraUidProvider uidProvider;
     private final CassandraMessageDAO messageDAO;
+    private final CassandraMessageDAOV3 messageDAOV3;
     private final CassandraMessageIdDAO messageIdDAO;
     private final CassandraMessageIdToImapUidDAO imapUidDAO;
     private final CassandraMailboxCounterDAO mailboxCounterDAO;
@@ -93,7 +94,7 @@ public class CassandraMessageMapper implements MessageMapper {
 
     public CassandraMessageMapper(CassandraUidProvider uidProvider, CassandraModSeqProvider modSeqProvider,
                                   CassandraAttachmentMapper attachmentMapper,
-                                  CassandraMessageDAO messageDAO, CassandraMessageIdDAO messageIdDAO,
+                                  CassandraMessageDAO messageDAO, CassandraMessageDAOV3 messageDAOV3, CassandraMessageIdDAO messageIdDAO,
                                   CassandraMessageIdToImapUidDAO imapUidDAO, CassandraMailboxCounterDAO mailboxCounterDAO,
                                   CassandraMailboxRecentsDAO mailboxRecentDAO, CassandraApplicableFlagDAO applicableFlagDAO,
                                   CassandraIndexTableHandler indexTableHandler, CassandraFirstUnseenDAO firstUnseenDAO,
@@ -102,6 +103,7 @@ public class CassandraMessageMapper implements MessageMapper {
         this.uidProvider = uidProvider;
         this.modSeqProvider = modSeqProvider;
         this.messageDAO = messageDAO;
+        this.messageDAOV3 = messageDAOV3;
         this.messageIdDAO = messageIdDAO;
         this.imapUidDAO = imapUidDAO;
         this.mailboxCounterDAO = mailboxCounterDAO;
@@ -229,7 +231,8 @@ public class CassandraMessageMapper implements MessageMapper {
     }
 
     private Mono<MailboxMessage> retrieveMessage(ComposedMessageIdWithMetaData messageId, FetchType fetchType) {
-        return messageDAO.retrieveMessage(messageId, fetchType)
+        return messageDAOV3.retrieveMessage(messageId, fetchType)
+            .switchIfEmpty(messageDAO.retrieveMessage(messageId, fetchType))
             .flatMap(messageRepresentation -> attachmentLoader.addAttachmentToMessage(Pair.of(messageId, messageRepresentation), fetchType));
     }
 
@@ -274,8 +277,9 @@ public class CassandraMessageMapper implements MessageMapper {
 
     private Mono<SimpleMailboxMessage> expungeOne(ComposedMessageIdWithMetaData metaData) {
         return delete(metaData)
-            .then(messageDAO.retrieveMessage(metaData, FetchType.Metadata)
-                .map(pair -> pair.toMailboxMessage(metaData, ImmutableList.of())));
+            .then(messageDAOV3.retrieveMessage(metaData, FetchType.Metadata)
+                .switchIfEmpty(messageDAO.retrieveMessage(metaData, FetchType.Metadata)))
+            .map(pair -> pair.toMailboxMessage(metaData, ImmutableList.of()));
     }
 
     @Override
@@ -446,7 +450,7 @@ public class CassandraMessageMapper implements MessageMapper {
 
     private Mono<Void> save(Mailbox mailbox, MailboxMessage message) throws MailboxException {
         CassandraId mailboxId = (CassandraId) mailbox.getMailboxId();
-        return messageDAO.save(message)
+        return messageDAOV3.save(message)
             .thenEmpty(insertIds(message, mailboxId));
     }
 
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxManagerTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxManagerTest.java
index 83e024e..5552bda 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxManagerTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxManagerTest.java
@@ -53,7 +53,7 @@ import org.apache.james.mailbox.cassandra.mail.CassandraDeletedMessageDAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraFirstUnseenDAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraMailboxCounterDAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraMailboxRecentsDAO;
-import org.apache.james.mailbox.cassandra.mail.CassandraMessageDAO;
+import org.apache.james.mailbox.cassandra.mail.CassandraMessageDAOV3;
 import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdDAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdToImapUidDAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraUserMailboxRightsDAO;
@@ -820,8 +820,8 @@ public class CassandraMailboxManagerTest extends MailboxManagerTest<CassandraMai
                 new CassandraMessageId.Factory());
         }
 
-        private CassandraMessageDAO messageDAO(CassandraCluster cassandraCluster) {
-            return new CassandraMessageDAO(
+        private CassandraMessageDAOV3 messageDAO(CassandraCluster cassandraCluster) {
+            return new CassandraMessageDAOV3(
                 cassandraCluster.getConf(),
                 cassandraCluster.getTypesProvider(),
                 mock(BlobStore.class),
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 9919308..d5dd72a 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
@@ -40,6 +40,7 @@ import org.apache.james.mailbox.cassandra.mail.CassandraMailboxPathV2DAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraMailboxPathV3DAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraMailboxRecentsDAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraMessageDAO;
+import org.apache.james.mailbox.cassandra.mail.CassandraMessageDAOV3;
 import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdDAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraMessageIdToImapUidDAO;
 import org.apache.james.mailbox.cassandra.mail.CassandraModSeqProvider;
@@ -70,6 +71,7 @@ class CassandraSubscriptionManagerTest implements SubscriptionManagerContract {
     void setUp() {
         CassandraMessageIdToImapUidDAO imapUidDAO = null;
         CassandraMessageDAO messageDAO = null;
+        CassandraMessageDAOV3 messageDAOV3 = null;
         CassandraMessageIdDAO messageIdDAO = null;
         CassandraMailboxCounterDAO mailboxCounterDAO = null;
         CassandraMailboxRecentsDAO mailboxRecentsDAO = null;
@@ -97,6 +99,7 @@ class CassandraSubscriptionManagerTest implements SubscriptionManagerContract {
                 modSeqProvider,
                 cassandraCluster.getCassandraCluster().getConf(),
                 messageDAO,
+                messageDAOV3,
                 messageIdDAO,
                 imapUidDAO,
                 mailboxCounterDAO,
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapperTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapperTest.java
index fb69a32..d031a38 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapperTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapperTest.java
@@ -122,7 +122,7 @@ class CassandraMessageIdMapperTest extends MessageIdMapperTest {
             cassandra.getConf()
                 .registerScenario(fail()
                     .forever()
-                    .whenQueryStartsWith("INSERT INTO messageV2 (messageId,internalDate,bodyStartOctet,fullContentOctets,bodyOctets,bodyContent,headerContent,properties,textualLineCount,attachments)"));
+                    .whenQueryStartsWith("INSERT INTO messageV3"));
 
             try {
                 message1.setUid(mapperProvider.generateMessageUid());
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapperTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapperTest.java
index 20de2e5..f7a5d7d 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapperTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapperTest.java
@@ -198,7 +198,7 @@ class CassandraMessageMapperTest extends MessageMapperTest {
 
 
             assertThat(statementRecorder.listExecutedStatements(Selector.preparedStatement(
-                "SELECT * FROM messageV2 WHERE messageId=:messageId;")))
+                "SELECT * FROM messageV3 WHERE messageId=:messageId;")))
                 .hasSize(limit);
         }
 
@@ -228,7 +228,7 @@ class CassandraMessageMapperTest extends MessageMapperTest {
             cassandra.getConf()
                 .registerScenario(fail()
                     .forever()
-                    .whenQueryStartsWith("INSERT INTO messageV2 (messageId,internalDate,bodyStartOctet,fullContentOctets,bodyOctets,bodyContent,headerContent,properties,textualLineCount,attachments)"));
+                    .whenQueryStartsWith("INSERT INTO messageV3"));
 
             try {
                 messageMapper.add(benwaInboxMailbox, message1);
diff --git a/mailbox/tools/indexer/src/test/java/org/apache/mailbox/tools/indexer/CassandraReIndexerImplTest.java b/mailbox/tools/indexer/src/test/java/org/apache/mailbox/tools/indexer/CassandraReIndexerImplTest.java
index cbf263e..a2f0c08 100644
--- a/mailbox/tools/indexer/src/test/java/org/apache/mailbox/tools/indexer/CassandraReIndexerImplTest.java
+++ b/mailbox/tools/indexer/src/test/java/org/apache/mailbox/tools/indexer/CassandraReIndexerImplTest.java
@@ -318,7 +318,7 @@ public class CassandraReIndexerImplTest {
             cassandra.getConf()
                 .registerScenario(fail()
                     .forever()
-                    .whenQueryStartsWith("SELECT * FROM messageV2 WHERE messageId=:messageId;"));
+                    .whenQueryStartsWith("SELECT * FROM messageV3 WHERE messageId=:messageId;"));
 
             Task task = reIndexer.reIndex(ReIndexer.RunningOptions.DEFAULT);
             task.run();


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


[james-project] 04/11: JAMES-3430 Implement CassandraMessageDAOV3

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

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

commit 2b66fac96cc262640e895b6dea1f3e0ed2626312
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Oct 20 14:45:11 2020 +0700

    JAMES-3430 Implement CassandraMessageDAOV3
    
    Compared to V2 it offers a better structure for Properties (as column instead of as UDT list)
    resulting in occupied space decrease (1331 bytes per message -> 310 bytes per message on a simple
    benchmark), resulting in execution timing improvments (0.152ms -> 0.126ms at the table level, of
    course we will also benefits from transferring less bytes)
---
 .../cassandra/mail/CassandraMessageDAOV3.java      | 329 +++++++++++++++++++++
 .../cassandra/modules/CassandraMessageModule.java  |  27 ++
 .../cassandra/table/CassandraMessageV3Table.java   |  55 ++++
 .../cassandra/mail/CassandraMessageDAOV3Test.java  | 194 ++++++++++++
 4 files changed, 605 insertions(+)

diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3.java
new file mode 100644
index 0000000..dc1b1c0
--- /dev/null
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3.java
@@ -0,0 +1,329 @@
+/****************************************************************
+ * 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.blob.api.BlobStore.StoragePolicy.LOW_COST;
+import static org.apache.james.blob.api.BlobStore.StoragePolicy.SIZE_BASED;
+import static org.apache.james.mailbox.cassandra.table.CassandraMessageIds.MESSAGE_ID;
+import static org.apache.james.mailbox.cassandra.table.CassandraMessageV3Table.ATTACHMENTS;
+import static org.apache.james.mailbox.cassandra.table.CassandraMessageV3Table.BODY_CONTENT;
+import static org.apache.james.mailbox.cassandra.table.CassandraMessageV3Table.BODY_OCTECTS;
+import static org.apache.james.mailbox.cassandra.table.CassandraMessageV3Table.BODY_START_OCTET;
+import static org.apache.james.mailbox.cassandra.table.CassandraMessageV3Table.FULL_CONTENT_OCTETS;
+import static org.apache.james.mailbox.cassandra.table.CassandraMessageV3Table.HEADER_CONTENT;
+import static org.apache.james.mailbox.cassandra.table.CassandraMessageV3Table.INTERNAL_DATE;
+import static org.apache.james.mailbox.cassandra.table.CassandraMessageV3Table.Properties.CONTENT_DESCRIPTION;
+import static org.apache.james.mailbox.cassandra.table.CassandraMessageV3Table.Properties.CONTENT_DISPOSITION_PARAMETERS;
+import static org.apache.james.mailbox.cassandra.table.CassandraMessageV3Table.Properties.CONTENT_DISPOSITION_TYPE;
+import static org.apache.james.mailbox.cassandra.table.CassandraMessageV3Table.Properties.CONTENT_ID;
+import static org.apache.james.mailbox.cassandra.table.CassandraMessageV3Table.Properties.CONTENT_LANGUAGE;
+import static org.apache.james.mailbox.cassandra.table.CassandraMessageV3Table.Properties.CONTENT_LOCATION;
+import static org.apache.james.mailbox.cassandra.table.CassandraMessageV3Table.Properties.CONTENT_MD5;
+import static org.apache.james.mailbox.cassandra.table.CassandraMessageV3Table.Properties.CONTENT_TRANSFER_ENCODING;
+import static org.apache.james.mailbox.cassandra.table.CassandraMessageV3Table.Properties.CONTENT_TYPE_PARAMETERS;
+import static org.apache.james.mailbox.cassandra.table.CassandraMessageV3Table.Properties.MEDIA_TYPE;
+import static org.apache.james.mailbox.cassandra.table.CassandraMessageV3Table.Properties.SUB_TYPE;
+import static org.apache.james.mailbox.cassandra.table.CassandraMessageV3Table.TABLE_NAME;
+import static org.apache.james.mailbox.cassandra.table.CassandraMessageV3Table.TEXTUAL_LINE_COUNT;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import javax.inject.Inject;
+import javax.mail.util.SharedByteArrayInputStream;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.james.backends.cassandra.init.CassandraTypesProvider;
+import org.apache.james.backends.cassandra.init.configuration.CassandraConsistenciesConfiguration;
+import org.apache.james.backends.cassandra.utils.CassandraAsyncExecutor;
+import org.apache.james.blob.api.BlobId;
+import org.apache.james.blob.api.BlobStore;
+import org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
+import org.apache.james.mailbox.cassandra.table.CassandraMessageV3Table.Attachments;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.model.AttachmentId;
+import org.apache.james.mailbox.model.Cid;
+import org.apache.james.mailbox.model.ComposedMessageIdWithMetaData;
+import org.apache.james.mailbox.model.MessageAttachmentMetadata;
+import org.apache.james.mailbox.store.mail.MessageMapper.FetchType;
+import org.apache.james.mailbox.store.mail.model.MailboxMessage;
+import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder;
+
+import com.datastax.driver.core.BoundStatement;
+import com.datastax.driver.core.ConsistencyLevel;
+import com.datastax.driver.core.PreparedStatement;
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.Row;
+import com.datastax.driver.core.Session;
+import com.datastax.driver.core.UDTValue;
+import com.datastax.driver.core.querybuilder.QueryBuilder;
+import com.github.steveash.guavate.Guavate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.primitives.Bytes;
+
+import reactor.core.publisher.Mono;
+import reactor.util.function.Tuple2;
+
+public class CassandraMessageDAOV3 {
+    public static final long DEFAULT_LONG_VALUE = 0L;
+    private static final byte[] EMPTY_BYTE_ARRAY = {};
+
+    private final CassandraAsyncExecutor cassandraAsyncExecutor;
+    private final CassandraTypesProvider typesProvider;
+    private final BlobStore blobStore;
+    private final BlobId.Factory blobIdFactory;
+    private final PreparedStatement insert;
+    private final PreparedStatement delete;
+    private final PreparedStatement select;
+    private final Cid.CidParser cidParser;
+    private final ConsistencyLevel consistencyLevel;
+
+    @Inject
+    public CassandraMessageDAOV3(Session session, CassandraTypesProvider typesProvider, BlobStore blobStore,
+                                 BlobId.Factory blobIdFactory,
+                                 CassandraConsistenciesConfiguration consistenciesConfiguration) {
+        this.cassandraAsyncExecutor = new CassandraAsyncExecutor(session);
+        this.consistencyLevel = consistenciesConfiguration.getRegular();
+        this.typesProvider = typesProvider;
+        this.blobStore = blobStore;
+        this.blobIdFactory = blobIdFactory;
+
+        this.insert = prepareInsert(session);
+        this.delete = prepareDelete(session);
+        this.select = prepareSelect(session);
+        this.cidParser = Cid.parser().relaxed();
+    }
+
+    private PreparedStatement prepareSelect(Session session) {
+        return session.prepare(select()
+            .from(TABLE_NAME)
+            .where(eq(MESSAGE_ID, bindMarker(MESSAGE_ID))));
+    }
+
+    private PreparedStatement prepareInsert(Session session) {
+        return session.prepare(insertInto(TABLE_NAME)
+            .value(MESSAGE_ID, bindMarker(MESSAGE_ID))
+            .value(INTERNAL_DATE, bindMarker(INTERNAL_DATE))
+            .value(BODY_START_OCTET, bindMarker(BODY_START_OCTET))
+            .value(FULL_CONTENT_OCTETS, bindMarker(FULL_CONTENT_OCTETS))
+            .value(BODY_OCTECTS, bindMarker(BODY_OCTECTS))
+            .value(BODY_CONTENT, bindMarker(BODY_CONTENT))
+            .value(HEADER_CONTENT, bindMarker(HEADER_CONTENT))
+            .value(CONTENT_DESCRIPTION, bindMarker(CONTENT_DESCRIPTION))
+            .value(CONTENT_DISPOSITION_TYPE, bindMarker(CONTENT_DISPOSITION_TYPE))
+            .value(MEDIA_TYPE, bindMarker(MEDIA_TYPE))
+            .value(SUB_TYPE, bindMarker(SUB_TYPE))
+            .value(CONTENT_ID, bindMarker(CONTENT_ID))
+            .value(CONTENT_MD5, bindMarker(CONTENT_MD5))
+            .value(CONTENT_TRANSFER_ENCODING, bindMarker(CONTENT_TRANSFER_ENCODING))
+            .value(CONTENT_LOCATION, bindMarker(CONTENT_LOCATION))
+            .value(CONTENT_LANGUAGE, bindMarker(CONTENT_LANGUAGE))
+            .value(CONTENT_DISPOSITION_PARAMETERS, bindMarker(CONTENT_DISPOSITION_PARAMETERS))
+            .value(CONTENT_TYPE_PARAMETERS, bindMarker(CONTENT_TYPE_PARAMETERS))
+            .value(TEXTUAL_LINE_COUNT, bindMarker(TEXTUAL_LINE_COUNT))
+            .value(ATTACHMENTS, bindMarker(ATTACHMENTS)));
+    }
+
+    private PreparedStatement prepareDelete(Session session) {
+        return session.prepare(QueryBuilder.delete()
+            .from(TABLE_NAME)
+            .where(eq(MESSAGE_ID, bindMarker(MESSAGE_ID))));
+    }
+
+    public Mono<Void> save(MailboxMessage message) throws MailboxException {
+        return saveContent(message)
+            .flatMap(pair -> cassandraAsyncExecutor.executeVoid(boundWriteStatement(message, pair)));
+    }
+
+    private Mono<Tuple2<BlobId, BlobId>> saveContent(MailboxMessage message) throws MailboxException {
+        try {
+            byte[] headerContent = IOUtils.toByteArray(message.getHeaderContent());
+            byte[] bodyContent = IOUtils.toByteArray(message.getBodyContent());
+
+            Mono<BlobId> bodyFuture = Mono.from(blobStore.save(blobStore.getDefaultBucketName(), bodyContent, LOW_COST));
+            Mono<BlobId> headerFuture = Mono.from(blobStore.save(blobStore.getDefaultBucketName(), headerContent, SIZE_BASED));
+
+            return headerFuture.zipWith(bodyFuture);
+        } catch (IOException e) {
+            throw new MailboxException("Error saving mail content", e);
+        }
+    }
+
+    private BoundStatement boundWriteStatement(MailboxMessage message, Tuple2<BlobId, BlobId> pair) {
+        CassandraMessageId messageId = (CassandraMessageId) message.getMessageId();
+        PropertyBuilder propertyBuilder = new PropertyBuilder(message.getProperties());
+        return insert.bind()
+            .setUUID(MESSAGE_ID, messageId.get())
+            .setTimestamp(INTERNAL_DATE, message.getInternalDate())
+            .setInt(BODY_START_OCTET, (int) (message.getHeaderOctets()))
+            .setLong(FULL_CONTENT_OCTETS, message.getFullContentOctets())
+            .setLong(BODY_OCTECTS, message.getBodyOctets())
+            .setString(BODY_CONTENT, pair.getT2().asString())
+            .setString(HEADER_CONTENT, pair.getT1().asString())
+            .setLong(TEXTUAL_LINE_COUNT, Optional.ofNullable(message.getTextualLineCount()).orElse(DEFAULT_LONG_VALUE))
+            .setString(CONTENT_DESCRIPTION, propertyBuilder.getContentDescription())
+            .setString(CONTENT_DISPOSITION_TYPE, propertyBuilder.getContentDispositionType())
+            .setString(MEDIA_TYPE, propertyBuilder.getMediaType())
+            .setString(SUB_TYPE, propertyBuilder.getSubType())
+            .setString(CONTENT_ID, propertyBuilder.getContentID())
+            .setString(CONTENT_MD5, propertyBuilder.getContentMD5())
+            .setString(CONTENT_TRANSFER_ENCODING, propertyBuilder.getContentTransferEncoding())
+            .setString(CONTENT_LOCATION, propertyBuilder.getContentLocation())
+            .setList(CONTENT_LANGUAGE, propertyBuilder.getContentLanguage())
+            .setMap(CONTENT_DISPOSITION_PARAMETERS, propertyBuilder.getContentDispositionParameters())
+            .setMap(CONTENT_TYPE_PARAMETERS, propertyBuilder.getContentTypeParameters())
+            .setList(ATTACHMENTS, buildAttachmentUdt(message));
+    }
+
+    private ImmutableList<UDTValue> buildAttachmentUdt(MailboxMessage message) {
+        return message.getAttachments().stream()
+            .map(this::toUDT)
+            .collect(Guavate.toImmutableList());
+    }
+
+    private UDTValue toUDT(MessageAttachmentMetadata messageAttachment) {
+        UDTValue result = typesProvider.getDefinedUserType(ATTACHMENTS)
+            .newValue()
+            .setString(Attachments.ID, messageAttachment.getAttachmentId().getId())
+            .setBool(Attachments.IS_INLINE, messageAttachment.isInline());
+        messageAttachment.getName()
+            .ifPresent(name -> result.setString(Attachments.NAME, name));
+        messageAttachment.getCid()
+            .ifPresent(cid -> result.setString(Attachments.CID, cid.getValue()));
+        return result;
+    }
+
+    public Mono<MessageRepresentation> retrieveMessage(ComposedMessageIdWithMetaData id, FetchType fetchType) {
+        CassandraMessageId cassandraMessageId = (CassandraMessageId) id.getComposedMessageId().getMessageId();
+        return retrieveMessage(cassandraMessageId, fetchType);
+    }
+
+    public Mono<MessageRepresentation> retrieveMessage(CassandraMessageId cassandraMessageId, FetchType fetchType) {
+        return retrieveRow(cassandraMessageId)
+                .flatMap(resultSet -> message(resultSet, cassandraMessageId, fetchType));
+    }
+
+    private Mono<ResultSet> retrieveRow(CassandraMessageId messageId) {
+        return cassandraAsyncExecutor.execute(select
+            .bind()
+            .setUUID(MESSAGE_ID, messageId.get())
+            .setConsistencyLevel(consistencyLevel));
+    }
+
+    private Mono<MessageRepresentation>
+    message(ResultSet rows, CassandraMessageId cassandraMessageId, FetchType fetchType) {
+        if (rows.isExhausted()) {
+            return Mono.empty();
+        }
+
+        Row row = rows.one();
+        BlobId headerId = retrieveBlobId(HEADER_CONTENT, row);
+        BlobId bodyId = retrieveBlobId(BODY_CONTENT, row);
+        int bodyStartOctet = row.getInt(BODY_START_OCTET);
+
+        return buildContentRetriever(fetchType, headerId, bodyId, bodyStartOctet).map(content ->
+            new MessageRepresentation(
+                cassandraMessageId,
+                row.getTimestamp(INTERNAL_DATE),
+                row.getLong(FULL_CONTENT_OCTETS),
+                row.getInt(BODY_START_OCTET),
+                new SharedByteArrayInputStream(content),
+                getPropertyBuilder(row),
+                getAttachments(row).collect(Guavate.toImmutableList()),
+                headerId,
+                bodyId));
+    }
+
+    private PropertyBuilder getPropertyBuilder(Row row) {
+        PropertyBuilder property = new PropertyBuilder();
+        property.setContentDescription(row.getString(CONTENT_DESCRIPTION));
+        property.setContentDispositionType(row.getString(CONTENT_DISPOSITION_TYPE));
+        property.setMediaType(row.getString(MEDIA_TYPE));
+        property.setSubType(row.getString(SUB_TYPE));
+        property.setContentID(row.getString(CONTENT_ID));
+        property.setContentMD5(row.getString(CONTENT_MD5));
+        property.setContentTransferEncoding(row.getString(CONTENT_TRANSFER_ENCODING));
+        property.setContentLocation(row.getString(CONTENT_LOCATION));
+        property.setContentLanguage(row.getList(CONTENT_LANGUAGE, String.class));
+        property.setContentDispositionParameters(row.getMap(CONTENT_DISPOSITION_PARAMETERS, String.class, String.class));
+        property.setContentTypeParameters(row.getMap(CONTENT_TYPE_PARAMETERS, String.class, String.class));
+        property.setTextualLineCount(row.getLong(TEXTUAL_LINE_COUNT));
+        return property;
+    }
+
+    private Stream<MessageAttachmentRepresentation> getAttachments(Row row) {
+        List<UDTValue> udtValues = row.getList(ATTACHMENTS, UDTValue.class);
+        return attachmentByIds(udtValues);
+    }
+
+    private Stream<MessageAttachmentRepresentation> attachmentByIds(List<UDTValue> udtValues) {
+        return udtValues.stream()
+            .map(this::messageAttachmentByIdFrom);
+    }
+
+    private MessageAttachmentRepresentation messageAttachmentByIdFrom(UDTValue udtValue) {
+        return MessageAttachmentRepresentation.builder()
+            .attachmentId(AttachmentId.from(udtValue.getString(Attachments.ID)))
+            .name(udtValue.getString(Attachments.NAME))
+            .cid(cidParser.parse(udtValue.getString(Attachments.CID)))
+            .isInline(udtValue.getBool(Attachments.IS_INLINE))
+            .build();
+    }
+
+    public Mono<Void> delete(CassandraMessageId messageId) {
+        return cassandraAsyncExecutor.executeVoid(delete.bind()
+            .setUUID(MESSAGE_ID, messageId.get()));
+    }
+
+    private Mono<byte[]> buildContentRetriever(FetchType fetchType, BlobId headerId, BlobId bodyId, int bodyStartOctet) {
+        switch (fetchType) {
+            case Full:
+                return getFullContent(headerId, bodyId);
+            case Headers:
+                return getContent(headerId);
+            case Body:
+                return getContent(bodyId)
+                    .map(data -> Bytes.concat(new byte[bodyStartOctet], data));
+            case Metadata:
+                return Mono.just(EMPTY_BYTE_ARRAY);
+            default:
+                throw new RuntimeException("Unknown FetchType " + fetchType);
+        }
+    }
+
+    private Mono<byte[]> getFullContent(BlobId headerId, BlobId bodyId) {
+        return getContent(headerId)
+            .zipWith(getContent(bodyId), Bytes::concat);
+    }
+
+    private Mono<byte[]> getContent(BlobId blobId) {
+        return Mono.from(blobStore.readBytes(blobStore.getDefaultBucketName(), blobId));
+    }
+
+    private BlobId retrieveBlobId(String field, Row row) {
+        return blobIdFactory.from(row.getString(field));
+    }
+}
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraMessageModule.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraMessageModule.java
index 280eee0..e7c36c6 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraMessageModule.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraMessageModule.java
@@ -22,6 +22,8 @@ package org.apache.james.mailbox.cassandra.modules;
 import static com.datastax.driver.core.DataType.bigint;
 import static com.datastax.driver.core.DataType.cboolean;
 import static com.datastax.driver.core.DataType.cint;
+import static com.datastax.driver.core.DataType.frozenList;
+import static com.datastax.driver.core.DataType.frozenMap;
 import static com.datastax.driver.core.DataType.set;
 import static com.datastax.driver.core.DataType.text;
 import static com.datastax.driver.core.DataType.timestamp;
@@ -31,6 +33,7 @@ import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.mailbox.cassandra.table.CassandraMessageIdTable;
 import org.apache.james.mailbox.cassandra.table.CassandraMessageIds;
 import org.apache.james.mailbox.cassandra.table.CassandraMessageV2Table;
+import org.apache.james.mailbox.cassandra.table.CassandraMessageV3Table;
 import org.apache.james.mailbox.cassandra.table.Flag;
 import org.apache.james.mailbox.cassandra.table.MessageIdToImapUid;
 
@@ -94,6 +97,30 @@ public interface CassandraMessageModule {
             .addColumn(CassandraMessageV2Table.HEADER_CONTENT, text())
             .addUDTListColumn(CassandraMessageV2Table.ATTACHMENTS, SchemaBuilder.frozen(CassandraMessageV2Table.ATTACHMENTS))
             .addUDTListColumn(CassandraMessageV2Table.PROPERTIES, SchemaBuilder.frozen(CassandraMessageV2Table.PROPERTIES)))
+        .table(CassandraMessageV3Table.TABLE_NAME)
+        .comment("Holds message metadata, independently of any mailboxes. Content of messages is stored " +
+            "in `blobs` and `blobparts` tables. Optimizes property storage compared to V2.")
+        .statement(statement -> statement
+            .addPartitionKey(CassandraMessageIds.MESSAGE_ID, timeuuid())
+            .addColumn(CassandraMessageV3Table.INTERNAL_DATE, timestamp())
+            .addColumn(CassandraMessageV3Table.BODY_START_OCTET, cint())
+            .addColumn(CassandraMessageV3Table.BODY_OCTECTS, bigint())
+            .addColumn(CassandraMessageV3Table.TEXTUAL_LINE_COUNT, bigint())
+            .addColumn(CassandraMessageV3Table.FULL_CONTENT_OCTETS, bigint())
+            .addColumn(CassandraMessageV3Table.BODY_CONTENT, text())
+            .addColumn(CassandraMessageV3Table.HEADER_CONTENT, text())
+            .addColumn(CassandraMessageV3Table.Properties.CONTENT_DESCRIPTION, text())
+            .addColumn(CassandraMessageV3Table.Properties.CONTENT_DISPOSITION_TYPE, text())
+            .addColumn(CassandraMessageV3Table.Properties.MEDIA_TYPE, text())
+            .addColumn(CassandraMessageV3Table.Properties.SUB_TYPE, text())
+            .addColumn(CassandraMessageV3Table.Properties.CONTENT_ID, text())
+            .addColumn(CassandraMessageV3Table.Properties.CONTENT_MD5, text())
+            .addColumn(CassandraMessageV3Table.Properties.CONTENT_TRANSFER_ENCODING, text())
+            .addColumn(CassandraMessageV3Table.Properties.CONTENT_LOCATION, text())
+            .addColumn(CassandraMessageV3Table.Properties.CONTENT_LANGUAGE, frozenList(text()))
+            .addColumn(CassandraMessageV3Table.Properties.CONTENT_DISPOSITION_PARAMETERS, frozenMap(text(), text()))
+            .addColumn(CassandraMessageV3Table.Properties.CONTENT_TYPE_PARAMETERS, frozenMap(text(), text()))
+            .addUDTListColumn(CassandraMessageV2Table.ATTACHMENTS, SchemaBuilder.frozen(CassandraMessageV3Table.ATTACHMENTS)))
         .type(CassandraMessageV2Table.PROPERTIES)
         .statement(statement -> statement
             .addColumn(CassandraMessageV2Table.Properties.NAMESPACE, text())
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraMessageV3Table.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraMessageV3Table.java
new file mode 100644
index 0000000..b87d074
--- /dev/null
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraMessageV3Table.java
@@ -0,0 +1,55 @@
+/****************************************************************
+ * 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 CassandraMessageV3Table {
+
+    String TABLE_NAME = "messageV3";
+    String INTERNAL_DATE = "internalDate";
+    String BODY_START_OCTET = "bodyStartOctet";
+    String FULL_CONTENT_OCTETS = "fullContentOctets";
+    String BODY_OCTECTS = "bodyOctets";
+    String TEXTUAL_LINE_COUNT = "textualLineCount";
+    String BODY_CONTENT = "bodyContent";
+    String HEADER_CONTENT = "headerContent";
+    String ATTACHMENTS = "attachments";
+
+    interface Properties {
+        String MEDIA_TYPE = "mediaType";
+        String SUB_TYPE = "subType";
+        String CONTENT_ID = "contentId";
+        String CONTENT_LOCATION = "contentLocation";
+        String CONTENT_DESCRIPTION = "contentDescription";
+        String CONTENT_TRANSFER_ENCODING = "contentTransferEncoding";
+        String CONTENT_DISPOSITION_TYPE = "contentDispositionType";
+        String CONTENT_DISPOSITION_PARAMETERS = "contentDispositionParameters";
+        String CONTENT_TYPE_PARAMETERS = "contentTypeParameters";
+        String CONTENT_MD5 = "contentMd5";
+        String CONTENT_LANGUAGE = "contentLanguage";
+    }
+
+    interface Attachments {
+        String ID = "id";
+        String NAME = "name";
+        String CID = "cid";
+        String IS_INLINE = "isInline";
+    }
+
+}
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3Test.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3Test.java
new file mode 100644
index 0000000..ab6e594
--- /dev/null
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3Test.java
@@ -0,0 +1,194 @@
+/****************************************************************
+ * 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.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+import javax.mail.Flags;
+import javax.mail.util.SharedByteArrayInputStream;
+
+import org.apache.commons.io.IOUtils;
+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.versions.CassandraSchemaVersionModule;
+import org.apache.james.blob.api.BlobStore;
+import org.apache.james.blob.api.HashBlobId;
+import org.apache.james.blob.cassandra.CassandraBlobModule;
+import org.apache.james.blob.cassandra.CassandraBlobStoreFactory;
+import org.apache.james.mailbox.MessageUid;
+import org.apache.james.mailbox.ModSeq;
+import org.apache.james.mailbox.cassandra.ids.CassandraId;
+import org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
+import org.apache.james.mailbox.cassandra.modules.CassandraMessageModule;
+import org.apache.james.mailbox.model.ComposedMessageId;
+import org.apache.james.mailbox.model.ComposedMessageIdWithMetaData;
+import org.apache.james.mailbox.model.MessageAttachmentMetadata;
+import org.apache.james.mailbox.model.MessageId;
+import org.apache.james.mailbox.store.mail.MessageMapper;
+import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder;
+import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.primitives.Bytes;
+
+import reactor.core.publisher.Mono;
+
+class CassandraMessageDAOV3Test {
+    private static final int BODY_START = 16;
+    private static final CassandraId MAILBOX_ID = CassandraId.timeBased();
+    private static final String CONTENT = "Subject: Test7 \n\nBody7\n.\n";
+    private static final MessageUid messageUid = MessageUid.of(1);
+    private static final List<MessageAttachmentMetadata> NO_ATTACHMENT = ImmutableList.of();
+
+    public static final CassandraModule MODULES = CassandraModule.aggregateModules(
+            CassandraMessageModule.MODULE,
+            CassandraBlobModule.MODULE,
+            CassandraSchemaVersionModule.MODULE);
+
+    @RegisterExtension
+    static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(
+            MODULES);
+
+    private CassandraMessageDAOV3 testee;
+
+    private SimpleMailboxMessage message;
+    private CassandraMessageId messageId;
+    private ComposedMessageIdWithMetaData messageIdWithMetadata;
+
+    @BeforeEach
+    void setUp(CassandraCluster cassandra) {
+        CassandraMessageId.Factory messageIdFactory = new CassandraMessageId.Factory();
+        messageId = messageIdFactory.generate();
+        BlobStore blobStore = CassandraBlobStoreFactory.forTesting(cassandra.getConf())
+            .passthrough();
+        HashBlobId.Factory blobIdFactory = new HashBlobId.Factory();
+        testee = new CassandraMessageDAOV3(
+            cassandra.getConf(),
+            cassandra.getTypesProvider(),
+            blobStore,
+            blobIdFactory,
+            cassandraCluster.getCassandraConsistenciesConfiguration());
+
+        messageIdWithMetadata = ComposedMessageIdWithMetaData.builder()
+                .composedMessageId(new ComposedMessageId(MAILBOX_ID, messageId, messageUid))
+                .flags(new Flags())
+                .modSeq(ModSeq.of(1))
+                .build();
+    }
+
+    @Test
+    void saveShouldSaveNullValueForTextualLineCountAsZero() throws Exception {
+        message = createMessage(messageId, CONTENT, BODY_START, new PropertyBuilder(), NO_ATTACHMENT);
+
+        testee.save(message).block();
+
+        MessageRepresentation attachmentRepresentation =
+            toMessage(testee.retrieveMessage(messageIdWithMetadata, MessageMapper.FetchType.Metadata));
+
+        assertThat(attachmentRepresentation.getProperties().getTextualLineCount())
+            .isEqualTo(0L);
+    }
+
+    @Test
+    void saveShouldSaveTextualLineCount() throws Exception {
+        long textualLineCount = 10L;
+        PropertyBuilder propertyBuilder = new PropertyBuilder();
+        propertyBuilder.setTextualLineCount(textualLineCount);
+        message = createMessage(messageId, CONTENT, BODY_START, propertyBuilder, NO_ATTACHMENT);
+
+        testee.save(message).block();
+
+        MessageRepresentation attachmentRepresentation =
+            toMessage(testee.retrieveMessage(messageIdWithMetadata, MessageMapper.FetchType.Metadata));
+
+        assertThat(attachmentRepresentation.getProperties().getTextualLineCount()).isEqualTo(textualLineCount);
+    }
+
+    @Test
+    void saveShouldStoreMessageWithFullContent() throws Exception {
+        message = createMessage(messageId, CONTENT, BODY_START, new PropertyBuilder(), NO_ATTACHMENT);
+
+        testee.save(message).block();
+
+        MessageRepresentation attachmentRepresentation =
+            toMessage(testee.retrieveMessage(messageIdWithMetadata, MessageMapper.FetchType.Full));
+
+        assertThat(IOUtils.toString(attachmentRepresentation.getContent(), StandardCharsets.UTF_8))
+            .isEqualTo(CONTENT);
+    }
+
+    @Test
+    void saveShouldStoreMessageWithBodyContent() throws Exception {
+        message = createMessage(messageId, CONTENT, BODY_START, new PropertyBuilder(), NO_ATTACHMENT);
+
+        testee.save(message).block();
+
+        MessageRepresentation attachmentRepresentation =
+            toMessage(testee.retrieveMessage(messageIdWithMetadata, MessageMapper.FetchType.Body));
+
+        byte[] expected = Bytes.concat(
+            new byte[BODY_START],
+            CONTENT.substring(BODY_START).getBytes(StandardCharsets.UTF_8));
+        assertThat(IOUtils.toString(attachmentRepresentation.getContent(), StandardCharsets.UTF_8))
+            .isEqualTo(IOUtils.toString(new ByteArrayInputStream(expected), StandardCharsets.UTF_8));
+    }
+
+    @Test
+    void saveShouldStoreMessageWithHeaderContent() throws Exception {
+        message = createMessage(messageId, CONTENT, BODY_START, new PropertyBuilder(), NO_ATTACHMENT);
+
+        testee.save(message).block();
+
+        MessageRepresentation attachmentRepresentation =
+            toMessage(testee.retrieveMessage(messageIdWithMetadata, MessageMapper.FetchType.Headers));
+
+        assertThat(IOUtils.toString(attachmentRepresentation.getContent(), StandardCharsets.UTF_8))
+            .isEqualTo(CONTENT.substring(0, BODY_START));
+    }
+
+    private SimpleMailboxMessage createMessage(MessageId messageId, String content, int bodyStart, PropertyBuilder propertyBuilder, Collection<MessageAttachmentMetadata> attachments) {
+        return SimpleMailboxMessage.builder()
+            .messageId(messageId)
+            .mailboxId(MAILBOX_ID)
+            .uid(messageUid)
+            .internalDate(new Date())
+            .bodyStartOctet(bodyStart)
+            .size(content.length())
+            .content(new SharedByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)))
+            .flags(new Flags())
+            .properties(propertyBuilder)
+            .addAttachments(attachments)
+            .build();
+    }
+
+    private MessageRepresentation toMessage(Mono<MessageRepresentation> read) {
+        return read.blockOptional()
+            .orElseThrow(() -> new IllegalStateException("Collection is not supposed to be empty"));
+    }
+}


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


[james-project] 11/11: JAMES-3433 Ensure CachedBlobStore is only queried for headers

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

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

commit 920e5759c84b6d858d30a3f41063596c6310cd1b
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Oct 22 08:58:55 2020 +0700

    JAMES-3433 Ensure CachedBlobStore  is only queried for headers
---
 server/blob/blob-cassandra/pom.xml                 |  8 +++--
 .../blob/cassandra/cache/CachedBlobStoreTest.java  | 35 ++++++++++++++++++++++
 2 files changed, 41 insertions(+), 2 deletions(-)

diff --git a/server/blob/blob-cassandra/pom.xml b/server/blob/blob-cassandra/pom.xml
index ba5426f..3a07679 100644
--- a/server/blob/blob-cassandra/pom.xml
+++ b/server/blob/blob-cassandra/pom.xml
@@ -59,8 +59,7 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
-            <artifactId>metrics-tests</artifactId>
-            <scope>test</scope>
+            <artifactId>james-server-mail-store</artifactId>
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
@@ -68,6 +67,11 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>metrics-tests</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>testing-base</artifactId>
             <scope>test</scope>
         </dependency>
diff --git a/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/cache/CachedBlobStoreTest.java b/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/cache/CachedBlobStoreTest.java
index 580f910..1261075 100644
--- a/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/cache/CachedBlobStoreTest.java
+++ b/server/blob/blob-cassandra/src/test/java/org/apache/james/blob/cassandra/cache/CachedBlobStoreTest.java
@@ -34,8 +34,12 @@ import java.io.ByteArrayInputStream;
 import java.nio.charset.StandardCharsets;
 import java.time.Duration;
 
+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.StatementRecorder;
+import org.apache.james.backends.cassandra.StatementRecorder.Selector;
 import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.blob.api.BlobId;
 import org.apache.james.blob.api.BlobStore;
@@ -43,9 +47,13 @@ import org.apache.james.blob.api.BlobStoreContract;
 import org.apache.james.blob.api.BucketName;
 import org.apache.james.blob.api.HashBlobId;
 import org.apache.james.blob.api.ObjectNotFoundException;
+import org.apache.james.blob.api.Store;
 import org.apache.james.blob.api.TestBlobId;
 import org.apache.james.blob.cassandra.CassandraBlobModule;
 import org.apache.james.blob.cassandra.CassandraBlobStoreFactory;
+import org.apache.james.blob.mail.MimeMessagePartsId;
+import org.apache.james.blob.mail.MimeMessageStore;
+import org.apache.james.core.builder.MimeMessageBuilder;
 import org.apache.james.metrics.tests.RecordingMetricFactory;
 import org.assertj.core.api.SoftAssertions;
 import org.junit.jupiter.api.BeforeEach;
@@ -298,6 +306,33 @@ public class CachedBlobStoreTest implements BlobStoreContract {
             .hasSameContentAs(new ByteArrayInputStream(APPROXIMATELY_FIVE_KILOBYTES));
     }
 
+    @Test
+    public void cachedBlobStoreShouldOnlyBeQueriedForHeaders(CassandraCluster cassandra) throws Exception {
+        MimeMessage message = MimeMessageBuilder.mimeMessageBuilder()
+            .addHeader("Date", "Thu, 6 Sep 2018 13:29:13 +0700 (ICT)")
+            .addHeader("Message-ID", "<84...@localhost.localdomain>")
+            .addFrom("any@any.com")
+            .addToRecipient("toddy@any.com")
+            .setSubject("Important Mail")
+            .setText("Important mail content")
+            .build();
+
+        Store<MimeMessage, MimeMessagePartsId> mimeMessageStore = new MimeMessageStore.Factory(testee())
+            .mimeMessageStore();
+        MimeMessagePartsId partsId = mimeMessageStore
+            .save(message)
+            .block();
+
+        StatementRecorder statementRecorder = new StatementRecorder();
+        cassandra.getConf().recordStatements(statementRecorder);
+        cassandra.getConf().printStatements();
+
+        mimeMessageStore.read(partsId).block();
+
+        assertThat(statementRecorder.listExecutedStatements(Selector.preparedStatementStartingWith("SELECT * FROM blob_cache")))
+            .hasSize(1);
+    }
+
     @Nested
     class MetricsTest {
         @Test


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