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]

Reply via email to