You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2023/02/14 00:12:11 UTC
[james-project] branch master updated: JAMES-3882 Move messages into DeletedMessageVault asynchronously for … (#1436)
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
The following commit(s) were added to refs/heads/master by this push:
new b3a3c20249 JAMES-3882 Move messages into DeletedMessageVault asynchronously for … (#1436)
b3a3c20249 is described below
commit b3a3c20249fd7e6759ee9a7db823e01199bc12c1
Author: Benoit TELLIER <bt...@linagora.com>
AuthorDate: Tue Feb 14 07:12:05 2023 +0700
JAMES-3882 Move messages into DeletedMessageVault asynchronously for … (#1436)
---
.../james/mailbox/MetadataWithMailboxId.java | 24 ++--
.../apache/james/mailbox/MailboxManagerTest.java | 12 +-
.../CassandraMailboxSessionMapperFactory.java | 3 +-
.../mailbox/cassandra/DeleteMessageListener.java | 27 +++--
.../deleted-messages-vault-cassandra/pom.xml | 4 +
.../DeletedMessageVaultDeletionCallback.java | 133 +++++++++++++++++++++
.../james/vault/DeletedMessageVaultHook.java | 2 +-
.../james/mailbox/store/StoreMailboxManager.java | 4 +-
.../james/mailbox/store/StoreMessageIdManager.java | 20 ++--
.../AbstractMessageIdManagerSideEffectTest.java | 8 +-
.../sample-configuration/listeners.xml | 6 -
.../modules/ROOT/pages/configure/listeners.adoc | 26 ----
.../docs/modules/ROOT/pages/configure/vault.adoc | 5 +-
.../sample-configuration/listeners.xml | 7 --
.../sample-configuration/listeners.xml | 6 -
.../CassandraDeletedMessageVaultModule.java | 6 +
.../modules/mailbox/CassandraMailboxModule.java | 1 +
...RabbitMQDeletedMessageVaultIntegrationTest.java | 5 +-
...LinshareBlobExportMechanismIntegrationTest.java | 3 +-
.../vault/DeletedMessageVaultIntegrationTest.java | 7 ++
src/homepage/howTo/deleted-messages-vault.html | 1 +
src/site/xdoc/server/config-listeners.xml | 1 +
upgrade-instructions.md | 12 ++
23 files changed, 229 insertions(+), 94 deletions(-)
diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/MetadataWithMailboxId.java b/mailbox/api/src/main/java/org/apache/james/mailbox/MetadataWithMailboxId.java
index a33345ee58..8fb98bfd3c 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/MetadataWithMailboxId.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/MetadataWithMailboxId.java
@@ -22,24 +22,31 @@ package org.apache.james.mailbox;
import java.util.Objects;
import org.apache.james.mailbox.model.MailboxId;
+import org.apache.james.mailbox.model.MessageId;
import org.apache.james.mailbox.model.MessageMetaData;
public class MetadataWithMailboxId {
public static MetadataWithMailboxId from(MessageMetaData messageMetaData, MailboxId mailboxId) {
- return new MetadataWithMailboxId(messageMetaData, mailboxId);
+ return new MetadataWithMailboxId(messageMetaData.getMessageId(), messageMetaData.getSize(), mailboxId);
}
- private final MessageMetaData messageMetaData;
+ private final MessageId messageId;
+ private final long size;
private final MailboxId mailboxId;
- private MetadataWithMailboxId(MessageMetaData messageMetaData, MailboxId mailboxId) {
- this.messageMetaData = messageMetaData;
+ public MetadataWithMailboxId(MessageId messageId, long size, MailboxId mailboxId) {
+ this.messageId = messageId;
+ this.size = size;
this.mailboxId = mailboxId;
}
- public MessageMetaData getMessageMetaData() {
- return messageMetaData;
+ public MessageId getMessageId() {
+ return messageId;
+ }
+
+ public long getSize() {
+ return size;
}
public MailboxId getMailboxId() {
@@ -51,7 +58,8 @@ public class MetadataWithMailboxId {
if (o instanceof MetadataWithMailboxId) {
MetadataWithMailboxId that = (MetadataWithMailboxId) o;
- return Objects.equals(this.messageMetaData, that.messageMetaData)
+ return Objects.equals(this.messageId, that.messageId)
+ && Objects.equals(this.size, that.size)
&& Objects.equals(this.mailboxId, that.mailboxId);
}
return false;
@@ -59,6 +67,6 @@ public class MetadataWithMailboxId {
@Override
public final int hashCode() {
- return Objects.hash(messageMetaData, mailboxId);
+ return Objects.hash(messageId, size, mailboxId);
}
}
\ No newline at end of file
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 79e66ae419..5bd6502f16 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
@@ -2599,7 +2599,7 @@ public abstract class MailboxManagerTest<T extends MailboxManager> {
.hasSameElementsAs(preDeleteCaptor2.getValue().getDeletionMetadataList())
.allSatisfy(deleteMetadata -> SoftAssertions.assertSoftly(softy -> {
softy.assertThat(deleteMetadata.getMailboxId()).isEqualTo(inboxId);
- softy.assertThat(deleteMetadata.getMessageMetaData().getMessageId()).isEqualTo(composeId.getMessageId());
+ softy.assertThat(deleteMetadata.getMessageId()).isEqualTo(composeId.getMessageId());
}));
}
@@ -2620,7 +2620,7 @@ public abstract class MailboxManagerTest<T extends MailboxManager> {
.hasSameElementsAs(preDeleteCaptor2.getValue().getDeletionMetadataList())
.allSatisfy(deleteMetadata -> SoftAssertions.assertSoftly(softy -> {
softy.assertThat(deleteMetadata.getMailboxId()).isEqualTo(inboxId);
- softy.assertThat(deleteMetadata.getMessageMetaData().getMessageId()).isEqualTo(composeId.getMessageId());
+ softy.assertThat(deleteMetadata.getMessageId()).isEqualTo(composeId.getMessageId());
}));
}
@@ -2641,7 +2641,7 @@ public abstract class MailboxManagerTest<T extends MailboxManager> {
.hasSameElementsAs(preDeleteCaptor2.getValue().getDeletionMetadataList())
.allSatisfy(deleteMetadata -> SoftAssertions.assertSoftly(softy -> {
softy.assertThat(deleteMetadata.getMailboxId()).isEqualTo(inboxId);
- softy.assertThat(deleteMetadata.getMessageMetaData().getMessageId()).isEqualTo(composeId.getMessageId());
+ softy.assertThat(deleteMetadata.getMessageId()).isEqualTo(composeId.getMessageId());
}));
}
@@ -2667,7 +2667,7 @@ public abstract class MailboxManagerTest<T extends MailboxManager> {
.hasSameElementsAs(preDeleteCaptor2.getAllValues())
.flatExtracting(PreDeletionHook.DeleteOperation::getDeletionMetadataList)
.allSatisfy(deleteMetadata -> assertThat(deleteMetadata.getMailboxId()).isEqualTo(inboxId))
- .extracting(deleteMetadata -> deleteMetadata.getMessageMetaData().getMessageId())
+ .extracting(deleteMetadata -> deleteMetadata.getMessageId())
.containsOnly(composeId1.getMessageId(), composeId2.getMessageId());
}
@@ -2691,7 +2691,7 @@ public abstract class MailboxManagerTest<T extends MailboxManager> {
.hasSameElementsAs(preDeleteCaptor2.getValue().getDeletionMetadataList())
.allSatisfy(deleteMetadata -> SoftAssertions.assertSoftly(softy -> {
softy.assertThat(deleteMetadata.getMailboxId()).isEqualTo(inboxId);
- softy.assertThat(deleteMetadata.getMessageMetaData().getMessageId()).isEqualTo(composeId1.getMessageId());
+ softy.assertThat(deleteMetadata.getMessageId()).isEqualTo(composeId1.getMessageId());
}));
}
@@ -2726,7 +2726,7 @@ public abstract class MailboxManagerTest<T extends MailboxManager> {
assertThat(preDeleteCaptor1.getAllValues())
.hasSameElementsAs(preDeleteCaptor2.getAllValues())
.flatExtracting(PreDeletionHook.DeleteOperation::getDeletionMetadataList)
- .extracting(deleteMetadata -> deleteMetadata.getMessageMetaData().getMessageId())
+ .extracting(deleteMetadata -> deleteMetadata.getMessageId())
.containsOnly(composeId1.getMessageId(), composeId2.getMessageId());
assertThat(preDeleteCaptor1.getAllValues())
diff --git a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxSessionMapperFactory.java b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxSessionMapperFactory.java
index 22e150af51..84d86b1882 100644
--- a/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxSessionMapperFactory.java
+++ b/mailbox/cassandra/src/main/java/org/apache/james/mailbox/cassandra/CassandraMailboxSessionMapperFactory.java
@@ -64,6 +64,7 @@ import org.apache.james.mailbox.store.mail.UidProvider;
import org.apache.james.mailbox.store.user.SubscriptionMapper;
import com.datastax.oss.driver.api.core.CqlSession;
+import com.google.common.collect.ImmutableSet;
/**
* Cassandra implementation of {@link MailboxSessionMapperFactory}
@@ -233,6 +234,6 @@ public class CassandraMailboxSessionMapperFactory extends MailboxSessionMapperFa
public DeleteMessageListener deleteMessageListener() {
return new DeleteMessageListener(threadDAO, threadLookupDAO, imapUidDAO, messageIdDAO, messageDAO, messageDAOV3, attachmentDAOV2,
attachmentMessageIdDAO, aclMapper, userMailboxRightsDAO, applicableFlagDAO, firstUnseenDAO, deletedMessageDAO,
- mailboxCounterDAO, mailboxRecentsDAO, blobStore, cassandraConfiguration);
+ mailboxCounterDAO, mailboxRecentsDAO, blobStore, cassandraConfiguration, ImmutableSet.of());
}
}
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 7817f93f82..d24f781761 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
@@ -25,6 +25,7 @@ import static org.apache.james.util.FunctionalUtils.negate;
import static org.apache.james.util.ReactorUtils.DEFAULT_CONCURRENCY;
import java.util.Optional;
+import java.util.Set;
import java.util.function.Predicate;
import javax.inject.Inject;
@@ -32,6 +33,7 @@ import javax.inject.Inject;
import org.apache.james.backends.cassandra.init.configuration.CassandraConfiguration;
import org.apache.james.backends.cassandra.init.configuration.JamesExecutionProfiles;
import org.apache.james.blob.api.BlobStore;
+import org.apache.james.core.Username;
import org.apache.james.events.Event;
import org.apache.james.events.EventListener;
import org.apache.james.events.Group;
@@ -60,6 +62,8 @@ import org.apache.james.mailbox.events.MailboxEvents.Expunged;
import org.apache.james.mailbox.events.MailboxEvents.MailboxDeletion;
import org.apache.james.mailbox.model.ComposedMessageIdWithMetaData;
import org.apache.james.mailbox.model.MailboxACL;
+import org.apache.james.mailbox.model.MailboxId;
+import org.apache.james.mailbox.model.MailboxPath;
import org.apache.james.mailbox.model.MessageMetaData;
import org.apache.james.mailbox.model.MessageRange;
import org.apache.james.mailbox.store.mail.MessageMapper;
@@ -87,6 +91,11 @@ public class DeleteMessageListener implements EventListener.ReactiveGroupEventLi
}
+ @FunctionalInterface
+ public interface DeletionCallback {
+ Mono<Void> forMessage(MessageRepresentation message, MailboxId mailboxId, Username owner);
+ }
+
private final CassandraThreadDAO threadDAO;
private final CassandraThreadLookupDAO threadLookupDAO;
private final CassandraMessageIdToImapUidDAO imapUidDAO;
@@ -104,6 +113,7 @@ public class DeleteMessageListener implements EventListener.ReactiveGroupEventLi
private final CassandraMailboxRecentsDAO recentsDAO;
private final BlobStore blobStore;
private final CassandraConfiguration cassandraConfiguration;
+ private final Set<DeletionCallback> deletionCallbackList;
@Inject
public DeleteMessageListener(CassandraThreadDAO threadDAO, CassandraThreadLookupDAO threadLookupDAO,
@@ -113,7 +123,7 @@ public class DeleteMessageListener implements EventListener.ReactiveGroupEventLi
CassandraUserMailboxRightsDAO rightsDAO, CassandraApplicableFlagDAO applicableFlagDAO,
CassandraFirstUnseenDAO firstUnseenDAO, CassandraDeletedMessageDAO deletedMessageDAO,
CassandraMailboxCounterDAO counterDAO, CassandraMailboxRecentsDAO recentsDAO, BlobStore blobStore,
- CassandraConfiguration cassandraConfiguration) {
+ CassandraConfiguration cassandraConfiguration, Set<DeletionCallback> deletionCallbackList) {
this.threadDAO = threadDAO;
this.threadLookupDAO = threadLookupDAO;
this.imapUidDAO = imapUidDAO;
@@ -131,6 +141,7 @@ public class DeleteMessageListener implements EventListener.ReactiveGroupEventLi
this.recentsDAO = recentsDAO;
this.blobStore = blobStore;
this.cassandraConfiguration = cassandraConfiguration;
+ this.deletionCallbackList = deletionCallbackList;
}
@Override
@@ -155,18 +166,18 @@ public class DeleteMessageListener implements EventListener.ReactiveGroupEventLi
CassandraId mailboxId = (CassandraId) mailboxDeletion.getMailboxId();
- return handleMailboxDeletion(mailboxId);
+ return handleMailboxDeletion(mailboxId, mailboxDeletion.getMailboxPath());
}
return Mono.empty();
}
- private Mono<Void> handleMailboxDeletion(CassandraId mailboxId) {
+ private Mono<Void> handleMailboxDeletion(CassandraId mailboxId, MailboxPath path) {
int prefetch = 1;
return Flux.mergeDelayError(prefetch,
messageIdDAO.retrieveMessages(mailboxId, MessageRange.all(), Limit.unlimited())
.map(CassandraMessageMetadata::getComposedMessageId)
.map(ComposedMessageIdWithMetaData::getComposedMessageId)
- .concatMap(metadata -> handleMessageDeletionAsPartOfMailboxDeletion((CassandraMessageId) metadata.getMessageId(), mailboxId)
+ .concatMap(metadata -> handleMessageDeletionAsPartOfMailboxDeletion((CassandraMessageId) metadata.getMessageId(), mailboxId, path.getUser())
.then(imapUidDAO.delete((CassandraMessageId) metadata.getMessageId(), mailboxId))
.then(messageIdDAO.delete(mailboxId, metadata.getUid()))),
deleteAcl(mailboxId),
@@ -183,7 +194,7 @@ public class DeleteMessageListener implements EventListener.ReactiveGroupEventLi
.values())
.map(MessageMetaData::getMessageId)
.map(CassandraMessageId.class::cast)
- .concatMap(this::handleMessageDeletion)
+ .concatMap(messageId -> handleMessageDeletion(messageId, expunged.getMailboxId(), expunged.getMailboxPath().getUser()))
.then();
}
@@ -193,10 +204,11 @@ public class DeleteMessageListener implements EventListener.ReactiveGroupEventLi
.then(aclMapper.delete(mailboxId)));
}
- private Mono<Void> handleMessageDeletion(CassandraMessageId messageId) {
+ private Mono<Void> handleMessageDeletion(CassandraMessageId messageId, MailboxId mailboxId, Username owner) {
return Mono.just(messageId)
.filterWhen(this::isReferenced)
.flatMap(id -> readMessage(id)
+ .flatMap(message -> Flux.fromIterable(deletionCallbackList).concatMap(callback -> callback.forMessage(message, mailboxId, owner)).then().thenReturn(message))
.flatMap(message -> deleteUnreferencedAttachments(message).thenReturn(message))
.flatMap(this::deleteMessageBlobs)
.flatMap(this::deleteAttachmentMessageIds)
@@ -208,10 +220,11 @@ public class DeleteMessageListener implements EventListener.ReactiveGroupEventLi
.then(threadLookupDAO.deleteOneRow(messageId)));
}
- private Mono<Void> handleMessageDeletionAsPartOfMailboxDeletion(CassandraMessageId messageId, CassandraId excludedId) {
+ private Mono<Void> handleMessageDeletionAsPartOfMailboxDeletion(CassandraMessageId messageId, CassandraId excludedId, Username owner) {
return Mono.just(messageId)
.filterWhen(id -> isReferenced(id, excludedId))
.flatMap(id -> readMessage(id)
+ .flatMap(message -> Flux.fromIterable(deletionCallbackList).concatMap(callback -> callback.forMessage(message, excludedId, owner)).then().thenReturn(message))
.flatMap(message -> deleteUnreferencedAttachments(message).thenReturn(message))
.flatMap(this::deleteMessageBlobs)
.flatMap(this::deleteAttachmentMessageIds)
diff --git a/mailbox/plugin/deleted-messages-vault-cassandra/pom.xml b/mailbox/plugin/deleted-messages-vault-cassandra/pom.xml
index a56d98340d..f66d3bba84 100644
--- a/mailbox/plugin/deleted-messages-vault-cassandra/pom.xml
+++ b/mailbox/plugin/deleted-messages-vault-cassandra/pom.xml
@@ -55,6 +55,10 @@
<type>test-jar</type>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>apache-james-mailbox-cassandra</artifactId>
+ </dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>apache-james-mailbox-deleted-messages-vault</artifactId>
diff --git a/mailbox/plugin/deleted-messages-vault-cassandra/src/main/java/org/apache/james/vault/metadata/DeletedMessageVaultDeletionCallback.java b/mailbox/plugin/deleted-messages-vault-cassandra/src/main/java/org/apache/james/vault/metadata/DeletedMessageVaultDeletionCallback.java
new file mode 100644
index 0000000000..57746c6be7
--- /dev/null
+++ b/mailbox/plugin/deleted-messages-vault-cassandra/src/main/java/org/apache/james/vault/metadata/DeletedMessageVaultDeletionCallback.java
@@ -0,0 +1,133 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+
+package org.apache.james.vault.metadata;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.SequenceInputStream;
+import java.time.Clock;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.Date;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.inject.Inject;
+
+import org.apache.james.blob.api.BlobStore;
+import org.apache.james.core.MailAddress;
+import org.apache.james.core.MaybeSender;
+import org.apache.james.core.Username;
+import org.apache.james.mailbox.cassandra.DeleteMessageListener;
+import org.apache.james.mailbox.cassandra.mail.MessageRepresentation;
+import org.apache.james.mailbox.model.MailboxId;
+import org.apache.james.mailbox.model.MessageId;
+import org.apache.james.mime4j.MimeIOException;
+import org.apache.james.mime4j.codec.DecodeMonitor;
+import org.apache.james.mime4j.dom.Message;
+import org.apache.james.mime4j.dom.address.Mailbox;
+import org.apache.james.mime4j.message.DefaultMessageBuilder;
+import org.apache.james.mime4j.stream.MimeConfig;
+import org.apache.james.server.core.Envelope;
+import org.apache.james.vault.DeletedMessage;
+import org.apache.james.vault.DeletedMessageVault;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableSet;
+
+import reactor.core.publisher.Mono;
+
+public class DeletedMessageVaultDeletionCallback implements DeleteMessageListener.DeletionCallback {
+ private static final Logger LOGGER = LoggerFactory.getLogger(DeletedMessageVaultDeletionCallback.class);
+
+
+ private final DeletedMessageVault deletedMessageVault;
+ private final BlobStore blobStore;
+ private final Clock clock;
+
+ @Inject
+ public DeletedMessageVaultDeletionCallback(DeletedMessageVault deletedMessageVault, BlobStore blobStore, Clock clock) {
+ this.deletedMessageVault = deletedMessageVault;
+ this.blobStore = blobStore;
+ this.clock = clock;
+ }
+
+ @Override
+ public Mono<Void> forMessage(MessageRepresentation message, MailboxId mailboxId, Username owner) {
+ return Mono.from(blobStore.readBytes(blobStore.getDefaultBucketName(), message.getHeaderId(), BlobStore.StoragePolicy.LOW_COST))
+ .flatMap(bytes -> {
+ Optional<Message> mimeMessage = parseMessage(new ByteArrayInputStream(bytes), message.getMessageId());
+ DeletedMessage deletedMessage = DeletedMessage.builder()
+ .messageId(message.getMessageId())
+ .originMailboxes(mailboxId)
+ .user(owner)
+ .deliveryDate(ZonedDateTime.ofInstant(message.getInternalDate().toInstant(), ZoneOffset.UTC))
+ .deletionDate(ZonedDateTime.ofInstant(clock.instant(), ZoneOffset.UTC))
+ .sender(retrieveSender(mimeMessage))
+ .recipients(retrieveRecipients(mimeMessage))
+ .hasAttachment(!message.getAttachments().isEmpty())
+ .size(message.getSize())
+ .subject(mimeMessage.map(Message::getSubject))
+ .build();
+
+ return Mono.from(blobStore.readReactive(blobStore.getDefaultBucketName(), message.getBodyId(), BlobStore.StoragePolicy.LOW_COST))
+ .map(bodyStream -> new SequenceInputStream(new ByteArrayInputStream(bytes), bodyStream))
+ .flatMap(bodyStream -> Mono.from(deletedMessageVault.append(deletedMessage, bodyStream)));
+ });
+ }
+
+ private Optional<Message> parseMessage(InputStream inputStream, MessageId messageId) {
+ DefaultMessageBuilder messageBuilder = new DefaultMessageBuilder();
+ messageBuilder.setMimeEntityConfig(MimeConfig.PERMISSIVE);
+ messageBuilder.setDecodeMonitor(DecodeMonitor.SILENT);
+ try {
+ return Optional.ofNullable(messageBuilder.parseMessage(inputStream));
+ } catch (MimeIOException e) {
+ LOGGER.warn("Can not parse the message {}", messageId, e);
+ return Optional.empty();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ private ZonedDateTime retrieveDeliveryDate(Optional<Message> mimeMessage, org.apache.james.mailbox.store.mail.model.Message message) {
+ return mimeMessage.map(Message::getDate)
+ .map(Date::toInstant)
+ .map(instant -> ZonedDateTime.ofInstant(instant, ZoneOffset.UTC))
+ .orElse(ZonedDateTime.ofInstant(message.getInternalDate().toInstant(), ZoneOffset.UTC));
+ }
+
+ private MaybeSender retrieveSender(Optional<Message> mimeMessage) {
+ return mimeMessage
+ .map(Message::getSender)
+ .map(Mailbox::getAddress)
+ .map(MaybeSender::getMailSender)
+ .orElse(MaybeSender.nullSender());
+ }
+
+ private Set<MailAddress> retrieveRecipients(Optional<Message> maybeMessage) {
+ return maybeMessage.map(message -> Envelope.fromMime4JMessage(message, Envelope.ValidationPolicy.IGNORE))
+ .map(Envelope::getRecipients)
+ .orElse(ImmutableSet.of());
+ }
+}
diff --git a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageVaultHook.java b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageVaultHook.java
index 0af3a67b56..3e5c4b5499 100644
--- a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageVaultHook.java
+++ b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageVaultHook.java
@@ -140,7 +140,7 @@ public class DeletedMessageVaultHook implements PreDeletionHook {
private Flux<DeletedMessageMailboxContext> addOwnerToMetadata(GroupedFlux<MailboxId, MetadataWithMailboxId> groupedFlux) {
return retrieveMailboxUser(groupedFlux.key())
.flatMapMany(owner -> groupedFlux.map(metadata ->
- new DeletedMessageMailboxContext(metadata.getMessageMetaData().getMessageId(), owner, ImmutableList.of(metadata.getMailboxId()))));
+ new DeletedMessageMailboxContext(metadata.getMessageId(), owner, ImmutableList.of(metadata.getMailboxId()))));
}
private Mono<Username> retrieveMailboxUser(MailboxId mailboxId) {
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 0ac05f4934..aae28f62b9 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
@@ -67,7 +67,6 @@ import org.apache.james.mailbox.model.MailboxMetaData.Selectability;
import org.apache.james.mailbox.model.MailboxPath;
import org.apache.james.mailbox.model.MessageId;
import org.apache.james.mailbox.model.MessageId.Factory;
-import org.apache.james.mailbox.model.MessageMetaData;
import org.apache.james.mailbox.model.MessageRange;
import org.apache.james.mailbox.model.MultimailboxesSearchQuery;
import org.apache.james.mailbox.model.QuotaRoot;
@@ -494,8 +493,7 @@ public class StoreMailboxManager implements MailboxManager {
.collect(ImmutableList.toImmutableList())
.flatMap(metadata -> {
long totalSize = metadata.stream()
- .map(MetadataWithMailboxId::getMessageMetaData)
- .mapToLong(MessageMetaData::getSize)
+ .mapToLong(MetadataWithMailboxId::getSize)
.sum();
return preDeletionHooks.runHooks(PreDeletionHook.DeleteOperation.from(metadata))
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageIdManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageIdManager.java
index 99e3c0dda1..6ffa10f1ca 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageIdManager.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageIdManager.java
@@ -81,6 +81,7 @@ import com.github.fge.lambdas.Throwing;
import com.github.fge.lambdas.functions.ThrowingFunction;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
@@ -264,15 +265,16 @@ public class StoreMessageIdManager implements MessageIdManager {
}
private Mono<Void> deleteWithPreHooks(MessageIdMapper messageIdMapper, List<MailboxMessage> messageList, MailboxSession mailboxSession) {
- ImmutableList<MetadataWithMailboxId> metadataWithMailbox = messageList.stream()
- .map(mailboxMessage -> MetadataWithMailboxId.from(mailboxMessage.metaData(), mailboxMessage.getMailboxId()))
- .collect(ImmutableList.toImmutableList());
+ ImmutableMap<MetadataWithMailboxId, MessageMetaData> metadataWithMailbox = messageList.stream()
+ .collect(ImmutableMap.toImmutableMap(
+ message -> MetadataWithMailboxId.from(message.metaData(), message.getMailboxId()),
+ MailboxMessage::metaData));
- return preDeletionHooks.runHooks(PreDeletionHook.DeleteOperation.from(metadataWithMailbox))
+ return preDeletionHooks.runHooks(PreDeletionHook.DeleteOperation.from(metadataWithMailbox.keySet().asList()))
.then(delete(messageIdMapper, messageList, mailboxSession, metadataWithMailbox));
}
- private Mono<Void> delete(MessageIdMapper messageIdMapper, List<MailboxMessage> messageList, MailboxSession mailboxSession, ImmutableList<MetadataWithMailboxId> metadataWithMailbox) {
+ private Mono<Void> delete(MessageIdMapper messageIdMapper, List<MailboxMessage> messageList, MailboxSession mailboxSession, Map<MetadataWithMailboxId, MessageMetaData> metadataWithMailbox) {
MailboxMapper mailboxMapper = mailboxSessionMapperFactory.getMailboxMapper(mailboxSession);
return messageIdMapper.deleteReactive(
@@ -281,15 +283,15 @@ public class StoreMessageIdManager implements MessageIdManager {
Message::getMessageId,
MailboxMessage::getMailboxId)))
.then(
- Flux.fromIterable(metadataWithMailbox)
- .flatMap(metadataWithMailboxId -> mailboxMapper.findMailboxById(metadataWithMailboxId.getMailboxId())
+ Flux.fromIterable(metadataWithMailbox.entrySet())
+ .flatMap(metadataWithMailboxId -> mailboxMapper.findMailboxById(metadataWithMailboxId.getKey().getMailboxId())
.flatMap(mailbox -> eventBus.dispatch(EventFactory.expunged()
.randomEventId()
.mailboxSession(mailboxSession)
.mailbox(mailbox)
- .addMetaData(metadataWithMailboxId.getMessageMetaData())
+ .addMetaData(metadataWithMailboxId.getValue())
.build(),
- new MailboxIdRegistrationKey(metadataWithMailboxId.getMailboxId()))), DEFAULT_CONCURRENCY)
+ new MailboxIdRegistrationKey(metadataWithMailboxId.getKey().getMailboxId()))), DEFAULT_CONCURRENCY)
.then());
}
diff --git a/mailbox/store/src/test/java/org/apache/james/mailbox/store/AbstractMessageIdManagerSideEffectTest.java b/mailbox/store/src/test/java/org/apache/james/mailbox/store/AbstractMessageIdManagerSideEffectTest.java
index f3fa4a1bea..7823b7d401 100644
--- a/mailbox/store/src/test/java/org/apache/james/mailbox/store/AbstractMessageIdManagerSideEffectTest.java
+++ b/mailbox/store/src/test/java/org/apache/james/mailbox/store/AbstractMessageIdManagerSideEffectTest.java
@@ -217,8 +217,7 @@ public abstract class AbstractMessageIdManagerSideEffectTest {
.hasSameElementsAs(preDeleteCaptor2.getValue().getDeletionMetadataList())
.allSatisfy(deleteMetadata -> SoftAssertions.assertSoftly(softy -> {
softy.assertThat(deleteMetadata.getMailboxId()).isEqualTo(mailbox1.getMailboxId());
- softy.assertThat(deleteMetadata.getMessageMetaData().getMessageId()).isEqualTo(messageId);
- softy.assertThat(deleteMetadata.getMessageMetaData().getFlags()).isEqualTo(FLAGS);
+ softy.assertThat(deleteMetadata.getMessageId()).isEqualTo(messageId);
}));
}
@@ -243,9 +242,8 @@ public abstract class AbstractMessageIdManagerSideEffectTest {
.flatExtracting(PreDeletionHook.DeleteOperation::getDeletionMetadataList)
.allSatisfy(deleteMetadata -> SoftAssertions.assertSoftly(softy -> {
softy.assertThat(deleteMetadata.getMailboxId()).isEqualTo(mailbox1.getMailboxId());
- softy.assertThat(deleteMetadata.getMessageMetaData().getFlags()).isEqualTo(FLAGS);
}))
- .extracting(deleteMetadata -> deleteMetadata.getMessageMetaData().getMessageId())
+ .extracting(deleteMetadata -> deleteMetadata.getMessageId())
.containsOnly(messageId1, messageId2);
}
@@ -267,7 +265,7 @@ public abstract class AbstractMessageIdManagerSideEffectTest {
assertThat(preDeleteCaptor1.getAllValues())
.hasSameElementsAs(preDeleteCaptor2.getAllValues())
.flatExtracting(PreDeletionHook.DeleteOperation::getDeletionMetadataList)
- .extracting(deleteMetadata -> deleteMetadata.getMessageMetaData().getMessageId())
+ .extracting(deleteMetadata -> deleteMetadata.getMessageId())
.containsOnly(messageId1, messageId2);
assertThat(preDeleteCaptor1.getAllValues())
diff --git a/server/apps/cassandra-app/sample-configuration/listeners.xml b/server/apps/cassandra-app/sample-configuration/listeners.xml
index 9803da2286..d7c9ae12d3 100644
--- a/server/apps/cassandra-app/sample-configuration/listeners.xml
+++ b/server/apps/cassandra-app/sample-configuration/listeners.xml
@@ -24,12 +24,6 @@
<listener>
<class>org.apache.james.mailbox.cassandra.MailboxOperationLoggingListener</class>
</listener>
- <!-- Requires Deleted Message Vault to be activated. See deletedMessageVault.properties -->
- <!--
- <preDeletionHook>
- <class>org.apache.james.vault.DeletedMessageVaultHook</class>
- </preDeletionHook>
- -->
<!-- Enable to populate JMAP EmailQueryView -->
<!--
diff --git a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/listeners.adoc b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/listeners.adoc
index b9440fba89..1983741022 100644
--- a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/listeners.adoc
+++ b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/listeners.adoc
@@ -171,29 +171,3 @@ To mitigate this issue you might reach your administrator in order to increase y
This MailboxListener is supported.
-== PreDeletionHook configuration
-
-Before deleting a message in James, this message and some related information about the deletion will be passed to a set of PreDeletionHook instances,
-This process is called notifying, and it acts sequentially. If the notifying process for all PreDeletionHooks finish successfully, then the message will be processed to be deleted.
-Otherwise, that message won't be deleted.
-
-Pre Deletion Hook configuration is under the XML element <preDeletionHook>
-
-Already provided additional pre deletion hooks includes:
-
-* `org.apache.james.vault.DeletedMessageVaultHook`: Storing messages about being deleted into
-`org.apache.james.vault.DeletedMessageVault`
-
-
-Example:
-
-....
-<listeners>
- <!-- ... -->
- <preDeletionHook>
- <class>org.apache.james.vault.DeletedMessageVaultHook</class>
- </preDeletionHook>
-</listeners>
-....
-
-Read also xref:configure/vault.adoc[this page] for extra configuration required to support this feature.
diff --git a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/vault.adoc b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/vault.adoc
index 4a6ccae55b..75da84cc3a 100644
--- a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/vault.adoc
+++ b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/vault.adoc
@@ -9,10 +9,7 @@ xref:operate/webadmin.adoc#_deleted_messages_vault[WebAdmin endpoints].
== Deleted Messages Vault Configuration
-To make James use DeletedMessageVault, you need to configure the PreDeletionHook for it.
-
-How to do this is documented in the xref:configure/listeners.adoc[*listeners.xml*]
-Mailbox Listener Configuration page.
+Once the vault is active, James will start moving deleted messages to it asynchronously.
The Deleted Messages Vault also stores and manages deleted messages into a BlobStore. The BlobStore can be either
based on an object storage or on Cassandra. For configuring the BlobStore the vault will use, you can look at
diff --git a/server/apps/distributed-app/sample-configuration/listeners.xml b/server/apps/distributed-app/sample-configuration/listeners.xml
index d90a373d6e..94eba430d4 100644
--- a/server/apps/distributed-app/sample-configuration/listeners.xml
+++ b/server/apps/distributed-app/sample-configuration/listeners.xml
@@ -26,13 +26,6 @@
<class>org.apache.james.mailbox.cassandra.MailboxOperationLoggingListener</class>
</listener>
- <!-- Requires Deleted Message Vault to be activated. See deletedMessageVault.properties -->
- <!--
- <preDeletionHook>
- <class>org.apache.james.vault.DeletedMessageVaultHook</class>
- </preDeletionHook>
- -->
-
<!-- Enable to populate JMAP EmailQueryView -->
<!--
<listener>
diff --git a/server/apps/distributed-pop3-app/sample-configuration/listeners.xml b/server/apps/distributed-pop3-app/sample-configuration/listeners.xml
index b8e256b794..94eba430d4 100644
--- a/server/apps/distributed-pop3-app/sample-configuration/listeners.xml
+++ b/server/apps/distributed-pop3-app/sample-configuration/listeners.xml
@@ -25,12 +25,6 @@
<listener>
<class>org.apache.james.mailbox.cassandra.MailboxOperationLoggingListener</class>
</listener>
- <!-- Requires Deleted Message Vault to be activated. See deletedMessageVault.properties -->
- <!--
- <preDeletionHook>
- <class>org.apache.james.vault.DeletedMessageVaultHook</class>
- </preDeletionHook>
- -->
<!-- Enable to populate JMAP EmailQueryView -->
<!--
diff --git a/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraDeletedMessageVaultModule.java b/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraDeletedMessageVaultModule.java
index 5063a6f237..f8befa6e8f 100644
--- a/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraDeletedMessageVaultModule.java
+++ b/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraDeletedMessageVaultModule.java
@@ -20,6 +20,7 @@
package org.apache.james.modules.mailbox;
import org.apache.james.backends.cassandra.components.CassandraModule;
+import org.apache.james.mailbox.cassandra.DeleteMessageListener;
import org.apache.james.modules.vault.DeletedMessageVaultModule;
import org.apache.james.vault.DeletedMessageVault;
import org.apache.james.vault.blob.BlobStoreDeletedMessageVault;
@@ -28,6 +29,7 @@ import org.apache.james.vault.dto.DeletedMessageWithStorageInformationConverter;
import org.apache.james.vault.metadata.CassandraDeletedMessageMetadataVault;
import org.apache.james.vault.metadata.DeletedMessageMetadataModule;
import org.apache.james.vault.metadata.DeletedMessageMetadataVault;
+import org.apache.james.vault.metadata.DeletedMessageVaultDeletionCallback;
import org.apache.james.vault.metadata.MetadataDAO;
import org.apache.james.vault.metadata.StorageInformationDAO;
import org.apache.james.vault.metadata.UserPerBucketDAO;
@@ -60,5 +62,9 @@ public class CassandraDeletedMessageVaultModule extends AbstractModule {
bind(BlobStoreDeletedMessageVault.class).in(Scopes.SINGLETON);
bind(DeletedMessageVault.class)
.to(BlobStoreDeletedMessageVault.class);
+
+ Multibinder.newSetBinder(binder(), DeleteMessageListener.DeletionCallback.class)
+ .addBinding()
+ .to(DeletedMessageVaultDeletionCallback.class);
}
}
diff --git a/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java b/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java
index 72397e9793..00d6ddb446 100644
--- a/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java
+++ b/server/container/guice/cassandra/src/main/java/org/apache/james/modules/mailbox/CassandraMailboxModule.java
@@ -229,6 +229,7 @@ public class CassandraMailboxModule extends AbstractModule {
.addBinding().to(MailboxAnnotationListener.class);
Multibinder.newSetBinder(binder(), EventListener.ReactiveGroupEventListener.class)
.addBinding().to(DeleteMessageListener.class);
+ Multibinder.newSetBinder(binder(), DeleteMessageListener.DeletionCallback.class);
bind(MailboxManager.class).annotatedWith(Names.named(MAILBOXMANAGER_NAME)).to(MailboxManager.class);
diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/vault/RabbitMQDeletedMessageVaultIntegrationTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/vault/RabbitMQDeletedMessageVaultIntegrationTest.java
index ae708fb09c..78b8216247 100644
--- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/vault/RabbitMQDeletedMessageVaultIntegrationTest.java
+++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/vault/RabbitMQDeletedMessageVaultIntegrationTest.java
@@ -32,7 +32,6 @@ import org.apache.james.modules.AwsS3BlobStoreExtension;
import org.apache.james.modules.RabbitMQExtension;
import org.apache.james.modules.TestJMAPServerModule;
import org.apache.james.modules.blobstore.BlobStoreConfiguration;
-import org.apache.james.modules.vault.TestDeleteMessageVaultPreDeletionHookModule;
import org.apache.james.vault.VaultConfiguration;
import org.apache.james.webadmin.integration.vault.DeletedMessageVaultIntegrationTest;
import org.junit.jupiter.api.Disabled;
@@ -63,8 +62,7 @@ class RabbitMQDeletedMessageVaultIntegrationTest extends DeletedMessageVaultInte
.extension(new RabbitMQExtension())
.extension(new ClockExtension())
.server(configuration -> CassandraRabbitMQJamesServerMain.createServer(configuration)
- .overrideWith(new TestJMAPServerModule())
- .overrideWith(new TestDeleteMessageVaultPreDeletionHookModule()))
+ .overrideWith(new TestJMAPServerModule()))
.build();
@Override
@@ -79,4 +77,5 @@ class RabbitMQDeletedMessageVaultIntegrationTest extends DeletedMessageVaultInte
public void vaultExportShouldExportZipContainsVaultMessagesToShareeWhenImapDeletedMailbox(GuiceJamesServer jmapServer) {
}
+
}
diff --git a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/vault/RabbitMQLinshareBlobExportMechanismIntegrationTest.java b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/vault/RabbitMQLinshareBlobExportMechanismIntegrationTest.java
index 83b3652011..d1cc97fbbb 100644
--- a/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/vault/RabbitMQLinshareBlobExportMechanismIntegrationTest.java
+++ b/server/protocols/webadmin-integration-test/distributed-webadmin-integration-test/src/test/java/org/apache/james/webadmin/integration/rabbitmq/vault/RabbitMQLinshareBlobExportMechanismIntegrationTest.java
@@ -60,7 +60,6 @@ class RabbitMQLinshareBlobExportMechanismIntegrationTest extends LinshareBlobExp
.extension(new LinshareGuiceExtension())
.server(configuration -> CassandraRabbitMQJamesServerMain.createServer(configuration)
.overrideWith(new TestJMAPServerModule())
- .overrideWith(new TestRabbitMQModule(DockerRabbitMQSingleton.SINGLETON))
- .overrideWith(new TestDeleteMessageVaultPreDeletionHookModule()))
+ .overrideWith(new TestRabbitMQModule(DockerRabbitMQSingleton.SINGLETON)))
.build();
}
diff --git a/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/vault/DeletedMessageVaultIntegrationTest.java b/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/vault/DeletedMessageVaultIntegrationTest.java
index b7f899510c..466fb8e05b 100644
--- a/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/vault/DeletedMessageVaultIntegrationTest.java
+++ b/server/protocols/webadmin-integration-test/webadmin-integration-test-common/src/main/java/org/apache/james/webadmin/integration/vault/DeletedMessageVaultIntegrationTest.java
@@ -253,6 +253,7 @@ public abstract class DeletedMessageVaultIntegrationTest {
testIMAPClient.delete(MAILBOX_NAME);
WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(0));
+ Thread.sleep(1000); // Wait for messages to be moved to the vault
restoreAllMessagesOfHomer();
WAIT_TWO_MINUTES.untilAsserted(() -> assertThat(listMessageIdsForAccount(homerAccessToken)).hasSize(1));
@@ -929,6 +930,12 @@ public abstract class DeletedMessageVaultIntegrationTest {
private void homerDeletesMessages(List<String> idsToDestroy) {
deleteMessages(homerAccessToken, idsToDestroy);
+ // Grace period for the vault
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
}
private void bartDeletesMessages(List<String> idsToDestroy) {
diff --git a/src/homepage/howTo/deleted-messages-vault.html b/src/homepage/howTo/deleted-messages-vault.html
index f122c4ac73..f51ccb2a4d 100644
--- a/src/homepage/howTo/deleted-messages-vault.html
+++ b/src/homepage/howTo/deleted-messages-vault.html
@@ -132,6 +132,7 @@ layout: howTo
Before deleting a mail in James, PreDeletionHooks will be triggered to execute all declared hooks. If all hook executions success, then James will process to delete that mail.
There is already a DeletedMessageVaultHook in James, its job is to store deleted mails into Deleted Messages Vault. Thus, you need to configure this hook in listeners.xml configuration file.
</p>
+ <p><b>NOTE:</b> From James 3.8.0 onward the DeletedMessageVaultHook should no longer be specified for Cassandra based products.</p>
<p>
Sample DeletedMessageVaultHook configuration:
diff --git a/src/site/xdoc/server/config-listeners.xml b/src/site/xdoc/server/config-listeners.xml
index b8a018d650..f00736b03f 100644
--- a/src/site/xdoc/server/config-listeners.xml
+++ b/src/site/xdoc/server/config-listeners.xml
@@ -88,6 +88,7 @@
Storing messages about being deleted into <code>org.apache.james.vault.DeletedMessageVault</code>
</li>
</ul>
+ <p><b>NOTE:</b> From James 3.8.0 onward the DeletedMessageVaultHook should no longer be specified for Cassandra based products.</p>
</section>
<section name="Add custom Guice injections for extensions">
diff --git a/upgrade-instructions.md b/upgrade-instructions.md
index 2e464ac34d..adfc5bf7fa 100644
--- a/upgrade-instructions.md
+++ b/upgrade-instructions.md
@@ -26,6 +26,18 @@ Change list:
- [Adding saveDate column to messageIdTable and imapUidTable](#adding-savedate-column-to-messageidtable-and-imapuidtable)
- [Adding the saveDate to the OpenSearch index](#adding-the-savedate-to-the-opensearch-index)
- [Adding delegatedUser column to user_table](#adding-delegatedusers-column-to-user-table)
+- [DeletedMessageVaultHook should not be used on Cassandra based products](#deleted-message-vault-is-now-deactivated-by-default)
+
+### DeletedMessageVaultHook should not be used on Cassandra based products
+
+Date: 10/02/2023
+
+JIRA: https://issues.apache.org/jira/browse/JAMES-3882
+
+Concerned products: Distributed James, Cassandra James Server
+
+When the deleted message vault is enabled in Cassandra products, now the deleted messages are directly copied
+into the vault, asynchronously upon deletions. Configuring DeletedMessageVaultHook is thus no longer needed.
### Adding delegated_users column to user table
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org