Repository: james-project Updated Branches: refs/heads/master b5c125798 -> c4eb1ea91
JAMES-1744 Preview should not contain HTML tags Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/7ab817e6 Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/7ab817e6 Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/7ab817e6 Branch: refs/heads/master Commit: 7ab817e6031b008038b54baf55d065d5badaf8eb Parents: b5c1257 Author: Laura Royet <[email protected]> Authored: Fri May 27 14:07:08 2016 +0200 Committer: Laura Royet <[email protected]> Committed: Mon Jun 13 15:14:20 2016 +0200 ---------------------------------------------------------------------- .../org/apache/james/jmap/JMAPCommonModule.java | 4 + .../integration/GetMessagesMethodTest.java | 92 ++++++++- server/protocols/jmap/pom.xml | 5 + .../james/jmap/methods/GetMessagesMethod.java | 8 +- .../methods/SetMessagesCreationProcessor.java | 8 +- .../org/apache/james/jmap/model/Message.java | 139 -------------- .../apache/james/jmap/model/MessageFactory.java | 166 ++++++++++++++++ .../jmap/model/MessagePreviewGenerator.java | 66 +++++++ .../jmap/methods/GetMessagesMethodTest.java | 34 ++-- .../SetMessagesCreationProcessorTest.java | 25 ++- .../james/jmap/model/MailboxMessageTest.java | 56 +++--- .../jmap/model/MessagePreviewGeneratorTest.java | 187 +++++++++++++++++++ .../apache/james/jmap/send/MailFactoryTest.java | 10 +- 13 files changed, 601 insertions(+), 199 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/7ab817e6/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPCommonModule.java ---------------------------------------------------------------------- diff --git a/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPCommonModule.java b/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPCommonModule.java index cb8915e..71e0427 100644 --- a/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPCommonModule.java +++ b/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPCommonModule.java @@ -28,6 +28,8 @@ import org.apache.james.jmap.crypto.AccessTokenManagerImpl; import org.apache.james.jmap.crypto.JamesSignatureHandler; import org.apache.james.jmap.crypto.SignatureHandler; import org.apache.james.jmap.crypto.SignedContinuationTokenManager; +import org.apache.james.jmap.model.MessageFactory; +import org.apache.james.jmap.model.MessagePreviewGenerator; import org.apache.james.jmap.send.MailFactory; import org.apache.james.jmap.send.MailSpool; import org.apache.james.util.date.DefaultZonedDateTimeProvider; @@ -54,6 +56,8 @@ public class JMAPCommonModule extends AbstractModule { bind(MailSpool.class).in(Scopes.SINGLETON); bind(MailFactory.class).in(Scopes.SINGLETON); bind(AutomaticallySentMailDetectorImpl.class).in(Scopes.SINGLETON); + bind(MessageFactory.class).in(Scopes.SINGLETON); + bind(MessagePreviewGenerator.class).in(Scopes.SINGLETON); bind(SignatureHandler.class).to(JamesSignatureHandler.class); bind(ZonedDateTimeProvider.class).to(DefaultZonedDateTimeProvider.class); http://git-wip-us.apache.org/repos/asf/james-project/blob/7ab817e6/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/GetMessagesMethodTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/GetMessagesMethodTest.java b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/GetMessagesMethodTest.java index cff96a4..09ab9dc 100644 --- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/GetMessagesMethodTest.java +++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/GetMessagesMethodTest.java @@ -221,7 +221,93 @@ public abstract class GetMessagesMethodTest { .body(FIRST_MESSAGE + ".subject", equalTo("my test subject")) .body(FIRST_MESSAGE + ".htmlBody", equalTo("This is a <b>HTML</b> mail")) .body(FIRST_MESSAGE + ".isUnread", equalTo(true)) - .body(FIRST_MESSAGE + ".preview", equalTo("This is a <b>HTML</b> mail")) + .body(FIRST_MESSAGE + ".preview", equalTo("This is a HTML mail")) + .body(FIRST_MESSAGE + ".headers", equalTo(ImmutableMap.of("content-type", "text/html", "subject", "my test subject"))) + .body(FIRST_MESSAGE + ".date", equalTo("2014-10-30T14:12:00Z")); + } + + @Test + public void getMessagesShouldReturnMessageWhenComplexHtmlMessage() throws Exception { + jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "inbox"); + + ZonedDateTime dateTime = ZonedDateTime.parse("2014-10-30T14:12:00Z"); + jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "inbox"), + new ByteArrayInputStream("Content-Type: text/html\r\nSubject: my test subject\r\n\r\nThis is a <b>HTML</b> mail containing <u>underlined part</u>, <i>italic part</i> and <u><i>underlined AND italic part</i></u>".getBytes()), Date.from(dateTime.toInstant()), false, new Flags()); + + await(); + + given() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .header("Authorization", accessToken.serialize()) + .body("[[\"getMessages\", {\"ids\": [\"" + username + "|inbox|1\"]}, \"#0\"]]") + .when() + .post("/jmap") + .then() + .statusCode(200) + .body(NAME, equalTo("messages")) + .body(ARGUMENTS + ".list", hasSize(1)) + .body(FIRST_MESSAGE + ".id", equalTo(username + "|inbox|1")) + .body(FIRST_MESSAGE + ".threadId", equalTo(username + "|inbox|1")) + .body(FIRST_MESSAGE + ".subject", equalTo("my test subject")) + .body(FIRST_MESSAGE + ".htmlBody", equalTo("This is a <b>HTML</b> mail containing <u>underlined part</u>, <i>italic part</i> and <u><i>underlined AND italic part</i></u>")) + .body(FIRST_MESSAGE + ".isUnread", equalTo(true)) + .body(FIRST_MESSAGE + ".preview", equalTo("This is a HTML mail containing underlined part, italic part and underlined AND italic part")) + .body(FIRST_MESSAGE + ".headers", equalTo(ImmutableMap.of("content-type", "text/html", "subject", "my test subject"))) + .body(FIRST_MESSAGE + ".date", equalTo("2014-10-30T14:12:00Z")); + } + + @Test + public void messagePreviewShouldContainTagsWhenPlainTextMessageContainsTags() throws Exception { + jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "inbox"); + + ZonedDateTime dateTime = ZonedDateTime.parse("2014-10-30T14:12:00Z"); + jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "inbox"), + new ByteArrayInputStream("Content-Type: text/plain\r\nSubject: my test subject\r\n\r\nHere is a listing of HTML tags : <b>jfjfjfj</b>, <i>jfhdgdgdfj</i>, <u>jfjaaafj</u>".getBytes()), Date.from(dateTime.toInstant()), false, new Flags()); + + await(); + + given() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .header("Authorization", accessToken.serialize()) + .body("[[\"getMessages\", {\"ids\": [\"" + username + "|inbox|1\"]}, \"#0\"]]") + .when() + .post("/jmap") + .then() + .statusCode(200) + .body(NAME, equalTo("messages")) + .body(ARGUMENTS + ".list", hasSize(1)) + .body(FIRST_MESSAGE + ".preview", equalTo("Here is a listing of HTML tags : <b>jfjfjfj</b>, <i>jfhdgdgdfj</i>, <u>jfjaaafj</u>")); + } + + @Test + public void getMessagesShouldReturnMessageWhenHtmlMessageWithIncorrectTag() throws Exception { + jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "inbox"); + + ZonedDateTime dateTime = ZonedDateTime.parse("2014-10-30T14:12:00Z"); + jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "inbox"), + new ByteArrayInputStream("Content-Type: text/html\r\nSubject: my test subject\r\n\r\nThis is a <ganan>HTML</b> mail".getBytes()), Date.from(dateTime.toInstant()), false, new Flags()); + + await(); + + given() + .accept(ContentType.JSON) + .contentType(ContentType.JSON) + .header("Authorization", accessToken.serialize()) + .body("[[\"getMessages\", {\"ids\": [\"" + username + "|inbox|1\"]}, \"#0\"]]") + .when() + .post("/jmap") + .then() + .statusCode(200) + .body(NAME, equalTo("messages")) + .body(ARGUMENTS + ".list", hasSize(1)) + .body(FIRST_MESSAGE + ".id", equalTo(username + "|inbox|1")) + .body(FIRST_MESSAGE + ".threadId", equalTo(username + "|inbox|1")) + .body(FIRST_MESSAGE + ".subject", equalTo("my test subject")) + .body(FIRST_MESSAGE + ".htmlBody", equalTo("This is a <ganan>HTML</b> mail")) + .body(FIRST_MESSAGE + ".isUnread", equalTo(true)) + .body(FIRST_MESSAGE + ".preview", equalTo("This is a HTML mail")) .body(FIRST_MESSAGE + ".headers", equalTo(ImmutableMap.of("content-type", "text/html", "subject", "my test subject"))) .body(FIRST_MESSAGE + ".date", equalTo("2014-10-30T14:12:00Z")); } @@ -391,7 +477,7 @@ public abstract class GetMessagesMethodTest { .body(ARGUMENTS + ".list", hasSize(1)) .body(FIRST_MESSAGE + ".hasAttachment", equalTo(true)) .body(ATTACHMENTS, hasSize(2)) - .body(FIRST_MESSAGE + ".preview", equalTo("<b>html</b>\n")) + .body(FIRST_MESSAGE + ".preview", equalTo("html\n")) .body(FIRST_MESSAGE + ".textBody", nullValue()) .body(FIRST_MESSAGE + ".htmlBody", equalTo("<b>html</b>\n")); } @@ -443,7 +529,7 @@ public abstract class GetMessagesMethodTest { .body(ARGUMENTS + ".list", hasSize(1)) .body(FIRST_MESSAGE + ".hasAttachment", equalTo(true)) .body(ATTACHMENTS, hasSize(1)) - .body(FIRST_MESSAGE + ".preview", equalTo("<i>blabla</i>\n<b>bloblo</b>\n")) + .body(FIRST_MESSAGE + ".preview", equalTo("blabla\nbloblo\n")) .body(FIRST_MESSAGE + ".textBody", equalTo("/blabla/\n*bloblo*\n")) .body(FIRST_MESSAGE + ".htmlBody", equalTo("<i>blabla</i>\n<b>bloblo</b>\n")); } http://git-wip-us.apache.org/repos/asf/james-project/blob/7ab817e6/server/protocols/jmap/pom.xml ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/pom.xml b/server/protocols/jmap/pom.xml index 8851da0..46f4339 100644 --- a/server/protocols/jmap/pom.xml +++ b/server/protocols/jmap/pom.xml @@ -228,6 +228,11 @@ <artifactId>james-server-util</artifactId> </dependency> <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <version>3.3.2</version> + </dependency> + <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> http://git-wip-us.apache.org/repos/asf/james-project/blob/7ab817e6/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/GetMessagesMethod.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/GetMessagesMethod.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/GetMessagesMethod.java index 3dadb56..39319c5 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/GetMessagesMethod.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/GetMessagesMethod.java @@ -34,6 +34,7 @@ import org.apache.james.jmap.model.ClientId; import org.apache.james.jmap.model.GetMessagesRequest; import org.apache.james.jmap.model.GetMessagesResponse; import org.apache.james.jmap.model.Message; +import org.apache.james.jmap.model.MessageFactory; import org.apache.james.jmap.model.MessageId; import org.apache.james.jmap.model.MessageProperties; import org.apache.james.jmap.model.MessageProperties.HeaderProperty; @@ -68,15 +69,18 @@ public class GetMessagesMethod implements Method { private final MessageMapperFactory messageMapperFactory; private final MailboxMapperFactory mailboxMapperFactory; private final AttachmentMapperFactory attachmentMapperFactory; + private final MessageFactory messageFactory; @Inject @VisibleForTesting GetMessagesMethod( MessageMapperFactory messageMapperFactory, MailboxMapperFactory mailboxMapperFactory, - AttachmentMapperFactory attachmentMapperFactory) { + AttachmentMapperFactory attachmentMapperFactory, + MessageFactory messageFactory) { this.messageMapperFactory = messageMapperFactory; this.mailboxMapperFactory = mailboxMapperFactory; this.attachmentMapperFactory = attachmentMapperFactory; + this.messageFactory = messageFactory; } @Override @@ -135,7 +139,7 @@ public class GetMessagesMethod implements Method { private Function<CompletedMailboxMessage, Message> toJmapMessage(MailboxSession mailboxSession) { - return (completedMailboxMessage) -> Message.fromMailboxMessage( + return (completedMailboxMessage) -> messageFactory.fromMailboxMessage( completedMailboxMessage.mailboxMessage, completedMailboxMessage.attachments, uid -> new MessageId(mailboxSession.getUser(), completedMailboxMessage.mailboxPath , uid)); http://git-wip-us.apache.org/repos/asf/james-project/blob/7ab817e6/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java index a5097d7..49f87a9 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java @@ -40,6 +40,7 @@ import org.apache.james.jmap.exceptions.MailboxRoleNotFoundException; import org.apache.james.jmap.model.CreationMessage; import org.apache.james.jmap.model.CreationMessageId; import org.apache.james.jmap.model.Message; +import org.apache.james.jmap.model.MessageFactory; import org.apache.james.jmap.model.MessageId; import org.apache.james.jmap.model.MessageProperties; import org.apache.james.jmap.model.MessageProperties.MessageProperty; @@ -86,6 +87,7 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor { private final MIMEMessageConverter mimeMessageConverter; private final MailSpool mailSpool; private final MailFactory mailFactory; + private final MessageFactory messageFactory; @Inject @VisibleForTesting @@ -94,13 +96,15 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor { MailboxSessionMapperFactory mailboxSessionMapperFactory, MIMEMessageConverter mimeMessageConverter, MailSpool mailSpool, - MailFactory mailFactory) { + MailFactory mailFactory, + MessageFactory messageFactory) { this.mailboxMapperFactory = mailboxMapperFactory; this.mailboxManager = mailboxManager; this.mailboxSessionMapperFactory = mailboxSessionMapperFactory; this.mimeMessageConverter = mimeMessageConverter; this.mailSpool = mailSpool; this.mailFactory = mailFactory; + this.messageFactory = messageFactory; } @Override @@ -195,7 +199,7 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor { MessageMapper messageMapper = mailboxSessionMapperFactory.createMessageMapper(session); MailboxMessage newMailboxMessage = buildMailboxMessage(createdEntry, outbox); messageMapper.add(outbox, newMailboxMessage); - Message jmapMessage = Message.fromMailboxMessage(newMailboxMessage, ImmutableList.of(), buildMessageIdFromUid); + Message jmapMessage = messageFactory.fromMailboxMessage(newMailboxMessage, ImmutableList.of(), buildMessageIdFromUid); sendMessage(newMailboxMessage, jmapMessage, session); return new MessageWithId<>(createdEntry.getCreationId(), jmapMessage); } catch (MailboxException | MessagingException | IOException e) { http://git-wip-us.apache.org/repos/asf/james-project/blob/7ab817e6/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Message.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Message.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Message.java index 8e87a1e..1c1cd50 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Message.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Message.java @@ -19,24 +19,14 @@ package org.apache.james.jmap.model; -import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; -import java.util.function.Function; import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.apache.james.jmap.methods.GetMessagesMethod; import org.apache.james.jmap.methods.JmapResponseWriterImpl; -import org.apache.james.jmap.model.message.EMailer; -import org.apache.james.jmap.model.message.IndexableMessage; -import org.apache.james.mailbox.store.extractor.DefaultTextExtractor; -import org.apache.james.mailbox.store.mail.model.MailboxMessage; -import org.apache.james.util.streams.ImmutableCollectors; import com.fasterxml.jackson.annotation.JsonFilter; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -46,144 +36,15 @@ import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Multimap; @JsonDeserialize(builder = Message.Builder.class) @JsonFilter(JmapResponseWriterImpl.PROPERTIES_FILTER) public class Message { - public static final String NO_SUBJECT = "(No subject)"; - public static final String MULTIVALUED_HEADERS_SEPARATOR = ", "; - public static final String NO_BODY = "(Empty)"; - public static final ZoneId UTC_ZONE_ID = ZoneId.of("Z"); public static Builder builder() { return new Builder(); } - public static Message fromMailboxMessage(MailboxMessage mailboxMessage, - List<org.apache.james.mailbox.store.mail.model.Attachment> attachments, - Function<Long, MessageId> uidToMessageId) { - IndexableMessage im = IndexableMessage.from(mailboxMessage, new DefaultTextExtractor(), UTC_ZONE_ID); - MessageId messageId = uidToMessageId.apply(im.getId()); - return builder() - .id(messageId) - .blobId(String.valueOf(im.getId())) - .threadId(messageId.serialize()) - .mailboxIds(ImmutableList.of(im.getMailboxId())) - .inReplyToMessageId(getHeaderAsSingleValue(im, "in-reply-to")) - .isUnread(im.isUnRead()) - .isFlagged(im.isFlagged()) - .isAnswered(im.isAnswered()) - .isDraft(im.isDraft()) - .subject(getSubject(im)) - .headers(toMap(im.getHeaders())) - .from(firstElasticSearchEmailers(im.getFrom())) - .to(fromElasticSearchEmailers(im.getTo())) - .cc(fromElasticSearchEmailers(im.getCc())) - .bcc(fromElasticSearchEmailers(im.getBcc())) - .replyTo(fromElasticSearchEmailers(im.getReplyTo())) - .size(im.getSize()) - .date(getInternalDate(mailboxMessage, im)) - .preview(getPreview(im)) - .textBody(getTextBody(im)) - .htmlBody(getHtmlBody(im)) - .attachments(getAttachments(attachments)) - .build(); - } - - private static String getSubject(IndexableMessage im) { - return Optional.ofNullable( - Strings.emptyToNull( - im.getSubjects() - .stream() - .collect(Collectors.joining(MULTIVALUED_HEADERS_SEPARATOR)))) - .orElse(NO_SUBJECT); - } - - private static Emailer firstElasticSearchEmailers(Set<EMailer> emailers) { - return emailers.stream() - .findFirst() - .map(Message::fromElasticSearchEmailer) - .orElse(null); - } - - private static ImmutableList<Emailer> fromElasticSearchEmailers(Set<EMailer> emailers) { - return emailers.stream() - .map(Message::fromElasticSearchEmailer) - .collect(ImmutableCollectors.toImmutableList()); - } - - private static Emailer fromElasticSearchEmailer(EMailer emailer) { - return Emailer.builder() - .name(emailer.getName()) - .email(emailer.getAddress()) - .build(); - } - - private static String getPreview(IndexableMessage im) { - Stream<Optional<String>> htmlThenTextStream = Stream.of( - im.getBodyHtml(), - im.getBodyText()); - - String body = htmlThenTextStream - .filter(Optional::isPresent) - .map(Optional::get) - .filter(stringBody -> !stringBody.isEmpty()) - .findFirst() - .orElse(NO_BODY); - - return computePreview(body); - } - - @VisibleForTesting static String computePreview(String body) { - if (body.length() <= 256) { - return body; - } - return body.substring(0, 253) + "..."; - } - - private static ImmutableMap<String, String> toMap(Multimap<String, String> multimap) { - return multimap - .asMap() - .entrySet() - .stream() - .collect(ImmutableCollectors.toImmutableMap(Map.Entry::getKey, x -> joinOnComma(x.getValue()))); - } - - private static String getHeaderAsSingleValue(IndexableMessage im, String header) { - return Strings.emptyToNull(joinOnComma(im.getHeaders().get(header))); - } - - private static String joinOnComma(Iterable<String> iterable) { - return String.join(MULTIVALUED_HEADERS_SEPARATOR, iterable); - } - - private static ZonedDateTime getInternalDate(MailboxMessage mailboxMessage, IndexableMessage im) { - return ZonedDateTime.ofInstant(mailboxMessage.getInternalDate().toInstant(), UTC_ZONE_ID); - } - - private static String getTextBody(IndexableMessage im) { - return im.getBodyText().map(Strings::emptyToNull).orElse(null); - } - - private static String getHtmlBody(IndexableMessage im) { - return im.getBodyHtml().map(Strings::emptyToNull).orElse(null); - } - - private static List<Attachment> getAttachments(List<org.apache.james.mailbox.store.mail.model.Attachment> attachments) { - return attachments.stream() - .map(Message::fromMailboxAttachment) - .collect(ImmutableCollectors.toImmutableList()); - } - - private static Attachment fromMailboxAttachment(org.apache.james.mailbox.store.mail.model.Attachment attachment) { - return Attachment.builder() - .blobId(attachment.getAttachmentId().getId()) - .type(attachment.getType()) - .size(attachment.getSize()) - .build(); - } - @JsonPOJOBuilder(withPrefix = "") public static class Builder { private MessageId id; http://git-wip-us.apache.org/repos/asf/james-project/blob/7ab817e6/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageFactory.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageFactory.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageFactory.java new file mode 100644 index 0000000..8a5a277 --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessageFactory.java @@ -0,0 +1,166 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ +package org.apache.james.jmap.model; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import javax.inject.Inject; + +import org.apache.james.jmap.model.message.EMailer; +import org.apache.james.jmap.model.message.IndexableMessage; +import org.apache.james.mailbox.store.extractor.DefaultTextExtractor; +import org.apache.james.mailbox.store.mail.model.MailboxMessage; +import org.apache.james.util.streams.ImmutableCollectors; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Multimap; + +public class MessageFactory { + + public static final String NO_SUBJECT = "(No subject)"; + public static final String MULTIVALUED_HEADERS_SEPARATOR = ", "; + public static final ZoneId UTC_ZONE_ID = ZoneId.of("Z"); + + private final MessagePreviewGenerator messagePreview; + + @Inject + public MessageFactory(MessagePreviewGenerator messagePreview) { + this.messagePreview = messagePreview; + } + + public Message fromMailboxMessage(MailboxMessage mailboxMessage, + List<org.apache.james.mailbox.store.mail.model.Attachment> attachments, + Function<Long, MessageId> uidToMessageId) { + + IndexableMessage im = IndexableMessage.from(mailboxMessage, new DefaultTextExtractor(), UTC_ZONE_ID); + MessageId messageId = uidToMessageId.apply(im.getId()); + return Message.builder() + .id(messageId) + .blobId(String.valueOf(im.getId())) + .threadId(messageId.serialize()) + .mailboxIds(ImmutableList.of(im.getMailboxId())) + .inReplyToMessageId(getHeaderAsSingleValue(im, "in-reply-to")) + .isUnread(im.isUnRead()) + .isFlagged(im.isFlagged()) + .isAnswered(im.isAnswered()) + .isDraft(im.isDraft()) + .subject(getSubject(im)) + .headers(toMap(im.getHeaders())) + .from(firstElasticSearchEmailers(im.getFrom())) + .to(fromElasticSearchEmailers(im.getTo())) + .cc(fromElasticSearchEmailers(im.getCc())) + .bcc(fromElasticSearchEmailers(im.getBcc())) + .replyTo(fromElasticSearchEmailers(im.getReplyTo())) + .size(im.getSize()) + .date(getInternalDate(mailboxMessage, im)) + .preview(getPreview(im)) + .textBody(getTextBody(im)) + .htmlBody(getHtmlBody(im)) + .attachments(getAttachments(attachments)) + .build(); + } + + private String getPreview(IndexableMessage im) { + Optional<String> bodyHtml = im.getBodyHtml(); + if (bodyHtml.isPresent()) { + return messagePreview.forHTMLBody(bodyHtml); + } + return messagePreview.forTextBody(im.getBodyText()); + } + + private String getSubject(IndexableMessage im) { + return Optional.ofNullable( + Strings.emptyToNull( + im.getSubjects() + .stream() + .collect(Collectors.joining(MULTIVALUED_HEADERS_SEPARATOR)))) + .orElse(NO_SUBJECT); + } + + private Emailer firstElasticSearchEmailers(Set<EMailer> emailers) { + return emailers.stream() + .findFirst() + .map(this::fromElasticSearchEmailer) + .orElse(null); + } + + private ImmutableList<Emailer> fromElasticSearchEmailers(Set<EMailer> emailers) { + return emailers.stream() + .map(this::fromElasticSearchEmailer) + .collect(ImmutableCollectors.toImmutableList()); + } + + private Emailer fromElasticSearchEmailer(EMailer emailer) { + return Emailer.builder() + .name(emailer.getName()) + .email(emailer.getAddress()) + .build(); + } + + private ImmutableMap<String, String> toMap(Multimap<String, String> multimap) { + return multimap + .asMap() + .entrySet() + .stream() + .collect(ImmutableCollectors.toImmutableMap(Map.Entry::getKey, x -> joinOnComma(x.getValue()))); + } + + private String getHeaderAsSingleValue(IndexableMessage im, String header) { + return Strings.emptyToNull(joinOnComma(im.getHeaders().get(header))); + } + + private String joinOnComma(Iterable<String> iterable) { + return String.join(MULTIVALUED_HEADERS_SEPARATOR, iterable); + } + + private ZonedDateTime getInternalDate(MailboxMessage mailboxMessage, IndexableMessage im) { + return ZonedDateTime.ofInstant(mailboxMessage.getInternalDate().toInstant(), UTC_ZONE_ID); + } + + private String getTextBody(IndexableMessage im) { + return im.getBodyText().map(Strings::emptyToNull).orElse(null); + } + + private String getHtmlBody(IndexableMessage im) { + return im.getBodyHtml().map(Strings::emptyToNull).orElse(null); + } + + private List<Attachment> getAttachments(List<org.apache.james.mailbox.store.mail.model.Attachment> attachments) { + return attachments.stream() + .map(this::fromMailboxAttachment) + .collect(ImmutableCollectors.toImmutableList()); + } + + private Attachment fromMailboxAttachment(org.apache.james.mailbox.store.mail.model.Attachment attachment) { + return Attachment.builder() + .blobId(attachment.getAttachmentId().getId()) + .type(attachment.getType()) + .size(attachment.getSize()) + .build(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/james-project/blob/7ab817e6/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessagePreviewGenerator.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessagePreviewGenerator.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessagePreviewGenerator.java new file mode 100644 index 0000000..5a162cd --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MessagePreviewGenerator.java @@ -0,0 +1,66 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jmap.model; + +import java.util.Optional; + +import javax.inject.Inject; + +import org.apache.commons.lang3.StringUtils; +import org.apache.james.jmap.utils.HtmlTextExtractor; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; + +public class MessagePreviewGenerator { + + public static final String NO_BODY = "(Empty)"; + public static final int MAX_PREVIEW_LENGTH = 256; + + private final HtmlTextExtractor htmlTextExtractor; + + @Inject + public MessagePreviewGenerator(HtmlTextExtractor htmlTextExtractor) { + this.htmlTextExtractor = htmlTextExtractor; + } + + public String forHTMLBody(Optional<String> body) { + return body.filter(text -> !text.isEmpty()) + .map(this::asText) + .map(this::abbreviate) + .orElse(NO_BODY); + } + + public String forTextBody(Optional<String> body) { + return body.filter(text -> !text.isEmpty()) + .map(this::abbreviate) + .orElse(NO_BODY); + } + + @VisibleForTesting String asText(String body) throws IllegalArgumentException { + Preconditions.checkArgument(body != null); + return htmlTextExtractor.toPlainText(body); + } + + @VisibleForTesting String abbreviate(String body) { + return StringUtils.abbreviate(body, MAX_PREVIEW_LENGTH); + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/7ab817e6/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/GetMessagesMethodTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/GetMessagesMethodTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/GetMessagesMethodTest.java index ce86d03..9bfafe7 100644 --- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/GetMessagesMethodTest.java +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/GetMessagesMethodTest.java @@ -37,8 +37,12 @@ import org.apache.james.jmap.model.ClientId; import org.apache.james.jmap.model.GetMessagesRequest; import org.apache.james.jmap.model.GetMessagesResponse; import org.apache.james.jmap.model.Message; +import org.apache.james.jmap.model.MessageFactory; import org.apache.james.jmap.model.MessageId; +import org.apache.james.jmap.model.MessagePreviewGenerator; import org.apache.james.jmap.model.MessageProperties.MessageProperty; +import org.apache.james.jmap.utils.HtmlTextExtractor; +import org.apache.james.jmap.utils.MailboxBasedHtmlTextExtractor; import org.apache.james.mailbox.MailboxSession; import org.apache.james.mailbox.MessageManager; import org.apache.james.mailbox.acl.SimpleGroupMembershipResolver; @@ -48,6 +52,7 @@ import org.apache.james.mailbox.inmemory.InMemoryMailboxSessionMapperFactory; import org.apache.james.mailbox.model.MailboxPath; import org.apache.james.mailbox.store.MockAuthenticator; import org.apache.james.mailbox.store.StoreMailboxManager; +import org.apache.james.mailbox.store.extractor.DefaultTextExtractor; import org.apache.james.mailbox.store.mail.model.impl.MessageParser; import org.assertj.core.api.Condition; import org.assertj.core.data.MapEntry; @@ -102,11 +107,16 @@ public class GetMessagesMethodTest { private MailboxSession session; private MailboxPath inboxPath; private ClientId clientId; + + private MessageFactory messageFactory; @Before public void setup() throws MailboxException { clientId = ClientId.of("#0"); mailboxSessionMapperFactory = new InMemoryMailboxSessionMapperFactory(); + HtmlTextExtractor htmlTextExtractor = new MailboxBasedHtmlTextExtractor(new DefaultTextExtractor()); + MessagePreviewGenerator messagePreview = new MessagePreviewGenerator(htmlTextExtractor); + messageFactory = new MessageFactory(messagePreview); MockAuthenticator authenticator = new MockAuthenticator(); authenticator.addUser(ROBERT.username, ROBERT.password); UnionMailboxACLResolver aclResolver = new UnionMailboxACLResolver(); @@ -123,28 +133,28 @@ public class GetMessagesMethodTest { @Test public void processShouldThrowWhenNullRequest() { - GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory); + GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory, messageFactory); GetMessagesRequest request = null; assertThatThrownBy(() -> testee.process(request, mock(ClientId.class), mock(MailboxSession.class))).isInstanceOf(NullPointerException.class); } @Test public void processShouldThrowWhenNullSession() { - GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory); + GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory, messageFactory); MailboxSession mailboxSession = null; assertThatThrownBy(() -> testee.process(mock(GetMessagesRequest.class), mock(ClientId.class), mailboxSession)).isInstanceOf(NullPointerException.class); } @Test public void processShouldThrowWhenNullClientId() { - GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory); + GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory, messageFactory); ClientId clientId = null; assertThatThrownBy(() -> testee.process(mock(GetMessagesRequest.class), clientId, mock(MailboxSession.class))).isInstanceOf(NullPointerException.class); } @Test public void processShouldThrowWhenRequestHasAccountId() { - GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory); + GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory, messageFactory); assertThatThrownBy(() -> testee.process( GetMessagesRequest.builder().accountId("abc").build(), mock(ClientId.class), mock(MailboxSession.class))).isInstanceOf(NotImplementedException.class); } @@ -166,7 +176,7 @@ public class GetMessagesMethodTest { new MessageId(ROBERT, inboxPath, message3Uid))) .build(); - GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory); + GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory, messageFactory); List<JmapResponse> result = testee.process(request, clientId, session).collect(Collectors.toList()); assertThat(result).hasSize(1) @@ -195,7 +205,7 @@ public class GetMessagesMethodTest { .ids(ImmutableList.of(new MessageId(ROBERT, inboxPath, messageUid))) .build(); - GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory); + GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory, messageFactory); List<JmapResponse> result = testee.process(request, clientId, session).collect(Collectors.toList()); assertThat(result).hasSize(1) @@ -219,7 +229,7 @@ public class GetMessagesMethodTest { .properties(ImmutableList.of()) .build(); - GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory); + GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory, messageFactory); List<JmapResponse> result = testee.process(request, clientId, session).collect(Collectors.toList()); assertThat(result).hasSize(1) @@ -240,7 +250,7 @@ public class GetMessagesMethodTest { .ids(ImmutableList.of(new MessageId(ROBERT, inboxPath, message1Uid))) .build(); - GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory); + GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory, messageFactory); Stream<JmapResponse> result = testee.process(request, clientId, session); assertThat(result).hasSize(1) @@ -264,7 +274,7 @@ public class GetMessagesMethodTest { Set<MessageProperty> expected = Sets.newHashSet(MessageProperty.id, MessageProperty.subject); - GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory); + GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory, messageFactory); List<JmapResponse> result = testee.process(request, clientId, session).collect(Collectors.toList()); assertThat(result).hasSize(1) @@ -288,7 +298,7 @@ public class GetMessagesMethodTest { Set<MessageProperty> expected = Sets.newHashSet(MessageProperty.id, MessageProperty.textBody); - GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory); + GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory, messageFactory); List<JmapResponse> result = testee.process(request, clientId, session).collect(Collectors.toList()); assertThat(result).hasSize(1) @@ -315,7 +325,7 @@ public class GetMessagesMethodTest { Set<MessageProperty> expected = Sets.newHashSet(MessageProperty.id, MessageProperty.headers); - GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory); + GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory, messageFactory); List<JmapResponse> result = testee.process(request, clientId, session).collect(Collectors.toList()); assertThat(result) @@ -341,7 +351,7 @@ public class GetMessagesMethodTest { .properties(ImmutableList.of("headers.from", "headers.heADER2")) .build(); - GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory); + GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory, messageFactory); List<JmapResponse> result = testee.process(request, clientId, session).collect(Collectors.toList()); assertThat(result) http://git-wip-us.apache.org/repos/asf/james-project/blob/7ab817e6/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java index b8ec9e6..f28630b 100644 --- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java @@ -35,26 +35,34 @@ import org.apache.james.jmap.model.CreationMessage; import org.apache.james.jmap.model.CreationMessage.DraftEmailer; import org.apache.james.jmap.model.CreationMessageId; import org.apache.james.jmap.model.Message; +import org.apache.james.jmap.model.MessageFactory; import org.apache.james.jmap.model.MessageId; +import org.apache.james.jmap.model.MessagePreviewGenerator; import org.apache.james.jmap.model.SetMessagesRequest; import org.apache.james.jmap.model.SetMessagesResponse; import org.apache.james.jmap.send.MailFactory; import org.apache.james.jmap.send.MailMetadata; import org.apache.james.jmap.send.MailSpool; +import org.apache.james.jmap.utils.HtmlTextExtractor; +import org.apache.james.jmap.utils.MailboxBasedHtmlTextExtractor; import org.apache.james.mailbox.MailboxSession; import org.apache.james.mailbox.exception.MailboxException; import org.apache.james.mailbox.store.MailboxSessionMapperFactory; import org.apache.james.mailbox.store.TestId; +import org.apache.james.mailbox.store.extractor.DefaultTextExtractor; import org.apache.james.mailbox.store.mail.MessageMapper; import org.apache.james.mailbox.store.mail.model.Mailbox; import org.apache.james.mailbox.store.mail.model.MailboxMessage; import org.apache.mailet.Mail; +import org.junit.Before; import org.junit.Test; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; public class SetMessagesCreationProcessorTest { + + private MessageFactory messageFactory; private static final Message FAKE_MESSAGE = Message.builder() .id(MessageId.of("user|outbox|1")) @@ -67,10 +75,17 @@ public class SetMessagesCreationProcessorTest { .date(ZonedDateTime.now()) .preview("anything") .build(); + + @Before + public void setup() { + HtmlTextExtractor htmlTextExtractor = new MailboxBasedHtmlTextExtractor(new DefaultTextExtractor()); + MessagePreviewGenerator messagePreview = new MessagePreviewGenerator(htmlTextExtractor); + messageFactory = new MessageFactory(messagePreview); + } @Test public void processShouldReturnEmptyCreatedWhenRequestHasEmptyCreate() { - SetMessagesCreationProcessor sut = new SetMessagesCreationProcessor(null, null, null, null, null, null) { + SetMessagesCreationProcessor sut = new SetMessagesCreationProcessor(null, null, null, null, null, null, messageFactory) { @Override protected Optional<Mailbox> getOutbox(MailboxSession session) throws MailboxException { Mailbox fakeOutbox = (Mailbox) mock(Mailbox.class); @@ -104,7 +119,7 @@ public class SetMessagesCreationProcessorTest { when(mockSessionMapperFactory.createMessageMapper(any(MailboxSession.class))) .thenReturn(stubMapper); - SetMessagesCreationProcessor sut = new SetMessagesCreationProcessor(null, null, mockSessionMapperFactory, null, null, null) { + SetMessagesCreationProcessor sut = new SetMessagesCreationProcessor(null, null, mockSessionMapperFactory, null, null, null, messageFactory) { @Override protected MessageWithId<Message> createMessageInOutboxAndSend(MessageWithId.CreationMessageEntry createdEntry, MailboxSession session, Mailbox outbox, Function<Long, MessageId> buildMessageIdFromUid) { return new MessageWithId<>(createdEntry.getCreationId(), FAKE_MESSAGE); @@ -127,7 +142,7 @@ public class SetMessagesCreationProcessorTest { @Test(expected = MailboxRoleNotFoundException.class) public void processShouldThrowWhenOutboxNotFound() { // Given - SetMessagesCreationProcessor sut = new SetMessagesCreationProcessor(null, null, null, null, null, null) { + SetMessagesCreationProcessor sut = new SetMessagesCreationProcessor(null, null, null, null, null, null, messageFactory) { @Override protected Optional<Mailbox> getOutbox(MailboxSession session) throws MailboxException { return Optional.empty(); @@ -149,7 +164,7 @@ public class SetMessagesCreationProcessorTest { MailFactory mockedMailFactory = mock(MailFactory.class); SetMessagesCreationProcessor sut = new SetMessagesCreationProcessor(null, null, - stubSessionMapperFactory, new MIMEMessageConverter(), mockedMailSpool, mockedMailFactory) { + stubSessionMapperFactory, new MIMEMessageConverter(), mockedMailSpool, mockedMailFactory, messageFactory) { @Override protected Optional<Mailbox> getOutbox(MailboxSession session) throws MailboxException { TestId stubMailboxId = mock(TestId.class); @@ -178,7 +193,7 @@ public class SetMessagesCreationProcessorTest { MailFactory mockedMailFactory = mock(MailFactory.class); SetMessagesCreationProcessor sut = new SetMessagesCreationProcessor(null, null, - stubSessionMapperFactory, new MIMEMessageConverter(), mockedMailSpool, mockedMailFactory) { + stubSessionMapperFactory, new MIMEMessageConverter(), mockedMailSpool, mockedMailFactory, messageFactory) { @Override protected Optional<Mailbox> getOutbox(MailboxSession session) throws MailboxException { TestId stubMailboxId = mock(TestId.class); http://git-wip-us.apache.org/repos/asf/james-project/blob/7ab817e6/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MailboxMessageTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MailboxMessageTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MailboxMessageTest.java index d34d7a9..f356460 100644 --- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MailboxMessageTest.java +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MailboxMessageTest.java @@ -19,6 +19,7 @@ package org.apache.james.jmap.model; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; import java.time.ZoneId; import java.time.ZonedDateTime; @@ -30,11 +31,13 @@ import javax.mail.Flags.Flag; import javax.mail.util.SharedByteArrayInputStream; import org.apache.commons.io.IOUtils; +import org.apache.james.jmap.utils.HtmlTextExtractor; import org.apache.james.mailbox.store.TestId; import org.apache.james.mailbox.store.mail.model.AttachmentId; import org.apache.james.mailbox.store.mail.model.MailboxMessage; import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage; +import org.junit.Before; import org.junit.Test; import com.google.common.collect.ImmutableList; @@ -47,6 +50,17 @@ public class MailboxMessageTest { private static final ZonedDateTime ZONED_DATE = ZonedDateTime.of(2015, 07, 14, 12, 30, 42, 0, UTC_ZONE_ID); private static final Date INTERNAL_DATE = Date.from(ZONED_DATE.toInstant()); + private MessageFactory messageFactory; + private MessagePreviewGenerator messagePreview ; + private HtmlTextExtractor htmlTextExtractor; + + @Before + public void setUp() { + htmlTextExtractor = mock(HtmlTextExtractor.class); + messagePreview = new MessagePreviewGenerator(htmlTextExtractor); + messageFactory = new MessageFactory(messagePreview); + } + @Test(expected=IllegalStateException.class) public void buildShouldThrowWhenIdIsNull() { Message.builder().build(); @@ -241,8 +255,7 @@ public class MailboxMessageTest { new PropertyBuilder(), MAILBOX_ID); testMail.setModSeq(MOD_SEQ); - - Message testee = Message.fromMailboxMessage(testMail, ImmutableList.of(), x -> MessageId.of("user|box|" + x)); + Message testee = messageFactory.fromMailboxMessage(testMail, ImmutableList.of(), x -> MessageId.of("user|box|" + x)); assertThat(testee) .extracting(Message::getPreview, Message::getSize, Message::getSubject, Message::getHeaders, Message::getDate) .containsExactly("(Empty)", 0L, "(No subject)", ImmutableMap.of(), ZONED_DATE); @@ -263,8 +276,7 @@ public class MailboxMessageTest { new PropertyBuilder(), MAILBOX_ID); testMail.setModSeq(MOD_SEQ); - - Message testee = Message.fromMailboxMessage(testMail, ImmutableList.of(), x -> MessageId.of("user|box|" + x)); + Message testee = messageFactory.fromMailboxMessage(testMail, ImmutableList.of(), x -> MessageId.of("user|box|" + x)); assertThat(testee) .extracting(Message::isIsUnread, Message::isIsFlagged, Message::isIsAnswered, Message::isIsDraft) .containsExactly(true, true, true, true); @@ -306,7 +318,7 @@ public class MailboxMessageTest { .put("in-reply-to", "<[email protected]>") .put("other-header", "other header value") .build(); - Message testee = Message.fromMailboxMessage(testMail, ImmutableList.of(), x -> MessageId.of("user|box|" + x)); + Message testee = messageFactory.fromMailboxMessage(testMail, ImmutableList.of(), x -> MessageId.of("user|box|" + x)); Message expected = Message.builder() .id(MessageId.of("user|box|0")) .blobId("0") @@ -341,32 +353,9 @@ public class MailboxMessageTest { new PropertyBuilder(), MAILBOX_ID); testMail.setModSeq(MOD_SEQ); - - Message testee = Message.fromMailboxMessage(testMail, ImmutableList.of(), x -> MessageId.of("user|box|" + x)); + Message testee = messageFactory.fromMailboxMessage(testMail, ImmutableList.of(), x -> MessageId.of("user|box|" + x)); assertThat(testee.getTextBody()).hasValue("Mail body"); } - - @Test - public void bodyWith256LengthShouldNotBeTruncated() { - String body256 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" - + "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" - + "00000000001111111111222222222233333333334444444444555555"; - assertThat(body256.length()).isEqualTo(256); - assertThat(Message.computePreview(body256)).isEqualTo(body256); - } - - @Test - public void bodyWith257LengthShouldBeTruncated() { - String body257 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" - + "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" - + "000000000011111111112222222222333333333344444444445555555"; - String expected = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" - + "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" - + "00000000001111111111222222222233333333334444444444555..."; - assertThat(body257.length()).isEqualTo(257); - assertThat(expected.length()).isEqualTo(256); - assertThat(Message.computePreview(body257)).isEqualTo(expected); - } @Test public void previewShouldBeLimitedTo256Length() throws Exception { @@ -389,8 +378,7 @@ public class MailboxMessageTest { new PropertyBuilder(), MAILBOX_ID); testMail.setModSeq(MOD_SEQ); - - Message testee = Message.fromMailboxMessage(testMail, ImmutableList.of(), x -> MessageId.of("user|box|" + x)); + Message testee = messageFactory.fromMailboxMessage(testMail, ImmutableList.of(), x -> MessageId.of("user|box|" + x)); assertThat(testee.getPreview()).isEqualTo(expectedPreview); } @@ -405,8 +393,7 @@ public class MailboxMessageTest { new PropertyBuilder(), MAILBOX_ID); testMail.setModSeq(MOD_SEQ); - - Message testee = Message.fromMailboxMessage(testMail, ImmutableList.of(), x -> MessageId.of("user|box|" + x)); + Message testee = messageFactory.fromMailboxMessage(testMail, ImmutableList.of(), x -> MessageId.of("user|box|" + x)); assertThat(testee.getAttachments()).isEmpty(); } @@ -430,8 +417,7 @@ public class MailboxMessageTest { .size(payload.length()) .type(type) .build(); - - Message testee = Message.fromMailboxMessage(testMail, + Message testee = messageFactory.fromMailboxMessage(testMail, ImmutableList.of(org.apache.james.mailbox.store.mail.model.Attachment.builder() .attachmentId(AttachmentId.from(blodId)) .bytes(payload.getBytes()) http://git-wip-us.apache.org/repos/asf/james-project/blob/7ab817e6/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessagePreviewGeneratorTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessagePreviewGeneratorTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessagePreviewGeneratorTest.java new file mode 100644 index 0000000..f9ae3b3 --- /dev/null +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MessagePreviewGeneratorTest.java @@ -0,0 +1,187 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jmap.model; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Optional; + +import org.apache.james.jmap.utils.HtmlTextExtractor; +import org.junit.Before; +import org.junit.Test; + +public class MessagePreviewGeneratorTest { + + private MessagePreviewGenerator testee; + private HtmlTextExtractor htmlTextExtractor; + + @Before + public void setUp() { + htmlTextExtractor = mock(HtmlTextExtractor.class); + testee = new MessagePreviewGenerator(htmlTextExtractor); + } + + @Test + public void forHTMLBodyShouldReturnTruncatedStringWithoutHtmlTagWhenStringContainTagsAndIsLongerThan256Characters() { + //Given + String body = "This is a <b>HTML</b> mail containing <u>underlined part</u>, <i>italic part</i> and <u><i>underlined AND italic part</i></u>9999999999" + + "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" + + "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" + + "000000000011111111112222222222333333333344444444445555555"; + String bodyWithoutTags = "This is a HTML mail containing underlined part, italic part and underlined AND italic part9999999999" + + "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" + + "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" + + "000000000011111111112222222222333333333344444444445555555"; + String expected = "This is a HTML mail containing underlined part, italic part and underlined AND italic part9999999999" + + "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" + + "00000000001111111111222222222233333333334444444444555..."; + //When + when(htmlTextExtractor.toPlainText(body)) + .thenReturn(bodyWithoutTags); + String actual = testee.forHTMLBody(Optional.of(body)); + //Then + assertThat(actual).isEqualTo(expected); + } + + @Test + public void forHTMLBodyShouldReturnStringContainingEmptyWhenEmptyString() { + //Given + String body = "" ; + String expected = "(Empty)" ; + //When + when(htmlTextExtractor.toPlainText(body)) + .thenReturn(expected); + String actual = testee.forHTMLBody(Optional.of(body)); + //Then + assertThat(actual).isEqualTo(expected); + } + + @Test + public void forTextBodyShouldReturnTruncatedStringWhenStringContainTagsAndIsLongerThan256Characters() { + //Given + String body = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" + + "This is a <b>HTML</b> mail containing <u>underlined part</u>, <i>italic part</i>88888888889999999999" + + "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" + + "000000000011111111112222222222333333333344444444445555555"; + String expected = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" + + "This is a <b>HTML</b> mail containing <u>underlined part</u>, <i>italic part</i>88888888889999999999" + + "00000000001111111111222222222233333333334444444444555..."; + //When + String actual = testee.forTextBody(Optional.of(body)); + //Then + assertThat(actual).isEqualTo(expected); + } + + @Test + public void forTextBodyShouldReturnStringContainingEmptyWhenEmptyString() { + //Given + String expected = "(Empty)" ; + //When + String actual = testee.forTextBody(Optional.empty()); + //Then + assertThat(actual).isEqualTo(expected); + } + + @Test + public void asTextShouldReturnStringWithoutHtmlTag() { + //Given + String body = "This is a <b>HTML</b> mail"; + String expected = "This is a HTML mail"; + //When + when(htmlTextExtractor.toPlainText(body)) + .thenReturn(expected); + String actual = testee.asText(body); + //Then + assertThat(actual).isEqualTo(expected); + } + + @Test(expected = IllegalArgumentException.class) + public void asTextShouldThrowWhenNullString () { + testee.asText(null); + } + + @Test + public void asTextShouldReturnEmptyStringWhenEmptyString() { + //Given + String body = ""; + String expected = ""; + //When + when(htmlTextExtractor.toPlainText(body)) + .thenReturn(expected); + String actual = testee.asText(body); + //Then + assertThat(actual).isEqualTo(expected); + } + + @Test + public void abbreviateShouldNotTruncateAbodyWith256Length() { + //Given + String body256 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" + + "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" + + "00000000001111111111222222222233333333334444444444555555"; + //When + String actual = testee.abbreviate(body256); + //Then + assertThat(body256.length()).isEqualTo(256); + assertThat(actual).isEqualTo(body256); + } + + @Test + public void abbreviateShouldTruncateAbodyWith257Length() { + //Given + String body257 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" + + "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" + + "000000000011111111112222222222333333333344444444445555555"; + String expected = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" + + "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" + + "00000000001111111111222222222233333333334444444444555..."; + //When + String actual = testee.abbreviate(body257); + //Then + assertThat(body257.length()).isEqualTo(257); + assertThat(expected.length()).isEqualTo(256); + assertThat(actual).isEqualTo(expected); + } + + @Test + public void abbreviateShouldReturnNullStringWhenNullString() { + //Given + String body = null; + String expected = null; + //When + String actual = testee.abbreviate(body); + //Then + assertThat(actual).isEqualTo(expected); + } + + @Test + public void abbreviateShouldReturnEmptyStringWhenEmptyString() { + //Given + String body = ""; + String expected = ""; + //When + String actual = testee.abbreviate(body); + //Then + assertThat(actual).isEqualTo(expected); + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/7ab817e6/server/protocols/jmap/src/test/java/org/apache/james/jmap/send/MailFactoryTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/send/MailFactoryTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/send/MailFactoryTest.java index 98cd1d0..f5128a6 100644 --- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/send/MailFactoryTest.java +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/send/MailFactoryTest.java @@ -27,9 +27,14 @@ import javax.mail.Flags; import javax.mail.util.SharedByteArrayInputStream; import org.apache.james.jmap.model.Message; +import org.apache.james.jmap.model.MessageFactory; import org.apache.james.jmap.model.MessageId; +import org.apache.james.jmap.model.MessagePreviewGenerator; +import org.apache.james.jmap.utils.HtmlTextExtractor; +import org.apache.james.jmap.utils.MailboxBasedHtmlTextExtractor; import org.apache.james.mailbox.FlagsBuilder; import org.apache.james.mailbox.store.TestId; +import org.apache.james.mailbox.store.extractor.DefaultTextExtractor; import org.apache.james.mailbox.store.mail.model.MailboxMessage; import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder; import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage; @@ -69,7 +74,10 @@ public class MailFactoryTest { new FlagsBuilder().add(Flags.Flag.SEEN).build(), propertyBuilder, TestId.of(2)); - jmapMessage = Message.fromMailboxMessage(mailboxMessage, ImmutableList.of(), x -> MessageId.of("test|test|" + x)); + HtmlTextExtractor htmlTextExtractor = new MailboxBasedHtmlTextExtractor(new DefaultTextExtractor()); + MessagePreviewGenerator messagePreview = new MessagePreviewGenerator(htmlTextExtractor); + MessageFactory messageFactory = new MessageFactory(messagePreview) ; + jmapMessage = messageFactory.fromMailboxMessage(mailboxMessage, ImmutableList.of(), x -> MessageId.of("test|test|" + x)); } @Test(expected=NullPointerException.class) --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
