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