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 ro...@apache.org on 2016/08/29 13:28:09 UTC

[12/17] james-project git commit: JAMES-1818 Remove store usage in getMessagesMethod by using managers

JAMES-1818 Remove store usage in getMessagesMethod by using managers


Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/844a7409
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/844a7409
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/844a7409

Branch: refs/heads/master
Commit: 844a7409613678c7b3b34b060923d60cd32b0cee
Parents: 8c4e86d
Author: Raphael Ouazana <ra...@linagora.com>
Authored: Mon Aug 22 17:38:23 2016 +0200
Committer: Raphael Ouazana <ra...@linagora.com>
Committed: Mon Aug 29 15:15:44 2016 +0200

----------------------------------------------------------------------
 .../org/apache/james/jmap/JMAPCommonModule.java |   2 +
 .../integration/SetMessagesMethodTest.java      |   1 -
 .../test/resources/cucumber/GetMessages.feature |  10 +-
 .../james/jmap/methods/GetMessagesMethod.java   | 103 ++++++-----
 .../jmap/model/MessageContentExtractor.java     | 127 ++++++++++++++
 .../apache/james/jmap/model/MessageFactory.java | 127 +++++++++++++-
 .../jmap/methods/GetMessagesMethodTest.java     |  25 +--
 .../SetMessagesCreationProcessorTest.java       |   4 +-
 .../james/jmap/model/MailboxMessageTest.java    |   3 +-
 .../jmap/model/MessageContentExtractorTest.java | 171 +++++++++++++++++++
 .../apache/james/jmap/send/MailFactoryTest.java |   4 +-
 11 files changed, 506 insertions(+), 71 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/844a7409/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPCommonModule.java
----------------------------------------------------------------------
diff --git a/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPCommonModule.java b/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPCommonModule.java
index 77cfe0f..e9d5c6f 100644
--- a/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPCommonModule.java
+++ b/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPCommonModule.java
@@ -30,6 +30,7 @@ import org.apache.james.jmap.crypto.JamesSignatureHandler;
 import org.apache.james.jmap.crypto.SignatureHandler;
 import org.apache.james.jmap.crypto.SignedTokenFactory;
 import org.apache.james.jmap.crypto.SignedTokenManager;
+import org.apache.james.jmap.model.MessageContentExtractor;
 import org.apache.james.jmap.model.MessageFactory;
 import org.apache.james.jmap.model.MessagePreviewGenerator;
 import org.apache.james.jmap.send.MailFactory;
@@ -63,6 +64,7 @@ public class JMAPCommonModule extends AbstractModule {
         bind(AutomaticallySentMailDetectorImpl.class).in(Scopes.SINGLETON);
         bind(MessageFactory.class).in(Scopes.SINGLETON);
         bind(MessagePreviewGenerator.class).in(Scopes.SINGLETON);
+        bind(MessageContentExtractor.class).in(Scopes.SINGLETON);
         bind(HeadersAuthenticationExtractor.class).in(Scopes.SINGLETON);
         bind(StoreAttachmentManager.class).in(Scopes.SINGLETON);
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/844a7409/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java
index c70a7be..7fa3e83 100644
--- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java
+++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java
@@ -2014,7 +2014,6 @@ public abstract class SetMessagesMethodTest {
             .body(firstAttachment + ".size", equalTo((int) attachment.getSize()));
     }
 
-    @Ignore("We should rework org.apache.james.jmap.model.message.MimePart to handle multipart/alternative and multipart/mixed")
     @Test
     public void attachmentsAndBodyShouldBeRetrievedWhenChainingSetMessagesAndGetMessagesWithTextBodyAndHtmlAttachment() throws Exception {
         jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "sent");

http://git-wip-us.apache.org/repos/asf/james-project/blob/844a7409/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/GetMessages.feature
----------------------------------------------------------------------
diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/GetMessages.feature b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/GetMessages.feature
index 0cf3fb7..1a8271b 100644
--- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/GetMessages.feature
+++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/GetMessages.feature
@@ -44,7 +44,7 @@ Feature: GetMessages method
     And the isUnread of the message is "true"
     And the preview of the message is "testmail"
     And the headers of the message contains:
-      |subject |my test subject |
+      |Subject |my test subject |
     And the date of the message is "2014-10-30T14:12:00Z"
     And the hasAttachment of the message is "false"
     And the list of attachments of the message is empty
@@ -61,8 +61,8 @@ Feature: GetMessages method
     And the isUnread of the message is "true"
     And the preview of the message is <preview>
     And the headers of the message contains:
-      |content-type |text/html        |
-      |subject      |<subject-header> |
+      |Content-Type |text/html        |
+      |Subject      |<subject-header> |
     And the date of the message is "2014-10-30T14:12:00Z"
 
     Examples:
@@ -113,8 +113,8 @@ Feature: GetMessages method
     And the property "isUnread" of the message is null
     And the property "preview" of the message is null
     And the headers of the message contains:
-      |from    |user@domain.tld |
-      |header2 |Header2Content  |
+      |From    |user@domain.tld |
+      |HEADer2 |Header2Content  |
     And the property "date" of the message is null
 
   Scenario: Retrieving message should return not found when id does not match

http://git-wip-us.apache.org/repos/asf/james-project/blob/844a7409/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/GetMessagesMethod.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/GetMessagesMethod.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/GetMessagesMethod.java
index 8bc0a3e..1ae5a60 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/GetMessagesMethod.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/GetMessagesMethod.java
@@ -19,7 +19,6 @@
 
 package org.apache.james.jmap.methods;
 
-import java.util.Iterator;
 import java.util.List;
 import java.util.Optional;
 import java.util.function.Function;
@@ -38,21 +37,22 @@ import org.apache.james.jmap.model.MessageFactory;
 import org.apache.james.jmap.model.MessageId;
 import org.apache.james.jmap.model.MessageProperties;
 import org.apache.james.jmap.model.MessageProperties.HeaderProperty;
+import org.apache.james.mailbox.MailboxManager;
 import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MessageManager;
 import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.model.FetchGroupImpl;
+import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.mailbox.model.MailboxPath;
 import org.apache.james.mailbox.model.MessageAttachment;
-import org.apache.james.mailbox.model.MessageRange;
-import org.apache.james.mailbox.store.mail.MailboxMapperFactory;
-import org.apache.james.mailbox.store.mail.MessageMapper;
-import org.apache.james.mailbox.store.mail.MessageMapperFactory;
-import org.apache.james.mailbox.store.mail.model.Mailbox;
-import org.apache.james.mailbox.store.mail.model.MailboxMessage;
-import org.javatuples.Pair;
+import org.apache.james.mailbox.model.MessageResult;
+import org.apache.james.mailbox.model.MessageResultIterator;
+import org.javatuples.Triplet;
 
 import com.fasterxml.jackson.databind.ser.PropertyFilter;
 import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
 import com.github.fge.lambdas.Throwing;
+import com.github.fge.lambdas.functions.ThrowingFunction;
 import com.github.steveash.guavate.Guavate;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
@@ -63,17 +63,14 @@ public class GetMessagesMethod implements Method {
     public static final String HEADERS_FILTER = "headersFilter";
     private static final Method.Request.Name METHOD_NAME = Method.Request.name("getMessages");
     private static final Method.Response.Name RESPONSE_NAME = Method.Response.name("messages");
-    private final MessageMapperFactory messageMapperFactory;
-    private final MailboxMapperFactory mailboxMapperFactory;
+    private final MailboxManager mailboxManager;
     private final MessageFactory messageFactory;
 
     @Inject
     @VisibleForTesting GetMessagesMethod(
-            MessageMapperFactory messageMapperFactory,
-            MailboxMapperFactory mailboxMapperFactory,
+            MailboxManager mailboxManager,
             MessageFactory messageFactory) {
-        this.messageMapperFactory = messageMapperFactory;
-        this.mailboxMapperFactory = mailboxMapperFactory;
+        this.mailboxManager = mailboxManager;
         this.messageFactory = messageFactory;
     }
     
@@ -116,8 +113,8 @@ public class GetMessagesMethod implements Method {
     private GetMessagesResponse getMessagesResponse(MailboxSession mailboxSession, GetMessagesRequest getMessagesRequest) {
         getMessagesRequest.getAccountId().ifPresent(GetMessagesMethod::notImplemented);
         
-        Function<MessageId, Stream<CompletedMailboxMessage>> loadMessages = loadMessage(mailboxSession);
-        Function<CompletedMailboxMessage, Message> convertToJmapMessage = toJmapMessage(mailboxSession);
+        Function<MessageId, Stream<CompletedMessageResult>> loadMessages = loadMessage(mailboxSession);
+        Function<CompletedMessageResult, Message> convertToJmapMessage = toJmapMessage(mailboxSession);
         
         List<Message> result = getMessagesRequest.getIds().stream()
             .flatMap(loadMessages)
@@ -132,41 +129,51 @@ public class GetMessagesMethod implements Method {
     }
 
     
-    private Function<CompletedMailboxMessage, Message> toJmapMessage(MailboxSession mailboxSession) {
-        return (completedMailboxMessage) -> messageFactory.fromMailboxMessage(
-                completedMailboxMessage.mailboxMessage, 
-                completedMailboxMessage.attachments, 
-                uid -> new MessageId(mailboxSession.getUser(), completedMailboxMessage.mailboxPath , uid));
+    private Function<CompletedMessageResult, Message> toJmapMessage(MailboxSession mailboxSession) {
+        ThrowingFunction<CompletedMessageResult, Message> function = (completedMessageResult) -> messageFactory.fromMessageResult(
+                completedMessageResult.messageResult,
+                completedMessageResult.attachments,
+                completedMessageResult.mailboxId,
+                uid -> new MessageId(mailboxSession.getUser(), completedMessageResult.mailboxPath , uid));
+        return Throwing.function(function).sneakyThrow();
     }
 
-    private Function<MessageId, Stream<CompletedMailboxMessage>> 
+    private Function<MessageId, Stream<CompletedMessageResult>> 
                 loadMessage(MailboxSession mailboxSession) {
 
         return Throwing
                 .function((MessageId messageId) -> {
                      MailboxPath mailboxPath = messageId.getMailboxPath();
-                     MessageMapper messageMapper = messageMapperFactory.getMessageMapper(mailboxSession);
-                     Mailbox mailbox = mailboxMapperFactory.getMailboxMapper(mailboxSession).findMailboxByPath(mailboxPath);
-                     return Pair.with(
-                             messageMapper.findInMailbox(mailbox, MessageRange.one(messageId.getUid()), MessageMapper.FetchType.Full, 1),
-                             mailboxPath
+                     MessageManager messageManager = mailboxManager.getMailbox(messageId.getMailboxPath(), mailboxSession);
+                     return Triplet.with(
+                             messageManager.getMessages(messageId.getUidAsRange(), FetchGroupImpl.FULL_CONTENT, mailboxSession),
+                             mailboxPath,
+                             messageManager.getId()
                              );
                 })
-                .andThen(Throwing.function((pair) -> retrieveCompleteMailboxMessages(pair, mailboxSession)));
+                .andThen(Throwing.function((triplet) -> retrieveCompleteMessageResults(triplet, mailboxSession)));
     }
     
-    private Stream<CompletedMailboxMessage> retrieveCompleteMailboxMessages(Pair<Iterator<MailboxMessage>, MailboxPath> value, MailboxSession mailboxSession) throws MailboxException {
-        Iterable<MailboxMessage> iterable = () -> value.getValue0();
-        Stream<MailboxMessage> targetStream = StreamSupport.stream(iterable.spliterator(), false);
+    private Stream<CompletedMessageResult> retrieveCompleteMessageResults(Triplet<MessageResultIterator, MailboxPath, MailboxId> value, MailboxSession mailboxSession) throws MailboxException {
+        Iterable<MessageResult> iterable = () -> value.getValue0();
+        Stream<MessageResult> targetStream = StreamSupport.stream(iterable.spliterator(), false);
 
         MailboxPath mailboxPath = value.getValue1();
+        MailboxId mailboxId = value.getValue2();
         return targetStream
-                .map(message -> CompletedMailboxMessage.builder().mailboxMessage(message).attachments(message.getAttachments()))
+                .map(Throwing.function(this::initializeBuilder).sneakyThrow())
+                .map(builder -> builder.mailboxId(mailboxId))
                 .map(builder -> builder.mailboxPath(mailboxPath))
                 .map(builder -> builder.build()); 
     }
+    
+    private CompletedMessageResult.Builder initializeBuilder(MessageResult message) throws MailboxException {
+        return CompletedMessageResult.builder()
+                .messageResult(message)
+                .attachments(message.getAttachments());
+    }
 
-    private static class CompletedMailboxMessage {
+    private static class CompletedMessageResult {
 
         public static Builder builder() {
             return new Builder();
@@ -174,16 +181,17 @@ public class GetMessagesMethod implements Method {
 
         public static class Builder {
 
-            private MailboxMessage mailboxMessage;
+            private MessageResult messageResult;
             private List<MessageAttachment> attachments;
             private MailboxPath mailboxPath;
+            private MailboxId mailboxId;
 
             private Builder() {
             }
 
-            public Builder mailboxMessage(MailboxMessage mailboxMessage) {
-                Preconditions.checkArgument(mailboxMessage != null);
-                this.mailboxMessage = mailboxMessage;
+            public Builder messageResult(MessageResult messageResult) {
+                Preconditions.checkArgument(messageResult != null);
+                this.messageResult = messageResult;
                 return this;
             }
 
@@ -199,22 +207,31 @@ public class GetMessagesMethod implements Method {
                 return this;
             }
 
-            public CompletedMailboxMessage build() {
-                Preconditions.checkState(mailboxMessage != null);
+            public Builder mailboxId(MailboxId mailboxId) {
+                Preconditions.checkArgument(mailboxId != null);
+                this.mailboxId = mailboxId;
+                return this;
+            }
+
+            public CompletedMessageResult build() {
+                Preconditions.checkState(messageResult != null);
                 Preconditions.checkState(attachments != null);
                 Preconditions.checkState(mailboxPath != null);
-                return new CompletedMailboxMessage(mailboxMessage, attachments, mailboxPath);
+                Preconditions.checkState(mailboxId != null);
+                return new CompletedMessageResult(messageResult, attachments, mailboxPath, mailboxId);
             }
         }
 
-        private final MailboxMessage mailboxMessage;
+        private final MessageResult messageResult;
         private final List<MessageAttachment> attachments;
         private final MailboxPath mailboxPath;
+        private final MailboxId mailboxId;
 
-        public CompletedMailboxMessage(MailboxMessage mailboxMessage, List<MessageAttachment> attachments, MailboxPath mailboxPath) {
-            this.mailboxMessage = mailboxMessage;
+        public CompletedMessageResult(MessageResult messageResult, List<MessageAttachment> attachments, MailboxPath mailboxPath, MailboxId mailboxId) {
+            this.messageResult = messageResult;
             this.attachments = attachments;
             this.mailboxPath = mailboxPath;
+            this.mailboxId = mailboxId;
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/844a7409/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageContentExtractor.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageContentExtractor.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageContentExtractor.java
new file mode 100644
index 0000000..ecb9f48
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageContentExtractor.java
@@ -0,0 +1,127 @@
+/****************************************************************
+ * 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.model;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.james.mime4j.dom.Body;
+import org.apache.james.mime4j.dom.Entity;
+import org.apache.james.mime4j.dom.Multipart;
+import org.apache.james.mime4j.dom.TextBody;
+
+import com.github.fge.lambdas.Throwing;
+
+public class MessageContentExtractor {
+    
+    public MessageContent extract(org.apache.james.mime4j.dom.Message message) throws IOException {
+        Body body = message.getBody();
+        if (body instanceof TextBody) {
+            return parseTextBody(message, (TextBody)body);
+        }
+        if (body instanceof Multipart){
+            return parseMultipart(message, (Multipart)body);
+        }
+        return MessageContent.empty();
+    }
+
+    private MessageContent parseTextBody(Entity entity, TextBody textBody) throws IOException {
+        String bodyContent = asString(textBody);
+        if ("text/html".equals(entity.getMimeType())) {
+            return MessageContent.ofHtmlOnly(bodyContent);
+        }
+        return MessageContent.ofTextOnly(bodyContent);
+    }
+
+    private MessageContent parseMultipart(Entity entity, Multipart multipart) throws IOException {
+        if ("multipart/alternative".equals(entity.getMimeType())) {
+            return parseMultipartAlternative(multipart);
+        }
+        return parseMultipartMixed(multipart);
+    }
+
+    private String asString(TextBody textBody) throws IOException {
+        return IOUtils.toString(textBody.getInputStream(), textBody.getMimeCharset());
+    }
+
+    private MessageContent parseMultipartMixed(Multipart multipart) throws IOException {
+        List<Entity> parts = multipart.getBodyParts();
+        if (! parts.isEmpty()) {
+            Entity firstPart = parts.get(0);
+            if (firstPart.getBody() instanceof Multipart && "multipart/alternative".equals(firstPart.getMimeType())) {
+                return parseMultipartAlternative((Multipart)firstPart.getBody());
+            } else {
+                if (firstPart.getBody() instanceof TextBody) {
+                    return parseTextBody(firstPart, (TextBody)firstPart.getBody());
+                }
+            }
+        }
+        return MessageContent.empty();
+    }
+
+    private MessageContent parseMultipartAlternative(Multipart multipart) throws IOException {
+        Optional<String> textBody = getFirstMatchingTextBody(multipart, "text/plain");
+        Optional<String> htmlBody = getFirstMatchingTextBody(multipart, "text/html");
+        return new MessageContent(textBody, htmlBody);
+    }
+
+    private Optional<String> getFirstMatchingTextBody(Multipart multipart, String mimeType) throws IOException {
+        return multipart.getBodyParts()
+                .stream()
+                .filter(part -> mimeType.equals(part.getMimeType()))
+                .map(Entity::getBody)
+                .filter(TextBody.class::isInstance)
+                .map(TextBody.class::cast)
+                .findFirst()
+                .map(Throwing.function(this::asString).sneakyThrow());
+    }
+
+    public static class MessageContent {
+        private final Optional<String> textBody;
+        private final Optional<String> htmlBody;
+
+        public MessageContent(Optional<String> textBody, Optional<String> htmlBody) {
+            this.textBody = textBody;
+            this.htmlBody = htmlBody;
+        }
+
+        public static MessageContent ofTextOnly(String textBody) {
+            return new MessageContent(Optional.of(textBody), Optional.empty());
+        }
+
+        public static MessageContent ofHtmlOnly(String htmlBody) {
+            return new MessageContent(Optional.empty(), Optional.of(htmlBody));
+        }
+
+        public static MessageContent empty() {
+            return new MessageContent(Optional.empty(), Optional.empty());
+        }
+        
+        public Optional<String> getTextBody() {
+            return textBody;
+        }
+
+        public Optional<String> getHtmlBody() {
+            return htmlBody;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/844a7409/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageFactory.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageFactory.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageFactory.java
index 8790df3..b401b62 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageFactory.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageFactory.java
@@ -18,29 +18,44 @@
  ****************************************************************/
 package org.apache.james.jmap.model;
 
+import java.io.IOException;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
+import java.util.Collection;
+import java.util.Date;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Optional;
 import java.util.Set;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
 import javax.inject.Inject;
+import javax.mail.Flags;
 
+import org.apache.james.jmap.model.MessageContentExtractor.MessageContent;
 import org.apache.james.jmap.model.message.EMailer;
 import org.apache.james.jmap.model.message.IndexableMessage;
+import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.model.Cid;
+import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.mailbox.model.MessageAttachment;
+import org.apache.james.mailbox.model.MessageResult;
 import org.apache.james.mailbox.store.extractor.DefaultTextExtractor;
 import org.apache.james.mailbox.store.mail.model.MailboxMessage;
+import org.apache.james.mime4j.dom.address.AddressList;
+import org.apache.james.mime4j.dom.address.Mailbox;
+import org.apache.james.mime4j.dom.address.MailboxList;
+import org.apache.james.mime4j.message.MessageBuilder;
+import org.apache.james.mime4j.stream.Field;
 
 import com.github.steveash.guavate.Guavate;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Multimap;
+import com.google.common.collect.Multimaps;
 
 public class MessageFactory {
 
@@ -48,10 +63,53 @@ public class MessageFactory {
     public static final ZoneId UTC_ZONE_ID = ZoneId.of("Z");
 
     private final MessagePreviewGenerator messagePreview;
+    private final MessageContentExtractor messageContentExtractor;
 
     @Inject
-    public MessageFactory(MessagePreviewGenerator messagePreview) {
+    public MessageFactory(MessagePreviewGenerator messagePreview, MessageContentExtractor messageContentExtractor) {
         this.messagePreview = messagePreview;
+        this.messageContentExtractor = messageContentExtractor;
+    }
+
+    public Message fromMessageResult(MessageResult messageResult,
+            List<MessageAttachment> attachments,
+            MailboxId mailboxId,
+            Function<Long, MessageId> uidToMessageId) throws MailboxException {
+        MessageId messageId = uidToMessageId.apply(messageResult.getUid());
+
+        MessageBuilder parsedMessageResult;
+        MessageContent messageContent;
+        try {
+            parsedMessageResult = MessageBuilder.read(messageResult.getFullContent().getInputStream());
+            messageContent = messageContentExtractor.extract(parsedMessageResult.build());
+        } catch (IOException e) {
+            throw new MailboxException("Unable to parse message: " + e.getMessage(), e);
+        }
+
+        return Message.builder()
+                .id(messageId)
+                .blobId(BlobId.of(String.valueOf(messageResult.getUid())))
+                .threadId(messageId.serialize())
+                .mailboxIds(ImmutableList.of(mailboxId.serialize()))
+                .inReplyToMessageId(getHeader(parsedMessageResult, "in-reply-to"))
+                .isUnread(! messageResult.getFlags().contains(Flags.Flag.SEEN))
+                .isFlagged(messageResult.getFlags().contains(Flags.Flag.FLAGGED))
+                .isAnswered(messageResult.getFlags().contains(Flags.Flag.ANSWERED))
+                .isDraft(messageResult.getFlags().contains(Flags.Flag.DRAFT))
+                .subject(Strings.nullToEmpty(parsedMessageResult.getSubject()))
+                .headers(toMap(parsedMessageResult.getFields()))
+                .from(firstFromMailboxList(parsedMessageResult.getFrom()))
+                .to(fromAddressList(parsedMessageResult.getTo()))
+                .cc(fromAddressList(parsedMessageResult.getCc()))
+                .bcc(fromAddressList(parsedMessageResult.getBcc()))
+                .replyTo(fromAddressList(parsedMessageResult.getReplyTo()))
+                .size(parsedMessageResult.getSize())
+                .date(toZonedDateTime(messageResult.getInternalDate()))
+                .textBody(messageContent.getTextBody().orElse(null))
+                .htmlBody(messageContent.getHtmlBody().orElse(null))
+                .preview(getPreview(messageContent))
+                .attachments(getAttachments(attachments))
+                .build();
     }
 
     public Message fromMailboxMessage(MailboxMessage mailboxMessage,
@@ -86,6 +144,13 @@ public class MessageFactory {
                 .build();
     }
 
+    private String getPreview(MessageContent messageContent) {
+        if (messageContent.getHtmlBody().isPresent()) {
+            return messagePreview.forHTMLBody(messageContent.getHtmlBody());
+        }
+        return messagePreview.forTextBody(messageContent.getTextBody());
+    }
+
     private String getPreview(IndexableMessage im) {
         Optional<String> bodyHtml = im.getBodyHtml();
         if (bodyHtml.isPresent()) {
@@ -100,6 +165,40 @@ public class MessageFactory {
                     .map(String::trim)
                     .collect(Collectors.joining(MULTIVALUED_HEADERS_SEPARATOR));
     }
+    
+    private Emailer firstFromMailboxList(MailboxList list) {
+        if (list == null) {
+            return null;
+        }
+        return list.stream()
+                .map(this::fromMailbox)
+                .findFirst()
+                .orElse(null);
+    }
+    
+    private ImmutableList<Emailer> fromAddressList(AddressList list) {
+        if (list == null) {
+            return ImmutableList.of();
+        }
+        return list.flatten()
+            .stream()
+            .map(this::fromMailbox)
+            .collect(Guavate.toImmutableList());
+    }
+    
+    private Emailer fromMailbox(Mailbox mailbox) {
+        return Emailer.builder()
+                    .name(getNameOrAddress(mailbox))
+                    .email(mailbox.getAddress())
+                    .build();
+    }
+
+    private String getNameOrAddress(Mailbox mailbox) {
+        if (mailbox.getName() != null) {
+            return mailbox.getName();
+        }
+        return mailbox.getAddress();
+    }
 
     private Emailer firstElasticSearchEmailers(Set<EMailer> emailers) {
         return emailers.stream()
@@ -129,6 +228,28 @@ public class MessageFactory {
                 .collect(Guavate.toImmutableMap(Map.Entry::getKey, x -> joinOnComma(x.getValue())));
     }
     
+    private ImmutableMap<String, String> toMap(List<Field> fields) {
+        Function<Entry<String, Collection<Field>>, String> bodyConcatenator = fieldListEntry -> fieldListEntry.getValue()
+                .stream()
+                .map(Field::getBody)
+                .collect(Collectors.toList())
+                .stream()
+                .collect(Collectors.joining(","));
+        return Multimaps.index(fields, Field::getName)
+                .asMap()
+                .entrySet()
+                .stream()
+                .collect(Guavate.toImmutableMap(Map.Entry::getKey, bodyConcatenator));
+    }
+    
+    private String getHeader(MessageBuilder message, String header) {
+        Field field = message.getField(header);
+        if (field == null) {
+            return null;
+        }
+        return field.getBody();
+    }
+    
     private String getHeaderAsSingleValue(IndexableMessage im, String header) {
         return Strings.emptyToNull(joinOnComma(im.getHeaders().get(header)));
     }
@@ -141,6 +262,10 @@ public class MessageFactory {
         return ZonedDateTime.ofInstant(mailboxMessage.getInternalDate().toInstant(), UTC_ZONE_ID);
     }
 
+    private ZonedDateTime toZonedDateTime(Date date) {
+        return ZonedDateTime.ofInstant(date.toInstant(), UTC_ZONE_ID);
+    }
+
     private String getTextBody(IndexableMessage im) {
         return im.getBodyText().map(Strings::emptyToNull).orElse(null);
     }

http://git-wip-us.apache.org/repos/asf/james-project/blob/844a7409/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/GetMessagesMethodTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/GetMessagesMethodTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/GetMessagesMethodTest.java
index 54463ea..8b2ccdd 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/GetMessagesMethodTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/GetMessagesMethodTest.java
@@ -37,6 +37,7 @@ import org.apache.james.jmap.model.ClientId;
 import org.apache.james.jmap.model.GetMessagesRequest;
 import org.apache.james.jmap.model.GetMessagesResponse;
 import org.apache.james.jmap.model.Message;
+import org.apache.james.jmap.model.MessageContentExtractor;
 import org.apache.james.jmap.model.MessageFactory;
 import org.apache.james.jmap.model.MessageId;
 import org.apache.james.jmap.model.MessagePreviewGenerator;
@@ -102,21 +103,20 @@ public class GetMessagesMethodTest {
     private static final User ROBERT = new User("robert", "secret");
 
     private StoreMailboxManager mailboxManager;
-    private InMemoryMailboxSessionMapperFactory mailboxSessionMapperFactory;
+    private GetMessagesMethod testee;
 
     private MailboxSession session;
     private MailboxPath inboxPath;
     private ClientId clientId;
-    
-    private MessageFactory messageFactory;
 
     @Before
     public void setup() throws MailboxException {
         clientId = ClientId.of("#0");
-        mailboxSessionMapperFactory = new InMemoryMailboxSessionMapperFactory();
+        InMemoryMailboxSessionMapperFactory mailboxSessionMapperFactory = new InMemoryMailboxSessionMapperFactory();
         HtmlTextExtractor htmlTextExtractor = new MailboxBasedHtmlTextExtractor(new DefaultTextExtractor());
         MessagePreviewGenerator messagePreview = new MessagePreviewGenerator(htmlTextExtractor);
-        messageFactory = new MessageFactory(messagePreview);
+        MessageContentExtractor messageContentExtractor = new MessageContentExtractor();
+        MessageFactory messageFactory = new MessageFactory(messagePreview, messageContentExtractor);
         MockAuthenticator authenticator = new MockAuthenticator();
         authenticator.addUser(ROBERT.username, ROBERT.password);
         UnionMailboxACLResolver aclResolver = new UnionMailboxACLResolver();
@@ -129,32 +129,29 @@ public class GetMessagesMethodTest {
         session = mailboxManager.login(ROBERT.username, ROBERT.password, LOGGER);
         inboxPath = MailboxPath.inbox(session);
         mailboxManager.createMailbox(inboxPath, session);
+        testee = new GetMessagesMethod(mailboxManager, messageFactory);
     }
     
     @Test
     public void processShouldThrowWhenNullRequest() {
-        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, messageFactory);
         GetMessagesRequest request = null;
         assertThatThrownBy(() -> testee.process(request, mock(ClientId.class), mock(MailboxSession.class))).isInstanceOf(NullPointerException.class);
     }
 
     @Test
     public void processShouldThrowWhenNullSession() {
-        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, messageFactory);
         MailboxSession mailboxSession = null;
         assertThatThrownBy(() -> testee.process(mock(GetMessagesRequest.class), mock(ClientId.class), mailboxSession)).isInstanceOf(NullPointerException.class);
     }
 
     @Test
     public void processShouldThrowWhenNullClientId() {
-        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, messageFactory);
         ClientId clientId = null;
         assertThatThrownBy(() -> testee.process(mock(GetMessagesRequest.class), clientId, mock(MailboxSession.class))).isInstanceOf(NullPointerException.class);
     }
 
     @Test
     public void processShouldThrowWhenRequestHasAccountId() {
-        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, messageFactory);
         assertThatThrownBy(() -> testee.process(
                 GetMessagesRequest.builder().accountId("abc").build(), mock(ClientId.class), mock(MailboxSession.class))).isInstanceOf(NotImplementedException.class);
     }
@@ -176,7 +173,6 @@ public class GetMessagesMethodTest {
                           new MessageId(ROBERT, inboxPath, message3Uid)))
                 .build();
 
-        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, messageFactory);
         List<JmapResponse> result = testee.process(request, clientId, session).collect(Collectors.toList());
         
         assertThat(result).hasSize(1)
@@ -205,7 +201,6 @@ public class GetMessagesMethodTest {
                 .ids(ImmutableList.of(new MessageId(ROBERT, inboxPath, messageUid)))
                 .build();
 
-        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, messageFactory);
         List<JmapResponse> result = testee.process(request, clientId, session).collect(Collectors.toList());
         
         assertThat(result).hasSize(1)
@@ -229,7 +224,6 @@ public class GetMessagesMethodTest {
                 .properties(ImmutableList.of())
                 .build();
 
-        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, messageFactory);
         List<JmapResponse> result = testee.process(request, clientId, session).collect(Collectors.toList());
 
         assertThat(result).hasSize(1)
@@ -250,7 +244,6 @@ public class GetMessagesMethodTest {
                 .ids(ImmutableList.of(new MessageId(ROBERT, inboxPath, message1Uid)))
                 .build();
 
-        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, messageFactory);
         Stream<JmapResponse> result = testee.process(request, clientId, session);
 
         assertThat(result).hasSize(1)
@@ -274,7 +267,6 @@ public class GetMessagesMethodTest {
 
         Set<MessageProperty> expected = Sets.newHashSet(MessageProperty.id, MessageProperty.subject);
 
-        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, messageFactory);
         List<JmapResponse> result = testee.process(request, clientId, session).collect(Collectors.toList());
 
         assertThat(result).hasSize(1)
@@ -298,7 +290,6 @@ public class GetMessagesMethodTest {
 
         Set<MessageProperty> expected = Sets.newHashSet(MessageProperty.id, MessageProperty.textBody);
 
-        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, messageFactory);
         List<JmapResponse> result = testee.process(request, clientId, session).collect(Collectors.toList());
 
         assertThat(result).hasSize(1)
@@ -325,7 +316,6 @@ public class GetMessagesMethodTest {
 
         Set<MessageProperty> expected = Sets.newHashSet(MessageProperty.id, MessageProperty.headers);
 
-        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, messageFactory);
         List<JmapResponse> result = testee.process(request, clientId, session).collect(Collectors.toList());
 
         assertThat(result)
@@ -351,7 +341,6 @@ public class GetMessagesMethodTest {
                 .properties(ImmutableList.of("headers.from", "headers.heADER2"))
                 .build();
 
-        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, messageFactory);
         List<JmapResponse> result = testee.process(request, clientId, session).collect(Collectors.toList());
 
         assertThat(result)
@@ -362,6 +351,6 @@ public class GetMessagesMethodTest {
         ObjectMapper objectMapper = new ObjectMapper();
         objectMapper.setFilterProvider(actualFilterProvider.setDefaultFilter(SimpleBeanPropertyFilter.serializeAll()));
         String response = objectMapper.writer().writeValueAsString(result.get(0));
-        assertThat(JsonPath.parse(response).<Map<String, String>>read("$.response.list[0].headers")).containsOnly(MapEntry.entry("from", "user@domain.tld"), MapEntry.entry("header2", "Header2Content"));
+        assertThat(JsonPath.parse(response).<Map<String, String>>read("$.response.list[0].headers")).containsOnly(MapEntry.entry("From", "user@domain.tld"), MapEntry.entry("HEADer2", "Header2Content"));
     }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/844a7409/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java
index f81ed55..40649f5 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java
@@ -43,6 +43,7 @@ import org.apache.james.jmap.model.CreationMessage;
 import org.apache.james.jmap.model.CreationMessage.DraftEmailer;
 import org.apache.james.jmap.model.CreationMessageId;
 import org.apache.james.jmap.model.Message;
+import org.apache.james.jmap.model.MessageContentExtractor;
 import org.apache.james.jmap.model.MessageFactory;
 import org.apache.james.jmap.model.MessageId;
 import org.apache.james.jmap.model.MessagePreviewGenerator;
@@ -112,7 +113,8 @@ public class SetMessagesCreationProcessorTest {
     public void setup() {
         HtmlTextExtractor htmlTextExtractor = new MailboxBasedHtmlTextExtractor(new DefaultTextExtractor());
         MessagePreviewGenerator messagePreview = new MessagePreviewGenerator(htmlTextExtractor);
-        messageFactory = new MessageFactory(messagePreview);
+        MessageContentExtractor messageContentExtractor = new MessageContentExtractor();
+        messageFactory = new MessageFactory(messagePreview, messageContentExtractor);
     }
 
     private final CreationMessage.Builder creationMessageBuilder = CreationMessage.builder()

http://git-wip-us.apache.org/repos/asf/james-project/blob/844a7409/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MailboxMessageTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MailboxMessageTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MailboxMessageTest.java
index 4ad127c..da0f386 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MailboxMessageTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MailboxMessageTest.java
@@ -60,7 +60,8 @@ public class MailboxMessageTest {
     public void setUp() {
         htmlTextExtractor = mock(HtmlTextExtractor.class);
         messagePreview = new MessagePreviewGenerator(htmlTextExtractor);
-        messageFactory = new MessageFactory(messagePreview);
+        MessageContentExtractor messageContentExtractor = new MessageContentExtractor();
+        messageFactory = new MessageFactory(messagePreview, messageContentExtractor);
     }
     
     @Test(expected=IllegalStateException.class)

http://git-wip-us.apache.org/repos/asf/james-project/blob/844a7409/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageContentExtractorTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageContentExtractorTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageContentExtractorTest.java
new file mode 100644
index 0000000..388f115
--- /dev/null
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageContentExtractorTest.java
@@ -0,0 +1,171 @@
+/****************************************************************
+ * 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.model;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.IOException;
+
+import org.apache.james.jmap.model.MessageContentExtractor.MessageContent;
+import org.apache.james.mime4j.dom.Message;
+import org.apache.james.mime4j.dom.Multipart;
+import org.apache.james.mime4j.message.BasicBodyFactory;
+import org.apache.james.mime4j.message.BodyPart;
+import org.apache.james.mime4j.message.BodyPartBuilder;
+import org.apache.james.mime4j.message.MessageBuilder;
+import org.apache.james.mime4j.message.MultipartBuilder;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.base.Charsets;
+
+public class MessageContentExtractorTest {
+    private static final String BINARY_CONTENT = "binary";
+    private static final String TEXT_CONTENT = "text content";
+    private static final String HTML_CONTENT = "<b>html</b> content";
+
+    private MessageContentExtractor testee;
+
+    private BodyPart htmlPart;
+    private BodyPart textPart;
+
+    @Before
+    public void setup() throws IOException {
+        testee = new MessageContentExtractor();
+        textPart = BodyPartBuilder.create().setBody(TEXT_CONTENT, "plain", Charsets.UTF_8).build();
+        htmlPart = BodyPartBuilder.create().setBody(HTML_CONTENT, "html", Charsets.UTF_8).build();
+    }
+
+    @Test
+    public void extractShouldReturnEmptyWhenBinaryContentOnly() throws IOException {
+        Message message = MessageBuilder.create()
+                .setBody(BasicBodyFactory.INSTANCE.binaryBody(BINARY_CONTENT, Charsets.UTF_8))
+                .build();
+        MessageContent actual = testee.extract(message);
+        assertThat(actual.getTextBody()).isEmpty();
+        assertThat(actual.getHtmlBody()).isEmpty();
+    }
+
+    @Test
+    public void extractShouldReturnTextOnlyWhenTextOnlyBody() throws IOException {
+        Message message = MessageBuilder.create()
+                .setBody(TEXT_CONTENT, Charsets.UTF_8)
+                .build();
+        MessageContent actual = testee.extract(message);
+        assertThat(actual.getTextBody()).contains(TEXT_CONTENT);
+        assertThat(actual.getHtmlBody()).isEmpty();
+    }
+
+    @Test
+    public void extractShouldReturnHtmlOnlyWhenHtmlOnlyBody() throws IOException {
+        Message message = MessageBuilder.create()
+                .setBody(HTML_CONTENT, "html", Charsets.UTF_8)
+                .build();
+        MessageContent actual = testee.extract(message);
+        assertThat(actual.getTextBody()).isEmpty();
+        assertThat(actual.getHtmlBody()).contains(HTML_CONTENT);
+    }
+
+    @Test
+    public void extractShouldReturnHtmlAndTextWhenMultipartAlternative() throws IOException {
+        Multipart multipart = MultipartBuilder.create("alternative")
+                .addBodyPart(textPart)
+                .addBodyPart(htmlPart)
+                .build();
+        Message message = MessageBuilder.create()
+                .setBody(multipart)
+                .build();
+        MessageContent actual = testee.extract(message);
+        assertThat(actual.getTextBody()).contains(TEXT_CONTENT);
+        assertThat(actual.getHtmlBody()).contains(HTML_CONTENT);
+    }
+
+    @Test
+    public void extractShouldReturnHtmlWhenMultipartAlternativeWithoutPlainPart() throws IOException {
+        Multipart multipart = MultipartBuilder.create("alternative")
+                .addBodyPart(htmlPart)
+                .build();
+        Message message = MessageBuilder.create()
+                .setBody(multipart)
+                .build();
+        MessageContent actual = testee.extract(message);
+        assertThat(actual.getTextBody()).isEmpty();
+        assertThat(actual.getHtmlBody()).contains(HTML_CONTENT);
+    }
+
+    @Test
+    public void extractShouldReturnTextWhenMultipartAlternativeWithoutHtmlPart() throws IOException {
+        Multipart multipart = MultipartBuilder.create("alternative")
+                .addBodyPart(textPart)
+                .build();
+        Message message = MessageBuilder.create()
+                .setBody(multipart)
+                .build();
+        MessageContent actual = testee.extract(message);
+        assertThat(actual.getTextBody()).contains(TEXT_CONTENT);
+        assertThat(actual.getHtmlBody()).isEmpty();
+    }
+
+    @Test
+    public void extractShouldReturnFirstPartOnlyWhenMultipartMixedAndFirstPartIsText() throws IOException {
+        Multipart multipart = MultipartBuilder.create("mixed")
+                .addBodyPart(textPart)
+                .addBodyPart(htmlPart)
+                .build();
+        Message message = MessageBuilder.create()
+                .setBody(multipart)
+                .build();
+        MessageContent actual = testee.extract(message);
+        assertThat(actual.getTextBody()).contains(TEXT_CONTENT);
+        assertThat(actual.getHtmlBody()).isEmpty();
+    }
+
+    @Test
+    public void extractShouldReturnFirstPartOnlyWhenMultipartMixedAndFirstPartIsHtml() throws IOException {
+        Multipart multipart = MultipartBuilder.create("mixed")
+                .addBodyPart(htmlPart)
+                .addBodyPart(textPart)
+                .build();
+        Message message = MessageBuilder.create()
+                .setBody(multipart)
+                .build();
+        MessageContent actual = testee.extract(message);
+        assertThat(actual.getTextBody()).isEmpty();
+        assertThat(actual.getHtmlBody()).contains(HTML_CONTENT);
+    }
+
+    @Test
+    public void extractShouldReturnHtmlAndTextWhenMultipartMixedAndFirstPartIsMultipartAlternative() throws IOException {
+        BodyPart multipartAlternative = BodyPartBuilder.create()
+            .setBody(MultipartBuilder.create("alternative")
+                    .addBodyPart(htmlPart)
+                    .addBodyPart(textPart)
+                    .build())
+            .build();
+        Multipart multipartMixed = MultipartBuilder.create("mixed")
+                .addBodyPart(multipartAlternative)
+                .build();
+        Message message = MessageBuilder.create()
+                .setBody(multipartMixed)
+                .build();
+        MessageContent actual = testee.extract(message);
+        assertThat(actual.getTextBody()).contains(TEXT_CONTENT);
+        assertThat(actual.getHtmlBody()).contains(HTML_CONTENT);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/844a7409/server/protocols/jmap/src/test/java/org/apache/james/jmap/send/MailFactoryTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/send/MailFactoryTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/send/MailFactoryTest.java
index f5128a6..a527102 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/send/MailFactoryTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/send/MailFactoryTest.java
@@ -27,6 +27,7 @@ import javax.mail.Flags;
 import javax.mail.util.SharedByteArrayInputStream;
 
 import org.apache.james.jmap.model.Message;
+import org.apache.james.jmap.model.MessageContentExtractor;
 import org.apache.james.jmap.model.MessageFactory;
 import org.apache.james.jmap.model.MessageId;
 import org.apache.james.jmap.model.MessagePreviewGenerator;
@@ -76,7 +77,8 @@ public class MailFactoryTest {
                 TestId.of(2));
         HtmlTextExtractor htmlTextExtractor = new MailboxBasedHtmlTextExtractor(new DefaultTextExtractor());
         MessagePreviewGenerator messagePreview = new MessagePreviewGenerator(htmlTextExtractor);
-        MessageFactory messageFactory = new MessageFactory(messagePreview) ;
+        MessageContentExtractor messageContentExtractor = new MessageContentExtractor();
+        MessageFactory messageFactory = new MessageFactory(messagePreview, messageContentExtractor);
         jmapMessage = messageFactory.fromMailboxMessage(mailboxMessage, ImmutableList.of(), x -> MessageId.of("test|test|" + x));
     }
 


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