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 9a59a5456cd0e5eb1c528c6b4179e83c19933beb Author: datph <[email protected]> AuthorDate: Fri Mar 1 16:29:15 2019 +0700 JAMES-2662 Implement DeletedMessageVaultHook --- mailbox/plugin/deleted-messages-vault/pom.xml | 15 ++ .../james/vault/DeletedMessageConverter.java | 58 ++--- .../apache/james/vault/DeletedMessageMetadata.java | 84 ------ .../james/vault/DeletedMessageVaultHook.java | 168 ++++++++++++ .../james/vault/DeletedMessageConverterTest.java | 25 +- .../apache/james/vault/DeletedMessageFixture.java | 2 + ....java => DeletedMessageMailboxContextTest.java} | 3 +- .../james/vault/DeletedMessageVaultHookTest.java | 289 +++++++++++++++++++++ 8 files changed, 511 insertions(+), 133 deletions(-) diff --git a/mailbox/plugin/deleted-messages-vault/pom.xml b/mailbox/plugin/deleted-messages-vault/pom.xml index 5423077..1063fb4 100644 --- a/mailbox/plugin/deleted-messages-vault/pom.xml +++ b/mailbox/plugin/deleted-messages-vault/pom.xml @@ -43,7 +43,22 @@ <dependency> <groupId>${james.groupId}</groupId> <artifactId>apache-james-mailbox-memory</artifactId> + </dependency> + <dependency> + <groupId>${james.groupId}</groupId> + <artifactId>apache-james-mailbox-memory</artifactId> <scope>test</scope> + <type>test-jar</type> + </dependency> + <dependency> + <groupId>${james.groupId}</groupId> + <artifactId>apache-james-mailbox-store</artifactId> + </dependency> + <dependency> + <groupId>${james.groupId}</groupId> + <artifactId>apache-james-mailbox-store</artifactId> + <scope>test</scope> + <type>test-jar</type> </dependency> <dependency> <groupId>${james.groupId}</groupId> diff --git a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageConverter.java b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageConverter.java index 9c9516b..faacaf7 100644 --- a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageConverter.java +++ b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageConverter.java @@ -20,7 +20,6 @@ package org.apache.james.vault; import java.io.IOException; -import java.time.Clock; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Date; @@ -34,7 +33,6 @@ import javax.mail.internet.AddressException; import org.apache.james.core.MailAddress; import org.apache.james.core.MaybeSender; import org.apache.james.core.User; -import org.apache.james.mailbox.store.mail.model.MailboxMessage; import org.apache.james.mime4j.MimeIOException; import org.apache.james.mime4j.codec.DecodeMonitor; import org.apache.james.mime4j.dom.Message; @@ -54,53 +52,47 @@ class DeletedMessageConverter { private static final Logger LOGGER = LoggerFactory.getLogger(DeletedMessageConverter.class); - private final Clock clock; + DeletedMessage convert(DeletedMessageVaultHook.DeletedMessageMailboxContext deletedMessageMailboxContext, org.apache.james.mailbox.store.mail.model.Message message, ZonedDateTime deletionDate) throws IOException { + Preconditions.checkNotNull(deletedMessageMailboxContext); + Preconditions.checkNotNull(message); - DeletedMessageConverter(Clock clock) { - this.clock = clock; - } - - DeletedMessage convert(DeletedMessageMetadata deletedMessageMetadata, MailboxMessage mailboxMessage) throws IOException { - Preconditions.checkNotNull(deletedMessageMetadata); - Preconditions.checkNotNull(mailboxMessage); - - Optional<Message> mimeMessage = parseMessage(mailboxMessage); + Optional<Message> mimeMessage = parseMessage(message); return DeletedMessage.builder() - .messageId(deletedMessageMetadata.getMessageId()) - .originMailboxes(deletedMessageMetadata.getOwnerMailboxes()) - .user(retrieveOwner(deletedMessageMetadata)) - .deliveryDate(retrieveDeliveryDate(mimeMessage, mailboxMessage)) - .deletionDate(ZonedDateTime.ofInstant(clock.instant(), ZoneOffset.UTC)) + .messageId(deletedMessageMailboxContext.getMessageId()) + .originMailboxes(deletedMessageMailboxContext.getOwnerMailboxes()) + .user(retrieveOwner(deletedMessageMailboxContext)) + .deliveryDate(retrieveDeliveryDate(mimeMessage, message)) + .deletionDate(deletionDate) .sender(retrieveSender(mimeMessage)) .recipients(retrieveRecipients(mimeMessage)) - .hasAttachment(mailboxMessage.getAttachments().iterator().hasNext()) + .hasAttachment(!message.getAttachments().isEmpty()) .subject(mimeMessage.map(Message::getSubject)) .build(); } - private Optional<Message> parseMessage(MailboxMessage mailboxMessage) throws IOException { + private Optional<Message> parseMessage(org.apache.james.mailbox.store.mail.model.Message message) throws IOException { DefaultMessageBuilder messageBuilder = new DefaultMessageBuilder(); messageBuilder.setMimeEntityConfig(MimeConfig.PERMISSIVE); messageBuilder.setDecodeMonitor(DecodeMonitor.SILENT); try { - return Optional.ofNullable(messageBuilder.parseMessage(mailboxMessage.getFullContent())); + return Optional.ofNullable(messageBuilder.parseMessage(message.getFullContent())); } catch (MimeIOException e) { - LOGGER.warn("Can not parse the message {}", mailboxMessage.getUid(), e); + LOGGER.warn("Can not parse the message {}", message.getMessageId(), e); return Optional.empty(); } } - private User retrieveOwner(DeletedMessageMetadata metadata) { - Preconditions.checkNotNull(metadata.getOwner(), "Deleted mail is missing owner"); - return metadata.getOwner(); + private User retrieveOwner(DeletedMessageVaultHook.DeletedMessageMailboxContext deletedMessageMailboxContext) { + Preconditions.checkNotNull(deletedMessageMailboxContext.getOwner(), "Deleted mail is missing owner"); + return deletedMessageMailboxContext.getOwner(); } - private ZonedDateTime retrieveDeliveryDate(Optional<Message> mimeMessage, MailboxMessage mailboxMessage) { + 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(mailboxMessage.getInternalDate().toInstant(), ZoneOffset.UTC)); + .orElse(ZonedDateTime.ofInstant(message.getInternalDate().toInstant(), ZoneOffset.UTC)); } private MaybeSender retrieveSender(Optional<Message> mimeMessage) { @@ -113,11 +105,10 @@ class DeletedMessageConverter { private List<MailAddress> retrieveRecipients(Optional<Message> message) { return StreamUtils.flatten(combineRecipients(message) - .filter(Objects::nonNull) - .map(AddressList::flatten) - .flatMap(MailboxList::stream) - .map(Mailbox::getAddress) - .map(this::retrieveAddress)) + .map(AddressList::flatten) + .flatMap(MailboxList::stream) + .map(Mailbox::getAddress) + .map(this::retrieveAddress)) .collect(Guavate.toImmutableList()); } @@ -132,8 +123,9 @@ class DeletedMessageConverter { private Stream<AddressList> combineRecipients(Optional<Message> message) { return message.map(mimeMessage -> Stream.of(mimeMessage.getTo(), - mimeMessage.getCc(), - mimeMessage.getBcc())) + mimeMessage.getCc(), + mimeMessage.getBcc()) + .filter(Objects::nonNull)) .orElse(Stream.of()); } } diff --git a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageMetadata.java b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageMetadata.java deleted file mode 100644 index 9215ebd..0000000 --- a/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageMetadata.java +++ /dev/null @@ -1,84 +0,0 @@ -/**************************************************************** - * 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; - -import java.util.List; -import java.util.Objects; - -import org.apache.james.core.User; -import org.apache.james.mailbox.model.MailboxId; -import org.apache.james.mailbox.model.MessageId; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; - -class DeletedMessageMetadata { - private final MessageId messageId; - private final User owner; - private final List<MailboxId> ownerMailboxes; - - DeletedMessageMetadata(MessageId messageId, User owner, List<MailboxId> ownerMailboxes) { - this.messageId = messageId; - this.owner = owner; - this.ownerMailboxes = ownerMailboxes; - } - - DeletedMessageMetadata combineWith(DeletedMessageMetadata other) { - Preconditions.checkArgument(messageId.equals(other.messageId)); - Preconditions.checkArgument(owner.equals(other.owner)); - - return new DeletedMessageMetadata( - messageId, - owner, - ImmutableList.<MailboxId>builder() - .addAll(ownerMailboxes) - .addAll(other.ownerMailboxes) - .build()); - } - - public MessageId getMessageId() { - return messageId; - } - - public User getOwner() { - return owner; - } - - public List<MailboxId> getOwnerMailboxes() { - return ownerMailboxes; - } - - @Override - public final boolean equals(Object o) { - if (o instanceof DeletedMessageMetadata) { - DeletedMessageMetadata that = (DeletedMessageMetadata) o; - - return Objects.equals(this.messageId, that.messageId) - && Objects.equals(this.owner, that.owner) - && Objects.equals(this.ownerMailboxes, that.ownerMailboxes); - } - return false; - } - - @Override - public final int hashCode() { - return Objects.hash(messageId, owner, ownerMailboxes); - } -} 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 new file mode 100644 index 0000000..539da4d --- /dev/null +++ b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageVaultHook.java @@ -0,0 +1,168 @@ +/**************************************************************** + * 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; + +import java.time.Clock; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import org.apache.commons.lang3.tuple.Pair; +import org.apache.james.core.User; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.MetadataWithMailboxId; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.extension.PreDeletionHook; +import org.apache.james.mailbox.model.MailboxId; +import org.apache.james.mailbox.model.MessageId; +import org.apache.james.mailbox.store.MailboxSessionMapperFactory; +import org.apache.james.mailbox.store.SessionProvider; +import org.apache.james.mailbox.store.mail.MessageMapper; +import org.apache.james.mailbox.store.mail.model.MailboxMessage; +import org.reactivestreams.Publisher; + +import com.github.fge.lambdas.Throwing; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.GroupedFlux; +import reactor.core.publisher.Mono; + +public class DeletedMessageVaultHook implements PreDeletionHook { + static class DeletedMessageMailboxContext { + private static DeletedMessageMailboxContext combine(DeletedMessageMailboxContext first, DeletedMessageMailboxContext second) { + Preconditions.checkArgument(first.messageId.equals(second.getMessageId())); + Preconditions.checkArgument(first.owner.equals(second.getOwner())); + + return new DeletedMessageMailboxContext( + first.messageId, + first.owner, + ImmutableList.<MailboxId>builder() + .addAll(first.ownerMailboxes) + .addAll(second.ownerMailboxes) + .build()); + } + + private final MessageId messageId; + private final User owner; + private final List<MailboxId> ownerMailboxes; + + DeletedMessageMailboxContext(MessageId messageId, User owner, List<MailboxId> ownerMailboxes) { + this.messageId = messageId; + this.owner = owner; + this.ownerMailboxes = ownerMailboxes; + } + + MessageId getMessageId() { + return messageId; + } + + User getOwner() { + return owner; + } + + List<MailboxId> getOwnerMailboxes() { + return ownerMailboxes; + } + + @Override + public final boolean equals(Object o) { + if (o instanceof DeletedMessageMailboxContext) { + DeletedMessageMailboxContext that = (DeletedMessageMailboxContext) o; + + return Objects.equals(this.messageId, that.getMessageId()) + && Objects.equals(this.owner, that.getOwner()) + && Objects.equals(this.ownerMailboxes, that.getOwnerMailboxes()); + } + return false; + } + + @Override + public final int hashCode() { + return Objects.hash(messageId, owner, ownerMailboxes); + } + } + + private final MailboxSession session; + private final DeletedMessageVault deletedMessageVault; + private final DeletedMessageConverter deletedMessageConverter; + private final MailboxSessionMapperFactory mapperFactory; + private final Clock clock; + + DeletedMessageVaultHook(SessionProvider sessionProvider, + DeletedMessageVault deletedMessageVault, + DeletedMessageConverter deletedMessageConverter, + MailboxSessionMapperFactory mapperFactory, + Clock clock) { + this.session = sessionProvider.createSystemSession(getClass().getName()); + this.deletedMessageVault = deletedMessageVault; + this.deletedMessageConverter = deletedMessageConverter; + this.mapperFactory = mapperFactory; + this.clock = clock; + } + + @Override + public Publisher<Void> notifyDelete(DeleteOperation deleteOperation) { + Preconditions.checkNotNull(deleteOperation); + + return groupMetadataByOwnerAndMessageId(deleteOperation) + .flatMap(Throwing.function(this::appendToTheVault).sneakyThrow()) + .then(); + } + + private Mono<Void> appendToTheVault(DeletedMessageMailboxContext deletedMessageMailboxContext) throws MailboxException { + Optional<MailboxMessage> maybeMailboxMessage = mapperFactory.getMessageIdMapper(session) + .find(ImmutableList.of(deletedMessageMailboxContext.getMessageId()), MessageMapper.FetchType.Full).stream() + .findFirst(); + + return maybeMailboxMessage.map(Throwing.function(mailboxMessage -> Pair.of(mailboxMessage, + deletedMessageConverter.convert(deletedMessageMailboxContext, mailboxMessage, + ZonedDateTime.ofInstant(clock.instant(), ZoneOffset.UTC))))) + .map(Throwing.function(pairs -> Mono.from(deletedMessageVault + .append(pairs.getRight().getOwner(), pairs.getRight(), pairs.getLeft().getFullContent())))) + .orElse(Mono.empty()); + } + + private Flux<DeletedMessageMailboxContext> groupMetadataByOwnerAndMessageId(DeleteOperation deleteOperation) { + return Flux.fromIterable(deleteOperation.getDeletionMetadataList()) + .groupBy(MetadataWithMailboxId::getMailboxId) + .flatMap(Throwing.function(this::addOwnerToMetadata).sneakyThrow()) + .groupBy(this::toMessageIdUserPair) + .flatMap(groupFlux -> groupFlux.reduce(DeletedMessageMailboxContext::combine)); + } + + private Publisher<DeletedMessageMailboxContext> addOwnerToMetadata(GroupedFlux<MailboxId, MetadataWithMailboxId> groupedFlux) throws MailboxException { + User owner = retrieveMailboxUser(groupedFlux.key()); + return groupedFlux.map(metadata -> new DeletedMessageMailboxContext(metadata.getMessageMetaData().getMessageId(), owner, ImmutableList.of(metadata.getMailboxId()))); + } + + private Pair<MessageId, User> toMessageIdUserPair(DeletedMessageMailboxContext deletedMessageMetadata) { + return Pair.of(deletedMessageMetadata.getMessageId(), deletedMessageMetadata.getOwner()); + } + + private User retrieveMailboxUser(MailboxId mailboxId) throws MailboxException { + return User.fromUsername(mapperFactory.getMailboxMapper(session) + .findMailboxById(mailboxId) + .getUser()); + } +} \ No newline at end of file diff --git a/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageConverterTest.java b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageConverterTest.java index 74f7f88..9cea8d0 100644 --- a/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageConverterTest.java +++ b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageConverterTest.java @@ -35,8 +35,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.nio.charset.StandardCharsets; -import java.time.Clock; -import java.time.ZoneOffset; import java.util.Collection; import java.util.Date; import java.util.List; @@ -64,7 +62,7 @@ class DeletedMessageConverterTest { private static final String CC_FIELD = "cc"; private static final List<MailboxId> ORIGIN_MAILBOXES = ImmutableList.of(MAILBOX_ID_1, MAILBOX_ID_2); - private static final DeletedMessageMetadata DELETED_MESSAGE_METADATA = new DeletedMessageMetadata( + private static final DeletedMessageVaultHook.DeletedMessageMailboxContext DELETED_MESSAGE_MAILBOX_CONTEXT = new DeletedMessageVaultHook.DeletedMessageMailboxContext( DeletedMessageFixture.MESSAGE_ID, USER, ORIGIN_MAILBOXES); @@ -102,14 +100,13 @@ class DeletedMessageConverterTest { @BeforeEach void setUp() { - Clock clock = Clock.fixed(DELETION_DATE.toInstant(), ZoneOffset.UTC); - deletedMessageConverter = new DeletedMessageConverter(clock); + deletedMessageConverter = new DeletedMessageConverter(); } @Test void convertShouldThrowWhenNoOwner() { - DeletedMessageMetadata deletedMessageMetadata = new DeletedMessageMetadata(MESSAGE_ID, EMPTY_OWNER, ORIGIN_MAILBOXES); - assertThatThrownBy(() -> deletedMessageConverter.convert(deletedMessageMetadata, buildMessage(getMessageBuilder(), ATTACHMENTS))) + DeletedMessageVaultHook.DeletedMessageMailboxContext deletedMessageMailboxContext = new DeletedMessageVaultHook.DeletedMessageMailboxContext(MESSAGE_ID, EMPTY_OWNER, ORIGIN_MAILBOXES); + assertThatThrownBy(() -> deletedMessageConverter.convert(deletedMessageMailboxContext, buildMessage(getMessageBuilder(), ATTACHMENTS), DELETION_DATE)) .isInstanceOf(NullPointerException.class); } @@ -118,7 +115,7 @@ class DeletedMessageConverterTest { MessageBuilder builder = getMessageBuilder(); builder.headers.remove(DATE_FIELD); - assertThat(deletedMessageConverter.convert(DELETED_MESSAGE_METADATA, buildMessage(builder, NO_ATTACHMENT))) + assertThat(deletedMessageConverter.convert(DELETED_MESSAGE_MAILBOX_CONTEXT, buildMessage(builder, NO_ATTACHMENT), DELETION_DATE)) .isEqualTo(DELETED_MESSAGE_WITH_SUBJECT); } @@ -127,7 +124,7 @@ class DeletedMessageConverterTest { MessageBuilder builder = getMessageBuilder(); builder.headers.remove(SUBJECT_FIELD); - assertThat(deletedMessageConverter.convert(DELETED_MESSAGE_METADATA, buildMessage(builder, NO_ATTACHMENT))) + assertThat(deletedMessageConverter.convert(DELETED_MESSAGE_MAILBOX_CONTEXT, buildMessage(builder, NO_ATTACHMENT), DELETION_DATE)) .isEqualTo(DELETED_MESSAGE); } @@ -135,7 +132,7 @@ class DeletedMessageConverterTest { void convertShouldReturnCorrespondingDeletedMessage() throws Exception { MessageBuilder builder = getMessageBuilder(); - assertThat(deletedMessageConverter.convert(DELETED_MESSAGE_METADATA, buildMessage(builder, NO_ATTACHMENT))) + assertThat(deletedMessageConverter.convert(DELETED_MESSAGE_MAILBOX_CONTEXT, buildMessage(builder, NO_ATTACHMENT), DELETION_DATE)) .isEqualTo(DELETED_MESSAGE_WITH_SUBJECT); } @@ -145,7 +142,7 @@ class DeletedMessageConverterTest { builder.headers.remove(TO_FIELD); builder.headers.remove(CC_FIELD); - DeletedMessage deletedMessage = deletedMessageConverter.convert(DELETED_MESSAGE_METADATA, buildMessage(builder, NO_ATTACHMENT)); + DeletedMessage deletedMessage = deletedMessageConverter.convert(DELETED_MESSAGE_MAILBOX_CONTEXT, buildMessage(builder, NO_ATTACHMENT), DELETION_DATE); assertThat(deletedMessage.getRecipients()) .isEmpty(); @@ -157,7 +154,7 @@ class DeletedMessageConverterTest { builder.header(TO_FIELD, "bad@bad@bad"); builder.header(CC_FIELD, "dad@"); - DeletedMessage deletedMessage = deletedMessageConverter.convert(DELETED_MESSAGE_METADATA, buildMessage(builder, NO_ATTACHMENT)); + DeletedMessage deletedMessage = deletedMessageConverter.convert(DELETED_MESSAGE_MAILBOX_CONTEXT, buildMessage(builder, NO_ATTACHMENT), DELETION_DATE); assertThat(deletedMessage.getRecipients()) .isEmpty(); } @@ -167,7 +164,7 @@ class DeletedMessageConverterTest { MessageBuilder builder = getMessageBuilder(); builder.header(TO_FIELD, "bad@bad@bad"); - DeletedMessage deletedMessage = deletedMessageConverter.convert(DELETED_MESSAGE_METADATA, buildMessage(builder, NO_ATTACHMENT)); + DeletedMessage deletedMessage = deletedMessageConverter.convert(DELETED_MESSAGE_MAILBOX_CONTEXT, buildMessage(builder, NO_ATTACHMENT), DELETION_DATE); assertThat(deletedMessage.getRecipients()) .containsOnly(RECIPIENT2); } @@ -177,7 +174,7 @@ class DeletedMessageConverterTest { MessageBuilder builder = getMessageBuilder(); builder.headers.remove(SENDER_FIELD); - DeletedMessage deletedMessage = deletedMessageConverter.convert(DELETED_MESSAGE_METADATA, buildMessage(builder, NO_ATTACHMENT)); + DeletedMessage deletedMessage = deletedMessageConverter.convert(DELETED_MESSAGE_MAILBOX_CONTEXT, buildMessage(builder, NO_ATTACHMENT), DELETION_DATE); assertThat(deletedMessage.getSender()) .isEqualTo(MaybeSender.nullSender()); diff --git a/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageFixture.java b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageFixture.java index 612b17b..d2e5c31 100644 --- a/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageFixture.java +++ b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageFixture.java @@ -25,6 +25,7 @@ import static org.apache.mailet.base.MailAddressFixture.SENDER; import java.nio.charset.StandardCharsets; import java.time.ZonedDateTime; +import java.util.Date; import java.util.function.Function; import java.util.function.Supplier; @@ -43,6 +44,7 @@ public interface DeletedMessageFixture { User USER_2 = User.fromUsername("[email protected]"); ZonedDateTime DELIVERY_DATE = ZonedDateTime.parse("2014-10-30T14:12:00Z"); ZonedDateTime DELETION_DATE = ZonedDateTime.parse("2015-10-30T14:12:00Z"); + Date INTERNAL_DATE = Date.from(DELIVERY_DATE.toInstant()); byte[] CONTENT = "header: value\r\n\r\ncontent".getBytes(StandardCharsets.UTF_8); String SUBJECT = "subject"; diff --git a/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageMetadataTest.java b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageMailboxContextTest.java similarity index 97% rename from mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageMetadataTest.java rename to mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageMailboxContextTest.java index caa8271..fa8772b 100644 --- a/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageMetadataTest.java +++ b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageMailboxContextTest.java @@ -24,8 +24,7 @@ import org.junit.jupiter.api.Test; import nl.jqno.equalsverifier.EqualsVerifier; -public class DeletedMessageMetadataTest { - +class DeletedMessageMailboxContextTest { @Test void shouldMatchBeanContract() { EqualsVerifier.forClass(PreDeletionHook.DeleteOperation.class) diff --git a/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageVaultHookTest.java b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageVaultHookTest.java new file mode 100644 index 0000000..ff88104 --- /dev/null +++ b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageVaultHookTest.java @@ -0,0 +1,289 @@ +/**************************************************************** + * 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; + +import static org.apache.james.vault.DeletedMessageFixture.DELETION_DATE; +import static org.apache.james.vault.DeletedMessageFixture.DELIVERY_DATE; +import static org.apache.james.vault.DeletedMessageFixture.INTERNAL_DATE; +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.charset.StandardCharsets; +import java.time.Clock; +import java.time.ZoneOffset; +import java.util.List; + +import org.apache.james.core.MailAddress; +import org.apache.james.core.MaybeSender; +import org.apache.james.core.User; +import org.apache.james.mailbox.MailboxManager; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.MessageIdManager; +import org.apache.james.mailbox.MessageManager; +import org.apache.james.mailbox.inmemory.InMemoryMailboxSessionMapperFactory; +import org.apache.james.mailbox.model.ComposedMessageId; +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.MessageId; +import org.apache.james.mailbox.model.SearchQuery; +import org.apache.james.mailbox.store.Authenticator; +import org.apache.james.mailbox.store.Authorizator; +import org.apache.james.mailbox.store.CombinationManagerTestSystem; +import org.apache.james.mailbox.store.SessionProvider; +import org.apache.james.mailbox.store.mail.model.Mailbox; +import org.apache.james.mime4j.dom.Message; +import org.apache.james.vault.memory.MemoryDeletedMessagesVault; +import org.apache.james.vault.search.Query; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +class DeletedMessageVaultHookTest { + + private static final String ALICE_ADDRESS = "[email protected]"; + private static final String BOB_ADDRESS = "[email protected]"; + private static final String TEST_ADDRESS = "[email protected]"; + private static final User ALICE = User.fromUsername(ALICE_ADDRESS); + private static final User BOB = User.fromUsername(BOB_ADDRESS); + + private static final MailboxPath MAILBOX_ALICE_ONE = MailboxPath.forUser(ALICE_ADDRESS, "ALICE_ONE"); + private static final MailboxPath MAILBOX_BOB_ONE = MailboxPath.forUser(BOB_ADDRESS, "BOB_ONE"); + + private MailboxManager mailboxManager; + private MessageIdManager messageIdManager; + private Message mailContent; + private MemoryDeletedMessagesVault messageVault; + private Clock clock; + private SessionProvider sessionProvider; + private MailboxSession aliceSession; + private MailboxSession bobSession; + private InMemoryMailboxSessionMapperFactory mapperFactory; + private CombinationManagerTestSystem testingData; + private SearchQuery searchQuery; + + private DeletedMessage buildDeletedMessage(List<MailboxId> mailboxIds, MessageId messageId, User user) throws Exception { + return DeletedMessage.builder() + .messageId(messageId) + .originMailboxes(mailboxIds) + .user(user) + .deliveryDate(DELIVERY_DATE) + .deletionDate(DELETION_DATE) + .sender(MaybeSender.getMailSender(ALICE_ADDRESS)) + .recipients(new MailAddress(TEST_ADDRESS)) + .hasAttachment(false) + .subject("test") + .build(); + } + + private ComposedMessageId appendMessage(MessageManager messageManager) throws Exception { + return messageManager.appendMessage(MessageManager.AppendCommand.builder() + .withInternalDate(INTERNAL_DATE) + .build(mailContent), aliceSession); + } + + @BeforeEach + void setUp() throws Exception { + clock = Clock.fixed(DELETION_DATE.toInstant(), ZoneOffset.UTC); + messageVault = new MemoryDeletedMessagesVault(); + + Authenticator noAuthenticator = null; + Authorizator noAuthorizator = null; + sessionProvider = new SessionProvider(noAuthenticator, noAuthorizator); + aliceSession = sessionProvider.createSystemSession(ALICE_ADDRESS); + bobSession = sessionProvider.createSystemSession(BOB_ADDRESS); + + mapperFactory = new InMemoryMailboxSessionMapperFactory(); + DeletedMessageConverter deletedMessageConverter = new DeletedMessageConverter(); + DeletedMessageVaultHook messageVaultHook = new DeletedMessageVaultHook(sessionProvider, messageVault, deletedMessageConverter, mapperFactory, clock); + + testingData = MessageIdManagerWithPreDeletionHooksTestSystemProvider + .createTestingData(sessionProvider, mapperFactory, ImmutableSet.of(messageVaultHook)); + + mailboxManager = testingData.getMailboxManager(); + messageIdManager = testingData.getMessageIdManager(); + + mailContent = Message.Builder.of() + .setSubject("test") + .setBody("testmail", StandardCharsets.UTF_8) + .setSender(ALICE_ADDRESS) + .setTo(TEST_ADDRESS) + .setDate(INTERNAL_DATE) + .build(); + + searchQuery = new SearchQuery(); + searchQuery.andCriteria(SearchQuery.internalDateOn(INTERNAL_DATE, SearchQuery.DateResolution.Second)); + } + + @Test + void notifyDeleteShouldAppendMessageVault() throws Exception { + Mailbox aliceMailbox = testingData.createMailbox(MAILBOX_ALICE_ONE, aliceSession); + MessageManager messageManager = testingData.createMessageManager(aliceMailbox, aliceSession); + MessageId messageId = appendMessage(messageManager).getMessageId(); + + messageIdManager.delete(ImmutableList.of(messageId), aliceSession); + + DeletedMessage deletedMessage = buildDeletedMessage(ImmutableList.of(aliceMailbox.getMailboxId()), messageId, ALICE); + assertThat(messageVault.search(ALICE, Query.ALL).blockFirst()) + .isEqualTo(deletedMessage); + } + + @Test + void notifyDeleteShouldAppendMessageToVaultOfMailboxOwnerWhenOtherUserDeleteMessageInSharingMailbox() throws Exception { + Mailbox aliceMailbox = testingData.createMailbox(MAILBOX_ALICE_ONE, aliceSession); + MessageManager aliceMessageManager = testingData.createMessageManager(aliceMailbox, aliceSession); + MessageManager bobMessageManager = testingData.createMessageManager(aliceMailbox, bobSession); + ComposedMessageId composedMessageId = appendMessage(aliceMessageManager); + MessageId messageId = composedMessageId.getMessageId(); + + mailboxManager.setRights(MAILBOX_ALICE_ONE, + MailboxACL.EMPTY.apply(MailboxACL.command() + .forUser(BOB_ADDRESS) + .rights(MailboxACL.Right.Lookup, MailboxACL.Right.Read, MailboxACL.Right.DeleteMessages, MailboxACL.Right.PerformExpunge) + .asAddition()), + aliceSession); + + DeletedMessage deletedMessage = buildDeletedMessage(ImmutableList.of(aliceMailbox.getMailboxId()), messageId, ALICE); + bobMessageManager.delete(ImmutableList.copyOf(bobMessageManager.search(searchQuery, bobSession)), bobSession); + + assertThat(messageVault.search(ALICE, Query.ALL).blockFirst()) + .isEqualTo(deletedMessage); + } + + @Test + void notifyDeleteShouldNotAppendMessageToVaultOfOtherUserOfMailboxWhenOtherUserDeleteMessageInSharingMailbox() throws Exception { + Mailbox aliceMailbox = testingData.createMailbox(MAILBOX_ALICE_ONE, aliceSession); + MessageManager aliceMessageManager = testingData.createMessageManager(aliceMailbox, aliceSession); + MessageManager bobMessageManager = testingData.createMessageManager(aliceMailbox, bobSession); + appendMessage(aliceMessageManager); + + mailboxManager.setRights(MAILBOX_ALICE_ONE, + MailboxACL.EMPTY.apply(MailboxACL.command() + .forUser(BOB_ADDRESS) + .rights(MailboxACL.Right.Lookup, MailboxACL.Right.Read, MailboxACL.Right.DeleteMessages, MailboxACL.Right.PerformExpunge) + .asAddition()), + aliceSession); + + bobMessageManager.delete(ImmutableList.copyOf(bobMessageManager.search(searchQuery, bobSession)), bobSession); + + assertThat(messageVault.search(BOB, Query.ALL).collectList().block()) + .isEmpty(); + } + + @Test + void notifyDeleteShouldAppendMessageToVaultOfOtherUserOfMailboxWhenOtherUserDeleteMessageAfterMoveToAnotherMailbox() throws Exception { + Mailbox aliceMailbox = testingData.createMailbox(MAILBOX_ALICE_ONE, aliceSession); + Mailbox bobMailbox = testingData.createMailbox(MAILBOX_BOB_ONE, bobSession); + MessageManager aliceMessageManager = testingData.createMessageManager(aliceMailbox, aliceSession); + MessageManager bobMessageManager = testingData.createMessageManager(bobMailbox, bobSession); + ComposedMessageId composedMessageId = appendMessage(aliceMessageManager); + MessageId messageId = composedMessageId.getMessageId(); + + mailboxManager.setRights(MAILBOX_ALICE_ONE, + MailboxACL.EMPTY.apply(MailboxACL.command() + .forUser(BOB_ADDRESS) + .rights(MailboxACL.Right.Lookup, MailboxACL.Right.Read, MailboxACL.Right.DeleteMessages, MailboxACL.Right.PerformExpunge) + .asAddition()), + aliceSession); + + messageIdManager.setInMailboxes(messageId, ImmutableList.of(bobMailbox.getMailboxId()), bobSession); + + DeletedMessage deletedMessage = buildDeletedMessage(ImmutableList.of(bobMailbox.getMailboxId()), messageId, BOB); + bobMessageManager.delete(ImmutableList.copyOf(bobMessageManager.search(searchQuery, bobSession)), bobSession); + + assertThat(messageVault.search(BOB, Query.ALL).blockFirst()) + .isEqualTo(deletedMessage); + } + + @Test + void notifyDeleteShouldNotAppendMessageToVaultOfMailboxOwnerWhenOtherUserDeleteMessageAfterMoveToAnotherMailbox() throws Exception { + Mailbox aliceMailbox = testingData.createMailbox(MAILBOX_ALICE_ONE, aliceSession); + Mailbox bobMailbox = testingData.createMailbox(MAILBOX_BOB_ONE, bobSession); + MessageManager aliceMessageManager = testingData.createMessageManager(aliceMailbox, aliceSession); + MessageManager bobMessageManager = testingData.createMessageManager(bobMailbox, bobSession); + ComposedMessageId composedMessageId = appendMessage(aliceMessageManager); + MessageId messageId = composedMessageId.getMessageId(); + + mailboxManager.setRights(MAILBOX_ALICE_ONE, + MailboxACL.EMPTY.apply(MailboxACL.command() + .forUser(BOB_ADDRESS) + .rights(MailboxACL.Right.Lookup, MailboxACL.Right.Read, MailboxACL.Right.DeleteMessages, MailboxACL.Right.PerformExpunge) + .asAddition()), + aliceSession); + + messageIdManager.setInMailboxes(messageId, ImmutableList.of(bobMailbox.getMailboxId()), bobSession); + + bobMessageManager.delete(ImmutableList.copyOf(bobMessageManager.search(searchQuery, bobSession)), bobSession); + + assertThat(messageVault.search(ALICE, Query.ALL).collectList().block()) + .isEmpty(); + } + + @Test + void notifyDeleteShouldAppendMessageToVaultOfOtherUserOfMailboxWhenOtherUserDeleteMessageAfterCopyToAnotherMailbox() throws Exception { + Mailbox aliceMailbox = testingData.createMailbox(MAILBOX_ALICE_ONE, aliceSession); + Mailbox bobMailbox = testingData.createMailbox(MAILBOX_BOB_ONE, bobSession); + MessageManager aliceMessageManager = testingData.createMessageManager(aliceMailbox, aliceSession); + MessageManager bobMessageManager = testingData.createMessageManager(bobMailbox, bobSession); + ComposedMessageId composedMessageId = appendMessage(aliceMessageManager); + MessageId messageId = composedMessageId.getMessageId(); + + mailboxManager.setRights(MAILBOX_ALICE_ONE, + MailboxACL.EMPTY.apply(MailboxACL.command() + .forUser(BOB_ADDRESS) + .rights(MailboxACL.Right.Lookup, MailboxACL.Right.Read, MailboxACL.Right.DeleteMessages, MailboxACL.Right.PerformExpunge) + .asAddition()), + aliceSession); + + messageIdManager.setInMailboxes(messageId, ImmutableList.of(aliceMailbox.getMailboxId(), bobMailbox.getMailboxId()), bobSession); + + DeletedMessage deletedMessage = buildDeletedMessage(ImmutableList.of(bobMailbox.getMailboxId()), messageId, BOB); + bobMessageManager.delete(ImmutableList.copyOf(bobMessageManager.search(searchQuery, bobSession)), bobSession); + + assertThat(messageVault.search(BOB, Query.ALL).blockFirst()) + .isEqualTo(deletedMessage); + } + + @Test + void notifyDeleteShouldNotAppendMessageToVaultOfMailboxOwnerWhenOtherUserDeleteMessageAfterCopyToAnotherMailbox() throws Exception { + Mailbox aliceMailbox = testingData.createMailbox(MAILBOX_ALICE_ONE, aliceSession); + Mailbox bobMailbox = testingData.createMailbox(MAILBOX_BOB_ONE, bobSession); + MessageManager aliceMessageManager = testingData.createMessageManager(aliceMailbox, aliceSession); + MessageManager bobMessageManager = testingData.createMessageManager(bobMailbox, bobSession); + ComposedMessageId composedMessageId = appendMessage(aliceMessageManager); + MessageId messageId = composedMessageId.getMessageId(); + + mailboxManager.setRights(MAILBOX_ALICE_ONE, + MailboxACL.EMPTY.apply(MailboxACL.command() + .forUser(BOB_ADDRESS) + .rights(MailboxACL.Right.Lookup, MailboxACL.Right.Read, MailboxACL.Right.DeleteMessages, MailboxACL.Right.PerformExpunge) + .asAddition()), + aliceSession); + + messageIdManager.setInMailboxes(messageId, ImmutableList.of(aliceMailbox.getMailboxId(), bobMailbox.getMailboxId()), bobSession); + + bobMessageManager.delete(ImmutableList.copyOf(bobMessageManager.search(searchQuery, bobSession)), bobSession); + + assertThat(messageVault.search(ALICE, Query.ALL).collectList().block()) + .isEmpty(); + } + +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
