You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by bt...@apache.org on 2019/03/08 01:13:46 UTC

[james-project] 02/14: JAMES-2662 Introduce converter DeletedMessage

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 6eb2e9fcc6d833ef1acf485ab2c45a6783e9612f
Author: datph <dp...@linagora.com>
AuthorDate: Fri Mar 1 16:24:37 2019 +0700

    JAMES-2662 Introduce converter DeletedMessage
---
 .../james/vault/DeletedMessageConverter.java       | 139 ++++++++++++++++
 .../james/vault/DeletedMessageConverterTest.java   | 185 +++++++++++++++++++++
 2 files changed, 324 insertions(+)

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
new file mode 100644
index 0000000..9c9516b
--- /dev/null
+++ b/mailbox/plugin/deleted-messages-vault/src/main/java/org/apache/james/vault/DeletedMessageConverter.java
@@ -0,0 +1,139 @@
+/****************************************************************
+ * 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.io.IOException;
+import java.time.Clock;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+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;
+import org.apache.james.mime4j.dom.address.AddressList;
+import org.apache.james.mime4j.dom.address.Mailbox;
+import org.apache.james.mime4j.dom.address.MailboxList;
+import org.apache.james.mime4j.message.DefaultMessageBuilder;
+import org.apache.james.mime4j.stream.MimeConfig;
+import org.apache.james.util.StreamUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.github.steveash.guavate.Guavate;
+import com.google.common.base.Preconditions;
+
+class DeletedMessageConverter {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(DeletedMessageConverter.class);
+
+    private final Clock clock;
+
+    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);
+
+        return DeletedMessage.builder()
+            .messageId(deletedMessageMetadata.getMessageId())
+            .originMailboxes(deletedMessageMetadata.getOwnerMailboxes())
+            .user(retrieveOwner(deletedMessageMetadata))
+            .deliveryDate(retrieveDeliveryDate(mimeMessage, mailboxMessage))
+            .deletionDate(ZonedDateTime.ofInstant(clock.instant(), ZoneOffset.UTC))
+            .sender(retrieveSender(mimeMessage))
+            .recipients(retrieveRecipients(mimeMessage))
+            .hasAttachment(mailboxMessage.getAttachments().iterator().hasNext())
+            .subject(mimeMessage.map(Message::getSubject))
+            .build();
+    }
+
+    private Optional<Message> parseMessage(MailboxMessage mailboxMessage) throws IOException {
+        DefaultMessageBuilder messageBuilder = new DefaultMessageBuilder();
+        messageBuilder.setMimeEntityConfig(MimeConfig.PERMISSIVE);
+        messageBuilder.setDecodeMonitor(DecodeMonitor.SILENT);
+        try {
+            return Optional.ofNullable(messageBuilder.parseMessage(mailboxMessage.getFullContent()));
+        } catch (MimeIOException e) {
+            LOGGER.warn("Can not parse the message {}", mailboxMessage.getUid(), e);
+            return Optional.empty();
+        }
+    }
+
+    private User retrieveOwner(DeletedMessageMetadata metadata) {
+        Preconditions.checkNotNull(metadata.getOwner(), "Deleted mail is missing owner");
+        return metadata.getOwner();
+    }
+
+    private ZonedDateTime retrieveDeliveryDate(Optional<Message> mimeMessage, MailboxMessage mailboxMessage) {
+        return mimeMessage.map(Message::getDate)
+            .map(Date::toInstant)
+            .map(instant -> ZonedDateTime.ofInstant(instant, ZoneOffset.UTC))
+            .orElse(ZonedDateTime.ofInstant(mailboxMessage.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 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))
+            .collect(Guavate.toImmutableList());
+    }
+
+    private Stream<MailAddress> retrieveAddress(String address) {
+        try {
+            return Stream.of(new MailAddress(address));
+        } catch (AddressException e) {
+            LOGGER.warn("Can not create the mailAddress from {}", address, e);
+            return Stream.of();
+        }
+    }
+
+    private Stream<AddressList> combineRecipients(Optional<Message> message) {
+        return message.map(mimeMessage -> Stream.of(mimeMessage.getTo(),
+                mimeMessage.getCc(),
+                mimeMessage.getBcc()))
+            .orElse(Stream.of());
+    }
+}
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
new file mode 100644
index 0000000..74f7f88
--- /dev/null
+++ b/mailbox/plugin/deleted-messages-vault/src/test/java/org/apache/james/vault/DeletedMessageConverterTest.java
@@ -0,0 +1,185 @@
+/****************************************************************
+ * 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.DELETED_MESSAGE;
+import static org.apache.james.vault.DeletedMessageFixture.DELETED_MESSAGE_WITH_SUBJECT;
+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.MAILBOX_ID_1;
+import static org.apache.james.vault.DeletedMessageFixture.MAILBOX_ID_2;
+import static org.apache.james.vault.DeletedMessageFixture.MESSAGE_ID;
+import static org.apache.james.vault.DeletedMessageFixture.SUBJECT;
+import static org.apache.james.vault.DeletedMessageFixture.USER;
+import static org.apache.mailet.base.MailAddressFixture.RECIPIENT1;
+import static org.apache.mailet.base.MailAddressFixture.RECIPIENT2;
+import static org.apache.mailet.base.MailAddressFixture.SENDER;
+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;
+
+import org.apache.james.core.MaybeSender;
+import org.apache.james.core.User;
+import org.apache.james.mailbox.model.Attachment;
+import org.apache.james.mailbox.model.MailboxId;
+import org.apache.james.mailbox.model.MessageAttachment;
+import org.apache.james.mailbox.store.MessageBuilder;
+import org.apache.james.mailbox.store.mail.model.MailboxMessage;
+import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import com.google.common.collect.ImmutableList;
+
+class DeletedMessageConverterTest {
+
+    private static final String FROM_FIELD = "From";
+    private static final String TO_FIELD = "To";
+    private static final String DATE_FIELD = "Date";
+    private static final String SUBJECT_FIELD = "Subject";
+    private static final String SENDER_FIELD = "Sender";
+    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(
+        DeletedMessageFixture.MESSAGE_ID,
+        USER,
+        ORIGIN_MAILBOXES);
+
+    private static final User EMPTY_OWNER = null;
+
+    private static final Collection<MessageAttachment> NO_ATTACHMENT = ImmutableList.of();
+    private static final Collection<MessageAttachment> ATTACHMENTS = ImmutableList.of(MessageAttachment.builder()
+        .attachment(Attachment.builder()
+            .bytes("content".getBytes(StandardCharsets.UTF_8))
+            .type("type")
+            .build())
+        .build());
+
+    private DeletedMessageConverter deletedMessageConverter;
+
+    private MessageBuilder getMessageBuilder() {
+        MessageBuilder builder = new MessageBuilder();
+        builder.header(SENDER_FIELD, SENDER.asString());
+        builder.header(FROM_FIELD, "alice@james.com");
+        builder.header(TO_FIELD, RECIPIENT1.asString());
+        builder.header(CC_FIELD, RECIPIENT2.asString());
+        builder.header(SUBJECT_FIELD, SUBJECT);
+        builder.header(DATE_FIELD, "Thu, 30 Oct 2014 14:12:00 +0000 (GMT)");
+        return builder;
+    }
+
+    private MailboxMessage buildMessage(MessageBuilder messageBuilder, Collection<MessageAttachment> attachments) throws Exception {
+        MailboxMessage mailboxMessage = messageBuilder.build(MESSAGE_ID);
+        return SimpleMailboxMessage.fromWithoutAttachments(mailboxMessage)
+            .mailboxId(mailboxMessage.getMailboxId())
+            .internalDate(Date.from(DELIVERY_DATE.toInstant()))
+            .addAttachments(attachments).build();
+    }
+
+    @BeforeEach
+    void setUp() {
+        Clock clock = Clock.fixed(DELETION_DATE.toInstant(), ZoneOffset.UTC);
+        deletedMessageConverter = new DeletedMessageConverter(clock);
+    }
+
+    @Test
+    void convertShouldThrowWhenNoOwner() {
+        DeletedMessageMetadata deletedMessageMetadata = new DeletedMessageMetadata(MESSAGE_ID, EMPTY_OWNER, ORIGIN_MAILBOXES);
+        assertThatThrownBy(() -> deletedMessageConverter.convert(deletedMessageMetadata, buildMessage(getMessageBuilder(), ATTACHMENTS)))
+            .isInstanceOf(NullPointerException.class);
+    }
+
+    @Test
+    void convertShouldReturnCorrespondingDeletedMessageWhenNoDeliveryDateInMimeMessageButInMailboxMessage() throws Exception {
+        MessageBuilder builder = getMessageBuilder();
+        builder.headers.remove(DATE_FIELD);
+
+        assertThat(deletedMessageConverter.convert(DELETED_MESSAGE_METADATA, buildMessage(builder, NO_ATTACHMENT)))
+            .isEqualTo(DELETED_MESSAGE_WITH_SUBJECT);
+    }
+
+    @Test
+    void convertShouldReturnCorrespondingDeletedMessageWhenNoSubject() throws Exception {
+        MessageBuilder builder = getMessageBuilder();
+        builder.headers.remove(SUBJECT_FIELD);
+
+        assertThat(deletedMessageConverter.convert(DELETED_MESSAGE_METADATA, buildMessage(builder, NO_ATTACHMENT)))
+            .isEqualTo(DELETED_MESSAGE);
+    }
+
+    @Test
+    void convertShouldReturnCorrespondingDeletedMessage() throws Exception {
+        MessageBuilder builder = getMessageBuilder();
+
+        assertThat(deletedMessageConverter.convert(DELETED_MESSAGE_METADATA, buildMessage(builder, NO_ATTACHMENT)))
+            .isEqualTo(DELETED_MESSAGE_WITH_SUBJECT);
+    }
+
+    @Test
+    void convertShouldReturnCorrespondingDeletedMessageWhenNoRecipient() throws Exception {
+        MessageBuilder builder = getMessageBuilder();
+        builder.headers.remove(TO_FIELD);
+        builder.headers.remove(CC_FIELD);
+
+        DeletedMessage deletedMessage = deletedMessageConverter.convert(DELETED_MESSAGE_METADATA, buildMessage(builder, NO_ATTACHMENT));
+
+        assertThat(deletedMessage.getRecipients())
+            .isEmpty();
+    }
+
+    @Test
+    void convertShouldReturnCorrespondingDeletedMessageWhenInvalidRecipient() throws Exception {
+        MessageBuilder builder = getMessageBuilder();
+        builder.header(TO_FIELD, "bad@bad@bad");
+        builder.header(CC_FIELD, "dad@");
+
+        DeletedMessage deletedMessage = deletedMessageConverter.convert(DELETED_MESSAGE_METADATA, buildMessage(builder, NO_ATTACHMENT));
+        assertThat(deletedMessage.getRecipients())
+            .isEmpty();
+    }
+
+    @Test
+    void convertShouldReturnCorrespondingDeletedMessageWhenInvalidHaveOneOfBadRecipient() throws Exception {
+        MessageBuilder builder = getMessageBuilder();
+        builder.header(TO_FIELD, "bad@bad@bad");
+
+        DeletedMessage deletedMessage = deletedMessageConverter.convert(DELETED_MESSAGE_METADATA, buildMessage(builder, NO_ATTACHMENT));
+        assertThat(deletedMessage.getRecipients())
+            .containsOnly(RECIPIENT2);
+    }
+
+    @Test
+    void convertShouldReturnCorrespondingDeletedMessageWhenNoSender() throws Exception {
+        MessageBuilder builder = getMessageBuilder();
+        builder.headers.remove(SENDER_FIELD);
+
+        DeletedMessage deletedMessage = deletedMessageConverter.convert(DELETED_MESSAGE_METADATA, buildMessage(builder, NO_ATTACHMENT));
+
+        assertThat(deletedMessage.getSender())
+            .isEqualTo(MaybeSender.nullSender());
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org