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/12/06 02:34:15 UTC
[james-project] 05/21: JAMES-2992 MessageFastViewFactory
implementation
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 <dt...@linagora.com>
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: me@example.com\n"
+ "To: 1@example.com\n"
+ "Cc: 2@example.com, 3@example.com\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: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org