This is an automated email from the ASF dual-hosted git repository. rcordier pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 0d644ec12a50d23dad795d6e1d2e8c08301a00ec Author: Benoit TELLIER <[email protected]> AuthorDate: Wed Mar 18 12:48:48 2026 +0100 JAMES-4185 Add mailboxPath to deletion events Very convenient when writing extensions --- .../apache/james/mailbox/events/MailboxEvents.java | 2 +- .../mailbox/cassandra/DeleteMessageListener.java | 15 +++--- .../james/event/json/MailboxEventSerializer.scala | 8 ++-- .../MessageContentDeletionSerializationTest.java | 56 ++++++++++++++++++++-- .../mailbox/postgres/DeleteMessageListener.java | 17 ++++--- .../james/mailbox/store/event/EventFactory.java | 10 +++- 6 files changed, 86 insertions(+), 22 deletions(-) diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/events/MailboxEvents.java b/mailbox/api/src/main/java/org/apache/james/mailbox/events/MailboxEvents.java index e1ce43745c..b648cb24fb 100644 --- a/mailbox/api/src/main/java/org/apache/james/mailbox/events/MailboxEvents.java +++ b/mailbox/api/src/main/java/org/apache/james/mailbox/events/MailboxEvents.java @@ -540,7 +540,7 @@ public interface MailboxEvents { record MessageContentDeletionEvent(EventId eventId, Username username, MailboxId mailboxId, MessageId messageId, long size, Instant internalDate, Flags flags, boolean hasAttachments, Optional<String> headerBlobId, Optional<String> headerContent, - String bodyBlobId) implements Event { + String bodyBlobId, Optional<String> mailboxPath) implements Event { @Override public EventId getEventId() { diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/DeleteMessageListener.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/DeleteMessageListener.java index 3e57401ad3..919b08f5e6 100644 --- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/DeleteMessageListener.java +++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/DeleteMessageListener.java @@ -170,7 +170,7 @@ public class DeleteMessageListener implements EventListener.ReactiveGroupEventLi return Flux.mergeDelayError(prefetch, messageIdDAO.retrieveMessages(mailboxId, MessageRange.all(), Limit.unlimited()) .concatMap(metadata -> handleMessageDeletionAsPartOfMailboxDeletion((CassandraMessageId) metadata.getComposedMessageId().getComposedMessageId().getMessageId(), - metadata.getComposedMessageId().getThreadId(), metadata.getComposedMessageId().getFlags(), mailboxId, path.getUser()) + metadata.getComposedMessageId().getThreadId(), metadata.getComposedMessageId().getFlags(), mailboxId, path.getUser(), path) .then(imapUidDAO.delete((CassandraMessageId) metadata.getComposedMessageId().getComposedMessageId().getMessageId(), mailboxId)) .then(messageIdDAO.delete(mailboxId, metadata.getComposedMessageId().getComposedMessageId().getUid()))), deleteAcl(mailboxId), @@ -185,7 +185,7 @@ public class DeleteMessageListener implements EventListener.ReactiveGroupEventLi private Mono<Void> handleMessageDeletion(Expunged expunged) { return Flux.fromIterable(expunged.getExpunged().values()) .concatMap(metaData -> handleMessageDeletion((CassandraMessageId) metaData.getMessageId(), - expunged.getMailboxId(), metaData.getThreadId(), metaData.getFlags(), expunged.getMailboxPath().getUser())) + expunged.getMailboxId(), metaData.getThreadId(), metaData.getFlags(), expunged.getMailboxPath().getUser(), expunged.getMailboxPath())) .then(); } @@ -195,11 +195,11 @@ public class DeleteMessageListener implements EventListener.ReactiveGroupEventLi .then(aclMapper.delete(mailboxId))); } - private Mono<Void> handleMessageDeletion(CassandraMessageId messageId, MailboxId mailboxId, ThreadId threadId, Flags flags, Username owner) { + private Mono<Void> handleMessageDeletion(CassandraMessageId messageId, MailboxId mailboxId, ThreadId threadId, Flags flags, Username owner, MailboxPath mailboxPath) { return Mono.just(messageId) .filterWhen(this::isReferenced) .flatMap(id -> readMessage(id) - .flatMap(message -> dispatchMessageContentDeletionEvent(mailboxId, owner, flags, message) + .flatMap(message -> dispatchMessageContentDeletionEvent(mailboxId, owner, flags, message, mailboxPath) .thenReturn(message)) .flatMap(message -> deleteUnreferencedAttachments(message).thenReturn(message)) .flatMap(this::deleteMessageBlobs) @@ -210,7 +210,7 @@ public class DeleteMessageListener implements EventListener.ReactiveGroupEventLi .then(threadLookupDAO.deleteOneRow(threadId, messageId))); } - private Mono<Void> dispatchMessageContentDeletionEvent(MailboxId mailboxId, Username owner, Flags flags, MessageRepresentation message) { + private Mono<Void> dispatchMessageContentDeletionEvent(MailboxId mailboxId, Username owner, Flags flags, MessageRepresentation message, MailboxPath mailboxPath) { AuditTrail.entry() .action("DELETION") .username(owner::asString) @@ -231,15 +231,16 @@ public class DeleteMessageListener implements EventListener.ReactiveGroupEventLi .hasAttachments(!message.getAttachments().isEmpty()) .bodyBlobId(message.getBodyId().asString()) .headerBlobId(message.getHeaderId().asString()) + .mailboxPath(mailboxPath.asString()) .build(), ImmutableSet.of())); } - private Mono<Void> handleMessageDeletionAsPartOfMailboxDeletion(CassandraMessageId messageId, ThreadId threadId, Flags flags, CassandraId excludedId, Username owner) { + private Mono<Void> handleMessageDeletionAsPartOfMailboxDeletion(CassandraMessageId messageId, ThreadId threadId, Flags flags, CassandraId excludedId, Username owner, MailboxPath mailboxPath) { return Mono.just(messageId) .filterWhen(id -> isReferenced(id, excludedId)) .flatMap(id -> readMessage(id) - .flatMap(message -> dispatchMessageContentDeletionEvent(excludedId, owner, flags, message) + .flatMap(message -> dispatchMessageContentDeletionEvent(excludedId, owner, flags, message, mailboxPath) .thenReturn(message))) .flatMap(message -> deleteUnreferencedAttachments(message).thenReturn(message)) .flatMap(this::deleteMessageBlobs) diff --git a/mailbox/event/json/src/main/scala/org/apache/james/event/json/MailboxEventSerializer.scala b/mailbox/event/json/src/main/scala/org/apache/james/event/json/MailboxEventSerializer.scala index 4e3f194141..e112a21879 100644 --- a/mailbox/event/json/src/main/scala/org/apache/james/event/json/MailboxEventSerializer.scala +++ b/mailbox/event/json/src/main/scala/org/apache/james/event/json/MailboxEventSerializer.scala @@ -144,8 +144,9 @@ private object DTO { hasAttachments: Boolean, headerBlobId: Option[String], headerContent: Option[String], - bodyBlobId: String) extends Event { - override def toJava: JavaEvent = new JavaMessageContentDeletionEvent(eventId, username, mailboxId, messageId, size, internalDate, DTOs.Flags.toJavaFlags(flags.getOrElse(DTOs.Flags.empty)), hasAttachments, headerBlobId.toJava, headerContent.toJava, bodyBlobId) + bodyBlobId: String, + mailboxPath: Option[String] = None) extends Event { + override def toJava: JavaEvent = new JavaMessageContentDeletionEvent(eventId, username, mailboxId, messageId, size, internalDate, DTOs.Flags.toJavaFlags(flags.getOrElse(DTOs.Flags.empty)), hasAttachments, headerBlobId.toJava, headerContent.toJava, bodyBlobId, mailboxPath.toJava) } } @@ -252,7 +253,8 @@ private object ScalaConverter { hasAttachments = event.hasAttachments, headerBlobId = event.headerBlobId().toScala, bodyBlobId = event.bodyBlobId(), - headerContent = event.headerContent().toScala) + headerContent = event.headerContent().toScala, + mailboxPath = event.mailboxPath().toScala) def toScala(javaEvent: JavaEvent): Event = javaEvent match { case e: JavaAdded => toScala(e) diff --git a/mailbox/event/json/src/test/java/org/apache/james/event/json/MessageContentDeletionSerializationTest.java b/mailbox/event/json/src/test/java/org/apache/james/event/json/MessageContentDeletionSerializationTest.java index 4f2425b381..b8fa5151d8 100644 --- a/mailbox/event/json/src/test/java/org/apache/james/event/json/MessageContentDeletionSerializationTest.java +++ b/mailbox/event/json/src/test/java/org/apache/james/event/json/MessageContentDeletionSerializationTest.java @@ -53,6 +53,43 @@ class MessageContentDeletionSerializationTest { private static final Optional<String> HEADER_CONTENT = Optional.of("Header: value"); private static final Optional<String> EMPTY_HEADER_CONTENT = Optional.empty(); private static final String BODY_BLOB_ID = "body-blob-id"; + private static final Optional<String> MAILBOX_PATH = Optional.of("#TeamMailbox:[email protected]:sales"); + + private static final MessageContentDeletionEvent EVENT_WITH_MAILBOX_PATH = new MessageContentDeletionEvent( + EVENT_ID, + USERNAME, + MAILBOX_ID, + MESSAGE_ID, + SIZE, + INTERNAL_DATE, + FLAGS, + HAS_ATTACHMENTS, + HEADER_BLOB_ID, + HEADER_CONTENT, + BODY_BLOB_ID, + MAILBOX_PATH); + + private static final String JSON_WITH_MAILBOX_PATH = """ + { + "MessageContentDeletionEvent": { + "eventId": "6e0dd59d-660e-4d9b-b22f-0354479f47b4", + "username": "[email protected]", + "size": 12345, + "hasAttachments": true, + "internalDate": "2024-12-15T08:23:45Z", + "flags": { + "systemFlags": ["Flagged"], + "userFlags": ["$Forwarded"] + }, + "mailboxId": "18", + "headerBlobId": "header-blob-id", + "messageId": "42", + "bodyBlobId": "body-blob-id", + "headerContent": "Header: value", + "mailboxPath": "#TeamMailbox:[email protected]:sales" + } + } + """; private static final MessageContentDeletionEvent EVENT = new MessageContentDeletionEvent( EVENT_ID, @@ -65,7 +102,8 @@ class MessageContentDeletionSerializationTest { HAS_ATTACHMENTS, HEADER_BLOB_ID, HEADER_CONTENT, - BODY_BLOB_ID); + BODY_BLOB_ID, + Optional.empty()); private static final MessageContentDeletionEvent EVENT_WITHOUT_HEADER_CONTENT = new MessageContentDeletionEvent( EVENT_ID, @@ -78,7 +116,8 @@ class MessageContentDeletionSerializationTest { HAS_ATTACHMENTS, HEADER_BLOB_ID, EMPTY_HEADER_CONTENT, - BODY_BLOB_ID); + BODY_BLOB_ID, + Optional.empty()); private static final String JSON = """ { @@ -149,6 +188,16 @@ class MessageContentDeletionSerializationTest { assertThat(EVENT_SERIALIZER.fromJson(JSON_WITHOUT_HEADER_CONTENT).get()).isEqualTo(EVENT_WITHOUT_HEADER_CONTENT); } + @Test + void messageContentDeletionEventWithMailboxPathShouldBeWellSerialized() { + assertThatJson(EVENT_SERIALIZER.toJson(EVENT_WITH_MAILBOX_PATH)).isEqualTo(JSON_WITH_MAILBOX_PATH); + } + + @Test + void messageContentDeletionEventWithMailboxPathShouldBeWellDeserialized() { + assertThat(EVENT_SERIALIZER.fromJson(JSON_WITH_MAILBOX_PATH).get()).isEqualTo(EVENT_WITH_MAILBOX_PATH); + } + @Test void messageContentDeletionEventWithoutFlagsShouldBeWellDeserialized() { assertThat(EVENT_SERIALIZER.fromJson(JSON_WITHOUT_FLAGS).get()) @@ -163,7 +212,8 @@ class MessageContentDeletionSerializationTest { HAS_ATTACHMENTS, HEADER_BLOB_ID, EMPTY_HEADER_CONTENT, - BODY_BLOB_ID)); + BODY_BLOB_ID, + Optional.empty())); } } diff --git a/mailbox/postgres/src/main/java/org/apache/james/mailbox/postgres/DeleteMessageListener.java b/mailbox/postgres/src/main/java/org/apache/james/mailbox/postgres/DeleteMessageListener.java index 27e76ceaa1..f2b723b632 100644 --- a/mailbox/postgres/src/main/java/org/apache/james/mailbox/postgres/DeleteMessageListener.java +++ b/mailbox/postgres/src/main/java/org/apache/james/mailbox/postgres/DeleteMessageListener.java @@ -37,6 +37,7 @@ import org.apache.james.mailbox.events.MailboxEvents; import org.apache.james.mailbox.events.MailboxEvents.Expunged; import org.apache.james.mailbox.events.MailboxEvents.MailboxDeletion; import org.apache.james.mailbox.model.MailboxId; +import org.apache.james.mailbox.model.MailboxPath; import org.apache.james.mailbox.postgres.mail.MessageRepresentation; import org.apache.james.mailbox.postgres.mail.dao.PostgresAttachmentDAO; import org.apache.james.mailbox.postgres.mail.dao.PostgresMailboxMessageDAO; @@ -115,7 +116,7 @@ public class DeleteMessageListener implements EventListener.ReactiveGroupEventLi return postgresMailboxMessageDAO.deleteByMailboxId((PostgresMailboxId) event.getMailboxId()) .flatMap(metaData -> handleMessageDeletion(postgresMessageDAO, postgresMailboxMessageDAO, attachmentDAO, threadDAO, - (PostgresMessageId) metaData.getMessageId(), event.getMailboxId(), event.getMailboxPath().getUser(), metaData.getFlags()), + (PostgresMessageId) metaData.getMessageId(), event.getMailboxId(), event.getMailboxPath().getUser(), metaData.getFlags(), event.getMailboxPath()), LOW_CONCURRENCY) .then(); } @@ -129,7 +130,7 @@ public class DeleteMessageListener implements EventListener.ReactiveGroupEventLi return Flux.fromIterable(event.getExpunged() .values()) .flatMap(metaData -> handleMessageDeletion(postgresMessageDAO, postgresMailboxMessageDAO, attachmentDAO, threadDAO, - (PostgresMessageId) metaData.getMessageId(), event.getMailboxId(), event.getMailboxPath().getUser(), metaData.getFlags()), LOW_CONCURRENCY) + (PostgresMessageId) metaData.getMessageId(), event.getMailboxId(), event.getMailboxPath().getUser(), metaData.getFlags(), event.getMailboxPath()), LOW_CONCURRENCY) .then(); } @@ -140,25 +141,26 @@ public class DeleteMessageListener implements EventListener.ReactiveGroupEventLi PostgresMessageId messageId, MailboxId mailboxId, Username owner, - Flags flags) { + Flags flags, + MailboxPath mailboxPath) { return Mono.just(messageId) .filterWhen(msgId -> isUnreferenced(msgId, postgresMailboxMessageDAO)) .flatMap(msgId -> postgresMessageDAO.retrieveMessage(messageId) - .flatMap(messageRepresentation -> dispatchMessageContentDeletionEvent(mailboxId, owner, flags, messageRepresentation)) + .flatMap(messageRepresentation -> dispatchMessageContentDeletionEvent(mailboxId, owner, flags, messageRepresentation, mailboxPath)) .then(deleteBodyBlob(msgId, postgresMessageDAO)) .then(deleteAttachmentIfEnabled(msgId, attachmentDAO)) .then(threadDAO.deleteSome(owner, msgId)) .then(postgresMessageDAO.deleteByMessageId(msgId))); } - private Mono<Void> dispatchMessageContentDeletionEvent(MailboxId mailboxId, Username owner, Flags flags, MessageRepresentation message) { + private Mono<Void> dispatchMessageContentDeletionEvent(MailboxId mailboxId, Username owner, Flags flags, MessageRepresentation message, MailboxPath mailboxPath) { return Mono.fromCallable(() -> IOUtils.toString(message.getHeaderContent().getInputStream(), StandardCharsets.UTF_8)) .subscribeOn(ReactorUtils.BLOCKING_CALL_WRAPPER) - .flatMap(headerContent -> Mono.from(contentDeletionEventBus.dispatch(messageContentDeletionEvent(mailboxId, owner, flags, message, headerContent), + .flatMap(headerContent -> Mono.from(contentDeletionEventBus.dispatch(messageContentDeletionEvent(mailboxId, owner, flags, message, headerContent, mailboxPath), ImmutableSet.of()))); } - private MailboxEvents.MessageContentDeletionEvent messageContentDeletionEvent(MailboxId mailboxId, Username owner, Flags flags, MessageRepresentation message, String headerContent) { + private MailboxEvents.MessageContentDeletionEvent messageContentDeletionEvent(MailboxId mailboxId, Username owner, Flags flags, MessageRepresentation message, String headerContent, MailboxPath mailboxPath) { return EventFactory.messageContentDeleted() .randomEventId() .user(owner) @@ -170,6 +172,7 @@ public class DeleteMessageListener implements EventListener.ReactiveGroupEventLi .hasAttachments(!message.getAttachments().isEmpty()) .bodyBlobId(message.getBodyBlobId().asString()) .headerContent(headerContent) + .mailboxPath(mailboxPath.asString()) .build(); } diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/EventFactory.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/EventFactory.java index f280e33d0e..b902a5dd9a 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/EventFactory.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/event/EventFactory.java @@ -560,6 +560,7 @@ public class EventFactory { private final String bodyBlobId; private Optional<String> headerBlobId; private Optional<String> headerContent; + private Optional<String> mailboxPath; MessageContentDeletionFinalStage(Event.EventId eventId, Username username, @@ -581,6 +582,7 @@ public class EventFactory { this.bodyBlobId = bodyBlobId; this.headerBlobId = Optional.empty(); this.headerContent = Optional.empty(); + this.mailboxPath = Optional.empty(); } public MessageContentDeletionFinalStage headerBlobId(String headerBlobId) { @@ -593,6 +595,11 @@ public class EventFactory { return this; } + public MessageContentDeletionFinalStage mailboxPath(String mailboxPath) { + this.mailboxPath = Optional.ofNullable(mailboxPath); + return this; + } + public MailboxEvents.MessageContentDeletionEvent build() { Preconditions.checkNotNull(eventId); Preconditions.checkNotNull(username); @@ -614,7 +621,8 @@ public class EventFactory { hasAttachments, headerBlobId, headerContent, - bodyBlobId); + bodyBlobId, + mailboxPath); } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
