james-server-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From matth...@apache.org
Subject [2/3] james-project git commit: JAMES-1745 Handle attachments in GetMessages
Date Fri, 03 Jun 2016 12:36:33 GMT
JAMES-1745 Handle attachments in GetMessages


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

Branch: refs/heads/master
Commit: 4e13597bb71d35cec228a0d7a744c174c0728a7c
Parents: 7367a6f
Author: Antoine Duprat <aduprat@linagora.com>
Authored: Wed Jun 1 09:19:24 2016 +0200
Committer: Antoine Duprat <antduprat@gmail.com>
Committed: Fri Jun 3 13:10:59 2016 +0200

----------------------------------------------------------------------
 .../integration/GetMessagesMethodTest.java      |  39 ++++++-
 .../src/test/resources/twoAttachments.eml       |  73 ++++++++++++
 .../protocols/jmap/doc/specs/spec/message.mdwn  |   6 -
 .../james/jmap/methods/GetMessagesMethod.java   | 115 ++++++++++++++++---
 .../methods/SetMessagesCreationProcessor.java   |   2 +-
 .../org/apache/james/jmap/model/Attachment.java |  47 +++++++-
 .../org/apache/james/jmap/model/Message.java    |  39 +++++--
 .../james/jmap/json/ParsingWritingObjects.java  |   1 -
 .../jmap/methods/GetMessagesMethodTest.java     |  24 ++--
 .../apache/james/jmap/model/AttachmentTest.java |  10 +-
 .../james/jmap/model/MailboxMessageTest.java    |  99 ++++++++++++++--
 .../apache/james/jmap/send/MailFactoryTest.java |   3 +-
 .../jmap/src/test/resources/json/message.json   |   2 +-
 13 files changed, 384 insertions(+), 76 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/4e13597b/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 b013c3e..90776d8 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
@@ -51,6 +51,9 @@ import com.jayway.restassured.http.ContentType;
 public abstract class GetMessagesMethodTest {
     private static final String NAME = "[0][0]";
     private static final String ARGUMENTS = "[0][1]";
+    private static final String ATTACHMENTS = ARGUMENTS + ".list[0].attachments";
+    private static final String FIRST_ATTACHMENT = ATTACHMENTS + "[0]";
+    private static final String SECOND_ATTACHMENT = ATTACHMENTS + "[1]";
 
     protected abstract GuiceJamesServer createJmapServer();
 
@@ -66,6 +69,7 @@ public abstract class GetMessagesMethodTest {
         jmapServer.start();
         RestAssured.port = jmapServer.getJmapPort();
         RestAssured.config = newConfig().encoderConfig(encoderConfig().defaultContentCharset(Charsets.UTF_8));
+        RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
 
         String domain = "domain.tld";
         username = "username@" + domain;
@@ -185,7 +189,9 @@ public abstract class GetMessagesMethodTest {
             .body(ARGUMENTS + ".list[0].isUnread", equalTo(true))
             .body(ARGUMENTS + ".list[0].preview", equalTo("testmail"))
             .body(ARGUMENTS + ".list[0].headers", equalTo(ImmutableMap.of("subject", "my test subject")))
-            .body(ARGUMENTS + ".list[0].date", equalTo("2014-10-30T14:12:00Z"));
+            .body(ARGUMENTS + ".list[0].date", equalTo("2014-10-30T14:12:00Z"))
+            .body(ARGUMENTS + ".list[0].hasAttachment", equalTo(false))
+            .body(ATTACHMENTS, empty());
     }
 
     @Test
@@ -331,4 +337,35 @@ public abstract class GetMessagesMethodTest {
             .body(ARGUMENTS + ".list[0].id", equalTo("username@domain.tld|mailbox|1"))
             .body(ARGUMENTS + ".list[0].subject", equalTo("my test subject"));
     }
+
+    @Test
+    public void getMessagesShouldReturnAttachmentsWhenSome() 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"),
+                ClassLoader.getSystemResourceAsStream("twoAttachments.eml"), 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(ARGUMENTS + ".list[0].hasAttachment", equalTo(true))
+            .body(ATTACHMENTS, hasSize(2))
+            .body(FIRST_ATTACHMENT + ".blobId", equalTo("223a76c0e8c1b1762487d8e0598bd88497d73ef2"))
+            .body(FIRST_ATTACHMENT + ".type", equalTo("image/jpeg"))
+            .body(FIRST_ATTACHMENT + ".size", equalTo(846))
+            .body(SECOND_ATTACHMENT + ".blobId", equalTo("58aa22c2ec5770fb9e574ba19008dbfc647eba43"))
+            .body(SECOND_ATTACHMENT + ".type", equalTo("image/jpeg"))
+            .body(SECOND_ATTACHMENT + ".size", equalTo(597));
+    }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/4e13597b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/twoAttachments.eml
----------------------------------------------------------------------
diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/twoAttachments.eml b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/twoAttachments.eml
new file mode 100644
index 0000000..85f0e3b
--- /dev/null
+++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/twoAttachments.eml
@@ -0,0 +1,73 @@
+Return-Path: <from@linagora.com>
+Received: from alderaan.linagora.com (smtp.linagora.dc1 [172.16.18.53])
+	 by imap (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA;
+	 Fri, 27 Feb 2015 20:32:14 +0100
+X-Sieve: CMU Sieve 2.2
+Received: from canondir.par.lng (unknown [92.103.166.6])
+	(using TLSv1 with cipher AES256-SHA (256/256 bits))
+	(No client certificate requested)
+	by alderaan.linagora.com (Postfix) with ESMTPSA id BAB0D728
+	for <to@linagora.com>; Fri, 27 Feb 2015 20:31:38 +0100 (CET)
+X-Priority: 3 (Normal)
+From: "From"
+ <from@linagora.com>
+To: "To"
+ <to@linagora.com>
+Subject: [8/10]Attached Image light with text
+Date: Fri, 27 Mar 2015 21:48:38 +0100
+Message-Id: <20150227203838.0011.CanonTxNo.4037@canondir.par.lng>
+Mime-Version: 1.0
+Content-Type: multipart/mixed;
+ boundary="AHNPACBLDCDIDAGGGDDFAABECGCA"
+
+--AHNPACBLDCDIDAGGGDDFAABECGCA
+Content-Type: text/html; charset=utf-8; format=flowed
+Content-Transfer-Encoding: 8bit
+
+<b>html</b>
+
+--AHNPACBLDCDIDAGGGDDFAABECGCA
+Content-Type: image/jpeg;
+ name="4037_014.jpg"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment;
+ filename="4037_014.jpg"
+
+/9j/4X2cRXhpZgAASUkqAAgAAAANAA8BAgAKAAAAqgAAABABAgAJAAAAtAAAABIBAwABAAAA
+AQAAABoBBQABAAAAvgAAABsBBQABAAAAxgAAACgBAwABAAAAAgAAADEBAgAKAAAAzgAAADIB
+AgAUAAAA2AAAABMCAwABAAAAAgAAAGmHBAABAAAAfAIAAKXEBwDQAAAA7AAAANLGBwBAAAAA
+vAEAANPGBwCAAAAA/AEAAEwqAABQYW5hc29uaWMARE1DLUZaNDUAALQAAAABAAAAtAAAAAEA
+AABWZXIuMS4wICAAMjAxNDowMjoyNSAxMDozMjowOQBQcmludElNADAyNTAAAA4AAQAWABYA
+AgAAAAAAAwBkAAAABwAAAAAACAAAAAAACQAAAAAACgAAAAAACwCsAAAADAAAAAAADQAAAAAA
+DgDEAAAAAAEFAAAAAQEBAAAAEAGAAAAACREAABAnAAALDwAAECcAAJcFAAAQJwAAsAgAABAn
+AAABHAAAECcAAF4CAAAQJwAAiwAAABAnAADLAwAAECcAAOUbAAAQJwAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+
+--AHNPACBLDCDIDAGGGDDFAABECGCA
+Content-Type: image/jpeg;
+ name="4037_015.jpg"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment;
+ filename="4037_015.jpg"
+
+iVBORw0KGgoAAAANSUhEUgAABYUAAAL4CAIAAACrzA8fAAAACXBIWXMAAAsTAAALEwEAmpwY
+AAAAB3RJTUUH3wMNDiUMca0XkQAAIABJREFUeNq8vWm0bdlVHja/tc+99zX13qv2VSeVmlIv
+FSqEOhA2AoEhxMRBg8QOyEkG6WMDSZzhMQTGDDcwiJPhBBMby4SMDJthgmOMAYvGMcF0QgIU
+WRKobymp+qpX9fp779nry4+9mjnnWuuc80qMvCpdvXrv3nP22Xutueb85je/D1/3+q+ipF9z
+nGfGdZznGCMjKaSIkJQwBYSAEMI0Tatptdrb21utVqvVtJKAAAABEAAiUP+IiECYfsfyJf1B
+10+cOHH48OGVlZVSypaWlv379zc2Nr722msNDQ2u+y9zaYQeAQAAAACCHgEAAAB0y969e+ON
+KnpOe3v7JZdccj6fwPXr10+aNKnAB956663Zs2fjTgMAAADOKzhOAQAAAFCYs+yO0XfNNQAA
+AAAA/nNBfgQAAADQDVddddW6detSqdQZ7JvL5W655RZIEgAAAAAAEayKigqcBQAAAKAAhw8f
+3rZt25AhQwYNGpTJZHq4V2tr6/bt25csWbJjxw6cQwAAAACACMiPAAAAAAAAAAAAQH8D/wgA
+AAAAAAAAAAD0N9AjAAAAAAAAAAAA0N9AjwAAAAAAAAAAAEB/Az0CAAAAAAAAAAAA/Q30CAAA
+AAAAAAAAAPQ30CMAAAAAAAAAAADQ30CPAAAAAAAAAAAAQH/z/0IKJwmNQbpqAAAAAElFTkSu
+QmCC
+
+--AHNPACBLDCDIDAGGGDDFAABECGCA--

http://git-wip-us.apache.org/repos/asf/james-project/blob/4e13597b/server/protocols/jmap/doc/specs/spec/message.mdwn
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/doc/specs/spec/message.mdwn b/server/protocols/jmap/doc/specs/spec/message.mdwn
index 2c65069..ab144cb 100644
--- a/server/protocols/jmap/doc/specs/spec/message.mdwn
+++ b/server/protocols/jmap/doc/specs/spec/message.mdwn
@@ -35,9 +35,6 @@ A **Message** object has the following properties:
 - **isDraft**: `Boolean` (Mutable by the server only)
   Is the message a draft? This corresponds to the `\Draft` system flag in IMAP.
 - **hasAttachment**: `Boolean`
-  <aside class="warning">
-  Not implemented
-  </aside>
   Does the message have any attachments?
 - **headers**: `String[String]`
   A map of header name to (decoded) header value for all headers in the message. For headers that occur multiple times (e.g. `Received`), the values are concatenated with a single new line (`\n`) character in between each one.
@@ -67,9 +64,6 @@ A **Message** object has the following properties:
   </aside>
   The HTML body part for the message if present. If there is only a plain text version of the body, an HTML version will be generated from this. Any scripting content, or references to external plugins, MUST be stripped from the HTML by the server.
 - **attachments**: `Attachment[]|null`
-  <aside class="warning">
-  Not implemented
-  </aside>
   An array of attachment objects (see below) detailing all the attachments to the message.
 - **attachedMessages**: `String[Message]|null`
   <aside class="warning">

http://git-wip-us.apache.org/repos/asf/james-project/blob/4e13597b/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 b490ad7..2993ff3 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
@@ -38,11 +38,16 @@ import org.apache.james.jmap.model.MessageId;
 import org.apache.james.jmap.model.MessageProperties;
 import org.apache.james.jmap.model.MessageProperties.HeaderProperty;
 import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.model.MailboxPath;
 import org.apache.james.mailbox.model.MessageRange;
+import org.apache.james.mailbox.store.mail.AttachmentMapper;
+import org.apache.james.mailbox.store.mail.AttachmentMapperFactory;
 import org.apache.james.mailbox.store.mail.MailboxMapperFactory;
 import org.apache.james.mailbox.store.mail.MessageMapper;
 import org.apache.james.mailbox.store.mail.MessageMapperFactory;
+import org.apache.james.mailbox.store.mail.model.Attachment;
+import org.apache.james.mailbox.store.mail.model.AttachmentId;
 import org.apache.james.mailbox.store.mail.model.Mailbox;
 import org.apache.james.mailbox.store.mail.model.MailboxMessage;
 import org.apache.james.util.streams.Collectors;
@@ -62,13 +67,16 @@ public class GetMessagesMethod implements Method {
     private static final Method.Response.Name RESPONSE_NAME = Method.Response.name("messages");
     private final MessageMapperFactory messageMapperFactory;
     private final MailboxMapperFactory mailboxMapperFactory;
+    private final AttachmentMapperFactory attachmentMapperFactory;
 
     @Inject
     @VisibleForTesting GetMessagesMethod(
-            MessageMapperFactory messageMapperFactory, 
-            MailboxMapperFactory mailboxMapperFactory) {
+            MessageMapperFactory messageMapperFactory,
+            MailboxMapperFactory mailboxMapperFactory,
+            AttachmentMapperFactory attachmentMapperFactory) {
         this.messageMapperFactory = messageMapperFactory;
         this.mailboxMapperFactory = mailboxMapperFactory;
+        this.attachmentMapperFactory = attachmentMapperFactory;
     }
     
     @Override
@@ -110,8 +118,8 @@ public class GetMessagesMethod implements Method {
     private GetMessagesResponse getMessagesResponse(MailboxSession mailboxSession, GetMessagesRequest getMessagesRequest) {
         getMessagesRequest.getAccountId().ifPresent(GetMessagesMethod::notImplemented);
         
-        Function<MessageId, Stream<Pair<MailboxMessage, MailboxPath>>> loadMessages = loadMessage(mailboxSession);
-        Function<Pair<MailboxMessage, MailboxPath>, Message> convertToJmapMessage = toJmapMessage(mailboxSession);
+        Function<MessageId, Stream<CompletedMailboxMessage>> loadMessages = loadMessage(mailboxSession);
+        Function<CompletedMailboxMessage, Message> convertToJmapMessage = toJmapMessage(mailboxSession);
         
         List<Message> result = getMessagesRequest.getIds().stream()
             .flatMap(loadMessages)
@@ -126,17 +134,14 @@ public class GetMessagesMethod implements Method {
     }
 
     
-    private Function<Pair<MailboxMessage, MailboxPath>, Message> toJmapMessage(MailboxSession mailboxSession) {
-        return (value) -> {
-            MailboxMessage messageResult = value.getValue0();
-            MailboxPath mailboxPath = value.getValue1();
-            return Message.fromMailboxMessage(messageResult, uid -> new MessageId(mailboxSession.getUser(), mailboxPath , uid));
-        };
+    private Function<CompletedMailboxMessage, Message> toJmapMessage(MailboxSession mailboxSession) {
+        return (completedMailboxMessage) -> Message.fromMailboxMessage(
+                completedMailboxMessage.mailboxMessage, 
+                completedMailboxMessage.attachments, 
+                uid -> new MessageId(mailboxSession.getUser(), completedMailboxMessage.mailboxPath , uid));
     }
 
-    private Function<MessageId, Stream<
-                                    Pair<MailboxMessage,
-                                         MailboxPath>>> 
+    private Function<MessageId, Stream<CompletedMailboxMessage>> 
                 loadMessage(MailboxSession mailboxSession) {
 
         return Throwing
@@ -148,15 +153,89 @@ public class GetMessagesMethod implements Method {
                              messageMapper.findInMailbox(mailbox, MessageRange.one(messageId.getUid()), MessageMapper.FetchType.Full, 1),
                              mailboxPath
                              );
-         })
-                .andThen(this::iteratorToStream);
+                })
+                .andThen(Throwing.function((pair) -> retrieveCompleteMailboxMessages(pair, mailboxSession)));
     }
     
-    private Stream<Pair<MailboxMessage, MailboxPath>> iteratorToStream(Pair<Iterator<MailboxMessage>, MailboxPath> value) {
+    private Stream<CompletedMailboxMessage> retrieveCompleteMailboxMessages(Pair<Iterator<MailboxMessage>, MailboxPath> value, MailboxSession mailboxSession) throws MailboxException {
         Iterable<MailboxMessage> iterable = () -> value.getValue0();
         Stream<MailboxMessage> targetStream = StreamSupport.stream(iterable.spliterator(), false);
-        
+
+        Function<List<AttachmentId>, List<Attachment>> retrieveAttachments = retrieveAttachments(attachmentMapperFactory.getAttachmentMapper(mailboxSession));
+
         MailboxPath mailboxPath = value.getValue1();
-        return targetStream.map(x -> Pair.with(x, mailboxPath));
+        return targetStream
+                .map(message -> CompletedMailboxMessage.builder().mailboxMessage(message).attachmentIds(message.getAttachmentsIds()))
+                .map(builder -> builder.mailboxPath(mailboxPath))
+                .map(builder -> builder.retrieveAttachments(retrieveAttachments))
+                .map(builder -> builder.build()); 
+    }
+
+    private static class CompletedMailboxMessage {
+
+        public static Builder builder() {
+            return new Builder();
+        }
+
+        public static class Builder {
+
+            private MailboxMessage mailboxMessage;
+            private List<AttachmentId> attachmentIds;
+            private MailboxPath mailboxPath;
+            private Function<List<AttachmentId>, List<Attachment>> retrieveAttachments;
+
+            private Builder() {
+            }
+
+            public Builder mailboxMessage(MailboxMessage mailboxMessage) {
+                Preconditions.checkArgument(mailboxMessage != null);
+                this.mailboxMessage = mailboxMessage;
+                return this;
+            }
+
+            public Builder attachmentIds(List<AttachmentId> attachmentIds) {
+                Preconditions.checkArgument(attachmentIds != null);
+                this.attachmentIds = attachmentIds;
+                return this;
+            }
+
+            public Builder mailboxPath(MailboxPath mailboxPath) {
+                Preconditions.checkArgument(mailboxPath != null);
+                this.mailboxPath = mailboxPath;
+                return this;
+            }
+
+            public Builder retrieveAttachments(Function<List<AttachmentId>, List<Attachment>> retrieveAttachments) {
+                Preconditions.checkArgument(retrieveAttachments != null);
+                this.retrieveAttachments = retrieveAttachments;
+                return this;
+            }
+
+            public CompletedMailboxMessage build() {
+                Preconditions.checkState(mailboxMessage != null);
+                Preconditions.checkState(attachmentIds != null);
+                Preconditions.checkState(mailboxPath != null);
+                Preconditions.checkState(retrieveAttachments != null);
+                return new CompletedMailboxMessage(mailboxMessage, retrieveAttachments.apply(attachmentIds), mailboxPath);
+            }
+        }
+
+        private final MailboxMessage mailboxMessage;
+        private final List<Attachment> attachments;
+        private final MailboxPath mailboxPath;
+
+        public CompletedMailboxMessage(MailboxMessage mailboxMessage, List<Attachment> attachments, MailboxPath mailboxPath) {
+            this.mailboxMessage = mailboxMessage;
+            this.attachments = attachments;
+            this.mailboxPath = mailboxPath;
+        }
+    }
+
+    private Function<List<AttachmentId>, List<Attachment>> retrieveAttachments(AttachmentMapper attachmentMapper) {
+        return (attachmentsIds) -> {
+            return attachmentsIds.stream()
+                    .map(Throwing.function(id -> attachmentMapper.getAttachment(id)))
+                    .collect(Collectors.toImmutableList());
+        };
     }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/4e13597b/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 c7bacc5..a5097d7 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
@@ -195,7 +195,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, buildMessageIdFromUid);
+            Message jmapMessage = Message.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/4e13597b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Attachment.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Attachment.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Attachment.java
index d0550a2..8aa0927 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Attachment.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Attachment.java
@@ -24,6 +24,8 @@ import java.util.Optional;
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 
@@ -88,22 +90,21 @@ public class Attachment {
         public Attachment build() {
             Preconditions.checkState(!Strings.isNullOrEmpty(blobId), "'blobId' is mandatory");
             Preconditions.checkState(!Strings.isNullOrEmpty(type), "'type' is mandatory");
-            Preconditions.checkState(!Strings.isNullOrEmpty(name), "'name' is mandatory");
             Preconditions.checkState(size != null, "'size' is mandatory");
-            return new Attachment(blobId, type, name, size, Optional.ofNullable(cid), isInline, Optional.ofNullable(width), Optional.ofNullable(height));
+            return new Attachment(blobId, type, Optional.ofNullable(name), size, Optional.ofNullable(cid), isInline, Optional.ofNullable(width), Optional.ofNullable(height));
         }
     }
 
     private final String blobId;
     private final String type;
-    private final String name;
+    private final Optional<String> name;
     private final long size;
     private final Optional<String> cid;
     private final boolean isInline;
     private final Optional<Long> width;
     private final Optional<Long> height;
 
-    @VisibleForTesting Attachment(String blobId, String type, String name, long size, Optional<String> cid, boolean isInline, Optional<Long> width, Optional<Long> height) {
+    @VisibleForTesting Attachment(String blobId, String type, Optional<String> name, long size, Optional<String> cid, boolean isInline, Optional<Long> width, Optional<Long> height) {
         this.blobId = blobId;
         this.type = type;
         this.name = name;
@@ -122,7 +123,7 @@ public class Attachment {
         return type;
     }
 
-    public String getName() {
+    public Optional<String> getName() {
         return name;
     }
 
@@ -145,4 +146,40 @@ public class Attachment {
     public Optional<Long> getHeight() {
         return height;
     }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof Attachment) {
+            Attachment other = (Attachment) obj;
+            return Objects.equal(blobId, other.blobId)
+                && Objects.equal(type, other.type)
+                && Objects.equal(name, other.name)
+                && Objects.equal(size, other.size)
+                && Objects.equal(cid, other.cid)
+                && Objects.equal(isInline, other.isInline)
+                && Objects.equal(width, other.width)
+                && Objects.equal(height, other.height);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(blobId, type, name, size, cid, isInline, width, height);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects
+                .toStringHelper(this)
+                .add("blobId", blobId)
+                .add("type", type)
+                .add("name", name)
+                .add("size", size)
+                .add("cid", cid)
+                .add("isInline", isInline)
+                .add("width", width)
+                .add("height", height)
+                .toString();
+    }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/4e13597b/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 d299489..721c0f7 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
@@ -26,9 +26,9 @@ 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 org.apache.commons.lang.NotImplementedException;
 import org.apache.james.jmap.methods.GetMessagesMethod;
 import org.apache.james.jmap.methods.JmapResponseWriterImpl;
 import org.apache.james.jmap.model.message.EMailer;
@@ -60,11 +60,9 @@ public class Message {
     }
 
     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);
-        if (im.getHasAttachment()) {
-            throw new NotImplementedException();
-        }
         MessageId messageId = uidToMessageId.apply(im.getId());
         return builder()
                 .id(messageId)
@@ -88,6 +86,7 @@ public class Message {
                 .preview(getPreview(im))
                 .textBody(getTextBody(im))
                 .htmlBody(getHtmlBody(im))
+                .attachments(getAttachments(attachments))
                 .build();
     }
 
@@ -181,6 +180,20 @@ public class Message {
             && im.getSubType().equals(MediaType.HTML_UTF_8.subtype());
     }
 
+    private static List<Attachment> getAttachments(List<org.apache.james.mailbox.store.mail.model.Attachment> attachments) {
+        return attachments.stream()
+                .map(Message::fromMailboxAttachment)
+                .collect(org.apache.james.util.streams.Collectors.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;
@@ -192,7 +205,6 @@ public class Message {
         private boolean isFlagged;
         private boolean isAnswered;
         private boolean isDraft;
-        private boolean hasAttachment;
         private ImmutableMap<String, String> headers;
         private Emailer from;
         private final ImmutableList.Builder<Emailer> to;
@@ -262,11 +274,6 @@ public class Message {
             return this;
         }
 
-        public Builder hasAttachment(boolean hasAttachment) {
-            this.hasAttachment = hasAttachment;
-            return this;
-        }
-
         public Builder headers(ImmutableMap<String, String> headers) {
             this.headers = headers;
             return this;
@@ -350,6 +357,7 @@ public class Message {
             ImmutableList<Attachment> attachments = this.attachments.build();
             ImmutableMap<String, SubMessage> attachedMessages = this.attachedMessages.build();
             Preconditions.checkState(areAttachedMessagesKeysInAttachments(attachments, attachedMessages), "'attachedMessages' keys must be in 'attachements'");
+            boolean hasAttachment = !attachments.isEmpty();
 
             return new Message(id, blobId, threadId, mailboxIds, Optional.ofNullable(inReplyToMessageId), isUnread, isFlagged, isAnswered, isDraft, hasAttachment, headers, Optional.ofNullable(from),
                     to.build(), cc.build(), bcc.build(), replyTo.build(), subject, date, size, preview, Optional.ofNullable(textBody), Optional.ofNullable(htmlBody), attachments, attachedMessages);
@@ -357,9 +365,16 @@ public class Message {
     }
 
     protected static boolean areAttachedMessagesKeysInAttachments(ImmutableList<Attachment> attachments, ImmutableMap<String, SubMessage> attachedMessages) {
-        return attachments.stream()
+        return attachedMessages.isEmpty() || attachedMessages.keySet().stream()
+                .anyMatch(inAttachments(attachments));
+    }
+
+    private static Predicate<String> inAttachments(ImmutableList<Attachment> attachments) {
+        return (key) -> {
+            return attachments.stream()
                 .map(Attachment::getBlobId)
-                .allMatch(attachedMessages::containsKey);
+                .anyMatch(blobId -> blobId.equals(key));
+        };
     }
 
     private final MessageId id;

http://git-wip-us.apache.org/repos/asf/james-project/blob/4e13597b/server/protocols/jmap/src/test/java/org/apache/james/jmap/json/ParsingWritingObjects.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/json/ParsingWritingObjects.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/json/ParsingWritingObjects.java
index 866d1f8..b0ea677 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/json/ParsingWritingObjects.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/json/ParsingWritingObjects.java
@@ -71,7 +71,6 @@ public interface ParsingWritingObjects {
             .isFlagged(Common.IS_FLAGGED)
             .isAnswered(Common.IS_ANSWERED)
             .isDraft(Common.IS_DRAFT)
-            .hasAttachment(Common.HAS_ATTACHMENT)
             .headers(Common.HEADERS)
             .from(Common.FROM)
             .to(Common.TO)

http://git-wip-us.apache.org/repos/asf/james-project/blob/4e13597b/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 4632c1a..ce86d03 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
@@ -123,28 +123,28 @@ public class GetMessagesMethodTest {
     
     @Test
     public void processShouldThrowWhenNullRequest() {
-        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory);
+        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory);
         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);
+        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory);
         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);
+        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory);
         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);
+        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory);
         assertThatThrownBy(() -> testee.process(
                 GetMessagesRequest.builder().accountId("abc").build(), mock(ClientId.class), mock(MailboxSession.class))).isInstanceOf(NotImplementedException.class);
     }
@@ -166,7 +166,7 @@ public class GetMessagesMethodTest {
                           new MessageId(ROBERT, inboxPath, message3Uid)))
                 .build();
 
-        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory);
+        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory);
         List<JmapResponse> result = testee.process(request, clientId, session).collect(Collectors.toList());
         
         assertThat(result).hasSize(1)
@@ -195,7 +195,7 @@ public class GetMessagesMethodTest {
                 .ids(ImmutableList.of(new MessageId(ROBERT, inboxPath, messageUid)))
                 .build();
 
-        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory);
+        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory);
         List<JmapResponse> result = testee.process(request, clientId, session).collect(Collectors.toList());
         
         assertThat(result).hasSize(1)
@@ -219,7 +219,7 @@ public class GetMessagesMethodTest {
                 .properties(ImmutableList.of())
                 .build();
 
-        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory);
+        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory);
         List<JmapResponse> result = testee.process(request, clientId, session).collect(Collectors.toList());
 
         assertThat(result).hasSize(1)
@@ -240,7 +240,7 @@ public class GetMessagesMethodTest {
                 .ids(ImmutableList.of(new MessageId(ROBERT, inboxPath, message1Uid)))
                 .build();
 
-        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory);
+        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory);
         Stream<JmapResponse> result = testee.process(request, clientId, session);
 
         assertThat(result).hasSize(1)
@@ -264,7 +264,7 @@ public class GetMessagesMethodTest {
 
         Set<MessageProperty> expected = Sets.newHashSet(MessageProperty.id, MessageProperty.subject);
 
-        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory);
+        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory);
         List<JmapResponse> result = testee.process(request, clientId, session).collect(Collectors.toList());
 
         assertThat(result).hasSize(1)
@@ -288,7 +288,7 @@ public class GetMessagesMethodTest {
 
         Set<MessageProperty> expected = Sets.newHashSet(MessageProperty.id, MessageProperty.textBody);
 
-        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory);
+        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory);
         List<JmapResponse> result = testee.process(request, clientId, session).collect(Collectors.toList());
 
         assertThat(result).hasSize(1)
@@ -315,7 +315,7 @@ public class GetMessagesMethodTest {
 
         Set<MessageProperty> expected = Sets.newHashSet(MessageProperty.id, MessageProperty.headers);
 
-        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory);
+        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory);
         List<JmapResponse> result = testee.process(request, clientId, session).collect(Collectors.toList());
 
         assertThat(result)
@@ -341,7 +341,7 @@ public class GetMessagesMethodTest {
                 .properties(ImmutableList.of("headers.from", "headers.heADER2"))
                 .build();
 
-        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory);
+        GetMessagesMethod testee = new GetMessagesMethod(mailboxSessionMapperFactory, mailboxSessionMapperFactory, mailboxSessionMapperFactory);
         List<JmapResponse> result = testee.process(request, clientId, session).collect(Collectors.toList());
 
         assertThat(result)

http://git-wip-us.apache.org/repos/asf/james-project/blob/4e13597b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/AttachmentTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/AttachmentTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/AttachmentTest.java
index e7d9add..92b3194 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/AttachmentTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/AttachmentTest.java
@@ -56,18 +56,12 @@ public class AttachmentTest {
         Attachment.builder().blobId("blobId").type("").name("name").size(123).build();
     }
     
-    @Test(expected=IllegalStateException.class)
-    public void buildShouldThrowWhenNameIsEmpty() {
-        Attachment.builder().blobId("blobId").type("type").name("").size(123).build();
-    }
-    
     @Test
     public void buildShouldWorkWhenMandatoryFieldsArePresent() {
-        Attachment expected = new Attachment("blobId", "type", "name", 123, Optional.empty(), false, Optional.empty(), Optional.empty());
+        Attachment expected = new Attachment("blobId", "type", Optional.empty(), 123, Optional.empty(), false, Optional.empty(), Optional.empty());
         Attachment tested = Attachment.builder()
             .blobId("blobId")
             .type("type")
-            .name("name")
             .size(123)
             .build();
         assertThat(tested).isEqualToComparingFieldByField(expected);
@@ -75,7 +69,7 @@ public class AttachmentTest {
 
     @Test
     public void buildShouldWorkWithAllFieldsSet() {
-        Attachment expected = new Attachment("blobId", "type", "name", 123, Optional.of("cid"), true, Optional.of(456L), Optional.of(789L));
+        Attachment expected = new Attachment("blobId", "type", Optional.of("name"), 123, Optional.of("cid"), true, Optional.of(456L), Optional.of(789L));
         Attachment tested = Attachment.builder()
             .blobId("blobId")
             .type("type")

http://git-wip-us.apache.org/repos/asf/james-project/blob/4e13597b/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 5bad5c5..d34d7a9 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
@@ -30,8 +30,8 @@ import javax.mail.Flags.Flag;
 import javax.mail.util.SharedByteArrayInputStream;
 
 import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang.NotImplementedException;
 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;
@@ -212,7 +212,6 @@ public class MailboxMessageTest {
             .isFlagged(true)
             .isAnswered(true)
             .isDraft(true)
-            .hasAttachment(true)
             .headers(ImmutableMap.of("key", "value"))
             .from(from)
             .to(to)
@@ -243,7 +242,7 @@ public class MailboxMessageTest {
                 MAILBOX_ID);
         testMail.setModSeq(MOD_SEQ);
         
-        Message testee = Message.fromMailboxMessage(testMail, x -> MessageId.of("user|box|" + x));
+        Message testee = Message.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);
@@ -265,7 +264,7 @@ public class MailboxMessageTest {
                 MAILBOX_ID);
         testMail.setModSeq(MOD_SEQ);
         
-        Message testee = Message.fromMailboxMessage(testMail, x -> MessageId.of("user|box|" + x));
+        Message testee = Message.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);
@@ -307,7 +306,7 @@ public class MailboxMessageTest {
                 .put("in-reply-to", "<SNT124-W2664003139C1E520CF4F6787D30@phx.gbl>")
                 .put("other-header", "other header value")
                 .build();
-        Message testee = Message.fromMailboxMessage(testMail, x -> MessageId.of("user|box|" + x));
+        Message testee = Message.fromMailboxMessage(testMail, ImmutableList.of(), x -> MessageId.of("user|box|" + x));
         Message expected = Message.builder()
                 .id(MessageId.of("user|box|0"))
                 .blobId("0")
@@ -343,7 +342,7 @@ public class MailboxMessageTest {
                 MAILBOX_ID);
         testMail.setModSeq(MOD_SEQ);
         
-        Message testee = Message.fromMailboxMessage(testMail, x -> MessageId.of("user|box|" + x));
+        Message testee = Message.fromMailboxMessage(testMail, ImmutableList.of(), x -> MessageId.of("user|box|" + x));
         assertThat(testee.getTextBody()).hasValue("Mail body");
     }
     
@@ -391,12 +390,28 @@ public class MailboxMessageTest {
                 MAILBOX_ID);
         testMail.setModSeq(MOD_SEQ);
         
-        Message testee = Message.fromMailboxMessage(testMail, x -> MessageId.of("user|box|" + x));
+        Message testee = Message.fromMailboxMessage(testMail, ImmutableList.of(), x -> MessageId.of("user|box|" + x));
         assertThat(testee.getPreview()).isEqualTo(expectedPreview);
     }
     
-    @Test(expected=NotImplementedException.class)
-    public void attachmentsShouldNotBeHandledForNow() throws Exception {
+    @Test
+    public void attachmentsShouldBeEmptyWhenNone() throws Exception {
+        MailboxMessage testMail = new SimpleMailboxMessage(
+                INTERNAL_DATE,
+                0,
+                0,
+                new SharedByteArrayInputStream(IOUtils.toByteArray(ClassLoader.getSystemResourceAsStream("spamMail.eml"))),
+                new Flags(Flag.SEEN),
+                new PropertyBuilder(),
+                MAILBOX_ID);
+        testMail.setModSeq(MOD_SEQ);
+        
+        Message testee = Message.fromMailboxMessage(testMail, ImmutableList.of(), x -> MessageId.of("user|box|" + x));
+        assertThat(testee.getAttachments()).isEmpty();
+    }
+    
+    @Test
+    public void attachmentsShouldBeRetrievedWhenSome() throws Exception {
         MailboxMessage testMail = new SimpleMailboxMessage(
                 INTERNAL_DATE,
                 0,
@@ -407,6 +422,70 @@ public class MailboxMessageTest {
                 MAILBOX_ID);
         testMail.setModSeq(MOD_SEQ);
         
-        Message.fromMailboxMessage(testMail, x -> MessageId.of("user|box|" + x));
+        String payload = "payload";
+        String blodId = "id1";
+        String type = "content";
+        Attachment expectedAttachment = Attachment.builder()
+                .blobId(blodId)
+                .size(payload.length())
+                .type(type)
+                .build();
+
+        Message testee = Message.fromMailboxMessage(testMail, 
+                ImmutableList.of(org.apache.james.mailbox.store.mail.model.Attachment.builder()
+                        .attachmentId(AttachmentId.from(blodId))
+                        .bytes(payload.getBytes())
+                        .type(type)
+                        .size(payload.length())
+                        .build()), 
+                x -> MessageId.of("user|box|" + x));
+
+        assertThat(testee.getAttachments()).hasSize(1);
+        assertThat(testee.getAttachments().get(0)).isEqualToComparingFieldByField(expectedAttachment);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void buildShouldThrowWhenOneAttachedMessageIsNotInAttachments() throws Exception {
+        Message.builder()
+            .id(MessageId.of("user|box|1"))
+            .blobId("blodId")
+            .threadId("threadId")
+            .mailboxIds(ImmutableList.of("mailboxId"))
+            .headers(ImmutableMap.of("key", "value"))
+            .subject("subject")
+            .size(1)
+            .date(ZonedDateTime.now())
+            .preview("preview")
+            .attachedMessages(ImmutableMap.of("key", SubMessage.builder()
+                    .headers(ImmutableMap.of("key", "value"))
+                    .subject("subject")
+                    .date(ZonedDateTime.now())
+                    .build()))
+            .build();
+    }
+
+    @Test
+    public void buildShouldNotThrowWhenOneAttachedMessageIsInAttachments() throws Exception {
+        Message.builder()
+            .id(MessageId.of("user|box|1"))
+            .blobId("blodId")
+            .threadId("threadId")
+            .mailboxIds(ImmutableList.of("mailboxId"))
+            .headers(ImmutableMap.of("key", "value"))
+            .subject("subject")
+            .size(1)
+            .date(ZonedDateTime.now())
+            .preview("preview")
+            .attachments(ImmutableList.of(Attachment.builder()
+                    .blobId("key")
+                    .size(1)
+                    .type("type")
+                    .build()))
+            .attachedMessages(ImmutableMap.of("key", SubMessage.builder()
+                    .headers(ImmutableMap.of("key", "value"))
+                    .subject("subject")
+                    .date(ZonedDateTime.now())
+                    .build()))
+            .build();
     }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/4e13597b/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 84d00a6..98cd1d0 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
@@ -38,6 +38,7 @@ import org.apache.mailet.MailAddress;
 import org.junit.Before;
 import org.junit.Test;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 
 public class MailFactoryTest {
@@ -68,7 +69,7 @@ public class MailFactoryTest {
                 new FlagsBuilder().add(Flags.Flag.SEEN).build(),
                 propertyBuilder,
                 TestId.of(2));
-        jmapMessage = Message.fromMailboxMessage(mailboxMessage, x -> MessageId.of("test|test|" + x));
+        jmapMessage = Message.fromMailboxMessage(mailboxMessage, ImmutableList.of(), x -> MessageId.of("test|test|" + x));
     }
 
     @Test(expected=NullPointerException.class)

http://git-wip-us.apache.org/repos/asf/james-project/blob/4e13597b/server/protocols/jmap/src/test/resources/json/message.json
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/resources/json/message.json b/server/protocols/jmap/src/test/resources/json/message.json
index 28eecae..72d37bd 100644
--- a/server/protocols/jmap/src/test/resources/json/message.json
+++ b/server/protocols/jmap/src/test/resources/json/message.json
@@ -8,7 +8,7 @@
     "isFlagged": true,
     "isAnswered": true,
     "isDraft": true,
-    "hasAttachment": true,
+    "hasAttachment": false,
     "headers": { "h1": "h1Value", "h2": "h2Value" },
     "from": { "name": "myName", "email": "myEmail@james.org"},
     "to": [ { "name": "to1", "email": "to1@james.org"}, { "name": "to2", "email": "to2@james.org"} ],


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


Mime
View raw message