You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by ad...@apache.org on 2016/07/08 14:46:36 UTC

[1/5] james-project git commit: JAMES-1790 blobId deserves a real type

Repository: james-project
Updated Branches:
  refs/heads/master 1c294ddc0 -> 662fa4a95


JAMES-1790 blobId deserves a real type


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

Branch: refs/heads/master
Commit: 7be2166ecb1fe3e7ed0711b67b5b506b14366d0d
Parents: a8d2de9
Author: Matthieu Baechler <ma...@linagora.com>
Authored: Tue Jul 5 17:01:32 2016 +0200
Committer: Antoine Duprat <ad...@linagora.com>
Committed: Fri Jul 8 14:24:55 2016 +0200

----------------------------------------------------------------------
 .../org/apache/james/jmap/model/Attachment.java | 12 ++--
 .../org/apache/james/jmap/model/BlobId.java     | 58 +++++++++++++++++++
 .../org/apache/james/jmap/model/Message.java    | 28 +++++-----
 .../apache/james/jmap/model/MessageFactory.java |  4 +-
 .../org/apache/james/jmap/model/SubMessage.java | 12 ++--
 .../james/jmap/json/ParsingWritingObjects.java  |  3 +-
 .../SetMessagesCreationProcessorTest.java       |  3 +-
 .../apache/james/jmap/model/AttachmentTest.java | 21 +++----
 .../org/apache/james/jmap/model/BlobIdTest.java | 49 ++++++++++++++++
 .../james/jmap/model/MailboxMessageTest.java    | 59 +++++++++-----------
 .../jmap/model/SetMessagesResponseTest.java     |  4 +-
 .../james/jmap/model/SubMailboxMessageTest.java |  8 +--
 12 files changed, 180 insertions(+), 81 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/7be2166e/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 2cbed7f..2a62684 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
@@ -38,7 +38,7 @@ public class Attachment {
 
     @JsonPOJOBuilder(withPrefix = "")
     public static class Builder {
-        private String blobId;
+        private BlobId blobId;
         private String type;
         private String name;
         private Long size;
@@ -47,7 +47,7 @@ public class Attachment {
         private Long width;
         private Long height;
 
-        public Builder blobId(String blobId) {
+        public Builder blobId(BlobId blobId) {
             this.blobId = blobId;
             return this;
         }
@@ -88,14 +88,14 @@ public class Attachment {
         }
 
         public Attachment build() {
-            Preconditions.checkState(!Strings.isNullOrEmpty(blobId), "'blobId' is mandatory");
+            Preconditions.checkState(blobId != null, "'blobId' is mandatory");
             Preconditions.checkState(!Strings.isNullOrEmpty(type), "'type' is mandatory");
             Preconditions.checkState(size != null, "'size' is mandatory");
             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 BlobId blobId;
     private final String type;
     private final Optional<String> name;
     private final long size;
@@ -104,7 +104,7 @@ public class Attachment {
     private final Optional<Long> width;
     private final 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) {
+    @VisibleForTesting Attachment(BlobId 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;
@@ -115,7 +115,7 @@ public class Attachment {
         this.height = height;
     }
 
-    public String getBlobId() {
+    public BlobId getBlobId() {
         return blobId;
     }
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/7be2166e/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/BlobId.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/BlobId.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/BlobId.java
new file mode 100644
index 0000000..578c09f
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/BlobId.java
@@ -0,0 +1,58 @@
+/****************************************************************
+ * 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.Objects;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+
+public class BlobId {
+
+    public static BlobId of(String rawValue) {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(rawValue), "'rawValue' is mandatory");
+        return new BlobId(rawValue);
+    }
+
+    private final String rawValue;
+    
+    private BlobId(String rawValue) {
+        this.rawValue = rawValue;
+    }
+    
+    @JsonValue
+    public String getRawValue() {
+        return rawValue;
+    }
+    
+    @Override
+    public final boolean equals(Object obj) {
+        if (obj instanceof BlobId) {
+            BlobId other = (BlobId) obj;
+            return Objects.equals(this.rawValue, other.rawValue);
+        }
+        return false;
+    }
+    
+    @Override
+    public final int hashCode() {
+        return Objects.hashCode(this.rawValue);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/7be2166e/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 94b8517..2121843 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
@@ -48,7 +48,7 @@ public class Message {
     @JsonPOJOBuilder(withPrefix = "")
     public static class Builder {
         private MessageId id;
-        private String blobId;
+        private BlobId blobId;
         private String threadId;
         private ImmutableList<String> mailboxIds;
         private String inReplyToMessageId;
@@ -69,7 +69,7 @@ public class Message {
         private String textBody;
         private String htmlBody;
         private final ImmutableList.Builder<Attachment> attachments;
-        private final ImmutableMap.Builder<String, SubMessage> attachedMessages;
+        private final ImmutableMap.Builder<BlobId, SubMessage> attachedMessages;
 
         private Builder() {
             to = ImmutableList.builder();
@@ -85,7 +85,7 @@ public class Message {
             return this;
         }
 
-        public Builder blobId(String blobId) {
+        public Builder blobId(BlobId blobId) {
             this.blobId = blobId;
             return this;
         }
@@ -194,14 +194,14 @@ public class Message {
             return this;
         }
 
-        public Builder attachedMessages(Map<String, SubMessage> attachedMessages) {
+        public Builder attachedMessages(Map<BlobId, SubMessage> attachedMessages) {
             this.attachedMessages.putAll(attachedMessages);
             return this;
         }
 
         public Message build() {
             Preconditions.checkState(id != null, "'id' is mandatory");
-            Preconditions.checkState(!Strings.isNullOrEmpty(blobId), "'blobId' is mandatory");
+            Preconditions.checkState(blobId != null, "'blobId' is mandatory");
             Preconditions.checkState(!Strings.isNullOrEmpty(threadId), "'threadId' is mandatory");
             Preconditions.checkState(mailboxIds != null, "'mailboxIds' is mandatory");
             Preconditions.checkState(headers != null, "'headers' is mandatory");
@@ -210,7 +210,7 @@ public class Message {
             Preconditions.checkState(date != null, "'date' is mandatory");
             Preconditions.checkState(!Strings.isNullOrEmpty(preview), "'preview' is mandatory");
             ImmutableList<Attachment> attachments = this.attachments.build();
-            ImmutableMap<String, SubMessage> attachedMessages = this.attachedMessages.build();
+            ImmutableMap<BlobId, SubMessage> attachedMessages = this.attachedMessages.build();
             Preconditions.checkState(areAttachedMessagesKeysInAttachments(attachments, attachedMessages), "'attachedMessages' keys must be in 'attachements'");
             boolean hasAttachment = !attachments.isEmpty();
 
@@ -219,12 +219,12 @@ public class Message {
         }
     }
 
-    protected static boolean areAttachedMessagesKeysInAttachments(ImmutableList<Attachment> attachments, ImmutableMap<String, SubMessage> attachedMessages) {
+    protected static boolean areAttachedMessagesKeysInAttachments(ImmutableList<Attachment> attachments, ImmutableMap<BlobId, SubMessage> attachedMessages) {
         return attachedMessages.isEmpty() || attachedMessages.keySet().stream()
                 .anyMatch(inAttachments(attachments));
     }
 
-    private static Predicate<String> inAttachments(ImmutableList<Attachment> attachments) {
+    private static Predicate<BlobId> inAttachments(ImmutableList<Attachment> attachments) {
         return (key) -> {
             return attachments.stream()
                 .map(Attachment::getBlobId)
@@ -233,7 +233,7 @@ public class Message {
     }
 
     private final MessageId id;
-    private final String blobId;
+    private final BlobId blobId;
     private final String threadId;
     private final ImmutableList<String> mailboxIds;
     private final Optional<String> inReplyToMessageId;
@@ -256,11 +256,11 @@ public class Message {
     private final Optional<String> textBody;
     private final Optional<String> htmlBody;
     private final ImmutableList<Attachment> attachments;
-    private final ImmutableMap<String, SubMessage> attachedMessages;
+    private final ImmutableMap<BlobId, SubMessage> attachedMessages;
 
-    @VisibleForTesting Message(MessageId id, String blobId, String threadId, ImmutableList<String> mailboxIds, Optional<String> inReplyToMessageId, boolean isUnread, boolean isFlagged, boolean isAnswered, boolean isDraft, boolean hasAttachment, ImmutableMap<String, String> headers, Optional<Emailer> from,
+    @VisibleForTesting Message(MessageId id, BlobId blobId, String threadId, ImmutableList<String> mailboxIds, Optional<String> inReplyToMessageId, boolean isUnread, boolean isFlagged, boolean isAnswered, boolean isDraft, boolean hasAttachment, ImmutableMap<String, String> headers, Optional<Emailer> from,
             ImmutableList<Emailer> to, ImmutableList<Emailer> cc, ImmutableList<Emailer> bcc, ImmutableList<Emailer> replyTo, String subject, ZonedDateTime date, long size, String preview, Optional<String> textBody, Optional<String> htmlBody, ImmutableList<Attachment> attachments,
-            ImmutableMap<String, SubMessage> attachedMessages) {
+            ImmutableMap<BlobId, SubMessage> attachedMessages) {
         this.id = id;
         this.blobId = blobId;
         this.threadId = threadId;
@@ -291,7 +291,7 @@ public class Message {
         return id;
     }
 
-    public String getBlobId() {
+    public BlobId getBlobId() {
         return blobId;
     }
 
@@ -379,7 +379,7 @@ public class Message {
         return attachments;
     }
 
-    public ImmutableMap<String, SubMessage> getAttachedMessages() {
+    public ImmutableMap<BlobId, SubMessage> getAttachedMessages() {
         return attachedMessages;
     }
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/7be2166e/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
index a430627..d23215e 100644
--- 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
@@ -62,7 +62,7 @@ public class MessageFactory {
         MessageId messageId = uidToMessageId.apply(im.getId());
         return Message.builder()
                 .id(messageId)
-                .blobId(String.valueOf(im.getId()))
+                .blobId(BlobId.of(String.valueOf(im.getId())))
                 .threadId(messageId.serialize())
                 .mailboxIds(ImmutableList.of(im.getMailboxId()))
                 .inReplyToMessageId(getHeaderAsSingleValue(im, "in-reply-to"))
@@ -159,7 +159,7 @@ public class MessageFactory {
 
     private Attachment fromMailboxAttachment(MessageAttachment attachment) {
         return Attachment.builder()
-                    .blobId(attachment.getAttachmentId().getId())
+                    .blobId(BlobId.of(attachment.getAttachmentId().getId()))
                     .type(attachment.getAttachment().getType())
                     .size(attachment.getAttachment().getSize())
                     .name(attachment.getName().orNull())

http://git-wip-us.apache.org/repos/asf/james-project/blob/7be2166e/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SubMessage.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SubMessage.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SubMessage.java
index 38e68a9..cbaf1cd 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SubMessage.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SubMessage.java
@@ -52,7 +52,7 @@ public class SubMessage {
         private String textBody;
         private String htmlBody;
         private final ImmutableList.Builder<Attachment> attachments;
-        private final ImmutableMap.Builder<String, SubMessage> attachedMessages;
+        private final ImmutableMap.Builder<BlobId, SubMessage> attachedMessages;
         
         private Builder() {
             to = ImmutableList.builder();
@@ -118,7 +118,7 @@ public class SubMessage {
             return this;
         }
 
-        public Builder attachedMessages(Map<String, SubMessage> attachedMessages) {
+        public Builder attachedMessages(Map<BlobId, SubMessage> attachedMessages) {
             this.attachedMessages.putAll(attachedMessages);
             return this;
         }
@@ -128,7 +128,7 @@ public class SubMessage {
             Preconditions.checkState(!Strings.isNullOrEmpty(subject), "'subject' is mandatory");
             Preconditions.checkState(date != null, "'date' is mandatory");
             ImmutableList<Attachment> attachments = this.attachments.build();
-            ImmutableMap<String, SubMessage> attachedMessages = this.attachedMessages.build();
+            ImmutableMap<BlobId, SubMessage> attachedMessages = this.attachedMessages.build();
             Preconditions.checkState(Message.areAttachedMessagesKeysInAttachments(attachments, attachedMessages), "'attachedMessages' keys must be in 'attachements'");
             return new SubMessage(headers, Optional.ofNullable(from), to.build(), cc.build(), bcc.build(),
                     replyTo.build(), subject, date, Optional.ofNullable(textBody), Optional.ofNullable(htmlBody),
@@ -148,10 +148,10 @@ public class SubMessage {
     private final Optional<String> textBody;
     private final Optional<String> htmlBody;
     private final ImmutableList<Attachment> attachments;
-    private final ImmutableMap<String, SubMessage> attachedMessages;
+    private final ImmutableMap<BlobId, SubMessage> attachedMessages;
 
     @VisibleForTesting SubMessage(ImmutableMap<String, String> headers, Optional<Emailer> from, ImmutableList<Emailer> to, ImmutableList<Emailer> cc, ImmutableList<Emailer> bcc, ImmutableList<Emailer> replyTo, String subject, ZonedDateTime date, Optional<String> textBody,
-            Optional<String> htmlBody, ImmutableList<Attachment> attachments, ImmutableMap<String, SubMessage> attachedMessages) {
+            Optional<String> htmlBody, ImmutableList<Attachment> attachments, ImmutableMap<BlobId, SubMessage> attachedMessages) {
         super();
         this.headers = headers;
         this.from = from;
@@ -211,7 +211,7 @@ public class SubMessage {
         return attachments;
     }
 
-    public ImmutableMap<String, SubMessage> getAttachedMessages() {
+    public ImmutableMap<BlobId, SubMessage> getAttachedMessages() {
         return attachedMessages;
     }
     

http://git-wip-us.apache.org/repos/asf/james-project/blob/7be2166e/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 b0ea677..7723d8a 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
@@ -22,6 +22,7 @@ package org.apache.james.jmap.json;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
 
+import org.apache.james.jmap.model.BlobId;
 import org.apache.james.jmap.model.Emailer;
 import org.apache.james.jmap.model.Message;
 import org.apache.james.jmap.model.MessageId;
@@ -34,7 +35,7 @@ public interface ParsingWritingObjects {
 
     public interface Common {
         MessageId MESSAGE_ID = MessageId.of("username|mailbox|1");
-        String BLOB_ID = "myBlobId";
+        BlobId BLOB_ID = BlobId.of("myBlobId");
         String THREAD_ID = "myThreadId";
         ImmutableList<String> MAILBOX_IDS = ImmutableList.of("mailboxId1", "mailboxId2");
         String IN_REPLY_TO_MESSAGE_ID = "myInReplyToMessageId";

http://git-wip-us.apache.org/repos/asf/james-project/blob/7be2166e/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 7f32174..12eba05 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
@@ -34,6 +34,7 @@ import java.util.function.Supplier;
 import java.util.stream.Stream;
 
 import org.apache.james.jmap.methods.ValueWithId.MessageWithId;
+import org.apache.james.jmap.model.BlobId;
 import org.apache.james.jmap.model.CreationMessage;
 import org.apache.james.jmap.model.CreationMessage.DraftEmailer;
 import org.apache.james.jmap.model.CreationMessageId;
@@ -89,7 +90,7 @@ public class SetMessagesCreationProcessorTest {
 
     private static final Message FAKE_OUTBOX_MESSAGE = Message.builder()
             .id(MessageId.of(OUTBOX_MESSAGE_ID))
-            .blobId("anything")
+            .blobId(BlobId.of("anything"))
             .threadId("anything")
             .mailboxId(OUTBOX_ID.serialize())
             .headers(ImmutableMap.of())

http://git-wip-us.apache.org/repos/asf/james-project/blob/7be2166e/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 92b3194..c6a433a 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
@@ -33,34 +33,29 @@ public class AttachmentTest {
 
     @Test(expected=IllegalStateException.class)
     public void buildShouldThrowWhenTypeIsNull() {
-        Attachment.builder().blobId("blobId").build();
+        Attachment.builder().blobId(BlobId.of("blobId")).build();
     }
 
     @Test(expected=IllegalStateException.class)
     public void buildShouldThrowWhenNameIsNull() {
-        Attachment.builder().blobId("blobId").type("type").build();
+        Attachment.builder().blobId(BlobId.of("blobId")).type("type").build();
     }
 
     @Test(expected=IllegalStateException.class)
     public void buildShouldThrowWhenSizeIsNull() {
-        Attachment.builder().blobId("blobId").type("type").name("name").build();
-    }
-    
-    @Test(expected=IllegalStateException.class)
-    public void buildShouldThrowWhenBlobIdIsEmpty() {
-        Attachment.builder().blobId("").type("type").name("name").size(123).build();
+        Attachment.builder().blobId(BlobId.of("blobId")).type("type").name("name").build();
     }
     
     @Test(expected=IllegalStateException.class)
     public void buildShouldThrowWhenTypeIsEmpty() {
-        Attachment.builder().blobId("blobId").type("").name("name").size(123).build();
+        Attachment.builder().blobId(BlobId.of("blobId")).type("").name("name").size(123).build();
     }
     
     @Test
     public void buildShouldWorkWhenMandatoryFieldsArePresent() {
-        Attachment expected = new Attachment("blobId", "type", Optional.empty(), 123, Optional.empty(), false, Optional.empty(), Optional.empty());
+        Attachment expected = new Attachment(BlobId.of("blobId"), "type", Optional.empty(), 123, Optional.empty(), false, Optional.empty(), Optional.empty());
         Attachment tested = Attachment.builder()
-            .blobId("blobId")
+            .blobId(BlobId.of("blobId"))
             .type("type")
             .size(123)
             .build();
@@ -69,9 +64,9 @@ public class AttachmentTest {
 
     @Test
     public void buildShouldWorkWithAllFieldsSet() {
-        Attachment expected = new Attachment("blobId", "type", Optional.of("name"), 123, Optional.of("cid"), true, Optional.of(456L), Optional.of(789L));
+        Attachment expected = new Attachment(BlobId.of("blobId"), "type", Optional.of("name"), 123, Optional.of("cid"), true, Optional.of(456L), Optional.of(789L));
         Attachment tested = Attachment.builder()
-            .blobId("blobId")
+            .blobId(BlobId.of("blobId"))
             .type("type")
             .name("name")
             .size(123)

http://git-wip-us.apache.org/repos/asf/james-project/blob/7be2166e/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/BlobIdTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/BlobIdTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/BlobIdTest.java
new file mode 100644
index 0000000..bf04a1e
--- /dev/null
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/BlobIdTest.java
@@ -0,0 +1,49 @@
+/****************************************************************
+ * 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.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.junit.Test;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+public class BlobIdTest {
+
+    @Test
+    public void shouldNotAllowEmptyString() {
+        assertThatThrownBy(() -> BlobId.of("")).isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void shouldNotAllowNullInput() {
+        assertThatThrownBy(() -> BlobId.of(null)).isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    public void shouldCreateInstanceWhenSimpleString() {
+        assertThat(BlobId.of("simple string")).extracting(BlobId::getRawValue).containsExactly("simple string");
+    }
+    
+    @Test
+    public void shouldRespectJavaBeanContract() {
+        EqualsVerifier.forClass(BlobId.class).verify();
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/7be2166e/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 b11edcf..0293808 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
@@ -73,73 +73,68 @@ public class MailboxMessageTest {
     }
 
     @Test(expected=IllegalStateException.class)
-    public void buildShouldThrowWhenBlobIdIsEmpty() {
-        Message.builder().id(MessageId.of("user|box|1")).blobId("").build();
-    }
-
-    @Test(expected=IllegalStateException.class)
     public void buildShouldThrowWhenThreadIdIsNull() {
-        Message.builder().id(MessageId.of("user|box|1")).blobId("blobId").build();
+        Message.builder().id(MessageId.of("user|box|1")).blobId(BlobId.of("blobId")).build();
     }
 
     @Test(expected=IllegalStateException.class)
     public void buildShouldThrowWhenThreadIdIsEmpty() {
-        Message.builder().id(MessageId.of("user|box|1")).blobId("blobId").threadId("").build();
+        Message.builder().id(MessageId.of("user|box|1")).blobId(BlobId.of("blobId")).threadId("").build();
     }
 
     @Test(expected=IllegalStateException.class)
     public void buildShouldThrowWhenMailboxIdsIsNull() {
-        Message.builder().id(MessageId.of("user|box|1")).blobId("blobId").threadId("threadId").build();
+        Message.builder().id(MessageId.of("user|box|1")).blobId(BlobId.of("blobId")).threadId("threadId").build();
     }
 
     @Test(expected=IllegalStateException.class)
     public void buildShouldThrowWhenHeadersIsNull() {
-        Message.builder().id(MessageId.of("user|box|1")).blobId("blobId").threadId("threadId").mailboxIds(ImmutableList.of()).build();
+        Message.builder().id(MessageId.of("user|box|1")).blobId(BlobId.of("blobId")).threadId("threadId").mailboxIds(ImmutableList.of()).build();
     }
 
     @Test(expected=IllegalStateException.class)
     public void buildShouldThrowWhenSubjectIsNull() {
-        Message.builder().id(MessageId.of("user|box|1")).blobId("blobId").threadId("threadId").mailboxIds(ImmutableList.of()).headers(ImmutableMap.of()).build();
+        Message.builder().id(MessageId.of("user|box|1")).blobId(BlobId.of("blobId")).threadId("threadId").mailboxIds(ImmutableList.of()).headers(ImmutableMap.of()).build();
     }
 
     @Test(expected=IllegalStateException.class)
     public void buildShouldThrowWhenSubjectIsEmpty() {
-        Message.builder().id(MessageId.of("user|box|1")).blobId("blobId").threadId("threadId").mailboxIds(ImmutableList.of()).headers(ImmutableMap.of())
+        Message.builder().id(MessageId.of("user|box|1")).blobId(BlobId.of("blobId")).threadId("threadId").mailboxIds(ImmutableList.of()).headers(ImmutableMap.of())
             .subject("").build();
     }
 
     @Test(expected=IllegalStateException.class)
     public void buildShouldThrowWhenSizeIsNull() {
-        Message.builder().id(MessageId.of("user|box|1")).blobId("blobId").threadId("threadId").mailboxIds(ImmutableList.of()).headers(ImmutableMap.of())
+        Message.builder().id(MessageId.of("user|box|1")).blobId(BlobId.of("blobId")).threadId("threadId").mailboxIds(ImmutableList.of()).headers(ImmutableMap.of())
             .subject("subject").build();
     }
 
     @Test(expected=IllegalStateException.class)
     public void buildShouldThrowWhenDateIsNull() {
-        Message.builder().id(MessageId.of("user|box|1")).blobId("blobId").threadId("threadId").mailboxIds(ImmutableList.of()).headers(ImmutableMap.of())
+        Message.builder().id(MessageId.of("user|box|1")).blobId(BlobId.of("blobId")).threadId("threadId").mailboxIds(ImmutableList.of()).headers(ImmutableMap.of())
             .subject("subject").size(123).build();
     }
 
     @Test(expected=IllegalStateException.class)
     public void buildShouldThrowWhenPreviewIsNull() {
-        Message.builder().id(MessageId.of("user|box|1")).blobId("blobId").threadId("threadId").mailboxIds(ImmutableList.of()).headers(ImmutableMap.of())
+        Message.builder().id(MessageId.of("user|box|1")).blobId(BlobId.of("blobId")).threadId("threadId").mailboxIds(ImmutableList.of()).headers(ImmutableMap.of())
             .subject("subject").size(123).date(ZonedDateTime.now()).build();
     }
 
     @Test(expected=IllegalStateException.class)
     public void buildShouldThrowWhenPreviewIsEmpty() {
-        Message.builder().id(MessageId.of("user|box|1")).blobId("blobId").threadId("threadId").mailboxIds(ImmutableList.of()).headers(ImmutableMap.of())
+        Message.builder().id(MessageId.of("user|box|1")).blobId(BlobId.of("blobId")).threadId("threadId").mailboxIds(ImmutableList.of()).headers(ImmutableMap.of())
             .subject("subject").size(123).date(ZonedDateTime.now()).preview("").build();
     }
 
     @Test
     public void buildShouldWorkWhenMandatoryFieldsArePresent() {
         ZonedDateTime currentDate = ZonedDateTime.now();
-        Message expected = new Message(MessageId.of("user|box|1"), "blobId", "threadId", ImmutableList.of("mailboxId"), Optional.empty(), false, false, false, false, false, ImmutableMap.of("key", "value"), Optional.empty(),
+        Message expected = new Message(MessageId.of("user|box|1"), BlobId.of("blobId"), "threadId", ImmutableList.of("mailboxId"), Optional.empty(), false, false, false, false, false, ImmutableMap.of("key", "value"), Optional.empty(),
                 ImmutableList.of(), ImmutableList.of(), ImmutableList.of(), ImmutableList.of(), "subject", currentDate, 123, "preview", Optional.empty(), Optional.empty(), ImmutableList.of(), ImmutableMap.of());
         Message tested = Message.builder()
                 .id(MessageId.of("user|box|1"))
-                .blobId("blobId")
+                .blobId(BlobId.of("blobId"))
                 .threadId("threadId")
                 .mailboxIds(ImmutableList.of("mailboxId"))
                 .headers(ImmutableMap.of("key", "value"))
@@ -153,17 +148,17 @@ public class MailboxMessageTest {
 
     @Test(expected=IllegalStateException.class)
     public void buildShouldThrowWhenAttachedMessageIsNotMatchingAttachments() {
-        Attachment simpleAttachment = Attachment.builder().blobId("blobId").type("type").name("name").size(123).build();
+        Attachment simpleAttachment = Attachment.builder().blobId(BlobId.of("blobId")).type("type").name("name").size(123).build();
         ImmutableList<Attachment> attachments = ImmutableList.of(simpleAttachment);
         SubMessage simpleMessage = SubMessage.builder()
                 .headers(ImmutableMap.of("key", "value"))
                 .subject("subject")
                 .date(ZonedDateTime.now())
                 .build();
-        ImmutableMap<String, SubMessage> attachedMessages = ImmutableMap.of("differentBlobId", simpleMessage);
+        ImmutableMap<BlobId, SubMessage> attachedMessages = ImmutableMap.of(BlobId.of("differentBlobId"), simpleMessage);
         Message.builder()
             .id(MessageId.of("user|box|1"))
-            .blobId("blobId")
+            .blobId(BlobId.of("blobId"))
             .threadId("threadId")
             .mailboxIds(ImmutableList.of("mailboxId"))
             .headers(ImmutableMap.of("key", "value"))
@@ -184,17 +179,17 @@ public class MailboxMessageTest {
         ImmutableList<Emailer> bcc = ImmutableList.of(Emailer.builder().name("bcc").email("bcc@domain").build());
         ImmutableList<Emailer> replyTo = ImmutableList.of(Emailer.builder().name("replyTo").email("replyTo@domain").build());
         ZonedDateTime currentDate = ZonedDateTime.now();
-        Attachment simpleAttachment = Attachment.builder().blobId("blobId").type("type").name("name").size(123).build();
+        Attachment simpleAttachment = Attachment.builder().blobId(BlobId.of("blobId")).type("type").name("name").size(123).build();
         ImmutableList<Attachment> attachments = ImmutableList.of(simpleAttachment);
         SubMessage simpleMessage = SubMessage.builder()
                 .headers(ImmutableMap.of("key", "value"))
                 .subject("subject")
                 .date(currentDate)
                 .build();
-        ImmutableMap<String, SubMessage> attachedMessages = ImmutableMap.of("blobId", simpleMessage);
+        ImmutableMap<BlobId, SubMessage> attachedMessages = ImmutableMap.of(BlobId.of("blobId"), simpleMessage);
         Message expected = new Message(
                 MessageId.of("user|box|1"),
-                "blobId",
+                BlobId.of("blobId"),
                 "threadId",
                 ImmutableList.of("mailboxId"),
                 Optional.of("inReplyToMessageId"), 
@@ -219,7 +214,7 @@ public class MailboxMessageTest {
                 attachedMessages);
         Message tested = Message.builder()
             .id(MessageId.of("user|box|1"))
-            .blobId("blobId")
+            .blobId(BlobId.of("blobId"))
             .threadId("threadId")
             .mailboxIds(ImmutableList.of("mailboxId"))
             .inReplyToMessageId("inReplyToMessageId")
@@ -322,7 +317,7 @@ public class MailboxMessageTest {
         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")
+                .blobId(BlobId.of("0"))
                 .threadId("user|box|0")
                 .mailboxIds(ImmutableList.of(MAILBOX_ID.serialize()))
                 .inReplyToMessageId("<SN...@phx.gbl>")
@@ -411,7 +406,7 @@ public class MailboxMessageTest {
         testMail.setModSeq(MOD_SEQ);
         
         String payload = "payload";
-        String blodId = "id1";
+        BlobId blodId = BlobId.of("id1");
         String type = "content";
         Attachment expectedAttachment = Attachment.builder()
                 .blobId(blodId)
@@ -423,7 +418,7 @@ public class MailboxMessageTest {
         Message testee = messageFactory.fromMailboxMessage(testMail,
                 ImmutableList.of(MessageAttachment.builder()
                         .attachment(org.apache.james.mailbox.store.mail.model.Attachment.builder()
-                            .attachmentId(AttachmentId.from(blodId))
+                            .attachmentId(AttachmentId.from(blodId.getRawValue()))
                             .bytes(payload.getBytes())
                             .type(type)
                             .build())
@@ -440,7 +435,7 @@ public class MailboxMessageTest {
     public void buildShouldThrowWhenOneAttachedMessageIsNotInAttachments() throws Exception {
         Message.builder()
             .id(MessageId.of("user|box|1"))
-            .blobId("blodId")
+            .blobId(BlobId.of("blobId"))
             .threadId("threadId")
             .mailboxIds(ImmutableList.of("mailboxId"))
             .headers(ImmutableMap.of("key", "value"))
@@ -448,7 +443,7 @@ public class MailboxMessageTest {
             .size(1)
             .date(ZonedDateTime.now())
             .preview("preview")
-            .attachedMessages(ImmutableMap.of("key", SubMessage.builder()
+            .attachedMessages(ImmutableMap.of(BlobId.of("key"), SubMessage.builder()
                     .headers(ImmutableMap.of("key", "value"))
                     .subject("subject")
                     .date(ZonedDateTime.now())
@@ -460,7 +455,7 @@ public class MailboxMessageTest {
     public void buildShouldNotThrowWhenOneAttachedMessageIsInAttachments() throws Exception {
         Message.builder()
             .id(MessageId.of("user|box|1"))
-            .blobId("blodId")
+            .blobId(BlobId.of("blobId"))
             .threadId("threadId")
             .mailboxIds(ImmutableList.of("mailboxId"))
             .headers(ImmutableMap.of("key", "value"))
@@ -469,11 +464,11 @@ public class MailboxMessageTest {
             .date(ZonedDateTime.now())
             .preview("preview")
             .attachments(ImmutableList.of(Attachment.builder()
-                    .blobId("key")
+                    .blobId(BlobId.of("key"))
                     .size(1)
                     .type("type")
                     .build()))
-            .attachedMessages(ImmutableMap.of("key", SubMessage.builder()
+            .attachedMessages(ImmutableMap.of(BlobId.of("key"), SubMessage.builder()
                     .headers(ImmutableMap.of("key", "value"))
                     .subject("subject")
                     .date(ZonedDateTime.now())

http://git-wip-us.apache.org/repos/asf/james-project/blob/7be2166e/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java
index 631fa96..24402a9 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java
@@ -56,7 +56,7 @@ public class SetMessagesResponseTest {
         ImmutableMap<CreationMessageId, Message> created = ImmutableMap.of(CreationMessageId.of("user|created|1"),
             Message.builder()
                 .id(MessageId.of("user|created|1"))
-                .blobId("blobId")
+                .blobId(BlobId.of("blobId"))
                 .threadId("threadId")
                 .mailboxIds(ImmutableList.of("mailboxId"))
                 .headers(ImmutableMap.of("key", "value"))
@@ -106,7 +106,7 @@ public class SetMessagesResponseTest {
     private ImmutableMap<CreationMessageId, Message> buildMessage(CreationMessageId messageId) {
         return ImmutableMap.of(messageId, Message.builder()
                 .id(MessageId.of(messageId.getId()))
-                .blobId("blobId")
+                .blobId(BlobId.of("blobId"))
                 .threadId("threadId")
                 .mailboxIds(ImmutableList.of())
                 .headers(ImmutableMap.of())

http://git-wip-us.apache.org/repos/asf/james-project/blob/7be2166e/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SubMailboxMessageTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SubMailboxMessageTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SubMailboxMessageTest.java
index dc0ada9..daa2a8a 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SubMailboxMessageTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SubMailboxMessageTest.java
@@ -64,14 +64,14 @@ public class SubMailboxMessageTest {
 
     @Test(expected=IllegalStateException.class)
     public void buildShouldThrowWhenAttachedMessageIsNotMatchingAttachments() {
-        Attachment simpleAttachment = Attachment.builder().blobId("blobId").type("type").name("name").size(123).build();
+        Attachment simpleAttachment = Attachment.builder().blobId(BlobId.of("blobId")).type("type").name("name").size(123).build();
         ImmutableList<Attachment> attachments = ImmutableList.of(simpleAttachment);
         SubMessage simpleMessage = SubMessage.builder()
                 .headers(ImmutableMap.of("key", "value"))
                 .subject("subject")
                 .date(ZonedDateTime.now())
                 .build();
-        ImmutableMap<String, SubMessage> attachedMessages = ImmutableMap.of("differentBlobId", simpleMessage);
+        ImmutableMap<BlobId, SubMessage> attachedMessages = ImmutableMap.of(BlobId.of("differentBlobId"), simpleMessage);
         SubMessage.builder()
             .headers(ImmutableMap.of("key", "value"))
             .subject("subject")
@@ -89,14 +89,14 @@ public class SubMailboxMessageTest {
         ImmutableList<Emailer> bcc = ImmutableList.of(Emailer.builder().name("bcc").email("bcc@domain").build());
         ImmutableList<Emailer> replyTo = ImmutableList.of(Emailer.builder().name("replyTo").email("replyTo@domain").build());
         ZonedDateTime currentDate = ZonedDateTime.now();
-        Attachment simpleAttachment = Attachment.builder().blobId("blobId").type("type").name("name").size(123).build();
+        Attachment simpleAttachment = Attachment.builder().blobId(BlobId.of("blobId")).type("type").name("name").size(123).build();
         ImmutableList<Attachment> attachments = ImmutableList.of(simpleAttachment);
         SubMessage simpleMessage = SubMessage.builder()
                 .headers(ImmutableMap.of("key", "value"))
                 .subject("subject")
                 .date(currentDate)
                 .build();
-        ImmutableMap<String, SubMessage> attachedMessages = ImmutableMap.of("blobId", simpleMessage);
+        ImmutableMap<BlobId, SubMessage> attachedMessages = ImmutableMap.of(BlobId.of("blobId"), simpleMessage);
         SubMessage expected = new SubMessage(
                 ImmutableMap.of("key", "value"),
                 Optional.of(from),


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


[5/5] james-project git commit: JAMES-1790 check that blobids exist when attaching them

Posted by ad...@apache.org.
JAMES-1790 check that blobids exist when attaching them


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

Branch: refs/heads/master
Commit: 662fa4a954571ef6a0d7044873359211eeaae181
Parents: 7be2166
Author: Matthieu Baechler <ma...@linagora.com>
Authored: Tue Jul 5 17:09:13 2016 +0200
Committer: Antoine Duprat <ad...@linagora.com>
Committed: Fri Jul 8 16:45:00 2016 +0200

----------------------------------------------------------------------
 .../integration/SetMessagesMethodTest.java      | 619 +++++++++++++------
 .../AttachmentsNotFoundException.java           |  40 ++
 .../jmap/methods/MIMEMessageConverter.java      | 127 +++-
 .../methods/SetMessagesCreationProcessor.java   |  90 ++-
 .../james/jmap/model/CreationMessage.java       |  32 +-
 .../org/apache/james/jmap/model/SetError.java   |   9 +-
 .../james/jmap/model/SetMessagesError.java      |  92 +++
 .../jmap/methods/MIMEMessageConverterTest.java  |  73 ++-
 .../SetMessagesCreationProcessorTest.java       |  84 ++-
 9 files changed, 887 insertions(+), 279 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/662fa4a9/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java
index af96309..be3297b 100644
--- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java
+++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java
@@ -33,10 +33,12 @@ import static org.hamcrest.Matchers.hasKey;
 import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.isEmptyOrNullString;
 import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.nullValue;
 import static org.hamcrest.collection.IsMapWithSize.aMapWithSize;
 import static org.hamcrest.collection.IsMapWithSize.anEmptyMap;
 
 import java.io.ByteArrayInputStream;
+import java.io.IOException;
 import java.time.ZonedDateTime;
 import java.util.Date;
 import java.util.List;
@@ -52,6 +54,7 @@ import org.apache.james.jmap.model.mailbox.Role;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.model.MailboxConstants;
 import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.store.mail.model.Attachment;
 import org.apache.james.mailbox.store.mail.model.Mailbox;
 import org.hamcrest.Matchers;
 import org.junit.After;
@@ -93,12 +96,12 @@ public abstract class SetMessagesMethodTest {
         jmapServer = createJmapServer();
         jmapServer.start();
         RestAssured.requestSpecification = new RequestSpecBuilder()
-        		.setContentType(ContentType.JSON)
-        		.setAccept(ContentType.JSON)
-        		.setConfig(newConfig().encoderConfig(encoderConfig().defaultContentCharset(Charsets.UTF_8)))
-        		.setPort(jmapServer.getJmapPort())
-        		.build();
-        
+                .setContentType(ContentType.JSON)
+                .setAccept(ContentType.JSON)
+                .setConfig(newConfig().encoderConfig(encoderConfig().defaultContentCharset(Charsets.UTF_8)))
+                .setPort(jmapServer.getJmapPort())
+                .build();
+
         username = "username@" + USERS_DOMAIN;
         String password = "password";
         jmapServer.serverProbe().addDomain(USERS_DOMAIN);
@@ -148,6 +151,7 @@ public abstract class SetMessagesMethodTest {
         .when()
             .post("/jmap")
         .then()
+            .log().ifValidationFails()
             .statusCode(200)
             .body(NAME, equalTo("error"))
             .body(ARGUMENTS + ".type", equalTo("Not yet implemented"));
@@ -161,6 +165,7 @@ public abstract class SetMessagesMethodTest {
         .when()
             .post("/jmap")
         .then()
+            .log().ifValidationFails()
             .statusCode(200)
             .body(NAME, equalTo("error"))
             .body(ARGUMENTS + ".type", equalTo("Not yet implemented"));
@@ -215,7 +220,7 @@ public abstract class SetMessagesMethodTest {
         jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox");
 
         jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"),
-                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes()), new Date(), false, new Flags());
+                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags());
         await();
 
         given()
@@ -224,6 +229,7 @@ public abstract class SetMessagesMethodTest {
         .when()
             .post("/jmap")
         .then()
+            .log().ifValidationFails()
             .statusCode(200)
             .body(NAME, equalTo("messagesSet"))
             .body(ARGUMENTS + ".notDestroyed", anEmptyMap())
@@ -237,7 +243,7 @@ public abstract class SetMessagesMethodTest {
         jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox");
 
         jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"),
-                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes()), new Date(), false, new Flags());
+                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags());
         await();
 
         // When
@@ -247,6 +253,7 @@ public abstract class SetMessagesMethodTest {
         .when()
             .post("/jmap")
         .then()
+            .log().ifValidationFails()
             .statusCode(200);
 
         // Then
@@ -256,6 +263,7 @@ public abstract class SetMessagesMethodTest {
         .when()
             .post("/jmap")
         .then()
+            .log().ifValidationFails()
             .statusCode(200)
             .body(NAME, equalTo("messages"))
             .body(ARGUMENTS + ".list", empty());
@@ -266,13 +274,13 @@ public abstract class SetMessagesMethodTest {
         jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox");
 
         jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"),
-                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes()), new Date(), false, new Flags());
+                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags());
 
         jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"),
-                new ByteArrayInputStream("Subject: test2\r\n\r\ntestmail".getBytes()), new Date(), false, new Flags());
+                new ByteArrayInputStream("Subject: test2\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags());
 
         jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"),
-                new ByteArrayInputStream("Subject: test3\r\n\r\ntestmail".getBytes()), new Date(), false, new Flags());
+                new ByteArrayInputStream("Subject: test3\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags());
         await();
 
         String missingMessageId = username + "|mailbox|4";
@@ -282,6 +290,7 @@ public abstract class SetMessagesMethodTest {
         .when()
             .post("/jmap")
         .then()
+            .log().ifValidationFails()
             .statusCode(200)
             .body(NAME, equalTo("messagesSet"))
             .body(ARGUMENTS + ".destroyed", hasSize(2))
@@ -299,13 +308,13 @@ public abstract class SetMessagesMethodTest {
         jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox");
 
         jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"),
-                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes()), new Date(), false, new Flags());
+                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags());
 
         jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"),
-                new ByteArrayInputStream("Subject: test2\r\n\r\ntestmail".getBytes()), new Date(), false, new Flags());
+                new ByteArrayInputStream("Subject: test2\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags());
 
         jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"),
-                new ByteArrayInputStream("Subject: test3\r\n\r\ntestmail".getBytes()), new Date(), false, new Flags());
+                new ByteArrayInputStream("Subject: test3\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags());
         await();
 
         // When
@@ -315,6 +324,7 @@ public abstract class SetMessagesMethodTest {
         .when()
             .post("/jmap")
         .then()
+            .log().ifValidationFails()
             .statusCode(200);
 
         // Then
@@ -324,6 +334,7 @@ public abstract class SetMessagesMethodTest {
         .when()
             .post("/jmap")
         .then()
+            .log().ifValidationFails()
             .statusCode(200)
             .body(NAME, equalTo("messages"))
             .body(ARGUMENTS + ".list", hasSize(1));
@@ -335,7 +346,7 @@ public abstract class SetMessagesMethodTest {
         jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox");
 
         jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"),
-                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes()), new Date(), false, new Flags());
+                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags());
         await();
 
         String presumedMessageId = username + "|mailbox|1";
@@ -348,8 +359,8 @@ public abstract class SetMessagesMethodTest {
             .post("/jmap")
         // Then
         .then()
-            .spec(getSetMessagesUpdateOKResponseAssertions(presumedMessageId))
-            .log().ifValidationFails();
+            .log().ifValidationFails()
+            .spec(getSetMessagesUpdateOKResponseAssertions(presumedMessageId));
     }
 
     private ResponseSpecification getSetMessagesUpdateOKResponseAssertions(String messageId) {
@@ -369,27 +380,27 @@ public abstract class SetMessagesMethodTest {
         jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox");
 
         jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"),
-                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes()), new Date(), false, new Flags());
+                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags());
         await();
 
         String presumedMessageId = username + "|mailbox|1";
         given()
-                .header("Authorization", accessToken.serialize())
-                .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isUnread\" : false } } }, \"#0\"]]", presumedMessageId))
+            .header("Authorization", accessToken.serialize())
+            .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isUnread\" : false } } }, \"#0\"]]", presumedMessageId))
         // When
         .when()
-                .post("/jmap");
+            .post("/jmap");
         // Then
         with()
-                .header("Authorization", accessToken.serialize())
-                .body("[[\"getMessages\", {\"ids\": [\"" + presumedMessageId + "\"]}, \"#0\"]]")
-                .post("/jmap")
+            .header("Authorization", accessToken.serialize())
+            .body("[[\"getMessages\", {\"ids\": [\"" + presumedMessageId + "\"]}, \"#0\"]]")
+            .post("/jmap")
         .then()
-                .statusCode(200)
-                .body(NAME, equalTo("messages"))
-                .body(ARGUMENTS + ".list", hasSize(1))
-                .body(ARGUMENTS + ".list[0].isUnread", equalTo(false))
-                .log().ifValidationFails();
+            .log().ifValidationFails()
+            .statusCode(200)
+            .body(NAME, equalTo("messages"))
+            .body(ARGUMENTS + ".list", hasSize(1))
+            .body(ARGUMENTS + ".list[0].isUnread", equalTo(false));
     }
 
     @Test
@@ -398,20 +409,20 @@ public abstract class SetMessagesMethodTest {
         jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox");
 
         jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"),
-                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes()), new Date(), false, new Flags(Flags.Flag.SEEN));
+                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags(Flags.Flag.SEEN));
         await();
 
         String presumedMessageId = username + "|mailbox|1";
         given()
-                .header("Authorization", accessToken.serialize())
-                .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isUnread\" : true } } }, \"#0\"]]", presumedMessageId))
+            .header("Authorization", accessToken.serialize())
+            .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isUnread\" : true } } }, \"#0\"]]", presumedMessageId))
         // When
         .when()
-                .post("/jmap")
+            .post("/jmap")
         // Then
         .then()
-                .spec(getSetMessagesUpdateOKResponseAssertions(presumedMessageId))
-                .log().ifValidationFails();
+            .log().ifValidationFails()
+            .spec(getSetMessagesUpdateOKResponseAssertions(presumedMessageId));
     }
 
     @Test
@@ -420,26 +431,26 @@ public abstract class SetMessagesMethodTest {
         jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox");
 
         jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"),
-                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes()), new Date(), false, new Flags(Flags.Flag.SEEN));
+                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags(Flags.Flag.SEEN));
         await();
 
         String presumedMessageId = username + "|mailbox|1";
         given()
-                .header("Authorization", accessToken.serialize())
-                .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isUnread\" : true } } }, \"#0\"]]", presumedMessageId))
+            .header("Authorization", accessToken.serialize())
+            .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isUnread\" : true } } }, \"#0\"]]", presumedMessageId))
         // When
         .when()
-                .post("/jmap");
+            .post("/jmap");
         // Then
         with()
-                .header("Authorization", accessToken.serialize())
-                .body("[[\"getMessages\", {\"ids\": [\"" + presumedMessageId + "\"]}, \"#0\"]]")
-                .post("/jmap")
+            .header("Authorization", accessToken.serialize())
+            .body("[[\"getMessages\", {\"ids\": [\"" + presumedMessageId + "\"]}, \"#0\"]]")
+            .post("/jmap")
         .then()
-                .body(NAME, equalTo("messages"))
-                .body(ARGUMENTS + ".list", hasSize(1))
-                .body(ARGUMENTS + ".list[0].isUnread", equalTo(true))
-                .log().ifValidationFails();
+            .log().ifValidationFails()
+            .body(NAME, equalTo("messages"))
+            .body(ARGUMENTS + ".list", hasSize(1))
+            .body(ARGUMENTS + ".list[0].isUnread", equalTo(true));
     }
 
 
@@ -449,20 +460,20 @@ public abstract class SetMessagesMethodTest {
         jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox");
 
         jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"),
-                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes()), new Date(), false, new Flags());
+                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags());
         await();
 
         String presumedMessageId = username + "|mailbox|1";
         given()
-                .header("Authorization", accessToken.serialize())
-                .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isFlagged\" : true } } }, \"#0\"]]", presumedMessageId))
+            .header("Authorization", accessToken.serialize())
+            .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isFlagged\" : true } } }, \"#0\"]]", presumedMessageId))
         // When
         .when()
-                .post("/jmap")
+            .post("/jmap")
         // Then
         .then()
-                .spec(getSetMessagesUpdateOKResponseAssertions(presumedMessageId))
-                .log().ifValidationFails();
+            .log().ifValidationFails()
+            .spec(getSetMessagesUpdateOKResponseAssertions(presumedMessageId));
     }
 
     @Test
@@ -471,53 +482,53 @@ public abstract class SetMessagesMethodTest {
         jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox");
 
         jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"),
-                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes()), new Date(), false, new Flags());
+                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags());
         await();
 
         String presumedMessageId = username + "|mailbox|1";
         given()
-                .header("Authorization", accessToken.serialize())
-                .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isFlagged\" : true } } }, \"#0\"]]", presumedMessageId))
+            .header("Authorization", accessToken.serialize())
+            .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isFlagged\" : true } } }, \"#0\"]]", presumedMessageId))
         // When
         .when()
-                .post("/jmap");
+            .post("/jmap");
         // Then
         with()
-                .header("Authorization", accessToken.serialize())
-                .body("[[\"getMessages\", {\"ids\": [\"" + presumedMessageId + "\"]}, \"#0\"]]")
-                .post("/jmap")
+            .header("Authorization", accessToken.serialize())
+            .body("[[\"getMessages\", {\"ids\": [\"" + presumedMessageId + "\"]}, \"#0\"]]")
+            .post("/jmap")
         .then()
-                .body(NAME, equalTo("messages"))
-                .body(ARGUMENTS + ".list", hasSize(1))
-                .body(ARGUMENTS + ".list[0].isFlagged", equalTo(true))
-                .log().ifValidationFails();
+            .log().ifValidationFails()
+            .body(NAME, equalTo("messages"))
+            .body(ARGUMENTS + ".list", hasSize(1))
+            .body(ARGUMENTS + ".list[0].isFlagged", equalTo(true));
     }
 
     @Test
     public void setMessagesShouldRejectUpdateWhenPropertyHasWrongType() throws MailboxException {
         jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox");
         jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"),
-                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes()), new Date(), false, new Flags());
+                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags());
 
         await();
 
         String messageId = username + "|mailbox|1";
 
         given()
-                .header("Authorization", accessToken.serialize())
-                .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isUnread\" : \"123\" } } }, \"#0\"]]", messageId))
+            .header("Authorization", accessToken.serialize())
+            .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isUnread\" : \"123\" } } }, \"#0\"]]", messageId))
         .when()
-                .post("/jmap")
+            .post("/jmap")
         .then()
-                .log().ifValidationFails()
-                .statusCode(200)
-                .body(NAME, equalTo("messagesSet"))
-                .body(NOT_UPDATED, hasKey(messageId))
-                .body(NOT_UPDATED + "[\""+messageId+"\"].type", equalTo("invalidProperties"))
-                .body(NOT_UPDATED + "[\""+messageId+"\"].properties[0]", equalTo("isUnread"))
-                .body(NOT_UPDATED + "[\""+messageId+"\"].description", equalTo("isUnread: Can not construct instance of java.lang.Boolean from String value '123': only \"true\" or \"false\" recognized\n" +
-                        " at [Source: {\"isUnread\":\"123\"}; line: 1, column: 2] (through reference chain: org.apache.james.jmap.model.Builder[\"isUnread\"])"))
-                .body(ARGUMENTS + ".updated", hasSize(0));
+            .log().ifValidationFails()
+            .statusCode(200)
+            .body(NAME, equalTo("messagesSet"))
+            .body(NOT_UPDATED, hasKey(messageId))
+            .body(NOT_UPDATED + "[\""+messageId+"\"].type", equalTo("invalidProperties"))
+            .body(NOT_UPDATED + "[\""+messageId+"\"].properties[0]", equalTo("isUnread"))
+            .body(NOT_UPDATED + "[\""+messageId+"\"].description", equalTo("isUnread: Can not construct instance of java.lang.Boolean from String value '123': only \"true\" or \"false\" recognized\n" +
+                    " at [Source: {\"isUnread\":\"123\"}; line: 1, column: 2] (through reference chain: org.apache.james.jmap.model.Builder[\"isUnread\"])"))
+            .body(ARGUMENTS + ".updated", hasSize(0));
     }
 
     @Test
@@ -525,27 +536,27 @@ public abstract class SetMessagesMethodTest {
     public void setMessagesShouldRejectUpdateWhenPropertiesHaveWrongTypes() throws MailboxException {
         jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox");
         jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"),
-                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes()), new Date(), false, new Flags());
+                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags());
 
         await();
 
         String messageId = username + "|mailbox|1";
 
         given()
-                .header("Authorization", accessToken.serialize())
-                .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isUnread\" : \"123\", \"isFlagged\" : 456 } } }, \"#0\"]]", messageId))
+            .header("Authorization", accessToken.serialize())
+            .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isUnread\" : \"123\", \"isFlagged\" : 456 } } }, \"#0\"]]", messageId))
         .when()
-                .post("/jmap")
+            .post("/jmap")
         .then()
-                .log().ifValidationFails()
-                .statusCode(200)
-                .body(NAME, equalTo("messagesSet"))
-                .body(NOT_UPDATED, hasKey(messageId))
-                .body(NOT_UPDATED + "[\""+messageId+"\"].type", equalTo("invalidProperties"))
-                .body(NOT_UPDATED + "[\""+messageId+"\"].properties", hasSize(2))
-                .body(NOT_UPDATED + "[\""+messageId+"\"].properties[0]", equalTo("isUnread"))
-                .body(NOT_UPDATED + "[\""+messageId+"\"].properties[1]", equalTo("isFlagged"))
-                .body(ARGUMENTS + ".updated", hasSize(0));
+            .log().ifValidationFails()
+            .statusCode(200)
+            .body(NAME, equalTo("messagesSet"))
+            .body(NOT_UPDATED, hasKey(messageId))
+            .body(NOT_UPDATED + "[\""+messageId+"\"].type", equalTo("invalidProperties"))
+            .body(NOT_UPDATED + "[\""+messageId+"\"].properties", hasSize(2))
+            .body(NOT_UPDATED + "[\""+messageId+"\"].properties[0]", equalTo("isUnread"))
+            .body(NOT_UPDATED + "[\""+messageId+"\"].properties[1]", equalTo("isFlagged"))
+            .body(ARGUMENTS + ".updated", hasSize(0));
     }
 
     @Test
@@ -554,20 +565,20 @@ public abstract class SetMessagesMethodTest {
         jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox");
 
         jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"),
-                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes()), new Date(), false, new Flags());
+                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags());
         await();
 
         String presumedMessageId = username + "|mailbox|1";
         // When
         given()
-                .header("Authorization", accessToken.serialize())
-                .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isAnswered\" : true } } }, \"#0\"]]", presumedMessageId))
+            .header("Authorization", accessToken.serialize())
+            .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isAnswered\" : true } } }, \"#0\"]]", presumedMessageId))
         .when()
-                .post("/jmap")
+            .post("/jmap")
         // Then
         .then()
-                .spec(getSetMessagesUpdateOKResponseAssertions(presumedMessageId))
-                .log().ifValidationFails();
+            .log().ifValidationFails()
+            .spec(getSetMessagesUpdateOKResponseAssertions(presumedMessageId));
     }
 
     @Test
@@ -576,26 +587,26 @@ public abstract class SetMessagesMethodTest {
         jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "mailbox");
 
         jmapServer.serverProbe().appendMessage(username, new MailboxPath(MailboxConstants.USER_NAMESPACE, username, "mailbox"),
-                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes()), new Date(), false, new Flags());
+                new ByteArrayInputStream("Subject: test\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), new Date(), false, new Flags());
         await();
 
         String presumedMessageId = username + "|mailbox|1";
         given()
-                .header("Authorization", accessToken.serialize())
-                .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isAnswered\" : true } } }, \"#0\"]]", presumedMessageId))
+            .header("Authorization", accessToken.serialize())
+            .body(String.format("[[\"setMessages\", {\"update\": {\"%s\" : { \"isAnswered\" : true } } }, \"#0\"]]", presumedMessageId))
         // When
         .when()
-                .post("/jmap");
+            .post("/jmap");
         // Then
         with()
-                .header("Authorization", accessToken.serialize())
-                .body("[[\"getMessages\", {\"ids\": [\"" + presumedMessageId + "\"]}, \"#0\"]]")
-                .post("/jmap")
+            .header("Authorization", accessToken.serialize())
+            .body("[[\"getMessages\", {\"ids\": [\"" + presumedMessageId + "\"]}, \"#0\"]]")
+            .post("/jmap")
         .then()
-                .body(NAME, equalTo("messages"))
-                .body(ARGUMENTS + ".list", hasSize(1))
-                .body(ARGUMENTS + ".list[0].isAnswered", equalTo(true))
-                .log().ifValidationFails();
+            .log().ifValidationFails()
+            .body(NAME, equalTo("messages"))
+            .body(ARGUMENTS + ".list", hasSize(1))
+            .body(ARGUMENTS + ".list[0].isAnswered", equalTo(true));
     }
 
     @Test
@@ -640,34 +651,34 @@ public abstract class SetMessagesMethodTest {
                 "]";
 
         given()
-                .header("Authorization", accessToken.serialize())
-                .body(requestBody)
+            .header("Authorization", accessToken.serialize())
+            .body(requestBody)
         .when()
-                .post("/jmap")
+            .post("/jmap")
         .then()
-                .log().ifValidationFails()
-                .statusCode(200)
-                .body(NAME, equalTo("messagesSet"))
-                .body(ARGUMENTS + ".notCreated", aMapWithSize(0))
-                // note that assertions on result message had to be split between
-                // string-typed values and boolean-typed value assertions on the same .created entry
-                // make sure only one creation has been processed
-                .body(ARGUMENTS + ".created", aMapWithSize(1))
-                // assert server-set attributes are returned
-                .body(ARGUMENTS + ".created", hasEntry(equalTo(messageCreationId), Matchers.allOf(
-                        hasEntry(equalTo("id"), not(isEmptyOrNullString())),
-                        hasEntry(equalTo("blobId"), not(isEmptyOrNullString())),
-                        hasEntry(equalTo("threadId"), not(isEmptyOrNullString())),
-                        hasEntry(equalTo("size"), not(isEmptyOrNullString()))
-                )))
-                // assert that message flags are all unset
-                .body(ARGUMENTS + ".created", hasEntry(equalTo(messageCreationId), Matchers.allOf(
-                        hasEntry(equalTo("isDraft"), equalTo(false)),
-                        hasEntry(equalTo("isUnread"), equalTo(false)),
-                        hasEntry(equalTo("isFlagged"), equalTo(false)),
-                        hasEntry(equalTo("isAnswered"), equalTo(false))
-                )))
-                ;
+            .log().ifValidationFails()
+            .statusCode(200)
+            .body(NAME, equalTo("messagesSet"))
+            .body(ARGUMENTS + ".notCreated", aMapWithSize(0))
+            // note that assertions on result message had to be split between
+            // string-typed values and boolean-typed value assertions on the same .created entry
+            // make sure only one creation has been processed
+            .body(ARGUMENTS + ".created", aMapWithSize(1))
+            // assert server-set attributes are returned
+            .body(ARGUMENTS + ".created", hasEntry(equalTo(messageCreationId), Matchers.allOf(
+                    hasEntry(equalTo("id"), not(isEmptyOrNullString())),
+                    hasEntry(equalTo("blobId"), not(isEmptyOrNullString())),
+                    hasEntry(equalTo("threadId"), not(isEmptyOrNullString())),
+                    hasEntry(equalTo("size"), not(isEmptyOrNullString()))
+            )))
+            // assert that message flags are all unset
+            .body(ARGUMENTS + ".created", hasEntry(equalTo(messageCreationId), Matchers.allOf(
+                    hasEntry(equalTo("isDraft"), equalTo(false)),
+                    hasEntry(equalTo("isUnread"), equalTo(false)),
+                    hasEntry(equalTo("isFlagged"), equalTo(false)),
+                    hasEntry(equalTo("isAnswered"), equalTo(false))
+            )))
+            ;
     }
 
     @Test
@@ -728,23 +739,24 @@ public abstract class SetMessagesMethodTest {
                 "]";
 
         given()
-                .header("Authorization", accessToken.serialize())
-                .body(requestBody)
+            .header("Authorization", accessToken.serialize())
+            .body(requestBody)
         // When
         .when()
-                .post("/jmap");
+            .post("/jmap");
 
         // Then
         with()
-                .header("Authorization", accessToken.serialize())
-                .body("[[\"getMessages\", {\"ids\": [\"" + presumedMessageId + "\"]}, \"#0\"]]")
+            .header("Authorization", accessToken.serialize())
+            .body("[[\"getMessages\", {\"ids\": [\"" + presumedMessageId + "\"]}, \"#0\"]]")
         .post("/jmap")
         .then()
-                .body(NAME, equalTo("messages"))
-                .body(ARGUMENTS + ".list", hasSize(1))
-                .body(ARGUMENTS + ".list[0].subject", equalTo(messageSubject))
-                .body(ARGUMENTS + ".list[0].mailboxIds", contains(outboxId))
-                ;
+            .log().ifValidationFails()
+            .body(NAME, equalTo("messages"))
+            .body(ARGUMENTS + ".list", hasSize(1))
+            .body(ARGUMENTS + ".list[0].subject", equalTo(messageSubject))
+            .body(ARGUMENTS + ".list[0].mailboxIds", contains(outboxId))
+            ;
     }
 
     @Test
@@ -822,19 +834,19 @@ public abstract class SetMessagesMethodTest {
                 "]";
 
         given()
-                .header("Authorization", accessToken.serialize())
-                .body(requestBody)
-                .when()
-                .post("/jmap")
-                .then()
-                .log().ifValidationFails()
-                .statusCode(200)
-                .body(NAME, equalTo("messagesSet"))
+            .header("Authorization", accessToken.serialize())
+            .body(requestBody)
+        .when()
+            .post("/jmap")
+        .then()
+            .log().ifValidationFails()
+            .statusCode(200)
+            .body(NAME, equalTo("messagesSet"))
 
-                .body(ARGUMENTS + ".notCreated", hasKey(messageCreationId))
-                .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].type", equalTo("invalidProperties"))
-                .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].description", endsWith("no recipient address set"))
-                .body(ARGUMENTS + ".created", aMapWithSize(0));
+            .body(ARGUMENTS + ".notCreated", hasKey(messageCreationId))
+            .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].type", equalTo("invalidProperties"))
+            .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].description", endsWith("no recipient address set"))
+            .body(ARGUMENTS + ".created", aMapWithSize(0));
     }
 
     @Test
@@ -857,20 +869,20 @@ public abstract class SetMessagesMethodTest {
                 "]";
 
         given()
-                .header("Authorization", accessToken.serialize())
-                .body(requestBody)
+            .header("Authorization", accessToken.serialize())
+            .body(requestBody)
         .when()
-                .post("/jmap")
+            .post("/jmap")
         .then()
-                .log().ifValidationFails()
-                .statusCode(200)
-                .body(NAME, equalTo("messagesSet"))
-                .body(ARGUMENTS + ".notCreated", hasKey(messageCreationId))
-                .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].type", equalTo("invalidProperties"))
-                .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].description", endsWith("'from' address is mandatory"))
-                .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].properties", hasSize(1))
-                .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].properties", contains("from"))
-                .body(ARGUMENTS + ".created", aMapWithSize(0));
+            .log().ifValidationFails()
+            .statusCode(200)
+            .body(NAME, equalTo("messagesSet"))
+            .body(ARGUMENTS + ".notCreated", hasKey(messageCreationId))
+            .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].type", equalTo("invalidProperties"))
+            .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].description", endsWith("'from' address is mandatory"))
+            .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].properties", hasSize(1))
+            .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].properties", contains("from"))
+            .body(ARGUMENTS + ".created", aMapWithSize(0));
     }
 
     @Test
@@ -895,19 +907,19 @@ public abstract class SetMessagesMethodTest {
                 "]";
 
         given()
-                .header("Authorization", accessToken.serialize())
-                .body(requestBody)
+            .header("Authorization", accessToken.serialize())
+            .body(requestBody)
         .when()
-                .post("/jmap")
+            .post("/jmap")
         .then()
-                .log().ifValidationFails()
-                .statusCode(200)
-                .body(NAME, equalTo("messagesSet"))
-                .body(ARGUMENTS + ".created", aMapWithSize(1))
-                .body(ARGUMENTS + ".created", hasKey(messageCreationId))
-                .body(ARGUMENTS + ".created[\""+messageCreationId+"\"].headers.from", equalTo(fromAddress))
-                .body(ARGUMENTS + ".created[\""+messageCreationId+"\"].from.name", equalTo(fromAddress))
-                .body(ARGUMENTS + ".created[\""+messageCreationId+"\"].from.email", equalTo(fromAddress));
+            .log().ifValidationFails()
+            .statusCode(200)
+            .body(NAME, equalTo("messagesSet"))
+            .body(ARGUMENTS + ".created", aMapWithSize(1))
+            .body(ARGUMENTS + ".created", hasKey(messageCreationId))
+            .body(ARGUMENTS + ".created[\""+messageCreationId+"\"].headers.from", equalTo(fromAddress))
+            .body(ARGUMENTS + ".created[\""+messageCreationId+"\"].from.name", equalTo(fromAddress))
+            .body(ARGUMENTS + ".created[\""+messageCreationId+"\"].from.email", equalTo(fromAddress));
     }
 
     @Test
@@ -932,19 +944,19 @@ public abstract class SetMessagesMethodTest {
                 "]";
 
         given()
-                .header("Authorization", accessToken.serialize())
-                .body(requestBody)
+            .header("Authorization", accessToken.serialize())
+            .body(requestBody)
         .when()
-                .post("/jmap")
+            .post("/jmap")
         .then()
-                .log().ifValidationFails()
-                .statusCode(200)
-                .body(NAME, equalTo("messagesSet"))
-                .body(ARGUMENTS + ".created", aMapWithSize(1))
-                .body(ARGUMENTS + ".created", hasKey(messageCreationId))
-                .body(ARGUMENTS + ".created[\""+messageCreationId+"\"].headers.from", equalTo(fromAddress))
-                .body(ARGUMENTS + ".created[\""+messageCreationId+"\"].from.name", equalTo(fromAddress))
-                .body(ARGUMENTS + ".created[\""+messageCreationId+"\"].from.email", equalTo(fromAddress));
+            .log().ifValidationFails()
+            .statusCode(200)
+            .body(NAME, equalTo("messagesSet"))
+            .body(ARGUMENTS + ".created", aMapWithSize(1))
+            .body(ARGUMENTS + ".created", hasKey(messageCreationId))
+            .body(ARGUMENTS + ".created[\""+messageCreationId+"\"].headers.from", equalTo(fromAddress))
+            .body(ARGUMENTS + ".created[\""+messageCreationId+"\"].from.name", equalTo(fromAddress))
+            .body(ARGUMENTS + ".created[\""+messageCreationId+"\"].from.email", equalTo(fromAddress));
     }
 
     @Test
@@ -1004,20 +1016,20 @@ public abstract class SetMessagesMethodTest {
                 "]";
 
         given()
-                .header("Authorization", accessToken.serialize())
-                .body(requestBody)
+            .header("Authorization", accessToken.serialize())
+            .body(requestBody)
         .when()
-                .post("/jmap")
+            .post("/jmap")
         .then()
-                .log().ifValidationFails()
-                .statusCode(200)
-                .body(NAME, equalTo("messagesSet"))
-                .body(ARGUMENTS + ".notCreated", hasKey(messageCreationId))
-                .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].type", equalTo("invalidProperties"))
-                .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].properties", hasSize(1))
-                .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].properties", contains("subject"))
-                .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].description", endsWith("'subject' is missing"))
-                .body(ARGUMENTS + ".created", aMapWithSize(0));
+            .log().ifValidationFails()
+            .statusCode(200)
+            .body(NAME, equalTo("messagesSet"))
+            .body(ARGUMENTS + ".notCreated", hasKey(messageCreationId))
+            .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].type", equalTo("invalidProperties"))
+            .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].properties", hasSize(1))
+            .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].properties", contains("subject"))
+            .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].description", endsWith("'subject' is missing"))
+            .body(ARGUMENTS + ".created", aMapWithSize(0));
     }
 
 
@@ -1516,7 +1528,7 @@ public abstract class SetMessagesMethodTest {
 
         ZonedDateTime dateTime = ZonedDateTime.parse("2014-10-30T14:12:00Z");
         jmapServer.serverProbe().appendMessage(username, new MailboxPath("#private", username, "inbox"),
-                new ByteArrayInputStream("Subject: my test subject\r\n\r\ntestmail".getBytes()), Date.from(dateTime.toInstant()), false, new Flags());
+                new ByteArrayInputStream("Subject: my test subject\r\n\r\ntestmail".getBytes(Charsets.UTF_8)), Date.from(dateTime.toInstant()), false, new Flags());
 
         String messageToMoveId = "user|inbox|1";
 
@@ -1547,4 +1559,209 @@ public abstract class SetMessagesMethodTest {
                         + "(through reference chain: org.apache.james.jmap.model.Builder[\"mailboxIds\"])"))
                .body(ARGUMENTS + ".updated", hasSize(0));
     }
+    
+    @Test
+    public void setMessagesShouldReturnAttachmentsNotFoundWhenBlobIdDoesntExist() throws Exception {
+        jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "sent");
+        await();
+        String messageCreationId = "creationId";
+        String fromAddress = username;
+        String outboxId = getOutboxId(accessToken);
+        String requestBody = "[" +
+                "  [" +
+                "    \"setMessages\","+
+                "    {" +
+                "      \"create\": { \"" + messageCreationId  + "\" : {" +
+                "        \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
+                "        \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
+                "        \"subject\": \"Message with a broken blobId\"," +
+                "        \"textBody\": \"Test body\"," +
+                "        \"mailboxIds\": [\"" + outboxId + "\"], " +
+                "        \"attachments\": [" +
+                "                {\"blobId\" : \"brokenId1\", \"type\" : \"image/gif\", \"size\" : 1337}," +
+                "                {\"blobId\" : \"brokenId2\", \"type\" : \"image/jpeg\", \"size\" : 1337}" +
+                "             ]" +
+                "      }}" +
+                "    }," +
+                "    \"#0\"" +
+                "  ]" +
+                "]";
+
+        String notCreatedPath = ARGUMENTS + ".notCreated[\""+messageCreationId+"\"]";
+
+        given()
+            .header("Authorization", accessToken.serialize())
+            .body(requestBody)
+        .when()
+            .post("/jmap")
+        .then()
+            .statusCode(200)
+            .body(NAME, equalTo("messagesSet"))
+            .body(ARGUMENTS + ".notCreated", hasKey(messageCreationId))
+            .body(notCreatedPath + ".type", equalTo("invalidProperties"))
+            .body(notCreatedPath + ".attachmentsNotFound", contains("brokenId1", "brokenId2"))
+            .body(ARGUMENTS + ".created", aMapWithSize(0));
+    }
+
+    @Test
+    public void setMessagesShouldReturnAttachmentsWhenMessageHasAttachment() throws Exception {
+        jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "sent");
+
+        Attachment attachment = Attachment.builder()
+                .bytes("attachment".getBytes(Charsets.UTF_8))
+                .type("application/octet-stream")
+                .build();
+        uploadAttachment(attachment);
+        Attachment attachment2 = Attachment.builder()
+                .bytes("attachment2".getBytes(Charsets.UTF_8))
+                .type("application/octet-stream")
+                .build();
+        uploadAttachment(attachment2);
+
+        String messageCreationId = "creationId";
+        String fromAddress = username;
+        String outboxId = getOutboxId(accessToken);
+        String requestBody = "[" +
+                "  [" +
+                "    \"setMessages\","+
+                "    {" +
+                "      \"create\": { \"" + messageCreationId  + "\" : {" +
+                "        \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
+                "        \"to\": [{ \"name\": \"BOB\", \"email\": \"someone@example.com\"}]," +
+                "        \"subject\": \"Message with two attachments\"," +
+                "        \"textBody\": \"Test body\"," +
+                "        \"mailboxIds\": [\"" + outboxId + "\"], " +
+                "        \"attachments\": [" +
+                "               {\"blobId\" : \"" + attachment.getAttachmentId().getId() + "\", " +
+                "               \"type\" : \"" + attachment.getType() + "\", " +
+                "               \"size\" : " + attachment.getSize() + "}," +
+                "               {\"blobId\" : \"" + attachment2.getAttachmentId().getId() + "\", " +
+                "               \"type\" : \"" + attachment2.getType() + "\", " +
+                "               \"size\" : " + attachment2.getSize() + ", " +
+                "               \"cid\" : \"123456789\", " +
+                "               \"isInline\" : true }" +
+                "           ]" +
+                "      }}" +
+                "    }," +
+                "    \"#0\"" +
+                "  ]" +
+                "]";
+
+        String createdPath = ARGUMENTS + ".created[\""+messageCreationId+"\"]";
+        String firstAttachment = createdPath + ".attachments[0]";
+        String secondAttachment = createdPath + ".attachments[1]";
+
+        given()
+            .header("Authorization", accessToken.serialize())
+            .body(requestBody)
+        .when()
+            .post("/jmap")
+        .then()
+            .statusCode(200)
+            .body(NAME, equalTo("messagesSet"))
+            .body(ARGUMENTS + ".notCreated", aMapWithSize(0))
+            .body(ARGUMENTS + ".created", aMapWithSize(1))
+            .body(createdPath + ".attachments", hasSize(2))
+            .body(firstAttachment + ".blobId", equalTo(attachment.getAttachmentId().getId()))
+            .body(firstAttachment + ".type", equalTo("application/octet-stream; charset=UTF-8"))
+            .body(firstAttachment + ".size", equalTo((int) attachment.getSize()))
+            .body(firstAttachment + ".cid", nullValue())
+            .body(firstAttachment + ".isInline", equalTo(false))
+            .body(secondAttachment + ".blobId", equalTo(attachment2.getAttachmentId().getId()))
+            .body(secondAttachment + ".type", equalTo("application/octet-stream; charset=UTF-8"))
+            .body(secondAttachment + ".size", equalTo((int) attachment2.getSize()))
+            .body(secondAttachment + ".cid", equalTo("123456789"))
+            .body(secondAttachment + ".isInline", equalTo(true));
+    }
+
+    private void uploadAttachment(Attachment attachment) throws IOException {
+        with()
+            .header("Authorization", accessToken.serialize())
+            .contentType(attachment.getType())
+            .content(attachment.getStream())
+        .post("/upload");
+    }
+
+    @Test
+    public void attachmentsShouldBeRetrievedWhenChainingSetMessagesAndGetMessages() throws Exception {
+        jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, username, "sent");
+
+        Attachment attachment = Attachment.builder()
+                .bytes("attachment".getBytes(Charsets.UTF_8))
+                .type("application/octet-stream")
+                .build();
+        uploadAttachment(attachment);
+
+        String messageCreationId = "creationId";
+        String fromAddress = username;
+        String outboxId = getOutboxId(accessToken);
+        String requestBody = "[" +
+                "  [" +
+                "    \"setMessages\","+
+                "    {" +
+                "      \"create\": { \"" + messageCreationId  + "\" : {" +
+                "        \"from\": { \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}," +
+                "        \"to\": [{ \"name\": \"Me\", \"email\": \"" + fromAddress + "\"}]," +
+                "        \"subject\": \"Message with an attachment\"," +
+                "        \"textBody\": \"Test body\"," +
+                "        \"mailboxIds\": [\"" + outboxId + "\"], " +
+                "        \"attachments\": [" +
+                "               {\"blobId\" : \"" + attachment.getAttachmentId().getId() + "\", " +
+                "               \"type\" : \"" + attachment.getType() + "\", " +
+                "               \"size\" : " + attachment.getSize() + ", " +
+                "               \"cid\" : \"123456789\", " +
+                "               \"isInline\" : true }" +
+                "           ]" +
+                "      }}" +
+                "    }," +
+                "    \"#0\"" +
+                "  ]" +
+                "]";
+
+        given()
+            .header("Authorization", accessToken.serialize())
+            .body(requestBody)
+        .when()
+            .post("/jmap");
+
+        calmlyAwait.atMost(30, TimeUnit.SECONDS).until( () -> isAnyMessageFoundInInbox(accessToken));
+
+        String firstMessage = ARGUMENTS + ".list[0]";
+        String firstAttachment = firstMessage + ".attachments[0]";
+        String presumedMessageId = "username@domain.tld|INBOX|1";
+        given()
+            .header("Authorization", accessToken.serialize())
+            .body("[[\"getMessages\", {\"ids\": [\"" + presumedMessageId + "\"]}, \"#0\"]]")
+        .when()
+            .post("/jmap")
+        .then()
+            .statusCode(200)
+            .body(NAME, equalTo("messages"))
+            .body(ARGUMENTS + ".list", hasSize(1))
+            .body(firstMessage + ".attachments", hasSize(1))
+            .body(firstAttachment + ".blobId", equalTo(attachment.getAttachmentId().getId()))
+            .body(firstAttachment + ".type", equalTo("application/octet-stream"))
+            .body(firstAttachment + ".size", equalTo((int) attachment.getSize()))
+            .body(firstAttachment + ".cid", equalTo("123456789"))
+            .body(firstAttachment + ".isInline", equalTo(true));
+    }
+
+    private boolean isAnyMessageFoundInInbox(AccessToken recipientToken) {
+        try {
+            String inboxId = getMailboxId(accessToken, Role.INBOX);
+            with()
+                    .header("Authorization", recipientToken.serialize())
+                    .body("[[\"getMessageList\", {\"filter\":{\"inMailboxes\":[\"" + inboxId + "\"]}}, \"#0\"]]")
+            .when()
+                    .post("/jmap")
+            .then()
+                    .statusCode(200)
+                    .body(NAME, equalTo("messageList"))
+                    .body(ARGUMENTS + ".messageIds", hasSize(1));
+            return true;
+            
+        } catch (AssertionError e) {
+            return false;
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/james-project/blob/662fa4a9/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/AttachmentsNotFoundException.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/AttachmentsNotFoundException.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/AttachmentsNotFoundException.java
new file mode 100644
index 0000000..1c06cb4
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/AttachmentsNotFoundException.java
@@ -0,0 +1,40 @@
+/****************************************************************
+ * 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.exceptions;
+
+import java.util.List;
+
+import org.apache.james.jmap.model.BlobId;
+
+import com.google.common.collect.ImmutableList;
+
+public class AttachmentsNotFoundException extends Exception {
+    
+    private List<BlobId> attachmentIds;
+
+
+    public AttachmentsNotFoundException(List<BlobId> attachmentIds) {
+        this.attachmentIds = ImmutableList.copyOf(attachmentIds);
+    }
+    
+    public List<BlobId> getAttachmentIds() {
+        return attachmentIds;
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/662fa4a9/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java
index 25ba906..2eb52e7 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java
@@ -27,9 +27,11 @@ import java.util.TimeZone;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
+import org.apache.commons.io.IOUtils;
 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.mailbox.store.mail.model.MessageAttachment;
 import org.apache.james.mime4j.Charsets;
 import org.apache.james.mime4j.codec.DecodeMonitor;
 import org.apache.james.mime4j.dom.FieldParser;
@@ -37,9 +39,13 @@ import org.apache.james.mime4j.dom.Message;
 import org.apache.james.mime4j.dom.Multipart;
 import org.apache.james.mime4j.dom.TextBody;
 import org.apache.james.mime4j.dom.address.Mailbox;
+import org.apache.james.mime4j.dom.field.ContentDispositionField;
+import org.apache.james.mime4j.dom.field.ContentTypeField;
 import org.apache.james.mime4j.dom.field.UnstructuredField;
+import org.apache.james.mime4j.field.Fields;
 import org.apache.james.mime4j.field.UnstructuredFieldImpl;
 import org.apache.james.mime4j.message.BasicBodyFactory;
+import org.apache.james.mime4j.message.BodyPart;
 import org.apache.james.mime4j.message.BodyPartBuilder;
 import org.apache.james.mime4j.message.DefaultMessageWriter;
 import org.apache.james.mime4j.message.MessageBuilder;
@@ -51,16 +57,23 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Splitter;
 import com.google.common.base.Throwables;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
 import com.google.common.net.MediaType;
 
 public class MIMEMessageConverter {
+
     private static final Logger LOGGER = LoggerFactory.getLogger(MIMEMessageConverter.class);
 
     private static final String PLAIN_TEXT_MEDIA_TYPE = MediaType.PLAIN_TEXT_UTF_8.withoutParameters().toString();
     private static final String HTML_MEDIA_TYPE = MediaType.HTML_UTF_8.withoutParameters().toString();
     private static final NameValuePair UTF_8_CHARSET = new NameValuePair("charset", Charsets.UTF_8.name());
     private static final String MIXED_SUB_TYPE = "mixed";
+    private static final String FIELD_PARAMETERS_SEPARATOR = ";";
 
     private final BasicBodyFactory bodyFactory;
 
@@ -68,34 +81,34 @@ public class MIMEMessageConverter {
         this.bodyFactory = new BasicBodyFactory();
     }
 
-    public byte[] convert(ValueWithId.CreationMessageEntry creationMessageEntry) {
+    public byte[] convert(ValueWithId.CreationMessageEntry creationMessageEntry, ImmutableList<MessageAttachment> messageAttachments) {
 
         ByteArrayOutputStream buffer = new ByteArrayOutputStream();
         DefaultMessageWriter writer = new DefaultMessageWriter();
         try {
-            writer.writeMessage(convertToMime(creationMessageEntry), buffer);
+            writer.writeMessage(convertToMime(creationMessageEntry, messageAttachments), buffer);
         } catch (IOException e) {
             throw Throwables.propagate(e);
         }
         return buffer.toByteArray();
     }
 
-    @VisibleForTesting Message convertToMime(ValueWithId.CreationMessageEntry creationMessageEntry) {
+    @VisibleForTesting Message convertToMime(ValueWithId.CreationMessageEntry creationMessageEntry, ImmutableList<MessageAttachment> messageAttachments) {
         if (creationMessageEntry == null || creationMessageEntry.getValue() == null) {
             throw new IllegalArgumentException("creationMessageEntry is either null or has null message");
         }
 
         MessageBuilder messageBuilder = MessageBuilder.create();
-        if (mixedTextAndHtml(creationMessageEntry.getValue())) {
-            messageBuilder.setBody(createMultipartBody(creationMessageEntry.getValue()));
+        if (isMultipart(creationMessageEntry.getValue(), messageAttachments)) {
+            messageBuilder.setBody(createMultipartBody(creationMessageEntry.getValue(), messageAttachments));
         } else {
             messageBuilder.setBody(createTextBody(creationMessageEntry.getValue()));
         }
-        buildMimeHeaders(messageBuilder, creationMessageEntry.getCreationId(), creationMessageEntry.getValue());
+        buildMimeHeaders(messageBuilder, creationMessageEntry.getCreationId(), creationMessageEntry.getValue(), messageAttachments);
         return messageBuilder.build();
     }
 
-    private void buildMimeHeaders(MessageBuilder messageBuilder, CreationMessageId creationId, CreationMessage newMessage) {
+    private void buildMimeHeaders(MessageBuilder messageBuilder, CreationMessageId creationId, CreationMessage newMessage, ImmutableList<MessageAttachment> messageAttachments) {
         Optional<Mailbox> fromAddress = newMessage.getFrom().filter(DraftEmailer::hasValidEmail).map(this::convertEmailToMimeHeader);
         fromAddress.ifPresent(messageBuilder::setFrom);
         fromAddress.ifPresent(messageBuilder::setSender);
@@ -121,7 +134,7 @@ public class MIMEMessageConverter {
         // note that date conversion probably lose milliseconds!
         messageBuilder.setDate(Date.from(newMessage.getDate().toInstant()), TimeZone.getTimeZone(newMessage.getDate().getZone()));
         newMessage.getInReplyToMessageId().ifPresent(addInReplyToHeader(messageBuilder::addField));
-        if (!mixedTextAndHtml(newMessage)) {
+        if (!isMultipart(newMessage, messageAttachments)) {
             newMessage.getHtmlBody().ifPresent(x -> messageBuilder.setContentType(HTML_MEDIA_TYPE, UTF_8_CHARSET));
         }
     }
@@ -134,8 +147,9 @@ public class MIMEMessageConverter {
         };
     }
 
-    private boolean mixedTextAndHtml(CreationMessage newMessage) {
-        return newMessage.getTextBody().isPresent() && newMessage.getHtmlBody().isPresent();
+    private boolean isMultipart(CreationMessage newMessage, ImmutableList<MessageAttachment> messageAttachments) {
+        return (newMessage.getTextBody().isPresent() && newMessage.getHtmlBody().isPresent())
+                || !messageAttachments.isEmpty();
     }
 
     private TextBody createTextBody(CreationMessage newMessage) {
@@ -145,26 +159,93 @@ public class MIMEMessageConverter {
         return bodyFactory.textBody(body, Charsets.UTF_8);
     }
 
-    private Multipart createMultipartBody(CreationMessage newMessage) {
+    private Multipart createMultipartBody(CreationMessage newMessage, ImmutableList<MessageAttachment> messageAttachments) {
         try {
-            return MultipartBuilder.create(MIXED_SUB_TYPE)
-                    .addBodyPart(BodyPartBuilder.create()
-                            .use(bodyFactory)
-                            .setBody(newMessage.getTextBody().get(), Charsets.UTF_8)
-                            .setContentType(PLAIN_TEXT_MEDIA_TYPE, UTF_8_CHARSET)
-                            .build())
-                    .addBodyPart(BodyPartBuilder.create()
-                            .use(bodyFactory)
-                            .setBody(newMessage.getHtmlBody().get(), Charsets.UTF_8)
-                            .setContentType(HTML_MEDIA_TYPE, UTF_8_CHARSET)
-                            .build())
-                    .build();
+            MultipartBuilder builder = MultipartBuilder.create(MIXED_SUB_TYPE);
+            addText(builder, newMessage.getTextBody());
+            addHtml(builder, newMessage.getHtmlBody());
+
+            Consumer<MessageAttachment> addAttachment = addAttachment(builder);
+            messageAttachments.stream()
+                .forEach(addAttachment);
+
+            return builder.build();
         } catch (IOException e) {
             LOGGER.error("Error while creating textBody \n"+ newMessage.getTextBody().get() +"\n or htmlBody \n" + newMessage.getHtmlBody().get(), e);
             throw Throwables.propagate(e);
         }
     }
 
+    private void addText(MultipartBuilder builder, Optional<String> textBody) throws IOException {
+        if (textBody.isPresent()) {
+            builder.addBodyPart(BodyPartBuilder.create()
+                .use(bodyFactory)
+                .setBody(textBody.get(), Charsets.UTF_8)
+                .setContentType(PLAIN_TEXT_MEDIA_TYPE, UTF_8_CHARSET)
+                .build());
+        }
+    }
+
+    private void addHtml(MultipartBuilder builder, Optional<String> htmlBody) throws IOException {
+        if (htmlBody.isPresent()) {
+            builder.addBodyPart(BodyPartBuilder.create()
+                .use(bodyFactory)
+                .setBody(htmlBody.get(), Charsets.UTF_8)
+                .setContentType(HTML_MEDIA_TYPE, UTF_8_CHARSET)
+                .build());
+        }
+    }
+
+    private Consumer<MessageAttachment> addAttachment(MultipartBuilder builder) {
+        return att -> { 
+            try {
+                builder.addBodyPart(attachmentBodyPart(att));
+            } catch (IOException e) {
+                LOGGER.error("Error while creating attachment", e);
+                throw Throwables.propagate(e);
+            }
+        };
+    }
+
+    private BodyPart attachmentBodyPart(MessageAttachment att) throws IOException {
+        BodyPartBuilder builder = BodyPartBuilder.create()
+            .use(bodyFactory)
+            .setBody(IOUtils.toString(att.getAttachment().getStream()), Charsets.UTF_8)
+            .setField(contentTypeField(att))
+            .setField(contentDispositionField(att.isInline()));
+        contentId(builder, att);
+        return builder.build();
+    }
+
+    private void contentId(BodyPartBuilder builder, MessageAttachment att) {
+        if (att.getCid().isPresent()) {
+            builder.setField(new RawField("Content-ID", att.getCid().get()));
+        }
+    }
+
+    private ContentTypeField contentTypeField(MessageAttachment att) {
+        Builder<String, String> parameters = ImmutableMap.<String, String> builder();
+        if (att.getName().isPresent()) {
+            parameters.put("name", att.getName().get());
+        }
+        String type = att.getAttachment().getType();
+        if (type.contains(FIELD_PARAMETERS_SEPARATOR)) {
+            return Fields.contentType(contentTypeWithoutParameters(type), parameters.build());
+        }
+        return Fields.contentType(type, parameters.build());
+    }
+
+    private String contentTypeWithoutParameters(String type) {
+        return FluentIterable.from(Splitter.on(FIELD_PARAMETERS_SEPARATOR).split(type)).get(0);
+    }
+
+    private ContentDispositionField contentDispositionField(boolean isInline) {
+        if (isInline) {
+            return Fields.contentDisposition("inline");
+        }
+        return Fields.contentDisposition("attachment");
+    }
+
     private Mailbox convertEmailToMimeHeader(DraftEmailer address) {
         if (!address.hasValidEmail()) {
             throw new IllegalArgumentException("address");

http://git-wip-us.apache.org/repos/asf/james-project/blob/662fa4a9/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 3da428b..9d6210e 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
@@ -26,6 +26,7 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import javax.inject.Inject;
 import javax.mail.Flags;
@@ -33,8 +34,11 @@ import javax.mail.MessagingException;
 import javax.mail.internet.SharedInputStream;
 import javax.mail.util.SharedByteArrayInputStream;
 
+import org.apache.james.jmap.exceptions.AttachmentsNotFoundException;
 import org.apache.james.jmap.methods.ValueWithId.CreationMessageEntry;
 import org.apache.james.jmap.methods.ValueWithId.MessageWithId;
+import org.apache.james.jmap.model.Attachment;
+import org.apache.james.jmap.model.BlobId;
 import org.apache.james.jmap.model.CreationMessage;
 import org.apache.james.jmap.model.CreationMessageId;
 import org.apache.james.jmap.model.Message;
@@ -43,6 +47,7 @@ import org.apache.james.jmap.model.MessageId;
 import org.apache.james.jmap.model.MessageProperties;
 import org.apache.james.jmap.model.MessageProperties.MessageProperty;
 import org.apache.james.jmap.model.SetError;
+import org.apache.james.jmap.model.SetMessagesError;
 import org.apache.james.jmap.model.SetMessagesRequest;
 import org.apache.james.jmap.model.SetMessagesResponse;
 import org.apache.james.jmap.model.SetMessagesResponse.Builder;
@@ -52,16 +57,22 @@ import org.apache.james.jmap.send.MailMetadata;
 import org.apache.james.jmap.send.MailSpool;
 import org.apache.james.jmap.utils.SystemMailboxesProvider;
 import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.exception.AttachmentNotFoundException;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.exception.MailboxNotFoundException;
 import org.apache.james.mailbox.model.MailboxPath;
 import org.apache.james.mailbox.store.MailboxSessionMapperFactory;
+import org.apache.james.mailbox.store.mail.AttachmentMapper;
+import org.apache.james.mailbox.store.mail.AttachmentMapperFactory;
 import org.apache.james.mailbox.store.mail.MessageMapper;
+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.MailboxId;
 import org.apache.james.mailbox.store.mail.model.MailboxMessage;
+import org.apache.james.mailbox.store.mail.model.MessageAttachment;
 import org.apache.james.mailbox.store.mail.model.impl.PropertyBuilder;
 import org.apache.james.mailbox.store.mail.model.impl.SimpleMailboxMessage;
+import org.apache.james.util.streams.ImmutableCollectors;
 import org.apache.mailet.Mail;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -80,6 +91,7 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor {
     private final MailFactory mailFactory;
     private final MessageFactory messageFactory;
     private final SystemMailboxesProvider systemMailboxesProvider;
+    private AttachmentMapperFactory attachmentMapperFactory;
 
     
     @VisibleForTesting @Inject
@@ -88,13 +100,15 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor {
                                  MailSpool mailSpool,
                                  MailFactory mailFactory,
                                  MessageFactory messageFactory,
-                                 SystemMailboxesProvider systemMailboxesProvider) {
+                                 SystemMailboxesProvider systemMailboxesProvider,
+                                 AttachmentMapperFactory attachmentMapperFactory) {
         this.mailboxSessionMapperFactory = mailboxSessionMapperFactory;
         this.mimeMessageConverter = mimeMessageConverter;
         this.mailSpool = mailSpool;
         this.mailFactory = mailFactory;
         this.messageFactory = messageFactory;
         this.systemMailboxesProvider = systemMailboxesProvider;
+        this.attachmentMapperFactory = attachmentMapperFactory;
     }
 
     @Override
@@ -109,7 +123,7 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor {
     private void handleCreate(CreationMessageEntry create, Builder responseBuilder, MailboxSession mailboxSession) {
         try {
             validateImplementedFeature(create, mailboxSession);
-            validateArguments(create);
+            validateArguments(create, mailboxSession);
             validateRights(create, mailboxSession);
             MessageWithId created = handleOutboxMessages(create, mailboxSession);
             responseBuilder.created(created.getCreationId(), created.getValue());
@@ -123,6 +137,15 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor {
                                 Joiner.on(", ").join(e.getAllowedFroms()))
                         .build());
 
+        } catch (AttachmentsNotFoundException e) {
+            responseBuilder.notCreated(create.getCreationId(), 
+                    SetMessagesError.builder()
+                        .type("invalidProperties")
+                        .properties(MessageProperty.mailboxIds)
+                        .attachmentsNotFound(e.getAttachmentIds())
+                        .description("Attachment not found")
+                        .build());
+            
         } catch (MailboxNotImplementedException e) {
             responseBuilder.notCreated(create.getCreationId(), 
                     SetError.builder()
@@ -161,13 +184,41 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor {
         }
     }
     
-    private void validateArguments(CreationMessageEntry entry) throws MailboxInvalidMessageCreationException {
+    private void validateArguments(CreationMessageEntry entry, MailboxSession session) throws MailboxInvalidMessageCreationException, AttachmentsNotFoundException, MailboxException {
         CreationMessage message = entry.getValue();
         if (!message.isValid()) {
             throw new MailboxInvalidMessageCreationException();
         }
+        assertAttachmentsExist(entry, session);
     }
     
+    @VisibleForTesting void assertAttachmentsExist(CreationMessageEntry entry, MailboxSession session) throws AttachmentsNotFoundException, MailboxException {
+        List<Attachment> attachments = entry.getValue().getAttachments();
+        if (!attachments.isEmpty()) {
+            AttachmentMapper attachmentMapper = attachmentMapperFactory.getAttachmentMapper(session);
+            List<BlobId> notFounds = listAttachmentsNotFound(attachments, attachmentMapper);
+            if (!notFounds.isEmpty()) {
+                throw new AttachmentsNotFoundException(notFounds);
+            }
+        }
+    }
+
+    private List<BlobId> listAttachmentsNotFound(List<Attachment> attachments, AttachmentMapper attachmentMapper) {
+        return attachments.stream()
+            .flatMap(attachment -> {
+                try {
+                    attachmentMapper.getAttachment(getAttachmentId(attachment));
+                    return Stream.of();
+                } catch (AttachmentNotFoundException e) {
+                    return Stream.of(attachment.getBlobId());
+                }
+            }).collect(ImmutableCollectors.toImmutableList());
+    }
+
+    private AttachmentId getAttachmentId(Attachment attachment) {
+        return AttachmentId.from(attachment.getBlobId().getRawValue());
+    }
+
     private void validateRights(CreationMessageEntry entry, MailboxSession session) throws MailboxSendingNotAllowedException {
         List<String> allowedSenders = ImmutableList.of(session.getUser().getUserName());
         if (!isAllowedFromAddress(entry.getValue(), allowedSenders)) {
@@ -200,9 +251,9 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor {
         
         CreationMessageId creationId = createdEntry.getCreationId();
         MessageMapper messageMapper = mailboxSessionMapperFactory.createMessageMapper(session);
-        MailboxMessage newMailboxMessage = buildMailboxMessage(createdEntry, outbox);
+        MailboxMessage newMailboxMessage = buildMailboxMessage(session, createdEntry, outbox);
         messageMapper.add(outbox, newMailboxMessage);
-        Message jmapMessage = messageFactory.fromMailboxMessage(newMailboxMessage, ImmutableList.of(), buildMessageIdFromUid);
+        Message jmapMessage = messageFactory.fromMailboxMessage(newMailboxMessage, newMailboxMessage.getAttachments(), buildMessageIdFromUid);
         sendMessage(newMailboxMessage, jmapMessage, session);
         return new MessageWithId(creationId, jmapMessage);
     }
@@ -248,8 +299,9 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor {
         return new MessageId(session.getUser(), outboxPath, uid);
     }
 
-    private MailboxMessage buildMailboxMessage(MessageWithId.CreationMessageEntry createdEntry, Mailbox outbox) {
-        byte[] messageContent = mimeMessageConverter.convert(createdEntry);
+    private MailboxMessage buildMailboxMessage(MailboxSession session, MessageWithId.CreationMessageEntry createdEntry, Mailbox outbox) throws MailboxException {
+        ImmutableList<MessageAttachment> messageAttachments = getMessageAttachments(session, createdEntry.getValue().getAttachments());
+        byte[] messageContent = mimeMessageConverter.convert(createdEntry, messageAttachments);
         SharedInputStream content = new SharedByteArrayInputStream(messageContent);
         long size = messageContent.length;
         int bodyStartOctet = 0;
@@ -260,7 +312,29 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor {
         Date internalDate = Date.from(createdEntry.getValue().getDate().toInstant());
 
         return new SimpleMailboxMessage(internalDate, size,
-                bodyStartOctet, content, flags, propertyBuilder, mailboxId);
+                bodyStartOctet, content, flags, propertyBuilder, mailboxId, messageAttachments);
+    }
+
+    private ImmutableList<MessageAttachment> getMessageAttachments(MailboxSession session, ImmutableList<Attachment> attachments) throws MailboxException {
+        AttachmentMapper attachmentMapper = attachmentMapperFactory.getAttachmentMapper(session);
+        return attachments.stream()
+            .map(att -> messageAttachment(attachmentMapper, att))
+            .collect(ImmutableCollectors.toImmutableList());
+    }
+
+    private MessageAttachment messageAttachment(AttachmentMapper attachmentMapper, Attachment attachment) {
+        try {
+            return MessageAttachment.builder()
+                    .attachment(attachmentMapper.getAttachment(AttachmentId.from(attachment.getBlobId().getRawValue())))
+                    .name(attachment.getName().orElse(null))
+                    .cid(attachment.getCid().orElse(null))
+                    .isInline(attachment.isIsInline())
+                    .build();
+        } catch (AttachmentNotFoundException e) {
+            // should not happen (checked before)
+            LOG.error(String.format("Attachment %s not found", attachment.getBlobId()), e);
+            return null;
+        }
     }
 
     private PropertyBuilder buildPropertyBuilder() {

http://git-wip-us.apache.org/repos/asf/james-project/blob/662fa4a9/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java
index 60f7b91..5202a15 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java
@@ -25,6 +25,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 import javax.mail.internet.AddressException;
@@ -73,7 +74,7 @@ public class CreationMessage {
         private String textBody;
         private String htmlBody;
         private final ImmutableList.Builder<Attachment> attachments;
-        private final ImmutableMap.Builder<String, SubMessage> attachedMessages;
+        private final ImmutableMap.Builder<BlobId, SubMessage> attachedMessages;
 
         private Builder() {
             to = ImmutableList.builder();
@@ -89,6 +90,7 @@ public class CreationMessage {
             return mailboxIds(Arrays.asList(mailboxIds));
         }
 
+        @JsonDeserialize
         public Builder mailboxIds(List<String> mailboxIds) {
             this.mailboxIds = ImmutableList.copyOf(mailboxIds);
             return this;
@@ -169,27 +171,39 @@ public class CreationMessage {
             return this;
         }
 
+        public Builder attachments(Attachment... attachments) {
+            return attachments(Arrays.asList(attachments));
+        }
+        
+        @JsonDeserialize
         public Builder attachments(List<Attachment> attachments) {
             this.attachments.addAll(attachments);
             return this;
         }
 
-        public Builder attachedMessages(Map<String, SubMessage> attachedMessages) {
+        public Builder attachedMessages(Map<BlobId, SubMessage> attachedMessages) {
             this.attachedMessages.putAll(attachedMessages);
             return this;
         }
 
-        private static boolean areAttachedMessagesKeysInAttachments(ImmutableList<Attachment> attachments, ImmutableMap<String, SubMessage> attachedMessages) {
-            return attachments.stream()
+        private static boolean areAttachedMessagesKeysInAttachments(ImmutableList<Attachment> attachments, ImmutableMap<BlobId, SubMessage> attachedMessages) {
+            return attachedMessages.isEmpty() || attachedMessages.keySet().stream()
+                    .anyMatch(inAttachments(attachments));
+        }
+
+        private static Predicate<BlobId> inAttachments(ImmutableList<Attachment> attachments) {
+            return (key) -> {
+                return attachments.stream()
                     .map(Attachment::getBlobId)
-                    .allMatch(attachedMessages::containsKey);
+                    .anyMatch(blobId -> blobId.equals(key));
+            };
         }
 
         public CreationMessage build() {
             Preconditions.checkState(mailboxIds != null, "'mailboxIds' is mandatory");
             Preconditions.checkState(headers != null, "'headers' is mandatory");
             ImmutableList<Attachment> attachments = this.attachments.build();
-            ImmutableMap<String, SubMessage> attachedMessages = this.attachedMessages.build();
+            ImmutableMap<BlobId, SubMessage> attachedMessages = this.attachedMessages.build();
             Preconditions.checkState(areAttachedMessagesKeysInAttachments(attachments, attachedMessages), "'attachedMessages' keys must be in 'attachments'");
 
             if (date == null) {
@@ -218,12 +232,12 @@ public class CreationMessage {
     private final Optional<String> textBody;
     private final Optional<String> htmlBody;
     private final ImmutableList<Attachment> attachments;
-    private final ImmutableMap<String, SubMessage> attachedMessages;
+    private final ImmutableMap<BlobId, SubMessage> attachedMessages;
 
     @VisibleForTesting
     CreationMessage(ImmutableList<String> mailboxIds, Optional<String> inReplyToMessageId, boolean isUnread, boolean isFlagged, boolean isAnswered, boolean isDraft, ImmutableMap<String, String> headers, Optional<DraftEmailer> from,
                     ImmutableList<DraftEmailer> to, ImmutableList<DraftEmailer> cc, ImmutableList<DraftEmailer> bcc, ImmutableList<DraftEmailer> replyTo, String subject, ZonedDateTime date, Optional<String> textBody, Optional<String> htmlBody, ImmutableList<Attachment> attachments,
-                    ImmutableMap<String, SubMessage> attachedMessages) {
+                    ImmutableMap<BlobId, SubMessage> attachedMessages) {
         this.mailboxIds = mailboxIds;
         this.inReplyToMessageId = inReplyToMessageId;
         this.isUnread = isUnread;
@@ -312,7 +326,7 @@ public class CreationMessage {
         return attachments;
     }
 
-    public ImmutableMap<String, SubMessage> getAttachedMessages() {
+    public ImmutableMap<BlobId, SubMessage> getAttachedMessages() {
         return attachedMessages;
     }
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/662fa4a9/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetError.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetError.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetError.java
index 658491c..cda1aef 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetError.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetError.java
@@ -49,7 +49,7 @@ public class SetError {
         private String description;
         private Optional<ImmutableSet<MessageProperty>> properties = Optional.empty();
 
-        private Builder() {
+        protected Builder() {
         }
 
         public Builder type(String type) {
@@ -91,6 +91,13 @@ public class SetError {
         this.properties = properties;
     }
 
+    protected SetError(SetError setError) {
+        this.type = setError.type;
+        this.description = setError.description;
+        this.properties = setError.properties;
+    }
+
+    
     @JsonSerialize
     public String getType() {
         return type;

http://git-wip-us.apache.org/repos/asf/james-project/blob/662fa4a9/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesError.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesError.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesError.java
new file mode 100644
index 0000000..2cf19e0
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesError.java
@@ -0,0 +1,92 @@
+/****************************************************************
+ * 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.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.james.jmap.model.MessageProperties.MessageProperty;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.collect.ImmutableList;
+
+public class SetMessagesError extends SetError {
+
+    public static SetMessagesError.Builder builder() {
+        return new Builder();
+    }
+    
+    public static class Builder extends SetError.Builder {
+        
+        private List<BlobId> attachmentsNotFound;
+
+        private Builder() {
+            super();
+            attachmentsNotFound = new ArrayList<>();
+        }
+
+        @Override
+        public Builder description(String description) {
+            return (Builder) super.description(description);
+        }
+        
+        @Override
+        public Builder properties(MessageProperty... properties) {
+            return (Builder) super.properties(properties);
+        }
+        
+        @Override
+        public Builder properties(Set<MessageProperty> properties) {
+            return (Builder) super.properties(properties);
+        }
+        
+        @Override
+        public Builder type(String type) {
+            return (Builder) super.type(type);
+        }
+        
+        public Builder attachmentsNotFound(BlobId... attachmentIds) {
+            return attachmentsNotFound(Arrays.asList(attachmentIds));
+        }
+        
+        public Builder attachmentsNotFound(List<BlobId> attachmentIds) {
+            this.attachmentsNotFound.addAll(attachmentIds);
+            return this;
+        }
+        
+        @Override
+        public SetError build() {
+            return new SetMessagesError(super.build(), ImmutableList.copyOf(attachmentsNotFound));
+        }
+    }
+
+    private ImmutableList<BlobId> attachmentsNotFound;
+    
+    public SetMessagesError(SetError setError, ImmutableList<BlobId> attachmentsNotFound) {
+        super(setError);
+        this.attachmentsNotFound = attachmentsNotFound;
+    }
+    
+    @JsonSerialize
+    public List<BlobId> getAttachmentsNotFound() {
+        return attachmentsNotFound;
+    }
+}


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


[3/5] james-project git commit: JAMES-1790 stop logging errors when stopping MailQueue

Posted by ad...@apache.org.
JAMES-1790 stop logging errors when stopping MailQueue


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

Branch: refs/heads/master
Commit: 2a7c50f5f40ed43ff0e6fcef5faa1fcf097e3ca6
Parents: 1c294dd
Author: Matthieu Baechler <ma...@linagora.com>
Authored: Mon Jul 4 11:34:23 2016 +0200
Committer: Antoine Duprat <ad...@linagora.com>
Committed: Fri Jul 8 14:24:55 2016 +0200

----------------------------------------------------------------------
 .../modules/server/MemoryMailQueueFactory.java  | 11 +++-------
 .../mailetcontainer/impl/JamesMailSpooler.java  | 21 ++++++++++----------
 .../org/apache/james/queue/api/MailQueue.java   |  4 +---
 3 files changed, 15 insertions(+), 21 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/2a7c50f5/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/server/MemoryMailQueueFactory.java
----------------------------------------------------------------------
diff --git a/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/server/MemoryMailQueueFactory.java b/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/server/MemoryMailQueueFactory.java
index 75466e4..c1f92b1 100644
--- a/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/server/MemoryMailQueueFactory.java
+++ b/server/container/guice/memory-guice/src/main/java/org/apache/james/modules/server/MemoryMailQueueFactory.java
@@ -33,7 +33,6 @@ import org.apache.james.queue.api.MailQueueItemDecoratorFactory;
 import org.apache.mailet.Mail;
 
 import com.google.common.base.Objects;
-import com.google.common.base.Throwables;
 import com.google.inject.Inject;
 
 public class MemoryMailQueueFactory implements MailQueueFactory {
@@ -86,14 +85,10 @@ public class MemoryMailQueueFactory implements MailQueueFactory {
         }
 
         @Override
-        public MailQueueItem deQueue() throws MailQueueException {
+        public MailQueueItem deQueue() throws MailQueueException, InterruptedException {
             while (true) {
-                try {
-                    MemoryMailQueueItem item = mailItems.take();
-                    return mailQueueItemDecoratorFactory.decorate(item);
-                } catch (InterruptedException e) {
-                    Throwables.propagate(e);
-                }
+                MemoryMailQueueItem item = mailItems.take();
+                return mailQueueItemDecoratorFactory.decorate(item);
             }
         }
 

http://git-wip-us.apache.org/repos/asf/james-project/blob/2a7c50f5/server/mailet/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/impl/JamesMailSpooler.java
----------------------------------------------------------------------
diff --git a/server/mailet/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/impl/JamesMailSpooler.java b/server/mailet/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/impl/JamesMailSpooler.java
index ccfed91..f2c3e47 100644
--- a/server/mailet/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/impl/JamesMailSpooler.java
+++ b/server/mailet/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/impl/JamesMailSpooler.java
@@ -19,6 +19,14 @@
 
 package org.apache.james.mailetcontainer.impl;
 
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.inject.Inject;
+
 import org.apache.commons.configuration.ConfigurationException;
 import org.apache.commons.configuration.HierarchicalConfiguration;
 import org.apache.james.lifecycle.api.Configurable;
@@ -35,13 +43,6 @@ import org.apache.james.util.concurrent.JMXEnabledThreadPoolExecutor;
 import org.apache.mailet.Mail;
 import org.slf4j.Logger;
 
-import javax.annotation.PostConstruct;
-import javax.annotation.PreDestroy;
-import javax.inject.Inject;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
 /**
  * Manages the mail spool. This class is responsible for retrieving messages
  * from the spool, directing messages to the appropriate processor, and removing
@@ -192,11 +193,11 @@ public class JamesMailSpooler implements Runnable, Disposable, Configurable, Log
                     logger.error("Exception dequeue mail", e1);
 
                 }
+            } catch (InterruptedException interrupted) {
+                //MailSpooler is stopping
             }
         }
-        if (logger.isInfoEnabled()) {
-            logger.info("Stop " + getClass().getName() + ": " + Thread.currentThread().getName());
-        }
+        logger.info("Stop {} : {}", getClass().getName(), Thread.currentThread().getName());
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/james-project/blob/2a7c50f5/server/queue/queue-api/src/main/java/org/apache/james/queue/api/MailQueue.java
----------------------------------------------------------------------
diff --git a/server/queue/queue-api/src/main/java/org/apache/james/queue/api/MailQueue.java b/server/queue/queue-api/src/main/java/org/apache/james/queue/api/MailQueue.java
index ca82525..67c9282 100644
--- a/server/queue/queue-api/src/main/java/org/apache/james/queue/api/MailQueue.java
+++ b/server/queue/queue-api/src/main/java/org/apache/james/queue/api/MailQueue.java
@@ -86,10 +86,8 @@ public interface MailQueue {
      * block until a Mail is ready and then process the operation.
      * Implementations should take care todo some kind of transactions to not
      * loose any mail on error
-     * 
-     * @throws MailQueueException
      */
-    MailQueueItem deQueue() throws MailQueueException;
+    MailQueueItem deQueue() throws MailQueueException, InterruptedException;
 
     /**
      * Exception which will get thrown if any problems occur while working the


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


[4/5] james-project git commit: JAMES-1790 check that blobids exist when attaching them

Posted by ad...@apache.org.
http://git-wip-us.apache.org/repos/asf/james-project/blob/662fa4a9/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java
index e64d6a9..6f9b3d3 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java
@@ -30,7 +30,10 @@ import org.apache.james.jmap.methods.ValueWithId.MessageWithId;
 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.mailbox.store.mail.model.AttachmentId;
+import org.apache.james.mailbox.store.mail.model.MessageAttachment;
 import org.apache.james.mime4j.Charsets;
+import org.apache.james.mime4j.dom.Entity;
 import org.apache.james.mime4j.dom.Message;
 import org.apache.james.mime4j.dom.Multipart;
 import org.apache.james.mime4j.dom.TextBody;
@@ -57,7 +60,7 @@ public class MIMEMessageConverterTest {
 
         // When
         Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
-                CreationMessageId.of("user|mailbox|1"), messageHavingInReplyTo));
+                CreationMessageId.of("user|mailbox|1"), messageHavingInReplyTo), ImmutableList.of());
 
         // Then
         assertThat(result.getHeader().getFields("In-Reply-To")).extracting(Field::getBody)
@@ -68,7 +71,7 @@ public class MIMEMessageConverterTest {
     public void convertToMimeShouldThrowWhenMessageIsNull() {
         MIMEMessageConverter sut = new MIMEMessageConverter();
 
-        sut.convertToMime(new ValueWithId.CreationMessageEntry(CreationMessageId.of("any"), null));
+        sut.convertToMime(new ValueWithId.CreationMessageEntry(CreationMessageId.of("any"), null), ImmutableList.of());
     }
 
     @Test
@@ -85,7 +88,7 @@ public class MIMEMessageConverterTest {
 
         // When
         Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
-                CreationMessageId.of("user|mailbox|1"), testMessage));
+                CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of());
 
         // Then
         assertThat(result.getFrom()).extracting(Mailbox::getAddress).allMatch(f -> f.equals(joesEmail));
@@ -109,7 +112,7 @@ public class MIMEMessageConverterTest {
 
         // When
         Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
-                CreationMessageId.of("user|mailbox|1"), testMessage));
+                CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of());
 
         // Then
         assertThat(result.getDate()).isEqualToIgnoringMillis(Date.from(now));
@@ -130,7 +133,7 @@ public class MIMEMessageConverterTest {
 
         // When
         Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
-                CreationMessageId.of("user|mailbox|1"), testMessage));
+                CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of());
 
         // Then
         assertThat(result.getBody()).isEqualToComparingOnlyGivenFields(expected, "content", "charset");
@@ -150,7 +153,7 @@ public class MIMEMessageConverterTest {
 
         // When
         Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
-                CreationMessageId.of("user|mailbox|1"), testMessage));
+                CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of());
 
         // Then
         assertThat(result.getBody()).isEqualToComparingOnlyGivenFields(expected, "content", "charset");
@@ -171,7 +174,7 @@ public class MIMEMessageConverterTest {
 
         // When
         Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
-                CreationMessageId.of("user|mailbox|1"), testMessage));
+                CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of());
 
         // Then
         assertThat(result.getBody()).isEqualToComparingOnlyGivenFields(expected, "content", "charset");
@@ -192,7 +195,7 @@ public class MIMEMessageConverterTest {
 
         // When
         Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
-                CreationMessageId.of("user|mailbox|1"), testMessage));
+                CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of());
 
         // Then
         assertThat(result.getBody()).isInstanceOf(Multipart.class);
@@ -216,7 +219,7 @@ public class MIMEMessageConverterTest {
 
         String expectedHeaders = "MIME-Version: 1.0\r\n" +
                 "Content-Type: multipart/mixed;\r\n" +
-                " boundary=\"-=Part.0.";
+                " boundary=\"-=Part.1.";
         String expectedPart1 = "Content-Type: text/plain; charset=UTF-8\r\n" +
                 "\r\n" +
                 "Hello all!\r\n";
@@ -226,7 +229,7 @@ public class MIMEMessageConverterTest {
 
         // When
         byte[] convert = sut.convert(new MessageWithId.CreationMessageEntry(
-                CreationMessageId.of("user|mailbox|1"), testMessage));
+                CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of());
 
         // Then
         String actual = new String(convert, Charsets.UTF_8);
@@ -249,7 +252,7 @@ public class MIMEMessageConverterTest {
 
         // When
         Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
-                CreationMessageId.of("user|mailbox|1"), testMessage));
+                CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of());
 
         // Then
         assertThat(result.getMimeType()).isEqualTo("text/plain");
@@ -269,7 +272,7 @@ public class MIMEMessageConverterTest {
 
         // When
         Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
-                CreationMessageId.of("user|mailbox|1"), testMessage));
+                CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of());
 
         // Then
         assertThat(result.getMimeType()).isEqualTo("text/html");
@@ -290,7 +293,7 @@ public class MIMEMessageConverterTest {
 
         // When
         Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
-                CreationMessageId.of("user|mailbox|1"), testMessage));
+                CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of());
 
         // Then
         assertThat(result.getBody()).isEqualToComparingOnlyGivenFields(expected, "content", "charset");
@@ -312,10 +315,52 @@ public class MIMEMessageConverterTest {
 
         // When
         Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
-                CreationMessageId.of("user|mailbox|1"), testMessage));
+                CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of());
 
         // Then
         assertThat(result.getBody()).isEqualToComparingOnlyGivenFields(expected, "content", "charset");
         assertThat(result.getMimeType()).isEqualTo("text/plain");
     }
+
+    @Test
+    public void convertToMimeShouldAddAttachmentWhenOne() {
+        // Given
+        MIMEMessageConverter sut = new MIMEMessageConverter();
+
+        CreationMessage testMessage = CreationMessage.builder()
+                .mailboxIds(ImmutableList.of("dead-bada55"))
+                .subject("subject")
+                .from(DraftEmailer.builder().name("sender").build())
+                .htmlBody("Hello <b>all<b>!")
+                .build();
+
+        String expectedCID = "<cid>";
+        String expectedMimeType = "image/png";
+        String text = "123456";
+        TextBody expectedBody = new BasicBodyFactory().textBody(text, Charsets.UTF_8);
+        MessageAttachment attachment = MessageAttachment.builder()
+                .attachment(org.apache.james.mailbox.store.mail.model.Attachment.builder()
+                    .attachmentId(AttachmentId.from("blodId"))
+                    .bytes(text.getBytes())
+                    .type(expectedMimeType)
+                    .build())
+                .cid(expectedCID)
+                .isInline(true)
+                .build();
+
+        // When
+        Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry(
+                CreationMessageId.of("user|mailbox|1"), testMessage), ImmutableList.of(attachment));
+
+        // Then
+        assertThat(result.getBody()).isInstanceOf(Multipart.class);
+        assertThat(result.isMultipart()).isTrue();
+        Multipart typedResult = (Multipart)result.getBody();
+        assertThat(typedResult.getBodyParts()).hasSize(2);
+        Entity attachmentPart = typedResult.getBodyParts().get(1);
+        assertThat(result.getBody()).isEqualToComparingOnlyGivenFields(expectedBody);
+        assertThat(attachmentPart.getDispositionType()).isEqualTo("inline");
+        assertThat(attachmentPart.getMimeType()).isEqualTo(expectedMimeType);
+        assertThat(attachmentPart.getHeader().getField("Content-ID").getBody()).isEqualTo(expectedCID);
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/662fa4a9/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 12eba05..178c554 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
@@ -20,6 +20,7 @@
 package org.apache.james.jmap.methods;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
@@ -33,7 +34,10 @@ import java.util.function.Function;
 import java.util.function.Supplier;
 import java.util.stream.Stream;
 
+import org.apache.james.jmap.exceptions.AttachmentsNotFoundException;
+import org.apache.james.jmap.methods.ValueWithId.CreationMessageEntry;
 import org.apache.james.jmap.methods.ValueWithId.MessageWithId;
+import org.apache.james.jmap.model.Attachment;
 import org.apache.james.jmap.model.BlobId;
 import org.apache.james.jmap.model.CreationMessage;
 import org.apache.james.jmap.model.CreationMessage.DraftEmailer;
@@ -54,13 +58,17 @@ import org.apache.james.jmap.utils.HtmlTextExtractor;
 import org.apache.james.jmap.utils.MailboxBasedHtmlTextExtractor;
 import org.apache.james.jmap.utils.SystemMailboxesProvider;
 import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.exception.AttachmentNotFoundException;
 import org.apache.james.mailbox.exception.MailboxException;
 import org.apache.james.mailbox.mock.MockMailboxSession;
 import org.apache.james.mailbox.model.MailboxPath;
 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.AttachmentMapper;
+import org.apache.james.mailbox.store.mail.AttachmentMapperFactory;
 import org.apache.james.mailbox.store.mail.MessageMapper;
+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.mailbox.store.mail.model.impl.SimpleMailbox;
@@ -72,6 +80,7 @@ import org.junit.Test;
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 
 public class SetMessagesCreationProcessorTest {
     
@@ -133,6 +142,9 @@ public class SetMessagesCreationProcessorTest {
     private SystemMailboxesProvider fakeSystemMailboxesProvider;
     private MockMailboxSession session;
     private MIMEMessageConverter mimeMessageConverter;
+    private AttachmentMapper mockedAttachmentMapper;
+    private AttachmentMapperFactory mockedAttachmentMapperFactory;
+    private SetMessagesCreationProcessor sut;
 
     @Before
     public void setUp() throws MailboxException {
@@ -142,17 +154,19 @@ public class SetMessagesCreationProcessorTest {
                 .thenReturn(mockMapper);
         mockedMailSpool = mock(MailSpool.class);
         mockedMailFactory = mock(MailFactory.class);
-
+        mockedAttachmentMapperFactory = mock(AttachmentMapperFactory.class);
+        mockedAttachmentMapper = mock(AttachmentMapper.class);
+        when(mockedAttachmentMapperFactory.getAttachmentMapper(any(MailboxSession.class))).thenReturn(mockedAttachmentMapper);
+        
         fakeSystemMailboxesProvider = new TestSystemMailboxesProvider(() -> optionalOutbox, () -> optionalDrafts);
         session = new MockMailboxSession(USER);
         mimeMessageConverter = new MIMEMessageConverter();
+        sut = new SetMessagesCreationProcessor(
+                stubSessionMapperFactory, mimeMessageConverter, mockedMailSpool, mockedMailFactory, messageFactory, fakeSystemMailboxesProvider, mockedAttachmentMapperFactory);
     }
 
     @Test
     public void processShouldReturnEmptyCreatedWhenRequestHasEmptyCreate() {
-        SetMessagesCreationProcessor sut = new SetMessagesCreationProcessor(
-                stubSessionMapperFactory, mimeMessageConverter, mockedMailSpool, mockedMailFactory, messageFactory, fakeSystemMailboxesProvider);
-
         SetMessagesRequest requestWithEmptyCreate = SetMessagesRequest.builder().build();
 
         SetMessagesResponse result = sut.process(requestWithEmptyCreate, session);
@@ -169,8 +183,8 @@ public class SetMessagesCreationProcessorTest {
         when(mockSessionMapperFactory.createMessageMapper(any(MailboxSession.class)))
                 .thenReturn(stubMapper);
 
-        SetMessagesCreationProcessor sut = new SetMessagesCreationProcessor(
-                mockSessionMapperFactory, mimeMessageConverter, mockedMailSpool, mockedMailFactory, messageFactory, fakeSystemMailboxesProvider) {
+        sut = new SetMessagesCreationProcessor(
+                mockSessionMapperFactory, mimeMessageConverter, mockedMailSpool, mockedMailFactory, messageFactory, fakeSystemMailboxesProvider, mockedAttachmentMapperFactory) {
             @Override
             protected MessageWithId createMessageInOutboxAndSend(ValueWithId.CreationMessageEntry createdEntry, MailboxSession session, Mailbox outbox, Function<Long, MessageId> buildMessageIdFromUid) {
                 return new MessageWithId(createdEntry.getCreationId(), FAKE_OUTBOX_MESSAGE);
@@ -190,7 +204,7 @@ public class SetMessagesCreationProcessorTest {
         // Given
         TestSystemMailboxesProvider doNotProvideOutbox = new TestSystemMailboxesProvider(Optional::empty, () -> optionalDrafts);
         SetMessagesCreationProcessor sut = new SetMessagesCreationProcessor(
-                stubSessionMapperFactory, mimeMessageConverter, mockedMailSpool, mockedMailFactory, messageFactory, doNotProvideOutbox);
+                stubSessionMapperFactory, mimeMessageConverter, mockedMailSpool, mockedMailFactory, messageFactory, doNotProvideOutbox, mockedAttachmentMapperFactory);
         // When
         SetMessagesResponse actual = sut.process(createMessageInOutbox, session);
         
@@ -201,9 +215,6 @@ public class SetMessagesCreationProcessorTest {
 
     @Test
     public void processShouldCallMessageMapperWhenRequestHasNonEmptyCreate() throws MailboxException {
-        // Given
-        SetMessagesCreationProcessor sut = new SetMessagesCreationProcessor(
-                stubSessionMapperFactory, mimeMessageConverter, mockedMailSpool, mockedMailFactory, messageFactory, fakeSystemMailboxesProvider);
         // When
         sut.process(createMessageInOutbox, session);
 
@@ -213,9 +224,6 @@ public class SetMessagesCreationProcessorTest {
 
     @Test
     public void processShouldSendMailWhenRequestHasNonEmptyCreate() throws Exception {
-        // Given
-        SetMessagesCreationProcessor sut = new SetMessagesCreationProcessor(
-                stubSessionMapperFactory, mimeMessageConverter, mockedMailSpool, mockedMailFactory, messageFactory, fakeSystemMailboxesProvider);
         // When
         sut.process(createMessageInOutbox, session);
 
@@ -225,9 +233,6 @@ public class SetMessagesCreationProcessorTest {
 
     @Test
     public void processShouldNotSpoolMailWhenNotSavingToOutbox() throws Exception {
-        // Given
-        SetMessagesCreationProcessor sut = new SetMessagesCreationProcessor(
-                stubSessionMapperFactory, mimeMessageConverter, mockedMailSpool, mockedMailFactory, messageFactory, fakeSystemMailboxesProvider);
         // When
         SetMessagesRequest notInOutboxCreationRequest =
                 SetMessagesRequest.builder()
@@ -244,10 +249,6 @@ public class SetMessagesCreationProcessorTest {
 
     @Test
     public void processShouldReturnNotImplementedErrorWhenSavingToDrafts() {
-        // Given
-        SetMessagesCreationProcessor sut = new SetMessagesCreationProcessor(
-                stubSessionMapperFactory, mimeMessageConverter, mockedMailSpool, mockedMailFactory, messageFactory, fakeSystemMailboxesProvider);
-
         CreationMessageId creationMessageId = CreationMessageId.of("anything-really");
         SetMessagesRequest createMessageInDrafts = SetMessagesRequest.builder()
                 .create(
@@ -267,9 +268,6 @@ public class SetMessagesCreationProcessorTest {
 
     @Test
     public void processShouldNotSendWhenSavingToDrafts() throws Exception {
-        // Given
-        SetMessagesCreationProcessor sut = new SetMessagesCreationProcessor(
-                stubSessionMapperFactory, mimeMessageConverter, mockedMailSpool, mockedMailFactory, messageFactory, fakeSystemMailboxesProvider);
         // When
         CreationMessageId creationMessageId = CreationMessageId.of("anything-really");
         SetMessagesRequest createMessageInDrafts = SetMessagesRequest.builder()
@@ -283,6 +281,46 @@ public class SetMessagesCreationProcessorTest {
     }
 
 
+    @Test
+    public void assertAttachmentsExistShouldThrowWhenUnknownBlobId() throws AttachmentNotFoundException {
+        BlobId unknownBlobId = BlobId.of("unknownBlobId");
+        AttachmentId unknownAttachmentId = AttachmentId.from(unknownBlobId.getRawValue());
+        when(mockedAttachmentMapper.getAttachment(unknownAttachmentId)).thenThrow(new AttachmentNotFoundException(unknownBlobId.getRawValue()));
+        
+        assertThatThrownBy(() -> sut.assertAttachmentsExist(
+                new CreationMessageEntry(
+                        creationMessageId, 
+                        creationMessageBuilder.attachments(
+                                Attachment.builder().size(12l).type("image/jpeg").blobId(unknownBlobId).build())
+                            .build()
+                        ),
+                session))
+            .isInstanceOf(AttachmentsNotFoundException.class);
+    }
+    
+    @Test
+    public void assertAttachmentsExistShouldThrowWhenUnknownBlobIds() throws AttachmentNotFoundException {
+        BlobId unknownBlobId1 = BlobId.of("unknownBlobId1");
+        BlobId unknownBlobId2 = BlobId.of("unknownBlobId2");
+        AttachmentId unknownAttachmentId1 = AttachmentId.from(unknownBlobId1.getRawValue());
+        AttachmentId unknownAttachmentId2 = AttachmentId.from(unknownBlobId2.getRawValue());
+
+        when(mockedAttachmentMapper.getAttachment(unknownAttachmentId1)).thenThrow(new AttachmentNotFoundException(unknownBlobId1.getRawValue()));
+        when(mockedAttachmentMapper.getAttachment(unknownAttachmentId2)).thenThrow(new AttachmentNotFoundException(unknownBlobId2.getRawValue()));
+        
+        assertThatThrownBy(() -> sut.assertAttachmentsExist(
+                new CreationMessageEntry(
+                        creationMessageId, 
+                        creationMessageBuilder.attachments(
+                                Attachment.builder().size(12l).type("image/jpeg").blobId(unknownBlobId1).build(),
+                                Attachment.builder().size(23l).type("image/git").blobId(unknownBlobId2).build())
+                            .build()
+                        ),
+                session))
+            .isInstanceOf(AttachmentsNotFoundException.class)
+            .matches(e -> ((AttachmentsNotFoundException)e).getAttachmentIds().containsAll(ImmutableSet.of(unknownBlobId1, unknownBlobId2)));
+    }
+    
     public static class TestSystemMailboxesProvider implements SystemMailboxesProvider {
 
         private final Supplier<Optional<Mailbox>> outboxSupplier;


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


[2/5] james-project git commit: JAMES-1790 don't detect mailing lists on sender when none

Posted by ad...@apache.org.
JAMES-1790 don't detect mailing lists on sender when none


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

Branch: refs/heads/master
Commit: a8d2de92a8a3e7a09ddd08b8dd10f418183729e5
Parents: 2a7c50f
Author: Matthieu Baechler <ma...@linagora.com>
Authored: Mon Jul 4 12:08:44 2016 +0200
Committer: Antoine Duprat <ad...@linagora.com>
Committed: Fri Jul 8 14:24:55 2016 +0200

----------------------------------------------------------------------
 .../base/AutomaticallySentMailDetectorImpl.java | 37 ++++++++++++++------
 1 file changed, 27 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/a8d2de92/mailet/base/src/main/java/org/apache/mailet/base/AutomaticallySentMailDetectorImpl.java
----------------------------------------------------------------------
diff --git a/mailet/base/src/main/java/org/apache/mailet/base/AutomaticallySentMailDetectorImpl.java b/mailet/base/src/main/java/org/apache/mailet/base/AutomaticallySentMailDetectorImpl.java
index 72fad65..b6360fb 100644
--- a/mailet/base/src/main/java/org/apache/mailet/base/AutomaticallySentMailDetectorImpl.java
+++ b/mailet/base/src/main/java/org/apache/mailet/base/AutomaticallySentMailDetectorImpl.java
@@ -32,9 +32,19 @@ import org.apache.james.mime4j.parser.MimeStreamParser;
 import org.apache.james.mime4j.stream.BodyDescriptor;
 import org.apache.james.mime4j.stream.MimeConfig;
 import org.apache.mailet.Mail;
+import org.apache.mailet.MailAddress;
 
 public class AutomaticallySentMailDetectorImpl implements AutomaticallySentMailDetector {
 
+    private static final String[] MAILING_LIST_HEADERS = new String[] {
+            "List-Help",
+            "List-Subscribe",
+            "List-Unsubscribe",
+            "List-Owner",
+            "List-Post",
+            "List-Id",
+            "List-Archive" };
+
     public boolean isAutomaticallySent(Mail mail) throws MessagingException {
         return isMailingList(mail) ||
             isAutoSubmitted(mail) ||
@@ -42,20 +52,27 @@ public class AutomaticallySentMailDetectorImpl implements AutomaticallySentMailD
     }
 
     public boolean isMailingList(Mail mail) throws MessagingException {
-        String localPart = mail.getSender().getLocalPart();
+        return senderIsMailingList(mail)
+            || headerIsMailingList(mail);
+    }
+
+    private boolean senderIsMailingList(Mail mail) {
+        MailAddress sender = mail.getSender();
+        if (sender == null) {
+            return false;
+        }
+
+        String localPart = sender.getLocalPart();
         return localPart.startsWith("owner-")
             || localPart.endsWith("-request")
             || localPart.equalsIgnoreCase("MAILER-DAEMON")
             || localPart.equalsIgnoreCase("LISTSERV")
-            || localPart.equalsIgnoreCase("majordomo")
-            || mail.getMessage()
-            .getMatchingHeaders(new String[]{"List-Help",
-                "List-Subscribe",
-                "List-Unsubscribe",
-                "List-Owner",
-                "List-Post",
-                "List-Id",
-                "List-Archive"})
+            || localPart.equalsIgnoreCase("majordomo");
+    }
+
+    private boolean headerIsMailingList(Mail mail) throws MessagingException {
+        return mail.getMessage()
+            .getMatchingHeaders(MAILING_LIST_HEADERS)
             .hasMoreElements();
     }
 


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