james-server-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From btell...@apache.org
Subject james-project git commit: JAMES-1989 Move MessageContentExtractor to james-server-util-java8
Date Mon, 10 Apr 2017 01:35:48 GMT
Repository: james-project
Updated Branches:
  refs/heads/master fc7ded8b7 -> e1c486e24


JAMES-1989 Move MessageContentExtractor to james-server-util-java8


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

Branch: refs/heads/master
Commit: e1c486e24728a32d78533626a78f78c55c3c92d6
Parents: fc7ded8
Author: Antoine Duprat <aduprat@linagora.com>
Authored: Thu Apr 6 12:09:15 2017 +0200
Committer: Antoine Duprat <aduprat@linagora.com>
Committed: Thu Apr 6 12:16:25 2017 +0200

----------------------------------------------------------------------
 .../org/apache/james/jmap/JMAPCommonModule.java |   2 +-
 server/container/util-java8/pom.xml             |  16 +
 .../util/mime/MessageContentExtractor.java      | 226 +++++++++
 .../util/mime/MessageContentExtractorTest.java  | 491 +++++++++++++++++++
 .../jmap/model/MessageContentExtractor.java     | 226 ---------
 .../apache/james/jmap/model/MessageFactory.java |   3 +-
 .../jmap/methods/GetMessagesMethodTest.java     |   2 +-
 .../SetMessagesCreationProcessorTest.java       |   2 +-
 .../jmap/model/MessageContentExtractorTest.java | 490 ------------------
 .../james/jmap/model/MessageFactoryTest.java    |   1 +
 .../apache/james/jmap/send/MailFactoryTest.java |   2 +-
 11 files changed, 740 insertions(+), 721 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/e1c486e2/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPCommonModule.java
----------------------------------------------------------------------
diff --git a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPCommonModule.java b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPCommonModule.java
index c9d7678..565301f 100644
--- a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPCommonModule.java
+++ b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPCommonModule.java
@@ -31,7 +31,6 @@ 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.MailboxFactory;
-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;
@@ -39,6 +38,7 @@ import org.apache.james.jmap.send.MailSpool;
 import org.apache.james.jmap.utils.HeadersAuthenticationExtractor;
 import org.apache.james.util.date.DefaultZonedDateTimeProvider;
 import org.apache.james.util.date.ZonedDateTimeProvider;
+import org.apache.james.util.mime.MessageContentExtractor;
 import org.apache.mailet.base.AutomaticallySentMailDetector;
 import org.apache.mailet.base.AutomaticallySentMailDetectorImpl;
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/e1c486e2/server/container/util-java8/pom.xml
----------------------------------------------------------------------
diff --git a/server/container/util-java8/pom.xml b/server/container/util-java8/pom.xml
index a9a455c..3640006 100644
--- a/server/container/util-java8/pom.xml
+++ b/server/container/util-java8/pom.xml
@@ -127,6 +127,14 @@
             </activation>
             <dependencies>
                 <dependency>
+                    <groupId>org.apache.james</groupId>
+                    <artifactId>apache-mime4j-dom</artifactId>
+                </dependency>
+                <dependency>
+                    <groupId>com.github.fge</groupId>
+                    <artifactId>throwing-lambdas</artifactId>
+                </dependency>
+                <dependency>
                     <groupId>com.github.steveash.guavate</groupId>
                     <artifactId>guavate</artifactId>
                 </dependency>
@@ -135,6 +143,14 @@
                     <artifactId>guava</artifactId>
                 </dependency>
                 <dependency>
+                    <groupId>com.sun.mail</groupId>
+                    <artifactId>javax.mail</artifactId>
+                </dependency>
+                <dependency>
+                    <groupId>commons-io</groupId>
+                    <artifactId>commons-io</artifactId>
+                </dependency>
+                <dependency>
                     <groupId>javax.inject</groupId>
                     <artifactId>javax.inject</artifactId>
                 </dependency>

http://git-wip-us.apache.org/repos/asf/james-project/blob/e1c486e2/server/container/util-java8/src/main/java/org/apache/james/util/mime/MessageContentExtractor.java
----------------------------------------------------------------------
diff --git a/server/container/util-java8/src/main/java/org/apache/james/util/mime/MessageContentExtractor.java b/server/container/util-java8/src/main/java/org/apache/james/util/mime/MessageContentExtractor.java
new file mode 100644
index 0000000..7f819aa
--- /dev/null
+++ b/server/container/util-java8/src/main/java/org/apache/james/util/mime/MessageContentExtractor.java
@@ -0,0 +1,226 @@
+/****************************************************************
+ * 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.util.mime;
+
+import java.io.IOException;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+import javax.mail.internet.MimeMessage;
+
+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;
+import com.github.fge.lambdas.functions.ThrowingFunction;
+import com.google.common.base.Charsets;
+
+public class MessageContentExtractor {
+
+    public static final String CONTENT_ID = "Content-ID";
+    public static final String MULTIPART_ALTERNATIVE = "multipart/alternative";
+    public static final String TEXT_HTML = "text/html";
+    public static final String TEXT_PLAIN = "text/plain";
+
+    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 {
+        Optional<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 {
+        MessageContent messageContent = parseMultipartContent(entity, multipart);
+        if (!messageContent.isEmpty()) {
+            return messageContent;
+        }
+        return parseFirstFoundMultipart(multipart);
+    }
+
+    private MessageContent parseMultipartContent(Entity entity, Multipart multipart) throws IOException {
+        switch(entity.getMimeType()) {
+        case MULTIPART_ALTERNATIVE:
+            return retrieveHtmlAndPlainTextContent(multipart);
+        default:
+            return retrieveFirstReadablePart(multipart);
+        }
+    }
+
+    private MessageContent parseFirstFoundMultipart(Multipart multipart) throws IOException {
+        ThrowingFunction<Entity, MessageContent> parseMultipart = firstPart -> parseMultipart(firstPart, (Multipart)firstPart.getBody());
+        return multipart.getBodyParts()
+            .stream()
+            .filter(part -> part.getBody() instanceof Multipart)
+            .findFirst()
+            .map(Throwing.function(parseMultipart).sneakyThrow())
+            .orElse(MessageContent.empty());
+    }
+
+    private Optional<String> asString(TextBody textBody) throws IOException {
+        return Optional.ofNullable(IOUtils.toString(textBody.getInputStream(), Charsets.UTF_8));
+    }
+
+    private MessageContent retrieveHtmlAndPlainTextContent(Multipart multipart) throws IOException {
+        Optional<String> textBody = getFirstMatchingTextBody(multipart, TEXT_PLAIN);
+        Optional<String> htmlBody = getFirstMatchingTextBody(multipart, TEXT_HTML);
+        MessageContent directChildTextBodies = new MessageContent(textBody, htmlBody);
+        if (!directChildTextBodies.isComplete()) {
+            MessageContent fromInnerMultipart = parseFirstFoundMultipart(multipart);
+            return directChildTextBodies.merge(fromInnerMultipart);
+        }
+        return directChildTextBodies;
+    }
+
+    private MessageContent retrieveFirstReadablePart(Multipart multipart) throws IOException {
+        return retrieveFirstReadablePartMatching(multipart, this::isNotAttachment)
+            .orElseGet(() -> retrieveFirstReadablePartMatching(multipart, this::isInlinedWithoutCid)
+                .orElse(MessageContent.empty()));
+    }
+
+    private Optional<MessageContent> retrieveFirstReadablePartMatching(Multipart multipart, Predicate<Entity> predicate) {
+        return multipart.getBodyParts()
+            .stream()
+            .filter(predicate)
+            .flatMap(Throwing.function(this::extractContentIfReadable).sneakyThrow())
+            .findFirst();
+    }
+
+    private Stream<MessageContent> extractContentIfReadable(Entity entity) throws IOException {
+        if (TEXT_HTML.equals(entity.getMimeType()) && entity.getBody() instanceof TextBody) {
+            return Stream.of(
+                    MessageContent.ofHtmlOnly(asString((TextBody)entity.getBody())));
+        }
+        if (TEXT_PLAIN.equals(entity.getMimeType()) && entity.getBody() instanceof TextBody) {
+            return Stream.of(
+                    MessageContent.ofTextOnly(asString((TextBody)entity.getBody())));
+        }
+        if (entity.isMultipart() && entity.getBody() instanceof Multipart) {
+            MessageContent innerMultipartContent = parseMultipart(entity, (Multipart)entity.getBody());
+            if (!innerMultipartContent.isEmpty()) {
+                return Stream.of(innerMultipartContent);
+            }
+        }
+        return Stream.empty();
+    }
+
+    private Optional<String> getFirstMatchingTextBody(Multipart multipart, String mimeType) throws IOException {
+        Optional<String> firstMatchingTextBody = getFirstMatchingTextBody(multipart, mimeType, this::isNotAttachment);
+        if (firstMatchingTextBody.isPresent()) {
+            return firstMatchingTextBody;
+        }
+        Optional<String> fallBackInlinedBodyWithoutCid = getFirstMatchingTextBody(multipart, mimeType, this::isInlinedWithoutCid);
+        return fallBackInlinedBodyWithoutCid;
+    }
+
+    private Optional<String> getFirstMatchingTextBody(Multipart multipart, String mimeType, Predicate<Entity> condition) {
+        Function<TextBody, Optional<String>> textBodyOptionalFunction = Throwing
+            .<TextBody, Optional<String>>function(textBody ->  asString(textBody)).sneakyThrow();
+
+        return multipart.getBodyParts()
+            .stream()
+            .filter(part -> mimeType.equals(part.getMimeType()))
+            .filter(condition)
+            .map(Entity::getBody)
+            .filter(TextBody.class::isInstance)
+            .map(TextBody.class::cast)
+            .findFirst()
+            .flatMap(textBodyOptionalFunction);
+    }
+
+    private boolean isNotAttachment(Entity part) {
+        return part.getDispositionType() == null;
+    }
+
+    private boolean isInlinedWithoutCid(Entity part) {
+        return part.getDispositionType().equals(MimeMessage.INLINE) && part.getHeader().getField(CONTENT_ID) == null;
+    }
+
+    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(Optional<String> textBody) {
+            return new MessageContent(textBody, Optional.empty());
+        }
+
+        public static MessageContent ofHtmlOnly(Optional<String> htmlBody) {
+            return new MessageContent(Optional.empty(), htmlBody);
+        }
+
+        public static MessageContent empty() {
+            return new MessageContent(Optional.empty(), Optional.empty());
+        }
+        
+        public Optional<String> getTextBody() {
+            return textBody;
+        }
+
+        public Optional<String> getHtmlBody() {
+            return htmlBody;
+        }
+        
+        public boolean isEmpty() {
+            return equals(empty());
+        }
+
+        public boolean isComplete() {
+            return textBody.isPresent() && htmlBody.isPresent();
+        }
+
+        public MessageContent merge(MessageContent fromInnerMultipart) {
+            return new MessageContent(
+                    textBody.map(Optional::of).orElse(fromInnerMultipart.getTextBody()),
+                    htmlBody.map(Optional::of).orElse(fromInnerMultipart.getHtmlBody()));
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other == null || !(other instanceof MessageContent)) {
+                return false;
+            }
+            MessageContent otherMessageContent = (MessageContent)other;
+            return Objects.equals(this.textBody, otherMessageContent.textBody)
+                    && Objects.equals(this.htmlBody, otherMessageContent.htmlBody);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/e1c486e2/server/container/util-java8/src/test/java/org/apache/james/util/mime/MessageContentExtractorTest.java
----------------------------------------------------------------------
diff --git a/server/container/util-java8/src/test/java/org/apache/james/util/mime/MessageContentExtractorTest.java b/server/container/util-java8/src/test/java/org/apache/james/util/mime/MessageContentExtractorTest.java
new file mode 100644
index 0000000..9607564
--- /dev/null
+++ b/server/container/util-java8/src/test/java/org/apache/james/util/mime/MessageContentExtractorTest.java
@@ -0,0 +1,491 @@
+/****************************************************************
+ * 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.util.mime;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import javax.mail.internet.MimeMessage;
+
+import org.apache.james.mime4j.dom.Message;
+import org.apache.james.mime4j.dom.Multipart;
+import org.apache.james.mime4j.field.Fields;
+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.HeaderImpl;
+import org.apache.james.mime4j.message.MessageBuilder;
+import org.apache.james.mime4j.message.MultipartBuilder;
+import org.apache.james.mime4j.stream.Field;
+import org.apache.james.mime4j.util.ByteSequence;
+import org.apache.james.util.mime.MessageContentExtractor;
+import org.apache.james.util.mime.MessageContentExtractor.MessageContent;
+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 static final String TEXT_CONTENT2 = "other text content";
+    private static final String HTML_CONTENT2 = "other <b>html</b> content";
+    private static final String ATTACHMENT_CONTENT = "attachment content";
+    private static final String ANY_VALUE = "anyValue";
+    private static final Field CONTENT_ID_FIELD = new Field() {
+        @Override
+        public String getName() {
+            return MessageContentExtractor.CONTENT_ID;
+        }
+
+        @Override
+        public String getBody() {
+            return ANY_VALUE;
+        }
+
+        @Override
+        public ByteSequence getRaw() {
+            return ByteSequence.EMPTY;
+        }
+    };
+
+    private MessageContentExtractor testee;
+
+    private BodyPart htmlPart;
+    private BodyPart textPart;
+    private BodyPart textAttachment;
+    private BodyPart inlineText;
+    private BodyPart inlineImage;
+
+    @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();
+        textAttachment = BodyPartBuilder.create()
+                .setBody(ATTACHMENT_CONTENT, "plain", Charsets.UTF_8)
+                .setContentDisposition("attachment")
+                .build();
+        inlineText = BodyPartBuilder.create()
+                .setBody(ATTACHMENT_CONTENT, "plain", Charsets.UTF_8)
+                .setContentDisposition("inline")
+                .build();
+        inlineImage = BodyPartBuilder.create()
+                .setBody(new byte[0], "image/png")
+                .setContentDisposition("inline")
+                .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 extractShouldReturnFirstNonAttachmentPartWhenMultipartMixed() throws IOException {
+        Multipart multipart = MultipartBuilder.create("mixed")
+                .addBodyPart(textAttachment)
+                .addBodyPart(htmlPart)
+                .addBodyPart(textPart)
+                .build();
+        Message message = MessageBuilder.create()
+                .setBody(multipart)
+                .build();
+        MessageContent actual = testee.extract(message);
+        assertThat(actual.getHtmlBody()).contains(HTML_CONTENT);
+        assertThat(actual.getTextBody()).isEmpty();
+    }
+
+    @Test
+    public void extractShouldReturnInlinedTextBodyWithoutCIDWhenNoOtherValidParts() throws IOException {
+        String textBody = "body 1";
+        Multipart multipart = MultipartBuilder.create("report")
+            .addBodyPart(BodyPartBuilder.create()
+                .setBody(textBody, "plain", Charsets.UTF_8)
+                .setContentDisposition("inline")
+                .build())
+            .addBodyPart(BodyPartBuilder.create()
+                .setBody("body 2", "rfc822-headers", Charsets.UTF_8)
+                .setContentDisposition("inline")
+                .build())
+            .build();
+        Message message = MessageBuilder.create()
+            .setBody(multipart)
+            .build();
+
+        MessageContent actual = testee.extract(message);
+
+        assertThat(actual.getTextBody()).contains(textBody);
+    }
+
+    @Test
+    public void extractShouldReturnEmptyWhenMultipartMixedAndFirstPartIsATextAttachment() throws IOException {
+        Multipart multipart = MultipartBuilder.create("mixed")
+                .addBodyPart(textAttachment)
+                .build();
+        Message message = MessageBuilder.create()
+                .setBody(multipart)
+                .build();
+        MessageContent actual = testee.extract(message);
+        assertThat(actual.getTextBody()).isEmpty();
+        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);
+    }
+
+    @Test
+    public void extractShouldReturnHtmlWhenMultipartRelated() throws IOException {
+        Multipart multipart = MultipartBuilder.create("related")
+                .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 extractShouldReturnHtmlAndTextWhenMultipartAlternativeAndFirstPartIsMultipartRelated() throws IOException {
+        BodyPart multipartRelated = BodyPartBuilder.create()
+            .setBody(MultipartBuilder.create("related")
+                    .addBodyPart(htmlPart)
+                    .build())
+            .build();
+        Multipart multipartAlternative = MultipartBuilder.create("alternative")
+                .addBodyPart(multipartRelated)
+                .build();
+        Message message = MessageBuilder.create()
+                .setBody(multipartAlternative)
+                .build();
+        MessageContent actual = testee.extract(message);
+        assertThat(actual.getHtmlBody()).contains(HTML_CONTENT);
+    }
+
+    @Test
+    public void extractShouldRetrieveHtmlBodyWithOneInlinedHTMLAttachmentWithoutCid() throws IOException {
+        //Given
+        BodyPart inlinedHTMLPart = BodyPartBuilder.create()
+            .setBody(HTML_CONTENT, "html", Charsets.UTF_8)
+            .build();
+        HeaderImpl inlinedHeader = new HeaderImpl();
+        inlinedHeader.addField(Fields.contentDisposition(MimeMessage.INLINE));
+        inlinedHeader.addField(Fields.contentType("text/html; charset=utf-8"));
+        inlinedHTMLPart.setHeader(inlinedHeader);
+        Multipart multipartAlternative = MultipartBuilder.create("alternative")
+            .addBodyPart(inlinedHTMLPart)
+            .build();
+        Message message = MessageBuilder.create()
+            .setBody(multipartAlternative)
+            .build();
+
+        //When
+        MessageContent actual = testee.extract(message);
+
+        //Then
+        assertThat(actual.getHtmlBody()).contains(HTML_CONTENT);
+    }
+
+    @Test
+    public void extractShouldNotRetrieveHtmlBodyWithOneInlinedHTMLAttachmentWithCid() throws IOException {
+        //Given
+        BodyPart inlinedHTMLPart = BodyPartBuilder.create()
+            .setBody(HTML_CONTENT, "html", Charsets.UTF_8)
+            .build();
+        HeaderImpl inlinedHeader = new HeaderImpl();
+        inlinedHeader.addField(Fields.contentDisposition(MimeMessage.INLINE));
+        inlinedHeader.addField(Fields.contentType("text/html; charset=utf-8"));
+        inlinedHeader.addField(CONTENT_ID_FIELD);
+        inlinedHTMLPart.setHeader(inlinedHeader);
+        Multipart multipartAlternative = MultipartBuilder.create("alternative")
+            .addBodyPart(inlinedHTMLPart)
+            .build();
+        Message message = MessageBuilder.create()
+            .setBody(multipartAlternative)
+            .build();
+
+        //When
+        MessageContent actual = testee.extract(message);
+
+        //Then
+        assertThat(actual.getHtmlBody()).isEmpty();
+    }
+
+
+    @Test
+    public void extractShouldRetrieveTextBodyWithOneInlinedTextAttachmentWithoutCid() throws IOException {
+        //Given
+        BodyPart inlinedTextPart = BodyPartBuilder.create()
+            .setBody(TEXT_CONTENT, "text", Charsets.UTF_8)
+            .build();
+        HeaderImpl inlinedHeader = new HeaderImpl();
+        inlinedHeader.addField(Fields.contentDisposition(MimeMessage.INLINE));
+        inlinedHeader.addField(Fields.contentType("text/plain; charset=utf-8"));
+        inlinedTextPart.setHeader(inlinedHeader);
+        Multipart multipartAlternative = MultipartBuilder.create("alternative")
+            .addBodyPart(inlinedTextPart)
+            .build();
+        Message message = MessageBuilder.create()
+            .setBody(multipartAlternative)
+            .build();
+
+        //When
+        MessageContent actual = testee.extract(message);
+
+        //Then
+        assertThat(actual.getTextBody()).contains(TEXT_CONTENT);
+    }
+
+    @Test
+    public void extractShouldNotRetrieveTextBodyWithOneInlinedTextAttachmentWithCid() throws IOException {
+        //Given
+        BodyPart inlinedTextPart = BodyPartBuilder.create()
+            .setBody(TEXT_CONTENT, "text", Charsets.UTF_8)
+            .build();
+        HeaderImpl inlinedHeader = new HeaderImpl();
+        inlinedHeader.addField(Fields.contentDisposition(MimeMessage.INLINE));
+        inlinedHeader.addField(Fields.contentType("text/plain; charset=utf-8"));
+        inlinedHeader.addField(CONTENT_ID_FIELD);
+        inlinedTextPart.setHeader(inlinedHeader);
+        Multipart multipartAlternative = MultipartBuilder.create("alternative")
+            .addBodyPart(inlinedTextPart)
+            .build();
+        Message message = MessageBuilder.create()
+            .setBody(multipartAlternative)
+            .build();
+
+        //When
+        MessageContent actual = testee.extract(message);
+
+        //Then
+        assertThat(actual.getTextBody()).isEmpty();
+    }
+
+    @Test
+    public void extractShouldRetrieveTextAndHtmlBodyWhenOneInlinedTextAttachmentAndMainContentInMultipart() throws IOException {
+        BodyPart multipartAlternative = BodyPartBuilder.create()
+                .setBody(MultipartBuilder.create("alternative")
+                        .addBodyPart(textPart)
+                        .addBodyPart(htmlPart)
+                        .build())
+                .build();
+
+        Multipart multipartMixed = MultipartBuilder.create("mixed")
+                .addBodyPart(multipartAlternative)
+                .addBodyPart(inlineText)
+                .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);
+    }
+
+    @Test
+    public void extractShouldRetrieveTextBodyAndHtmlBodyWhenTextBodyInMainMultipartAndHtmlBodyInInnerMultipart() throws IOException {
+        BodyPart multipartRelated = BodyPartBuilder.create()
+                .setBody(MultipartBuilder.create("related")
+                        .addBodyPart(htmlPart)
+                        .addBodyPart(inlineImage)
+                        .build())
+                .build();
+
+        Multipart multipartAlternative = MultipartBuilder.create("alternative")
+                .addBodyPart(textPart)
+                .addBodyPart(multipartRelated)
+                .build();
+
+        Message message = MessageBuilder.create()
+                .setBody(multipartAlternative)
+                .build();
+
+        MessageContent actual = testee.extract(message);
+        assertThat(actual.getTextBody()).contains(TEXT_CONTENT);
+        assertThat(actual.getHtmlBody()).contains(HTML_CONTENT);
+    }
+
+    @Test
+    public void mergeMessageContentShouldReturnEmptyWhenAllEmpty() {
+        MessageContent messageContent1 = MessageContent.empty();
+        MessageContent messageContent2 = MessageContent.empty();
+        MessageContent expected = MessageContent.empty();
+
+        MessageContent actual = messageContent1.merge(messageContent2);
+
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void mergeMessageContentShouldReturnFirstWhenSecondEmpty() {
+        MessageContent messageContent1 = new MessageContent(Optional.of(TEXT_CONTENT), Optional.of(HTML_CONTENT));
+        MessageContent messageContent2 = MessageContent.empty();
+        MessageContent expected = messageContent1;
+
+        MessageContent actual = messageContent1.merge(messageContent2);
+
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void mergeMessageContentShouldReturnSecondWhenFirstEmpty() {
+        MessageContent messageContent1 = MessageContent.empty();
+        MessageContent messageContent2 = new MessageContent(Optional.of(TEXT_CONTENT), Optional.of(HTML_CONTENT));
+        MessageContent expected = messageContent2;
+
+        MessageContent actual = messageContent1.merge(messageContent2);
+
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void mergeMessageContentShouldReturnMixWhenFirstTextOnlyAndSecondHtmlOnly() {
+        MessageContent messageContent1 = MessageContent.ofTextOnly(Optional.of(TEXT_CONTENT));
+        MessageContent messageContent2 = MessageContent.ofHtmlOnly(Optional.of(HTML_CONTENT));
+        MessageContent expected = new MessageContent(Optional.of(TEXT_CONTENT), Optional.of(HTML_CONTENT));
+
+        MessageContent actual = messageContent1.merge(messageContent2);
+
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void mergeMessageContentShouldReturnMixWhenFirstHtmlOnlyAndSecondTextOnly() {
+        MessageContent messageContent1 = MessageContent.ofHtmlOnly(Optional.of(HTML_CONTENT));
+        MessageContent messageContent2 = MessageContent.ofTextOnly(Optional.of(TEXT_CONTENT));
+        MessageContent expected = new MessageContent(Optional.of(TEXT_CONTENT), Optional.of(HTML_CONTENT));
+
+        MessageContent actual = messageContent1.merge(messageContent2);
+
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void mergeMessageContentShouldReturnFirstWhenTwiceAreComplete() {
+        MessageContent messageContent1 = new MessageContent(Optional.of(TEXT_CONTENT), Optional.of(HTML_CONTENT));
+        MessageContent messageContent2 = new MessageContent(Optional.of(TEXT_CONTENT2), Optional.of(HTML_CONTENT2));
+        MessageContent expected = messageContent1;
+
+        MessageContent actual = messageContent1.merge(messageContent2);
+
+        assertThat(actual).isEqualTo(expected);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/e1c486e2/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
deleted file mode 100644
index 306f8c3..0000000
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageContentExtractor.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one   *
- * or more contributor license agreements.  See the NOTICE file *
- * distributed with this work for additional information        *
- * regarding copyright ownership.  The ASF licenses this file   *
- * to you under the Apache License, Version 2.0 (the            *
- * "License"); you may not use this file except in compliance   *
- * with the License.  You may obtain a copy of the License at   *
- *                                                              *
- *   http://www.apache.org/licenses/LICENSE-2.0                 *
- *                                                              *
- * Unless required by applicable law or agreed to in writing,   *
- * software distributed under the License is distributed on an  *
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
- * KIND, either express or implied.  See the License for the    *
- * specific language governing permissions and limitations      *
- * under the License.                                           *
- ****************************************************************/
-
-package org.apache.james.jmap.model;
-
-import java.io.IOException;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.function.Function;
-import java.util.function.Predicate;
-import java.util.stream.Stream;
-
-import javax.mail.internet.MimeMessage;
-
-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;
-import com.github.fge.lambdas.functions.ThrowingFunction;
-import com.google.common.base.Charsets;
-
-public class MessageContentExtractor {
-
-    public static final String CONTENT_ID = "Content-ID";
-    public static final String MULTIPART_ALTERNATIVE = "multipart/alternative";
-    public static final String TEXT_HTML = "text/html";
-    public static final String TEXT_PLAIN = "text/plain";
-
-    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 {
-        Optional<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 {
-        MessageContent messageContent = parseMultipartContent(entity, multipart);
-        if (!messageContent.isEmpty()) {
-            return messageContent;
-        }
-        return parseFirstFoundMultipart(multipart);
-    }
-
-    private MessageContent parseMultipartContent(Entity entity, Multipart multipart) throws IOException {
-        switch(entity.getMimeType()) {
-        case MULTIPART_ALTERNATIVE:
-            return retrieveHtmlAndPlainTextContent(multipart);
-        default:
-            return retrieveFirstReadablePart(multipart);
-        }
-    }
-
-    private MessageContent parseFirstFoundMultipart(Multipart multipart) throws IOException {
-        ThrowingFunction<Entity, MessageContent> parseMultipart = firstPart -> parseMultipart(firstPart, (Multipart)firstPart.getBody());
-        return multipart.getBodyParts()
-            .stream()
-            .filter(part -> part.getBody() instanceof Multipart)
-            .findFirst()
-            .map(Throwing.function(parseMultipart).sneakyThrow())
-            .orElse(MessageContent.empty());
-    }
-
-    private Optional<String> asString(TextBody textBody) throws IOException {
-        return Optional.ofNullable(IOUtils.toString(textBody.getInputStream(), Charsets.UTF_8));
-    }
-
-    private MessageContent retrieveHtmlAndPlainTextContent(Multipart multipart) throws IOException {
-        Optional<String> textBody = getFirstMatchingTextBody(multipart, TEXT_PLAIN);
-        Optional<String> htmlBody = getFirstMatchingTextBody(multipart, TEXT_HTML);
-        MessageContent directChildTextBodies = new MessageContent(textBody, htmlBody);
-        if (!directChildTextBodies.isComplete()) {
-            MessageContent fromInnerMultipart = parseFirstFoundMultipart(multipart);
-            return directChildTextBodies.merge(fromInnerMultipart);
-        }
-        return directChildTextBodies;
-    }
-
-    private MessageContent retrieveFirstReadablePart(Multipart multipart) throws IOException {
-        return retrieveFirstReadablePartMatching(multipart, this::isNotAttachment)
-            .orElseGet(() -> retrieveFirstReadablePartMatching(multipart, this::isInlinedWithoutCid)
-                .orElse(MessageContent.empty()));
-    }
-
-    private Optional<MessageContent> retrieveFirstReadablePartMatching(Multipart multipart, Predicate<Entity> predicate) {
-        return multipart.getBodyParts()
-            .stream()
-            .filter(predicate)
-            .flatMap(Throwing.function(this::extractContentIfReadable).sneakyThrow())
-            .findFirst();
-    }
-
-    private Stream<MessageContent> extractContentIfReadable(Entity entity) throws IOException {
-        if (TEXT_HTML.equals(entity.getMimeType()) && entity.getBody() instanceof TextBody) {
-            return Stream.of(
-                    MessageContent.ofHtmlOnly(asString((TextBody)entity.getBody())));
-        }
-        if (TEXT_PLAIN.equals(entity.getMimeType()) && entity.getBody() instanceof TextBody) {
-            return Stream.of(
-                    MessageContent.ofTextOnly(asString((TextBody)entity.getBody())));
-        }
-        if (entity.isMultipart() && entity.getBody() instanceof Multipart) {
-            MessageContent innerMultipartContent = parseMultipart(entity, (Multipart)entity.getBody());
-            if (!innerMultipartContent.isEmpty()) {
-                return Stream.of(innerMultipartContent);
-            }
-        }
-        return Stream.empty();
-    }
-
-    private Optional<String> getFirstMatchingTextBody(Multipart multipart, String mimeType) throws IOException {
-        Optional<String> firstMatchingTextBody = getFirstMatchingTextBody(multipart, mimeType, this::isNotAttachment);
-        if (firstMatchingTextBody.isPresent()) {
-            return firstMatchingTextBody;
-        }
-        Optional<String> fallBackInlinedBodyWithoutCid = getFirstMatchingTextBody(multipart, mimeType, this::isInlinedWithoutCid);
-        return fallBackInlinedBodyWithoutCid;
-    }
-
-    private Optional<String> getFirstMatchingTextBody(Multipart multipart, String mimeType, Predicate<Entity> condition) {
-        Function<TextBody, Optional<String>> textBodyOptionalFunction = Throwing
-            .<TextBody, Optional<String>>function(textBody ->  asString(textBody)).sneakyThrow();
-
-        return multipart.getBodyParts()
-            .stream()
-            .filter(part -> mimeType.equals(part.getMimeType()))
-            .filter(condition)
-            .map(Entity::getBody)
-            .filter(TextBody.class::isInstance)
-            .map(TextBody.class::cast)
-            .findFirst()
-            .flatMap(textBodyOptionalFunction);
-    }
-
-    private boolean isNotAttachment(Entity part) {
-        return part.getDispositionType() == null;
-    }
-
-    private boolean isInlinedWithoutCid(Entity part) {
-        return part.getDispositionType().equals(MimeMessage.INLINE) && part.getHeader().getField(CONTENT_ID) == null;
-    }
-
-    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(Optional<String> textBody) {
-            return new MessageContent(textBody, Optional.empty());
-        }
-
-        public static MessageContent ofHtmlOnly(Optional<String> htmlBody) {
-            return new MessageContent(Optional.empty(), htmlBody);
-        }
-
-        public static MessageContent empty() {
-            return new MessageContent(Optional.empty(), Optional.empty());
-        }
-        
-        public Optional<String> getTextBody() {
-            return textBody;
-        }
-
-        public Optional<String> getHtmlBody() {
-            return htmlBody;
-        }
-        
-        public boolean isEmpty() {
-            return equals(empty());
-        }
-
-        public boolean isComplete() {
-            return textBody.isPresent() && htmlBody.isPresent();
-        }
-
-        public MessageContent merge(MessageContent fromInnerMultipart) {
-            return new MessageContent(
-                    textBody.map(Optional::of).orElse(fromInnerMultipart.getTextBody()),
-                    htmlBody.map(Optional::of).orElse(fromInnerMultipart.getHtmlBody()));
-        }
-
-        @Override
-        public boolean equals(Object other) {
-            if (other == null || !(other instanceof MessageContent)) {
-                return false;
-            }
-            MessageContent otherMessageContent = (MessageContent)other;
-            return Objects.equals(this.textBody, otherMessageContent.textBody)
-                    && Objects.equals(this.htmlBody, otherMessageContent.htmlBody);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/james-project/blob/e1c486e2/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 53c9d7f..e7b1f2c 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
@@ -37,7 +37,6 @@ import javax.inject.Inject;
 import javax.mail.Flags;
 import javax.mail.internet.SharedInputStream;
 
-import org.apache.james.jmap.model.MessageContentExtractor.MessageContent;
 import org.apache.james.jmap.utils.HtmlTextExtractor;
 import org.apache.james.mailbox.MessageUid;
 import org.apache.james.mailbox.exception.MailboxException;
@@ -52,6 +51,8 @@ import org.apache.james.mime4j.dom.address.MailboxList;
 import org.apache.james.mime4j.message.MessageBuilder;
 import org.apache.james.mime4j.stream.Field;
 import org.apache.james.mime4j.stream.MimeConfig;
+import org.apache.james.util.mime.MessageContentExtractor;
+import org.apache.james.util.mime.MessageContentExtractor.MessageContent;
 
 import com.github.steveash.guavate.Guavate;
 import com.google.common.base.Preconditions;

http://git-wip-us.apache.org/repos/asf/james-project/blob/e1c486e2/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 15873ee..db400db 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,7 +37,6 @@ 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.MessagePreviewGenerator;
 import org.apache.james.jmap.model.MessageProperties.MessageProperty;
@@ -57,6 +56,7 @@ import org.apache.james.mailbox.model.MailboxId;
 import org.apache.james.mailbox.model.MailboxPath;
 import org.apache.james.mailbox.tika.extractor.TikaTextExtractor;
 import org.apache.james.metrics.logger.DefaultMetricFactory;
+import org.apache.james.util.mime.MessageContentExtractor;
 import org.assertj.core.api.Condition;
 import org.assertj.core.data.MapEntry;
 import org.assertj.core.groups.Tuple;

http://git-wip-us.apache.org/repos/asf/james-project/blob/e1c486e2/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 6a0dd63..7a36140 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
@@ -42,7 +42,6 @@ import org.apache.james.jmap.model.BlobId;
 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.MessageContentExtractor;
 import org.apache.james.jmap.model.MessageFactory;
 import org.apache.james.jmap.model.MessagePreviewGenerator;
 import org.apache.james.jmap.model.MessageProperties.MessageProperty;
@@ -68,6 +67,7 @@ import org.apache.james.mailbox.model.ComposedMessageId;
 import org.apache.james.mailbox.model.MailboxPath;
 import org.apache.james.mailbox.model.TestMessageId;
 import org.apache.james.metrics.api.NoopMetricFactory;
+import org.apache.james.util.mime.MessageContentExtractor;
 import org.apache.mailet.Mail;
 import org.junit.Before;
 import org.junit.Ignore;

http://git-wip-us.apache.org/repos/asf/james-project/blob/e1c486e2/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
deleted file mode 100644
index f40b94f..0000000
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageContentExtractorTest.java
+++ /dev/null
@@ -1,490 +0,0 @@
-/****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one   *
- * or more contributor license agreements.  See the NOTICE file *
- * distributed with this work for additional information        *
- * regarding copyright ownership.  The ASF licenses this file   *
- * to you under the Apache License, Version 2.0 (the            *
- * "License"); you may not use this file except in compliance   *
- * with the License.  You may obtain a copy of the License at   *
- *                                                              *
- *   http://www.apache.org/licenses/LICENSE-2.0                 *
- *                                                              *
- * Unless required by applicable law or agreed to in writing,   *
- * software distributed under the License is distributed on an  *
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
- * KIND, either express or implied.  See the License for the    *
- * specific language governing permissions and limitations      *
- * under the License.                                           *
- ****************************************************************/
-package org.apache.james.jmap.model;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import java.io.IOException;
-import java.util.Optional;
-
-import javax.mail.internet.MimeMessage;
-
-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.field.Fields;
-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.HeaderImpl;
-import org.apache.james.mime4j.message.MessageBuilder;
-import org.apache.james.mime4j.message.MultipartBuilder;
-import org.apache.james.mime4j.stream.Field;
-import org.apache.james.mime4j.util.ByteSequence;
-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 static final String TEXT_CONTENT2 = "other text content";
-    private static final String HTML_CONTENT2 = "other <b>html</b> content";
-    private static final String ATTACHMENT_CONTENT = "attachment content";
-    private static final String ANY_VALUE = "anyValue";
-    private static final Field CONTENT_ID_FIELD = new Field() {
-        @Override
-        public String getName() {
-            return MessageContentExtractor.CONTENT_ID;
-        }
-
-        @Override
-        public String getBody() {
-            return ANY_VALUE;
-        }
-
-        @Override
-        public ByteSequence getRaw() {
-            return ByteSequence.EMPTY;
-        }
-    };
-
-    private MessageContentExtractor testee;
-
-    private BodyPart htmlPart;
-    private BodyPart textPart;
-    private BodyPart textAttachment;
-    private BodyPart inlineText;
-    private BodyPart inlineImage;
-
-    @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();
-        textAttachment = BodyPartBuilder.create()
-                .setBody(ATTACHMENT_CONTENT, "plain", Charsets.UTF_8)
-                .setContentDisposition("attachment")
-                .build();
-        inlineText = BodyPartBuilder.create()
-                .setBody(ATTACHMENT_CONTENT, "plain", Charsets.UTF_8)
-                .setContentDisposition("inline")
-                .build();
-        inlineImage = BodyPartBuilder.create()
-                .setBody(new byte[0], "image/png")
-                .setContentDisposition("inline")
-                .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 extractShouldReturnFirstNonAttachmentPartWhenMultipartMixed() throws IOException {
-        Multipart multipart = MultipartBuilder.create("mixed")
-                .addBodyPart(textAttachment)
-                .addBodyPart(htmlPart)
-                .addBodyPart(textPart)
-                .build();
-        Message message = MessageBuilder.create()
-                .setBody(multipart)
-                .build();
-        MessageContent actual = testee.extract(message);
-        assertThat(actual.getHtmlBody()).contains(HTML_CONTENT);
-        assertThat(actual.getTextBody()).isEmpty();
-    }
-
-    @Test
-    public void extractShouldReturnInlinedTextBodyWithoutCIDWhenNoOtherValidParts() throws IOException {
-        String textBody = "body 1";
-        Multipart multipart = MultipartBuilder.create("report")
-            .addBodyPart(BodyPartBuilder.create()
-                .setBody(textBody, "plain", Charsets.UTF_8)
-                .setContentDisposition("inline")
-                .build())
-            .addBodyPart(BodyPartBuilder.create()
-                .setBody("body 2", "rfc822-headers", Charsets.UTF_8)
-                .setContentDisposition("inline")
-                .build())
-            .build();
-        Message message = MessageBuilder.create()
-            .setBody(multipart)
-            .build();
-
-        MessageContent actual = testee.extract(message);
-
-        assertThat(actual.getTextBody()).contains(textBody);
-    }
-
-    @Test
-    public void extractShouldReturnEmptyWhenMultipartMixedAndFirstPartIsATextAttachment() throws IOException {
-        Multipart multipart = MultipartBuilder.create("mixed")
-                .addBodyPart(textAttachment)
-                .build();
-        Message message = MessageBuilder.create()
-                .setBody(multipart)
-                .build();
-        MessageContent actual = testee.extract(message);
-        assertThat(actual.getTextBody()).isEmpty();
-        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);
-    }
-
-    @Test
-    public void extractShouldReturnHtmlWhenMultipartRelated() throws IOException {
-        Multipart multipart = MultipartBuilder.create("related")
-                .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 extractShouldReturnHtmlAndTextWhenMultipartAlternativeAndFirstPartIsMultipartRelated() throws IOException {
-        BodyPart multipartRelated = BodyPartBuilder.create()
-            .setBody(MultipartBuilder.create("related")
-                    .addBodyPart(htmlPart)
-                    .build())
-            .build();
-        Multipart multipartAlternative = MultipartBuilder.create("alternative")
-                .addBodyPart(multipartRelated)
-                .build();
-        Message message = MessageBuilder.create()
-                .setBody(multipartAlternative)
-                .build();
-        MessageContent actual = testee.extract(message);
-        assertThat(actual.getHtmlBody()).contains(HTML_CONTENT);
-    }
-
-    @Test
-    public void extractShouldRetrieveHtmlBodyWithOneInlinedHTMLAttachmentWithoutCid() throws IOException {
-        //Given
-        BodyPart inlinedHTMLPart = BodyPartBuilder.create()
-            .setBody(HTML_CONTENT, "html", Charsets.UTF_8)
-            .build();
-        HeaderImpl inlinedHeader = new HeaderImpl();
-        inlinedHeader.addField(Fields.contentDisposition(MimeMessage.INLINE));
-        inlinedHeader.addField(Fields.contentType("text/html; charset=utf-8"));
-        inlinedHTMLPart.setHeader(inlinedHeader);
-        Multipart multipartAlternative = MultipartBuilder.create("alternative")
-            .addBodyPart(inlinedHTMLPart)
-            .build();
-        Message message = MessageBuilder.create()
-            .setBody(multipartAlternative)
-            .build();
-
-        //When
-        MessageContent actual = testee.extract(message);
-
-        //Then
-        assertThat(actual.getHtmlBody()).contains(HTML_CONTENT);
-    }
-
-    @Test
-    public void extractShouldNotRetrieveHtmlBodyWithOneInlinedHTMLAttachmentWithCid() throws IOException {
-        //Given
-        BodyPart inlinedHTMLPart = BodyPartBuilder.create()
-            .setBody(HTML_CONTENT, "html", Charsets.UTF_8)
-            .build();
-        HeaderImpl inlinedHeader = new HeaderImpl();
-        inlinedHeader.addField(Fields.contentDisposition(MimeMessage.INLINE));
-        inlinedHeader.addField(Fields.contentType("text/html; charset=utf-8"));
-        inlinedHeader.addField(CONTENT_ID_FIELD);
-        inlinedHTMLPart.setHeader(inlinedHeader);
-        Multipart multipartAlternative = MultipartBuilder.create("alternative")
-            .addBodyPart(inlinedHTMLPart)
-            .build();
-        Message message = MessageBuilder.create()
-            .setBody(multipartAlternative)
-            .build();
-
-        //When
-        MessageContent actual = testee.extract(message);
-
-        //Then
-        assertThat(actual.getHtmlBody()).isEmpty();
-    }
-
-
-    @Test
-    public void extractShouldRetrieveTextBodyWithOneInlinedTextAttachmentWithoutCid() throws IOException {
-        //Given
-        BodyPart inlinedTextPart = BodyPartBuilder.create()
-            .setBody(TEXT_CONTENT, "text", Charsets.UTF_8)
-            .build();
-        HeaderImpl inlinedHeader = new HeaderImpl();
-        inlinedHeader.addField(Fields.contentDisposition(MimeMessage.INLINE));
-        inlinedHeader.addField(Fields.contentType("text/plain; charset=utf-8"));
-        inlinedTextPart.setHeader(inlinedHeader);
-        Multipart multipartAlternative = MultipartBuilder.create("alternative")
-            .addBodyPart(inlinedTextPart)
-            .build();
-        Message message = MessageBuilder.create()
-            .setBody(multipartAlternative)
-            .build();
-
-        //When
-        MessageContent actual = testee.extract(message);
-
-        //Then
-        assertThat(actual.getTextBody()).contains(TEXT_CONTENT);
-    }
-
-    @Test
-    public void extractShouldNotRetrieveTextBodyWithOneInlinedTextAttachmentWithCid() throws IOException {
-        //Given
-        BodyPart inlinedTextPart = BodyPartBuilder.create()
-            .setBody(TEXT_CONTENT, "text", Charsets.UTF_8)
-            .build();
-        HeaderImpl inlinedHeader = new HeaderImpl();
-        inlinedHeader.addField(Fields.contentDisposition(MimeMessage.INLINE));
-        inlinedHeader.addField(Fields.contentType("text/plain; charset=utf-8"));
-        inlinedHeader.addField(CONTENT_ID_FIELD);
-        inlinedTextPart.setHeader(inlinedHeader);
-        Multipart multipartAlternative = MultipartBuilder.create("alternative")
-            .addBodyPart(inlinedTextPart)
-            .build();
-        Message message = MessageBuilder.create()
-            .setBody(multipartAlternative)
-            .build();
-
-        //When
-        MessageContent actual = testee.extract(message);
-
-        //Then
-        assertThat(actual.getTextBody()).isEmpty();
-    }
-
-    @Test
-    public void extractShouldRetrieveTextAndHtmlBodyWhenOneInlinedTextAttachmentAndMainContentInMultipart() throws IOException {
-        BodyPart multipartAlternative = BodyPartBuilder.create()
-                .setBody(MultipartBuilder.create("alternative")
-                        .addBodyPart(textPart)
-                        .addBodyPart(htmlPart)
-                        .build())
-                .build();
-
-        Multipart multipartMixed = MultipartBuilder.create("mixed")
-                .addBodyPart(multipartAlternative)
-                .addBodyPart(inlineText)
-                .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);
-    }
-
-    @Test
-    public void extractShouldRetrieveTextBodyAndHtmlBodyWhenTextBodyInMainMultipartAndHtmlBodyInInnerMultipart() throws IOException {
-        BodyPart multipartRelated = BodyPartBuilder.create()
-                .setBody(MultipartBuilder.create("related")
-                        .addBodyPart(htmlPart)
-                        .addBodyPart(inlineImage)
-                        .build())
-                .build();
-
-        Multipart multipartAlternative = MultipartBuilder.create("alternative")
-                .addBodyPart(textPart)
-                .addBodyPart(multipartRelated)
-                .build();
-
-        Message message = MessageBuilder.create()
-                .setBody(multipartAlternative)
-                .build();
-
-        MessageContent actual = testee.extract(message);
-        assertThat(actual.getTextBody()).contains(TEXT_CONTENT);
-        assertThat(actual.getHtmlBody()).contains(HTML_CONTENT);
-    }
-
-    @Test
-    public void mergeMessageContentShouldReturnEmptyWhenAllEmpty() {
-        MessageContent messageContent1 = MessageContent.empty();
-        MessageContent messageContent2 = MessageContent.empty();
-        MessageContent expected = MessageContent.empty();
-
-        MessageContent actual = messageContent1.merge(messageContent2);
-
-        assertThat(actual).isEqualTo(expected);
-    }
-
-    @Test
-    public void mergeMessageContentShouldReturnFirstWhenSecondEmpty() {
-        MessageContent messageContent1 = new MessageContent(Optional.of(TEXT_CONTENT), Optional.of(HTML_CONTENT));
-        MessageContent messageContent2 = MessageContent.empty();
-        MessageContent expected = messageContent1;
-
-        MessageContent actual = messageContent1.merge(messageContent2);
-
-        assertThat(actual).isEqualTo(expected);
-    }
-
-    @Test
-    public void mergeMessageContentShouldReturnSecondWhenFirstEmpty() {
-        MessageContent messageContent1 = MessageContent.empty();
-        MessageContent messageContent2 = new MessageContent(Optional.of(TEXT_CONTENT), Optional.of(HTML_CONTENT));
-        MessageContent expected = messageContent2;
-
-        MessageContent actual = messageContent1.merge(messageContent2);
-
-        assertThat(actual).isEqualTo(expected);
-    }
-
-    @Test
-    public void mergeMessageContentShouldReturnMixWhenFirstTextOnlyAndSecondHtmlOnly() {
-        MessageContent messageContent1 = MessageContent.ofTextOnly(Optional.of(TEXT_CONTENT));
-        MessageContent messageContent2 = MessageContent.ofHtmlOnly(Optional.of(HTML_CONTENT));
-        MessageContent expected = new MessageContent(Optional.of(TEXT_CONTENT), Optional.of(HTML_CONTENT));
-
-        MessageContent actual = messageContent1.merge(messageContent2);
-
-        assertThat(actual).isEqualTo(expected);
-    }
-
-    @Test
-    public void mergeMessageContentShouldReturnMixWhenFirstHtmlOnlyAndSecondTextOnly() {
-        MessageContent messageContent1 = MessageContent.ofHtmlOnly(Optional.of(HTML_CONTENT));
-        MessageContent messageContent2 = MessageContent.ofTextOnly(Optional.of(TEXT_CONTENT));
-        MessageContent expected = new MessageContent(Optional.of(TEXT_CONTENT), Optional.of(HTML_CONTENT));
-
-        MessageContent actual = messageContent1.merge(messageContent2);
-
-        assertThat(actual).isEqualTo(expected);
-    }
-
-    @Test
-    public void mergeMessageContentShouldReturnFirstWhenTwiceAreComplete() {
-        MessageContent messageContent1 = new MessageContent(Optional.of(TEXT_CONTENT), Optional.of(HTML_CONTENT));
-        MessageContent messageContent2 = new MessageContent(Optional.of(TEXT_CONTENT2), Optional.of(HTML_CONTENT2));
-        MessageContent expected = messageContent1;
-
-        MessageContent actual = messageContent1.merge(messageContent2);
-
-        assertThat(actual).isEqualTo(expected);
-    }
-}

http://git-wip-us.apache.org/repos/asf/james-project/blob/e1c486e2/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageFactoryTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageFactoryTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageFactoryTest.java
index 1e0130c..af0abd2 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageFactoryTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessageFactoryTest.java
@@ -41,6 +41,7 @@ import org.apache.james.mailbox.model.Cid;
 import org.apache.james.mailbox.model.MessageAttachment;
 import org.apache.james.mailbox.model.TestMessageId;
 import org.apache.james.mailbox.tika.extractor.TikaTextExtractor;
+import org.apache.james.util.mime.MessageContentExtractor;
 import org.junit.Before;
 import org.junit.Test;
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/e1c486e2/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 c45574c..12a7ebc 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
@@ -31,7 +31,6 @@ 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.MessageFactory.MetaDataWithContent;
 import org.apache.james.jmap.model.MessagePreviewGenerator;
@@ -41,6 +40,7 @@ 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.TestMessageId;
+import org.apache.james.util.mime.MessageContentExtractor;
 import org.apache.mailet.Mail;
 import org.apache.mailet.MailAddress;
 import org.junit.Before;


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


Mime
View raw message