You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by rc...@apache.org on 2020/11/17 03:37:21 UTC

[james-project] 02/06: JAMES-3447 Email/set create should return blobId, threadId, size

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

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

commit bedde56801279f29aaf08db2fab1a0103e2fa427
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Mon Nov 16 16:01:50 2020 +0700

    JAMES-3447 Email/set create should return blobId, threadId, size
---
 .../org/apache/james/mailbox/MessageManager.java   |   8 +-
 .../james/mailbox/store/StoreMessageManager.java   |   2 +-
 .../methods/SetMessagesCreationProcessorTest.java  |   3 +-
 .../methods/SetMessagesUpdateProcessorTest.java    |   3 +-
 .../rfc8621/contract/EmailSetMethodContract.scala  | 823 +++++++++++++--------
 .../james/jmap/json/EmailSetSerializer.scala       |   2 +-
 .../scala/org/apache/james/jmap/mail/Email.scala   |   2 +-
 .../org/apache/james/jmap/mail/EmailSet.scala      |   3 +-
 .../jmap/method/EmailSetCreatePerformer.scala      |   6 +-
 9 files changed, 522 insertions(+), 330 deletions(-)

diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java b/mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java
index 779d84e..c5e8512 100644
--- a/mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java
+++ b/mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java
@@ -147,10 +147,12 @@ public interface MessageManager {
 
     class AppendResult {
         private final ComposedMessageId id;
+        private final Long size;
         private final Optional<List<MessageAttachmentMetadata>> messageAttachments;
 
-        public AppendResult(ComposedMessageId id, Optional<List<MessageAttachmentMetadata>> messageAttachments) {
+        public AppendResult(ComposedMessageId id, Long size, Optional<List<MessageAttachmentMetadata>> messageAttachments) {
             this.id = id;
+            this.size = size;
             this.messageAttachments = messageAttachments;
         }
 
@@ -158,6 +160,10 @@ public interface MessageManager {
             return id;
         }
 
+        public Long getSize() {
+            return size;
+        }
+
         public List<MessageAttachmentMetadata> getMessageAttachments() {
             Preconditions.checkState(messageAttachments.isPresent(), "'attachment storage' not supported by the implementation");
             return messageAttachments.get();
diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java
index 0103598..a1edc78 100644
--- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java
+++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java
@@ -450,7 +450,7 @@ public class StoreMessageManager implements MessageManager {
                     .block();
                 MessageMetaData messageMetaData = data.getLeft();
                 ComposedMessageId ids = new ComposedMessageId(mailbox.getMailboxId(), messageMetaData.getMessageId(), messageMetaData.getUid());
-                return new AppendResult(ids, data.getRight());
+                return new AppendResult(ids, messageMetaData.getSize(), data.getRight());
             }, MailboxPathLocker.LockType.Write);
         }
     }
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessorTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessorTest.java
index 453f932..6e4c162 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessorTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesCreationProcessorTest.java
@@ -102,6 +102,7 @@ public class SetMessagesCreationProcessorTest {
     private static final InMemoryId OUTBOX_ID = InMemoryId.of(12345);
     private static final String DRAFTS = "drafts";
     private static final InMemoryId DRAFTS_ID = InMemoryId.of(12);
+    private static final Long TEST_MESSAGE_SIZE = 1L;
 
     private final CreationMessage.Builder creationMessageBuilder = CreationMessage.builder()
             .from(DraftEmailer.builder().name("alice").email("alice@example.com").build())
@@ -194,7 +195,7 @@ public class SetMessagesCreationProcessorTest {
         when(outbox.getMailboxPath()).thenReturn(MailboxPath.forUser(USER, OUTBOX));
         
         when(outbox.appendMessage(any(MessageManager.AppendCommand.class), any(MailboxSession.class)))
-            .thenReturn(new MessageManager.AppendResult(new ComposedMessageId(OUTBOX_ID, TestMessageId.of(23), MessageUid.of(1)),
+            .thenReturn(new MessageManager.AppendResult(new ComposedMessageId(OUTBOX_ID, TestMessageId.of(23), MessageUid.of(1)), TEST_MESSAGE_SIZE,
                 Optional.of(ImmutableList.of())));
 
         drafts = mock(MessageManager.class);
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java
index b33f25c..5ce3a4b 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/draft/methods/SetMessagesUpdateProcessorTest.java
@@ -92,6 +92,7 @@ public class SetMessagesUpdateProcessorTest {
     private static final InMemoryId OUTBOX_ID = InMemoryId.of(12345);
     private static final String DRAFTS = "drafts";
     private static final InMemoryId DRAFTS_ID = InMemoryId.of(12);
+    private static final Long TEST_MESSAGE_SIZE = 1L;
 
     public static class TestSystemMailboxesProvider implements SystemMailboxesProvider {
 
@@ -200,7 +201,7 @@ public class SetMessagesUpdateProcessorTest {
 
         when(outbox.appendMessage(any(MessageManager.AppendCommand.class), any(MailboxSession.class)))
             .thenReturn(new MessageManager.AppendResult(
-                new ComposedMessageId(OUTBOX_ID, TestMessageId.of(23), MessageUid.of(1)),
+                new ComposedMessageId(OUTBOX_ID, TestMessageId.of(23), MessageUid.of(1)), TEST_MESSAGE_SIZE,
                 Optional.empty()));
 
         drafts = mock(MessageManager.class);
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
index 38bfb79..b843fed 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
@@ -49,7 +49,7 @@ import org.assertj.core.api.Assertions.assertThat
 import org.junit.jupiter.api.{BeforeEach, Test}
 import org.junit.jupiter.params.ParameterizedTest
 import org.junit.jupiter.params.provider.ValueSource
-import play.api.libs.json.{JsString, Json}
+import play.api.libs.json.{JsNumber, JsString, Json}
 
 import scala.jdk.CollectionConverters._
 
@@ -186,10 +186,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
+    val createResponse = Json.parse(response)
+      .\("methodResponses")
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = createResponse
+      .\("id")
+      .get.asInstanceOf[JsString].value
+    val size = createResponse
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
       .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -475,10 +493,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
+    val responseAsJson = Json.parse(response)
+      .\("methodResponses")
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
+      .\("id")
+      .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
       .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -535,10 +571,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
+    val responseAsJson = Json.parse(response)
+      .\("methodResponses")
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
+      .\("id")
+      .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
       .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -592,10 +646,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
+    val responseAsJson = Json.parse(response)
+      .\("methodResponses")
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
+      .\("id")
+      .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
       .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -648,10 +720,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
+    val createResponse = Json.parse(response)
+      .\("methodResponses")
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = createResponse
+      .\("id")
+      .get.asInstanceOf[JsString].value
+    val size = createResponse
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
       .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -709,10 +799,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
+    val createResponse = Json.parse(response)
+      .\("methodResponses")
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = createResponse
+      .\("id")
+      .get.asInstanceOf[JsString].value
+    val size = createResponse
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
       .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -767,10 +875,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
+    val createResponse = Json.parse(response)
+      .\("methodResponses")
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = createResponse
+      .\("id")
+      .get.asInstanceOf[JsString].value
+    val size = createResponse
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
       .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -826,10 +952,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
+    val responseAsJson = Json.parse(response)
+      .\("methodResponses")
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
+      .\("id")
+      .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
       .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -1162,10 +1306,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
+    val responseAsJson = Json.parse(response)
+      .\("methodResponses")
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
+      .\("id")
+      .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
       .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -1241,10 +1403,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
+    val responseAsJson = Json.parse(response)
+      .\("methodResponses")
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
+      .\("id")
+      .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
       .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -1794,20 +1974,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
-      .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
-
-    val blobIdToDownload = Json.parse(response)
+    val responseAsJson = Json.parse(response)
       .\("methodResponses")
-      .\(1).\(1)
-      .\("list")
-      .\(0)
-      .\("attachments")
-      .\(0)
-      .\("blobId")
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
+      .\("id")
       .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1].created.aaaaaa")
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -1821,7 +2009,7 @@ trait EmailSetMethodContract {
            |  "attachments": [
            |    {
            |      "partId": "5",
-           |      "blobId": "$blobIdToDownload",
+           |      "blobId": "${messageId}_5",
            |      "size": 11,
            |      "type": "text/plain",
            |      "charset": "UTF-8",
@@ -1836,7 +2024,7 @@ trait EmailSetMethodContract {
       .basePath("")
       .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
     .when
-      .get(s"/download/$accountId/$blobIdToDownload")
+      .get(s"/download/$accountId/${messageId}_5")
     .`then`
       .statusCode(SC_OK)
       .contentType("text/plain")
@@ -1929,18 +2117,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
-      .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
-
-    val messageId = Json.parse(response)
+    val responseAsJson = Json.parse(response)
       .\("methodResponses")
-      .\(1).\(1)
-      .\("list")
-      .\(0)
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
       .\("id")
       .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1].created.aaaaaa")
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -2075,18 +2273,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
-      .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
-
-    val messageId = Json.parse(response)
+    val responseAsJson = Json.parse(response)
       .\("methodResponses")
-      .\(1).\(1)
-      .\("list")
-      .\(0)
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
       .\("id")
       .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1].created.aaaaaa")
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .whenIgnoringPaths("methodResponses[1][1].list[0].id")
@@ -2239,79 +2447,59 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
-      .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
-
-    val messageId = Json.parse(response)
+    val responseAsJson = Json.parse(response)
       .\("methodResponses")
-      .\(1).\(1)
-      .\("list")
-      .\(0)
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
       .\("id")
       .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
 
     assertThatJson(response)
+      .inPath(s"methodResponses[1][1].list[0]")
       .isEqualTo(
         s"""{
-           |  "sessionState": "75128aab4b1b",
-           |  "methodResponses": [
-           |    ["Email/set", {
-           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |      "newState": "000001",
-           |      "created": {
-           |        "aaaaaa": {
-           |          "id": "$messageId"
-           |        }
-           |      }
-           |    }, "c1"],
-           |    ["Email/get", {
-           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |      "state": "000001",
-           |      "list": [
-           |        {
-           |          "id": "$messageId",
-           |          "bodyStructure": {
-           |            "type": "multipart/mixed",
+           |  "id": "$messageId",
+           |  "bodyStructure": {
+           |    "type": "multipart/mixed",
+           |    "subParts": [
+           |      {
+           |        "type": "multipart/related",
+           |        "subParts": [
+           |          {
+           |            "type": "multipart/alternative",
            |            "subParts": [
            |              {
-           |                "type": "multipart/related",
-           |                "subParts": [
-           |                  {
-           |                    "type": "multipart/alternative",
-           |                    "subParts": [
-           |                      {
-           |                        "type": "text/html"
-           |                      },
-           |                      {
-           |                        "type": "text/plain"
-           |                      }
-           |                    ]
-           |                  },
-           |                  {
-           |                    "type": "text/plain",
-           |                    "disposition": "inline",
-           |                    "cid": "abc"
-           |                  },
-           |                  {
-           |                    "type": "text/plain",
-           |                    "disposition": "inline",
-           |                    "cid": "def"
-           |                  }
-           |                ]
+           |                "type": "text/html"
            |              },
            |              {
-           |                "type": "text/plain",
-           |                "disposition": "attachment"
+           |                "type": "text/plain"
            |              }
            |            ]
+           |          },
+           |          {
+           |            "type": "text/plain",
+           |            "disposition": "inline",
+           |            "cid": "abc"
+           |          },
+           |          {
+           |            "type": "text/plain",
+           |            "disposition": "inline",
+           |            "cid": "def"
            |          }
-           |        }
-           |      ],
-           |      "notFound": []
-           |    }, "c2"]
-           |  ]
+           |        ]
+           |      },
+           |      {
+           |        "type": "text/plain",
+           |        "disposition": "attachment"
+           |      }
+           |    ]
+           |  }
            |}""".stripMargin)
   }
 
@@ -2396,64 +2584,54 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
-      .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
-
-    val messageId = Json.parse(response)
+    val responseAsJson = Json.parse(response)
       .\("methodResponses")
-      .\(1).\(1)
-      .\("list")
-      .\(0)
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
       .\("id")
       .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
 
     assertThatJson(response)
+      .inPath("methodResponses[0][1].created.aaaaaa")
       .isEqualTo(
         s"""{
-           |  "sessionState": "75128aab4b1b",
-           |  "methodResponses": [
-           |    ["Email/set", {
-           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |      "newState": "000001",
-           |      "created": {
-           |        "aaaaaa": {
-           |          "id": "$messageId"
-           |        }
-           |      }
-           |    }, "c1"],
-           |    ["Email/get", {
-           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |      "state": "000001",
-           |      "list": [
-           |        {
-           |          "id": "$messageId",
-           |          "bodyStructure": {
-           |            "type": "multipart/mixed",
-           |            "subParts": [
-           |              {
-           |                "type":"multipart/alternative",
-           |                "subParts": [
-           |                  {
-           |                    "type":"text/html"
-           |                  },
-           |                  {
-           |                    "type":"text/plain"
-           |                  }
-           |                ]
-           |              },
-           |              {
-           |                "type": "text/plain",
-           |                "disposition": "attachment"
-           |              }
-           |            ]
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
+
+    assertThatJson(response)
+      .inPath(s"methodResponses[1][1].list[0]")
+      .isEqualTo(
+        s"""{
+           |  "id": "$messageId",
+           |  "bodyStructure": {
+           |    "type": "multipart/mixed",
+           |    "subParts": [
+           |      {
+           |        "type":"multipart/alternative",
+           |        "subParts": [
+           |          {
+           |            "type":"text/html"
+           |          },
+           |          {
+           |            "type":"text/plain"
            |          }
-           |        }
-           |      ],
-           |      "notFound": []
-           |    }, "c2"]
-           |  ]
+           |        ]
+           |      },
+           |      {
+           |        "type": "text/plain",
+           |        "disposition": "attachment"
+           |      }
+           |    ]
+           |  }
            |}""".stripMargin)
   }
 
@@ -2546,70 +2724,60 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
-      .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
-
-    val messageId = Json.parse(response)
+    val responseAsJson = Json.parse(response)
       .\("methodResponses")
-      .\(1).\(1)
-      .\("list")
-      .\(0)
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
       .\("id")
       .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
 
     assertThatJson(response)
+      .inPath("methodResponses[0][1].created.aaaaaa")
       .isEqualTo(
         s"""{
-           |  "sessionState": "75128aab4b1b",
-           |  "methodResponses": [
-           |    ["Email/set", {
-           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |      "newState": "000001",
-           |      "created": {
-           |        "aaaaaa": {
-           |          "id": "$messageId"
-           |        }
-           |      }
-           |    }, "c1"],
-           |    ["Email/get", {
-           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |      "state": "000001",
-           |      "list": [
-           |        {
-           |          "id": "$messageId",
-           |          "bodyStructure": {
-           |            "type": "multipart/related",
-           |            "subParts": [
-           |              {
-           |                "type":"multipart/alternative",
-           |                "subParts": [
-           |                  {
-           |                    "type":"text/html"
-           |                  },
-           |                  {
-           |                    "type":"text/plain"
-           |                  }
-           |                ]
-           |              },
-           |              {
-           |                "type": "text/plain",
-           |                "disposition": "inline",
-           |                "cid": "abc"
-           |              },
-           |              {
-           |                "type": "text/plain",
-           |                "disposition": "inline",
-           |                "cid": "def"
-           |              }
-           |            ]
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
+
+    assertThatJson(response)
+      .inPath(s"methodResponses[1][1].list[0]")
+      .isEqualTo(
+        s"""{
+           |  "id": "$messageId",
+           |  "bodyStructure": {
+           |    "type": "multipart/related",
+           |    "subParts": [
+           |      {
+           |        "type":"multipart/alternative",
+           |        "subParts": [
+           |          {
+           |            "type":"text/html"
+           |          },
+           |          {
+           |            "type":"text/plain"
            |          }
-           |        }
-           |      ],
-           |      "notFound": []
-           |    }, "c2"]
-           |  ]
+           |        ]
+           |      },
+           |      {
+           |        "type": "text/plain",
+           |        "disposition": "inline",
+           |        "cid": "abc"
+           |      },
+           |      {
+           |        "type": "text/plain",
+           |        "disposition": "inline",
+           |        "cid": "def"
+           |      }
+           |    ]
+           |  }
            |}""".stripMargin)
   }
 
@@ -2685,18 +2853,28 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
-      .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
-
-    val messageId = Json.parse(response)
+    val responseAsJson = Json.parse(response)
       .\("methodResponses")
-      .\(1).\(1)
-      .\("list")
-      .\(0)
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
       .\("id")
       .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
+
+    assertThatJson(response)
+      .inPath("methodResponses[0][1].created.aaaaaa")
+      .isEqualTo(
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
 
     assertThatJson(response)
       .isEqualTo(
@@ -2708,7 +2886,10 @@ trait EmailSetMethodContract {
            |      "newState": "000001",
            |      "created": {
            |        "aaaaaa": {
-           |          "id": "$messageId"
+           |          "id": "$messageId",
+           |          "blobId": "$messageId",
+           |          "threadId": "$messageId",
+           |          "size": $size
            |        }
            |      }
            |    }, "c1"],
@@ -2794,18 +2975,18 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
-      .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
-
-    val messageId = Json.parse(response)
+    val responseAsJson = Json.parse(response)
       .\("methodResponses")
-      .\(1).\(1)
-      .\("list")
-      .\(0)
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
       .\("id")
       .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
 
     assertThatJson(response)
       .isEqualTo(
@@ -2817,7 +2998,10 @@ trait EmailSetMethodContract {
            |      "newState": "000001",
            |      "created": {
            |        "aaaaaa": {
-           |          "id": "$messageId"
+           |          "id": "$messageId",
+           |          "blobId": "$messageId",
+           |          "threadId": "$messageId",
+           |          "size": $size
            |        }
            |      }
            |    }, "c1"],
@@ -2956,80 +3140,69 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
-      .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
-
-    val messageId = Json.parse(response)
+    val createResponse = Json.parse(response)
       .\("methodResponses")
-      .\(1).\(1)
-      .\("list")
-      .\(0)
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = createResponse
       .\("id")
       .get.asInstanceOf[JsString].value
+    val size = createResponse
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
 
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
+      .inPath("methodResponses[0][1].created.aaaaaa")
       .isEqualTo(
         s"""{
-           |  "sessionState": "75128aab4b1b",
-           |  "methodResponses": [
-           |    ["Email/set", {
-           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |      "newState": "000001",
-           |      "created": {
-           |        "aaaaaa": {
-           |          "id": "$messageId"
-           |        }
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
+
+    assertThatJson(response)
+      .inPath(s"methodResponses[1][1].list[0]")
+      .isEqualTo(
+        s"""{
+           |  "id": "$messageId",
+           |  "bodyStructure": {
+           |    "type": "multipart/mixed",
+           |    "subParts": [
+           |      {
+           |        "type": "multipart/related",
+           |        "subParts": [
+           |          {
+           |            "type": "multipart/alternative",
+           |            "subParts": [
+           |              {
+           |                "type": "text/html"
+           |              },
+           |              {
+           |                "type": "text/plain"
+           |              }
+           |            ]
+           |          },
+           |          {
+           |            "type": "text/plain",
+           |            "disposition": "inline",
+           |            "cid": "abc"
+           |          },
+           |          {
+           |            "type": "text/plain",
+           |            "disposition": "inline",
+           |            "cid": "def"
+           |          }
+           |        ]
+           |      },
+           |      {
+           |        "type": "text/plain",
+           |        "disposition": "attachment"
            |      }
-           |    }, "c1"],
-           |    ["Email/get", {
-           |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
-           |      "state": "000001",
-           |      "list": [
-           |        {
-           |          "id": "$messageId",
-           |          "bodyStructure": {
-           |          "type": "multipart/mixed",
-           |          "subParts": [
-           |            {
-           |              "type": "multipart/related",
-           |              "subParts": [
-           |                {
-           |                  "type": "multipart/alternative",
-           |                  "subParts": [
-           |                    {
-           |                      "type": "text/html"
-           |                    },
-           |                    {
-           |                      "type": "text/plain"
-           |                    }
-           |                  ]
-           |                },
-           |                {
-           |                  "type": "text/plain",
-           |                  "disposition": "inline",
-           |                  "cid": "abc"
-           |                },
-           |                {
-           |                  "type": "text/plain",
-           |                  "disposition": "inline",
-           |                  "cid": "def"
-           |                }
-           |              ]
-           |            },
-           |            {
-           |              "type": "text/plain",
-           |              "disposition": "attachment"
-           |             }
-           |           ]
-           |         }
-           |       }
-           |     ],
-           |     "notFound": []
-           |    }, "c2"]
-           |  ]
+           |    ]
+           |  }
            |}""".stripMargin)
   }
 
@@ -3103,26 +3276,34 @@ trait EmailSetMethodContract {
       .body
       .asString
 
-    assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
-      .inPath("methodResponses[0][1].created.aaaaaa")
-      .isEqualTo("{}".stripMargin)
-
-    val blobIdToDownload = Json.parse(response)
+    val responseAsJson = Json.parse(response)
       .\("methodResponses")
-      .\(1).\(1)
-      .\("list")
-      .\(0)
-      .\("attachments")
-      .\(0)
-      .\("blobId")
+      .\(0).\(1)
+      .\("created")
+      .\("aaaaaa")
+
+    val messageId = responseAsJson
+      .\("id")
       .get.asInstanceOf[JsString].value
+    val size = responseAsJson
+      .\("size")
+      .get.asInstanceOf[JsNumber].value
 
     assertThatJson(response)
-      .whenIgnoringPaths("methodResponses[1][1].list[0].id")
-      .inPath(s"methodResponses[1][1].list")
+      .inPath("methodResponses[0][1].created.aaaaaa")
       .isEqualTo(
-        s"""[{
+        s"""{
+           | "id": "$messageId",
+           | "blobId": "$messageId",
+           | "threadId": "$messageId",
+           | "size": $size
+           |}""".stripMargin)
+
+    assertThatJson(response)
+      .inPath(s"methodResponses[1][1].list[0]")
+      .isEqualTo(
+        s"""{
+           |  "id": "$messageId",
            |  "mailboxIds": {
            |    "${mailboxId.serialize}": true
            |  },
@@ -3131,7 +3312,7 @@ trait EmailSetMethodContract {
            |    {
            |      "name": "myAttachment",
            |      "partId": "5",
-           |      "blobId": "$blobIdToDownload",
+           |      "blobId": "${messageId}_5",
            |      "size": 11,
            |      "type": "text/plain",
            |      "charset": "UTF-8",
@@ -3140,7 +3321,7 @@ trait EmailSetMethodContract {
            |      "location": "http://125.26.23.36/content"
            |    }
            |  ]
-           |}]""".stripMargin)
+           |}""".stripMargin)
   }
 
   @Test
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala
index 6ebc2c6..accb3fb 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala
@@ -202,6 +202,7 @@ class EmailSetSerializer @Inject()(messageIdFactory: MessageId.Factory, mailboxI
     keywordsMap => STRICT_KEYWORDS_FACTORY.fromSet(keywordsMap.keys.toSet)
       .fold(e => JsError(e.getMessage), keywords => JsSuccess(keywords)))
 
+  private implicit val blobIdFormat: Format[BlobId] = Json.valueFormat[BlobId]
   private implicit val unitWrites: Writes[Unit] = _ => JsNull
   private implicit val updatedWrites: Writes[Map[MessageId, Unit]] = mapWrites[MessageId, Unit](_.serialize, unitWrites)
   private implicit val notDestroyedWrites: Writes[Map[UnparsedMessageId, SetError]] = mapWrites[UnparsedMessageId, SetError](_.value, setErrorWrites)
@@ -339,7 +340,6 @@ class EmailSetSerializer @Inject()(messageIdFactory: MessageId.Factory, mailboxI
       case AsGroupedAddresses => GroupedAddressReads
     }
 
-  private implicit val blobIdReads: Reads[BlobId] = Json.valueReads[BlobId]
   private implicit val nameReads: Reads[Name] = Json.valueReads[Name]
   private implicit val charsetReads: Reads[Charset] = Json.valueReads[Charset]
   private implicit val dispositionReads: Reads[Disposition] = Json.valueReads[Disposition]
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala
index 534b1ec..23fd436 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala
@@ -82,7 +82,7 @@ object Email {
   type Size = Long Refined NonNegative
   val Zero: Size = 0L
 
-  private[mail] def sanitizeSize(value: Long): Size = {
+  def sanitizeSize(value: Long): Size = {
     val size: Either[String, Size] = refineV[NonNegative](value)
     size.fold(e => {
       logger.error(s"Encountered an invalid Email size: $e")
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala
index ca1af42..dab3d2c 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala
@@ -32,6 +32,7 @@ import org.apache.james.jmap.core.Id.Id
 import org.apache.james.jmap.core.State.State
 import org.apache.james.jmap.core.{AccountId, SetError, UTCDate}
 import org.apache.james.jmap.mail.Disposition.INLINE
+import org.apache.james.jmap.mail.Email.Size
 import org.apache.james.jmap.mail.EmailSet.{EmailCreationId, UnparsedMessageId}
 import org.apache.james.jmap.method.WithAccountId
 import org.apache.james.mailbox.exception.AttachmentNotFoundException
@@ -394,5 +395,5 @@ case class InvalidEmailPropertyException(property: String, cause: String) extend
 
 case class InvalidEmailUpdateException(property: String, cause: String) extends EmailUpdateValidationException
 
-case class EmailCreationResponse(id: MessageId)
+case class EmailCreationResponse(id: MessageId, blobId: Option[BlobId], threadId: Option[BlobId], size: Size)
 
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetCreatePerformer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetCreatePerformer.scala
index 662c750..0bc684f 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetCreatePerformer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetCreatePerformer.scala
@@ -29,7 +29,7 @@ import org.apache.james.jmap.core.SetError.SetErrorDescription
 import org.apache.james.jmap.core.{Properties, SetError, UTCDate}
 import org.apache.james.jmap.json.EmailSetSerializer
 import org.apache.james.jmap.mail.EmailSet.EmailCreationId
-import org.apache.james.jmap.mail.{EmailCreationRequest, EmailCreationResponse, EmailSetRequest}
+import org.apache.james.jmap.mail.{BlobId, Email, EmailCreationRequest, EmailCreationResponse, EmailSetRequest}
 import org.apache.james.jmap.method.EmailSetCreatePerformer.{CreationFailure, CreationResult, CreationResults, CreationSuccess}
 import org.apache.james.mailbox.MessageManager.AppendCommand
 import org.apache.james.mailbox.exception.{AttachmentNotFoundException, MailboxNotFoundException}
@@ -99,7 +99,9 @@ class EmailSetCreatePerformer @Inject()(serializer: EmailSetSerializer,
                 .withInternalDate(Date.from(request.receivedAt.getOrElse(UTCDate(ZonedDateTime.now())).asUTC.toInstant))
                 .build(message),
                 mailboxSession)
-            CreationSuccess(clientId, EmailCreationResponse(appendResult.getId.getMessageId))
+
+            val blobId: Option[BlobId] = BlobId.of(appendResult.getId.getMessageId).toOption
+            CreationSuccess(clientId, EmailCreationResponse(appendResult.getId.getMessageId, blobId, blobId, Email.sanitizeSize(appendResult.getSize)))
           })
             .subscribeOn(Schedulers.elastic())
             .onErrorResume(e => SMono.just[CreationResult](CreationFailure(clientId, e))))


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