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 2021/04/07 00:57:08 UTC
[james-project] branch master updated: JAMES-3434 Fix
EmailSubmission/set onSuccessUpdateEmail property (#356)
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
The following commit(s) were added to refs/heads/master by this push:
new 486d03c JAMES-3434 Fix EmailSubmission/set onSuccessUpdateEmail property (#356)
486d03c is described below
commit 486d03cd2f8cfb14d3344377a098c106d8e4a652
Author: Tellier Benoit <bt...@linagora.com>
AuthorDate: Wed Apr 7 07:57:03 2021 +0700
JAMES-3434 Fix EmailSubmission/set onSuccessUpdateEmail property (#356)
* JAMES-3434 Fix EmailSubmission/set onSuccessUpdateEmail property
Re-reading https://jmap.io/spec-mail.html#emailsubmissionset
```
onSuccessUpdateEmail: Id[PatchObject]|null A map of EmailSubmission id
to an object containing properties to update on the Email object
referenced by the EmailSubmission if the create/update/destroy
succeeds. (For references to EmailSubmissions created in the same
“/set” invocation, this is equivalent to a creation-reference, so the
id will be the creation id prefixed with a #.)
```
We are currently using a message id, not an EmailSubmission id...
* JAMES-3434 Pre-validate EmailSubmissionSet
We should not send the email if we are not able to resolve the
onSuccessUpdateEmail afterwards.
* JAMES-3434 Document EmailSubmission/set onSuccessUpdateEmail limitations
* JAMES-3434 EmailSubmission/set onSuccessDestroy fix
It also relies on an EmailSubmission creation id, that then
needs to be resoled to MessageIds.
---
.../EmailSubmissionSetMethodContract.scala | 268 ++++++++++++++++++++-
.../doc/specs/spec/mail/messagesubmission.mdown | 8 +-
.../james/jmap/mail/EmailSubmissionSet.scala | 77 +++++-
.../jmap/method/EmailSubmissionSetMethod.scala | 101 +++++---
4 files changed, 406 insertions(+), 48 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/EmailSubmissionSetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSubmissionSetMethodContract.scala
index 79ca9d5..b526e76 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSubmissionSetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSubmissionSetMethodContract.scala
@@ -857,7 +857,7 @@ trait EmailSubmissionSetMethodContract {
| }
| },
| "onSuccessUpdateEmail": {
- | "${messageId.serialize}": {
+ | "#k1490": {
| "keywords": {"$$sent":true}
| }
| }
@@ -933,6 +933,142 @@ trait EmailSubmissionSetMethodContract {
}
@Test
+ def setShouldFailWhenOnSuccessUpdateEmailMissesTheCreationIdSharp(server: GuiceJamesServer): Unit = {
+ val message: Message = Message.Builder
+ .of
+ .setSubject("test")
+ .setSender(BOB.asString)
+ .setFrom(BOB.asString)
+ .setTo(ANDRE.asString)
+ .setBody("testmail", StandardCharsets.UTF_8)
+ .build
+
+ val bobDraftsPath = MailboxPath.forUser(BOB, DefaultMailboxes.DRAFTS)
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobDraftsPath)
+ val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl]).appendMessage(BOB.asString(), bobDraftsPath, AppendCommand.builder()
+ .build(message))
+ .getMessageId
+
+ val requestBob =
+ s"""{
+ | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:submission"],
+ | "methodCalls": [
+ | ["EmailSubmission/set", {
+ | "accountId": "$ACCOUNT_ID",
+ | "create": {
+ | "k1490": {
+ | "emailId": "${messageId.serialize}",
+ | "envelope": {
+ | "mailFrom": {"email": "${BOB.asString}"},
+ | "rcptTo": [{"email": "${ANDRE.asString}"}]
+ | }
+ | }
+ | },
+ | "onSuccessUpdateEmail": {
+ | "notStored": {
+ | "keywords": {"$$sent":true}
+ | }
+ | }
+ | }, "c1"]]
+ |}""".stripMargin
+
+ val response = `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(requestBob)
+ .when
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .isEqualTo(s"""{
+ | "sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+ | "methodResponses": [
+ | [
+ | "error",
+ | {
+ | "type": "invalidArguments",
+ | "description": "notStored cannot be retrieved as storage for EmailSubmission is not yet implemented"
+ | },
+ | "c1"
+ | ]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def setShouldFailWhenOnSuccessUpdateEmailDoesNotReferenceACreationWithinThisCall(server: GuiceJamesServer): Unit = {
+ val message: Message = Message.Builder
+ .of
+ .setSubject("test")
+ .setSender(BOB.asString)
+ .setFrom(BOB.asString)
+ .setTo(ANDRE.asString)
+ .setBody("testmail", StandardCharsets.UTF_8)
+ .build
+
+ val bobDraftsPath = MailboxPath.forUser(BOB, DefaultMailboxes.DRAFTS)
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobDraftsPath)
+ val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl]).appendMessage(BOB.asString(), bobDraftsPath, AppendCommand.builder()
+ .build(message))
+ .getMessageId
+
+ val requestBob =
+ s"""{
+ | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:submission"],
+ | "methodCalls": [
+ | ["EmailSubmission/set", {
+ | "accountId": "$ACCOUNT_ID",
+ | "create": {
+ | "k1490": {
+ | "emailId": "${messageId.serialize}",
+ | "envelope": {
+ | "mailFrom": {"email": "${BOB.asString}"},
+ | "rcptTo": [{"email": "${ANDRE.asString}"}]
+ | }
+ | }
+ | },
+ | "onSuccessUpdateEmail": {
+ | "#badReference": {
+ | "keywords": {"$$sent":true}
+ | }
+ | }
+ | }, "c1"]]
+ |}""".stripMargin
+
+ val response = `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(requestBob)
+ .when
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .isEqualTo(s"""{
+ | "sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+ | "methodResponses": [
+ | [
+ | "error",
+ | {
+ | "type": "invalidArguments",
+ | "description": "#badReference cannot be referenced in current method call"
+ | },
+ | "c1"
+ | ]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
def onSuccessDestroyEmailShouldTriggerAnImplicitEmailSetCall(server: GuiceJamesServer): Unit = {
val message: Message = Message.Builder
.of
@@ -964,7 +1100,7 @@ trait EmailSubmissionSetMethodContract {
| }
| }
| },
- | "onSuccessDestroyEmail": ["${messageId.serialize}"]
+ | "onSuccessDestroyEmail": ["#k1490"]
| }, "c1"],
| ["Email/get",
| {
@@ -1030,6 +1166,134 @@ trait EmailSubmissionSetMethodContract {
}
@Test
+ def setShouldFailWhenOnSuccessDestroyEmailMissesTheCreationIdSharp(server: GuiceJamesServer): Unit = {
+ val message: Message = Message.Builder
+ .of
+ .setSubject("test")
+ .setSender(BOB.asString)
+ .setFrom(BOB.asString)
+ .setTo(ANDRE.asString)
+ .setBody("testmail", StandardCharsets.UTF_8)
+ .build
+
+ val bobDraftsPath = MailboxPath.forUser(BOB, DefaultMailboxes.DRAFTS)
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobDraftsPath)
+ val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl]).appendMessage(BOB.asString(), bobDraftsPath, AppendCommand.builder()
+ .build(message))
+ .getMessageId
+
+ val requestBob =
+ s"""{
+ | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:submission"],
+ | "methodCalls": [
+ | ["EmailSubmission/set", {
+ | "accountId": "$ACCOUNT_ID",
+ | "create": {
+ | "k1490": {
+ | "emailId": "${messageId.serialize}",
+ | "envelope": {
+ | "mailFrom": {"email": "${BOB.asString}"},
+ | "rcptTo": [{"email": "${ANDRE.asString}"}]
+ | }
+ | }
+ | },
+ | "onSuccessDestroyEmail": ["notFound"]
+ | }, "c1"]]
+ |}""".stripMargin
+
+ val response = `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(requestBob)
+ .when
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .isEqualTo(s"""{
+ | "sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+ | "methodResponses": [
+ | [
+ | "error",
+ | {
+ | "type": "invalidArguments",
+ | "description": "notFound cannot be retrieved as storage for EmailSubmission is not yet implemented"
+ | },
+ | "c1"
+ | ]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
+ def setShouldFailWhenOnSuccessDestroyEmailDoesNotReferenceACreationWithinThisCall(server: GuiceJamesServer): Unit = {
+ val message: Message = Message.Builder
+ .of
+ .setSubject("test")
+ .setSender(BOB.asString)
+ .setFrom(BOB.asString)
+ .setTo(ANDRE.asString)
+ .setBody("testmail", StandardCharsets.UTF_8)
+ .build
+
+ val bobDraftsPath = MailboxPath.forUser(BOB, DefaultMailboxes.DRAFTS)
+ server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobDraftsPath)
+ val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl]).appendMessage(BOB.asString(), bobDraftsPath, AppendCommand.builder()
+ .build(message))
+ .getMessageId
+
+ val requestBob =
+ s"""{
+ | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:submission"],
+ | "methodCalls": [
+ | ["EmailSubmission/set", {
+ | "accountId": "$ACCOUNT_ID",
+ | "create": {
+ | "k1490": {
+ | "emailId": "${messageId.serialize}",
+ | "envelope": {
+ | "mailFrom": {"email": "${BOB.asString}"},
+ | "rcptTo": [{"email": "${ANDRE.asString}"}]
+ | }
+ | }
+ | },
+ | "onSuccessDestroyEmail": ["#notFound"]
+ | }, "c1"]]
+ |}""".stripMargin
+
+ val response = `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(requestBob)
+ .when
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response)
+ .isEqualTo(s"""{
+ | "sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+ | "methodResponses": [
+ | [
+ | "error",
+ | {
+ | "type": "invalidArguments",
+ | "description": "#notFound cannot be referenced in current method call"
+ | },
+ | "c1"
+ | ]
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
def setShouldRejectOtherAccountIds(server: GuiceJamesServer): Unit = {
val message: Message = Message.Builder
.of
diff --git a/server/protocols/jmap-rfc-8621/doc/specs/spec/mail/messagesubmission.mdown b/server/protocols/jmap-rfc-8621/doc/specs/spec/mail/messagesubmission.mdown
index 0777f32..0a45fd1 100644
--- a/server/protocols/jmap-rfc-8621/doc/specs/spec/mail/messagesubmission.mdown
+++ b/server/protocols/jmap-rfc-8621/doc/specs/spec/mail/messagesubmission.mdown
@@ -211,14 +211,14 @@ This is a standard "/set" method as described in [@!RFC8620], Section 5.3 with t
- **onSuccessUpdateEmail**: `Id[PatchObject]|null`
A map of *EmailSubmission id* to an object containing properties to update on the Email object referenced by the EmailSubmission if the create/update/destroy succeeds. (For references to EmailSubmissions created in the same "/set" invocation, this is equivalent to a creation-reference, so the id will be the creation id prefixed with a `#`.)
-> :information_source:
-> Implemented
+> :warning:
+> Partially implemented: Because we do not have storage for email submission, references across method calls, and API requests will not be supported.
- **onSuccessDestroyEmail**: `Id[]|null`
A list of *EmailSubmission ids* for which the Email with the corresponding emailId should be destroyed if the create/update/destroy succeeds. (For references to EmailSubmission creations, this is equivalent to a creation-reference so the id will be the creation id prefixed with a `#`.)
-> :information_source:
-> Implemented
+> :warning:
+> Partially implemented: Because we do not have storage for email submission, references across method calls, and API requests will not be supported.
After all create/update/destroy items in the *EmailSubmission/set* invocation have been processed, a single implicit *Email/set* call MUST be made to perform any changes requested in these two arguments. The response to this MUST be returned after the *EmailSubmission/set* response.
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSubmissionSet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSubmissionSet.scala
index e0944fc..0e1da02 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSubmissionSet.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSubmissionSet.scala
@@ -21,11 +21,13 @@ package org.apache.james.jmap.mail
import java.util.UUID
+import cats.implicits._
+import eu.timepit.refined.auto.autoUnwrap
import eu.timepit.refined.collection.NonEmpty
import eu.timepit.refined.refineV
import eu.timepit.refined.types.string.NonEmptyString
import org.apache.james.core.MailAddress
-import org.apache.james.jmap.core.Id.Id
+import org.apache.james.jmap.core.Id.{Id, IdConstraint}
import org.apache.james.jmap.core.SetError.SetErrorDescription
import org.apache.james.jmap.core.{AccountId, Id, Properties, SetError, State}
import org.apache.james.jmap.mail.EmailSet.UnparsedMessageId
@@ -44,13 +46,72 @@ object EmailSubmissionId {
case class EmailSubmissionSetRequest(accountId: AccountId,
create: Option[Map[EmailSubmissionCreationId, JsObject]],
- onSuccessUpdateEmail: Option[Map[UnparsedMessageId, JsObject]],
- onSuccessDestroyEmail: Option[DestroyIds]) extends WithAccountId {
- def implicitEmailSetRequest: EmailSetRequest = EmailSetRequest(
- accountId = accountId,
- create = None,
- update = onSuccessUpdateEmail,
- destroy = onSuccessDestroyEmail)
+ onSuccessUpdateEmail: Option[Map[EmailSubmissionCreationId, JsObject]],
+ onSuccessDestroyEmail: Option[List[EmailSubmissionCreationId]]) extends WithAccountId {
+ def implicitEmailSetRequest(messageIdResolver: EmailSubmissionCreationId => Either[IllegalArgumentException, MessageId]): Either[IllegalArgumentException, EmailSetRequest] =
+ for {
+ update <- resolveOnSuccessUpdateEmail(messageIdResolver)
+ destroy <- resolveOnSuccessDestroyEmail(messageIdResolver)
+ } yield {
+ EmailSetRequest(
+ accountId = accountId,
+ create = None,
+ update = update,
+ destroy = destroy.map(DestroyIds(_)))
+ }
+
+ def resolveOnSuccessUpdateEmail(messageIdResolver: EmailSubmissionCreationId => Either[IllegalArgumentException, MessageId]): Either[IllegalArgumentException, Option[Map[UnparsedMessageId, JsObject]]]=
+ onSuccessUpdateEmail.map(map => map.toList
+ .map {
+ case (creationId, json) => messageIdResolver.apply(creationId).map(messageId => (EmailSet.asUnparsed(messageId), json))
+ }
+ .sequence
+ .map(list => list.toMap))
+ .sequence
+
+ def resolveOnSuccessDestroyEmail(messageIdResolver: EmailSubmissionCreationId => Either[IllegalArgumentException, MessageId]): Either[IllegalArgumentException, Option[List[UnparsedMessageId]]]=
+ onSuccessDestroyEmail.map(list => list
+ .map(creationId => messageIdResolver.apply(creationId).map(messageId => EmailSet.asUnparsed(messageId)))
+ .sequence)
+ .sequence
+
+ def validate: Either[IllegalArgumentException, EmailSubmissionSetRequest] = {
+ val supportedCreationIds: List[EmailSubmissionCreationId] = create.getOrElse(Map()).keys.toList
+
+ validateOnSuccessUpdateEmail(supportedCreationIds)
+ .flatMap(_ => validateOnSuccessDestroyEmail(supportedCreationIds))
+ }
+
+ private def validateOnSuccessDestroyEmail(supportedCreationIds: List[EmailSubmissionCreationId]) : Either[IllegalArgumentException, EmailSubmissionSetRequest] =
+ onSuccessDestroyEmail.getOrElse(List())
+ .map(id => validate(id, supportedCreationIds))
+ .sequence
+ .map(_ => this)
+
+
+ private def validateOnSuccessUpdateEmail(supportedCreationIds: List[EmailSubmissionCreationId]) : Either[IllegalArgumentException, EmailSubmissionSetRequest] =
+ onSuccessUpdateEmail.getOrElse(Map())
+ .keys
+ .toList
+ .map(id => validate(id, supportedCreationIds))
+ .sequence
+ .map(_ => this)
+
+ private def validate(creationId: EmailSubmissionCreationId, supportedCreationIds: List[EmailSubmissionCreationId]): Either[IllegalArgumentException, EmailSubmissionCreationId] = {
+ if (creationId.startsWith("#")) {
+ val realId = creationId.substring(1)
+ val validatedId: Either[String, EmailSubmissionCreationId] = refineV[IdConstraint](realId)
+ validatedId
+ .left.map(s => new IllegalArgumentException(s))
+ .flatMap(id => if (supportedCreationIds.contains(id)) {
+ scala.Right(id)
+ } else {
+ Left(new IllegalArgumentException(s"$creationId cannot be referenced in current method call"))
+ })
+ } else {
+ Left(new IllegalArgumentException(s"$creationId cannot be retrieved as storage for EmailSubmission is not yet implemented"))
+ }
+ }
}
case class EmailSubmissionSetResponse(accountId: AccountId,
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSubmissionSetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSubmissionSetMethod.scala
index 544f13b..4c7f82e 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSubmissionSetMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSubmissionSetMethod.scala
@@ -23,6 +23,7 @@ import java.io.InputStream
import cats.implicits._
import eu.timepit.refined.auto._
+import eu.timepit.refined.refineV
import javax.annotation.PreDestroy
import javax.inject.Inject
import javax.mail.Address
@@ -30,6 +31,7 @@ import javax.mail.Message.RecipientType
import javax.mail.internet.{InternetAddress, MimeMessage}
import org.apache.james.core.{MailAddress, Username}
import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, EMAIL_SUBMISSION, JMAP_CORE}
+import org.apache.james.jmap.core.Id.IdConstraint
import org.apache.james.jmap.core.Invocation.{Arguments, MethodName}
import org.apache.james.jmap.core.SetError.{SetErrorDescription, SetErrorType}
import org.apache.james.jmap.core.{ClientId, Id, Invocation, Properties, ServerId, SetError, State}
@@ -39,7 +41,7 @@ import org.apache.james.jmap.mail.{EmailSubmissionAddress, EmailSubmissionCreati
import org.apache.james.jmap.method.EmailSubmissionSetMethod.{LOGGER, MAIL_METADATA_USERNAME_ATTRIBUTE}
import org.apache.james.jmap.routes.{ProcessingContext, SessionSupplier}
import org.apache.james.lifecycle.api.{LifecycleUtil, Startable}
-import org.apache.james.mailbox.model.{FetchGroup, MessageResult}
+import org.apache.james.mailbox.model.{FetchGroup, MessageId, MessageResult}
import org.apache.james.mailbox.{MailboxSession, MessageIdManager}
import org.apache.james.metrics.api.MetricFactory
import org.apache.james.queue.api.MailQueueFactory.SPOOL
@@ -47,7 +49,6 @@ import org.apache.james.queue.api.{MailQueue, MailQueueFactory}
import org.apache.james.rrt.api.CanSendFrom
import org.apache.james.server.core.{MailImpl, MimeMessageSource, MimeMessageWrapper}
import org.apache.mailet.{Attribute, AttributeName, AttributeValue}
-import org.reactivestreams.Publisher
import org.slf4j.{Logger, LoggerFactory}
import play.api.libs.json._
import reactor.core.scala.publisher.{SFlux, SMono}
@@ -91,7 +92,9 @@ class EmailSubmissionSetMethod @Inject()(serializer: EmailSubmissionSetSerialize
sealed trait CreationResult {
def emailSubmissionCreationId: EmailSubmissionCreationId
}
- case class CreationSuccess(emailSubmissionCreationId: EmailSubmissionCreationId, emailSubmissionCreationResponse: EmailSubmissionCreationResponse) extends CreationResult
+ case class CreationSuccess(emailSubmissionCreationId: EmailSubmissionCreationId,
+ emailSubmissionCreationResponse: EmailSubmissionCreationResponse,
+ messageId: MessageId) extends CreationResult
case class CreationFailure(emailSubmissionCreationId: EmailSubmissionCreationId, exception: Throwable) extends CreationResult {
def asSetError: SetError = exception match {
case e: EmailSubmissionCreationParseException => e.setError
@@ -126,6 +129,29 @@ class EmailSubmissionSetMethod @Inject()(serializer: EmailSubmissionSetSerialize
case _ => None
}
.toMap
+
+ def resolveMessageId(creationId: EmailSubmissionCreationId): Either[IllegalArgumentException, MessageId] = {
+ if (creationId.startsWith("#")) {
+ val realId = creationId.substring(1)
+ val validatedId: Either[String, EmailSubmissionCreationId] = refineV[IdConstraint](realId)
+ validatedId
+ .left.map(s => new IllegalArgumentException(s))
+ .flatMap(id => retrieveMessageId(id)
+ .map(scala.Right(_))
+ .getOrElse(Left(new IllegalArgumentException(s"$creationId cannot be referenced in current method call"))))
+ } else {
+ Left(new IllegalArgumentException(s"$creationId cannot be retrieved as storage for EmailSubmission is not yet implemented"))
+ }
+ }
+
+ private def retrieveMessageId(creationId: EmailSubmissionCreationId): Option[MessageId] =
+ created.flatMap {
+ case success: CreationSuccess => Some(success)
+ case _: CreationFailure => None
+ }.filter(_.emailSubmissionCreationId.equals(creationId))
+ .map(_.messageId)
+ .toList
+ .headOption
}
def init: Unit =
@@ -135,34 +161,40 @@ class EmailSubmissionSetMethod @Inject()(serializer: EmailSubmissionSetSerialize
Try(queue.close())
.recover(e => LOGGER.debug("error closing queue", e))
- override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: EmailSubmissionSetRequest): Publisher[InvocationWithContext] = {
- val result = for {
- createdResults <- create(request, mailboxSession, invocation.processingContext)
- } yield InvocationWithContext(
- invocation = Invocation(
- methodName = invocation.invocation.methodName,
- arguments = Arguments(serializer.serializeEmailSubmissionSetResponse(EmailSubmissionSetResponse(
- accountId = request.accountId,
- newState = State.INSTANCE,
- created = Some(createdResults._1.retrieveCreated).filter(_.nonEmpty),
- notCreated = Some(createdResults._1.retrieveErrors).filter(_.nonEmpty)))
- .as[JsObject]),
- methodCallId = invocation.invocation.methodCallId),
- processingContext = createdResults._2)
-
- SFlux.concat(result,
- emailSetMethod.doProcess(
- capabilities = capabilities,
- invocation = invocation,
- mailboxSession = mailboxSession,
- request = request.implicitEmailSetRequest))
- }
+ override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: EmailSubmissionSetRequest): SFlux[InvocationWithContext] =
+ create(request, mailboxSession, invocation.processingContext)
+ .flatMapMany(createdResults => {
+ val explicitInvocation: InvocationWithContext = InvocationWithContext(
+ invocation = Invocation(
+ methodName = invocation.invocation.methodName,
+ arguments = Arguments(serializer.serializeEmailSubmissionSetResponse(EmailSubmissionSetResponse(
+ accountId = request.accountId,
+ newState = State.INSTANCE,
+ created = Some(createdResults._1.retrieveCreated).filter(_.nonEmpty),
+ notCreated = Some(createdResults._1.retrieveErrors).filter(_.nonEmpty)))
+ .as[JsObject]),
+ methodCallId = invocation.invocation.methodCallId),
+ processingContext = createdResults._2)
+
- override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[IllegalArgumentException, EmailSubmissionSetRequest] =
- serializer.deserializeEmailSubmissionSetRequest(invocation.arguments.value) match {
+ val emailSetCall: SMono[InvocationWithContext] = request.implicitEmailSetRequest(createdResults._1.resolveMessageId)
+ .fold(e => SMono.error(e),
+ emailSetRequest => emailSetMethod.doProcess(
+ capabilities = capabilities,
+ invocation = invocation,
+ mailboxSession = mailboxSession,
+ request = emailSetRequest))
+
+ SFlux.concat(SMono.just(explicitInvocation), emailSetCall)
+ })
+
+ override def getRequest(mailboxSession: MailboxSession, invocation: Invocation): Either[IllegalArgumentException, EmailSubmissionSetRequest] = {
+ val maybeRequestRequest = serializer.deserializeEmailSubmissionSetRequest(invocation.arguments.value) match {
case JsSuccess(emailSubmissionSetRequest, _) => Right(emailSubmissionSetRequest)
case errors: JsError => Left(new IllegalArgumentException(ResponseSerializer.serialize(errors).toString))
}
+ maybeRequestRequest.flatMap(_.validate)
+ }
private def create(request: EmailSubmissionSetRequest,
session: MailboxSession,
@@ -185,12 +217,13 @@ class EmailSubmissionSetMethod @Inject()(serializer: EmailSubmissionSetSerialize
processingContext: ProcessingContext): (CreationResult, ProcessingContext) =
parseCreate(jsObject)
.flatMap(emailSubmissionCreationRequest => sendEmail(mailboxSession, emailSubmissionCreationRequest))
- .flatMap(creationResponse => recordCreationIdInProcessingContext(emailSubmissionCreationId, processingContext, creationResponse.id)
- .map(context => (creationResponse, context)))
+ .flatMap {
+ case (creationResponse, messageId) =>
+ recordCreationIdInProcessingContext(emailSubmissionCreationId, processingContext, creationResponse.id)
+ .map(context => (creationResponse, messageId, context))
+ }
.fold(e => (CreationFailure(emailSubmissionCreationId, e), processingContext),
- creationResponseWithUpdatedContext => {
- (CreationSuccess(emailSubmissionCreationId, creationResponseWithUpdatedContext._1), creationResponseWithUpdatedContext._2)
- })
+ creation => CreationSuccess(emailSubmissionCreationId, creation._1, creation._2) -> creation._3)
private def parseCreate(jsObject: JsObject): Either[EmailSubmissionCreationParseException, EmailSubmissionCreationRequest] =
EmailSubmissionCreationRequest.validateProperties(jsObject)
@@ -208,7 +241,7 @@ class EmailSubmissionSetMethod @Inject()(serializer: EmailSubmissionSetSerialize
}
private def sendEmail(mailboxSession: MailboxSession,
- request: EmailSubmissionCreationRequest): Either[Throwable, EmailSubmissionCreationResponse] = {
+ request: EmailSubmissionCreationRequest): Either[Throwable, (EmailSubmissionCreationResponse, MessageId)] = {
val message: Either[Exception, MessageResult] = messageIdManager.getMessage(request.emailId, FetchGroup.FULL_CONTENT, mailboxSession)
.asScala
.toList
@@ -236,7 +269,7 @@ class EmailSubmissionSetMethod @Inject()(serializer: EmailSubmissionSetSerialize
} yield {
EmailSubmissionCreationResponse(submissionId)
}
- result.toEither
+ result.toEither.map(response => (response, request.emailId))
})
}
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org