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 7abe77b1c81f76ff0efa5c7277bb8340b96ff8a7 Author: Rene Cordier <[email protected]> AuthorDate: Fri Aug 29 09:42:38 2025 +0700 JAMES-3340 Add threadId column in EmailQueryView and populate it via the listener --- .../projections/CassandraEmailQueryView.java | 12 ++- .../CassandraEmailQueryViewDataDefinition.java | 7 +- .../table/CassandraEmailQueryViewTable.java | 1 + .../projections/CassandraEmailQueryViewTest.java | 7 ++ .../projections/PostgresEmailQueryView.java | 5 +- .../projections/PostgresEmailQueryViewDAO.java | 5 +- .../PostgresEmailQueryViewDataDefinition.java | 2 + .../PostgresEmailQueryViewManagerRLSTest.java | 6 +- .../projections/PostgresEmailQueryViewTest.java | 7 ++ .../james/jmap/api/projections/EmailQueryView.java | 16 ++- .../memory/projections/MemoryEmailQueryView.java | 5 +- .../api/projections/EmailQueryViewContract.java | 113 +++++++++++---------- .../projections/MemoryEmailQueryViewTest.java | 8 ++ .../jmap/event/PopulateEmailQueryViewListener.java | 2 +- .../data/jmap/EmailQueryViewPopulator.java | 6 +- 15 files changed, 128 insertions(+), 74 deletions(-) diff --git a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/projections/CassandraEmailQueryView.java b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/projections/CassandraEmailQueryView.java index a73ed7ffb9..2f674d5a10 100644 --- a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/projections/CassandraEmailQueryView.java +++ b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/projections/CassandraEmailQueryView.java @@ -31,6 +31,7 @@ import static org.apache.james.jmap.cassandra.projections.table.CassandraEmailQu import static org.apache.james.jmap.cassandra.projections.table.CassandraEmailQueryViewTable.SENT_AT; import static org.apache.james.jmap.cassandra.projections.table.CassandraEmailQueryViewTable.TABLE_NAME_RECEIVED_AT; import static org.apache.james.jmap.cassandra.projections.table.CassandraEmailQueryViewTable.TABLE_NAME_SENT_AT; +import static org.apache.james.jmap.cassandra.projections.table.CassandraEmailQueryViewTable.THREAD_ID; import java.time.Instant; import java.time.ZonedDateTime; @@ -45,6 +46,7 @@ import org.apache.james.mailbox.cassandra.ids.CassandraId; import org.apache.james.mailbox.cassandra.ids.CassandraMessageId; import org.apache.james.mailbox.model.MailboxId; import org.apache.james.mailbox.model.MessageId; +import org.apache.james.mailbox.model.ThreadId; import org.apache.james.util.streams.Limit; import com.datastax.oss.driver.api.core.CqlSession; @@ -128,6 +130,7 @@ public class CassandraEmailQueryView implements EmailQueryView { .value(MAILBOX_ID, bindMarker(MAILBOX_ID)) .value(MESSAGE_ID, bindMarker(MESSAGE_ID)) .value(SENT_AT, bindMarker(SENT_AT)) + .value(THREAD_ID, bindMarker(THREAD_ID)) .build()); insertReceivedAt = session.prepare(insertInto(TABLE_NAME_RECEIVED_AT) @@ -135,6 +138,7 @@ public class CassandraEmailQueryView implements EmailQueryView { .value(MESSAGE_ID, bindMarker(MESSAGE_ID)) .value(RECEIVED_AT, bindMarker(RECEIVED_AT)) .value(SENT_AT, bindMarker(SENT_AT)) + .value(THREAD_ID, bindMarker(THREAD_ID)) .build()); deleteLookupRecord = session.prepare(deleteFrom(DATE_LOOKUP_TABLE) @@ -297,7 +301,7 @@ public class CassandraEmailQueryView implements EmailQueryView { } @Override - public Mono<Void> save(MailboxId mailboxId, ZonedDateTime sentAt, ZonedDateTime receivedAt, MessageId messageId) { + public Mono<Void> save(MailboxId mailboxId, ZonedDateTime sentAt, ZonedDateTime receivedAt, MessageId messageId, ThreadId threadId) { CassandraMessageId cassandraMessageId = (CassandraMessageId) messageId; CassandraId cassandraId = (CassandraId) mailboxId; @@ -310,12 +314,14 @@ public class CassandraEmailQueryView implements EmailQueryView { executor.executeVoid(insertSentAt.bind() .setUuid(MESSAGE_ID, cassandraMessageId.get()) .set(MAILBOX_ID, cassandraId.asUuid(), TypeCodecs.UUID) - .setInstant(SENT_AT, sentAt.toInstant())), + .setInstant(SENT_AT, sentAt.toInstant()) + .setUuid(THREAD_ID, ((CassandraMessageId) threadId.getBaseMessageId()).get())), executor.executeVoid(insertReceivedAt.bind() .setUuid(MESSAGE_ID, cassandraMessageId.get()) .set(MAILBOX_ID, cassandraId.asUuid(), TypeCodecs.UUID) .setInstant(RECEIVED_AT, receivedAt.toInstant()) - .setInstant(SENT_AT, sentAt.toInstant()))) + .setInstant(SENT_AT, sentAt.toInstant()) + .setUuid(THREAD_ID, ((CassandraMessageId) threadId.getBaseMessageId()).get()))) .then()); } } diff --git a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/projections/CassandraEmailQueryViewDataDefinition.java b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/projections/CassandraEmailQueryViewDataDefinition.java index be8806e819..195f775eb2 100644 --- a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/projections/CassandraEmailQueryViewDataDefinition.java +++ b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/projections/CassandraEmailQueryViewDataDefinition.java @@ -29,6 +29,7 @@ import static org.apache.james.jmap.cassandra.projections.table.CassandraEmailQu import static org.apache.james.jmap.cassandra.projections.table.CassandraEmailQueryViewTable.SENT_AT; import static org.apache.james.jmap.cassandra.projections.table.CassandraEmailQueryViewTable.TABLE_NAME_RECEIVED_AT; import static org.apache.james.jmap.cassandra.projections.table.CassandraEmailQueryViewTable.TABLE_NAME_SENT_AT; +import static org.apache.james.jmap.cassandra.projections.table.CassandraEmailQueryViewTable.THREAD_ID; import org.apache.james.backends.cassandra.components.CassandraDataDefinition; @@ -43,7 +44,8 @@ public interface CassandraEmailQueryViewDataDefinition { .statement(statement -> types -> statement .withPartitionKey(MAILBOX_ID, DataTypes.UUID) .withClusteringColumn(SENT_AT, DataTypes.TIMESTAMP) - .withClusteringColumn(MESSAGE_ID, DataTypes.UUID)) + .withClusteringColumn(MESSAGE_ID, DataTypes.UUID) + .withColumn(THREAD_ID, DataTypes.UUID)) .table(TABLE_NAME_RECEIVED_AT) .comment("Storing the JMAP projections for list of emails within a mailbox to not rely on OpenSearch for basic Email/query (sorts and filter on receivedAt).") @@ -54,7 +56,8 @@ public interface CassandraEmailQueryViewDataDefinition { .withPartitionKey(MAILBOX_ID, DataTypes.UUID) .withClusteringColumn(RECEIVED_AT, DataTypes.TIMESTAMP) .withClusteringColumn(MESSAGE_ID, DataTypes.UUID) - .withColumn(SENT_AT, DataTypes.TIMESTAMP)) + .withColumn(SENT_AT, DataTypes.TIMESTAMP) + .withColumn(THREAD_ID, DataTypes.UUID)) .table(DATE_LOOKUP_TABLE) .comment("Given a MailboxId+MessageId lookup the dates of a message to delete it.") diff --git a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/projections/table/CassandraEmailQueryViewTable.java b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/projections/table/CassandraEmailQueryViewTable.java index 65747c722f..a7ccea8f1b 100644 --- a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/projections/table/CassandraEmailQueryViewTable.java +++ b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/projections/table/CassandraEmailQueryViewTable.java @@ -30,4 +30,5 @@ public interface CassandraEmailQueryViewTable { CqlIdentifier MESSAGE_ID = CqlIdentifier.fromCql("messageId"); CqlIdentifier RECEIVED_AT = CqlIdentifier.fromCql("receivedAt"); CqlIdentifier SENT_AT = CqlIdentifier.fromCql("sentAt"); + CqlIdentifier THREAD_ID = CqlIdentifier.fromCql("thread_id"); } diff --git a/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/projections/CassandraEmailQueryViewTest.java b/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/projections/CassandraEmailQueryViewTest.java index f43d4fd8a0..33f0eabada 100644 --- a/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/projections/CassandraEmailQueryViewTest.java +++ b/server/data/data-jmap-cassandra/src/test/java/org/apache/james/jmap/cassandra/projections/CassandraEmailQueryViewTest.java @@ -27,6 +27,7 @@ import org.apache.james.mailbox.cassandra.ids.CassandraId; import org.apache.james.mailbox.cassandra.ids.CassandraMessageId; import org.apache.james.mailbox.model.MailboxId; import org.apache.james.mailbox.model.MessageId; +import org.apache.james.mailbox.model.ThreadId; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.RegisterExtension; @@ -37,6 +38,7 @@ public class CassandraEmailQueryViewTest implements EmailQueryViewContract { public static final CassandraMessageId MESSAGE_ID_2 = MESSAGE_ID_FACTORY.generate(); public static final CassandraMessageId MESSAGE_ID_3 = MESSAGE_ID_FACTORY.generate(); public static final CassandraMessageId MESSAGE_ID_4 = MESSAGE_ID_FACTORY.generate(); + public static final ThreadId THREAD_ID = ThreadId.fromBaseMessageId(MESSAGE_ID_FACTORY.generate()); @RegisterExtension static CassandraClusterExtension cassandra = new CassandraClusterExtension(CassandraEmailQueryViewDataDefinition.MODULE); @@ -77,4 +79,9 @@ public class CassandraEmailQueryViewTest implements EmailQueryViewContract { public MessageId messageId4() { return MESSAGE_ID_4; } + + @Override + public ThreadId threadId() { + return THREAD_ID; + } } diff --git a/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/projections/PostgresEmailQueryView.java b/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/projections/PostgresEmailQueryView.java index 0f801feecf..1e1b866faf 100644 --- a/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/projections/PostgresEmailQueryView.java +++ b/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/projections/PostgresEmailQueryView.java @@ -26,6 +26,7 @@ import jakarta.inject.Inject; import org.apache.james.jmap.api.projections.EmailQueryView; import org.apache.james.mailbox.model.MailboxId; import org.apache.james.mailbox.model.MessageId; +import org.apache.james.mailbox.model.ThreadId; import org.apache.james.mailbox.postgres.PostgresMailboxId; import org.apache.james.mailbox.postgres.PostgresMessageId; import org.apache.james.util.streams.Limit; @@ -82,7 +83,7 @@ public class PostgresEmailQueryView implements EmailQueryView { } @Override - public Mono<Void> save(MailboxId mailboxId, ZonedDateTime sentAt, ZonedDateTime receivedAt, MessageId messageId) { - return emailQueryViewDAO.save(PostgresMailboxId.class.cast(mailboxId), sentAt, receivedAt, PostgresMessageId.class.cast(messageId)); + public Mono<Void> save(MailboxId mailboxId, ZonedDateTime sentAt, ZonedDateTime receivedAt, MessageId messageId, ThreadId threadId) { + return emailQueryViewDAO.save(PostgresMailboxId.class.cast(mailboxId), sentAt, receivedAt, PostgresMessageId.class.cast(messageId), threadId); } } diff --git a/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/projections/PostgresEmailQueryViewDAO.java b/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/projections/PostgresEmailQueryViewDAO.java index 4cc5045957..609662856c 100644 --- a/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/projections/PostgresEmailQueryViewDAO.java +++ b/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/projections/PostgresEmailQueryViewDAO.java @@ -25,6 +25,7 @@ import static org.apache.james.jmap.postgres.projections.PostgresEmailQueryViewD import static org.apache.james.jmap.postgres.projections.PostgresEmailQueryViewDataDefinition.PostgresEmailQueryViewTable.RECEIVED_AT; import static org.apache.james.jmap.postgres.projections.PostgresEmailQueryViewDataDefinition.PostgresEmailQueryViewTable.SENT_AT; import static org.apache.james.jmap.postgres.projections.PostgresEmailQueryViewDataDefinition.PostgresEmailQueryViewTable.TABLE_NAME; +import static org.apache.james.jmap.postgres.projections.PostgresEmailQueryViewDataDefinition.PostgresEmailQueryViewTable.THREAD_ID; import java.time.ZonedDateTime; @@ -33,6 +34,7 @@ import jakarta.inject.Named; import org.apache.james.backends.postgres.utils.PostgresExecutor; import org.apache.james.mailbox.model.MessageId; +import org.apache.james.mailbox.model.ThreadId; import org.apache.james.mailbox.postgres.PostgresMailboxId; import org.apache.james.mailbox.postgres.PostgresMessageId; import org.apache.james.util.streams.Limit; @@ -131,12 +133,13 @@ public class PostgresEmailQueryViewDAO { .where(MAILBOX_ID.eq(mailboxId.asUuid())))); } - public Mono<Void> save(PostgresMailboxId mailboxId, ZonedDateTime sentAt, ZonedDateTime receivedAt, PostgresMessageId messageId) { + public Mono<Void> save(PostgresMailboxId mailboxId, ZonedDateTime sentAt, ZonedDateTime receivedAt, PostgresMessageId messageId, ThreadId threadId) { return postgresExecutor.executeVoid(dslContext -> Mono.from(dslContext.insertInto(TABLE_NAME) .set(MAILBOX_ID, mailboxId.asUuid()) .set(MESSAGE_ID, messageId.asUuid()) .set(SENT_AT, sentAt.toOffsetDateTime()) .set(RECEIVED_AT, receivedAt.toOffsetDateTime()) + .set(THREAD_ID, ((PostgresMessageId) threadId.getBaseMessageId()).asUuid()) .onConflictOnConstraint(PK_CONSTRAINT_NAME) .doNothing())); } diff --git a/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/projections/PostgresEmailQueryViewDataDefinition.java b/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/projections/PostgresEmailQueryViewDataDefinition.java index b2eb132100..d023576f51 100644 --- a/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/projections/PostgresEmailQueryViewDataDefinition.java +++ b/server/data/data-jmap-postgres/src/main/java/org/apache/james/jmap/postgres/projections/PostgresEmailQueryViewDataDefinition.java @@ -46,6 +46,7 @@ public interface PostgresEmailQueryViewDataDefinition { Field<UUID> MESSAGE_ID = PostgresMessageDataDefinition.MESSAGE_ID; Field<OffsetDateTime> RECEIVED_AT = DSL.field("received_at", SQLDataType.TIMESTAMPWITHTIMEZONE.notNull()); Field<OffsetDateTime> SENT_AT = DSL.field("sent_at", SQLDataType.TIMESTAMPWITHTIMEZONE.notNull()); + Field<UUID> THREAD_ID = PostgresMessageDataDefinition.MessageToMailboxTable.THREAD_ID; Name PK_CONSTRAINT_NAME = DSL.name("email_query_view_pkey"); @@ -55,6 +56,7 @@ public interface PostgresEmailQueryViewDataDefinition { .column(MESSAGE_ID) .column(RECEIVED_AT) .column(SENT_AT) + .column(THREAD_ID) .constraint(DSL.constraint(PK_CONSTRAINT_NAME).primaryKey(MAILBOX_ID, MESSAGE_ID)))) .supportsRowLevelSecurity() .build(); diff --git a/server/data/data-jmap-postgres/src/test/java/org/apache/james/jmap/postgres/projections/PostgresEmailQueryViewManagerRLSTest.java b/server/data/data-jmap-postgres/src/test/java/org/apache/james/jmap/postgres/projections/PostgresEmailQueryViewManagerRLSTest.java index fbd6d96693..99427ba58e 100644 --- a/server/data/data-jmap-postgres/src/test/java/org/apache/james/jmap/postgres/projections/PostgresEmailQueryViewManagerRLSTest.java +++ b/server/data/data-jmap-postgres/src/test/java/org/apache/james/jmap/postgres/projections/PostgresEmailQueryViewManagerRLSTest.java @@ -26,6 +26,7 @@ import java.time.ZonedDateTime; import org.apache.james.backends.postgres.PostgresExtension; import org.apache.james.core.Username; import org.apache.james.jmap.api.projections.EmailQueryViewManager; +import org.apache.james.mailbox.model.ThreadId; import org.apache.james.mailbox.postgres.PostgresMailboxId; import org.apache.james.mailbox.postgres.PostgresMessageId; import org.apache.james.util.streams.Limit; @@ -37,6 +38,7 @@ public class PostgresEmailQueryViewManagerRLSTest { public static final PostgresMailboxId MAILBOX_ID_1 = PostgresMailboxId.generate(); public static final PostgresMessageId.Factory MESSAGE_ID_FACTORY = new PostgresMessageId.Factory(); public static final PostgresMessageId MESSAGE_ID_1 = MESSAGE_ID_FACTORY.generate(); + public static final ThreadId THREAD_ID_1 = ThreadId.fromBaseMessageId(MESSAGE_ID_FACTORY.generate()); private static final ZonedDateTime DATE_1 = ZonedDateTime.parse("2010-10-30T15:12:00Z"); private static final ZonedDateTime DATE_2 = ZonedDateTime.parse("2010-10-30T16:12:00Z"); @@ -54,7 +56,7 @@ public class PostgresEmailQueryViewManagerRLSTest { void emailQueryViewCanBeAccessedAtTheDataLevelByMembersOfTheSameDomain() { Username username = Username.of("alice@domain1"); - emailQueryViewManager.getEmailQueryView(username).save(MAILBOX_ID_1, DATE_1, DATE_2, MESSAGE_ID_1).block(); + emailQueryViewManager.getEmailQueryView(username).save(MAILBOX_ID_1, DATE_1, DATE_2, MESSAGE_ID_1, THREAD_ID_1).block(); assertThat(emailQueryViewManager.getEmailQueryView(username).listMailboxContentSortedByReceivedAt(MAILBOX_ID_1, Limit.limit(1)).collectList().block()) .isNotEmpty(); @@ -65,7 +67,7 @@ public class PostgresEmailQueryViewManagerRLSTest { Username username = Username.of("alice@domain1"); Username username2 = Username.of("bob@domain2"); - emailQueryViewManager.getEmailQueryView(username).save(MAILBOX_ID_1, DATE_1, DATE_2, MESSAGE_ID_1).block(); + emailQueryViewManager.getEmailQueryView(username).save(MAILBOX_ID_1, DATE_1, DATE_2, MESSAGE_ID_1, THREAD_ID_1).block(); assertThat(emailQueryViewManager.getEmailQueryView(username2).listMailboxContentSortedByReceivedAt(MAILBOX_ID_1, Limit.limit(1)).collectList().block()) .isEmpty(); diff --git a/server/data/data-jmap-postgres/src/test/java/org/apache/james/jmap/postgres/projections/PostgresEmailQueryViewTest.java b/server/data/data-jmap-postgres/src/test/java/org/apache/james/jmap/postgres/projections/PostgresEmailQueryViewTest.java index 652fa676d1..29c366d2ca 100644 --- a/server/data/data-jmap-postgres/src/test/java/org/apache/james/jmap/postgres/projections/PostgresEmailQueryViewTest.java +++ b/server/data/data-jmap-postgres/src/test/java/org/apache/james/jmap/postgres/projections/PostgresEmailQueryViewTest.java @@ -24,6 +24,7 @@ import org.apache.james.jmap.api.projections.EmailQueryView; import org.apache.james.jmap.api.projections.EmailQueryViewContract; import org.apache.james.mailbox.model.MailboxId; import org.apache.james.mailbox.model.MessageId; +import org.apache.james.mailbox.model.ThreadId; import org.apache.james.mailbox.postgres.PostgresMailboxId; import org.apache.james.mailbox.postgres.PostgresMessageId; import org.junit.jupiter.api.extension.RegisterExtension; @@ -35,6 +36,7 @@ public class PostgresEmailQueryViewTest implements EmailQueryViewContract { public static final PostgresMessageId MESSAGE_ID_2 = MESSAGE_ID_FACTORY.generate(); public static final PostgresMessageId MESSAGE_ID_3 = MESSAGE_ID_FACTORY.generate(); public static final PostgresMessageId MESSAGE_ID_4 = MESSAGE_ID_FACTORY.generate(); + public static final ThreadId THREAD_ID = ThreadId.fromBaseMessageId(MESSAGE_ID_FACTORY.generate()); @RegisterExtension static PostgresExtension postgresExtension = PostgresExtension.withoutRowLevelSecurity(PostgresEmailQueryViewDataDefinition.MODULE); @@ -68,4 +70,9 @@ public class PostgresEmailQueryViewTest implements EmailQueryViewContract { public MessageId messageId4() { return MESSAGE_ID_4; } + + @Override + public ThreadId threadId() { + return THREAD_ID; + } } diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/projections/EmailQueryView.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/projections/EmailQueryView.java index 11afa60599..e7b1975613 100644 --- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/projections/EmailQueryView.java +++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/projections/EmailQueryView.java @@ -24,6 +24,7 @@ import java.util.Objects; import org.apache.james.mailbox.model.MailboxId; import org.apache.james.mailbox.model.MessageId; +import org.apache.james.mailbox.model.ThreadId; import org.apache.james.util.streams.Limit; import reactor.core.publisher.Flux; @@ -35,12 +36,14 @@ public interface EmailQueryView { private final MessageId messageId; private final ZonedDateTime sentAt; private final ZonedDateTime receivedAt; + private final ThreadId threadId; - public Entry(MailboxId mailboxId, MessageId messageId, ZonedDateTime sentAt, ZonedDateTime receivedAt) { + public Entry(MailboxId mailboxId, MessageId messageId, ZonedDateTime sentAt, ZonedDateTime receivedAt, ThreadId threadId) { this.mailboxId = mailboxId; this.messageId = messageId; this.sentAt = sentAt; this.receivedAt = receivedAt; + this.threadId = threadId; } public MailboxId getMailboxId() { @@ -59,6 +62,10 @@ public interface EmailQueryView { return receivedAt; } + public ThreadId getThreadId() { + return threadId; + } + @Override public final boolean equals(Object o) { if (o instanceof Entry) { @@ -67,14 +74,15 @@ public interface EmailQueryView { return Objects.equals(this.mailboxId, entry.mailboxId) && Objects.equals(this.messageId, entry.messageId) && Objects.equals(this.sentAt, entry.sentAt) - && Objects.equals(this.receivedAt, entry.receivedAt); + && Objects.equals(this.receivedAt, entry.receivedAt) + && Objects.equals(this.threadId, entry.threadId); } return false; } @Override public final int hashCode() { - return Objects.hash(mailboxId, messageId, sentAt, receivedAt); + return Objects.hash(mailboxId, messageId, sentAt, receivedAt, threadId); } } @@ -212,5 +220,5 @@ public interface EmailQueryView { Mono<Void> delete(MailboxId mailboxId); - Mono<Void> save(MailboxId mailboxId, ZonedDateTime sentAt, ZonedDateTime receivedAt, MessageId messageId); + Mono<Void> save(MailboxId mailboxId, ZonedDateTime sentAt, ZonedDateTime receivedAt, MessageId messageId, ThreadId threadId); } diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/projections/MemoryEmailQueryView.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/projections/MemoryEmailQueryView.java index a75b75ca18..27b4493aae 100644 --- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/projections/MemoryEmailQueryView.java +++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/projections/MemoryEmailQueryView.java @@ -27,6 +27,7 @@ import jakarta.inject.Inject; import org.apache.james.jmap.api.projections.EmailQueryView; import org.apache.james.mailbox.model.MailboxId; import org.apache.james.mailbox.model.MessageId; +import org.apache.james.mailbox.model.ThreadId; import org.apache.james.util.streams.Limit; import com.google.common.base.Preconditions; @@ -114,7 +115,7 @@ public class MemoryEmailQueryView implements EmailQueryView { } @Override - public Mono<Void> save(MailboxId mailboxId, ZonedDateTime sentAt, ZonedDateTime receivedAt, MessageId messageId) { - return Mono.fromRunnable(() -> entries.put(mailboxId, messageId, new Entry(mailboxId, messageId, sentAt, receivedAt))); + public Mono<Void> save(MailboxId mailboxId, ZonedDateTime sentAt, ZonedDateTime receivedAt, MessageId messageId, ThreadId threadId) { + return Mono.fromRunnable(() -> entries.put(mailboxId, messageId, new Entry(mailboxId, messageId, sentAt, receivedAt, threadId))); } } diff --git a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/projections/EmailQueryViewContract.java b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/projections/EmailQueryViewContract.java index ac99142ec4..5283f37939 100644 --- a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/projections/EmailQueryViewContract.java +++ b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/projections/EmailQueryViewContract.java @@ -27,6 +27,7 @@ import java.time.ZonedDateTime; import org.apache.james.mailbox.model.MailboxId; import org.apache.james.mailbox.model.MessageId; +import org.apache.james.mailbox.model.ThreadId; import org.apache.james.util.streams.Limit; import org.junit.jupiter.api.Test; @@ -51,6 +52,8 @@ public interface EmailQueryViewContract { MessageId messageId4(); + ThreadId threadId(); + @Test default void listMailboxContentShouldReturnEmptyByDefault() { assertThat(testee().listMailboxContentSortedBySentAt(mailboxId1(), Limit.limit(12)).collectList().block()) @@ -59,9 +62,9 @@ public interface EmailQueryViewContract { @Test default void listMailboxContentShouldBeOrderedBySentAt() { - testee().save(mailboxId1(), DATE_1, DATE_2, messageId1()).block(); - testee().save(mailboxId1(), DATE_3, DATE_4, messageId2()).block(); - testee().save(mailboxId1(), DATE_2, DATE_6, messageId3()).block(); + testee().save(mailboxId1(), DATE_1, DATE_2, messageId1(), threadId()).block(); + testee().save(mailboxId1(), DATE_3, DATE_4, messageId2(), threadId()).block(); + testee().save(mailboxId1(), DATE_2, DATE_6, messageId3(), threadId()).block(); assertThat(testee().listMailboxContentSortedBySentAt(mailboxId1(), Limit.limit(12)).collectList().block()) .containsExactly(messageId2(), messageId3(), messageId1()); @@ -69,9 +72,9 @@ public interface EmailQueryViewContract { @Test default void listMailboxContentShouldApplyLimit() { - testee().save(mailboxId1(), DATE_1, DATE_2, messageId1()).block(); - testee().save(mailboxId1(), DATE_3, DATE_4, messageId2()).block(); - testee().save(mailboxId1(), DATE_2, DATE_6, messageId3()).block(); + testee().save(mailboxId1(), DATE_1, DATE_2, messageId1(), threadId()).block(); + testee().save(mailboxId1(), DATE_3, DATE_4, messageId2(), threadId()).block(); + testee().save(mailboxId1(), DATE_2, DATE_6, messageId3(), threadId()).block(); assertThat(testee().listMailboxContentSortedBySentAt(mailboxId1(), Limit.limit(2)).collectList().block()) .containsExactly(messageId2(), messageId3()); @@ -79,9 +82,9 @@ public interface EmailQueryViewContract { @Test default void listMailboxContentSinceReceivedAtShouldExcludeTooOldItems() { - testee().save(mailboxId1(), DATE_1, DATE_2, messageId1()).block(); - testee().save(mailboxId1(), DATE_3, DATE_4, messageId2()).block(); - testee().save(mailboxId1(), DATE_5, DATE_6, messageId3()).block(); + testee().save(mailboxId1(), DATE_1, DATE_2, messageId1(), threadId()).block(); + testee().save(mailboxId1(), DATE_3, DATE_4, messageId2(), threadId()).block(); + testee().save(mailboxId1(), DATE_5, DATE_6, messageId3(), threadId()).block(); assertThat(testee().listMailboxContentSinceAfterSortedBySentAt(mailboxId1(), DATE_3, Limit.limit(12)).collectList().block()) .containsExactly(messageId3(), messageId2()); @@ -89,9 +92,9 @@ public interface EmailQueryViewContract { @Test default void listMailboxContentSinceReceivedAtShouldReturnEmptyWhenNoneMatch() { - testee().save(mailboxId1(), DATE_1, DATE_2, messageId1()).block(); - testee().save(mailboxId1(), DATE_3, DATE_4, messageId2()).block(); - testee().save(mailboxId1(), DATE_5, DATE_6, messageId3()).block(); + testee().save(mailboxId1(), DATE_1, DATE_2, messageId1(), threadId()).block(); + testee().save(mailboxId1(), DATE_3, DATE_4, messageId2(), threadId()).block(); + testee().save(mailboxId1(), DATE_5, DATE_6, messageId3(), threadId()).block(); assertThat(testee().listMailboxContentSinceAfterSortedBySentAt(mailboxId1(), DATE_7, Limit.limit(12)).collectList().block()) .isEmpty(); @@ -99,9 +102,9 @@ public interface EmailQueryViewContract { @Test default void listMailboxContentSinceReceivedAtAtShouldApplyLimit() { - testee().save(mailboxId1(), DATE_1, DATE_2, messageId1()).block(); - testee().save(mailboxId1(), DATE_3, DATE_4, messageId2()).block(); - testee().save(mailboxId1(), DATE_5, DATE_6, messageId3()).block(); + testee().save(mailboxId1(), DATE_1, DATE_2, messageId1(), threadId()).block(); + testee().save(mailboxId1(), DATE_3, DATE_4, messageId2(), threadId()).block(); + testee().save(mailboxId1(), DATE_5, DATE_6, messageId3(), threadId()).block(); assertThat(testee().listMailboxContentSinceAfterSortedBySentAt(mailboxId1(), DATE_1, Limit.limit(2)).collectList().block()) .containsExactly(messageId3(), messageId2()); @@ -109,9 +112,9 @@ public interface EmailQueryViewContract { @Test default void listMailboxContentSinceSentdAtShouldExcludeTooOldItems() { - testee().save(mailboxId1(), DATE_1, DATE_2, messageId1()).block(); - testee().save(mailboxId1(), DATE_3, DATE_4, messageId2()).block(); - testee().save(mailboxId1(), DATE_5, DATE_6, messageId3()).block(); + testee().save(mailboxId1(), DATE_1, DATE_2, messageId1(), threadId()).block(); + testee().save(mailboxId1(), DATE_3, DATE_4, messageId2(), threadId()).block(); + testee().save(mailboxId1(), DATE_5, DATE_6, messageId3(), threadId()).block(); assertThat(testee().listMailboxContentSinceSentAt(mailboxId1(), DATE_2, Limit.limit(12)).collectList().block()) .containsExactly(messageId3(), messageId2()); @@ -119,9 +122,9 @@ public interface EmailQueryViewContract { @Test default void listMailboxContentSinceSentAtAtShouldApplyLimit() { - testee().save(mailboxId1(), DATE_1, DATE_2, messageId1()).block(); - testee().save(mailboxId1(), DATE_3, DATE_4, messageId2()).block(); - testee().save(mailboxId1(), DATE_5, DATE_6, messageId3()).block(); + testee().save(mailboxId1(), DATE_1, DATE_2, messageId1(), threadId()).block(); + testee().save(mailboxId1(), DATE_3, DATE_4, messageId2(), threadId()).block(); + testee().save(mailboxId1(), DATE_5, DATE_6, messageId3(), threadId()).block(); assertThat(testee().listMailboxContentSinceSentAt(mailboxId1(), DATE_1, Limit.limit(2)).collectList().block()) .containsExactly(messageId3(), messageId2()); @@ -129,9 +132,9 @@ public interface EmailQueryViewContract { @Test default void listMailboxContentSinceSentAtShouldReturnEmptyWhenNoneMatch() { - testee().save(mailboxId1(), DATE_1, DATE_2, messageId1()).block(); - testee().save(mailboxId1(), DATE_3, DATE_4, messageId2()).block(); - testee().save(mailboxId1(), DATE_5, DATE_6, messageId3()).block(); + testee().save(mailboxId1(), DATE_1, DATE_2, messageId1(), threadId()).block(); + testee().save(mailboxId1(), DATE_3, DATE_4, messageId2(), threadId()).block(); + testee().save(mailboxId1(), DATE_5, DATE_6, messageId3(), threadId()).block(); assertThat(testee().listMailboxContentSinceSentAt(mailboxId1(), DATE_7, Limit.limit(12)).collectList().block()) .isEmpty(); @@ -139,9 +142,9 @@ public interface EmailQueryViewContract { @Test default void listMailboxContentShouldNotReturnClearedContent() { - testee().save(mailboxId1(), DATE_1, DATE_2, messageId1()).block(); - testee().save(mailboxId1(), DATE_3, DATE_4, messageId2()).block(); - testee().save(mailboxId1(), DATE_2, DATE_6, messageId3()).block(); + testee().save(mailboxId1(), DATE_1, DATE_2, messageId1(), threadId()).block(); + testee().save(mailboxId1(), DATE_3, DATE_4, messageId2(), threadId()).block(); + testee().save(mailboxId1(), DATE_2, DATE_6, messageId3(), threadId()).block(); testee().delete(mailboxId1()).block(); @@ -151,9 +154,9 @@ public interface EmailQueryViewContract { @Test default void listMailboxContentShouldNotReturnDeletedContent() { - testee().save(mailboxId1(), DATE_1, DATE_2, messageId1()).block(); - testee().save(mailboxId1(), DATE_3, DATE_4, messageId2()).block(); - testee().save(mailboxId1(), DATE_2, DATE_6, messageId3()).block(); + testee().save(mailboxId1(), DATE_1, DATE_2, messageId1(), threadId()).block(); + testee().save(mailboxId1(), DATE_3, DATE_4, messageId2(), threadId()).block(); + testee().save(mailboxId1(), DATE_2, DATE_6, messageId3(), threadId()).block(); testee().delete(mailboxId1(), messageId2()).block(); @@ -163,9 +166,9 @@ public interface EmailQueryViewContract { @Test default void listMailboxContentSinceReceivedAtShouldNotReturnClearedContent() { - testee().save(mailboxId1(), DATE_1, DATE_2, messageId1()).block(); - testee().save(mailboxId1(), DATE_3, DATE_4, messageId2()).block(); - testee().save(mailboxId1(), DATE_5, DATE_6, messageId3()).block(); + testee().save(mailboxId1(), DATE_1, DATE_2, messageId1(), threadId()).block(); + testee().save(mailboxId1(), DATE_3, DATE_4, messageId2(), threadId()).block(); + testee().save(mailboxId1(), DATE_5, DATE_6, messageId3(), threadId()).block(); testee().delete(mailboxId1()).block(); @@ -175,9 +178,9 @@ public interface EmailQueryViewContract { @Test default void listMailboxContentSinceSentAtShouldNotReturnClearedContent() { - testee().save(mailboxId1(), DATE_1, DATE_2, messageId1()).block(); - testee().save(mailboxId1(), DATE_3, DATE_4, messageId2()).block(); - testee().save(mailboxId1(), DATE_5, DATE_6, messageId3()).block(); + testee().save(mailboxId1(), DATE_1, DATE_2, messageId1(), threadId()).block(); + testee().save(mailboxId1(), DATE_3, DATE_4, messageId2(), threadId()).block(); + testee().save(mailboxId1(), DATE_5, DATE_6, messageId3(), threadId()).block(); testee().delete(mailboxId1()).block(); @@ -187,8 +190,8 @@ public interface EmailQueryViewContract { @Test default void saveShouldBeIdempotent() { - testee().save(mailboxId1(), DATE_1, DATE_2, messageId1()).block(); - testee().save(mailboxId1(), DATE_1, DATE_2, messageId1()).block(); + testee().save(mailboxId1(), DATE_1, DATE_2, messageId1(), threadId()).block(); + testee().save(mailboxId1(), DATE_1, DATE_2, messageId1(), threadId()).block(); assertThat(testee().listMailboxContentSortedBySentAt(mailboxId1(), Limit.limit(12)).collectList().block()) .containsExactly(messageId1()); @@ -196,8 +199,8 @@ public interface EmailQueryViewContract { @Test default void datesCanBeDuplicated() { - testee().save(mailboxId1(), DATE_1, DATE_2, messageId1()).block(); - testee().save(mailboxId1(), DATE_1, DATE_2, messageId2()).block(); + testee().save(mailboxId1(), DATE_1, DATE_2, messageId1(), threadId()).block(); + testee().save(mailboxId1(), DATE_1, DATE_2, messageId2(), threadId()).block(); assertThat(testee().listMailboxContentSortedBySentAt(mailboxId1(), Limit.limit(12)).collectList().block()) .containsExactlyInAnyOrder(messageId1(), messageId2()); @@ -205,9 +208,9 @@ public interface EmailQueryViewContract { @Test default void listMailboxContentSinceReceivedAtShouldNotReturnDeletedContent() { - testee().save(mailboxId1(), DATE_1, DATE_2, messageId1()).block(); - testee().save(mailboxId1(), DATE_3, DATE_4, messageId2()).block(); - testee().save(mailboxId1(), DATE_5, DATE_6, messageId3()).block(); + testee().save(mailboxId1(), DATE_1, DATE_2, messageId1(), threadId()).block(); + testee().save(mailboxId1(), DATE_3, DATE_4, messageId2(), threadId()).block(); + testee().save(mailboxId1(), DATE_5, DATE_6, messageId3(), threadId()).block(); testee().delete(mailboxId1(), messageId2()).block(); @@ -217,9 +220,9 @@ public interface EmailQueryViewContract { @Test default void listMailboxContentSinceSentAtShouldNotReturnDeletedContent() { - testee().save(mailboxId1(), DATE_1, DATE_2, messageId1()).block(); - testee().save(mailboxId1(), DATE_3, DATE_4, messageId2()).block(); - testee().save(mailboxId1(), DATE_5, DATE_6, messageId3()).block(); + testee().save(mailboxId1(), DATE_1, DATE_2, messageId1(), threadId()).block(); + testee().save(mailboxId1(), DATE_3, DATE_4, messageId2(), threadId()).block(); + testee().save(mailboxId1(), DATE_5, DATE_6, messageId3(), threadId()).block(); testee().delete(mailboxId1(), messageId2()).block(); @@ -229,9 +232,9 @@ public interface EmailQueryViewContract { @Test default void listMailboxContentSortedByReceivedAtShouldBeSortedByReceivedAt() { - testee().save(mailboxId1(), DATE_1, DATE_4, messageId1()).block(); - testee().save(mailboxId1(), DATE_2, DATE_3, messageId2()).block(); - testee().save(mailboxId1(), DATE_5, DATE_6, messageId3()).block(); + testee().save(mailboxId1(), DATE_1, DATE_4, messageId1(), threadId()).block(); + testee().save(mailboxId1(), DATE_2, DATE_3, messageId2(), threadId()).block(); + testee().save(mailboxId1(), DATE_5, DATE_6, messageId3(), threadId()).block(); assertThat(testee().listMailboxContentSortedByReceivedAt(mailboxId1(), Limit.limit(12)).collectList().block()) .containsExactly(messageId3(), messageId1(), messageId2()); @@ -239,9 +242,9 @@ public interface EmailQueryViewContract { @Test default void listMailboxContentSinceSortedByReceivedAtShouldBeSortedByReceivedAt() { - testee().save(mailboxId1(), DATE_1, DATE_4, messageId1()).block(); - testee().save(mailboxId1(), DATE_2, DATE_3, messageId2()).block(); - testee().save(mailboxId1(), DATE_5, DATE_6, messageId3()).block(); + testee().save(mailboxId1(), DATE_1, DATE_4, messageId1(), threadId()).block(); + testee().save(mailboxId1(), DATE_2, DATE_3, messageId2(), threadId()).block(); + testee().save(mailboxId1(), DATE_5, DATE_6, messageId3(), threadId()).block(); assertThat(testee().listMailboxContentSinceAfterSortedByReceivedAt(mailboxId1(), DATE_4, Limit.limit(12)).collectList().block()) .containsExactly(messageId3(), messageId1()); @@ -249,9 +252,9 @@ public interface EmailQueryViewContract { @Test default void listMailboxContentBeforeSortedByReceivedAtShouldBeSortedByReceivedAt() { - testee().save(mailboxId1(), DATE_1, DATE_4, messageId1()).block(); - testee().save(mailboxId1(), DATE_2, DATE_3, messageId2()).block(); - testee().save(mailboxId1(), DATE_5, DATE_6, messageId3()).block(); + testee().save(mailboxId1(), DATE_1, DATE_4, messageId1(), threadId()).block(); + testee().save(mailboxId1(), DATE_2, DATE_3, messageId2(), threadId()).block(); + testee().save(mailboxId1(), DATE_5, DATE_6, messageId3(), threadId()).block(); assertThat(testee().listMailboxContentBeforeSortedByReceivedAt(mailboxId1(), DATE_4, Limit.limit(12)).collectList().block()) .containsExactly(messageId1(), messageId2()); diff --git a/server/data/data-jmap/src/test/java/org/apache/james/jmap/memory/projections/MemoryEmailQueryViewTest.java b/server/data/data-jmap/src/test/java/org/apache/james/jmap/memory/projections/MemoryEmailQueryViewTest.java index 13838942bc..8c53060187 100644 --- a/server/data/data-jmap/src/test/java/org/apache/james/jmap/memory/projections/MemoryEmailQueryViewTest.java +++ b/server/data/data-jmap/src/test/java/org/apache/james/jmap/memory/projections/MemoryEmailQueryViewTest.java @@ -25,6 +25,7 @@ import org.apache.james.mailbox.model.MailboxId; import org.apache.james.mailbox.model.MessageId; import org.apache.james.mailbox.model.TestId; import org.apache.james.mailbox.model.TestMessageId; +import org.apache.james.mailbox.model.ThreadId; import org.junit.jupiter.api.BeforeEach; public class MemoryEmailQueryViewTest implements EmailQueryViewContract { @@ -64,4 +65,11 @@ public class MemoryEmailQueryViewTest implements EmailQueryViewContract { public MessageId messageId4() { return TestMessageId.of(4); } + + @Override + public ThreadId threadId() { + return ThreadId.fromBaseMessageId(TestMessageId.of(1)); + } + + } diff --git a/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/event/PopulateEmailQueryViewListener.java b/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/event/PopulateEmailQueryViewListener.java index af9faf78c2..253b954440 100644 --- a/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/event/PopulateEmailQueryViewListener.java +++ b/server/protocols/jmap-rfc-8621/src/main/java/org/apache/james/jmap/event/PopulateEmailQueryViewListener.java @@ -185,7 +185,7 @@ public class PopulateEmailQueryViewListener implements ReactiveGroupEventListene return Mono.fromCallable(() -> parseMessage(messageResult)) .map(header -> date(header).orElse(messageResult.getInternalDate())) .map(date -> ZonedDateTime.ofInstant(date.toInstant(), ZoneOffset.UTC)) - .flatMap(sentAt -> viewManager.getEmailQueryView(username).save(mailboxId, sentAt, receivedAt, messageResult.getMessageId())) + .flatMap(sentAt -> viewManager.getEmailQueryView(username).save(mailboxId, sentAt, receivedAt, messageResult.getMessageId(), messageResult.getThreadId())) .then(); } diff --git a/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/EmailQueryViewPopulator.java b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/EmailQueryViewPopulator.java index 207dc4af8f..144ca3e362 100644 --- a/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/EmailQueryViewPopulator.java +++ b/server/protocols/webadmin/webadmin-jmap/src/main/java/org/apache/james/webadmin/data/jmap/EmailQueryViewPopulator.java @@ -43,6 +43,7 @@ import org.apache.james.mailbox.model.MailboxMetaData; import org.apache.james.mailbox.model.MessageId; import org.apache.james.mailbox.model.MessageRange; import org.apache.james.mailbox.model.MessageResult; +import org.apache.james.mailbox.model.ThreadId; import org.apache.james.mailbox.model.search.MailboxQuery; import org.apache.james.mime4j.dom.Message; import org.apache.james.mime4j.message.DefaultMessageBuilder; @@ -152,15 +153,16 @@ public class EmailQueryViewPopulator { return Mono.fromCallable(() -> { MailboxId mailboxId = messageResult.getMailboxId(); MessageId messageId = messageResult.getMessageId(); + ThreadId threadId = messageResult.getThreadId(); ZonedDateTime receivedAt = ZonedDateTime.ofInstant(messageResult.getInternalDate().toInstant(), ZoneOffset.UTC); Message mime4JMessage = parseMessage(messageResult); Date sentAtDate = Optional.ofNullable(mime4JMessage.getDate()).orElse(messageResult.getInternalDate()); ZonedDateTime sentAt = ZonedDateTime.ofInstant(sentAtDate.toInstant(), ZoneOffset.UTC); mime4JMessage.dispose(); - return new EmailQueryView.Entry(mailboxId, messageId, sentAt, receivedAt); + return new EmailQueryView.Entry(mailboxId, messageId, sentAt, receivedAt, threadId); }) - .flatMap(entry -> emailQueryView.save(entry.getMailboxId(), entry.getSentAt(), entry.getReceivedAt(), entry.getMessageId())) + .flatMap(entry -> emailQueryView.save(entry.getMailboxId(), entry.getSentAt(), entry.getReceivedAt(), entry.getMessageId(), entry.getThreadId())) .thenReturn(Result.COMPLETED) .doOnSuccess(any -> progress.incrementProcessedMessageCount()) .onErrorResume(e -> { --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
