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 <hqt...@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-unsubscr...@james.apache.org
For additional commands, e-mail: notifications-h...@james.apache.org

Reply via email to