You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by bt...@apache.org on 2019/12/18 11:23:26 UTC

[james-project] 11/13: JAMES-2993 Create a Preview.Factory

This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit cd3f6089548b0b1882cfb735121b73bf2c371eb7
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon Dec 16 13:55:04 2019 +0700

    JAMES-2993 Create a Preview.Factory
---
 .../org/apache/james/jmap/api/model/Preview.java   |  50 +++++++
 .../MessageFastViewPrecomputedProperties.java      |  28 ++++
 .../ComputeMessageFastViewProjectionListener.java  |  17 +--
 .../MessageFastViewProjectionItemFactoryTest.java  | 144 +++++++++++++++++++++
 .../org/apache/james/jmap/PreviewFactoryTest.java  |  73 +++++++++++
 ...mputeMessageFastViewProjectionListenerTest.java |   3 +-
 .../src/test/resources/inlineAttachment.eml        |  24 ++++
 .../src/test/resources/oneAttachment.eml           |  16 +++
 8 files changed, 341 insertions(+), 14 deletions(-)

diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/model/Preview.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/model/Preview.java
index d4f7fb4..42b2428 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/model/Preview.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/model/Preview.java
@@ -19,15 +19,65 @@
 
 package org.apache.james.jmap.api.model;
 
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import java.util.Objects;
 
+import javax.inject.Inject;
+
 import org.apache.commons.lang3.StringUtils;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.model.MessageResult;
+import org.apache.james.mime4j.dom.Message;
+import org.apache.james.mime4j.stream.MimeConfig;
+import org.apache.james.util.html.HtmlTextExtractor;
+import org.apache.james.util.mime.MessageContentExtractor;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Preconditions;
 
 public class Preview {
+    public static class Factory {
+        private final MessageContentExtractor messageContentExtractor;
+        private final HtmlTextExtractor htmlTextExtractor;
+
+        @Inject
+        public Factory(MessageContentExtractor messageContentExtractor, HtmlTextExtractor htmlTextExtractor) {
+            this.messageContentExtractor = messageContentExtractor;
+            this.htmlTextExtractor = htmlTextExtractor;
+        }
+
+        public Preview fromMessageResult(MessageResult messageResult) throws MailboxException, IOException {
+            try (InputStream inputStream = messageResult.getFullContent().getInputStream()) {
+                return fromInputStream(inputStream);
+            }
+        }
+
+        public Preview fromMessageAsString(String messageAsString) throws IOException {
+            return fromInputStream(new ByteArrayInputStream(messageAsString.getBytes(StandardCharsets.UTF_8)));
+        }
+
+        private Preview fromInputStream(InputStream inputStream) throws IOException {
+            return fromMime4JMessage(parse(inputStream));
+        }
+
+        private Preview fromMime4JMessage(Message mimeMessage) throws IOException {
+            MessageContentExtractor.MessageContent messageContent = messageContentExtractor.extract(mimeMessage);
+            return messageContent.extractMainTextContent(htmlTextExtractor)
+                .map(Preview::compute)
+                .orElse(Preview.EMPTY);
+        }
+
+        private Message parse(InputStream inputStream) throws IOException {
+            return Message.Builder.of()
+                .use(MimeConfig.PERMISSIVE)
+                .parse(inputStream)
+                .build();
+        }
+    }
 
     public static final Preview EMPTY = Preview.from("");
 
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/projections/MessageFastViewPrecomputedProperties.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/projections/MessageFastViewPrecomputedProperties.java
index 997ad66..f3dbe8b 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/projections/MessageFastViewPrecomputedProperties.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/projections/MessageFastViewPrecomputedProperties.java
@@ -19,9 +19,16 @@
 
 package org.apache.james.jmap.api.projections;
 
+import java.io.IOException;
+import java.util.List;
 import java.util.Objects;
 
+import javax.inject.Inject;
+
 import org.apache.james.jmap.api.model.Preview;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.model.MessageAttachment;
+import org.apache.james.mailbox.model.MessageResult;
 
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Preconditions;
@@ -66,6 +73,27 @@ public class MessageFastViewPrecomputedProperties {
         return preview -> hasAttachment -> new Builder.FinalStage(preview, hasAttachment);
     }
 
+    public static class Factory {
+        private final Preview.Factory previewFactory;
+
+        @Inject
+        public Factory(Preview.Factory previewFactory) {
+            this.previewFactory = previewFactory;
+        }
+
+        public MessageFastViewPrecomputedProperties from(MessageResult messageResult) throws MailboxException, IOException {
+            return builder()
+                .preview(previewFactory.fromMessageResult(messageResult))
+                .hasAttachment(hasAttachment(messageResult.getLoadedAttachments()))
+                .build();
+        }
+
+        private boolean hasAttachment(List<MessageAttachment> attachments) {
+            return attachments.stream()
+                .anyMatch(attachment -> !attachment.isInlinedWithCid());
+        }
+    }
+
     private final Preview preview;
     private final boolean hasAttachment;
 
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/event/ComputeMessageFastViewProjectionListener.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/event/ComputeMessageFastViewProjectionListener.java
index 87ce35f..d4e690e 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/event/ComputeMessageFastViewProjectionListener.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/event/ComputeMessageFastViewProjectionListener.java
@@ -24,11 +24,8 @@ import java.io.IOException;
 import javax.inject.Inject;
 
 import org.apache.commons.lang3.tuple.Pair;
-import org.apache.james.jmap.api.model.Preview;
 import org.apache.james.jmap.api.projections.MessageFastViewPrecomputedProperties;
 import org.apache.james.jmap.api.projections.MessageFastViewProjection;
-import org.apache.james.jmap.draft.model.message.view.MessageFullView;
-import org.apache.james.jmap.draft.model.message.view.MessageFullViewFactory;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.MessageIdManager;
 import org.apache.james.mailbox.SessionProvider;
@@ -38,7 +35,6 @@ import org.apache.james.mailbox.events.MailboxListener;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.model.FetchGroup;
 import org.apache.james.mailbox.model.MessageResult;
-import org.parboiled.common.ImmutableList;
 
 import com.github.fge.lambdas.Throwing;
 import com.google.common.annotations.VisibleForTesting;
@@ -57,16 +53,16 @@ public class ComputeMessageFastViewProjectionListener implements MailboxListener
     private final MessageIdManager messageIdManager;
     private final MessageFastViewProjection messageFastViewProjection;
     private final SessionProvider sessionProvider;
-    private final MessageFullViewFactory messageFullViewFactory;
+    private final MessageFastViewPrecomputedProperties.Factory messageFastViewPrecomputedPropertiesFactory;
 
     @Inject
     public ComputeMessageFastViewProjectionListener(SessionProvider sessionProvider, MessageIdManager messageIdManager,
                                                     MessageFastViewProjection messageFastViewProjection,
-                                                    MessageFullViewFactory messageFullViewFactory) {
+                                                    MessageFastViewPrecomputedProperties.Factory messageFastViewPrecomputedPropertiesFactory) {
         this.sessionProvider = sessionProvider;
         this.messageIdManager = messageIdManager;
         this.messageFastViewProjection = messageFastViewProjection;
-        this.messageFullViewFactory = messageFullViewFactory;
+        this.messageFastViewPrecomputedPropertiesFactory = messageFastViewPrecomputedPropertiesFactory;
     }
 
     @Override
@@ -95,11 +91,6 @@ public class ComputeMessageFastViewProjectionListener implements MailboxListener
 
     @VisibleForTesting
     MessageFastViewPrecomputedProperties computeFastViewPrecomputedProperties(MessageResult messageResult) throws MailboxException, IOException {
-        MessageFullView message = messageFullViewFactory.fromMessageResults(ImmutableList.of(messageResult));
-
-        return MessageFastViewPrecomputedProperties.builder()
-            .preview(Preview.from(message.getPreview().getValue()))
-            .hasAttachment(message.isHasAttachment())
-            .build();
+        return messageFastViewPrecomputedPropertiesFactory.from(messageResult);
     }
 }
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/MessageFastViewProjectionItemFactoryTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/MessageFastViewProjectionItemFactoryTest.java
new file mode 100644
index 0000000..84fe433
--- /dev/null
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/MessageFastViewProjectionItemFactoryTest.java
@@ -0,0 +1,144 @@
+/****************************************************************
+ * 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;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.james.core.Username;
+import org.apache.james.jmap.api.model.Preview;
+import org.apache.james.jmap.api.projections.MessageFastViewPrecomputedProperties;
+import org.apache.james.jmap.draft.utils.JsoupHtmlTextExtractor;
+import org.apache.james.mailbox.MailboxManager;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MessageManager;
+import org.apache.james.mailbox.inmemory.manager.InMemoryIntegrationResources;
+import org.apache.james.mailbox.model.ComposedMessageId;
+import org.apache.james.mailbox.model.FetchGroup;
+import org.apache.james.mailbox.model.MailboxId;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.model.MessageRange;
+import org.apache.james.mailbox.model.MessageResult;
+import org.apache.james.util.ClassLoaderUtils;
+import org.apache.james.util.mime.MessageContentExtractor;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class MessageFastViewProjectionItemFactoryTest {
+    public static final Username BOB = Username.of("bob");
+    MessageFastViewPrecomputedProperties.Factory testee;
+    MailboxManager mailboxManager;
+    MailboxSession session;
+    MessageManager mailbox;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        testee = new MessageFastViewPrecomputedProperties.Factory(
+            new Preview.Factory(new MessageContentExtractor(), new JsoupHtmlTextExtractor()));
+        mailboxManager = InMemoryIntegrationResources.defaultResources().getMailboxManager();
+        session = mailboxManager.createSystemSession(BOB);
+        MailboxId mailboxId = mailboxManager.createMailbox(MailboxPath.inbox(BOB), session).get();
+        mailbox = mailboxManager.getMailbox(mailboxId, session);
+    }
+
+    @Test
+    void fromShouldReturnEmptyWhenNoBodyPart() throws Exception {
+        MessageFastViewPrecomputedProperties actual = testee.from(toMessageResult("header: value\r\n"));
+
+        assertThat(actual)
+            .isEqualTo(MessageFastViewPrecomputedProperties.builder()
+                .preview(Preview.EMPTY)
+                .hasAttachment(false)
+                .build());
+    }
+
+    @Test
+    void fromShouldReturnEmptyWhenEmptyBodyPart() throws Exception {
+        MessageFastViewPrecomputedProperties actual = testee.from(toMessageResult("header: value\r\n\r\n"));
+
+        assertThat(actual)
+            .isEqualTo(MessageFastViewPrecomputedProperties.builder()
+                .preview(Preview.EMPTY)
+                .hasAttachment(false)
+                .build());
+    }
+
+    @Test
+    void fromShouldReturnEmptyWhenBlankBodyPart() throws Exception {
+        MessageFastViewPrecomputedProperties actual = testee.from(toMessageResult("header: value\r\n\r\n  \r\n  \r\n"));
+
+        assertThat(actual)
+            .isEqualTo(MessageFastViewPrecomputedProperties.builder()
+                .preview(Preview.EMPTY)
+                .hasAttachment(false)
+                .build());
+    }
+
+    @Test
+    void fromShouldReturnSanitizedBodyTextValue() throws Exception {
+        MessageFastViewPrecomputedProperties actual = testee.from(toMessageResult("header: value\r\n\r\n  \r\nmessage  \r\n"));
+
+        assertThat(actual)
+            .isEqualTo(MessageFastViewPrecomputedProperties.builder()
+                .preview(Preview.from("message"))
+                .hasAttachment(false)
+                .build());
+    }
+
+    @Test
+    void fromShouldExtractHtml() throws Exception {
+        MessageFastViewPrecomputedProperties actual = testee.from(toMessageResult(ClassLoaderUtils.getSystemResourceAsString("fullMessage.eml")));
+
+        assertThat(actual)
+            .isEqualTo(MessageFastViewPrecomputedProperties.builder()
+                .preview(Preview.from("blabla bloblo"))
+                .hasAttachment()
+                .build());
+    }
+
+    @Test
+    void fromShouldParseAttachmentWhenOnlyAttachment() throws Exception {
+        MessageFastViewPrecomputedProperties actual = testee.from(toMessageResult(ClassLoaderUtils.getSystemResourceAsString("oneAttachment.eml")));
+
+        assertThat(actual)
+            .isEqualTo(MessageFastViewPrecomputedProperties.builder()
+                .preview(Preview.EMPTY)
+                .hasAttachment()
+                .build());
+    }
+
+    @Test
+    void fromShouldIngnoreAttachmentsWhenInlined() throws Exception {
+        MessageFastViewPrecomputedProperties actual = testee.from(toMessageResult(ClassLoaderUtils.getSystemResourceAsString("inlineAttachment.eml")));
+
+        assertThat(actual)
+            .isEqualTo(MessageFastViewPrecomputedProperties.builder()
+                .preview(Preview.from("I'm the body!"))
+                .hasAttachment(false)
+                .build());
+    }
+
+    MessageResult toMessageResult(String messageAsString) throws Exception {
+        ComposedMessageId composedMessageId = mailbox.appendMessage(MessageManager.AppendCommand.builder()
+            .build(messageAsString), session);
+
+        return mailbox.getMessages(MessageRange.one(composedMessageId.getUid()), FetchGroup.FULL_CONTENT, session)
+            .next();
+    }
+}
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/PreviewFactoryTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/PreviewFactoryTest.java
new file mode 100644
index 0000000..f55ff31
--- /dev/null
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/PreviewFactoryTest.java
@@ -0,0 +1,73 @@
+/****************************************************************
+ * 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;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.james.jmap.api.model.Preview;
+import org.apache.james.jmap.draft.utils.JsoupHtmlTextExtractor;
+import org.apache.james.util.ClassLoaderUtils;
+import org.apache.james.util.mime.MessageContentExtractor;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class PreviewFactoryTest {
+    Preview.Factory testee;
+
+    @BeforeEach
+    void setUp() {
+        testee = new Preview.Factory(new MessageContentExtractor(), new JsoupHtmlTextExtractor());
+    }
+
+    @Test
+    void fromMessageAsStringShouldReturnEmptyWhenNoBodyPart() throws Exception {
+        Preview actual = testee.fromMessageAsString("header: value\r\n");
+
+        assertThat(actual).isEqualTo(Preview.EMPTY);
+    }
+
+    @Test
+    void fromMessageAsStringShouldReturnEmptyWhenEmptyBodyPart() throws Exception {
+        Preview actual = testee.fromMessageAsString("header: value\r\n\r\n");
+
+        assertThat(actual).isEqualTo(Preview.EMPTY);
+    }
+
+    @Test
+    void fromMessageAsStringShouldReturnEmptyWhenBlankBodyPart() throws Exception {
+        Preview actual = testee.fromMessageAsString("header: value\r\n\r\n  \r\n  \r\n");
+
+        assertThat(actual).isEqualTo(Preview.EMPTY);
+    }
+
+    @Test
+    void fromMessageAsStringShouldReturnSanitizedBodyTextValue() throws Exception {
+        Preview actual = testee.fromMessageAsString("header: value\r\n\r\n  \r\nmessage  \r\n");
+
+        assertThat(actual).isEqualTo(Preview.from("message"));
+    }
+
+    @Test
+    void fromMessageAsStringShouldExtractHtml() throws Exception {
+        Preview actual = testee.fromMessageAsString(ClassLoaderUtils.getSystemResourceAsString("fullMessage.eml"));
+
+        assertThat(actual).isEqualTo(Preview.from("blabla bloblo"));
+    }
+}
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/event/ComputeMessageFastViewProjectionListenerTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/event/ComputeMessageFastViewProjectionListenerTest.java
index babc63d..aa20d96 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/event/ComputeMessageFastViewProjectionListenerTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/event/ComputeMessageFastViewProjectionListenerTest.java
@@ -129,7 +129,8 @@ class ComputeMessageFastViewProjectionListenerTest {
         SessionProviderImpl sessionProvider = new SessionProviderImpl(authenticator, FakeAuthorizator.defaultReject());
 
         listener = spy(new ComputeMessageFastViewProjectionListener(sessionProvider, messageIdManager,
-            messageFastViewProjection, messageFullViewFactory));
+            messageFastViewProjection,
+            new MessageFastViewPrecomputedProperties.Factory(new Preview.Factory(messageContentExtractor, htmlTextExtractor))));
 
         resources.getEventBus().register(listener);
 
diff --git a/server/protocols/jmap-draft/src/test/resources/inlineAttachment.eml b/server/protocols/jmap-draft/src/test/resources/inlineAttachment.eml
new file mode 100644
index 0000000..79e4c11
--- /dev/null
+++ b/server/protocols/jmap-draft/src/test/resources/inlineAttachment.eml
@@ -0,0 +1,24 @@
+To: me@linagora.com
+From: Benoit Tellier <me...@linagora.com>
+Subject: Mail with text attachment
+Message-ID: <be...@any.com>
+Date: Thu, 13 Oct 2016 10:26:20 +0200
+MIME-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="------------4FD2D252DB453546C22C25B2"
+
+This is a multi-part message in MIME format.
+--------------4FD2D252DB453546C22C25B2
+Content-Type: text/plain; charset=utf-8
+Content-Transfer-Encoding: 7bit
+
+I'm the body!
+
+--------------4FD2D252DB453546C22C25B2
+Content-Disposition: inline
+Content-Type: text/plain; charset=UTF-8;
+ name="attachment.txt"
+Content-ID: <id>
+
+I'm a Schroedinger cat
+--------------4FD2D252DB453546C22C25B2--
diff --git a/server/protocols/jmap-draft/src/test/resources/oneAttachment.eml b/server/protocols/jmap-draft/src/test/resources/oneAttachment.eml
new file mode 100644
index 0000000..3c422f9
--- /dev/null
+++ b/server/protocols/jmap-draft/src/test/resources/oneAttachment.eml
@@ -0,0 +1,16 @@
+Return-Path: <ch...@apache.org>
+Subject: 29989 btellier
+From: <an...@mail.com>
+Content-Disposition: attachment
+MIME-Version: 1.0
+Date: Sun, 02 Apr 2017 22:09:04 -0000
+Content-Type: application/zip; name="9559333830.zip"
+To: <ch...@apache.org>
+Message-ID: <14...@any.com>
+Content-Transfer-Encoding: base64
+
+UEsDBBQAAgAIAEQeg0oN2YT/EAsAAMsWAAAIABwAMjIwODUuanNVVAkAAxBy4VgQcuFYdXgLAAEE
+AAAAAAQAAAAApZhbi1zHFYWfY/B/MP3i7kwj1/2CokAwBPIQ+sGPkgJ1tURkdeiMbYzQf8+3q8+M
+ZmQllgn2aHrqnNq1L2uvtavnj2/b7evz26/Op5M6q/P+8OUX77784g8/lQtLisXTU/68vfzCv/Lg
+D9vqs/3b8fNXf92273ey4XTCykk9w9LpfD7tX+zGzU83b8pPg39uBr/Kmxe7w9PLuP3xwpFKTJ32
+AAEEAAAAAAQAAAAAUEsFBgAAAAABAAEATgAAAFILAAAAAA==


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