You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2020/10/28 04:53:31 UTC

[james-project] 06/10: JAMES-3436 Email/set create: Support convenience messageId headers

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

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

commit 08c28e928f34a2043d3bdb25b0bfe44ddd229628
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Oct 27 11:16:53 2020 +0700

    JAMES-3436 Email/set create: Support convenience messageId headers
---
 .../rfc8621/contract/EmailSetMethodContract.scala  | 58 ++++++++++++++++++++++
 .../james/jmap/json/EmailSetSerializer.scala       | 13 ++++-
 .../scala/org/apache/james/jmap/mail/Email.scala   |  8 +--
 .../org/apache/james/jmap/mail/EmailHeader.scala   |  7 ++-
 .../org/apache/james/jmap/mail/EmailSet.scala      |  7 +++
 5 files changed, 86 insertions(+), 7 deletions(-)

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 c5ed96d..0e2f4bd 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
@@ -530,6 +530,64 @@ trait EmailSetMethodContract {
   }
 
   @Test
+  def createShouldSupportMessageIdHeaders(server: GuiceJamesServer): Unit = {
+    val bobPath = MailboxPath.inbox(BOB)
+    val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
+
+    val request = s"""{
+         |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [
+         |    ["Email/set", {
+         |      "accountId": "$ACCOUNT_ID",
+         |      "create": {
+         |        "aaaaaa":{
+         |          "mailboxIds": {
+         |             "${mailboxId.serialize}": true
+         |          },
+         |          "references": ["aa@bb", "cc@dd"],
+         |          "inReplyTo": ["ee@ff", "gg@hh"],
+         |          "messageId": ["ii@jj", "kk@ll"]
+         |        }
+         |      }
+         |    }, "c1"],
+         |    ["Email/get",
+         |     {
+         |       "accountId": "$ACCOUNT_ID",
+         |       "ids": ["#aaaaaa"],
+         |       "properties": ["references", "inReplyTo", "messageId"]
+         |     },
+         |     "c2"]]
+         |}""".stripMargin
+
+    val response = `given`
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[0][1].created.aaaaaa.id")
+      .inPath("methodResponses[0][1].created.aaaaaa")
+      .isEqualTo("{}".stripMargin)
+
+    assertThatJson(response)
+      .whenIgnoringPaths("methodResponses[1][1].list[0].id")
+      .inPath(s"methodResponses[1][1].list")
+      .isEqualTo(
+        s"""[{
+           |  "references": ["aa@bb", "cc@dd"],
+           |  "inReplyTo": ["ee@ff", "gg@hh"],
+           |  "messageId": ["ii@jj", "kk@ll"]
+           |}]""".stripMargin)
+  }
+
+  @Test
   def createShouldFailIfForbidden(server: GuiceJamesServer): Unit = {
     val andrePath = MailboxPath.inbox(ANDRE)
     val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(andrePath)
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 a5f0cd2..caacce8 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
@@ -23,12 +23,12 @@ import cats.implicits._
 import eu.timepit.refined.refineV
 import javax.inject.Inject
 import org.apache.james.jmap.mail.EmailSet.{EmailCreationId, UnparsedMessageId, UnparsedMessageIdConstraint}
-import org.apache.james.jmap.mail.{AddressesHeaderValue, DestroyIds, EmailAddress, EmailCreationRequest, EmailCreationResponse, EmailSetRequest, EmailSetResponse, EmailSetUpdate, EmailerName, MailboxIds, Subject}
+import org.apache.james.jmap.mail.{AddressesHeaderValue, DestroyIds, EmailAddress, EmailCreationRequest, EmailCreationResponse, EmailSetRequest, EmailSetResponse, EmailSetUpdate, EmailerName, HeaderMessageId, MailboxIds, MessageIdsHeaderValue, Subject}
 import org.apache.james.jmap.model.Id.IdConstraint
 import org.apache.james.jmap.model.KeywordsFactory.STRICT_KEYWORDS_FACTORY
 import org.apache.james.jmap.model.{Keyword, Keywords, SetError}
 import org.apache.james.mailbox.model.{MailboxId, MessageId}
-import play.api.libs.json.{JsBoolean, JsError, JsNull, JsObject, JsResult, JsString, JsSuccess, JsValue, Json, OWrites, Reads, Writes}
+import play.api.libs.json.{JsArray, JsBoolean, JsError, JsNull, JsObject, JsResult, JsString, JsSuccess, JsValue, Json, OWrites, Reads, Writes}
 
 import scala.util.Try
 
@@ -231,8 +231,17 @@ class EmailSetSerializer @Inject()(messageIdFactory: MessageId.Factory, mailboxI
 
   private implicit val subjectReads: Reads[Subject] = Json.valueReads[Subject]
   private implicit val emailerNameReads: Reads[EmailerName] = Json.valueReads[EmailerName]
+  private implicit val headerMessageIdReads: Reads[HeaderMessageId] = Json.valueReads[HeaderMessageId]
   private implicit val emailAddressReads: Reads[EmailAddress] = Json.reads[EmailAddress]
   private implicit val addressesHeaderValueReads: Reads[AddressesHeaderValue] = Json.valueReads[AddressesHeaderValue]
+  private implicit val messageIdsHeaderValueReads: Reads[MessageIdsHeaderValue] = {
+    case JsArray(value) => value.map(headerMessageIdReads.reads)
+      .map(_.asEither)
+      .toList
+      .sequence
+      .fold(e => JsError(e),
+        ids => JsSuccess(MessageIdsHeaderValue(Some(ids).filter(_.nonEmpty))))
+  }
   private implicit val emailCreationRequestReads: Reads[EmailCreationRequest] = Json.reads[EmailCreationRequest]
 
   def deserialize(input: JsValue): JsResult[EmailSetRequest] = Json.fromJson[EmailSetRequest](input)
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 42c2358..fb93168 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
@@ -276,10 +276,10 @@ object EmailHeaders {
   private def extractMessageId(mime4JMessage: Message, fieldName: String): MessageIdsHeaderValue =
     MessageIdsHeaderValue(
       Option(mime4JMessage.getHeader.getFields(fieldName))
-        .map(_.asScala
-          .map(_.getBody)
-          .map(HeaderMessageId.from)
-          .toList)
+        .map(_.asScala.toList)
+        .flatMap(fields => fields.map(field => MessageIdsHeaderValue.from(field).value)
+          .sequence
+          .map(_.flatten))
         .filter(_.nonEmpty))
 
   private def extractAddresses(mime4JMessage: Message, fieldName: String): Option[AddressesHeaderValue] =
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailHeader.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailHeader.scala
index 02f6645..9b877fb 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailHeader.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailHeader.scala
@@ -128,7 +128,12 @@ case class AddressesHeaderValue(value: List[EmailAddress]) extends EmailHeaderVa
   def asMime4JMailboxList: Option[List[Mime4jMailbox]] = Some(value.map(_.asMime4JMailbox)).filter(_.nonEmpty)
 }
 case class GroupedAddressesHeaderValue(value: List[EmailAddressGroup]) extends EmailHeaderValue
-case class MessageIdsHeaderValue(value: Option[List[HeaderMessageId]]) extends EmailHeaderValue
+case class MessageIdsHeaderValue(value: Option[List[HeaderMessageId]]) extends EmailHeaderValue {
+  def asString: Option[String] = value.map(messageIds => messageIds
+    .map(_.value)
+    .map(messageId => s"<${messageId}>")
+    .mkString(" "))
+}
 case class DateHeaderValue(value: Option[UTCDate]) extends EmailHeaderValue
 case class URLsHeaderValue(value: Option[List[HeaderURL]]) extends EmailHeaderValue
 
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 42d68fc..767f021 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
@@ -33,6 +33,7 @@ import org.apache.james.mailbox.model.MessageId
 import org.apache.james.mime4j.dom.Message
 import org.apache.james.mime4j.dom.field.FieldName
 import org.apache.james.mime4j.field.Fields
+import org.apache.james.mime4j.stream.RawField
 import play.api.libs.json.JsObject
 
 import scala.jdk.CollectionConverters._
@@ -53,6 +54,9 @@ object EmailSet {
 }
 
 case class EmailCreationRequest(mailboxIds: MailboxIds,
+                                messageId: Option[MessageIdsHeaderValue],
+                                references: Option[MessageIdsHeaderValue],
+                                inReplyTo: Option[MessageIdsHeaderValue],
                                 from: Option[AddressesHeaderValue],
                                 to: Option[AddressesHeaderValue],
                                 cc: Option[AddressesHeaderValue],
@@ -65,6 +69,9 @@ case class EmailCreationRequest(mailboxIds: MailboxIds,
                                 receivedAt: Option[UTCDate]) {
   def toMime4JMessage: Message = {
     val builder = Message.Builder.of
+    references.flatMap(_.asString).map(new RawField("References", _)).foreach(builder.setField)
+    inReplyTo.flatMap(_.asString).map(new RawField("In-Reply-To", _)).foreach(builder.setField)
+    messageId.flatMap(_.asString).map(new RawField(FieldName.MESSAGE_ID, _)).foreach(builder.setField)
     subject.foreach(value => builder.setSubject(value.value))
     from.flatMap(_.asMime4JMailboxList).map(_.asJava).foreach(builder.setFrom)
     to.flatMap(_.asMime4JMailboxList).map(_.asJava).foreach(builder.setTo)


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