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 26784d5868513db3dd13fae221c369896e141932 Author: Benoit Tellier <[email protected]> AuthorDate: Mon Oct 26 12:44:38 2020 +0100 JAMES-3436 Email/set create: Support convenience address headers --- .../rfc8621/contract/EmailGetMethodContract.scala | 2 +- .../rfc8621/contract/EmailSetMethodContract.scala | 62 ++++++++++++++++++++++ .../james/jmap/json/EmailGetSerializer.scala | 3 +- .../james/jmap/json/EmailSetSerializer.scala | 5 +- .../scala/org/apache/james/jmap/json/package.scala | 8 +++ .../scala/org/apache/james/jmap/mail/Email.scala | 2 +- .../org/apache/james/jmap/mail/EmailAddress.scala | 23 ++++---- .../org/apache/james/jmap/mail/EmailHeader.scala | 6 ++- .../org/apache/james/jmap/mail/EmailSet.scala | 15 ++++++ 9 files changed, 110 insertions(+), 16 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/EmailGetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailGetMethodContract.scala index a6ca953..47a5c3e 100644 --- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailGetMethodContract.scala +++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailGetMethodContract.scala @@ -6223,7 +6223,7 @@ trait EmailGetMethodContract { | "c1"]] |}""".stripMargin) .when - .post + .post.prettyPeek .`then` .statusCode(SC_OK) .contentType(JSON) 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 64bbdf6..b04ca96 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 @@ -237,6 +237,68 @@ trait EmailSetMethodContract { } @Test + def createShouldHandleAddressHeaders(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}, + | "cc": [{"name": "MODALİF", "email": "[email protected]"}], + | "bcc": [{"email": "[email protected]"}], + | "to": [{"email": "[email protected]"}, {"email": "[email protected]"}], + | "from": [{"email": "[email protected]"}, {"email": "[email protected]"}], + | "sender": [{"email": "[email protected]"}], + | "replyTo": [{"email": "[email protected]"}, {"email": "[email protected]"}] + | } + | } + | }, "c1"], + | ["Email/get", + | { + | "accountId": "$ACCOUNT_ID", + | "ids": ["#aaaaaa"], + | "properties": ["cc", "bcc", "sender", "from", "to", "replyTo"] + | }, + | "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"""[{ + | "cc": [{"name": "MODALİF", "email": "[email protected]"}], + | "bcc": [{"email": "[email protected]"}], + | "to": [{"email": "[email protected]"}, {"email": "[email protected]"}], + | "from": [{"email": "[email protected]"}, {"email": "[email protected]"}], + | "sender": [{"email": "[email protected]"}], + | "replyTo": [{"email": "[email protected]"}, {"email": "[email protected]"}] + |}]""".stripMargin) + } + + @Test def createShouldSupportKeywords(server: GuiceJamesServer): Unit = { val bobPath = MailboxPath.inbox(BOB) val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala index 9ec87f6..e8f3c54 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailGetSerializer.scala @@ -20,7 +20,7 @@ package org.apache.james.jmap.json import org.apache.james.jmap.api.model.Preview -import org.apache.james.jmap.mail.{Address, AddressesHeaderValue, BlobId, Charset, DateHeaderValue, Disposition, EmailAddress, EmailAddressGroup, EmailBody, EmailBodyMetadata, EmailBodyPart, EmailBodyValue, EmailFastView, EmailFullView, EmailGetRequest, EmailGetResponse, EmailHeader, EmailHeaderName, EmailHeaderValue, EmailHeaderView, EmailHeaders, EmailIds, EmailMetadata, EmailMetadataView, EmailNotFound, EmailView, EmailerName, FetchAllBodyValues, FetchHTMLBodyValues, FetchTextBodyValu [...] +import org.apache.james.jmap.mail.{AddressesHeaderValue, BlobId, Charset, DateHeaderValue, Disposition, EmailAddress, EmailAddressGroup, EmailBody, EmailBodyMetadata, EmailBodyPart, EmailBodyValue, EmailFastView, EmailFullView, EmailGetRequest, EmailGetResponse, EmailHeader, EmailHeaderName, EmailHeaderValue, EmailHeaderView, EmailHeaders, EmailIds, EmailMetadata, EmailMetadataView, EmailNotFound, EmailView, EmailerName, FetchAllBodyValues, FetchHTMLBodyValues, FetchTextBodyValues, Group [...] import org.apache.james.jmap.model._ import org.apache.james.mailbox.model.{Cid, MailboxId, MessageId} import play.api.libs.functional.syntax._ @@ -45,7 +45,6 @@ object EmailGetSerializer { private implicit val languageWrites: Writes[Language] = Json.valueWrites[Language] private implicit val locationWrites: Writes[Location] = Json.valueWrites[Location] private implicit val emailerNameWrites: Writes[EmailerName] = Json.valueWrites[EmailerName] - private implicit val addressWrites: Writes[Address] = Json.valueWrites[Address] private implicit val emailAddressWrites: Writes[EmailAddress] = Json.writes[EmailAddress] private implicit val headerMessageIdWrites: Writes[HeaderMessageId] = Json.valueWrites[HeaderMessageId] private implicit val isEncodingProblemWrites: Writes[IsEncodingProblem] = Json.valueWrites[IsEncodingProblem] 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 e36e3ca..a5f0cd2 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,7 +23,7 @@ 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.{DestroyIds, EmailCreationRequest, EmailCreationResponse, EmailSetRequest, EmailSetResponse, EmailSetUpdate, MailboxIds, Subject} +import org.apache.james.jmap.mail.{AddressesHeaderValue, DestroyIds, EmailAddress, EmailCreationRequest, EmailCreationResponse, EmailSetRequest, EmailSetResponse, EmailSetUpdate, EmailerName, MailboxIds, 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} @@ -230,6 +230,9 @@ class EmailSetSerializer @Inject()(messageIdFactory: MessageId.Factory, mailboxI private implicit val emailResponseSetWrites: OWrites[EmailSetResponse] = Json.writes[EmailSetResponse] private implicit val subjectReads: Reads[Subject] = Json.valueReads[Subject] + private implicit val emailerNameReads: Reads[EmailerName] = Json.valueReads[EmailerName] + private implicit val emailAddressReads: Reads[EmailAddress] = Json.reads[EmailAddress] + private implicit val addressesHeaderValueReads: Reads[AddressesHeaderValue] = Json.valueReads[AddressesHeaderValue] 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/json/package.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/package.scala index 05eaeab..2c4e5c2 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/package.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/package.scala @@ -23,6 +23,7 @@ import java.time.ZonedDateTime import java.time.format.DateTimeFormatter import eu.timepit.refined.api.{RefType, Validate} +import org.apache.james.core.MailAddress import org.apache.james.jmap.model.Id.Id import org.apache.james.jmap.model.SetError.SetErrorDescription import org.apache.james.jmap.model.{AccountId, Properties, SetError, UTCDate} @@ -101,6 +102,13 @@ package object json { private[json] implicit val propertiesFormat: Format[Properties] = Json.valueFormat[Properties] private[json] implicit val setErrorDescriptionWrites: Writes[SetErrorDescription] = Json.valueWrites[SetErrorDescription] private[json] implicit val setErrorWrites: Writes[SetError] = Json.writes[SetError] + private[json] implicit val mailAddressWrites: Writes[MailAddress] = mailAddress => JsString(mailAddress.asString) + private[json] implicit val mailAddressReads: Reads[MailAddress] = { + case JsString(value) => Try(new MailAddress(value)) + .fold(e => JsError(e.getMessage), + mailAddress => JsSuccess(mailAddress)) + case _ => JsError("mail address needs to be represented with a JsString") + } private[json] implicit val utcDateWrites: Writes[UTCDate] = utcDate => JsString(utcDate.asUTC.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssX"))) } 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 9174ef9..0804583 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 @@ -286,7 +286,7 @@ object EmailHeaders { .flatMap { case f: AddressListField => Some(AddressesHeaderValue(EmailAddress.from(f.getAddressList))) case f: MailboxListField => Some(AddressesHeaderValue(EmailAddress.from(f.getMailboxList))) - case f: MailboxField => Some(AddressesHeaderValue(List(EmailAddress.from(f.getMailbox)))) + case f: MailboxField => Some(AddressesHeaderValue(List(EmailAddress.from(f.getMailbox).toOption).flatten)) case _ => None } .filter(_.value.nonEmpty) diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailAddress.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailAddress.scala index 59f3a6c..80548d6 100644 --- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailAddress.scala +++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailAddress.scala @@ -19,9 +19,11 @@ package org.apache.james.jmap.mail +import org.apache.james.core.MailAddress import org.apache.james.mime4j.dom.address.{AddressList, MailboxList, Mailbox => Mime4jMailbox} import scala.jdk.CollectionConverters._ +import scala.util.Try object EmailerName { def from(value: String): EmailerName = EmailerName(value.strip()) @@ -29,8 +31,6 @@ object EmailerName { case class EmailerName(value: String) extends AnyVal -case class Address(value: String) extends AnyVal - object EmailAddress { def from(addressList: AddressList): List[EmailAddress] = Option(addressList) .map(addressList => from(addressList.flatten())) @@ -39,13 +39,18 @@ object EmailAddress { def from(addressList: MailboxList): List[EmailAddress] = addressList.asScala .toList - .map(from) + .flatMap(mailbox => from(mailbox).toOption) - def from(mailbox: Mime4jMailbox): EmailAddress = { - EmailAddress( - name = Option(mailbox.getName).map(EmailerName.from), - email = Address(mailbox.getAddress)) - } + def from(mailbox: Mime4jMailbox): Try[EmailAddress] = + Try(new MailAddress(mailbox.getAddress)) + .map(email => EmailAddress( + name = Option(mailbox.getName).map(EmailerName.from), + email = email)) } -case class EmailAddress(name: Option[EmailerName], email: Address) +case class EmailAddress(name: Option[EmailerName], email: MailAddress) { + val asMime4JMailbox: Mime4jMailbox = new Mime4jMailbox( + name.map(_.value).orNull, + email.getLocalPart, + email.getDomain.asString) +} 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 8c8247c..02f6645 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 @@ -71,7 +71,7 @@ object GroupedAddressesHeaderValue extends EmailHeaderValue { case mailbox: Mime4jMailbox => Some(mailbox) case _ => None }) - .map(EmailAddress.from(_)) + .flatMap(EmailAddress.from(_).toOption) GroupedAddressesHeaderValue(List(EmailAddressGroup(None, addressesWithoutGroup)) ++ groups) } @@ -124,7 +124,9 @@ case class EmailHeaderName(value: String) extends AnyVal sealed trait EmailHeaderValue case class RawHeaderValue(value: String) extends EmailHeaderValue case class TextHeaderValue(value: String) extends EmailHeaderValue -case class AddressesHeaderValue(value: List[EmailAddress]) extends EmailHeaderValue +case class AddressesHeaderValue(value: List[EmailAddress]) extends EmailHeaderValue { + 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 DateHeaderValue(value: Option[UTCDate]) 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 90c9c41..f64db7e 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 @@ -30,8 +30,11 @@ import org.apache.james.jmap.model.State.State import org.apache.james.jmap.model.{AccountId, Keywords, SetError, UTCDate} 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 play.api.libs.json.JsObject +import scala.jdk.CollectionConverters._ import scala.util.{Right, Try} object EmailSet { @@ -49,12 +52,24 @@ object EmailSet { } case class EmailCreationRequest(mailboxIds: MailboxIds, + from: Option[AddressesHeaderValue], + to: Option[AddressesHeaderValue], + cc: Option[AddressesHeaderValue], + bcc: Option[AddressesHeaderValue], + sender: Option[AddressesHeaderValue], + replyTo: Option[AddressesHeaderValue], subject: Option[Subject], keywords: Option[Keywords], receivedAt: Option[UTCDate]) { def toMime4JMessage: Message = { val builder = Message.Builder.of subject.foreach(value => builder.setSubject(value.value)) + from.flatMap(_.asMime4JMailboxList).map(_.asJava).foreach(builder.setFrom) + to.flatMap(_.asMime4JMailboxList).map(_.asJava).foreach(builder.setTo) + cc.flatMap(_.asMime4JMailboxList).map(_.asJava).foreach(builder.setCc) + bcc.flatMap(_.asMime4JMailboxList).map(_.asJava).foreach(builder.setBcc) + sender.flatMap(_.asMime4JMailboxList).map(_.asJava).map(Fields.addressList(FieldName.SENDER, _)).foreach(builder.setField) + replyTo.flatMap(_.asMime4JMailboxList).map(_.asJava).foreach(builder.setReplyTo) builder.setBody("", StandardCharsets.UTF_8) builder.build() } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
