james-server-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From btell...@apache.org
Subject [1/6] james-project git commit: JAMES-1717 Provide message composition and text extraction as separate service and rely on Mailbox Text extractor
Date Fri, 03 Jun 2016 12:38:33 GMT
Repository: james-project
Updated Branches:
  refs/heads/master bfe79736e -> 4dc1d61b5


JAMES-1717 Provide message composition and text extraction as separate service and rely on
Mailbox Text extractor


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

Branch: refs/heads/master
Commit: 4dc1d61b5e51472541abbc6cf37183abe4e19cf7
Parents: 5f4f804
Author: Benoit Tellier <btellier@linagora.com>
Authored: Thu Jun 2 18:25:58 2016 +0700
Committer: Benoit Tellier <btellier@linagora.com>
Committed: Fri Jun 3 19:38:15 2016 +0700

----------------------------------------------------------------------
 .../java/org/apache/james/jmap/JMAPModule.java  |   5 +
 .../james/jmap/MailetPreconditionTest.java      |   4 +-
 .../modules/data/MemoryDataJmapModule.java      |   5 +
 server/protocols/jmap/pom.xml                   |  10 +-
 .../james/jmap/mailet/VacationMailet.java       |   8 +-
 .../apache/james/jmap/mailet/VacationReply.java |  57 +-------
 .../james/jmap/utils/HtmlTextExtractor.java     |  26 ++++
 .../utils/MailboxBasedHtmlTextExtractor.java    |  33 +++++
 .../jmap/utils/MimeMessageBodyGenerator.java    |  90 ++++++++++++
 .../james/jmap/mailet/VacationMailetTest.java   |   5 +-
 .../james/jmap/mailet/VacationReplyTest.java    |  91 ++++++------
 .../MailboxBasedHtmlTextExtractorTest.java      | 110 +++++++++++++++
 .../utils/MimeMessageBodyGeneratorTest.java     | 140 +++++++++++++++++++
 .../jmap/src/test/resources/example.html        |  21 +++
 14 files changed, 501 insertions(+), 104 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/4dc1d61b/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPModule.java
----------------------------------------------------------------------
diff --git a/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPModule.java
b/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPModule.java
index 0744b49..40ff85f 100644
--- a/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPModule.java
+++ b/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPModule.java
@@ -29,6 +29,8 @@ import org.apache.commons.io.FileUtils;
 import org.apache.james.filesystem.api.FileSystem;
 import org.apache.james.jmap.mailet.VacationMailet;
 import org.apache.james.jmap.methods.RequestHandler;
+import org.apache.james.jmap.utils.HtmlTextExtractor;
+import org.apache.james.jmap.utils.MailboxBasedHtmlTextExtractor;
 import org.apache.james.lifecycle.api.Configurable;
 import org.apache.james.mailbox.MailboxManager;
 import org.apache.james.mailetcontainer.impl.MatcherMailetPair;
@@ -57,6 +59,9 @@ public class JMAPModule extends AbstractModule {
         install(new MethodsModule());
         bind(JMAPServer.class).in(Scopes.SINGLETON);
         bind(RequestHandler.class).in(Scopes.SINGLETON);
+        bind(MailboxBasedHtmlTextExtractor.class).in(Scopes.SINGLETON);
+
+        bind(HtmlTextExtractor.class).to(MailboxBasedHtmlTextExtractor.class);
         Multibinder.newSetBinder(binder(), ConfigurationPerformer.class).addBinding().to(MoveCapabilityPrecondition.class);
 
         Multibinder<CamelMailetContainerModule.TransportProcessorCheck> transportProcessorChecks
= Multibinder.newSetBinder(binder(), CamelMailetContainerModule.TransportProcessorCheck.class);

http://git-wip-us.apache.org/repos/asf/james-project/blob/4dc1d61b/server/container/guice/guice-common/src/test/java/org/apache/james/jmap/MailetPreconditionTest.java
----------------------------------------------------------------------
diff --git a/server/container/guice/guice-common/src/test/java/org/apache/james/jmap/MailetPreconditionTest.java
b/server/container/guice/guice-common/src/test/java/org/apache/james/jmap/MailetPreconditionTest.java
index ecfa619..21245c3 100644
--- a/server/container/guice/guice-common/src/test/java/org/apache/james/jmap/MailetPreconditionTest.java
+++ b/server/container/guice/guice-common/src/test/java/org/apache/james/jmap/MailetPreconditionTest.java
@@ -53,7 +53,7 @@ public class MailetPreconditionTest {
 
     @Test(expected = ConfigurationException.class)
     public void vacationMailetCheckShouldThrowOnWrongMatcher() throws Exception {
-        List<MatcherMailetPair> pairs = Lists.newArrayList(new MatcherMailetPair(new
All(), new VacationMailet(null, null, null, null)));
+        List<MatcherMailetPair> pairs = Lists.newArrayList(new MatcherMailetPair(new
All(), new VacationMailet(null, null, null, null, null)));
         new JMAPModule.VacationMailetCheck().check(pairs);
     }
 
@@ -65,7 +65,7 @@ public class MailetPreconditionTest {
 
     @Test
     public void vacationMailetCheckShouldNotThrowIfValidPairPresent() throws Exception {
-        List<MatcherMailetPair> pairs = Lists.newArrayList(new MatcherMailetPair(new
RecipientIsLocal(), new VacationMailet(null, null, null, null)));
+        List<MatcherMailetPair> pairs = Lists.newArrayList(new MatcherMailetPair(new
RecipientIsLocal(), new VacationMailet(null, null, null, null, null)));
         new JMAPModule.VacationMailetCheck().check(pairs);
     }
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/4dc1d61b/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/data/MemoryDataJmapModule.java
----------------------------------------------------------------------
diff --git a/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/data/MemoryDataJmapModule.java
b/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/data/MemoryDataJmapModule.java
index 737328e..02924b1 100644
--- a/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/data/MemoryDataJmapModule.java
+++ b/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/data/MemoryDataJmapModule.java
@@ -25,6 +25,8 @@ import org.apache.james.jmap.api.vacation.VacationRepository;
 import org.apache.james.jmap.memory.access.MemoryAccessTokenRepository;
 import org.apache.james.jmap.memory.vacation.MemoryNotificationRegistry;
 import org.apache.james.jmap.memory.vacation.MemoryVacationRepository;
+import org.apache.james.mailbox.store.extractor.TextExtractor;
+import org.apache.james.mailbox.tika.extractor.TikaTextExtractor;
 
 import com.google.inject.AbstractModule;
 import com.google.inject.Scopes;
@@ -41,5 +43,8 @@ public class MemoryDataJmapModule extends AbstractModule {
 
         bind(MemoryNotificationRegistry.class).in(Scopes.SINGLETON);
         bind(NotificationRegistry.class).to(MemoryNotificationRegistry.class);
+
+        bind(TikaTextExtractor.class).in(Scopes.SINGLETON);
+        bind(TextExtractor.class).to(TikaTextExtractor.class);
     }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/4dc1d61b/server/protocols/jmap/pom.xml
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/pom.xml b/server/protocols/jmap/pom.xml
index d800fa6..8851da0 100644
--- a/server/protocols/jmap/pom.xml
+++ b/server/protocols/jmap/pom.xml
@@ -179,6 +179,11 @@
                 </dependency>
                 <dependency>
                     <groupId>org.apache.james</groupId>
+                    <artifactId>apache-james-mailbox-tika</artifactId>
+                    <scope>test</scope>
+                </dependency>
+                <dependency>
+                    <groupId>org.apache.james</groupId>
                     <artifactId>apache-mailet-base</artifactId>
                     <scope>test</scope>
                     <type>test-jar</type>
@@ -274,11 +279,6 @@
                     <artifactId>bcpkix-jdk15on</artifactId>
                 </dependency>
                 <dependency>
-                    <groupId>org.jsoup</groupId>
-                    <artifactId>jsoup</artifactId>
-                    <version>1.9.2</version>
-                </dependency>
-                <dependency>
                     <groupId>org.apache.james</groupId>
                     <artifactId>apache-james-mailbox-store</artifactId>
                     <scope>test</scope>

http://git-wip-us.apache.org/repos/asf/james-project/blob/4dc1d61b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/VacationMailet.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/VacationMailet.java
b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/VacationMailet.java
index c5ae96a..8d0ef5c 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/VacationMailet.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/VacationMailet.java
@@ -30,6 +30,7 @@ import org.apache.james.jmap.api.vacation.NotificationRegistry;
 import org.apache.james.jmap.api.vacation.RecipientId;
 import org.apache.james.jmap.api.vacation.Vacation;
 import org.apache.james.jmap.api.vacation.VacationRepository;
+import org.apache.james.jmap.utils.MimeMessageBodyGenerator;
 import org.apache.james.util.date.ZonedDateTimeProvider;
 import org.apache.mailet.Mail;
 import org.apache.mailet.MailAddress;
@@ -46,14 +47,17 @@ public class VacationMailet extends GenericMailet {
     private final ZonedDateTimeProvider zonedDateTimeProvider;
     private final AutomaticallySentMailDetector automaticallySentMailDetector;
     private final NotificationRegistry notificationRegistry;
+    private final MimeMessageBodyGenerator mimeMessageBodyGenerator;
 
     @Inject
     public VacationMailet(VacationRepository vacationRepository, ZonedDateTimeProvider zonedDateTimeProvider,
-                          AutomaticallySentMailDetector automaticallySentMailDetector, NotificationRegistry
notificationRegistry) {
+                          AutomaticallySentMailDetector automaticallySentMailDetector, NotificationRegistry
notificationRegistry,
+                          MimeMessageBodyGenerator mimeMessageBodyGenerator) {
         this.vacationRepository = vacationRepository;
         this.zonedDateTimeProvider = zonedDateTimeProvider;
         this.automaticallySentMailDetector = automaticallySentMailDetector;
         this.notificationRegistry = notificationRegistry;
+        this.mimeMessageBodyGenerator = mimeMessageBodyGenerator;
     }
 
     @Override
@@ -95,7 +99,7 @@ public class VacationMailet extends GenericMailet {
             VacationReply vacationReply = VacationReply.builder(processedMail)
                 .receivedMailRecipient(recipient)
                 .vacation(vacation)
-                .build();
+                .build(mimeMessageBodyGenerator);
             sendNotification(vacationReply);
             notificationRegistry.register(AccountId.fromString(recipient.toString()),
                 RecipientId.fromMailAddress(processedMail.getSender()),

http://git-wip-us.apache.org/repos/asf/james-project/blob/4dc1d61b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/VacationReply.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/VacationReply.java
b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/VacationReply.java
index a848759..9ad8e0f 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/VacationReply.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/mailet/VacationReply.java
@@ -19,23 +19,16 @@
 
 package org.apache.james.jmap.mailet;
 
-import java.io.IOException;
 import java.util.List;
 
-import javax.activation.DataHandler;
 import javax.mail.MessagingException;
-import javax.mail.Multipart;
-import javax.mail.internet.MimeBodyPart;
 import javax.mail.internet.MimeMessage;
-import javax.mail.internet.MimeMultipart;
-import javax.mail.util.ByteArrayDataSource;
 
 import org.apache.james.jmap.api.vacation.Vacation;
-import org.apache.james.mime4j.dom.field.ContentTypeField;
+import org.apache.james.jmap.utils.MimeMessageBodyGenerator;
 import org.apache.mailet.Mail;
 import org.apache.mailet.MailAddress;
 import org.apache.mailet.base.AutomaticallySentMailDetector;
-import org.jsoup.Jsoup;
 
 import com.github.fge.lambdas.Throwing;
 import com.google.common.base.Preconditions;
@@ -45,7 +38,6 @@ public class VacationReply {
 
     public static final String FROM_HEADER = "from";
     public static final String TO_HEADER = "to";
-    public static final String MIXED = "mixed";
 
     public static Builder builder(Mail originalMail) {
         return new Builder(originalMail);
@@ -74,60 +66,21 @@ public class VacationReply {
             return this;
         }
 
-        public VacationReply build() throws MessagingException {
+        public VacationReply build(MimeMessageBodyGenerator mimeMessageBodyGenerator) throws
MessagingException {
             Preconditions.checkState(mailRecipient != null, "Original recipient address should
not be null");
             Preconditions.checkState(originalMail.getSender() != null, "Original sender address
should not be null");
 
-            return new VacationReply(mailRecipient, ImmutableList.of(originalMail.getSender()),
generateMimeMessage());
+            return new VacationReply(mailRecipient, ImmutableList.of(originalMail.getSender()),
generateMimeMessage(mimeMessageBodyGenerator));
         }
 
-        private MimeMessage generateMimeMessage() throws MessagingException {
+        private MimeMessage generateMimeMessage(MimeMessageBodyGenerator mimeMessageBodyGenerator)
throws MessagingException {
             MimeMessage reply = (MimeMessage) originalMail.getMessage().reply(NOT_REPLY_TO_ALL);
             vacation.getSubject().ifPresent(Throwing.consumer(subjectString -> reply.setHeader("subject",
subjectString)));
             reply.setHeader(FROM_HEADER, mailRecipient.toString());
             reply.setHeader(TO_HEADER, originalMail.getSender().toString());
             reply.setHeader(AutomaticallySentMailDetector.AUTO_SUBMITTED_HEADER, AutomaticallySentMailDetector.AUTO_REPLIED_VALUE);
 
-            return addBody(reply);
-        }
-
-        @SuppressWarnings("OptionalGetWithoutIsPresent")
-        private MimeMessage addBody(MimeMessage reply) throws MessagingException {
-            if (vacation.getHtmlBody().isPresent()) {
-                reply.setContent(generateMultipart());
-            } else {
-                reply.setText(vacation.getTextBody().get());
-            }
-            return reply;
-        }
-
-        @SuppressWarnings("OptionalGetWithoutIsPresent")
-        private Multipart generateMultipart() throws MessagingException {
-            try {
-                Multipart multipart = new MimeMultipart(MIXED);
-                addTextPart(multipart, vacation.getHtmlBody().get(), "text/html");
-                addTextPart(multipart, retrievePlainTextMessage(), ContentTypeField.TYPE_TEXT_PLAIN);
-                return multipart;
-            } catch (IOException e) {
-                throw new MessagingException("Cannot read specified content", e);
-            }
-        }
-
-        @SuppressWarnings("OptionalGetWithoutIsPresent")
-        private String retrievePlainTextMessage() {
-            return vacation.getTextBody()
-                .orElseGet(() -> Jsoup.parse(vacation.getHtmlBody().get()).text());
-        }
-
-        private Multipart addTextPart(Multipart multipart, String text, String contentType)
throws MessagingException, IOException {
-            MimeBodyPart textReasonPart = new MimeBodyPart();
-            textReasonPart.setDataHandler(
-                new DataHandler(
-                    new ByteArrayDataSource(
-                        text,
-                        contentType + "; charset=UTF-8")));
-            multipart.addBodyPart(textReasonPart);
-            return multipart;
+            return mimeMessageBodyGenerator.from(reply, vacation.getTextBody(), vacation.getHtmlBody());
         }
     }
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/4dc1d61b/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/HtmlTextExtractor.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/HtmlTextExtractor.java
b/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/HtmlTextExtractor.java
new file mode 100644
index 0000000..6f9fd72
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/HtmlTextExtractor.java
@@ -0,0 +1,26 @@
+/****************************************************************
+ * 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.utils;
+
+public interface HtmlTextExtractor {
+
+    String toPlainText(String html);
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/4dc1d61b/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/MailboxBasedHtmlTextExtractor.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/MailboxBasedHtmlTextExtractor.java
b/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/MailboxBasedHtmlTextExtractor.java
new file mode 100644
index 0000000..db9f0d4
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/MailboxBasedHtmlTextExtractor.java
@@ -0,0 +1,33 @@
+
+
+package org.apache.james.jmap.utils;
+
+import java.io.ByteArrayInputStream;
+
+import javax.inject.Inject;
+
+import org.apache.james.mailbox.store.extractor.TextExtractor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class MailboxBasedHtmlTextExtractor implements HtmlTextExtractor {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(MailboxBasedHtmlTextExtractor.class);
+
+    private final TextExtractor textExtractor;
+
+    @Inject
+    public MailboxBasedHtmlTextExtractor(TextExtractor textExtractor) {
+        this.textExtractor = textExtractor;
+    }
+
+    @Override
+    public String toPlainText(String html) {
+        try {
+            return textExtractor.extractContent(new ByteArrayInputStream(html.getBytes()),
"text/html", "").getTextualContent();
+        } catch (Exception e) {
+            LOGGER.warn("Error extracting text from HTML", e);
+            return html;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/4dc1d61b/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/MimeMessageBodyGenerator.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/MimeMessageBodyGenerator.java
b/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/MimeMessageBodyGenerator.java
new file mode 100644
index 0000000..e0704ca
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/MimeMessageBodyGenerator.java
@@ -0,0 +1,90 @@
+/****************************************************************
+ O * 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.utils;
+
+import java.io.IOException;
+import java.util.Optional;
+
+import javax.activation.DataHandler;
+import javax.inject.Inject;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+import javax.mail.util.ByteArrayDataSource;
+
+import org.apache.james.mime4j.dom.field.ContentTypeField;
+
+import com.github.fge.lambdas.Throwing;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+
+public class MimeMessageBodyGenerator {
+    public static final String MIXED = "mixed";
+
+    private final HtmlTextExtractor htmlTextExtractor;
+
+    @Inject
+    @VisibleForTesting
+    MimeMessageBodyGenerator(HtmlTextExtractor htmlTextExtractor) {
+        this.htmlTextExtractor = htmlTextExtractor;
+    }
+
+    public MimeMessage from(MimeMessage messageHoldingHeaders, Optional<String> plainText,
Optional<String> htmlText) throws MessagingException {
+        Preconditions.checkNotNull(messageHoldingHeaders);
+        Preconditions.checkNotNull(plainText);
+        Preconditions.checkNotNull(htmlText);
+        Preconditions.checkState(plainText.isPresent() || htmlText.isPresent(), "MimeMessageBodyGenerator
needs at least plainText or html text");
+        if (htmlText.isPresent()) {
+            messageHoldingHeaders.setContent(generateMultipart(htmlText.get(), plainText));
+        } else {
+            plainText.ifPresent(Throwing.consumer(messageHoldingHeaders::setText));
+        }
+        return messageHoldingHeaders;
+    }
+
+    private Multipart generateMultipart(String htmlText, Optional<String> plainText)
throws MessagingException {
+        try {
+            Multipart multipart = new MimeMultipart(MIXED);
+            addTextPart(multipart, htmlText, "text/html");
+            addTextPart(multipart, retrievePlainTextMessage(plainText, htmlText), ContentTypeField.TYPE_TEXT_PLAIN);
+            return multipart;
+        } catch (IOException e) {
+            throw new MessagingException("Cannot read specified content", e);
+        }
+    }
+
+    private Multipart addTextPart(Multipart multipart, String text, String contentType) throws
MessagingException, IOException {
+        MimeBodyPart textReasonPart = new MimeBodyPart();
+        textReasonPart.setDataHandler(
+            new DataHandler(
+                new ByteArrayDataSource(
+                    text,
+                    contentType + "; charset=UTF-8")));
+        multipart.addBodyPart(textReasonPart);
+        return multipart;
+    }
+
+    private String retrievePlainTextMessage(Optional<String> plainText, String htmlText)
{
+        return plainText.orElseGet(() -> htmlTextExtractor.toPlainText(htmlText));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/4dc1d61b/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/VacationMailetTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/VacationMailetTest.java
b/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/VacationMailetTest.java
index f3047e4..1ba6995 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/VacationMailetTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/VacationMailetTest.java
@@ -38,6 +38,7 @@ import org.apache.james.jmap.api.vacation.NotificationRegistry;
 import org.apache.james.jmap.api.vacation.RecipientId;
 import org.apache.james.jmap.api.vacation.Vacation;
 import org.apache.james.jmap.api.vacation.VacationRepository;
+import org.apache.james.jmap.utils.MimeMessageBodyGenerator;
 import org.apache.james.util.date.ZonedDateTimeProvider;
 import org.apache.mailet.MailAddress;
 import org.apache.mailet.MailetContext;
@@ -66,6 +67,7 @@ public class VacationMailetTest {
     private VacationMailet testee;
     private VacationRepository vacationRepository;
     private ZonedDateTimeProvider zonedDateTimeProvider;
+    private MimeMessageBodyGenerator mimeMessageBodyGenerator;
     private MailetContext mailetContext;
     private MailAddress originalSender;
     private MailAddress originalRecipient;
@@ -85,11 +87,12 @@ public class VacationMailetTest {
             .sender(originalSender)
             .build();
 
+        mimeMessageBodyGenerator = mock(MimeMessageBodyGenerator.class);
         vacationRepository = mock(VacationRepository.class);
         zonedDateTimeProvider = mock(ZonedDateTimeProvider.class);
         automaticallySentMailDetector = mock(AutomaticallySentMailDetector.class);
         notificationRegistry = mock(NotificationRegistry.class);
-        testee = new VacationMailet(vacationRepository, zonedDateTimeProvider, automaticallySentMailDetector,
notificationRegistry);
+        testee = new VacationMailet(vacationRepository, zonedDateTimeProvider, automaticallySentMailDetector,
notificationRegistry, mimeMessageBodyGenerator);
         mailetContext = mock(MailetContext.class);
         testee.init(new FakeMailetConfig("vacation", mailetContext));
     }

http://git-wip-us.apache.org/repos/asf/james-project/blob/4dc1d61b/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/VacationReplyTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/VacationReplyTest.java
b/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/VacationReplyTest.java
index 5bb2ddd..73d8e69 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/VacationReplyTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/mailet/VacationReplyTest.java
@@ -20,30 +20,41 @@
 package org.apache.james.jmap.mailet;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import java.util.Optional;
 import java.util.Properties;
 
+import javax.mail.MessagingException;
 import javax.mail.Session;
 import javax.mail.internet.MimeMessage;
 
-import org.apache.commons.io.IOUtils;
 import org.apache.james.jmap.api.vacation.Vacation;
+import org.apache.james.jmap.utils.MimeMessageBodyGenerator;
 import org.apache.mailet.MailAddress;
 import org.apache.mailet.base.test.FakeMail;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
 import org.junit.Before;
 import org.junit.Test;
 
+import com.google.common.base.Throwables;
+
 public class VacationReplyTest {
 
     public static final String REASON = "I am in vacation dudes ! (plain text)";
     public static final String HTML_REASON = "<b>I am in vacation dudes !</b>
(html text)";
-    public static final String HTML_EXTRACTED_REASON = "I am in vacation dudes ! (html text)";
     public static final String SUBJECT = "subject";
 
+    private MimeMessageBodyGenerator mimeMessageBodyGenerator;
     private MailAddress originalSender;
     private MailAddress originalRecipient;
     private FakeMail mail;
+    private MimeMessage generatedBody;
 
     @Before
     public void setUp() throws Exception {
@@ -51,8 +62,13 @@ public class VacationReplyTest {
         originalRecipient = new MailAddress("benwa@apache.org");
 
         mail = new FakeMail();
-        mail.setMessage(new MimeMessage(Session.getInstance(new Properties()) ,ClassLoader.getSystemResourceAsStream("spamMail.eml")));
+
+        mail.setMessage(new MimeMessage(Session.getInstance(new Properties()), ClassLoader.getSystemResourceAsStream("spamMail.eml")));
         mail.setSender(originalSender);
+
+        mimeMessageBodyGenerator = mock(MimeMessageBodyGenerator.class);
+        generatedBody = new MimeMessage(Session.getInstance(new Properties()));
+        when(mimeMessageBodyGenerator.from(any(MimeMessage.class), any(), any())).thenReturn(generatedBody);
     }
 
     @Test
@@ -65,56 +81,27 @@ public class VacationReplyTest {
                 .htmlBody(HTML_REASON)
                 .build())
             .receivedMailRecipient(originalRecipient)
-            .build();
-
-        assertThat(vacationReply.getRecipients()).containsExactly(originalSender);
-        assertThat(vacationReply.getSender()).isEqualTo(originalRecipient);
-        assertThat(IOUtils.toString(vacationReply.getMimeMessage().getInputStream())).contains(REASON);
-        assertThat(IOUtils.toString(vacationReply.getMimeMessage().getInputStream())).contains(HTML_REASON);
-    }
-
-    @Test
-    public void vacationReplyShouldExtractPlainTextContentWhenOnlyHtmlBody() throws Exception
{
-        VacationReply vacationReply = VacationReply.builder(mail)
-            .vacation(Vacation.builder()
-                .enabled(true)
-                .htmlBody(HTML_REASON)
-                .build())
-            .receivedMailRecipient(originalRecipient)
-            .build();
+            .build(mimeMessageBodyGenerator);
 
         assertThat(vacationReply.getRecipients()).containsExactly(originalSender);
         assertThat(vacationReply.getSender()).isEqualTo(originalRecipient);
-        assertThat(IOUtils.toString(vacationReply.getMimeMessage().getInputStream())).contains(HTML_EXTRACTED_REASON);
-        assertThat(IOUtils.toString(vacationReply.getMimeMessage().getInputStream())).contains(HTML_REASON);
+        assertThat(vacationReply.getMimeMessage()).isEqualTo(generatedBody);
     }
 
     @Test
-    public void vacationReplyShouldNotBeMultipartWhenVacationHaveNoHTML() throws Exception
{
+    public void vacationReplyShouldAddReSuffixToSubjectByDefault() throws Exception {
         VacationReply vacationReply = VacationReply.builder(mail)
             .vacation(Vacation.builder()
                 .enabled(true)
                 .textBody(REASON)
                 .build())
             .receivedMailRecipient(originalRecipient)
-            .build();
+            .build(mimeMessageBodyGenerator);
 
+        verify(mimeMessageBodyGenerator).from(argThat(createSubjectMatcher("Re: Original
subject")), any(), any());
         assertThat(vacationReply.getRecipients()).containsExactly(originalSender);
         assertThat(vacationReply.getSender()).isEqualTo(originalRecipient);
-        assertThat(IOUtils.toString(vacationReply.getMimeMessage().getInputStream())).isEqualTo(REASON);
-    }
-
-    @Test
-    public void vacationReplyShouldAddReSuffixToSubjectByDefault() throws Exception {
-        VacationReply vacationReply = VacationReply.builder(mail)
-            .vacation(Vacation.builder()
-                .enabled(true)
-                .textBody(REASON)
-                .build())
-            .receivedMailRecipient(originalRecipient)
-            .build();
-
-        assertThat(vacationReply.getMimeMessage().getHeader("subject")).containsExactly("Re:
Original subject");
+        assertThat(vacationReply.getMimeMessage()).isEqualTo(generatedBody);
     }
 
     @Test
@@ -126,9 +113,12 @@ public class VacationReplyTest {
                 .subject(Optional.of(SUBJECT))
                 .build())
             .receivedMailRecipient(originalRecipient)
-            .build();
+            .build(mimeMessageBodyGenerator);
 
-        assertThat(vacationReply.getMimeMessage().getHeader("subject")).containsExactly(SUBJECT);
+        verify(mimeMessageBodyGenerator).from(argThat(createSubjectMatcher(SUBJECT)), any(),
any());
+        assertThat(vacationReply.getRecipients()).containsExactly(originalSender);
+        assertThat(vacationReply.getSender()).isEqualTo(originalRecipient);
+        assertThat(vacationReply.getMimeMessage()).isEqualTo(generatedBody);
     }
 
     @Test(expected = NullPointerException.class)
@@ -139,8 +129,25 @@ public class VacationReplyTest {
     @Test(expected = NullPointerException.class)
     public void vacationReplyShouldThrowOnNullOriginalEMailAddress() throws Exception {
         VacationReply.builder(new FakeMail())
-            .receivedMailRecipient(null)
-            .build();
+            .receivedMailRecipient(null);
     }
 
+    private BaseMatcher<MimeMessage> createSubjectMatcher(final String expectedSubject)
{
+        return new BaseMatcher<MimeMessage>() {
+            @Override
+            public boolean matches(Object o) {
+                MimeMessage mimeMessage = (MimeMessage) o;
+                try {
+                    return mimeMessage.getSubject().equals(expectedSubject);
+                } catch (MessagingException e) {
+                    throw Throwables.propagate(e);
+                }
+            }
+
+            @Override
+            public void describeTo(Description description) {
+
+            }
+        };
+    }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/4dc1d61b/server/protocols/jmap/src/test/java/org/apache/james/jmap/utils/MailboxBasedHtmlTextExtractorTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/utils/MailboxBasedHtmlTextExtractorTest.java
b/server/protocols/jmap/src/test/java/org/apache/james/jmap/utils/MailboxBasedHtmlTextExtractorTest.java
new file mode 100644
index 0000000..6697ba8
--- /dev/null
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/utils/MailboxBasedHtmlTextExtractorTest.java
@@ -0,0 +1,110 @@
+/****************************************************************
+ * 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.utils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.james.mailbox.tika.extractor.TikaTextExtractor;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MailboxBasedHtmlTextExtractorTest {
+
+    private MailboxBasedHtmlTextExtractor textExtractor;
+
+    @Before
+    public void setUp() {
+        textExtractor = new MailboxBasedHtmlTextExtractor(new TikaTextExtractor());
+    }
+
+    @Test
+    public void toPlainTextShouldNotModifyPlainText() {
+        String textWithoutHtml = "text without html";
+        assertThat(textExtractor.toPlainText(textWithoutHtml)).isEqualTo(textWithoutHtml);
+    }
+
+    @Test
+    public void toPlainTextShouldRemoveSimpleHtmlTag() {
+        String html = "This is an <b>HTML</b> text !";
+        String expectedPlainText = "This is an HTML text !";
+        assertThat(textExtractor.toPlainText(html)).isEqualTo(expectedPlainText);
+    }
+
+    @Test
+    public void toPlainTextShouldReplaceSkipLine() {
+        String html = "<p>This is an<br/>HTML text !</p>";
+        String expectedPlainText = "This is an\nHTML text !\n";
+        assertThat(textExtractor.toPlainText(html)).isEqualTo(expectedPlainText);
+    }
+
+    @Test
+    public void toPlainTextShouldSkipLinesBetweenParagraph() {
+        String html = "<p>para1</p><p>para2</p>";
+        String expectedPlainText = "para1\npara2\n";
+        assertThat(textExtractor.toPlainText(html)).isEqualTo(expectedPlainText);
+    }
+
+    @Test
+    public void nonClosedHtmlShouldBeTranslated() {
+        String html = "This is an <b>HTML text !";
+        String expectedPlainText = "This is an HTML text !";
+        assertThat(textExtractor.toPlainText(html)).isEqualTo(expectedPlainText);
+    }
+
+    @Test
+    public void brokenHtmlShouldBeTranslatedUntilTheBrokenBalise() {
+        String html = "This is an <b>HTML</b missing missing missing !";
+        String expectedPlainText = "This is an HTML";
+        assertThat(textExtractor.toPlainText(html)).isEqualTo(expectedPlainText);
+    }
+
+    @Test
+    public void toPlainTextShouldWorkWithMoreComplexHTML() throws Exception {
+        String html = IOUtils.toString(ClassLoader.getSystemResource("example.html"));
+        String expectedPlainText = "\n" +
+            "    Why a new Logo?\n" +
+            "\n" +
+            "\n" +
+            "\n" +
+            "    We are happy with our current logo, but for the\n" +
+            "        upcoming James Server 3.0 release, we would like to\n" +
+            "        give our community the opportunity to create a new image for James.\n"
+
+            "\n" +
+            "\n" +
+            "\n" +
+            "    Don't be shy, take your inkscape and gimp, and send us on\n" +
+            "        the James Server User mailing list\n" +
+            "        your creations. We will publish them on this page.\n" +
+            "\n" +
+            "\n" +
+            "\n" +
+            "    We need an horizontal logo (100p height) to be show displayed on the upper\n"
+
+            "        left corner of this page, an avatar (48x48p) to be used on a Twitter
stream for example.\n" +
+            "        The used fonts should be redistributable (or commonly available on Windows
and Linux).\n" +
+            "        The chosen logo should be delivered in SVG format.\n" +
+            "        We also like the Apache feather.\n" +
+            "\n" +
+            "\n" +
+            "\n";
+        assertThat(textExtractor.toPlainText(html)).isEqualTo(expectedPlainText);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/4dc1d61b/server/protocols/jmap/src/test/java/org/apache/james/jmap/utils/MimeMessageBodyGeneratorTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/utils/MimeMessageBodyGeneratorTest.java
b/server/protocols/jmap/src/test/java/org/apache/james/jmap/utils/MimeMessageBodyGeneratorTest.java
new file mode 100644
index 0000000..b525660
--- /dev/null
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/utils/MimeMessageBodyGeneratorTest.java
@@ -0,0 +1,140 @@
+/****************************************************************
+ * 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.utils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import java.util.Optional;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MimeMessageBodyGeneratorTest {
+
+    private MimeMessageBodyGenerator mimeMessageBodyGenerator;
+    private HtmlTextExtractor htmlTextExtractor;
+    private MimeMessage original;
+
+    @Before
+    public void setUp() {
+        original = new MimeMessage(Session.getDefaultInstance(new Properties()));
+        htmlTextExtractor = mock(HtmlTextExtractor.class);
+        mimeMessageBodyGenerator = new MimeMessageBodyGenerator(htmlTextExtractor);
+    }
+
+    @Test
+    public void fromShouldNotWriteAMultipartWhenOnlyPlainText() throws Exception {
+        assertThat(IOUtils.toString(
+            mimeMessageBodyGenerator.from(original,
+                Optional.of("Plain text"),
+                Optional.empty())
+                .getInputStream()))
+            .isEqualTo("Plain text");
+        verifyZeroInteractions(htmlTextExtractor);
+    }
+
+    @Test
+    public void fromShouldPreservePreviouslySetHeaders() throws Exception {
+        String subject = "Important, I should be kept";
+        original.setHeader("Subject", subject);
+
+        mimeMessageBodyGenerator.from(original,
+            Optional.of("Plain text"),
+            Optional.empty())
+            .getInputStream();
+
+        assertThat(original.getSubject()).isEqualTo(subject);
+        verifyZeroInteractions(htmlTextExtractor);
+    }
+
+    @Test
+    public void fromShouldProvideAPlainTextVersionWhenOnlyHtml() throws Exception {
+        String htmlText = "<p>HTML text</p>";
+        String plainText = "Plain text";
+        when(htmlTextExtractor.toPlainText(htmlText)).thenReturn(plainText);
+
+        String rowContent = IOUtils.toString(
+            mimeMessageBodyGenerator.from(original,
+                Optional.empty(),
+                Optional.of(htmlText))
+                .getInputStream());
+
+        assertThat(rowContent).containsSequence(htmlText);
+        assertThat(rowContent).containsSequence(plainText);
+    }
+
+    @Test
+    public void fromShouldCombinePlainTextAndHtml() throws Exception {
+        String htmlText = "<p>HTML text</p>";
+        String plainText = "Plain text";
+
+        String rowContent = IOUtils.toString(
+            mimeMessageBodyGenerator.from(original,
+                Optional.of(plainText),
+                Optional.of(htmlText))
+                .getInputStream());
+
+        assertThat(rowContent).containsSequence(htmlText);
+        assertThat(rowContent).containsSequence(plainText);
+        verifyZeroInteractions(htmlTextExtractor);
+    }
+
+    @Test
+    public void fromShouldThrowWhenNoPlainTextNorHtmlBody() throws Exception {
+        assertThatThrownBy(() -> mimeMessageBodyGenerator.from(original,
+            Optional.empty(),
+            Optional.empty()))
+            .isInstanceOf(IllegalStateException.class);
+    }
+
+    @Test
+    public void fromShouldThrowOnNullPlainText() throws Exception {
+        assertThatThrownBy(() -> mimeMessageBodyGenerator.from(original,
+            null,
+            Optional.empty()))
+            .isInstanceOf(NullPointerException.class);
+    }
+
+    @Test
+    public void fromShouldThrowOnNullHtml() throws Exception {
+        assertThatThrownBy(() -> mimeMessageBodyGenerator.from(original,
+            Optional.empty(),
+            null))
+            .isInstanceOf(NullPointerException.class);
+    }
+
+    @Test
+    public void fromShouldThrowOnNullMimeMessageToDecorate() throws Exception {
+        assertThatThrownBy(() -> mimeMessageBodyGenerator.from(null,
+            Optional.empty(),
+            Optional.empty()))
+            .isInstanceOf(NullPointerException.class);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/4dc1d61b/server/protocols/jmap/src/test/resources/example.html
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/resources/example.html b/server/protocols/jmap/src/test/resources/example.html
new file mode 100644
index 0000000..59d3395
--- /dev/null
+++ b/server/protocols/jmap/src/test/resources/example.html
@@ -0,0 +1,21 @@
+<div class="section">
+    <h3>Why a new Logo?<a name="Why_a_new_Logo"></a></h3>
+
+
+    <p>We are happy with our current logo, but for the
+        upcoming James Server 3.0 release, we would like to
+        give our community the opportunity to create a new image for James.</p>
+
+
+    <p>Don't be shy, take your inkscape and gimp, and send us on
+        the <a href="mail.html">James Server User mailing list</a>
+        your creations. We will publish them on this page.</p>
+
+
+    <p>We need an horizontal logo (100p height) to be show displayed on the upper
+        left corner of this page, an avatar (48x48p) to be used on a Twitter stream for example.
+        The used fonts should be redistributable (or commonly available on Windows and Linux).
+        The chosen logo should be delivered in SVG format.
+        We also like the <a class="externalLink" href="http://www.apache.org/foundation/press/kit/">Apache
feather</a>.</p>
+
+</div>
\ No newline at end of file


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


Mime
View raw message