You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2022/12/12 06:13:49 UTC

[james-project] 06/08: JAMES-3754 Mapper layers should set new saveDate when add/copy/move messages

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

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

commit 746ca874aecfb5ea77679a081a32999471bbbce7
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Thu Dec 1 17:35:58 2022 +0700

    JAMES-3754 Mapper layers should set new saveDate when add/copy/move messages
---
 .../CassandraMailboxSessionMapperFactory.java      |  9 +-
 .../mailbox/cassandra/mail/AttachmentLoader.java   |  5 +-
 .../cassandra/mail/CassandraMessageIdMapper.java   | 10 ++-
 .../cassandra/mail/CassandraMessageMapper.java     | 24 ++++--
 .../cassandra/mail/CassandraMessageMetadata.java   |  5 ++
 .../cassandra/mail/MessageRepresentation.java      |  4 +-
 .../CassandraSubscriptionManagerTest.java          |  5 +-
 .../TestCassandraMailboxSessionMapperFactory.java  | 13 +++
 .../cassandra/mail/CassandraMapperProvider.java    | 14 +++-
 ...andraMessageIdMapperRelaxedConsistencyTest.java | 39 ++++++---
 .../mail/CassandraMessageIdMapperTest.java         | 16 +++-
 ...ssandraMessageMapperRelaxedConsistencyTest.java | 39 ++++++---
 .../cassandra/mail/CassandraMessageMapperTest.java | 16 +++-
 .../mailbox/cassandra/mail/utils/GuiceUtils.java   |  4 +-
 .../model/openjpa/AbstractJPAMailboxMessage.java   |  5 ++
 .../mailbox/jpa/mail/JpaMessageMapperTest.java     | 16 +++-
 .../InMemoryMailboxSessionMapperFactory.java       |  6 +-
 .../inmemory/mail/InMemoryMessageMapper.java       |  7 +-
 .../inmemory/mail/InMemoryMapperProvider.java      |  9 +-
 .../inmemory/mail/InMemoryMessageIdMapperTest.java |  9 +-
 .../inmemory/mail/MemoryMessageMapperTest.java     | 10 ++-
 .../manager/InMemoryIntegrationResources.java      |  4 +-
 .../OpenSearchListeningMessageSearchIndexTest.java |  6 +-
 .../mailbox/store/mail/AbstractMessageMapper.java  |  5 +-
 .../mailbox/store/mail/model/MailboxMessage.java   |  2 +
 .../mail/model/impl/SimpleMailboxMessage.java      |  7 +-
 .../store/mail/model/MessageIdMapperTest.java      | 51 ++++++++++++
 .../store/mail/model/MessageMapperTest.java        | 96 ++++++++++++++++++++++
 28 files changed, 370 insertions(+), 66 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 4e57852f88..22e150af51 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
@@ -19,6 +19,8 @@
 
 package org.apache.james.mailbox.cassandra;
 
+import java.time.Clock;
+
 import javax.inject.Inject;
 
 import org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration;
@@ -106,7 +108,8 @@ public class CassandraMailboxSessionMapperFactory extends MailboxSessionMapperFa
                                                 CassandraUserMailboxRightsDAO userMailboxRightsDAO,
                                                 RecomputeMailboxCountersService recomputeMailboxCountersService,
                                                 CassandraConfiguration cassandraConfiguration,
-                                                BatchSizes batchSizes) {
+                                                BatchSizes batchSizes,
+                                                Clock clock) {
         this.uidProvider = uidProvider;
         this.modSeqProvider = modSeqProvider;
         this.threadDAO = threadDAO;
@@ -150,10 +153,10 @@ public class CassandraMailboxSessionMapperFactory extends MailboxSessionMapperFa
             firstUnseenDAO,
             deletedMessageDAO,
             blobStore,
-            cassandraConfiguration, batchSizes, recomputeMailboxCountersService);
+            cassandraConfiguration, batchSizes, recomputeMailboxCountersService, clock);
         this.cassandraMessageIdMapper = new CassandraMessageIdMapper(cassandraMailboxMapper, mailboxDAO,
             cassandraAttachmentMapper, imapUidDAO, messageIdDAO, messageDAO, messageDAOV3, indexTableHandler,
-            modSeqProvider, blobStore, cassandraConfiguration, batchSizes);
+            modSeqProvider, blobStore, cassandraConfiguration, batchSizes, clock);
         this.cassandraAnnotationMapper = new CassandraAnnotationMapper(session);
     }
 
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/AttachmentLoader.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/AttachmentLoader.java
index 4d1a4f77c8..f37267caa2 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/AttachmentLoader.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/AttachmentLoader.java
@@ -18,7 +18,9 @@
  ****************************************************************/
 package org.apache.james.mailbox.cassandra.mail;
 
+import java.util.Date;
 import java.util.List;
+import java.util.Optional;
 import java.util.stream.Stream;
 
 import org.apache.commons.lang3.tuple.Pair;
@@ -43,9 +45,10 @@ public class AttachmentLoader {
     }
 
     public Mono<CassandraMailboxMessage> addAttachmentToMessage(Pair<ComposedMessageIdWithMetaData, MessageRepresentation> messageRepresentation,
+                                                                Optional<Date> saveDate,
                                                                 MessageMapper.FetchType fetchType) {
         return loadAttachments(messageRepresentation.getRight().getAttachments().stream(), fetchType)
-            .map(attachments -> messageRepresentation.getRight().toMailboxMessage(messageRepresentation.getLeft(), attachments))
+            .map(attachments -> messageRepresentation.getRight().toMailboxMessage(messageRepresentation.getLeft(), attachments, saveDate))
             .map(message -> new CassandraMailboxMessage(message, messageRepresentation.getRight().getHeaderId()));
     }
 
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 39bf53c626..9b2b7dd7af 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
@@ -23,8 +23,10 @@ import static org.apache.james.backends.cassandra.init.configuration.JamesExecut
 import static org.apache.james.blob.api.BlobStore.StoragePolicy.SIZE_BASED;
 import static org.apache.james.util.ReactorUtils.DEFAULT_CONCURRENCY;
 
+import java.time.Clock;
 import java.time.Duration;
 import java.util.Collection;
+import java.util.Date;
 import java.util.List;
 import java.util.Optional;
 import java.util.function.Function;
@@ -90,11 +92,12 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
     private final BlobStore blobStore;
     private final CassandraConfiguration cassandraConfiguration;
     private final BatchSizes batchSizes;
+    private final Clock clock;
 
     public CassandraMessageIdMapper(MailboxMapper mailboxMapper, CassandraMailboxDAO mailboxDAO, CassandraAttachmentMapper attachmentMapper,
                                     CassandraMessageIdToImapUidDAO imapUidDAO, CassandraMessageIdDAO messageIdDAO,
                                     CassandraMessageDAO messageDAO, CassandraMessageDAOV3 messageDAOV3, CassandraIndexTableHandler indexTableHandler,
-                                    ModSeqProvider modSeqProvider, BlobStore blobStore, CassandraConfiguration cassandraConfiguration, BatchSizes batchSizes) {
+                                    ModSeqProvider modSeqProvider, BlobStore blobStore, CassandraConfiguration cassandraConfiguration, BatchSizes batchSizes, Clock clock) {
 
         this.mailboxMapper = mailboxMapper;
         this.mailboxDAO = mailboxDAO;
@@ -108,6 +111,7 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
         this.blobStore = blobStore;
         this.cassandraConfiguration = cassandraConfiguration;
         this.batchSizes = batchSizes;
+        this.clock = clock;
     }
 
     @Override
@@ -138,7 +142,7 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
         return messageDAOV3.retrieveMessage(metadata.getComposedMessageId(), fetchType)
             .switchIfEmpty(Mono.defer(() -> messageDAO.retrieveMessage(metadata.getComposedMessageId(), fetchType)))
             .map(messageRepresentation -> Pair.of(metadata.getComposedMessageId(), messageRepresentation))
-            .flatMap(messageRepresentation -> attachmentLoader.addAttachmentToMessage(messageRepresentation, fetchType));
+            .flatMap(messageRepresentation -> attachmentLoader.addAttachmentToMessage(messageRepresentation, metadata.getSaveDate(), fetchType));
     }
 
     @Override
@@ -187,6 +191,7 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
     @Override
     public void save(MailboxMessage mailboxMessage) throws MailboxException {
         CassandraId mailboxId = (CassandraId) mailboxMessage.getMailboxId();
+        mailboxMessage.setSaveDate(Date.from(clock.instant()));
         MailboxReactorUtils.block(mailboxMapper.findMailboxById(mailboxId)
             .switchIfEmpty(Mono.error(() -> new MailboxNotFoundException(mailboxId)))
             .then(messageDAOV3.save(mailboxMessage))
@@ -200,6 +205,7 @@ public class CassandraMessageIdMapper implements MessageIdMapper {
 
     @Override
     public Mono<Void> copyInMailboxReactive(MailboxMessage mailboxMessage, Mailbox mailbox) {
+        mailboxMessage.setSaveDate(Date.from(clock.instant()));
         CassandraId mailboxId = (CassandraId) mailbox.getMailboxId();
         return insertMetadata(mailboxMessage, mailboxId, CassandraMessageMetadata.from(mailboxMessage)
             .withMailboxId(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 ae473652a4..5f6924ae0b 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
@@ -25,9 +25,11 @@ import static org.apache.james.blob.api.BlobStore.StoragePolicy.SIZE_BASED;
 import static org.apache.james.util.ReactorUtils.DEFAULT_CONCURRENCY;
 
 import java.security.SecureRandom;
+import java.time.Clock;
 import java.time.Duration;
 import java.util.Collection;
 import java.util.Comparator;
+import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -110,6 +112,7 @@ public class CassandraMessageMapper implements MessageMapper {
     private final RecomputeMailboxCountersService recomputeMailboxCountersService;
     private final SecureRandom secureRandom;
     private final int reactorConcurrency;
+    private final Clock clock;
 
     public CassandraMessageMapper(UidProvider uidProvider, ModSeqProvider modSeqProvider,
                                   CassandraAttachmentMapper attachmentMapper,
@@ -118,7 +121,7 @@ public class CassandraMessageMapper implements MessageMapper {
                                   CassandraMailboxRecentsDAO mailboxRecentDAO, CassandraApplicableFlagDAO applicableFlagDAO,
                                   CassandraIndexTableHandler indexTableHandler, CassandraFirstUnseenDAO firstUnseenDAO,
                                   CassandraDeletedMessageDAO deletedMessageDAO, BlobStore blobStore, CassandraConfiguration cassandraConfiguration,
-                                  BatchSizes batchSizes, RecomputeMailboxCountersService recomputeMailboxCountersService) {
+                                  BatchSizes batchSizes, RecomputeMailboxCountersService recomputeMailboxCountersService, Clock clock) {
         this.uidProvider = uidProvider;
         this.modSeqProvider = modSeqProvider;
         this.messageDAO = messageDAO;
@@ -138,6 +141,7 @@ public class CassandraMessageMapper implements MessageMapper {
         this.recomputeMailboxCountersService = recomputeMailboxCountersService;
         this.secureRandom = new SecureRandom();
         this.reactorConcurrency = evaluateReactorConcurrency();
+        this.clock = clock;
     }
 
     @Override
@@ -273,7 +277,7 @@ public class CassandraMessageMapper implements MessageMapper {
         return messageDAOV3.retrieveMessage(metadata.getComposedMessageId(), fetchType)
             .switchIfEmpty(Mono.defer(() -> messageDAO.retrieveMessage(metadata.getComposedMessageId(), fetchType)))
             .map(messageRepresentation -> Pair.of(metadata.getComposedMessageId(), messageRepresentation))
-            .flatMap(messageRepresentation -> attachmentLoader.addAttachmentToMessage(messageRepresentation, fetchType));
+            .flatMap(messageRepresentation -> attachmentLoader.addAttachmentToMessage(messageRepresentation, metadata.getSaveDate(), fetchType));
     }
 
     @Override
@@ -333,18 +337,17 @@ public class CassandraMessageMapper implements MessageMapper {
 
         return Flux.fromIterable(MessageRange.toRanges(uids))
             .concatMap(range -> messageIdDAO.retrieveMessages(mailboxId, range, Limit.unlimited()))
-            .map(CassandraMessageMetadata::getComposedMessageId)
-            .flatMap(this::expungeOne, cassandraConfiguration.getExpungeChunkSize())
+            .flatMap(cassandraMessageMetadata -> expungeOne(cassandraMessageMetadata.getComposedMessageId(), cassandraMessageMetadata.getSaveDate()), cassandraConfiguration.getExpungeChunkSize())
             .collect(ImmutableMap.toImmutableMap(MailboxMessage::getUid, MailboxMessage::metaData))
             .flatMap(messageMap -> indexTableHandler.updateIndexOnDelete(mailboxId, messageMap.values())
                 .thenReturn(messageMap));
     }
 
-    private Mono<SimpleMailboxMessage> expungeOne(ComposedMessageIdWithMetaData metaData) {
+    private Mono<SimpleMailboxMessage> expungeOne(ComposedMessageIdWithMetaData metaData, Optional<Date> saveDate) {
         return delete(metaData)
             .then(messageDAOV3.retrieveMessage(metaData, FetchType.METADATA)
                 .switchIfEmpty(Mono.defer(() -> messageDAO.retrieveMessage(metaData, FetchType.METADATA))))
-            .map(pair -> pair.toMailboxMessage(metaData, ImmutableList.of()));
+            .map(pair -> pair.toMailboxMessage(metaData, ImmutableList.of(), saveDate));
     }
 
     @Override
@@ -401,14 +404,14 @@ public class CassandraMessageMapper implements MessageMapper {
     public Mono<MessageMetaData> addReactive(Mailbox mailbox, MailboxMessage message) {
         CassandraId mailboxId = (CassandraId) mailbox.getMailboxId();
 
-        return addUidAndModseq(message, mailboxId)
+        return addUidAndModseqAndSaveDate(message, mailboxId)
             .flatMap(Throwing.function((MailboxMessage messageWithUidAndModSeq) ->
                 save(mailbox, messageWithUidAndModSeq)
                     .thenReturn(messageWithUidAndModSeq)))
             .map(MailboxMessage::metaData);
     }
 
-    private Mono<MailboxMessage> addUidAndModseq(MailboxMessage message, CassandraId mailboxId) {
+    private Mono<MailboxMessage> addUidAndModseqAndSaveDate(MailboxMessage message, CassandraId mailboxId) {
         Mono<MessageUid> messageUidMono = uidProvider
             .nextUidReactive(mailboxId)
             .switchIfEmpty(Mono.error(() -> new MailboxException("Can not find a UID to save " + message.getMessageId() + " in " + mailboxId)));
@@ -420,6 +423,7 @@ public class CassandraMessageMapper implements MessageMapper {
                 .doOnNext(tuple -> {
                     message.setUid(tuple.getT1());
                     message.setModSeq(tuple.getT2());
+                    message.setSaveDate(Date.from(clock.instant()));
                 })
                 .thenReturn(message);
     }
@@ -545,6 +549,7 @@ public class CassandraMessageMapper implements MessageMapper {
     @Override
     public Mono<MessageMetaData> copyReactive(Mailbox mailbox, MailboxMessage original) {
         original.setFlags(new FlagsBuilder().add(original.createFlags()).add(Flag.RECENT).build());
+        original.setSaveDate(Date.from(clock.instant()));
         return setInMailboxReactive(mailbox, original);
     }
 
@@ -556,6 +561,7 @@ public class CassandraMessageMapper implements MessageMapper {
         return setMessagesInMailboxReactive(mailbox, originals.stream()
             .map(original -> {
                 original.setFlags(new FlagsBuilder().add(original.createFlags()).add(Flag.RECENT).build());
+                original.setSaveDate(Date.from(clock.instant()));
                 return original;
             }).collect(ImmutableList.toImmutableList()))
             .collectList();
@@ -584,7 +590,7 @@ public class CassandraMessageMapper implements MessageMapper {
 
     private Mono<MessageMetaData> setInMailboxReactive(Mailbox mailbox, MailboxMessage message) {
         CassandraId mailboxId = (CassandraId) mailbox.getMailboxId();
-        return addUidAndModseq(message, mailboxId)
+        return addUidAndModseqAndSaveDate(message, mailboxId)
             .flatMap(messageWithUidAndModseq ->
                 insertMetadata(messageWithUidAndModseq, mailboxId,
                     CassandraMessageMetadata.from(messageWithUidAndModseq)
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMetadata.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMetadata.java
index 0e34163bf5..ebc9436315 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMetadata.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMetadata.java
@@ -203,6 +203,11 @@ public class CassandraMessageMetadata {
             delegate.setFlags(flags);
         }
 
+        @Override
+        public void setSaveDate(Date saveDate) {
+            delegate.setSaveDate(saveDate);
+        }
+
         @Override
         public Flags createFlags() {
             return delegate.createFlags();
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 39cada4b95..025e1b2bf9 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
@@ -21,6 +21,7 @@ package org.apache.james.mailbox.cassandra.mail;
 
 import java.util.Date;
 import java.util.List;
+import java.util.Optional;
 
 import org.apache.james.blob.api.BlobId;
 import org.apache.james.mailbox.model.ComposedMessageIdWithMetaData;
@@ -54,7 +55,7 @@ public class MessageRepresentation {
         this.bodyId = bodyId;
     }
 
-    public SimpleMailboxMessage toMailboxMessage(ComposedMessageIdWithMetaData metadata, List<MessageAttachmentMetadata> attachments) {
+    public SimpleMailboxMessage toMailboxMessage(ComposedMessageIdWithMetaData metadata, List<MessageAttachmentMetadata> attachments, Optional<Date> saveDate) {
         return SimpleMailboxMessage.builder()
             .messageId(messageId)
             .threadId(metadata.getThreadId())
@@ -62,6 +63,7 @@ public class MessageRepresentation {
             .uid(metadata.getComposedMessageId().getUid())
             .modseq(metadata.getModSeq())
             .internalDate(internalDate)
+            .saveDate(saveDate)
             .bodyStartOctet(bodyStartOctet)
             .size(size)
             .content(content)
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 2c6a90d691..e51dc31081 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
@@ -21,6 +21,8 @@ package org.apache.james.mailbox.cassandra;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
+import java.time.Clock;
+
 import org.apache.james.backends.cassandra.CassandraClusterExtension;
 import org.apache.james.backends.cassandra.components.CassandraModule;
 import org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration;
@@ -136,7 +138,8 @@ class CassandraSubscriptionManagerTest implements SubscriptionManagerContract {
             userMailboxRightsDAO,
             recomputeMailboxCountersService,
             CassandraConfiguration.DEFAULT_CONFIGURATION,
-            BatchSizes.defaultValues());
+            BatchSizes.defaultValues(),
+            Clock.systemUTC());
 
         InVMEventBus eventBus = new InVMEventBus(new InVmEventDelivery(new RecordingMetricFactory()), EventBusTestFixture.RETRY_BACKOFF_CONFIGURATION, new MemoryEventDeadLetters());
 
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/TestCassandraMailboxSessionMapperFactory.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/TestCassandraMailboxSessionMapperFactory.java
index a1e3d7976f..a4cd1f124d 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/TestCassandraMailboxSessionMapperFactory.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/TestCassandraMailboxSessionMapperFactory.java
@@ -19,11 +19,14 @@
 
 package org.apache.james.mailbox.cassandra;
 
+import java.time.Clock;
+
 import org.apache.james.backends.cassandra.CassandraCluster;
 import org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration;
 import org.apache.james.mailbox.cassandra.ids.CassandraMessageId;
 import org.apache.james.mailbox.cassandra.mail.utils.GuiceUtils;
 import org.apache.james.mailbox.store.BatchSizes;
+import org.apache.james.utils.UpdatableTickingClock;
 
 import com.google.inject.Guice;
 import com.google.inject.util.Modules;
@@ -42,6 +45,16 @@ public class TestCassandraMailboxSessionMapperFactory {
             .getInstance(CassandraMailboxSessionMapperFactory.class);
     }
 
+    public static CassandraMailboxSessionMapperFactory forTests(CassandraCluster cassandra,
+                                                                CassandraMessageId.Factory factory,
+                                                                CassandraConfiguration cassandraConfiguration,
+                                                                UpdatableTickingClock updatableTickingClock) {
+
+        return Guice.createInjector(Modules.override(GuiceUtils.commonModules(cassandra.getConf(), cassandra.getTypesProvider(), factory, cassandraConfiguration))
+                .with(binder -> binder.bind(Clock.class).toInstance(updatableTickingClock)))
+            .getInstance(CassandraMailboxSessionMapperFactory.class);
+    }
+
     public static CassandraMailboxSessionMapperFactory forTests(CassandraCluster cassandra,
                                                                 CassandraMessageId.Factory factory,
                                                                 CassandraConfiguration cassandraConfiguration,
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMapperProvider.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMapperProvider.java
index 145fb709b8..e3e7cd9f2b 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMapperProvider.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMapperProvider.java
@@ -18,6 +18,7 @@
  ****************************************************************/
 package org.apache.james.mailbox.cassandra.mail;
 
+import java.time.Instant;
 import java.util.List;
 
 import org.apache.james.backends.cassandra.CassandraCluster;
@@ -41,6 +42,7 @@ import org.apache.james.mailbox.store.mail.MessageIdMapper;
 import org.apache.james.mailbox.store.mail.MessageMapper;
 import org.apache.james.mailbox.store.mail.model.MapperProvider;
 import org.apache.james.mailbox.store.mail.model.MessageUidProvider;
+import org.apache.james.utils.UpdatableTickingClock;
 
 import com.google.common.collect.ImmutableList;
 
@@ -51,6 +53,7 @@ public class CassandraMapperProvider implements MapperProvider {
     private final CassandraCluster cassandra;
     private final MessageUidProvider messageUidProvider;
     private final CassandraModSeqProvider cassandraModSeqProvider;
+    private final UpdatableTickingClock updatableTickingClock;
     private final MailboxSession mailboxSession = MailboxSessionUtil.create(Username.of("benwa"));
     private CassandraMailboxSessionMapperFactory mapperFactory;
 
@@ -61,7 +64,8 @@ public class CassandraMapperProvider implements MapperProvider {
         cassandraModSeqProvider = new CassandraModSeqProvider(
                 this.cassandra.getConf(),
                 cassandraConfiguration);
-        mapperFactory = createMapperFactory(cassandraConfiguration);
+        updatableTickingClock = new UpdatableTickingClock(Instant.now());
+        mapperFactory = createMapperFactory(cassandraConfiguration, updatableTickingClock);
     }
 
     @Override
@@ -84,10 +88,11 @@ public class CassandraMapperProvider implements MapperProvider {
         return mapperFactory.getMessageIdMapper(mailboxSession);
     }
 
-    private CassandraMailboxSessionMapperFactory createMapperFactory(CassandraConfiguration cassandraConfiguration) {
+    private CassandraMailboxSessionMapperFactory createMapperFactory(CassandraConfiguration cassandraConfiguration, UpdatableTickingClock updatableTickingClock) {
         return TestCassandraMailboxSessionMapperFactory.forTests(cassandra,
             new CassandraMessageId.Factory(),
-            cassandraConfiguration);
+            cassandraConfiguration,
+            updatableTickingClock);
     }
 
     @Override
@@ -125,4 +130,7 @@ public class CassandraMapperProvider implements MapperProvider {
         return cassandraModSeqProvider.highestModSeq(mailbox);
     }
 
+    public UpdatableTickingClock getUpdatableTickingClock() {
+        return updatableTickingClock;
+    }
 }
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapperRelaxedConsistencyTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapperRelaxedConsistencyTest.java
index 5a00c78bc2..5380a66fa9 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapperRelaxedConsistencyTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdMapperRelaxedConsistencyTest.java
@@ -22,6 +22,7 @@ package org.apache.james.mailbox.cassandra.mail;
 import org.apache.james.backends.cassandra.CassandraClusterExtension;
 import org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration;
 import org.apache.james.mailbox.store.mail.model.MessageIdMapperTest;
+import org.apache.james.utils.UpdatableTickingClock;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.extension.RegisterExtension;
@@ -32,27 +33,41 @@ class CassandraMessageIdMapperRelaxedConsistencyTest {
 
     @Nested
     class WeakReadConsistency extends MessageIdMapperTest {
+        private final CassandraMapperProvider mapperProvider = new CassandraMapperProvider(
+            cassandraCluster.getCassandraCluster(),
+            CassandraConfiguration.builder()
+                .messageReadStrongConsistency(false)
+                .messageWriteStrongConsistency(true)
+                .build());
+
         @Override
         protected CassandraMapperProvider provideMapper() {
-            return new CassandraMapperProvider(
-                cassandraCluster.getCassandraCluster(),
-                CassandraConfiguration.builder()
-                    .messageReadStrongConsistency(false)
-                    .messageWriteStrongConsistency(true)
-                    .build());
+            return mapperProvider;
+        }
+
+        @Override
+        protected UpdatableTickingClock updatableTickingClock() {
+            return mapperProvider.getUpdatableTickingClock();
         }
     }
 
     @Nested
     class WeakWriteConsistency extends MessageIdMapperTest {
+        private final CassandraMapperProvider mapperProvider = new CassandraMapperProvider(
+            cassandraCluster.getCassandraCluster(),
+            CassandraConfiguration.builder()
+                .messageReadStrongConsistency(false)
+                .messageWriteStrongConsistency(false)
+                .build());
+
         @Override
         protected CassandraMapperProvider provideMapper() {
-            return new CassandraMapperProvider(
-                cassandraCluster.getCassandraCluster(),
-                CassandraConfiguration.builder()
-                    .messageReadStrongConsistency(false)
-                    .messageWriteStrongConsistency(false)
-                    .build());
+            return mapperProvider;
+        }
+
+        @Override
+        protected UpdatableTickingClock updatableTickingClock() {
+            return mapperProvider.getUpdatableTickingClock();
         }
 
         @Disabled("JAMES-3435 Without strong consistency flags update is not thread safe as long as it follows a read-before-write pattern")
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 958be20af9..4af4d7785b 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
@@ -47,6 +47,7 @@ import org.apache.james.mailbox.store.mail.MessageMapper;
 import org.apache.james.mailbox.store.mail.model.MailboxMessage;
 import org.apache.james.mailbox.store.mail.model.MessageIdMapperTest;
 import org.apache.james.util.streams.Limit;
+import org.apache.james.utils.UpdatableTickingClock;
 import org.assertj.core.api.SoftAssertions;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Tag;
@@ -61,12 +62,19 @@ class CassandraMessageIdMapperTest extends MessageIdMapperTest {
 
     @RegisterExtension
     static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(MailboxAggregateModule.MODULE);
-    
+
+    private final CassandraMapperProvider mapperProvider = new CassandraMapperProvider(
+        cassandraCluster.getCassandraCluster(),
+        CassandraConfiguration.DEFAULT_CONFIGURATION);
+
     @Override
     protected CassandraMapperProvider provideMapper() {
-        return new CassandraMapperProvider(
-            cassandraCluster.getCassandraCluster(),
-            CassandraConfiguration.DEFAULT_CONFIGURATION);
+        return mapperProvider;
+    }
+
+    @Override
+    protected UpdatableTickingClock updatableTickingClock() {
+        return mapperProvider.getUpdatableTickingClock();
     }
 
     @Test
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapperRelaxedConsistencyTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapperRelaxedConsistencyTest.java
index 4028772cec..f6d65f6c00 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapperRelaxedConsistencyTest.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageMapperRelaxedConsistencyTest.java
@@ -23,6 +23,7 @@ import org.apache.james.backends.cassandra.CassandraClusterExtension;
 import org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration;
 import org.apache.james.mailbox.store.mail.model.MapperProvider;
 import org.apache.james.mailbox.store.mail.model.MessageMapperTest;
+import org.apache.james.utils.UpdatableTickingClock;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.extension.RegisterExtension;
@@ -33,27 +34,41 @@ class CassandraMessageMapperRelaxedConsistencyTest {
 
     @Nested
     class WeakReadConsistency extends MessageMapperTest {
+        private final CassandraMapperProvider cassandraMapperProvider = new CassandraMapperProvider(
+            cassandraCluster.getCassandraCluster(),
+            CassandraConfiguration.builder()
+                .messageReadStrongConsistency(false)
+                .messageWriteStrongConsistency(true)
+                .build());
+
         @Override
         protected MapperProvider createMapperProvider() {
-            return new CassandraMapperProvider(
-                cassandraCluster.getCassandraCluster(),
-                CassandraConfiguration.builder()
-                    .messageReadStrongConsistency(false)
-                    .messageWriteStrongConsistency(true)
-                    .build());
+            return cassandraMapperProvider;
+        }
+
+        @Override
+        protected UpdatableTickingClock updatableTickingClock() {
+            return cassandraMapperProvider.getUpdatableTickingClock();
         }
     }
 
     @Nested
     class WeakWriteConsistency extends MessageMapperTest {
+        private final CassandraMapperProvider cassandraMapperProvider = new CassandraMapperProvider(
+            cassandraCluster.getCassandraCluster(),
+            CassandraConfiguration.builder()
+                .messageReadStrongConsistency(false)
+                .messageWriteStrongConsistency(false)
+                .build());
+
         @Override
         protected MapperProvider createMapperProvider() {
-            return new CassandraMapperProvider(
-                cassandraCluster.getCassandraCluster(),
-                CassandraConfiguration.builder()
-                    .messageReadStrongConsistency(false)
-                    .messageWriteStrongConsistency(false)
-                    .build());
+            return cassandraMapperProvider;
+        }
+
+        @Override
+        protected UpdatableTickingClock updatableTickingClock() {
+            return cassandraMapperProvider.getUpdatableTickingClock();
         }
 
         @Disabled("JAMES-3435 Without strong consistency flags update is not thread safe as long as it follows a read-before-write pattern")
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 ec84ffb1fe..dfc02de3ac 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
@@ -46,6 +46,7 @@ import org.apache.james.mailbox.store.mail.model.MailboxMessage;
 import org.apache.james.mailbox.store.mail.model.MapperProvider;
 import org.apache.james.mailbox.store.mail.model.MessageMapperTest;
 import org.apache.james.util.streams.Limit;
+import org.apache.james.utils.UpdatableTickingClock;
 import org.assertj.core.api.SoftAssertions;
 import org.awaitility.Awaitility;
 import org.junit.jupiter.api.Nested;
@@ -58,12 +59,19 @@ import com.google.common.collect.ImmutableList;
 class CassandraMessageMapperTest extends MessageMapperTest {
     @RegisterExtension
     static CassandraClusterExtension cassandraCluster = new CassandraClusterExtension(MailboxAggregateModule.MODULE);
-    
+
+    private final CassandraMapperProvider cassandraMapperProvider = new CassandraMapperProvider(
+        cassandraCluster.getCassandraCluster(),
+        CassandraConfiguration.DEFAULT_CONFIGURATION);
+
     @Override
     protected MapperProvider createMapperProvider() {
-        return new CassandraMapperProvider(
-            cassandraCluster.getCassandraCluster(),
-            CassandraConfiguration.DEFAULT_CONFIGURATION);
+        return cassandraMapperProvider;
+    }
+
+    @Override
+    protected UpdatableTickingClock updatableTickingClock() {
+        return cassandraMapperProvider.getUpdatableTickingClock();
     }
 
     @Nested
diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/GuiceUtils.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/GuiceUtils.java
index a6dd9b3773..ed06c2c280 100644
--- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/GuiceUtils.java
+++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/utils/GuiceUtils.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.mailbox.cassandra.mail.utils;
 
+import java.time.Clock;
 import java.util.Set;
 
 import org.apache.james.backends.cassandra.CassandraCluster;
@@ -94,6 +95,7 @@ public class GuiceUtils {
             binder -> Multibinder.newSetBinder(binder, new TypeLiteral<EventDTOModule<? extends Event, ? extends EventDTO>>() {}),
             binder -> binder.bind(EventStore.class).to(CassandraEventStore.class),
             binder -> binder.bind(CassandraTypesProvider.class).toInstance(typesProvider),
-            binder -> binder.bind(CassandraConfiguration.class).toInstance(configuration));
+            binder -> binder.bind(CassandraConfiguration.class).toInstance(configuration),
+            binder -> binder.bind(Clock.class).toInstance(Clock.systemUTC()));
     }
 }
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 879171416d..d8c40546db 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
@@ -435,6 +435,11 @@ public abstract class AbstractJPAMailboxMessage implements MailboxMessage {
         this.uid = uid.asLong();
     }
 
+    @Override
+    public void setSaveDate(Date saveDate) {
+
+    }
+
     @Override
     public long getHeaderOctets() {
         return bodyStartOctet;
diff --git a/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/mail/JpaMessageMapperTest.java b/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/mail/JpaMessageMapperTest.java
index 1e5db354e0..6a9c7055dd 100644
--- a/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/mail/JpaMessageMapperTest.java
+++ b/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/mail/JpaMessageMapperTest.java
@@ -35,7 +35,10 @@ import org.apache.james.mailbox.model.UpdatedFlags;
 import org.apache.james.mailbox.store.FlagsUpdateCalculator;
 import org.apache.james.mailbox.store.mail.model.MapperProvider;
 import org.apache.james.mailbox.store.mail.model.MessageMapperTest;
+import org.apache.james.utils.UpdatableTickingClock;
 import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 class JpaMessageMapperTest extends MessageMapperTest {
@@ -46,7 +49,12 @@ class JpaMessageMapperTest extends MessageMapperTest {
     protected MapperProvider createMapperProvider() {
         return new JPAMapperProvider(JPA_TEST_CLUSTER);
     }
-    
+
+    @Override
+    protected UpdatableTickingClock updatableTickingClock() {
+        return null;
+    }
+
     @AfterEach
     void cleanUp() {
         JPA_TEST_CLUSTER.clear(JPAMailboxFixture.MAILBOX_TABLE_NAMES);
@@ -139,4 +147,10 @@ class JpaMessageMapperTest extends MessageMapperTest {
                     .newFlags(new Flags())
                     .build());
     }
+
+    @Nested
+    @Disabled("JPA does not support saveDate.")
+    class SaveDateTests {
+
+    }
 }
diff --git a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxSessionMapperFactory.java b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxSessionMapperFactory.java
index 42967eeccf..c9e384f3bb 100644
--- a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxSessionMapperFactory.java
+++ b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxSessionMapperFactory.java
@@ -18,6 +18,8 @@
  ****************************************************************/
 package org.apache.james.mailbox.inmemory;
 
+import java.time.Clock;
+
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.inmemory.mail.InMemoryAnnotationMapper;
@@ -49,11 +51,11 @@ public class InMemoryMailboxSessionMapperFactory extends MailboxSessionMapperFac
     private final InMemoryUidProvider uidProvider;
     private final InMemoryModSeqProvider modSeqProvider;
 
-    public InMemoryMailboxSessionMapperFactory() {
+    public InMemoryMailboxSessionMapperFactory(Clock clock) {
         mailboxMapper = new InMemoryMailboxMapper();
         uidProvider = new InMemoryUidProvider();
         modSeqProvider = new InMemoryModSeqProvider();
-        messageMapper = new InMemoryMessageMapper(null, uidProvider, modSeqProvider);
+        messageMapper = new InMemoryMessageMapper(null, uidProvider, modSeqProvider, clock);
         messageIdMapper = new InMemoryMessageIdMapper(mailboxMapper, messageMapper);
 
         subscriptionMapper = new InMemorySubscriptionMapper();
diff --git a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageMapper.java b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageMapper.java
index 3545e85b7c..5b6295e0c5 100644
--- a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageMapper.java
+++ b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageMapper.java
@@ -19,8 +19,10 @@
 
 package org.apache.james.mailbox.inmemory.mail;
 
+import java.time.Clock;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -54,8 +56,8 @@ public class InMemoryMessageMapper extends AbstractMessageMapper {
     private static final int INITIAL_SIZE = 256;
 
     public InMemoryMessageMapper(MailboxSession session, UidProvider uidProvider,
-            ModSeqProvider modSeqProvider) {
-        super(session, uidProvider, modSeqProvider);
+            ModSeqProvider modSeqProvider, Clock clock) {
+        super(session, uidProvider, modSeqProvider, clock);
         this.mailboxByUid = new ConcurrentHashMap<>(INITIAL_SIZE);
     }
 
@@ -200,6 +202,7 @@ public class InMemoryMessageMapper extends AbstractMessageMapper {
         SimpleMailboxMessage copy = SimpleMailboxMessage.copy(mailbox.getMailboxId(), message);
         copy.setUid(message.getUid());
         copy.setModSeq(message.getModSeq());
+        copy.setSaveDate(Date.from(clock.instant()));
         getMembershipByUidForMailbox(mailbox).put(message.getUid(), copy);
 
         return copy.metaData();
diff --git a/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/mail/InMemoryMapperProvider.java b/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/mail/InMemoryMapperProvider.java
index 8f60b26760..4667385719 100644
--- a/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/mail/InMemoryMapperProvider.java
+++ b/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/mail/InMemoryMapperProvider.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.mailbox.inmemory.mail;
 
+import java.time.Instant;
 import java.util.List;
 import java.util.concurrent.ThreadLocalRandom;
 
@@ -39,6 +40,7 @@ import org.apache.james.mailbox.store.mail.MessageIdMapper;
 import org.apache.james.mailbox.store.mail.MessageMapper;
 import org.apache.james.mailbox.store.mail.model.MapperProvider;
 import org.apache.james.mailbox.store.mail.model.MessageUidProvider;
+import org.apache.james.utils.UpdatableTickingClock;
 
 import com.google.common.collect.ImmutableList;
 
@@ -50,12 +52,14 @@ public class InMemoryMapperProvider implements MapperProvider {
     private final MessageId.Factory messageIdFactory;
     private final MessageUidProvider messageUidProvider;
     private final InMemoryMailboxSessionMapperFactory inMemoryMailboxSessionMapperFactory;
+    private final UpdatableTickingClock clock;
 
 
     public InMemoryMapperProvider() {
         messageIdFactory = new InMemoryMessageId.Factory();
         messageUidProvider = new MessageUidProvider();
-        inMemoryMailboxSessionMapperFactory = new InMemoryMailboxSessionMapperFactory();
+        clock = new UpdatableTickingClock(Instant.now());
+        inMemoryMailboxSessionMapperFactory = new InMemoryMailboxSessionMapperFactory(clock);
     }
 
     @Override
@@ -124,4 +128,7 @@ public class InMemoryMapperProvider implements MapperProvider {
             .highestModSeq(mailbox);
     }
 
+    public UpdatableTickingClock getClock() {
+        return clock;
+    }
 }
diff --git a/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageIdMapperTest.java b/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageIdMapperTest.java
index faf70ab9f9..080f8b8915 100644
--- a/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageIdMapperTest.java
+++ b/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/mail/InMemoryMessageIdMapperTest.java
@@ -21,11 +21,18 @@ package org.apache.james.mailbox.inmemory.mail;
 
 import org.apache.james.mailbox.store.mail.model.MapperProvider;
 import org.apache.james.mailbox.store.mail.model.MessageIdMapperTest;
+import org.apache.james.utils.UpdatableTickingClock;
 
 class InMemoryMessageIdMapperTest extends MessageIdMapperTest {
+    private final InMemoryMapperProvider mapperProvider = new InMemoryMapperProvider();
 
     @Override
     protected MapperProvider provideMapper() {
-        return new InMemoryMapperProvider();
+        return mapperProvider;
+    }
+
+    @Override
+    protected UpdatableTickingClock updatableTickingClock() {
+        return mapperProvider.getClock();
     }
 }
diff --git a/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/mail/MemoryMessageMapperTest.java b/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/mail/MemoryMessageMapperTest.java
index 77b3696081..8e0c618a69 100644
--- a/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/mail/MemoryMessageMapperTest.java
+++ b/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/mail/MemoryMessageMapperTest.java
@@ -21,11 +21,19 @@ package org.apache.james.mailbox.inmemory.mail;
 
 import org.apache.james.mailbox.store.mail.model.MapperProvider;
 import org.apache.james.mailbox.store.mail.model.MessageMapperTest;
+import org.apache.james.utils.UpdatableTickingClock;
 
 class MemoryMessageMapperTest extends MessageMapperTest {
+    private final InMemoryMapperProvider inMemoryMapperProvider = new InMemoryMapperProvider();
     
     @Override
     protected MapperProvider createMapperProvider() {
-        return new InMemoryMapperProvider();
+        return inMemoryMapperProvider;
     }
+
+    @Override
+    protected UpdatableTickingClock updatableTickingClock() {
+        return inMemoryMapperProvider.getClock();
+    }
+
 }
diff --git a/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/manager/InMemoryIntegrationResources.java b/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/manager/InMemoryIntegrationResources.java
index d6a5c06759..8a390fecfa 100644
--- a/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/manager/InMemoryIntegrationResources.java
+++ b/mailbox/memory/src/test/java/org/apache/james/mailbox/inmemory/manager/InMemoryIntegrationResources.java
@@ -294,7 +294,8 @@ public class InMemoryIntegrationResources implements IntegrationResources<StoreM
             Preconditions.checkState(searchIndexFactory.isPresent());
             Preconditions.checkState(messageParser.isPresent());
 
-            InMemoryMailboxSessionMapperFactory mailboxSessionMapperFactory = new InMemoryMailboxSessionMapperFactory();
+            UpdatableTickingClock clock = new UpdatableTickingClock(Instant.now());
+            InMemoryMailboxSessionMapperFactory mailboxSessionMapperFactory = new InMemoryMailboxSessionMapperFactory(clock);
 
             EventBus eventBus = this.eventBus.get();
             StoreRightManager storeRightManager = new StoreRightManager(mailboxSessionMapperFactory, new UnionMailboxACLResolver(), eventBus);
@@ -314,7 +315,6 @@ public class InMemoryIntegrationResources implements IntegrationResources<StoreM
 
             InMemoryMessageId.Factory messageIdFactory = new InMemoryMessageId.Factory();
             ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm = new NaiveThreadIdGuessingAlgorithm();
-            UpdatableTickingClock clock = new UpdatableTickingClock(Instant.now());
 
             MailboxManagerPreInstanciationStage preInstanciationStage = new MailboxManagerPreInstanciationStage(mailboxSessionMapperFactory, sessionProvider);
             PreDeletionHooks hooks = createHooks(preInstanciationStage);
diff --git a/mailbox/opensearch/src/test/java/org/apache/james/mailbox/opensearch/events/OpenSearchListeningMessageSearchIndexTest.java b/mailbox/opensearch/src/test/java/org/apache/james/mailbox/opensearch/events/OpenSearchListeningMessageSearchIndexTest.java
index 3070f59872..ce34fbe26b 100644
--- a/mailbox/opensearch/src/test/java/org/apache/james/mailbox/opensearch/events/OpenSearchListeningMessageSearchIndexTest.java
+++ b/mailbox/opensearch/src/test/java/org/apache/james/mailbox/opensearch/events/OpenSearchListeningMessageSearchIndexTest.java
@@ -26,6 +26,7 @@ import static org.awaitility.Durations.ONE_HUNDRED_MILLISECONDS;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
+import java.time.Instant;
 import java.time.ZoneId;
 import java.util.Date;
 
@@ -77,6 +78,7 @@ import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder;
 import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage;
 import org.apache.james.mailbox.store.search.ListeningMessageSearchIndex;
 import org.apache.james.mailbox.store.search.ListeningMessageSearchIndexContract;
+import org.apache.james.utils.UpdatableTickingClock;
 import org.awaitility.Awaitility;
 import org.awaitility.Durations;
 import org.awaitility.core.ConditionFactory;
@@ -166,13 +168,15 @@ class OpenSearchListeningMessageSearchIndexTest {
     OpenSearchIndexer openSearchIndexer;
     OpenSearchSearcher openSearchSearcher;
     SessionProviderImpl sessionProvider;
+    UpdatableTickingClock clock;
 
     @RegisterExtension
     DockerOpenSearchExtension openSearch = new DockerOpenSearchExtension();
 
     @BeforeEach
     void setup() throws Exception {
-        mapperFactory = new InMemoryMailboxSessionMapperFactory();
+        clock = new UpdatableTickingClock(Instant.now());
+        mapperFactory = new InMemoryMailboxSessionMapperFactory(clock);
 
         MessageToOpenSearchJson messageToOpenSearchJson = new MessageToOpenSearchJson(
             new DefaultTextExtractor(),
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractMessageMapper.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractMessageMapper.java
index ad1d9adeed..96a5d09ebc 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractMessageMapper.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractMessageMapper.java
@@ -18,6 +18,7 @@
  ****************************************************************/
 package org.apache.james.mailbox.store.mail;
 
+import java.time.Clock;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -54,11 +55,13 @@ public abstract class AbstractMessageMapper extends TransactionalMapper implemen
     protected final MailboxSession mailboxSession;
     private final UidProvider uidProvider;
     private final ModSeqProvider modSeqProvider;
+    protected final Clock clock;
 
-    public AbstractMessageMapper(MailboxSession mailboxSession, UidProvider uidProvider, ModSeqProvider modSeqProvider) {
+    public AbstractMessageMapper(MailboxSession mailboxSession, UidProvider uidProvider, ModSeqProvider modSeqProvider, Clock clock) {
         this.mailboxSession = mailboxSession;
         this.uidProvider = uidProvider;
         this.modSeqProvider = modSeqProvider;
+        this.clock = clock;
     }
     
     @Override
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/MailboxMessage.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/MailboxMessage.java
index d6e7f17b2b..249cece8dc 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/MailboxMessage.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/MailboxMessage.java
@@ -108,6 +108,8 @@ public interface MailboxMessage extends Message, Comparable<MailboxMessage> {
      */
     void setFlags(Flags flags);
 
+    void setSaveDate(Date saveDate);
+
     /**
      * Creates a new flags instance populated
      * with the current flag data.
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 e5caaa9bef..c5339ff690 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
@@ -208,7 +208,7 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage {
     private MessageUid uid;
     private final MailboxId mailboxId;
     private final ThreadId threadId;
-    private final Optional<Date> saveDate;
+    private Optional<Date> saveDate;
     private boolean answered;
     private boolean deleted;
     private boolean draft;
@@ -319,6 +319,11 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage {
         this.uid = uid;
     }
 
+    @Override
+    public void setSaveDate(Date saveDate) {
+        this.saveDate = Optional.of(saveDate);
+    }
+
     @Override
     public Optional<Date> getSaveDate() {
         return saveDate;
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 cf66cdcd05..a1c2abd8fb 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
@@ -24,6 +24,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 import java.time.Duration;
+import java.time.temporal.ChronoUnit;
 import java.util.Date;
 import java.util.List;
 
@@ -52,9 +53,11 @@ import org.apache.james.mailbox.store.mail.MessageMapper.FetchType;
 import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder;
 import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage;
 import org.apache.james.util.concurrency.ConcurrentTestRunner;
+import org.apache.james.utils.UpdatableTickingClock;
 import org.assertj.core.data.MapEntry;
 import org.junit.Assume;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 import com.google.common.collect.ImmutableList;
@@ -84,6 +87,8 @@ public abstract class MessageIdMapperTest {
 
     protected abstract MapperProvider provideMapper();
 
+    protected abstract UpdatableTickingClock updatableTickingClock();
+
     @BeforeEach
     void setUp() throws MailboxException {
         this.mapperProvider = provideMapper();
@@ -984,6 +989,52 @@ public abstract class MessageIdMapperTest {
                 .build());
     }
 
+    @Nested
+    class SaveDateTests {
+        @Test
+        void saveMessagesShouldSetNewSaveDate() throws MailboxException {
+            message1.setUid(mapperProvider.generateMessageUid());
+            message1.setModSeq(mapperProvider.generateModSeq(benwaInboxMailbox));
+            message1.setFlags(new Flags(Flag.SEEN));
+
+            message2.setUid(mapperProvider.generateMessageUid());
+            message2.setModSeq(mapperProvider.generateModSeq(benwaInboxMailbox));
+
+            sut.save(message1);
+            updatableTickingClock().setInstant(updatableTickingClock().instant().plusSeconds(1000));
+            sut.save(message2);
+
+            MailboxMessage firstMessage = sut.find(ImmutableList.of(message1.getMessageId()), FetchType.METADATA).get(0);
+            MailboxMessage secondMessage = sut.find(ImmutableList.of(message2.getMessageId()), FetchType.METADATA).get(0);
+
+            assertThat(firstMessage.getSaveDate()).isNotEqualTo(secondMessage.getSaveDate());
+        }
+
+        @Test
+        void copyInMailboxReactiveShouldSetNewSaveDate() throws MailboxException, InterruptedException {
+            message1.setUid(mapperProvider.generateMessageUid());
+            message1.setModSeq(mapperProvider.generateModSeq(benwaInboxMailbox));
+            message1.setFlags(new Flags(Flag.SEEN));
+            sut.save(message1);
+
+            MailboxMessage copy = sut.find(ImmutableList.of(message1.getMessageId()), FetchType.METADATA).get(0)
+                .copy(benwaWorkMailbox);
+            copy.setUid(mapperProvider.generateMessageUid());
+            copy.setModSeq(mapperProvider.generateModSeq(benwaWorkMailbox));
+
+            updatableTickingClock().setInstant(updatableTickingClock().instant().plus(8, ChronoUnit.DAYS));
+
+            sut.copyInMailboxReactive(copy, benwaWorkMailbox).block();
+
+            List<MailboxMessage> messages = sut.find(ImmutableList.of(message1.getMessageId()), FetchType.METADATA);
+            MailboxMessage firstMessage = messages.get(0);
+            MailboxMessage secondMessage = messages.get(1);
+
+            assertThat(firstMessage.getSaveDate()).isNotEqualTo(secondMessage.getSaveDate());
+        }
+    }
+
+
     private Mailbox createMailbox(MailboxPath mailboxPath) throws MailboxException {
         return mailboxMapper.create(mailboxPath, UID_VALIDITY).block();
     }
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 5503e4e782..f2b478d744 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
@@ -42,6 +42,7 @@ import org.apache.james.mailbox.MessageUid;
 import org.apache.james.mailbox.ModSeq;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.model.ByteContent;
+import org.apache.james.mailbox.model.Content;
 import org.apache.james.mailbox.model.Mailbox;
 import org.apache.james.mailbox.model.MailboxCounters;
 import org.apache.james.mailbox.model.MailboxPath;
@@ -59,8 +60,10 @@ import org.apache.james.mailbox.store.mail.model.MapperProvider.Capabilities;
 import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder;
 import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage;
 import org.apache.james.util.concurrency.ConcurrentTestRunner;
+import org.apache.james.utils.UpdatableTickingClock;
 import org.junit.Assume;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
 import com.google.common.collect.ImmutableList;
@@ -94,6 +97,8 @@ public abstract class MessageMapperTest {
 
     protected abstract MapperProvider createMapperProvider();
 
+    protected abstract UpdatableTickingClock updatableTickingClock();
+
     @BeforeEach
     void setUp() throws Exception {
         this.mapperProvider = createMapperProvider();
@@ -1220,6 +1225,97 @@ public abstract class MessageMapperTest {
             .containsOnly(message1.getUid(), message5.getUid());
     }
 
+    @Nested
+    class SaveDateTests {
+        @Test
+        void addMessageShouldSetNewSaveDate() throws MailboxException {
+            MailboxMessage messageWithoutSaveDate = createMessage(Optional.empty());
+
+            MessageMetaData messageMetaData = messageMapper.add(benwaInboxMailbox, messageWithoutSaveDate);
+
+            assertThat(messageMetaData.getSaveDate()).isPresent();
+        }
+
+        @Test
+        void deleteMessageShouldReturnMetaDataContainsSaveDate() throws MailboxException {
+            MessageMetaData toBeDeletedMessage = messageMapper.add(benwaInboxMailbox, createMessage(Optional.empty()));
+
+            assertThat(messageMapper.deleteMessages(benwaInboxMailbox, List.of(toBeDeletedMessage.getUid()))
+                .values()
+                .stream()
+                .allMatch(messageMetaData -> messageMetaData.getSaveDate().equals(toBeDeletedMessage.getSaveDate())))
+                .isTrue();
+        }
+
+        @Test
+        void copyMessageShouldSetNewSaveDate() throws MailboxException {
+            MailboxMessage originalMessage = createMessage(Optional.of(new Date()));
+            MessageUid uid = messageMapper.add(benwaInboxMailbox, originalMessage).getUid();
+
+            updatableTickingClock().setInstant(updatableTickingClock().instant().plusSeconds(1000));
+
+            MessageMetaData copiedMessageMetaData = messageMapper.copy(benwaInboxMailbox,
+                messageMapper.findInMailbox(benwaInboxMailbox, MessageRange.one(uid), FetchType.METADATA, 1).next());
+
+            assertThat(copiedMessageMetaData.getSaveDate()).isNotEqualTo(originalMessage.getSaveDate());
+        }
+
+        @Test
+        void copyListOfMessagesShouldSetNewSaveDate() throws MailboxException {
+            MailboxMessage originalMessage = createMessage(Optional.of(new Date()));
+            MessageUid uid = messageMapper.add(benwaInboxMailbox, originalMessage).getUid();
+
+            updatableTickingClock().setInstant(updatableTickingClock().instant().plusSeconds(1000));
+
+            List<MessageMetaData> copiedMessageMetaData = messageMapper.copy(benwaInboxMailbox,
+                List.of(messageMapper.findInMailbox(benwaInboxMailbox, MessageRange.one(uid), FetchType.METADATA, 1).next()));
+
+            assertThat(copiedMessageMetaData.get(0).getSaveDate()).isNotEqualTo(originalMessage.getSaveDate());
+        }
+
+        @Test
+        void moveMessageShouldSetNewSaveDate() throws MailboxException {
+            MailboxMessage originalMessage = createMessage(Optional.of(new Date()));
+            MessageUid uid = messageMapper.add(benwaInboxMailbox, originalMessage).getUid();
+
+            updatableTickingClock().setInstant(updatableTickingClock().instant().plusSeconds(1000));
+
+            MessageMetaData movedMessageMetaData = messageMapper.move(benwaInboxMailbox,
+                messageMapper.findInMailbox(benwaInboxMailbox, MessageRange.one(uid), FetchType.METADATA, 1).next());
+
+            assertThat(movedMessageMetaData.getSaveDate()).isNotEqualTo(originalMessage.getSaveDate());
+        }
+
+        @Test
+        void moveListOfMessagesShouldSetNewSaveDate() throws MailboxException {
+            MailboxMessage originalMessage = createMessage(Optional.of(new Date()));
+            MessageUid uid = messageMapper.add(benwaInboxMailbox, originalMessage).getUid();
+
+            updatableTickingClock().setInstant(updatableTickingClock().instant().plusSeconds(1000));
+
+            List<MessageMetaData> movedMessageMetaData = messageMapper.move(benwaInboxMailbox,
+                List.of(messageMapper.findInMailbox(benwaInboxMailbox, MessageRange.one(uid), FetchType.METADATA, 1).next()));
+
+            assertThat(movedMessageMetaData.get(0).getSaveDate()).isNotEqualTo(originalMessage.getSaveDate());
+        }
+
+        private SimpleMailboxMessage createMessage(Optional<Date> saveDate) throws MailboxException {
+            Content content = new ByteContent("Subject: messagePropertiesShouldBeStoredWhenDuplicateEntries \n\nBody\n.\n".getBytes());
+            return SimpleMailboxMessage.builder()
+                .messageId(mapperProvider.generateMessageId())
+                .mailboxId(benwaInboxMailbox.getMailboxId())
+                .threadId(ThreadId.fromBaseMessageId(mapperProvider.generateMessageId()))
+                .internalDate(new Date())
+                .saveDate(saveDate)
+                .bodyStartOctet(16)
+                .size(content.size())
+                .content(content)
+                .flags(new Flags())
+                .properties(new PropertyBuilder())
+                .build();
+        }
+    }
+
     private List<MessageUid> markThenPerformRetrieveMessagesMarkedForDeletion(MessageRange range) throws MailboxException {
         messageMapper.updateFlags(benwaInboxMailbox, message1.getUid(), new FlagsUpdateCalculator(new Flags(Flags.Flag.DELETED), FlagsUpdateMode.REPLACE));
         messageMapper.updateFlags(benwaInboxMailbox, message4.getUid(), new FlagsUpdateCalculator(new Flags(Flags.Flag.DELETED), FlagsUpdateMode.REPLACE));


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