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 503e33494d89a4a4c8b3ecdf38e8c553aca21cd7 Author: Tran Tien Duc <[email protected]> AuthorDate: Fri Nov 29 17:43:42 2019 +0700 JAMES-2992 MessageFastViewFactory implementation --- .../api/projections/MessageFastViewProjection.java | 20 ++ .../MessageFastViewProjectionContract.java | 67 +++++- .../methods/SetMessagesCreationProcessor.java | 19 +- .../org/apache/james/jmap/draft/model/Emailer.java | 30 ++- .../draft/model/message/view/MessageFastView.java | 3 +- .../model/message/view/MessageFastViewFactory.java | 151 +++++++++++++ .../model/message/view/MessageFullViewFactory.java | 52 ++--- .../message/view/MessageHeaderViewFactory.java | 43 +--- .../model/message/view/MessageViewFactory.java | 69 +++--- .../jmap/draft/methods/MessageSenderTest.java | 3 +- .../message/view/MessageFastViewFactoryTest.java | 236 +++++++++++++++++++++ 11 files changed, 558 insertions(+), 135 deletions(-) diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/projections/MessageFastViewProjection.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/projections/MessageFastViewProjection.java index 9dc4bad..a93f582 100644 --- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/projections/MessageFastViewProjection.java +++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/projections/MessageFastViewProjection.java @@ -19,9 +19,19 @@ package org.apache.james.jmap.api.projections; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.tuple.Pair; import org.apache.james.mailbox.model.MessageId; import org.reactivestreams.Publisher; +import com.google.common.base.Preconditions; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; + public interface MessageFastViewProjection { Publisher<Void> store(MessageId messageId, MessageFastViewPrecomputedProperties preview); @@ -29,4 +39,14 @@ public interface MessageFastViewProjection { Publisher<MessageFastViewPrecomputedProperties> retrieve(MessageId messageId); Publisher<Void> delete(MessageId messageId); + + default Publisher<Map<MessageId, MessageFastViewPrecomputedProperties>> retrieve(List<MessageId> messageIds) { + Preconditions.checkNotNull(messageIds); + + return Flux.fromIterable(messageIds) + .flatMap(messageId -> Mono.from(this.retrieve(messageId)) + .map(preview -> Pair.of(messageId, preview))) + .collectMap(Pair::getLeft, Pair::getRight) + .subscribeOn(Schedulers.boundedElastic()); + } } diff --git a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/projections/MessageFastViewProjectionContract.java b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/projections/MessageFastViewProjectionContract.java index d2dfdfe..7c11d05 100644 --- a/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/projections/MessageFastViewProjectionContract.java +++ b/server/data/data-jmap/src/test/java/org/apache/james/jmap/api/projections/MessageFastViewProjectionContract.java @@ -28,11 +28,16 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.stream.IntStream; import org.apache.james.jmap.api.model.Preview; +import java.util.List; + import org.apache.james.mailbox.model.MessageId; import org.apache.james.util.concurrency.ConcurrentTestRunner; import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.Test; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + import reactor.core.publisher.Mono; public interface MessageFastViewProjectionContract { @@ -54,7 +59,7 @@ public interface MessageFastViewProjectionContract { @Test default void retrieveShouldThrowWhenNullMessageId() { - assertThatThrownBy(() -> Mono.from(testee().retrieve(null)).block()) + assertThatThrownBy(() -> Mono.from(testee().retrieve((MessageId) null)).block()) .isInstanceOf(NullPointerException.class); } @@ -97,6 +102,66 @@ public interface MessageFastViewProjectionContract { } @Test + default void retrieveShouldThrowWhenNullMessageIds() { + assertThatThrownBy(() -> Mono.from(testee().retrieve((List<MessageId>) null)).block()) + .isInstanceOf(NullPointerException.class); + } + + @Test + default void retrieveShouldReturnEmptyWhenEmptyMessageIds() { + assertThat(Mono.from(testee().retrieve(ImmutableList.of())).block()) + .isEmpty(); + } + + @Test + default void retrieveShouldReturnAMapContainingStoredPreviews() { + MessageId messageId1 = newMessageId(); + MessageId messageId2 = newMessageId(); + Mono.from(testee().store(messageId1, MESSAGE_FAST_VIEW_PRECOMPUTED_PROPERTIES_1)) + .block(); + Mono.from(testee().store(messageId2, MESSAGE_FAST_VIEW_PRECOMPUTED_PROPERTIES_2)) + .block(); + + assertThat(Mono.from(testee().retrieve(ImmutableList.of(messageId1, messageId2))).block()) + .isEqualTo(ImmutableMap.builder() + .put(messageId1, MESSAGE_FAST_VIEW_PRECOMPUTED_PROPERTIES_1) + .put(messageId2, MESSAGE_FAST_VIEW_PRECOMPUTED_PROPERTIES_2) + .build()); + } + + @Test + default void retrieveShouldReturnOnlyPreviewsAvailableInTheStore() { + MessageId messageId1 = newMessageId(); + MessageId messageId2 = newMessageId(); + MessageId messageId3 = newMessageId(); + Mono.from(testee().store(messageId1, MESSAGE_FAST_VIEW_PRECOMPUTED_PROPERTIES_1)) + .block(); + Mono.from(testee().store(messageId2, MESSAGE_FAST_VIEW_PRECOMPUTED_PROPERTIES_2)) + .block(); + + assertThat(Mono.from(testee().retrieve(ImmutableList.of(messageId1, messageId2, messageId3))).block()) + .isEqualTo(ImmutableMap.builder() + .put(messageId1, MESSAGE_FAST_VIEW_PRECOMPUTED_PROPERTIES_1) + .put(messageId2, MESSAGE_FAST_VIEW_PRECOMPUTED_PROPERTIES_2) + .build()); + } + + @Test + default void retrieveShouldReturnOnlyPreviewsByAskedMessageIds() { + MessageId messageId1 = newMessageId(); + MessageId messageId2 = newMessageId(); + Mono.from(testee().store(messageId1, MESSAGE_FAST_VIEW_PRECOMPUTED_PROPERTIES_1)) + .block(); + Mono.from(testee().store(messageId2, MESSAGE_FAST_VIEW_PRECOMPUTED_PROPERTIES_2)) + .block(); + + assertThat(Mono.from(testee().retrieve(ImmutableList.of(messageId1))).block()) + .isEqualTo(ImmutableMap.builder() + .put(messageId1, MESSAGE_FAST_VIEW_PRECOMPUTED_PROPERTIES_1) + .build()); + } + + @Test default void storeShouldThrowWhenNullMessageId() { assertThatThrownBy(() -> Mono.from(testee().store(null, MESSAGE_FAST_VIEW_PRECOMPUTED_PROPERTIES_1)).block()) .isInstanceOf(NullPointerException.class); diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessor.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessor.java index aa99deb..b51c831 100644 --- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessor.java +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessor.java @@ -21,6 +21,7 @@ package org.apache.james.jmap.draft.methods; import static org.apache.james.jmap.draft.methods.Method.JMAP_PREFIX; +import java.io.IOException; import java.util.List; import java.util.Optional; import java.util.Set; @@ -195,7 +196,7 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor { .description(e.getMessage()) .build()); - } catch (MailboxException | MessagingException e) { + } catch (MailboxException | MessagingException | IOException e) { LOG.error("Unexpected error while creating message", e); responseBuilder.notCreated(create.getCreationId(), SetError.builder() @@ -213,7 +214,9 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor { .collect(Guavate.toImmutableList()); } - private void performCreate(CreationMessageEntry entry, Builder responseBuilder, MailboxSession session) throws MailboxException, InvalidMailboxForCreationException, MessagingException, AttachmentsNotFoundException { + private void performCreate(CreationMessageEntry entry, Builder responseBuilder, MailboxSession session) + throws MailboxException, InvalidMailboxForCreationException, MessagingException, AttachmentsNotFoundException, IOException { + if (isAppendToMailboxWithRole(Role.OUTBOX, entry.getValue(), session)) { sendMailViaOutbox(entry, responseBuilder, session); } else if (entry.getValue().isDraft()) { @@ -239,13 +242,17 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor { } } - private void sendMailViaOutbox(CreationMessageEntry entry, Builder responseBuilder, MailboxSession session) throws AttachmentsNotFoundException, MailboxException, MessagingException { + private void sendMailViaOutbox(CreationMessageEntry entry, Builder responseBuilder, MailboxSession session) + throws AttachmentsNotFoundException, MailboxException, MessagingException, IOException { + validateArguments(entry, session); MessageWithId created = handleOutboxMessages(entry, session); responseBuilder.created(created.getCreationId(), created.getValue()); } - private void saveDraft(CreationMessageEntry entry, Builder responseBuilder, MailboxSession session) throws AttachmentsNotFoundException, MailboxException, MessagingException { + private void saveDraft(CreationMessageEntry entry, Builder responseBuilder, MailboxSession session) + throws AttachmentsNotFoundException, MailboxException, MessagingException, IOException { + attachmentChecker.assertAttachmentsExist(entry, session); MessageWithId created = handleDraftMessages(entry, session); responseBuilder.created(created.getCreationId(), created.getValue()); @@ -273,7 +280,7 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor { .allMatch(path -> path.belongsTo(session)); } - private MessageWithId handleOutboxMessages(CreationMessageEntry entry, MailboxSession session) throws MailboxException, MessagingException { + private MessageWithId handleOutboxMessages(CreationMessageEntry entry, MailboxSession session) throws MailboxException, MessagingException, IOException { assertUserIsSender(session, entry.getValue().getFrom()); MetaDataWithContent newMessage = messageAppender.appendMessageInMailboxes(entry, toMailboxIds(entry), session); MessageFullView jmapMessage = messageFullViewFactory.fromMetaDataWithContent(newMessage); @@ -292,7 +299,7 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor { } } - private MessageWithId handleDraftMessages(CreationMessageEntry entry, MailboxSession session) throws MailboxException, MessagingException { + private MessageWithId handleDraftMessages(CreationMessageEntry entry, MailboxSession session) throws MailboxException, MessagingException, IOException { MetaDataWithContent newMessage = messageAppender.appendMessageInMailboxes(entry, toMailboxIds(entry), session); MessageFullView jmapMessage = messageFullViewFactory.fromMetaDataWithContent(newMessage); return new ValueWithId.MessageWithId(entry.getCreationId(), jmapMessage); diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/Emailer.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/Emailer.java index 9060c76..819831c 100644 --- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/Emailer.java +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/Emailer.java @@ -48,22 +48,20 @@ public class Emailer { private static final Logger LOGGER = LoggerFactory.getLogger(Emailer.class); public static List<Emailer> fromAddressList(AddressList list) { - if (list == null) { - return ImmutableList.of(); - } - return list.flatten() - .stream() - .map(Emailer::fromMailbox) - .collect(Guavate.toImmutableList()); + return Optional.ofNullable(list) + .map(addresses -> addresses.flatten() + .stream() + .map(Emailer::fromMailbox) + .collect(Guavate.toImmutableList())) + .orElse(ImmutableList.of()); } public static Emailer firstFromMailboxList(MailboxList list) { - if (list == null) { - return null; - } - return list.stream() - .map(Emailer::fromMailbox) - .findFirst() + return Optional.ofNullable(list) + .map(mailboxes -> mailboxes.stream() + .map(Emailer::fromMailbox) + .findFirst() + .orElse(null)) .orElse(null); } @@ -76,10 +74,8 @@ public class Emailer { } private static String getNameOrAddress(Mailbox mailbox) { - if (mailbox.getName() != null) { - return mailbox.getName(); - } - return mailbox.getAddress(); + return Optional.ofNullable(mailbox.getName()) + .orElseGet(mailbox::getAddress); } diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageFastView.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageFastView.java index dfa6ce6..3143ba1 100644 --- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageFastView.java +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageFastView.java @@ -57,6 +57,7 @@ public class MessageFastView extends MessageHeaderView { protected Builder() { super(); + this.preview = Optional.empty(); } public S preview(Preview preview) { @@ -77,13 +78,13 @@ public class MessageFastView extends MessageHeaderView { keywords.orElse(Keywords.DEFAULT_VALUE)); } + @Override public void checkState() { super.checkState(); Preconditions.checkState(preview != null, "'preview' is mandatory"); } } - private final PreviewDTO preview; @VisibleForTesting diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageFastViewFactory.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageFastViewFactory.java new file mode 100644 index 0000000..edc43b7 --- /dev/null +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageFastViewFactory.java @@ -0,0 +1,151 @@ +/**************************************************************** + * 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.jmap.draft.model.message.view; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; + +import org.apache.james.jmap.api.projections.MessageFastViewPrecomputedProperties; +import org.apache.james.jmap.api.projections.MessageFastViewProjection; +import org.apache.james.jmap.draft.model.BlobId; +import org.apache.james.jmap.draft.model.Emailer; +import org.apache.james.mailbox.BlobManager; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.MessageIdManager; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.FetchGroup; +import org.apache.james.mailbox.model.MailboxId; +import org.apache.james.mailbox.model.MessageId; +import org.apache.james.mailbox.model.MessageResult; +import org.apache.james.mime4j.dom.Message; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.github.steveash.guavate.Guavate; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; + +public class MessageFastViewFactory implements MessageViewFactory<MessageFastView> { + + private static class FromMessageResultAndPreview implements Helpers.FromMessageResult<MessageFastView> { + + private final BlobManager blobManager; + private final Map<MessageId, MessageFastViewPrecomputedProperties> fastProjections; + + private FromMessageResultAndPreview(BlobManager blobManager, + Map<MessageId, MessageFastViewPrecomputedProperties> fastProjections) { + this.blobManager = blobManager; + this.fastProjections = fastProjections; + } + + @Override + public MessageFastView fromMessageResults(Collection<MessageResult> messageResults) throws MailboxException, IOException { + Helpers.assertOneMessageId(messageResults); + MessageResult firstMessageResult = messageResults.iterator().next(); + Preconditions.checkArgument(fastProjections.containsKey(firstMessageResult.getMessageId()), + "FromMessageResultAndPreview usage requires a precomputed preview"); + + List<MailboxId> mailboxIds = Helpers.getMailboxIds(messageResults); + + Message mimeMessage = Helpers.parse(firstMessageResult.getFullContent().getInputStream()); + + return MessageFastView.builder() + .id(firstMessageResult.getMessageId()) + .mailboxIds(mailboxIds) + .blobId(BlobId.of(blobManager.toBlobId(firstMessageResult.getMessageId()))) + .threadId(firstMessageResult.getMessageId().serialize()) + .keywords(Helpers.getKeywords(messageResults)) + .size(firstMessageResult.getSize()) + .inReplyToMessageId(Helpers.getHeaderValue(mimeMessage, "in-reply-to")) + .subject(Strings.nullToEmpty(mimeMessage.getSubject()).trim()) + .headers(Helpers.toHeaderMap(mimeMessage.getHeader().getFields())) + .from(Emailer.firstFromMailboxList(mimeMessage.getFrom())) + .to(Emailer.fromAddressList(mimeMessage.getTo())) + .cc(Emailer.fromAddressList(mimeMessage.getCc())) + .bcc(Emailer.fromAddressList(mimeMessage.getBcc())) + .replyTo(Emailer.fromAddressList(mimeMessage.getReplyTo())) + .date(Helpers.getDateFromHeaderOrInternalDateOtherwise(mimeMessage, firstMessageResult)) + .preview(fastProjections.get(firstMessageResult.getMessageId()).getPreview()) + .build(); + } + } + + private static final Logger LOGGER = LoggerFactory.getLogger(MessageFastViewFactory.class); + + private final BlobManager blobManager; + private final MessageIdManager messageIdManager; + private final MessageFastViewProjection fastViewProjection; + private final MessageFullViewFactory messageFullViewFactory; + + @Inject + @VisibleForTesting + MessageFastViewFactory(BlobManager blobManager, MessageIdManager messageIdManager, MessageFastViewProjection fastViewProjection, + MessageFullViewFactory messageFullViewFactory) { + this.blobManager = blobManager; + this.messageIdManager = messageIdManager; + this.fastViewProjection = fastViewProjection; + this.messageFullViewFactory = messageFullViewFactory; + } + + @Override + public List<MessageFastView> fromMessageIds(List<MessageId> messageIds, MailboxSession mailboxSession) { + return Mono.from(fastViewProjection.retrieve(messageIds)) + .flatMapMany(fastProjections -> gatherMessageViews(messageIds, mailboxSession, fastProjections)) + .collectList() + .subscribeOn(Schedulers.boundedElastic()) + .block(); + } + + private Flux<MessageFastView> gatherMessageViews(List<MessageId> messageIds, MailboxSession mailboxSession, + Map<MessageId, MessageFastViewPrecomputedProperties> fastProjections) { + return Flux.merge( + fetch(ImmutableList.copyOf(fastProjections.keySet()), FetchGroup.HEADERS, mailboxSession) + .map(messageResults -> Helpers.toMessageViews(messageResults, new FromMessageResultAndPreview(blobManager, fastProjections))), + fetch(withoutPreviews(messageIds, fastProjections), FetchGroup.FULL_CONTENT, mailboxSession) + .map(messageResults -> Helpers.toMessageViews(messageResults, messageFullViewFactory::fromMessageResults))) + .flatMap(Flux::fromIterable); + } + + private List<MessageId> withoutPreviews(List<MessageId> messageIds, Map<MessageId, MessageFastViewPrecomputedProperties> fastProjections) { + return ImmutableList.copyOf(Sets.difference( + ImmutableSet.copyOf(messageIds), + fastProjections.keySet())); + } + + private Mono<List<MessageResult>> fetch(List<MessageId> messageIds, FetchGroup fetchGroup, MailboxSession mailboxSession) { + return Mono.fromCallable(() -> messageIdManager.getMessages(messageIds, fetchGroup, mailboxSession)) + .onErrorResume(MailboxException.class, ex -> { + LOGGER.error("cannot read messages {}", messageIds, ex); + return Mono.just(ImmutableList.of()); + }); + } +} diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageFullViewFactory.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageFullViewFactory.java index b5b20c1..7cbd989 100644 --- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageFullViewFactory.java +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageFullViewFactory.java @@ -33,6 +33,7 @@ import javax.mail.internet.SharedInputStream; import org.apache.james.jmap.api.model.Preview; import org.apache.james.jmap.draft.model.Attachment; import org.apache.james.jmap.draft.model.BlobId; +import org.apache.james.jmap.draft.model.Emailer; import org.apache.james.jmap.draft.model.Keywords; import org.apache.james.jmap.draft.model.MessageProperties; import org.apache.james.jmap.draft.utils.HtmlTextExtractor; @@ -47,7 +48,6 @@ import org.apache.james.mailbox.model.MessageAttachment; import org.apache.james.mailbox.model.MessageId; import org.apache.james.mailbox.model.MessageResult; import org.apache.james.mime4j.dom.Message; -import org.apache.james.mime4j.stream.MimeConfig; import org.apache.james.util.mime.MessageContentExtractor; import org.apache.james.util.mime.MessageContentExtractor.MessageContent; @@ -77,8 +77,8 @@ public class MessageFullViewFactory implements MessageViewFactory<MessageFullVie return Helpers.toMessageViews(messages, this::fromMessageResults); } - public MessageFullView fromMetaDataWithContent(MetaDataWithContent message) throws MailboxException { - Message mimeMessage = parse(message); + public MessageFullView fromMetaDataWithContent(MetaDataWithContent message) throws MailboxException, IOException { + Message mimeMessage = Helpers.parse(message.getContent()); MessageContent messageContent = extractContent(mimeMessage); Optional<String> htmlBody = messageContent.getHtmlBody(); Optional<String> mainTextContent = mainTextContent(messageContent); @@ -91,15 +91,15 @@ public class MessageFullViewFactory implements MessageViewFactory<MessageFullVie .blobId(BlobId.of(blobManager.toBlobId(message.getMessageId()))) .threadId(message.getMessageId().serialize()) .mailboxIds(message.getMailboxIds()) - .inReplyToMessageId(Helpers.getHeader(mimeMessage, "in-reply-to")) + .inReplyToMessageId(Helpers.getHeaderValue(mimeMessage, "in-reply-to")) .keywords(message.getKeywords()) .subject(Strings.nullToEmpty(mimeMessage.getSubject()).trim()) - .headers(Helpers.toMap(mimeMessage.getHeader().getFields())) - .from(Helpers.firstFromMailboxList(mimeMessage.getFrom())) - .to(Helpers.fromAddressList(mimeMessage.getTo())) - .cc(Helpers.fromAddressList(mimeMessage.getCc())) - .bcc(Helpers.fromAddressList(mimeMessage.getBcc())) - .replyTo(Helpers.fromAddressList(mimeMessage.getReplyTo())) + .headers(Helpers.toHeaderMap(mimeMessage.getHeader().getFields())) + .from(Emailer.firstFromMailboxList(mimeMessage.getFrom())) + .to(Emailer.fromAddressList(mimeMessage.getTo())) + .cc(Emailer.fromAddressList(mimeMessage.getCc())) + .bcc(Emailer.fromAddressList(mimeMessage.getBcc())) + .replyTo(Emailer.fromAddressList(mimeMessage.getReplyTo())) .size(message.getSize()) .date(getDateFromHeaderOrInternalDateOtherwise(mimeMessage, message)) .textBody(textBody) @@ -109,11 +109,17 @@ public class MessageFullViewFactory implements MessageViewFactory<MessageFullVie .build(); } - private MessageFullView fromMessageResults(Collection<MessageResult> messageResults) throws MailboxException { + private Instant getDateFromHeaderOrInternalDateOtherwise(Message mimeMessage, MessageFullViewFactory.MetaDataWithContent message) { + return Optional.ofNullable(mimeMessage.getDate()) + .map(Date::toInstant) + .orElse(message.getInternalDate()); + } + + MessageFullView fromMessageResults(Collection<MessageResult> messageResults) throws MailboxException, IOException { return fromMetaDataWithContent(toMetaDataWithContent(messageResults)); } - private MetaDataWithContent toMetaDataWithContent(Collection<MessageResult> messageResults) throws MailboxException { + MetaDataWithContent toMetaDataWithContent(Collection<MessageResult> messageResults) throws MailboxException { Helpers.assertOneMessageId(messageResults); MessageResult firstMessageResult = messageResults.iterator().next(); @@ -127,19 +133,13 @@ public class MessageFullViewFactory implements MessageViewFactory<MessageFullVie .build(); } - private Instant getDateFromHeaderOrInternalDateOtherwise(Message mimeMessage, MetaDataWithContent message) { - return Optional.ofNullable(mimeMessage.getDate()) - .map(Date::toInstant) - .orElse(message.getInternalDate()); - } - private Optional<String> computeTextBodyIfNeeded(MessageContent messageContent, Optional<String> mainTextContent) { return messageContent.getTextBody() .map(Optional::of) .orElse(mainTextContent); } - private Optional<String> mainTextContent(MessageContent messageContent) { + Optional<String> mainTextContent(MessageContent messageContent) { return messageContent.getHtmlBody() .map(htmlTextExtractor::toPlainText) .filter(s -> !Strings.isNullOrEmpty(s)) @@ -147,19 +147,7 @@ public class MessageFullViewFactory implements MessageViewFactory<MessageFullVie .orElse(messageContent.getTextBody()); } - private Message parse(MetaDataWithContent message) throws MailboxException { - try { - return Message.Builder - .of() - .use(MimeConfig.PERMISSIVE) - .parse(message.getContent()) - .build(); - } catch (IOException e) { - throw new MailboxException("Unable to parse message: " + e.getMessage(), e); - } - } - - private MessageContent extractContent(Message mimeMessage) throws MailboxException { + MessageContent extractContent(Message mimeMessage) throws MailboxException { try { return messageContentExtractor.extract(mimeMessage); } catch (IOException e) { diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageHeaderViewFactory.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageHeaderViewFactory.java index e840d2d..b519b87 100644 --- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageHeaderViewFactory.java +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageHeaderViewFactory.java @@ -20,15 +20,13 @@ package org.apache.james.jmap.draft.model.message.view; import java.io.IOException; -import java.time.Instant; import java.util.Collection; -import java.util.Date; import java.util.List; -import java.util.Optional; import javax.inject.Inject; import org.apache.james.jmap.draft.model.BlobId; +import org.apache.james.jmap.draft.model.Emailer; import org.apache.james.jmap.draft.model.MessageProperties; import org.apache.james.mailbox.BlobManager; import org.apache.james.mailbox.MailboxSession; @@ -38,7 +36,6 @@ import org.apache.james.mailbox.model.MailboxId; import org.apache.james.mailbox.model.MessageId; import org.apache.james.mailbox.model.MessageResult; import org.apache.james.mime4j.dom.Message; -import org.apache.james.mime4j.stream.MimeConfig; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; @@ -60,13 +57,13 @@ public class MessageHeaderViewFactory implements MessageViewFactory<MessageHeade return Helpers.toMessageViews(messages, this::fromMessageResults); } - private MessageHeaderView fromMessageResults(Collection<MessageResult> messageResults) throws MailboxException { + private MessageHeaderView fromMessageResults(Collection<MessageResult> messageResults) throws MailboxException, IOException { Helpers.assertOneMessageId(messageResults); MessageResult firstMessageResult = messageResults.iterator().next(); List<MailboxId> mailboxIds = Helpers.getMailboxIds(messageResults); - Message mimeMessage = parse(firstMessageResult); + Message mimeMessage = Helpers.parse(firstMessageResult.getFullContent().getInputStream()); return MessageHeaderView.messageHeaderBuilder() .id(firstMessageResult.getMessageId()) @@ -75,33 +72,15 @@ public class MessageHeaderViewFactory implements MessageViewFactory<MessageHeade .threadId(firstMessageResult.getMessageId().serialize()) .keywords(Helpers.getKeywords(messageResults)) .size(firstMessageResult.getSize()) - .inReplyToMessageId(Helpers.getHeader(mimeMessage, "in-reply-to")) + .inReplyToMessageId(Helpers.getHeaderValue(mimeMessage, "in-reply-to")) .subject(Strings.nullToEmpty(mimeMessage.getSubject()).trim()) - .headers(Helpers.toMap(mimeMessage.getHeader().getFields())) - .from(Helpers.firstFromMailboxList(mimeMessage.getFrom())) - .to(Helpers.fromAddressList(mimeMessage.getTo())) - .cc(Helpers.fromAddressList(mimeMessage.getCc())) - .bcc(Helpers.fromAddressList(mimeMessage.getBcc())) - .replyTo(Helpers.fromAddressList(mimeMessage.getReplyTo())) - .date(getDateFromHeaderOrInternalDateOtherwise(mimeMessage, firstMessageResult)) + .headers(Helpers.toHeaderMap(mimeMessage.getHeader().getFields())) + .from(Emailer.firstFromMailboxList(mimeMessage.getFrom())) + .to(Emailer.fromAddressList(mimeMessage.getTo())) + .cc(Emailer.fromAddressList(mimeMessage.getCc())) + .bcc(Emailer.fromAddressList(mimeMessage.getBcc())) + .replyTo(Emailer.fromAddressList(mimeMessage.getReplyTo())) + .date(Helpers.getDateFromHeaderOrInternalDateOtherwise(mimeMessage, firstMessageResult)) .build(); } - - private Message parse(MessageResult message) throws MailboxException { - try { - return Message.Builder - .of() - .use(MimeConfig.PERMISSIVE) - .parse(message.getFullContent().getInputStream()) - .build(); - } catch (IOException e) { - throw new MailboxException("Unable to parse message: " + e.getMessage(), e); - } - } - - private Instant getDateFromHeaderOrInternalDateOtherwise(Message mimeMessage, MessageResult message) { - return Optional.ofNullable(mimeMessage.getDate()) - .map(Date::toInstant) - .orElse(message.getInternalDate().toInstant()); - } } diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageViewFactory.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageViewFactory.java index 1a08705..0025f19 100644 --- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageViewFactory.java +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/model/message/view/MessageViewFactory.java @@ -19,14 +19,18 @@ package org.apache.james.jmap.draft.model.message.view; +import java.io.IOException; +import java.io.InputStream; +import java.time.Instant; import java.util.Collection; +import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.apache.james.jmap.draft.model.Emailer; import org.apache.james.jmap.draft.model.Keywords; import org.apache.james.jmap.draft.utils.KeywordsCombiner; import org.apache.james.mailbox.MailboxSession; @@ -34,17 +38,15 @@ import org.apache.james.mailbox.exception.MailboxException; import org.apache.james.mailbox.model.MailboxId; import org.apache.james.mailbox.model.MessageId; import org.apache.james.mailbox.model.MessageResult; -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.dom.Message; import org.apache.james.mime4j.stream.Field; +import org.apache.james.mime4j.stream.MimeConfig; import org.apache.james.mime4j.util.MimeUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.github.steveash.guavate.Guavate; import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Multimaps; @@ -60,7 +62,7 @@ public interface MessageViewFactory<T extends MessageView> { class Helpers { interface FromMessageResult<T extends MessageView> { - T fromMessageResults(Collection<MessageResult> messageResults) throws MailboxException; + T fromMessageResults(Collection<MessageResult> messageResults) throws MailboxException, IOException; } static void assertOneMessageId(Collection<MessageResult> messageResults) { @@ -68,7 +70,7 @@ public interface MessageViewFactory<T extends MessageView> { Preconditions.checkArgument(hasOnlyOneMessageId(messageResults), "MessageResults need to share the same messageId"); } - static boolean hasOnlyOneMessageId(Collection<MessageResult> messageResults) { + private static boolean hasOnlyOneMessageId(Collection<MessageResult> messageResults) { return messageResults .stream() .map(MessageResult::getMessageId) @@ -91,7 +93,7 @@ public interface MessageViewFactory<T extends MessageView> { .get(); } - static String getHeader(org.apache.james.mime4j.dom.Message message, String header) { + static String getHeaderValue(org.apache.james.mime4j.dom.Message message, String header) { Field field = message.getHeader().getField(header); if (field == null) { return null; @@ -99,7 +101,7 @@ public interface MessageViewFactory<T extends MessageView> { return field.getBody(); } - static ImmutableMap<String, String> toMap(List<Field> fields) { + static ImmutableMap<String, String> toHeaderMap(List<Field> fields) { Function<Map.Entry<String, Collection<Field>>, String> bodyConcatenator = fieldListEntry -> fieldListEntry.getValue() .stream() .map(Field::getBody) @@ -115,41 +117,6 @@ public interface MessageViewFactory<T extends MessageView> { .collect(Guavate.toImmutableMap(Map.Entry::getKey, bodyConcatenator)); } - static Emailer firstFromMailboxList(MailboxList list) { - if (list == null) { - return null; - } - return list.stream() - .map(Helpers::fromMailbox) - .findFirst() - .orElse(null); - } - - static Emailer fromMailbox(Mailbox mailbox) { - return Emailer.builder() - .name(getNameOrAddress(mailbox)) - .email(mailbox.getAddress()) - .allowInvalid() - .build(); - } - - static String getNameOrAddress(Mailbox mailbox) { - if (mailbox.getName() != null) { - return mailbox.getName(); - } - return mailbox.getAddress(); - } - - static ImmutableList<Emailer> fromAddressList(AddressList list) { - if (list == null) { - return ImmutableList.of(); - } - return list.flatten() - .stream() - .map(Helpers::fromMailbox) - .collect(Guavate.toImmutableList()); - } - static <T extends MessageView> Function<Collection<MessageResult>, Stream<T>> toMessageViews(FromMessageResult<T> converter) { return messageResults -> { try { @@ -171,5 +138,19 @@ public interface MessageViewFactory<T extends MessageView> { .flatMap(toMessageViews(converter)) .collect(Guavate.toImmutableList()); } + + static Instant getDateFromHeaderOrInternalDateOtherwise(Message mimeMessage, MessageResult message) { + return Optional.ofNullable(mimeMessage.getDate()) + .map(Date::toInstant) + .orElse(message.getInternalDate().toInstant()); + } + + static Message parse(InputStream messageContent) throws IOException { + return Message.Builder + .of() + .use(MimeConfig.PERMISSIVE) + .parse(messageContent) + .build(); + } } } diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/MessageSenderTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/MessageSenderTest.java index 3791de6..f8f768c 100644 --- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/MessageSenderTest.java +++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/MessageSenderTest.java @@ -43,7 +43,6 @@ import org.apache.james.jmap.draft.utils.HtmlTextExtractor; import org.apache.james.mailbox.BlobManager; import org.apache.james.mailbox.MessageIdManager; import org.apache.james.mailbox.MessageUid; -import org.apache.james.mailbox.exception.MailboxException; import org.apache.james.mailbox.inmemory.InMemoryId; import org.apache.james.mailbox.model.BlobId; import org.apache.james.mailbox.model.MessageId; @@ -64,7 +63,7 @@ class MessageSenderTest { private MessageFullView jmapMessage; @BeforeEach - void setup() throws MailboxException { + void setup() throws Exception { String headers = "From: [email protected]\n" + "To: [email protected]\n" + "Cc: [email protected], [email protected]\n" diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/model/message/view/MessageFastViewFactoryTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/model/message/view/MessageFastViewFactoryTest.java new file mode 100644 index 0000000..044bcaf --- /dev/null +++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/model/message/view/MessageFastViewFactoryTest.java @@ -0,0 +1,236 @@ +/**************************************************************** + * 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.jmap.draft.model.message.view; + +import static org.apache.james.jmap.draft.model.message.view.MessageViewFixture.ALICE_EMAIL; +import static org.apache.james.jmap.draft.model.message.view.MessageViewFixture.BOB; +import static org.apache.james.jmap.draft.model.message.view.MessageViewFixture.BOB_EMAIL; +import static org.apache.james.jmap.draft.model.message.view.MessageViewFixture.HEADERS_MAP; +import static org.apache.james.jmap.draft.model.message.view.MessageViewFixture.JACK_EMAIL; +import static org.apache.james.jmap.draft.model.message.view.MessageViewFixture.JACOB_EMAIL; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.spy; + +import java.util.List; +import java.util.Optional; + +import javax.mail.Flags; + +import org.apache.james.jmap.api.model.Preview; +import org.apache.james.jmap.api.projections.MessageFastViewPrecomputedProperties; +import org.apache.james.jmap.draft.model.BlobId; +import org.apache.james.jmap.draft.model.Keyword; +import org.apache.james.jmap.draft.model.Keywords; +import org.apache.james.jmap.draft.model.Number; +import org.apache.james.jmap.draft.model.PreviewDTO; +import org.apache.james.jmap.draft.utils.HtmlTextExtractor; +import org.apache.james.jmap.draft.utils.JsoupHtmlTextExtractor; +import org.apache.james.jmap.memory.projections.MemoryMessageFastViewProjection; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.MessageIdManager; +import org.apache.james.mailbox.MessageManager; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.inmemory.InMemoryMailboxManager; +import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources; +import org.apache.james.mailbox.model.ComposedMessageId; +import org.apache.james.mailbox.model.MailboxId; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.store.StoreBlobManager; +import org.apache.james.util.ClassLoaderUtils; +import org.apache.james.util.mime.MessageContentExtractor; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.google.common.collect.ImmutableList; + +import reactor.core.publisher.Mono; + +class MessageFastViewFactoryTest { + + private static final String PREVIEW_1_STRING = "preview 1"; + private static final Preview PREVIEW_1 = Preview.from(PREVIEW_1_STRING); + private static final String PREVIEW_3_STRING = "preview 3"; + private static final Preview PREVIEW_3 = Preview.from(PREVIEW_3_STRING); + private static final String PREVIEW_4_STRING = "preview 4"; + private static final Preview PREVIEW_4 = Preview.from(PREVIEW_4_STRING); + private static final String DEFAULT_PREVIEW_STRING = "blabla bloblo"; + private static final MessageFastViewPrecomputedProperties PROJECTION_1 = MessageFastViewPrecomputedProperties + .builder() + .preview(PREVIEW_1) + .hasAttachment() + .build(); + private static final MessageFastViewPrecomputedProperties PROJECTION_3 = MessageFastViewPrecomputedProperties + .builder() + .preview(PREVIEW_3) + .noAttachments() + .build(); + private static final MessageFastViewPrecomputedProperties PROJECTION_4 = MessageFastViewPrecomputedProperties + .builder() + .preview(PREVIEW_4) + .noAttachments() + .build(); + + private MessageIdManager messageIdManager; + private MailboxSession session; + private MessageManager bobInbox; + private ComposedMessageId previewComputedMessage1; + private ComposedMessageId missingPreviewComputedMessage1; + private ComposedMessageId previewComputedMessage2; + private ComposedMessageId previewComputedMessage3; + private MessageFastViewFactory messageFastViewFactory; + private MemoryMessageFastViewProjection fastViewProjection; + private StoreBlobManager blobManager; + private MessageFullViewFactory messageFullViewFactory; + + @BeforeEach + void setUp() throws Exception { + HtmlTextExtractor htmlTextExtractor = new JsoupHtmlTextExtractor(); + + MessageContentExtractor messageContentExtractor = new MessageContentExtractor(); + + InMemoryIntegrationResources resources = InMemoryIntegrationResources.defaultResources(); + messageIdManager = spy(resources.getMessageIdManager()); + InMemoryMailboxManager mailboxManager = resources.getMailboxManager(); + + session = mailboxManager.createSystemSession(BOB); + MailboxId bobInboxId = mailboxManager.createMailbox(MailboxPath.inbox(session), session).get(); + + bobInbox = mailboxManager.getMailbox(bobInboxId, session); + + previewComputedMessage1 = bobInbox.appendMessage(MessageManager.AppendCommand.builder() + .withFlags(new Flags(Flags.Flag.SEEN)) + .build(ClassLoaderUtils.getSystemResourceAsSharedStream("fullMessage.eml")), + session); + missingPreviewComputedMessage1 = bobInbox.appendMessage(MessageManager.AppendCommand.builder() + .withFlags(new Flags(Flags.Flag.SEEN)) + .build(ClassLoaderUtils.getSystemResourceAsSharedStream("fullMessage.eml")), + session); + previewComputedMessage2 = bobInbox.appendMessage(MessageManager.AppendCommand.builder() + .withFlags(new Flags(Flags.Flag.SEEN)) + .build(ClassLoaderUtils.getSystemResourceAsSharedStream("fullMessage.eml")), + session); + previewComputedMessage3 = bobInbox.appendMessage(MessageManager.AppendCommand.builder() + .withFlags(new Flags(Flags.Flag.SEEN)) + .build(ClassLoaderUtils.getSystemResourceAsSharedStream("fullMessage.eml")), + session); + + fastViewProjection = new MemoryMessageFastViewProjection(); + + Mono.from(fastViewProjection.store(previewComputedMessage1.getMessageId(), PROJECTION_1)) + .block(); + Mono.from(fastViewProjection.store(previewComputedMessage2.getMessageId(), PROJECTION_3)) + .block(); + Mono.from(fastViewProjection.store(previewComputedMessage3.getMessageId(), PROJECTION_4)) + .block(); + + blobManager = resources.getBlobManager(); + messageFullViewFactory = new MessageFullViewFactory(blobManager, messageContentExtractor, htmlTextExtractor, messageIdManager); + messageFastViewFactory = new MessageFastViewFactory(blobManager, messageIdManager, fastViewProjection, messageFullViewFactory); + } + + @Test + void fromMessageIdsShouldReturnAMessageWithPreviewInThePreviewStore() throws Exception { + MessageFastView actual = messageFastViewFactory.fromMessageIds(ImmutableList.of(previewComputedMessage1.getMessageId()), session).get(0); + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(actual.getId()).isEqualTo(previewComputedMessage1.getMessageId()); + softly.assertThat(actual.getMailboxIds()).containsExactly(bobInbox.getId()); + softly.assertThat(actual.getThreadId()).isEqualTo(previewComputedMessage1.getMessageId().serialize()); + softly.assertThat(actual.getSize()).isEqualTo(Number.fromLong(2255)); + softly.assertThat(actual.getKeywords()).isEqualTo(Keywords.strictFactory().from(Keyword.SEEN).asMap()); + softly.assertThat(actual.getBlobId()).isEqualTo(BlobId.of(previewComputedMessage1.getMessageId().serialize())); + softly.assertThat(actual.getInReplyToMessageId()).isEqualTo(Optional.of(BOB.asString())); + softly.assertThat(actual.getHeaders()).isEqualTo(HEADERS_MAP); + softly.assertThat(actual.getFrom()).isEqualTo(Optional.of(ALICE_EMAIL)); + softly.assertThat(actual.getTo()).isEqualTo(ImmutableList.of(BOB_EMAIL)); + softly.assertThat(actual.getCc()).isEqualTo(ImmutableList.of(JACK_EMAIL, JACOB_EMAIL)); + softly.assertThat(actual.getBcc()).isEqualTo(ImmutableList.of(ALICE_EMAIL)); + softly.assertThat(actual.getReplyTo()).isEqualTo(ImmutableList.of(ALICE_EMAIL)); + softly.assertThat(actual.getSubject()).isEqualTo("Full message"); + softly.assertThat(actual.getDate()).isEqualTo("2016-06-07T14:23:37Z"); + + softly.assertThat(actual.getPreview()).isEqualTo(PreviewDTO.of(PREVIEW_1_STRING)); + }); + } + + @Test + void fromMessageIdsShouldReturnAMessageWithPreviewComputedFromFullMessageWhenNotInThePreviewStore() throws Exception { + MessageFastView actual = messageFastViewFactory.fromMessageIds(ImmutableList.of(missingPreviewComputedMessage1.getMessageId()), session).get(0); + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(actual.getId()).isEqualTo(missingPreviewComputedMessage1.getMessageId()); + softly.assertThat(actual.getMailboxIds()).containsExactly(bobInbox.getId()); + softly.assertThat(actual.getThreadId()).isEqualTo(missingPreviewComputedMessage1.getMessageId().serialize()); + softly.assertThat(actual.getSize()).isEqualTo(Number.fromLong(2255)); + softly.assertThat(actual.getKeywords()).isEqualTo(Keywords.strictFactory().from(Keyword.SEEN).asMap()); + softly.assertThat(actual.getBlobId()).isEqualTo(BlobId.of(missingPreviewComputedMessage1.getMessageId().serialize())); + softly.assertThat(actual.getInReplyToMessageId()).isEqualTo(Optional.of(BOB.asString())); + softly.assertThat(actual.getHeaders()).isEqualTo(HEADERS_MAP); + softly.assertThat(actual.getFrom()).isEqualTo(Optional.of(ALICE_EMAIL)); + softly.assertThat(actual.getTo()).isEqualTo(ImmutableList.of(BOB_EMAIL)); + softly.assertThat(actual.getCc()).isEqualTo(ImmutableList.of(JACK_EMAIL, JACOB_EMAIL)); + softly.assertThat(actual.getBcc()).isEqualTo(ImmutableList.of(ALICE_EMAIL)); + softly.assertThat(actual.getReplyTo()).isEqualTo(ImmutableList.of(ALICE_EMAIL)); + softly.assertThat(actual.getSubject()).isEqualTo("Full message"); + softly.assertThat(actual.getDate()).isEqualTo("2016-06-07T14:23:37Z"); + + softly.assertThat(actual.getPreview()).isEqualTo(PreviewDTO.of(DEFAULT_PREVIEW_STRING)); + }); + } + + @Test + void fromMessageIdsShouldReturnMessagesWithPreviews() throws Exception { + List<MessageFastView> actual = messageFastViewFactory + .fromMessageIds(ImmutableList.of( + previewComputedMessage2.getMessageId(), + previewComputedMessage3.getMessageId(), + missingPreviewComputedMessage1.getMessageId(), + previewComputedMessage1.getMessageId()), + session); + + assertThat(actual) + .hasSize(4) + .extracting(MessageFastView::getPreview) + .containsOnly( + PreviewDTO.of(PREVIEW_3_STRING), + PreviewDTO.of(PREVIEW_4_STRING), + PreviewDTO.of(DEFAULT_PREVIEW_STRING), + PreviewDTO.of(PREVIEW_1_STRING)); + } + + @Test + void fromMessageIdsShouldKeepProcessingEvenWhenFetchingFail() throws Exception { + doThrow(new MailboxException("mock exception")) + .doCallRealMethod() + .when(messageIdManager).getMessages(any(), any(), any()); + + List<MessageFastView> actual = messageFastViewFactory + .fromMessageIds(ImmutableList.of( + missingPreviewComputedMessage1.getMessageId(), + previewComputedMessage1.getMessageId()), + session); + + assertThat(actual) + .hasSize(1) + .extracting(MessageFastView::getId) + .containsAnyOf(missingPreviewComputedMessage1.getMessageId(), previewComputedMessage1.getMessageId()); + } +} \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
