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 439c961a4c413475686633e5bf66c61acee60915 Author: Quan Tran <[email protected]> AuthorDate: Tue Nov 29 16:23:53 2022 +0700 JAMES-3754 Add saveDate field to MailboxMessage and associated Cassandra tables Add saveDate column to`messageIdTable` and `imapUidTable` --- .../apache/james/mailbox/MailboxManagerTest.java | 20 +++++++ mailbox/cassandra/pom.xml | 5 ++ .../mailbox/cassandra/CassandraMailboxManager.java | 9 ++-- .../mailbox/cassandra/CassandraMessageManager.java | 7 ++- .../cassandra/mail/CassandraMessageIdDAO.java | 5 ++ .../mail/CassandraMessageIdToImapUidDAO.java | 8 +++ .../cassandra/mail/CassandraMessageMetadata.java | 34 ++++++++++-- .../cassandra/modules/CassandraMessageModule.java | 2 + .../cassandra/table/CassandraMessageIdTable.java | 2 + .../cassandra/CassandraMailboxManagerProvider.java | 12 +++-- .../cassandra/CassandraTestSystemFixture.java | 5 +- .../CassandraMailboxManagerAttachmentTest.java | 6 ++- .../cassandra/mail/CassandraMessageDAOV3Test.java | 18 ++++--- .../cassandra/mail/CassandraMessageIdDAOTest.java | 52 ++++++++++++++++++ .../mail/CassandraMessageIdToImapUidDAOTest.java | 51 ++++++++++++++++++ mailbox/jpa/pom.xml | 5 ++ .../model/openjpa/AbstractJPAMailboxMessage.java | 6 +++ .../mailbox/jpa/openjpa/OpenJPAMailboxManager.java | 9 ++-- .../mailbox/jpa/openjpa/OpenJPAMessageFactory.java | 2 +- .../mailbox/jpa/openjpa/OpenJPAMessageManager.java | 6 ++- .../james/mailbox/jpa/JPAMailboxManagerTest.java | 6 +++ .../mailbox/jpa/JpaMailboxManagerProvider.java | 5 +- mailbox/memory/pom.xml | 5 ++ .../mailbox/inmemory/InMemoryMailboxManager.java | 9 ++-- .../mailbox/inmemory/InMemoryMessageManager.java | 7 ++- .../manager/InMemoryIntegrationResources.java | 6 ++- mailbox/store/pom.xml | 5 ++ .../apache/james/mailbox/store/MessageFactory.java | 7 +-- .../apache/james/mailbox/store/MessageStorer.java | 34 +++++++----- .../james/mailbox/store/StoreMailboxManager.java | 13 +++-- .../james/mailbox/store/mail/MessageMapper.java | 1 + .../store/mail/model/DelegatingMailboxMessage.java | 1 + .../mailbox/store/mail/model/MailboxMessage.java | 7 +++ .../mail/model/impl/SimpleMailboxMessage.java | 34 +++++++++--- .../apache/james/mailbox/store/MessageBuilder.java | 4 +- .../mailbox/store/StoreMailboxManagerTest.java | 7 ++- .../model/MessageWithAttachmentMapperTest.java | 3 +- .../mail/model/impl/SimpleMailboxMessageTest.java | 63 +++++++++++++++++++++- mpt/impl/imap-mailbox/cassandra/pom.xml | 5 ++ .../cassandra/host/CassandraHostSystem.java | 6 ++- mpt/impl/imap-mailbox/jpa/pom.xml | 5 ++ .../mpt/imapmailbox/jpa/host/JPAHostSystem.java | 5 +- 42 files changed, 435 insertions(+), 67 deletions(-) diff --git a/mailbox/api/src/test/java/org/apache/james/mailbox/MailboxManagerTest.java b/mailbox/api/src/test/java/org/apache/james/mailbox/MailboxManagerTest.java index ed642c8c1f..033d9b6bfe 100644 --- a/mailbox/api/src/test/java/org/apache/james/mailbox/MailboxManagerTest.java +++ b/mailbox/api/src/test/java/org/apache/james/mailbox/MailboxManagerTest.java @@ -2873,6 +2873,26 @@ public abstract class MailboxManagerTest<T extends MailboxManager> { } } + @Nested + class SaveDateTests { + private MessageManager inboxManager; + + @BeforeEach + void setUp() throws Exception { + session = mailboxManager.createSystemSession(USER_1); + MailboxPath inbox = MailboxPath.inbox(session); + mailboxManager.createMailbox(inbox, session).get(); + inboxManager = mailboxManager.getMailbox(inbox, session); + } + + @Test + void shouldSetSaveDateWhenAppendMessage() throws Exception { + ComposedMessageId composeId1 = inboxManager.appendMessage(AppendCommand.builder().build(message), session).getId(); + MessageResult messageResult = inboxManager.getMessages(MessageRange.one(composeId1.getUid()), FetchGroup.MINIMAL, session).next(); + assertThat(messageResult.getSaveDate()).isPresent(); + } + } + @Nested class RightTests { diff --git a/mailbox/cassandra/pom.xml b/mailbox/cassandra/pom.xml index 290df88663..4b4bd0dc9a 100644 --- a/mailbox/cassandra/pom.xml +++ b/mailbox/cassandra/pom.xml @@ -129,6 +129,11 @@ <artifactId>james-server-task-memory</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>${james.groupId}</groupId> + <artifactId>james-server-testing</artifactId> + <scope>runtime</scope> + </dependency> <dependency> <groupId>${james.groupId}</groupId> <artifactId>james-server-util</artifactId> diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxManager.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxManager.java index a81735072b..5ca039320a 100644 --- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxManager.java +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxManager.java @@ -19,6 +19,7 @@ package org.apache.james.mailbox.cassandra; +import java.time.Clock; import java.util.EnumSet; import javax.inject.Inject; @@ -66,7 +67,7 @@ public class CassandraMailboxManager extends StoreMailboxManager { StoreMailboxAnnotationManager annotationManager, StoreRightManager storeRightManager, QuotaComponents quotaComponents, MessageSearchIndex index, MailboxManagerConfiguration configuration, - PreDeletionHooks preDeletionHooks, ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm) { + PreDeletionHooks preDeletionHooks, ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm, Clock clock) { super(mapperFactory, sessionProvider, locker, @@ -79,7 +80,8 @@ public class CassandraMailboxManager extends StoreMailboxManager { index, configuration, preDeletionHooks, - threadIdGuessingAlgorithm); + threadIdGuessingAlgorithm, + clock); this.locker = locker; this.mapperFactory = mapperFactory; } @@ -108,7 +110,8 @@ public class CassandraMailboxManager extends StoreMailboxManager { configuration.getBatchSizes(), getStoreRightManager(), getPreDeletionHooks(), - getThreadIdGuessingAlgorithm()); + getThreadIdGuessingAlgorithm(), + getClock()); } @Override diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMessageManager.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMessageManager.java index cbbe0f4b36..0c02d5d6f0 100644 --- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMessageManager.java +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMessageManager.java @@ -19,6 +19,8 @@ package org.apache.james.mailbox.cassandra; +import java.time.Clock; + import javax.mail.Flags; import org.apache.james.events.EventBus; @@ -49,10 +51,11 @@ public class CassandraMessageManager extends StoreMessageManager { BatchSizes batchSizes, StoreRightManager storeRightManager, PreDeletionHooks preDeletionHooks, - ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm) { + ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm, + Clock clock) { super(CassandraMailboxManager.MESSAGE_CAPABILITIES, mapperFactory, index, eventBus, locker, mailbox, quotaManager, quotaRootResolver, batchSizes, storeRightManager, - preDeletionHooks, new MessageStorer.WithAttachment(mapperFactory, messageIdFactory, new MessageFactory.StoreMessageFactory(), mapperFactory, messageParser, threadIdGuessingAlgorithm)); + preDeletionHooks, new MessageStorer.WithAttachment(mapperFactory, messageIdFactory, new MessageFactory.StoreMessageFactory(), mapperFactory, messageParser, threadIdGuessingAlgorithm, clock)); } /** diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAO.java index 4d4177811a..540a33939b 100644 --- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAO.java +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAO.java @@ -26,6 +26,7 @@ import static com.datastax.oss.driver.api.querybuilder.relation.Relation.column; import static com.datastax.oss.driver.api.querybuilder.update.Assignment.append; import static com.datastax.oss.driver.api.querybuilder.update.Assignment.remove; import static com.datastax.oss.driver.api.querybuilder.update.Assignment.setColumn; +import static org.apache.james.mailbox.cassandra.table.CassandraMessageIdTable.SAVE_DATE; import static org.apache.james.mailbox.cassandra.table.CassandraMessageIdTable.TABLE_NAME; import static org.apache.james.mailbox.cassandra.table.CassandraMessageIdTable.THREAD_ID; import static org.apache.james.mailbox.cassandra.table.CassandraMessageIds.IMAP_UID; @@ -171,6 +172,7 @@ public class CassandraMessageIdDAO { setColumn(SEEN, bindMarker(SEEN)), setColumn(USER, bindMarker(USER)), setColumn(INTERNAL_DATE, bindMarker(INTERNAL_DATE)), + setColumn(SAVE_DATE, bindMarker(SAVE_DATE)), setColumn(BODY_START_OCTET, bindMarker(BODY_START_OCTET)), setColumn(FULL_CONTENT_OCTETS, bindMarker(FULL_CONTENT_OCTETS)), setColumn(HEADER_CONTENT, bindMarker(HEADER_CONTENT)), @@ -342,6 +344,7 @@ public class CassandraMessageIdDAO { .setBoolean(SEEN, flags.contains(Flag.SEEN)) .setBoolean(USER, flags.contains(Flag.USER)) .setInstant(INTERNAL_DATE, metadata.getInternalDate().get().toInstant()) + .setInstant(SAVE_DATE, metadata.getSaveDate().map(Date::toInstant).orElse(null)) .setInt(BODY_START_OCTET, Math.toIntExact(metadata.getBodyStartOctet().get())) .setLong(FULL_CONTENT_OCTETS, metadata.getSize().get()) .setString(HEADER_CONTENT, metadata.getHeaderContent().get().asString()) @@ -566,6 +569,8 @@ public class CassandraMessageIdDAO { .bodyStartOctet(row.get(BODY_START_OCTET, Integer.class)) .internalDate(Optional.ofNullable(row.get(INTERNAL_DATE, TypeCodecs.TIMESTAMP)) .map(Date::from)) + .saveDate(Optional.ofNullable(row.get(SAVE_DATE, TypeCodecs.TIMESTAMP)) + .map(Date::from)) .size(row.get(FULL_CONTENT_OCTETS, Long.class)) .headerContent(Optional.ofNullable(row.get(HEADER_CONTENT, TypeCodecs.TEXT)) .map(blobIdFactory::from)) diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAO.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAO.java index 19d450e1ac..315d7cf535 100644 --- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAO.java +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAO.java @@ -26,6 +26,7 @@ import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.selectFrom; import static com.datastax.oss.driver.api.querybuilder.relation.Relation.column; import static com.datastax.oss.driver.api.querybuilder.update.Assignment.setColumn; import static org.apache.james.backends.cassandra.init.configuration.JamesExecutionProfiles.ConsistencyChoice.STRONG; +import static org.apache.james.mailbox.cassandra.table.CassandraMessageIdTable.SAVE_DATE; import static org.apache.james.mailbox.cassandra.table.CassandraMessageIds.IMAP_UID; import static org.apache.james.mailbox.cassandra.table.CassandraMessageIds.MAILBOX_ID; import static org.apache.james.mailbox.cassandra.table.CassandraMessageIds.MESSAGE_ID; @@ -144,6 +145,7 @@ public class CassandraMessageIdToImapUidDAO { .value(USER, bindMarker(USER)) .value(USER_FLAGS, bindMarker(USER_FLAGS)) .value(INTERNAL_DATE, bindMarker(INTERNAL_DATE)) + .value(SAVE_DATE, bindMarker(SAVE_DATE)) .value(BODY_START_OCTET, bindMarker(BODY_START_OCTET)) .value(FULL_CONTENT_OCTETS, bindMarker(FULL_CONTENT_OCTETS)) .value(HEADER_CONTENT, bindMarker(HEADER_CONTENT)); @@ -161,6 +163,7 @@ public class CassandraMessageIdToImapUidDAO { setColumn(SEEN, bindMarker(SEEN)), setColumn(USER, bindMarker(USER)), setColumn(INTERNAL_DATE, bindMarker(INTERNAL_DATE)), + setColumn(SAVE_DATE, bindMarker(SAVE_DATE)), setColumn(BODY_START_OCTET, bindMarker(BODY_START_OCTET)), setColumn(FULL_CONTENT_OCTETS, bindMarker(FULL_CONTENT_OCTETS)), setColumn(HEADER_CONTENT, bindMarker(HEADER_CONTENT))) @@ -187,6 +190,7 @@ public class CassandraMessageIdToImapUidDAO { .value(USER, bindMarker(USER)) .value(USER_FLAGS, bindMarker(USER_FLAGS)) .value(INTERNAL_DATE, bindMarker(INTERNAL_DATE)) + .value(SAVE_DATE, bindMarker(SAVE_DATE)) .value(BODY_START_OCTET, bindMarker(BODY_START_OCTET)) .value(FULL_CONTENT_OCTETS, bindMarker(FULL_CONTENT_OCTETS)) .value(HEADER_CONTENT, bindMarker(HEADER_CONTENT)); @@ -267,6 +271,7 @@ public class CassandraMessageIdToImapUidDAO { .setBoolean(SEEN, flags.contains(Flag.SEEN)) .setBoolean(USER, flags.contains(Flag.USER)) .setInstant(INTERNAL_DATE, metadata.getInternalDate().get().toInstant()) + .setInstant(SAVE_DATE, metadata.getSaveDate().map(Date::toInstant).orElse(null)) .setInt(BODY_START_OCTET, Math.toIntExact(metadata.getBodyStartOctet().get())) .setLong(FULL_CONTENT_OCTETS, metadata.getSize().get()) .setString(HEADER_CONTENT, metadata.getHeaderContent().get().asString()) @@ -290,6 +295,7 @@ public class CassandraMessageIdToImapUidDAO { .setBoolean(USER, flags.contains(Flag.USER)) .setSet(USER_FLAGS, ImmutableSet.copyOf(flags.getUserFlags()), String.class) .setInstant(INTERNAL_DATE, metadata.getInternalDate().get().toInstant()) + .setInstant(SAVE_DATE, metadata.getSaveDate().map(Date::toInstant).orElse(null)) .setInt(BODY_START_OCTET, Math.toIntExact(metadata.getBodyStartOctet().get())) .setLong(FULL_CONTENT_OCTETS, metadata.getSize().get()) .setString(HEADER_CONTENT, metadata.getHeaderContent().get().asString())); @@ -398,6 +404,8 @@ public class CassandraMessageIdToImapUidDAO { .bodyStartOctet(row.get(BODY_START_OCTET, Integer.class)) .internalDate(Optional.ofNullable(row.get(INTERNAL_DATE, TypeCodecs.TIMESTAMP)) .map(Date::from)) + .saveDate(Optional.ofNullable(row.get(SAVE_DATE, TypeCodecs.TIMESTAMP)) + .map(Date::from)) .size(row.get(FULL_CONTENT_OCTETS, Long.class)) .headerContent(Optional.ofNullable(row.getString(HEADER_CONTENT)) .map(blobIdFactory::from)) 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 16ede5de7c..0e34163bf5 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 @@ -53,12 +53,14 @@ public class CassandraMessageMetadata { public static class Builder { private ComposedMessageIdWithMetaData composedMessageId; private Optional<Date> internalDate; + private Optional<Date> saveDate; private Optional<Long> bodyStartOctet; private Optional<Long> size; private Optional<BlobId> headerContent; public Builder() { this.internalDate = Optional.empty(); + this.saveDate = Optional.empty(); this.size = Optional.empty(); this.headerContent = Optional.empty(); this.bodyStartOctet = Optional.empty(); @@ -79,6 +81,16 @@ public class CassandraMessageMetadata { return this; } + public Builder saveDate(Date date) { + this.saveDate = Optional.ofNullable(date); + return this; + } + + public Builder saveDate(Optional<Date> date) { + this.saveDate = date; + return this; + } + public Builder bodyStartOctet(Long bodyStartOctet) { this.bodyStartOctet = Optional.ofNullable(bodyStartOctet); return this; @@ -113,7 +125,7 @@ public class CassandraMessageMetadata { public CassandraMessageMetadata build() { Preconditions.checkState(composedMessageId != null, "'composedMessageId' is compulsory"); - return new CassandraMessageMetadata(composedMessageId, internalDate, bodyStartOctet, size, headerContent); + return new CassandraMessageMetadata(composedMessageId, internalDate, saveDate, bodyStartOctet, size, headerContent); } } @@ -266,6 +278,11 @@ public class CassandraMessageMetadata { return delegate.getAttachments(); } + @Override + public Optional<Date> getSaveDate() { + return delegate.getSaveDate(); + } + public BlobId getHeaderBlobId() { return headerBlobId; } @@ -289,6 +306,7 @@ public class CassandraMessageMetadata { .threadId(mailboxMessage.getThreadId()) .build(), Optional.of(mailboxMessage.getInternalDate()), + mailboxMessage.getSaveDate(), Optional.of(mailboxMessage.getHeaderOctets()), Optional.of(mailboxMessage.getFullContentOctets()), Optional.of(headerBlobId)); @@ -303,13 +321,15 @@ public class CassandraMessageMetadata { private final ComposedMessageIdWithMetaData composedMessageId; private final Optional<Date> internalDate; + private final Optional<Date> saveDate; private final Optional<Long> bodyStartOctet; private final Optional<Long> size; private final Optional<BlobId> headerContent; - public CassandraMessageMetadata(ComposedMessageIdWithMetaData composedMessageId, Optional<Date> internalDate, Optional<Long> bodyStartOctet, Optional<Long> size, Optional<BlobId> headerContent) { + public CassandraMessageMetadata(ComposedMessageIdWithMetaData composedMessageId, Optional<Date> internalDate, Optional<Date> saveDate, Optional<Long> bodyStartOctet, Optional<Long> size, Optional<BlobId> headerContent) { this.composedMessageId = composedMessageId; this.internalDate = internalDate; + this.saveDate = saveDate; this.bodyStartOctet = bodyStartOctet; this.size = size; this.headerContent = headerContent; @@ -337,6 +357,7 @@ public class CassandraMessageMetadata { .size(size.get()) .bodyStartOctet(Math.toIntExact(bodyStartOctet.get())) .internalDate(internalDate.get()) + .saveDate(saveDate) .properties(new PropertyBuilder()) .build(), getHeaderContent().get()); @@ -362,9 +383,14 @@ public class CassandraMessageMetadata { return size; } + public Optional<Date> getSaveDate() { + return saveDate; + } + public CassandraMessageMetadata withMailboxId(MailboxId mailboxId) { return builder() .internalDate(internalDate) + .saveDate(saveDate) .size(size) .bodyStartOctet(bodyStartOctet) .headerContent(headerContent) @@ -385,6 +411,7 @@ public class CassandraMessageMetadata { CassandraMessageMetadata other = (CassandraMessageMetadata) obj; return Objects.equal(this.composedMessageId, other.composedMessageId) && Objects.equal(this.internalDate, other.internalDate) + && Objects.equal(this.saveDate, other.saveDate) && Objects.equal(this.bodyStartOctet, other.bodyStartOctet) && Objects.equal(this.size, other.size) && Objects.equal(this.headerContent, other.headerContent); @@ -394,7 +421,7 @@ public class CassandraMessageMetadata { @Override public int hashCode() { - return Objects.hashCode(composedMessageId, internalDate, bodyStartOctet, size, headerContent); + return Objects.hashCode(composedMessageId, internalDate, saveDate, bodyStartOctet, size, headerContent); } @Override @@ -403,6 +430,7 @@ public class CassandraMessageMetadata { .toStringHelper(this) .add("composedMessageId", composedMessageId) .add("internalDate", internalDate) + .add("saveDate", saveDate) .add("bodyStartOctet", bodyStartOctet) .add("size", size) .add("headerContent", headerContent) diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraMessageModule.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraMessageModule.java index 96304e5af3..a667a31f66 100644 --- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraMessageModule.java +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/modules/CassandraMessageModule.java @@ -67,6 +67,7 @@ public interface CassandraMessageModule { .withColumn(Flag.USER, BOOLEAN) .withColumn(Flag.USER_FLAGS, setOf(TEXT)) .withColumn(CassandraMessageV3Table.INTERNAL_DATE, TIMESTAMP) + .withColumn(CassandraMessageIdTable.SAVE_DATE, TIMESTAMP) .withColumn(CassandraMessageV3Table.BODY_START_OCTET, INT) .withColumn(CassandraMessageV3Table.FULL_CONTENT_OCTETS, BIGINT) .withColumn(CassandraMessageV3Table.HEADER_CONTENT, TEXT)) @@ -91,6 +92,7 @@ public interface CassandraMessageModule { .withColumn(Flag.USER, BOOLEAN) .withColumn(Flag.USER_FLAGS, setOf(TEXT)) .withColumn(CassandraMessageV3Table.INTERNAL_DATE, TIMESTAMP) + .withColumn(CassandraMessageIdTable.SAVE_DATE, TIMESTAMP) .withColumn(CassandraMessageV3Table.BODY_START_OCTET, INT) .withColumn(CassandraMessageV3Table.FULL_CONTENT_OCTETS, BIGINT) .withColumn(CassandraMessageV3Table.HEADER_CONTENT, TEXT)) diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraMessageIdTable.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraMessageIdTable.java index 6ab2d26bd2..15ba47fcba 100644 --- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraMessageIdTable.java +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/table/CassandraMessageIdTable.java @@ -27,4 +27,6 @@ public interface CassandraMessageIdTable { CqlIdentifier MOD_SEQ = CqlIdentifier.fromCql("modSeq"); CqlIdentifier THREAD_ID = CqlIdentifier.fromCql("threadId"); + + CqlIdentifier SAVE_DATE = CqlIdentifier.fromCql("save_date"); } diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxManagerProvider.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxManagerProvider.java index 376cec8f73..f3b669ec97 100644 --- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxManagerProvider.java +++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraMailboxManagerProvider.java @@ -19,6 +19,9 @@ package org.apache.james.mailbox.cassandra; +import java.time.Clock; +import java.time.Instant; + import org.apache.james.backends.cassandra.CassandraCluster; import org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration; import org.apache.james.events.EventBusTestFixture; @@ -56,6 +59,7 @@ import org.apache.james.mailbox.store.quota.StoreQuotaManager; import org.apache.james.mailbox.store.search.MessageSearchIndex; import org.apache.james.mailbox.store.search.SimpleMessageSearchIndex; import org.apache.james.metrics.tests.RecordingMetricFactory; +import org.apache.james.utils.UpdatableTickingClock; import com.datastax.oss.driver.api.core.CqlSession; @@ -81,6 +85,7 @@ public class CassandraMailboxManagerProvider { MailboxManagerConfiguration mailboxManagerConfiguration) { CassandraMessageId.Factory messageIdFactory = new CassandraMessageId.Factory(); ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm = new NaiveThreadIdGuessingAlgorithm(); + UpdatableTickingClock clock = new UpdatableTickingClock(Instant.now()); CassandraMailboxSessionMapperFactory mapperFactory = TestCassandraMailboxSessionMapperFactory.forTests( cassandra, @@ -88,7 +93,7 @@ public class CassandraMailboxManagerProvider { cassandraConfiguration); return provideMailboxManager(cassandra.getConf(), preDeletionHooks, mapperFactory, - mailboxManagerConfiguration, messageIdFactory, threadIdGuessingAlgorithm); + mailboxManagerConfiguration, messageIdFactory, threadIdGuessingAlgorithm, clock); } private static CassandraMailboxManager provideMailboxManager(CqlSession session, @@ -96,7 +101,8 @@ public class CassandraMailboxManagerProvider { CassandraMailboxSessionMapperFactory mapperFactory, MailboxManagerConfiguration mailboxManagerConfiguration, MessageId.Factory messageIdFactory, - ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm) { + ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm, + Clock clock) { MailboxACLResolver aclResolver = new UnionMailboxACLResolver(); MessageParser messageParser = new MessageParser(); InVMEventBus eventBus = new InVMEventBus(new InVmEventDelivery(new RecordingMetricFactory()), EventBusTestFixture.RETRY_BACKOFF_CONFIGURATION, new MemoryEventDeadLetters()); @@ -122,7 +128,7 @@ public class CassandraMailboxManagerProvider { CassandraMailboxManager manager = new CassandraMailboxManager(mapperFactory, sessionProvider, new NoMailboxPathLocker(), messageParser, messageIdFactory, eventBus, annotationManager, storeRightManager, - quotaComponents, index, mailboxManagerConfiguration, preDeletionHooks, threadIdGuessingAlgorithm); + quotaComponents, index, mailboxManagerConfiguration, preDeletionHooks, threadIdGuessingAlgorithm, clock); eventBus.register(quotaUpdater); eventBus.register(new MailboxAnnotationListener(mapperFactory, sessionProvider)); diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraTestSystemFixture.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraTestSystemFixture.java index a31836e8b2..774959e2ca 100644 --- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraTestSystemFixture.java +++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/CassandraTestSystemFixture.java @@ -21,6 +21,8 @@ package org.apache.james.mailbox.cassandra; import static org.mockito.Mockito.mock; +import java.time.Instant; + import org.apache.james.backends.cassandra.CassandraCluster; import org.apache.james.events.EventBus; import org.apache.james.events.EventBusTestFixture; @@ -56,6 +58,7 @@ import org.apache.james.mailbox.store.quota.QuotaComponents; import org.apache.james.mailbox.store.search.MessageSearchIndex; import org.apache.james.mailbox.store.search.SimpleMessageSearchIndex; import org.apache.james.metrics.tests.RecordingMetricFactory; +import org.apache.james.utils.UpdatableTickingClock; public class CassandraTestSystemFixture { @@ -78,7 +81,7 @@ public class CassandraTestSystemFixture { CassandraMailboxManager cassandraMailboxManager = new CassandraMailboxManager(mapperFactory, sessionProvider, new NoMailboxPathLocker(), new MessageParser(), new CassandraMessageId.Factory(), eventBus, annotationManager, storeRightManager, quotaComponents, index, MailboxManagerConfiguration.DEFAULT, PreDeletionHooks.NO_PRE_DELETION_HOOK, - new NaiveThreadIdGuessingAlgorithm()); + new NaiveThreadIdGuessingAlgorithm(), new UpdatableTickingClock(Instant.now())); eventBus.register(new MailboxAnnotationListener(mapperFactory, sessionProvider)); eventBus.register(mapperFactory.deleteMessageListener()); diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxManagerAttachmentTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxManagerAttachmentTest.java index 273f354e8e..a0e3b7e50c 100644 --- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxManagerAttachmentTest.java +++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMailboxManagerAttachmentTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.io.InputStream; +import java.time.Instant; import org.apache.james.backends.cassandra.CassandraClusterExtension; import org.apache.james.events.EventBusTestFixture; @@ -55,6 +56,7 @@ import org.apache.james.mailbox.store.quota.QuotaComponents; import org.apache.james.mailbox.store.search.MessageSearchIndex; import org.apache.james.mailbox.store.search.SimpleMessageSearchIndex; import org.apache.james.metrics.tests.RecordingMetricFactory; +import org.apache.james.utils.UpdatableTickingClock; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.RegisterExtension; @@ -92,13 +94,13 @@ class CassandraMailboxManagerAttachmentTest extends AbstractMailboxManagerAttach mailboxManager = new CassandraMailboxManager(mailboxSessionMapperFactory, sessionProvider, new NoMailboxPathLocker(), new MessageParser(), messageIdFactory, eventBus, annotationManager, storeRightManager, quotaComponents, - index, MailboxManagerConfiguration.DEFAULT, PreDeletionHooks.NO_PRE_DELETION_HOOK, threadIdGuessingAlgorithm); + index, MailboxManagerConfiguration.DEFAULT, PreDeletionHooks.NO_PRE_DELETION_HOOK, threadIdGuessingAlgorithm, new UpdatableTickingClock(Instant.now())); MessageParser failingMessageParser = mock(MessageParser.class); when(failingMessageParser.retrieveAttachments(any(InputStream.class))) .thenThrow(new RuntimeException("Message parser set to fail")); parseFailingMailboxManager = new CassandraMailboxManager(mailboxSessionMapperFactory, sessionProvider, new NoMailboxPathLocker(), failingMessageParser, messageIdFactory, - eventBus, annotationManager, storeRightManager, quotaComponents, index, MailboxManagerConfiguration.DEFAULT, PreDeletionHooks.NO_PRE_DELETION_HOOK, threadIdGuessingAlgorithm); + eventBus, annotationManager, storeRightManager, quotaComponents, index, MailboxManagerConfiguration.DEFAULT, PreDeletionHooks.NO_PRE_DELETION_HOOK, threadIdGuessingAlgorithm, new UpdatableTickingClock(Instant.now())); } @Override diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3Test.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3Test.java index 9b6f6fd2ce..23f64fa854 100644 --- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3Test.java +++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageDAOV3Test.java @@ -18,12 +18,14 @@ ****************************************************************/ package org.apache.james.mailbox.cassandra.mail; +import static org.apache.james.mailbox.store.mail.model.MailboxMessage.EMPTY_SAVE_DATE; import static org.assertj.core.api.Assertions.assertThat; import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Date; import java.util.List; +import java.util.Optional; import javax.mail.Flags; @@ -112,7 +114,7 @@ class CassandraMessageDAOV3Test { @Test void saveShouldSaveNullValueForTextualLineCountAsZero() throws Exception { - message = createMessage(messageId, threadId, CONTENT, BODY_START, new PropertyBuilder(), NO_ATTACHMENT); + message = createMessage(messageId, threadId, CONTENT, BODY_START, new PropertyBuilder(), NO_ATTACHMENT, EMPTY_SAVE_DATE); testee.save(message).block(); @@ -128,7 +130,7 @@ class CassandraMessageDAOV3Test { long textualLineCount = 10L; PropertyBuilder propertyBuilder = new PropertyBuilder(); propertyBuilder.setTextualLineCount(textualLineCount); - message = createMessage(messageId, threadId, CONTENT, BODY_START, propertyBuilder, NO_ATTACHMENT); + message = createMessage(messageId, threadId, CONTENT, BODY_START, propertyBuilder, NO_ATTACHMENT, EMPTY_SAVE_DATE); testee.save(message).block(); @@ -140,7 +142,7 @@ class CassandraMessageDAOV3Test { @Test void saveShouldStoreMessageWithFullContent() throws Exception { - message = createMessage(messageId, threadId, CONTENT, BODY_START, new PropertyBuilder(), NO_ATTACHMENT); + message = createMessage(messageId, threadId, CONTENT, BODY_START, new PropertyBuilder(), NO_ATTACHMENT, EMPTY_SAVE_DATE); testee.save(message).block(); @@ -153,7 +155,7 @@ class CassandraMessageDAOV3Test { @Test void saveShouldStoreMessageWithHeaderContent() throws Exception { - message = createMessage(messageId, threadId, CONTENT, BODY_START, new PropertyBuilder(), NO_ATTACHMENT); + message = createMessage(messageId, threadId, CONTENT, BODY_START, new PropertyBuilder(), NO_ATTACHMENT, EMPTY_SAVE_DATE); testee.save(message).block(); @@ -172,8 +174,8 @@ class CassandraMessageDAOV3Test { @Test void blobReferencesShouldReturnAllBlobs() throws Exception { - message = createMessage(messageId, threadId, CONTENT, BODY_START, new PropertyBuilder(), NO_ATTACHMENT); - MailboxMessage message2 = createMessage(messageId2, threadId, CONTENT_2, BODY_START, new PropertyBuilder(), NO_ATTACHMENT); + message = createMessage(messageId, threadId, CONTENT, BODY_START, new PropertyBuilder(), NO_ATTACHMENT, EMPTY_SAVE_DATE); + MailboxMessage message2 = createMessage(messageId2, threadId, CONTENT_2, BODY_START, new PropertyBuilder(), NO_ATTACHMENT, EMPTY_SAVE_DATE); testee.save(message).block(); testee.save(message2).block(); @@ -181,13 +183,15 @@ class CassandraMessageDAOV3Test { .hasSize(4); } - private SimpleMailboxMessage createMessage(MessageId messageId, ThreadId threadId, String content, int bodyStart, PropertyBuilder propertyBuilder, Collection<MessageAttachmentMetadata> attachments) { + private SimpleMailboxMessage createMessage(MessageId messageId, ThreadId threadId, String content, int bodyStart, PropertyBuilder propertyBuilder, + Collection<MessageAttachmentMetadata> attachments, Optional<Date> saveDate) { return SimpleMailboxMessage.builder() .messageId(messageId) .threadId(threadId) .mailboxId(MAILBOX_ID) .uid(messageUid) .internalDate(new Date()) + .saveDate(saveDate) .bodyStartOctet(bodyStart) .size(content.length()) .content(new ByteContent(content.getBytes(StandardCharsets.UTF_8))) diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAOTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAOTest.java index 6f76a71fef..a3f65698d1 100644 --- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAOTest.java +++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdDAOTest.java @@ -207,6 +207,58 @@ class CassandraMessageIdDAOTest { assertThat(message.get().getComposedMessageId()).isEqualTo(composedMessageIdWithMetaData); } + @Test + void shouldHandleNullSaveDateWell() { + CassandraMessageId messageId = messageIdFactory.generate(); + CassandraId mailboxId = CassandraId.timeBased(); + MessageUid messageUid = MessageUid.of(1); + + ComposedMessageIdWithMetaData composedMessageIdWithMetaData = ComposedMessageIdWithMetaData.builder() + .composedMessageId(new ComposedMessageId(mailboxId, messageId, messageUid)) + .flags(new Flags()) + .modSeq(ModSeq.of(1)) + .threadId(ThreadId.fromBaseMessageId(messageId)) + .build(); + testee.insert(CassandraMessageMetadata.builder() + .ids(composedMessageIdWithMetaData) + .internalDate(new Date()) + .bodyStartOctet(18L) + .size(36L) + .headerContent(Optional.of(HEADER_BLOB_ID_1)) + .build()) + .block(); + + Optional<CassandraMessageMetadata> message = testee.retrieve(mailboxId, messageUid).block(); + assertThat(message.get().getSaveDate()).isEmpty(); + } + + @Test + void shouldHandleSaveDateWell() { + CassandraMessageId messageId = messageIdFactory.generate(); + CassandraId mailboxId = CassandraId.timeBased(); + MessageUid messageUid = MessageUid.of(1); + Optional<Date> saveDate = Optional.of(new Date()); + + ComposedMessageIdWithMetaData composedMessageIdWithMetaData = ComposedMessageIdWithMetaData.builder() + .composedMessageId(new ComposedMessageId(mailboxId, messageId, messageUid)) + .flags(new Flags()) + .modSeq(ModSeq.of(1)) + .threadId(ThreadId.fromBaseMessageId(messageId)) + .build(); + testee.insert(CassandraMessageMetadata.builder() + .ids(composedMessageIdWithMetaData) + .internalDate(new Date()) + .saveDate(saveDate) + .bodyStartOctet(18L) + .size(36L) + .headerContent(Optional.of(HEADER_BLOB_ID_1)) + .build()) + .block(); + + Optional<CassandraMessageMetadata> message = testee.retrieve(mailboxId, messageUid).block(); + assertThat(message.get().getSaveDate()).isEqualTo(saveDate); + } + @Test void updateShouldUpdateModSeq() { CassandraMessageId messageId = messageIdFactory.generate(); diff --git a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAOTest.java b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAOTest.java index 0891106753..857ac74ae1 100644 --- a/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAOTest.java +++ b/mailbox/cassandra/src/test/java/org/apache/james/mailbox/cassandra/mail/CassandraMessageIdToImapUidDAOTest.java @@ -181,6 +181,57 @@ class CassandraMessageIdToImapUidDAOTest { .containsOnly(expectedComposedMessageId); } + @Test + void shouldHandleNullSaveDateWell() { + CassandraMessageId messageId = CassandraMessageId.Factory.of(Uuids.timeBased()); + CassandraId mailboxId = CassandraId.timeBased(); + MessageUid messageUid = MessageUid.of(1); + + testee.insert(CassandraMessageMetadata.builder() + .ids(ComposedMessageIdWithMetaData.builder() + .composedMessageId(new ComposedMessageId(mailboxId, messageId, messageUid)) + .flags(new Flags()) + .modSeq(ModSeq.of(1)) + .threadId(ThreadId.fromBaseMessageId(messageId)) + .build()) + .internalDate(new Date()) + .saveDate(Optional.empty()) + .bodyStartOctet(18L) + .size(36L) + .headerContent(Optional.of(HEADER_BLOB_ID_1)) + .build()) + .block(); + + List<CassandraMessageMetadata> messages = testee.retrieve(messageId, Optional.empty()).collectList().block(); + assertThat(messages.get(0).getSaveDate()).isEmpty(); + } + + @Test + void shouldHandleSaveDateWell() { + CassandraMessageId messageId = CassandraMessageId.Factory.of(Uuids.timeBased()); + CassandraId mailboxId = CassandraId.timeBased(); + MessageUid messageUid = MessageUid.of(1); + Optional<Date> saveDate = Optional.of(new Date()); + + testee.insert(CassandraMessageMetadata.builder() + .ids(ComposedMessageIdWithMetaData.builder() + .composedMessageId(new ComposedMessageId(mailboxId, messageId, messageUid)) + .flags(new Flags()) + .modSeq(ModSeq.of(1)) + .threadId(ThreadId.fromBaseMessageId(messageId)) + .build()) + .internalDate(new Date()) + .saveDate(saveDate) + .bodyStartOctet(18L) + .size(36L) + .headerContent(Optional.of(HEADER_BLOB_ID_1)) + .build()) + .block(); + + List<CassandraMessageMetadata> messages = testee.retrieve(messageId, Optional.empty()).collectList().block(); + assertThat(messages.get(0).getSaveDate()).isEqualTo(saveDate); + } + @Test void updateShouldReturnTrueWhenOldModSeqMatches() { CassandraMessageId messageId = CassandraMessageId.Factory.of(Uuids.timeBased()); diff --git a/mailbox/jpa/pom.xml b/mailbox/jpa/pom.xml index ee8e93a90f..8fbaf2ad5d 100644 --- a/mailbox/jpa/pom.xml +++ b/mailbox/jpa/pom.xml @@ -89,6 +89,11 @@ <artifactId>james-server-data-jpa</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>${james.groupId}</groupId> + <artifactId>james-server-testing</artifactId> + <scope>runtime</scope> + </dependency> <dependency> <groupId>${james.groupId}</groupId> <artifactId>james-server-util</artifactId> 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 cae6c2554c..879171416d 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 @@ -25,6 +25,7 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; import javax.mail.Flags; @@ -500,6 +501,11 @@ public abstract class AbstractJPAMailboxMessage implements MailboxMessage { return new ThreadId(getMessageId()); } + @Override + public Optional<Date> getSaveDate() { + return Optional.empty(); + } + public String toString() { return "message(" + "mailboxId = " + this.getMailboxId() + TOSTRING_SEPARATOR diff --git a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMailboxManager.java b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMailboxManager.java index 606d8a71e2..5346770f52 100644 --- a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMailboxManager.java +++ b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMailboxManager.java @@ -19,6 +19,7 @@ package org.apache.james.mailbox.jpa.openjpa; +import java.time.Clock; import java.util.EnumSet; import javax.inject.Inject; @@ -61,11 +62,12 @@ public class OpenJPAMailboxManager extends StoreMailboxManager { StoreRightManager storeRightManager, QuotaComponents quotaComponents, MessageSearchIndex index, - ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm) { + ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm, + Clock clock) { super(mapperFactory, sessionProvider, new JVMMailboxPathLocker(), messageParser, messageIdFactory, annotationManager, eventBus, storeRightManager, quotaComponents, - index, MailboxManagerConfiguration.DEFAULT, PreDeletionHooks.NO_PRE_DELETION_HOOK, threadIdGuessingAlgorithm); + index, MailboxManagerConfiguration.DEFAULT, PreDeletionHooks.NO_PRE_DELETION_HOOK, threadIdGuessingAlgorithm, clock); } @Override @@ -80,7 +82,8 @@ public class OpenJPAMailboxManager extends StoreMailboxManager { getMessageIdFactory(), configuration.getBatchSizes(), getStoreRightManager(), - getThreadIdGuessingAlgorithm()); + getThreadIdGuessingAlgorithm(), + getClock()); } @Override diff --git a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageFactory.java b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageFactory.java index b296258ced..79b08c492e 100644 --- a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageFactory.java +++ b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageFactory.java @@ -52,7 +52,7 @@ public class OpenJPAMessageFactory implements MessageFactory<AbstractJPAMailboxM } @Override - public AbstractJPAMailboxMessage createMessage(MessageId messageId, ThreadId threadId, Mailbox mailbox, Date internalDate, int size, int bodyStartOctet, Content content, Flags flags, PropertyBuilder propertyBuilder, List<MessageAttachmentMetadata> attachments) throws MailboxException { + public AbstractJPAMailboxMessage createMessage(MessageId messageId, ThreadId threadId, Mailbox mailbox, Date internalDate, Date saveDate, int size, int bodyStartOctet, Content content, Flags flags, PropertyBuilder propertyBuilder, List<MessageAttachmentMetadata> attachments) throws MailboxException { switch (feature) { case Streaming: return new JPAStreamingMailboxMessage(JPAMailbox.from(mailbox), internalDate, size, flags, content, diff --git a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageManager.java b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageManager.java index 21dfb88926..567e8d5ac6 100644 --- a/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageManager.java +++ b/mailbox/jpa/src/main/java/org/apache/james/mailbox/jpa/openjpa/OpenJPAMessageManager.java @@ -19,6 +19,8 @@ package org.apache.james.mailbox.jpa.openjpa; +import java.time.Clock; + import javax.mail.Flags; import org.apache.james.events.EventBus; @@ -48,10 +50,10 @@ public class OpenJPAMessageManager extends StoreMessageManager { MailboxPathLocker locker, Mailbox mailbox, QuotaManager quotaManager, QuotaRootResolver quotaRootResolver, MessageId.Factory messageIdFactory, BatchSizes batchSizes, - StoreRightManager storeRightManager, ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm) { + StoreRightManager storeRightManager, ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm, Clock clock) { super(StoreMailboxManager.DEFAULT_NO_MESSAGE_CAPABILITIES, mapperFactory, index, eventBus, locker, mailbox, quotaManager, quotaRootResolver, batchSizes, storeRightManager, PreDeletionHooks.NO_PRE_DELETION_HOOK, - new MessageStorer.WithoutAttachment(mapperFactory, messageIdFactory, new OpenJPAMessageFactory(OpenJPAMessageFactory.AdvancedFeature.None), threadIdGuessingAlgorithm)); + new MessageStorer.WithoutAttachment(mapperFactory, messageIdFactory, new OpenJPAMessageFactory(OpenJPAMessageFactory.AdvancedFeature.None), threadIdGuessingAlgorithm, clock)); } /** diff --git a/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAMailboxManagerTest.java b/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAMailboxManagerTest.java index bab657d713..b31ce31433 100644 --- a/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAMailboxManagerTest.java +++ b/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JPAMailboxManagerTest.java @@ -67,6 +67,12 @@ class JPAMailboxManagerTest extends MailboxManagerTest<OpenJPAMailboxManager> { } + @Nested + @Disabled("JPA does not support saveDate.") + class SaveDateTests { + + } + @Override protected EventBus retrieveEventBus(OpenJPAMailboxManager mailboxManager) { return mailboxManager.getEventBus(); diff --git a/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JpaMailboxManagerProvider.java b/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JpaMailboxManagerProvider.java index 1aafb340d2..b951986ab2 100644 --- a/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JpaMailboxManagerProvider.java +++ b/mailbox/jpa/src/test/java/org/apache/james/mailbox/jpa/JpaMailboxManagerProvider.java @@ -19,6 +19,8 @@ package org.apache.james.mailbox.jpa; +import java.time.Instant; + import javax.persistence.EntityManagerFactory; import org.apache.james.backends.jpa.JpaTestCluster; @@ -44,6 +46,7 @@ import org.apache.james.mailbox.store.quota.QuotaComponents; import org.apache.james.mailbox.store.search.MessageSearchIndex; import org.apache.james.mailbox.store.search.SimpleMessageSearchIndex; import org.apache.james.metrics.tests.RecordingMetricFactory; +import org.apache.james.utils.UpdatableTickingClock; public class JpaMailboxManagerProvider { @@ -71,6 +74,6 @@ public class JpaMailboxManagerProvider { return new OpenJPAMailboxManager(mf, sessionProvider, messageParser, new DefaultMessageId.Factory(), eventBus, annotationManager, - storeRightManager, quotaComponents, index, new NaiveThreadIdGuessingAlgorithm()); + storeRightManager, quotaComponents, index, new NaiveThreadIdGuessingAlgorithm(), new UpdatableTickingClock(Instant.now())); } } diff --git a/mailbox/memory/pom.xml b/mailbox/memory/pom.xml index ab6466cb74..13443d537f 100644 --- a/mailbox/memory/pom.xml +++ b/mailbox/memory/pom.xml @@ -74,6 +74,11 @@ <artifactId>james-server-data-memory</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>${james.groupId}</groupId> + <artifactId>james-server-testing</artifactId> + <scope>runtime</scope> + </dependency> <dependency> <groupId>${james.groupId}</groupId> <artifactId>james-server-util</artifactId> diff --git a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxManager.java b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxManager.java index bf27e7767d..f7380c749f 100644 --- a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxManager.java +++ b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMailboxManager.java @@ -19,6 +19,7 @@ package org.apache.james.mailbox.inmemory; +import java.time.Clock; import java.util.EnumSet; import javax.inject.Inject; @@ -60,10 +61,11 @@ public class InMemoryMailboxManager extends StoreMailboxManager { QuotaComponents quotaComponents, MessageSearchIndex searchIndex, PreDeletionHooks preDeletionHooks, - ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm) { + ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm, + Clock clock) { super(mailboxSessionMapperFactory, sessionProvider, locker, messageParser, messageIdFactory, annotationManager, eventBus, storeRightManager, quotaComponents, searchIndex, MailboxManagerConfiguration.DEFAULT, - preDeletionHooks, threadIdGuessingAlgorithm); + preDeletionHooks, threadIdGuessingAlgorithm, clock); } @Override @@ -90,6 +92,7 @@ public class InMemoryMailboxManager extends StoreMailboxManager { configuration.getBatchSizes(), getStoreRightManager(), getPreDeletionHooks(), - getThreadIdGuessingAlgorithm()); + getThreadIdGuessingAlgorithm(), + getClock()); } } diff --git a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMessageManager.java b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMessageManager.java index 9f95cf7f71..46d61cec72 100644 --- a/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMessageManager.java +++ b/mailbox/memory/src/main/java/org/apache/james/mailbox/inmemory/InMemoryMessageManager.java @@ -1,5 +1,7 @@ package org.apache.james.mailbox.inmemory; +import java.time.Clock; + import javax.mail.Flags; import org.apache.james.events.EventBus; @@ -33,12 +35,13 @@ public class InMemoryMessageManager extends StoreMessageManager { BatchSizes batchSizes, StoreRightManager storeRightManager, PreDeletionHooks preDeletionHooks, - ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm) { + ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm, + Clock clock) { super(InMemoryMailboxManager.MESSAGE_CAPABILITIES, mapperFactory, index, eventBus, locker, mailbox, quotaManager, quotaRootResolver, batchSizes, storeRightManager, preDeletionHooks, new MessageStorer.WithAttachment(mapperFactory, messageIdFactory, new MessageFactory.StoreMessageFactory(), (InMemoryMailboxSessionMapperFactory) mapperFactory, messageParser, - threadIdGuessingAlgorithm)); + threadIdGuessingAlgorithm, clock)); } @Override 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 724938e235..d6a5c06759 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 @@ -19,6 +19,7 @@ package org.apache.james.mailbox.inmemory.manager; +import java.time.Instant; import java.util.Collection; import java.util.Optional; import java.util.function.Function; @@ -72,6 +73,7 @@ import org.apache.james.mailbox.store.search.ListeningMessageSearchIndex; import org.apache.james.mailbox.store.search.MessageSearchIndex; import org.apache.james.mailbox.store.search.SimpleMessageSearchIndex; import org.apache.james.metrics.tests.RecordingMetricFactory; +import org.apache.james.utils.UpdatableTickingClock; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; @@ -312,6 +314,7 @@ 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); @@ -334,7 +337,8 @@ public class InMemoryIntegrationResources implements IntegrationResources<StoreM quotaComponents, index, hooks, - threadIdGuessingAlgorithm); + threadIdGuessingAlgorithm, + clock); eventBus.register(listeningCurrentQuotaUpdater); eventBus.register(new MailboxAnnotationListener(mailboxSessionMapperFactory, sessionProvider)); diff --git a/mailbox/store/pom.xml b/mailbox/store/pom.xml index 43b2320f26..199632266e 100644 --- a/mailbox/store/pom.xml +++ b/mailbox/store/pom.xml @@ -55,6 +55,11 @@ <artifactId>james-mdn</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>${james.groupId}</groupId> + <artifactId>james-server-testing</artifactId> + <scope>runtime</scope> + </dependency> <dependency> <groupId>${james.groupId}</groupId> <artifactId>james-server-util</artifactId> diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageFactory.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageFactory.java index dffe153301..0c6181e0ff 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageFactory.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageFactory.java @@ -21,6 +21,7 @@ package org.apache.james.mailbox.store; import java.util.Date; import java.util.List; +import java.util.Optional; import javax.mail.Flags; @@ -35,17 +36,17 @@ import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage; public interface MessageFactory<T extends MailboxMessage> { - T createMessage(MessageId messageId, ThreadId threadId, Mailbox mailbox, Date internalDate, int size, int bodyStartOctet, + T createMessage(MessageId messageId, ThreadId threadId, Mailbox mailbox, Date internalDate, Date saveDate, int size, int bodyStartOctet, Content content, Flags flags, PropertyBuilder propertyBuilder, List<MessageAttachmentMetadata> attachments) throws MailboxException; class StoreMessageFactory implements MessageFactory<SimpleMailboxMessage> { @Override - public SimpleMailboxMessage createMessage(MessageId messageId, ThreadId threadId, Mailbox mailbox, Date internalDate, int size, + public SimpleMailboxMessage createMessage(MessageId messageId, ThreadId threadId, Mailbox mailbox, Date internalDate, Date saveDate, int size, int bodyStartOctet, Content content, Flags flags, PropertyBuilder propertyBuilder, List<MessageAttachmentMetadata> attachments) { return new SimpleMailboxMessage(messageId, threadId, internalDate, size, bodyStartOctet, content, flags, propertyBuilder.build(), - mailbox.getMailboxId(), attachments); + mailbox.getMailboxId(), attachments, Optional.of(saveDate)); } } } diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageStorer.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageStorer.java index 797648020c..b612de7b45 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageStorer.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/MessageStorer.java @@ -20,6 +20,7 @@ package org.apache.james.mailbox.store; import java.io.InputStream; +import java.time.Clock; import java.util.Date; import java.util.List; import java.util.Optional; @@ -60,14 +61,14 @@ public interface MessageStorer { /** * If supported by the underlying implementation, this method will parse the messageContent to retrieve associated * attachments and will store them. - * + * <p> * Otherwize an empty optional will be returned on the right side of the pair. */ Mono<Pair<MessageMetaData, Optional<List<MessageAttachmentMetadata>>>> appendMessageToStore(Mailbox mailbox, Date internalDate, int size, int bodyStartOctet, Content content, Flags flags, PropertyBuilder propertyBuilder, Optional<Message> maybeMessage, MailboxSession session, HeaderImpl headers) throws MailboxException; /** * MessageStorer parsing, storing and returning AttachmentMetadata - * + * <p> * To be used with implementation that supports attachment content storage */ class WithAttachment implements MessageStorer { @@ -79,16 +80,18 @@ public interface MessageStorer { private final AttachmentMapperFactory attachmentMapperFactory; private final MessageParser messageParser; private final ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm; + private final Clock clock; public WithAttachment(MailboxSessionMapperFactory mapperFactory, MessageId.Factory messageIdFactory, MessageFactory messageFactory, AttachmentMapperFactory attachmentMapperFactory, - MessageParser messageParser, ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm) { + MessageParser messageParser, ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm, Clock clock) { this.mapperFactory = mapperFactory; this.messageIdFactory = messageIdFactory; this.messageFactory = messageFactory; this.attachmentMapperFactory = attachmentMapperFactory; this.messageParser = messageParser; this.threadIdGuessingAlgorithm = threadIdGuessingAlgorithm; + this.clock = clock; } @Override @@ -105,13 +108,14 @@ public interface MessageStorer { storeAttachments(messageId, content, maybeMessage, session) .zipWith(threadIdGuessingAlgorithm.guessThreadIdReactive(messageId, mimeMessageId, inReplyTo, references, subject, session)) .flatMap(Throwing.function((Tuple2<List<MessageAttachmentMetadata>, ThreadId> pair) -> { - List<MessageAttachmentMetadata> attachments = pair.getT1(); - ThreadId threadId = pair.getT2(); - - MailboxMessage message = messageFactory.createMessage(messageId, threadId, mailbox, internalDate, size, bodyStartOctet, content, flags, propertyBuilder, attachments); - return Mono.from(messageMapper.addReactive(mailbox, message)) - .map(metadata -> Pair.of(metadata, Optional.of(attachments))); - }).sneakyThrow())); + List<MessageAttachmentMetadata> attachments = pair.getT1(); + ThreadId threadId = pair.getT2(); + Date saveDate = Date.from(clock.instant()); + + MailboxMessage message = messageFactory.createMessage(messageId, threadId, mailbox, internalDate, saveDate, size, bodyStartOctet, content, flags, propertyBuilder, attachments); + return Mono.from(messageMapper.addReactive(mailbox, message)) + .map(metadata -> Pair.of(metadata, Optional.of(attachments))); + }).sneakyThrow())); } private Mono<List<MessageAttachmentMetadata>> storeAttachments(MessageId messageId, Content messageContent, Optional<Message> maybeMessage, MailboxSession session) { @@ -141,7 +145,7 @@ public interface MessageStorer { /** * MessageStorer that does not parse, store, nor return Attachment metadata - * + * <p> * To be used when the underlying implementation does not support attachment storage. */ class WithoutAttachment implements MessageStorer { @@ -149,12 +153,14 @@ public interface MessageStorer { private final MessageId.Factory messageIdFactory; private final MessageFactory messageFactory; private final ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm; + private final Clock clock; - public WithoutAttachment(MailboxSessionMapperFactory mapperFactory, MessageId.Factory messageIdFactory, MessageFactory messageFactory, ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm) { + public WithoutAttachment(MailboxSessionMapperFactory mapperFactory, MessageId.Factory messageIdFactory, MessageFactory messageFactory, ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm, Clock clock) { this.mapperFactory = mapperFactory; this.messageIdFactory = messageIdFactory; this.messageFactory = messageFactory; this.threadIdGuessingAlgorithm = threadIdGuessingAlgorithm; + this.clock = clock; } @Override @@ -169,7 +175,9 @@ public interface MessageStorer { return mapperFactory.getMessageMapper(session) .executeReactive(threadIdGuessingAlgorithm.guessThreadIdReactive(messageId, mimeMessageId, inReplyTo, references, subject, session) .flatMap(Throwing.function((ThreadId threadId) -> { - MailboxMessage message = messageFactory.createMessage(messageId, threadId, mailbox, internalDate, size, bodyStartOctet, content, flags, propertyBuilder, ImmutableList.of()); + Date saveDate = Date.from(clock.instant()); + + MailboxMessage message = messageFactory.createMessage(messageId, threadId, mailbox, internalDate, saveDate, size, bodyStartOctet, content, flags, propertyBuilder, ImmutableList.of()); return Mono.from(messageMapper.addReactive(mailbox, message)) .map(metadata -> Pair.of(metadata, Optional.empty())); }))); diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java index 9577ac410a..8d374d587a 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java @@ -22,6 +22,7 @@ package org.apache.james.mailbox.store; import static org.apache.james.mailbox.store.MailboxReactorUtils.block; import static org.apache.james.mailbox.store.mail.AbstractMessageMapper.UNLIMITED; +import java.time.Clock; import java.time.Duration; import java.util.EnumSet; import java.util.List; @@ -132,14 +133,15 @@ public class StoreMailboxManager implements MailboxManager { private final PreDeletionHooks preDeletionHooks; protected final MailboxManagerConfiguration configuration; private final ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm; + private final Clock clock; @Inject public StoreMailboxManager(MailboxSessionMapperFactory mailboxSessionMapperFactory, SessionProvider sessionProvider, MailboxPathLocker locker, MessageParser messageParser, - MessageId.Factory messageIdFactory, MailboxAnnotationManager annotationManager, + Factory messageIdFactory, MailboxAnnotationManager annotationManager, EventBus eventBus, StoreRightManager storeRightManager, QuotaComponents quotaComponents, MessageSearchIndex searchIndex, MailboxManagerConfiguration configuration, - PreDeletionHooks preDeletionHooks, ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm) { + PreDeletionHooks preDeletionHooks, ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm, Clock clock) { Preconditions.checkNotNull(eventBus); Preconditions.checkNotNull(mailboxSessionMapperFactory); @@ -158,6 +160,7 @@ public class StoreMailboxManager implements MailboxManager { this.configuration = configuration; this.preDeletionHooks = preDeletionHooks; this.threadIdGuessingAlgorithm = threadIdGuessingAlgorithm; + this.clock = clock; } public QuotaComponents getQuotaComponents() { @@ -231,6 +234,10 @@ public class StoreMailboxManager implements MailboxManager { return threadIdGuessingAlgorithm; } + public Clock getClock() { + return clock; + } + @Override public MailboxSession createSystemSession(Username userName) { return sessionProvider.createSystemSession(userName); @@ -271,7 +278,7 @@ public class StoreMailboxManager implements MailboxManager { return new StoreMessageManager(DEFAULT_NO_MESSAGE_CAPABILITIES, getMapperFactory(), getMessageSearchIndex(), getEventBus(), getLocker(), mailbox, quotaManager, getQuotaComponents().getQuotaRootResolver(), configuration.getBatchSizes(), - getStoreRightManager(), preDeletionHooks, new MessageStorer.WithoutAttachment(mailboxSessionMapperFactory, messageIdFactory, new MessageFactory.StoreMessageFactory(), threadIdGuessingAlgorithm)); + getStoreRightManager(), preDeletionHooks, new MessageStorer.WithoutAttachment(mailboxSessionMapperFactory, messageIdFactory, new MessageFactory.StoreMessageFactory(), threadIdGuessingAlgorithm, clock)); } @Override diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MessageMapper.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MessageMapper.java index 4cefb617ca..3d25e3507f 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MessageMapper.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MessageMapper.java @@ -289,6 +289,7 @@ public interface MessageMapper extends Mapper { * {@link MailboxMessage#getBodyOctets()} * {@link MailboxMessage#getFullContentOctets()} * {@link MailboxMessage#getInternalDate()} + * {@link MailboxMessage#getSaveDate()} * {@link MailboxMessage#getMailboxId()} * {@link MailboxMessage#getMediaType()} * {@link MailboxMessage#getModSeq()} diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/DelegatingMailboxMessage.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/DelegatingMailboxMessage.java index 94fa44dd3d..dc41398a47 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/DelegatingMailboxMessage.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/DelegatingMailboxMessage.java @@ -113,6 +113,7 @@ public abstract class DelegatingMailboxMessage implements MailboxMessage { return message.getMessageId(); } + @Override public ThreadId getThreadId() { return new ThreadId(message.getMessageId()); } 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 a5d89b0503..6bfd3efa1f 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 @@ -18,6 +18,9 @@ ****************************************************************/ package org.apache.james.mailbox.store.mail.model; +import java.util.Date; +import java.util.Optional; + import javax.mail.Flags; import org.apache.james.mailbox.MessageUid; @@ -36,6 +39,8 @@ import org.apache.james.mailbox.model.ThreadId; */ public interface MailboxMessage extends Message, Comparable<MailboxMessage> { + Optional<Date> EMPTY_SAVE_DATE = Optional.empty(); + ThreadId getThreadId(); ComposedMessageIdWithMetaData getComposedMessageIdWithMetaData(); @@ -120,4 +125,6 @@ public interface MailboxMessage extends Message, Comparable<MailboxMessage> { } MailboxMessage copy(Mailbox mailbox) throws MailboxException; + + Optional<Date> getSaveDate(); } \ No newline at end of file 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 c611139539..e5caaa9bef 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 @@ -43,6 +43,7 @@ import org.apache.james.mailbox.model.ThreadId; import org.apache.james.mailbox.store.mail.model.DelegatingMailboxMessage; import org.apache.james.mailbox.store.mail.model.MailboxMessage; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; import com.google.common.base.Preconditions; @@ -59,6 +60,7 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage { private MessageId messageId; private ThreadId threadId; private Date internalDate; + private Optional<Date> saveDate = Optional.empty(); private Long size; private Integer bodyStartOctet; private Content content; @@ -94,6 +96,16 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage { return this; } + public Builder saveDate(Date saveDate) { + this.saveDate = Optional.ofNullable(saveDate); + return this; + } + + public Builder saveDate(Optional<Date> saveDate) { + this.saveDate = saveDate; + return this; + } + public Builder size(long size) { Preconditions.checkArgument(size >= 0, "size can not be negative"); this.size = size; @@ -149,7 +161,7 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage { ImmutableList<MessageAttachmentMetadata> attachments = this.attachments.build(); SimpleMailboxMessage simpleMailboxMessage = new SimpleMailboxMessage(messageId, threadId, internalDate, size, - bodyStartOctet, content, flags, properties, mailboxId, attachments); + bodyStartOctet, content, flags, properties, mailboxId, attachments, saveDate); uid.ifPresent(simpleMailboxMessage::setUid); modseq.ifPresent(simpleMailboxMessage::setModSeq); @@ -174,6 +186,7 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage { .content(copyFullContent(original)) .messageId(original.getMessageId()) .internalDate(original.getInternalDate()) + .saveDate(original.getSaveDate()) .size(original.getFullContentOctets()) .flags(original.createFlags()) .properties(original.getProperties()); @@ -195,6 +208,7 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage { private MessageUid uid; private final MailboxId mailboxId; private final ThreadId threadId; + private final Optional<Date> saveDate; private boolean answered; private boolean deleted; private boolean draft; @@ -206,7 +220,7 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage { public SimpleMailboxMessage(MessageId messageId, ThreadId threadId, Date internalDate, long size, int bodyStartOctet, Content content, Flags flags, - Properties properties, MailboxId mailboxId, List<MessageAttachmentMetadata> attachments) { + Properties properties, MailboxId mailboxId, List<MessageAttachmentMetadata> attachments, Optional<Date> saveDate) { super(new SimpleMessage( messageId, content, size, internalDate, @@ -215,17 +229,19 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage { properties, attachments)); - setFlags(flags); - this.mailboxId = mailboxId; - this.threadId = threadId; + setFlags(flags); + this.mailboxId = mailboxId; + this.threadId = threadId; + this.saveDate = saveDate; } + @VisibleForTesting public SimpleMailboxMessage(MessageId messageId, ThreadId threadId, Date internalDate, long size, int bodyStartOctet, Content content, Flags flags, Properties properties, MailboxId mailboxId) { this(messageId, threadId, internalDate, size, bodyStartOctet, content, flags, - properties, mailboxId, ImmutableList.of()); + properties, mailboxId, ImmutableList.of(), EMPTY_SAVE_DATE); } @Override @@ -303,6 +319,11 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage { this.uid = uid; } + @Override + public Optional<Date> getSaveDate() { + return saveDate; + } + @Override public synchronized void setFlags(Flags flags) { answered = flags.contains(Flags.Flag.ANSWERED); @@ -333,6 +354,7 @@ public class SimpleMailboxMessage extends DelegatingMailboxMessage { .add("uid", this.uid) .add("mailboxId", this.mailboxId) .add("threadId", this.threadId) + .add("saveDate", this.saveDate) .add("answered", this.answered) .add("deleted", this.deleted) .add("draft", this.draft) diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageBuilder.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageBuilder.java index 79eecaadcd..91de349a16 100644 --- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageBuilder.java +++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/MessageBuilder.java @@ -26,6 +26,7 @@ import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import javax.mail.Flags; @@ -50,6 +51,7 @@ public class MessageBuilder { private TestId mailboxId = TestId.of(113); private MessageUid uid = MessageUid.of(776); private Date internalDate = new Date(); + private Optional<Date> saveDate = Optional.of(new Date()); private int size = 8867; private Flags flags = new Flags(); private byte[] body = {}; @@ -83,7 +85,7 @@ public class MessageBuilder { byte[] headerContent = getHeaderContent(); ThreadId threadId = ThreadId.fromBaseMessageId(messageId); SimpleMailboxMessage mailboxMessage = new SimpleMailboxMessage(messageId, threadId, internalDate, size, headerContent.length, - new ByteContent(Bytes.concat(headerContent, body)), flags, new PropertyBuilder().build(), mailboxId, NO_ATTACHMENTS); + new ByteContent(Bytes.concat(headerContent, body)), flags, new PropertyBuilder().build(), mailboxId, NO_ATTACHMENTS, saveDate); mailboxMessage.setUid(uid); return mailboxMessage; } diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/StoreMailboxManagerTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/StoreMailboxManagerTest.java index f6fbfa3442..8ac2b58d86 100644 --- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/StoreMailboxManagerTest.java +++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/StoreMailboxManagerTest.java @@ -25,6 +25,8 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.time.Instant; + import org.apache.james.core.Username; import org.apache.james.events.InVMEventBus; import org.apache.james.events.MemoryEventDeadLetters; @@ -36,9 +38,9 @@ import org.apache.james.mailbox.MailboxSessionUtil; import org.apache.james.mailbox.MessageManager; import org.apache.james.mailbox.acl.UnionMailboxACLResolver; import org.apache.james.mailbox.exception.BadCredentialsException; +import org.apache.james.mailbox.exception.ForbiddenDelegationException; import org.apache.james.mailbox.exception.MailboxException; import org.apache.james.mailbox.exception.MailboxNotFoundException; -import org.apache.james.mailbox.exception.ForbiddenDelegationException; import org.apache.james.mailbox.exception.UserDoesNotExistException; import org.apache.james.mailbox.model.Mailbox; import org.apache.james.mailbox.model.MailboxACL; @@ -58,6 +60,7 @@ import org.apache.james.mailbox.store.quota.QuotaComponents; import org.apache.james.mailbox.store.search.MessageSearchIndex; import org.apache.james.mailbox.store.search.SimpleMessageSearchIndex; import org.apache.james.metrics.tests.RecordingMetricFactory; +import org.apache.james.utils.UpdatableTickingClock; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -110,7 +113,7 @@ class StoreMailboxManagerTest { storeMailboxManager = new StoreMailboxManager(mockedMapperFactory, sessionProvider, new JVMMailboxPathLocker(), new MessageParser(), messageIdFactory, annotationManager, eventBus, storeRightManager, quotaComponents, index, MailboxManagerConfiguration.DEFAULT, - PreDeletionHooks.NO_PRE_DELETION_HOOK, threadIdGuessingAlgorithm); + PreDeletionHooks.NO_PRE_DELETION_HOOK, threadIdGuessingAlgorithm, new UpdatableTickingClock(Instant.now())); } @Test diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageWithAttachmentMapperTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageWithAttachmentMapperTest.java index 42d66e0098..c39bba5e45 100644 --- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageWithAttachmentMapperTest.java +++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/MessageWithAttachmentMapperTest.java @@ -19,6 +19,7 @@ package org.apache.james.mailbox.store.mail.model; +import static org.apache.james.mailbox.store.mail.model.MailboxMessage.EMPTY_SAVE_DATE; import static org.apache.james.mailbox.store.mail.model.MessageAssert.assertThat; import static org.assertj.core.api.Assertions.assertThat; @@ -181,7 +182,7 @@ public abstract class MessageWithAttachmentMapperTest { } private SimpleMailboxMessage createMessage(Mailbox mailbox, MessageId messageId, ThreadId threadId, String content, int bodyStart, PropertyBuilder propertyBuilder, List<MessageAttachmentMetadata> attachments) { - return new SimpleMailboxMessage(messageId, threadId, new Date(), content.length(), bodyStart, new ByteContent(content.getBytes()), new Flags(), propertyBuilder.build(), mailbox.getMailboxId(), attachments); + return new SimpleMailboxMessage(messageId, threadId, new Date(), content.length(), bodyStart, new ByteContent(content.getBytes()), new Flags(), propertyBuilder.build(), mailbox.getMailboxId(), attachments, EMPTY_SAVE_DATE); } private SimpleMailboxMessage createMessage(Mailbox mailbox, MessageId messageId, ThreadId threadId, String content, int bodyStart, PropertyBuilder propertyBuilder) { diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailboxMessageTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailboxMessageTest.java index 217009043d..4f4ed98701 100644 --- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailboxMessageTest.java +++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailboxMessageTest.java @@ -19,6 +19,7 @@ package org.apache.james.mailbox.store.mail.model.impl; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.io.IOException; @@ -26,6 +27,8 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Calendar; import java.util.Date; +import java.util.List; +import java.util.Optional; import javax.mail.Flags; @@ -43,6 +46,7 @@ import org.apache.james.mailbox.model.TestId; import org.apache.james.mailbox.model.TestMessageId; import org.apache.james.mailbox.model.ThreadId; import org.apache.james.mailbox.store.mail.model.DefaultMessageId; +import org.apache.james.mailbox.store.mail.model.MailboxMessage; import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -113,13 +117,16 @@ class SimpleMailboxMessageTest { propertyBuilder.setSubType(plain); MessageId messageId = new TestMessageId.Factory().generate(); ThreadId threadId = ThreadId.fromBaseMessageId(messageId); + Optional<Date> saveDate = Optional.of(new Date()); SimpleMailboxMessage original = new SimpleMailboxMessage(messageId, threadId, new Date(), MESSAGE_CONTENT.length(), BODY_START_OCTET, CONTENT_STREAM, new Flags(), propertyBuilder.build(), - TEST_ID); + TEST_ID, + List.of(), + saveDate); SimpleMailboxMessage copy = SimpleMailboxMessage.copy(TestId.of(1337), original); @@ -132,7 +139,7 @@ class SimpleMailboxMessageTest { assertThat(SimpleMailboxMessage.copy(TEST_ID, original).getTextualLineCount()).isEqualTo(textualLineCount); assertThat(SimpleMailboxMessage.copy(TEST_ID, original).getMediaType()).isEqualTo(text); assertThat(SimpleMailboxMessage.copy(TEST_ID, original).getSubType()).isEqualTo(plain); - + assertThat(SimpleMailboxMessage.copy(TEST_ID, original).getSaveDate()).isEqualTo(saveDate); } private static SimpleMailboxMessage buildMessage(String content) { @@ -254,6 +261,22 @@ class SimpleMailboxMessageTest { .isInstanceOf(NullPointerException.class); } + @Test + void buildShouldNotThrowOnMissingSaveDate() { + assertThatCode(() -> SimpleMailboxMessage.builder() + .messageId(MESSAGE_ID) + .mailboxId(TEST_ID) + .threadId(THREAD_ID) + .internalDate(new Date()) + .bodyStartOctet(BODY_START_OCTET) + .size(SIZE) + .content(CONTENT_STREAM) + .flags(new Flags()) + .properties(new PropertyBuilder()) + .build()) + .doesNotThrowAnyException(); + } + @Test void buildShouldThrowOnMissingMailboxId() { Date internalDate = new Date(); @@ -370,4 +393,40 @@ class SimpleMailboxMessageTest { assertThat(message.getThreadId().getBaseMessageId()).isInstanceOf(MessageId.class); } + @Test + void simpleMessageShouldReturnSaveDateWhenEmpty() { + MailboxMessage mailboxMessage = SimpleMailboxMessage.builder() + .messageId(MESSAGE_ID) + .mailboxId(TEST_ID) + .threadId(THREAD_ID) + .internalDate(new Date()) + .bodyStartOctet(BODY_START_OCTET) + .size(SIZE) + .content(CONTENT_STREAM) + .flags(new Flags()) + .properties(new PropertyBuilder()) + .build(); + + assertThat(mailboxMessage.getSaveDate()).isEmpty(); + } + + @Test + void simpleMessageShouldReturnSaveDate() { + Optional<Date> saveDate = Optional.of(new Date()); + MailboxMessage mailboxMessage = SimpleMailboxMessage.builder() + .messageId(MESSAGE_ID) + .mailboxId(TEST_ID) + .threadId(THREAD_ID) + .internalDate(new Date()) + .saveDate(saveDate) + .bodyStartOctet(BODY_START_OCTET) + .size(SIZE) + .content(CONTENT_STREAM) + .flags(new Flags()) + .properties(new PropertyBuilder()) + .build(); + + assertThat(mailboxMessage.getSaveDate()).isEqualTo(saveDate); + } + } diff --git a/mpt/impl/imap-mailbox/cassandra/pom.xml b/mpt/impl/imap-mailbox/cassandra/pom.xml index d50eb2454b..2d0ef3d317 100644 --- a/mpt/impl/imap-mailbox/cassandra/pom.xml +++ b/mpt/impl/imap-mailbox/cassandra/pom.xml @@ -76,6 +76,11 @@ <artifactId>event-bus-in-vm</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>${james.groupId}</groupId> + <artifactId>james-server-testing</artifactId> + <scope>runtime</scope> + </dependency> <dependency> <groupId>${james.groupId}</groupId> <artifactId>metrics-tests</artifactId> diff --git a/mpt/impl/imap-mailbox/cassandra/src/test/java/org/apache/james/mpt/imapmailbox/cassandra/host/CassandraHostSystem.java b/mpt/impl/imap-mailbox/cassandra/src/test/java/org/apache/james/mpt/imapmailbox/cassandra/host/CassandraHostSystem.java index e9b7b3c789..055718d03e 100644 --- a/mpt/impl/imap-mailbox/cassandra/src/test/java/org/apache/james/mpt/imapmailbox/cassandra/host/CassandraHostSystem.java +++ b/mpt/impl/imap-mailbox/cassandra/src/test/java/org/apache/james/mpt/imapmailbox/cassandra/host/CassandraHostSystem.java @@ -18,6 +18,8 @@ ****************************************************************/ package org.apache.james.mpt.imapmailbox.cassandra.host; +import java.time.Instant; + import org.apache.james.backends.cassandra.CassandraCluster; import org.apache.james.core.quota.QuotaCountLimit; import org.apache.james.core.quota.QuotaSizeLimit; @@ -65,6 +67,7 @@ import org.apache.james.metrics.tests.RecordingMetricFactory; import org.apache.james.mpt.api.ImapFeatures; import org.apache.james.mpt.api.ImapFeatures.Feature; import org.apache.james.mpt.host.JamesImapHostSystem; +import org.apache.james.utils.UpdatableTickingClock; import com.datastax.oss.driver.api.core.CqlSession; @@ -95,6 +98,7 @@ public class CassandraHostSystem extends JamesImapHostSystem { CqlSession session = cassandra.getConf(); CassandraMessageId.Factory messageIdFactory = new CassandraMessageId.Factory(); ThreadIdGuessingAlgorithm threadIdGuessingAlgorithm = new NaiveThreadIdGuessingAlgorithm(); + UpdatableTickingClock clock = new UpdatableTickingClock(Instant.now()); CassandraMailboxSessionMapperFactory mapperFactory = TestCassandraMailboxSessionMapperFactory.forTests( cassandra, messageIdFactory); @@ -123,7 +127,7 @@ public class CassandraHostSystem extends JamesImapHostSystem { mailboxManager = new CassandraMailboxManager(mapperFactory, sessionProvider, new JVMMailboxPathLocker(), new MessageParser(), messageIdFactory, eventBus, annotationManager, storeRightManager, quotaComponents, index, MailboxManagerConfiguration.DEFAULT, - PreDeletionHooks.NO_PRE_DELETION_HOOK, threadIdGuessingAlgorithm); + PreDeletionHooks.NO_PRE_DELETION_HOOK, threadIdGuessingAlgorithm, clock); eventBus.register(quotaUpdater); diff --git a/mpt/impl/imap-mailbox/jpa/pom.xml b/mpt/impl/imap-mailbox/jpa/pom.xml index 2a51980556..64553eea5f 100644 --- a/mpt/impl/imap-mailbox/jpa/pom.xml +++ b/mpt/impl/imap-mailbox/jpa/pom.xml @@ -74,6 +74,11 @@ <artifactId>event-bus-in-vm</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>${james.groupId}</groupId> + <artifactId>james-server-testing</artifactId> + <scope>runtime</scope> + </dependency> <dependency> <groupId>${james.groupId}</groupId> <artifactId>metrics-tests</artifactId> diff --git a/mpt/impl/imap-mailbox/jpa/src/test/java/org/apache/james/mpt/imapmailbox/jpa/host/JPAHostSystem.java b/mpt/impl/imap-mailbox/jpa/src/test/java/org/apache/james/mpt/imapmailbox/jpa/host/JPAHostSystem.java index 4ef7a4fa78..f2ecfd6b74 100644 --- a/mpt/impl/imap-mailbox/jpa/src/test/java/org/apache/james/mpt/imapmailbox/jpa/host/JPAHostSystem.java +++ b/mpt/impl/imap-mailbox/jpa/src/test/java/org/apache/james/mpt/imapmailbox/jpa/host/JPAHostSystem.java @@ -19,6 +19,8 @@ package org.apache.james.mpt.imapmailbox.jpa.host; +import java.time.Instant; + import javax.persistence.EntityManagerFactory; import org.apache.james.backends.jpa.JpaTestCluster; @@ -65,6 +67,7 @@ import org.apache.james.metrics.tests.RecordingMetricFactory; import org.apache.james.mpt.api.ImapFeatures; import org.apache.james.mpt.api.ImapFeatures.Feature; import org.apache.james.mpt.host.JamesImapHostSystem; +import org.apache.james.utils.UpdatableTickingClock; import com.google.common.collect.ImmutableList; @@ -116,7 +119,7 @@ public class JPAHostSystem extends JamesImapHostSystem { MessageSearchIndex index = new SimpleMessageSearchIndex(mapperFactory, mapperFactory, new DefaultTextExtractor(), attachmentContentLoader); mailboxManager = new OpenJPAMailboxManager(mapperFactory, sessionProvider, messageParser, new DefaultMessageId.Factory(), - eventBus, annotationManager, storeRightManager, quotaComponents, index, new NaiveThreadIdGuessingAlgorithm()); + eventBus, annotationManager, storeRightManager, quotaComponents, index, new NaiveThreadIdGuessingAlgorithm(), new UpdatableTickingClock(Instant.now())); eventBus.register(quotaUpdater); eventBus.register(new MailboxAnnotationListener(mapperFactory, sessionProvider)); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
