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/07 04:52:32 UTC
[james-project] 01/03: JAMES-3404 Implicitly handle client id
resolution with JSON substitution
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 08e3d1dec0f1ae74b3621d3aea9b174db1661596
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Oct 6 11:28:34 2020 +0700
JAMES-3404 Implicitly handle client id resolution with JSON substitution
This empowers implicit handling, making it easier to implement client Id resolution
---
.../contract/MailboxSetMethodContract.scala | 10 ++---
.../apache/james/jmap/json/MailboxSerializer.scala | 3 +-
.../james/jmap/json/VacationSerializer.scala | 14 ++++---
.../org/apache/james/jmap/mail/MailboxGet.scala | 28 ++++++++++++-
.../org/apache/james/jmap/mail/MailboxSet.scala | 30 +++++---------
.../apache/james/jmap/mail/VacationResponse.scala | 1 +
.../james/jmap/method/MailboxGetMethod.scala | 15 ++++---
.../james/jmap/method/MailboxSetMethod.scala | 46 ++++++++++++----------
.../jmap/method/VacationResponseGetMethod.scala | 13 +++---
.../james/jmap/routes/ProcessingContext.scala | 45 +++++++++------------
.../jmap/json/MailboxGetSerializationTest.scala | 18 +--------
.../VacationResponseGetSerializationTest.scala | 3 --
12 files changed, 109 insertions(+), 117 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/MailboxSetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxSetMethodContract.scala
index 413aadb..c81aef5 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxSetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/MailboxSetMethodContract.scala
@@ -2807,7 +2807,7 @@ trait MailboxSetMethodContract {
| "notDestroyed": {
| "#C42": {
| "type": "invalidArguments",
- | "description": "#C42 is not a mailboxId: ClientId(#C42) was not used in previously defined creationIds"
+ | "description": "#C42 is not a mailboxId: #C42 was not used in previously defined creationIds"
| }
| }
| }, "c2"],
@@ -2881,7 +2881,7 @@ trait MailboxSetMethodContract {
| "notDestroyed": {
| "#C42": {
| "type": "invalidArguments",
- | "description": "#C42 is not a mailboxId: ClientId(#C42) was not used in previously defined creationIds"
+ | "description": "#C42 is not a mailboxId: #C42 was not used in previously defined creationIds"
| }
| }
| }, "c2"]
@@ -2919,7 +2919,7 @@ trait MailboxSetMethodContract {
.body
.asString
- val message = "# is not a mailboxId: Left predicate of ((!(0 < 1) && !(0 > 255)) && \\\"\\\".matches(\\\"^[#a-zA-Z0-9-_]*$\\\")) failed: Predicate taking size() = 0 failed: Left predicate of (!(0 < 1) && !(0 > 255)) failed: Predicate (0 < 1) did not fail."
+ val message = "# is not a mailboxId: # was not used in previously defined creationIds"
assertThatJson(response).isEqualTo(
s"""{
| "sessionState": "75128aab4b1b",
@@ -6728,7 +6728,7 @@ trait MailboxSetMethodContract {
| "notUpdated": {
| "${mailboxId.serialize}": {
| "type": "invalidArguments",
- | "description": "ClientId(#C42) was not used in previously defined creationIds",
+ | "description": "#C42 was not used in previously defined creationIds",
| "properties": ["parentId"]
| }
| }
@@ -7357,7 +7357,7 @@ trait MailboxSetMethodContract {
| "notUpdated": {
| "#invalid": {
| "type": "invalidArguments",
- | "description": "ClientId(#invalid) was not used in previously defined creationIds"
+ | "description": "#invalid was not used in previously defined creationIds"
| }
| }
| },
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/MailboxSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/MailboxSerializer.scala
index d8e4467..edb6d11 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/MailboxSerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/MailboxSerializer.scala
@@ -23,7 +23,8 @@ import eu.timepit.refined._
import eu.timepit.refined.collection.NonEmpty
import javax.inject.Inject
import org.apache.james.core.{Domain, Username}
-import org.apache.james.jmap.mail.MailboxSetRequest.{MailboxCreationId, UnparsedMailboxId, UnparsedMailboxIdConstraint}
+import org.apache.james.jmap.mail.MailboxGet.{UnparsedMailboxId, UnparsedMailboxIdConstraint}
+import org.apache.james.jmap.mail.MailboxSetRequest.MailboxCreationId
import org.apache.james.jmap.mail.{DelegatedNamespace, Ids, IsSubscribed, Mailbox, MailboxCreationRequest, MailboxCreationResponse, MailboxGetRequest, MailboxGetResponse, MailboxNamespace, MailboxPatchObject, MailboxRights, MailboxSetRequest, MailboxSetResponse, MailboxUpdateResponse, MayAddItems, MayCreateChild, MayDelete, MayReadItems, MayRemoveItems, MayRename, MaySetKeywords, MaySetSeen, MaySubmit, NotFound, PersonalNamespace, Quota, QuotaId, QuotaRoot, Quotas, RemoveEmailsOnDestroy, [...]
import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
import org.apache.james.jmap.model._
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/VacationSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/VacationSerializer.scala
index 70f61b6..38c830c 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/VacationSerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/VacationSerializer.scala
@@ -21,9 +21,8 @@ package org.apache.james.jmap.json
import java.time.format.DateTimeFormatter
-import org.apache.james.jmap.mail.MailboxSetRequest.UnparsedMailboxId
import org.apache.james.jmap.mail.VacationResponse.{UnparsedVacationResponseId, VACATION_RESPONSE_ID}
-import org.apache.james.jmap.mail.{FromDate, HtmlBody, IsEnabled, NotFound, Subject, TextBody, ToDate, VacationResponse, VacationResponseGetRequest, VacationResponseGetResponse, VacationResponseId, VacationResponseIds, VacationResponseNotFound}
+import org.apache.james.jmap.mail.{FromDate, HtmlBody, IsEnabled, Subject, TextBody, ToDate, VacationResponse, VacationResponseGetRequest, VacationResponseGetResponse, VacationResponseId, VacationResponseIds, VacationResponseNotFound}
import org.apache.james.jmap.model._
import org.apache.james.jmap.vacation.{VacationResponsePatchObject, VacationResponseSetError, VacationResponseSetRequest, VacationResponseSetResponse, VacationResponseUpdateResponse}
import play.api.libs.json._
@@ -38,9 +37,6 @@ object VacationSerializer {
}
private implicit val vacationResponseSetRequestReads: Reads[VacationResponseSetRequest] = Json.reads[VacationResponseSetRequest]
- private implicit def notFoundWrites(implicit mailboxIdWrites: Writes[UnparsedMailboxId]): Writes[NotFound] =
- notFound => JsArray(notFound.value.toList.map(mailboxIdWrites.writes))
-
private implicit val vacationResponseSetUpdateResponseWrites: Writes[VacationResponseUpdateResponse] = Json.valueWrites[VacationResponseUpdateResponse]
private implicit val vacationResponseSetErrorWrites: Writes[VacationResponseSetError] = Json.writes[VacationResponseSetError]
@@ -51,6 +47,11 @@ object VacationSerializer {
utcDate => JsString(utcDate.asUTC.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssX")))
private implicit val vacationResponseIdWrites: Writes[VacationResponseId] = _ => JsString(VACATION_RESPONSE_ID.value)
+ private implicit val vacationResponseIdReads: Reads[VacationResponseId] = {
+ case JsString("singleton") => JsSuccess(VacationResponseId())
+ case JsString(_) => JsError("Only singleton is supported as a VacationResponseId")
+ case _ => JsError("Expecting JsString(singleton) to represent a VacationResponseId")
+ }
private implicit val isEnabledWrites: Writes[IsEnabled] = Json.valueWrites[IsEnabled]
private implicit val fromDateWrites: Writes[FromDate] = Json.valueWrites[FromDate]
private implicit val toDateWrites: Writes[ToDate] = Json.valueWrites[ToDate]
@@ -61,7 +62,8 @@ object VacationSerializer {
implicit def vacationResponseWrites(properties: Properties): Writes[VacationResponse] = Json.writes[VacationResponse]
.transform(properties.filter(_))
- private implicit val vacationResponseIdReads: Reads[VacationResponseIds] = Json.valueReads[VacationResponseIds]
+ private implicit val vacationResponseIdsReads: Reads[VacationResponseIds] = Json.valueReads[VacationResponseIds]
+
private implicit val vacationResponseGetRequest: Reads[VacationResponseGetRequest] = Json.reads[VacationResponseGetRequest]
private implicit def vacationResponseNotFoundWrites(implicit idWrites: Writes[UnparsedVacationResponseId]): Writes[VacationResponseNotFound] =
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxGet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxGet.scala
index b230a77..bd7b8ba 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxGet.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxGet.scala
@@ -19,10 +19,36 @@
package org.apache.james.jmap.mail
-import org.apache.james.jmap.mail.MailboxSetRequest.UnparsedMailboxId
+import eu.timepit.refined
+import eu.timepit.refined.api.Refined
+import eu.timepit.refined.collection.NonEmpty
+import org.apache.james.jmap.mail.MailboxGet.UnparsedMailboxId
import org.apache.james.jmap.method.WithAccountId
import org.apache.james.jmap.model.State.State
import org.apache.james.jmap.model.{AccountId, Properties}
+import org.apache.james.mailbox.model.MailboxId
+
+import scala.util.{Failure, Try}
+
+object MailboxGet {
+ type UnparsedMailboxIdConstraint = NonEmpty
+ type UnparsedMailboxId = String Refined UnparsedMailboxIdConstraint
+
+ def asUnparsed(mailboxId: MailboxId): UnparsedMailboxId = refined.refineV[UnparsedMailboxIdConstraint](mailboxId.serialize()) match {
+ case Left(e) => throw new IllegalArgumentException(e)
+ case scala.Right(value) => value
+ }
+
+ def parse(mailboxIdFactory: MailboxId.Factory)(unparsed: UnparsedMailboxId): Try[MailboxId] =
+ parseString(mailboxIdFactory)(unparsed.value)
+
+ def parseString(mailboxIdFactory: MailboxId.Factory)(unparsed: String): Try[MailboxId] =
+ unparsed match {
+ case a if a.startsWith("#") =>
+ Failure(new IllegalArgumentException(s"$unparsed was not used in previously defined creationIds"))
+ case _ => Try(mailboxIdFactory.fromString(unparsed))
+ }
+}
case class Ids(value: List[UnparsedMailboxId])
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxSet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxSet.scala
index cbd034e..20801bd 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxSet.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/MailboxSet.scala
@@ -19,7 +19,6 @@
package org.apache.james.jmap.mail
-import eu.timepit.refined
import eu.timepit.refined.api.Refined
import eu.timepit.refined.auto._
import eu.timepit.refined.collection.NonEmpty
@@ -27,15 +26,15 @@ import eu.timepit.refined.refineV
import eu.timepit.refined.types.string.NonEmptyString
import org.apache.james.core.Username
import org.apache.james.jmap.json.MailboxSerializer
+import org.apache.james.jmap.mail.MailboxGet.UnparsedMailboxId
import org.apache.james.jmap.mail.MailboxName.MailboxName
import org.apache.james.jmap.mail.MailboxPatchObject.MailboxPatchObjectKey
-import org.apache.james.jmap.mail.MailboxSetRequest.{MailboxCreationId, UnparsedMailboxId, UnparsedMailboxIdConstraint}
+import org.apache.james.jmap.mail.MailboxSetRequest.MailboxCreationId
import org.apache.james.jmap.method.{MailboxCreationParseException, WithAccountId}
import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
import org.apache.james.jmap.model.SetError.{SetErrorDescription, SetErrorType}
import org.apache.james.jmap.model.State.State
import org.apache.james.jmap.model.{AccountId, CapabilityIdentifier, Properties, SetError}
-import org.apache.james.jmap.routes.ProcessingContext
import org.apache.james.mailbox.model.{MailboxId, MailboxACL => JavaMailboxACL}
import org.apache.james.mailbox.{MailboxSession, Role}
import play.api.libs.json.{JsBoolean, JsError, JsNull, JsObject, JsString, JsSuccess, JsValue}
@@ -48,14 +47,7 @@ case class MailboxSetRequest(accountId: AccountId,
onDestroyRemoveEmails: Option[RemoveEmailsOnDestroy]) extends WithAccountId
object MailboxSetRequest {
- type UnparsedMailboxIdConstraint = NonEmpty
type MailboxCreationId = String Refined NonEmpty
- type UnparsedMailboxId = String Refined UnparsedMailboxIdConstraint
-
- def asUnparsed(mailboxId: MailboxId): UnparsedMailboxId = refined.refineV[UnparsedMailboxIdConstraint](mailboxId.serialize()) match {
- case Left(e) => throw new IllegalArgumentException(e)
- case scala.Right(value) => value
- }
}
case class RemoveEmailsOnDestroy(value: Boolean) extends AnyVal
@@ -114,12 +106,11 @@ object MailboxPatchObject {
}
case class MailboxPatchObject(value: Map[String, JsValue]) {
- def validate(processingContext: ProcessingContext,
- mailboxIdFactory: MailboxId.Factory,
+ def validate(mailboxIdFactory: MailboxId.Factory,
serializer: MailboxSerializer,
capabilities: Set[CapabilityIdentifier],
mailboxSession: MailboxSession): Either[PatchUpdateValidationException, ValidatedMailboxPatchObject] = {
- val asUpdatedIterable = updates(serializer, capabilities, processingContext, mailboxIdFactory, mailboxSession)
+ val asUpdatedIterable = updates(serializer, capabilities, mailboxIdFactory, mailboxSession)
val maybeParseException: Option[PatchUpdateValidationException] = asUpdatedIterable
.flatMap(x => x match {
@@ -174,12 +165,11 @@ case class MailboxPatchObject(value: Map[String, JsValue]) {
def updates(serializer: MailboxSerializer,
capabilities: Set[CapabilityIdentifier],
- processingContext: ProcessingContext,
mailboxIdFactory: MailboxId.Factory,
mailboxSession: MailboxSession): Iterable[Either[PatchUpdateValidationException, Update]] = value.map({
case (property, newValue) => property match {
case "name" => NameUpdate.parse(newValue, mailboxSession)
- case "parentId" => ParentIdUpdate.parse(newValue, processingContext, mailboxIdFactory)
+ case "parentId" => ParentIdUpdate.parse(newValue, mailboxIdFactory)
case "sharedWith" => SharedWithResetUpdate.parse(serializer, capabilities)(newValue)
case "role" => Left(ServerSetPropertyException(MailboxPatchObject.roleProperty))
case "sortOrder" => Left(ServerSetPropertyException(MailboxPatchObject.sortOrderProperty))
@@ -331,15 +321,13 @@ object SharedWithPartialUpdate {
}
object ParentIdUpdate {
- def parse(newValue: JsValue, processingContext: ProcessingContext, mailboxIdFactory: MailboxId.Factory):
+ def parse(newValue: JsValue, mailboxIdFactory: MailboxId.Factory):
Either[PatchUpdateValidationException, Update] =
(newValue match {
case JsString(id) =>
- for {
- unparsedMailboxId <- refineV[UnparsedMailboxIdConstraint](id)
- mailboxId <- processingContext.resolveMailboxId(unparsedMailboxId, mailboxIdFactory).left.map(_.getMessage)
- } yield changeParentTo(mailboxId)
-
+ MailboxGet.parseString(mailboxIdFactory)(id)
+ .fold(e => Left(e.getMessage),
+ mailboxId => scala.Right(changeParentTo(mailboxId)))
case JsNull => scala.Right(removeParent())
case _ => Left("Expecting a JSON string or null as an argument")
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/VacationResponse.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/VacationResponse.scala
index db02ec3..d25c603 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/VacationResponse.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/VacationResponse.scala
@@ -38,6 +38,7 @@ case class HtmlBody(value: String)
object VacationResponse {
val VACATION_RESPONSE_ID: Id = "singleton"
+ val UNPARSED_SINGLETON: UnparsedVacationResponseId = "singleton"
type UnparsedVacationResponseId = String Refined NonEmpty
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxGetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxGetMethod.scala
index 15c01c9..154da66 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxGetMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxGetMethod.scala
@@ -23,14 +23,13 @@ import eu.timepit.refined.auto._
import javax.inject.Inject
import org.apache.james.jmap.http.SessionSupplier
import org.apache.james.jmap.json.{MailboxSerializer, ResponseSerializer}
-import org.apache.james.jmap.mail.MailboxSetRequest.UnparsedMailboxId
+import org.apache.james.jmap.mail.MailboxGet.UnparsedMailboxId
import org.apache.james.jmap.mail._
import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
import org.apache.james.jmap.model.DefaultCapabilities.{CORE_CAPABILITY, MAIL_CAPABILITY}
import org.apache.james.jmap.model.Invocation.{Arguments, MethodName}
import org.apache.james.jmap.model.State.INSTANCE
import org.apache.james.jmap.model.{AccountId, Capabilities, CapabilityIdentifier, ErrorCode, Invocation, MailboxFactory, Properties, Subscriptions}
-import org.apache.james.jmap.routes.ProcessingContext
import org.apache.james.jmap.utils.quotas.{QuotaLoader, QuotaLoaderWithPreloadedDefaultFactory}
import org.apache.james.mailbox.exception.MailboxNotFoundException
import org.apache.james.mailbox.model.search.MailboxQuery
@@ -42,6 +41,7 @@ import reactor.core.scala.publisher.{SFlux, SMono}
import reactor.core.scheduler.Schedulers
import scala.jdk.CollectionConverters._
+import scala.util.Try
class MailboxGetMethod @Inject() (serializer: MailboxSerializer,
mailboxManager: MailboxManager,
@@ -59,7 +59,7 @@ class MailboxGetMethod @Inject() (serializer: MailboxSerializer,
def empty(): MailboxGetResults = MailboxGetResults(Set.empty, NotFound(Set.empty))
def found(mailbox: Mailbox): MailboxGetResults = MailboxGetResults(Set(mailbox), NotFound(Set.empty))
def notFound(mailboxId: UnparsedMailboxId): MailboxGetResults = MailboxGetResults(Set.empty, NotFound(Set(mailboxId)))
- def notFound(mailboxId: MailboxId): MailboxGetResults = MailboxGetResults(Set.empty, NotFound(Set(MailboxSetRequest.asUnparsed(mailboxId))))
+ def notFound(mailboxId: MailboxId): MailboxGetResults = MailboxGetResults(Set.empty, NotFound(Set(MailboxGet.asUnparsed(mailboxId))))
}
case class MailboxGetResults(mailboxes: Set[Mailbox], notFound: NotFound) {
@@ -75,7 +75,7 @@ class MailboxGetMethod @Inject() (serializer: MailboxSerializer,
override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: MailboxGetRequest): SMono[InvocationWithContext] = {
val requestedProperties: Properties = request.properties.getOrElse(Mailbox.allProperties)
(requestedProperties -- Mailbox.allProperties match {
- case invalidProperties if invalidProperties.isEmpty() => getMailboxes(capabilities, request, invocation.processingContext, mailboxSession)
+ case invalidProperties if invalidProperties.isEmpty() => getMailboxes(capabilities, request, mailboxSession)
.reduce(MailboxGetResults.empty(), MailboxGetResults.merge)
.map(mailboxes => mailboxes.asResponse(request.accountId))
.map(mailboxGetResponse => Invocation(
@@ -101,16 +101,15 @@ class MailboxGetMethod @Inject() (serializer: MailboxSerializer,
private def getMailboxes(capabilities: Set[CapabilityIdentifier],
mailboxGetRequest: MailboxGetRequest,
- processingContext: ProcessingContext,
mailboxSession: MailboxSession): SFlux[MailboxGetResults] =
mailboxGetRequest.ids match {
case None => getAllMailboxes(capabilities, mailboxSession)
.map(MailboxGetResults.found)
case Some(ids) => SFlux.fromIterable(ids.value)
- .flatMap(id => processingContext.resolveMailboxId(id, mailboxIdFactory)
- .fold(e => SMono.just(MailboxGetResults.notFound(id)),
- mailboxId => getMailboxResultById(capabilities, mailboxId, mailboxSession)))
+ .flatMap(id => Try(mailboxIdFactory.fromString(id.value))
+ .fold(e => SMono.just(MailboxGetResults.notFound(id)),
+ mailboxId => getMailboxResultById(capabilities, mailboxId, mailboxSession)))
}
private def getMailboxResultById(capabilities: Set[CapabilityIdentifier],
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxSetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxSetMethod.scala
index 5c9a50a..6a40581 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxSetMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/MailboxSetMethod.scala
@@ -23,8 +23,9 @@ import eu.timepit.refined.auto._
import javax.inject.Inject
import org.apache.james.jmap.http.SessionSupplier
import org.apache.james.jmap.json.{MailboxSerializer, ResponseSerializer}
-import org.apache.james.jmap.mail.MailboxSetRequest.{MailboxCreationId, UnparsedMailboxId}
-import org.apache.james.jmap.mail.{InvalidPatchException, InvalidPropertyException, InvalidUpdateException, IsSubscribed, MailboxCreationRequest, MailboxCreationResponse, MailboxPatchObject, MailboxRights, MailboxSetError, MailboxSetRequest, MailboxSetResponse, MailboxUpdateResponse, NameUpdate, ParentIdUpdate, RemoveEmailsOnDestroy, ServerSetPropertyException, SortOrder, TotalEmails, TotalThreads, UnreadEmails, UnreadThreads, UnsupportedPropertyUpdatedException, ValidatedMailboxPatchObject}
+import org.apache.james.jmap.mail.MailboxGet.UnparsedMailboxId
+import org.apache.james.jmap.mail.MailboxSetRequest.MailboxCreationId
+import org.apache.james.jmap.mail.{InvalidPatchException, InvalidPropertyException, InvalidUpdateException, IsSubscribed, MailboxCreationRequest, MailboxCreationResponse, MailboxGet, MailboxPatchObject, MailboxRights, MailboxSetError, MailboxSetRequest, MailboxSetResponse, MailboxUpdateResponse, NameUpdate, ParentIdUpdate, RemoveEmailsOnDestroy, ServerSetPropertyException, SortOrder, TotalEmails, TotalThreads, UnreadEmails, UnreadThreads, UnsupportedPropertyUpdatedException, ValidatedMai [...]
import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
import org.apache.james.jmap.model.DefaultCapabilities.{CORE_CAPABILITY, MAIL_CAPABILITY}
import org.apache.james.jmap.model.Invocation.{Arguments, MethodName}
@@ -42,6 +43,7 @@ import reactor.core.scala.publisher.{SFlux, SMono}
import reactor.core.scheduler.Schedulers
import scala.jdk.CollectionConverters._
+import scala.util.Try
case class MailboxHasMailException(mailboxId: MailboxId) extends Exception
case class SystemMailboxChangeException(mailboxId: MailboxId) extends Exception
@@ -155,7 +157,7 @@ class MailboxSetMethod @Inject()(serializer: MailboxSerializer,
override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: MailboxSetRequest): SMono[InvocationWithContext] = for {
creationResultsWithUpdatedProcessingContext <- createMailboxes(mailboxSession, request, invocation.processingContext)
- deletionResults <- deleteMailboxes(mailboxSession, request, invocation.processingContext)
+ deletionResults <- deleteMailboxes(mailboxSession, request)
updateResults <- updateMailboxes(mailboxSession, request, invocation.processingContext, capabilities)
} yield InvocationWithContext(createResponse(capabilities, invocation.invocation, request, creationResultsWithUpdatedProcessingContext._1, deletionResults, updateResults), creationResultsWithUpdatedProcessingContext._2)
@@ -168,9 +170,10 @@ class MailboxSetMethod @Inject()(serializer: MailboxSerializer,
SFlux.fromIterable(mailboxSetRequest.update.getOrElse(Seq()))
.flatMap({
case (unparsedMailboxId: UnparsedMailboxId, patch: MailboxPatchObject) =>
- processingContext.resolveMailboxId(unparsedMailboxId, mailboxIdFactory).fold(
+ MailboxGet.parse(mailboxIdFactory)(unparsedMailboxId)
+ .fold(
e => SMono.just(UpdateFailure(unparsedMailboxId, e, None)),
- mailboxId => updateMailbox(mailboxSession, processingContext, mailboxId, unparsedMailboxId, patch, capabilities))
+ mailboxId => updateMailbox(mailboxSession, mailboxId, unparsedMailboxId, patch, capabilities))
.onErrorResume(e => SMono.just(UpdateFailure(unparsedMailboxId, e, None)))
})
.collectSeq()
@@ -178,12 +181,11 @@ class MailboxSetMethod @Inject()(serializer: MailboxSerializer,
}
private def updateMailbox(mailboxSession: MailboxSession,
- processingContext: ProcessingContext,
mailboxId: MailboxId,
unparsedMailboxId: UnparsedMailboxId,
patch: MailboxPatchObject,
capabilities: Set[CapabilityIdentifier]): SMono[UpdateResult] = {
- patch.validate(processingContext, mailboxIdFactory, serializer, capabilities, mailboxSession)
+ patch.validate(mailboxIdFactory, serializer, capabilities, mailboxSession)
.fold(e => SMono.raiseError(e), validatedPatch =>
updateMailboxRights(mailboxId, validatedPatch, mailboxSession)
.`then`(updateSubscription(mailboxId, validatedPatch, mailboxSession))
@@ -299,24 +301,24 @@ class MailboxSetMethod @Inject()(serializer: MailboxSerializer,
}
- private def deleteMailboxes(mailboxSession: MailboxSession, mailboxSetRequest: MailboxSetRequest, processingContext: ProcessingContext): SMono[DeletionResults] = {
+ private def deleteMailboxes(mailboxSession: MailboxSession, mailboxSetRequest: MailboxSetRequest): SMono[DeletionResults] = {
SFlux.fromIterable(mailboxSetRequest.destroy.getOrElse(Seq()))
- .flatMap(id => delete(mailboxSession, processingContext, id, mailboxSetRequest.onDestroyRemoveEmails.getOrElse(RemoveEmailsOnDestroy(false)))
+ .flatMap(id => delete(mailboxSession, id, mailboxSetRequest.onDestroyRemoveEmails.getOrElse(RemoveEmailsOnDestroy(false)))
.onErrorRecover(e => DeletionFailure(id, e)))
.collectSeq()
.map(DeletionResults)
}
- private def delete(mailboxSession: MailboxSession, processingContext: ProcessingContext, id: UnparsedMailboxId, onDestroy: RemoveEmailsOnDestroy): SMono[DeletionResult] = {
- processingContext.resolveMailboxId(id, mailboxIdFactory) match {
- case Right(mailboxId) => SMono.fromCallable(() => delete(mailboxSession, mailboxId, onDestroy))
- .subscribeOn(Schedulers.elastic())
- .`then`(SMono.just[DeletionResult](DeletionSuccess(mailboxId)))
- case Left(e) => SMono.raiseError(e)
- }
+ private def delete(mailboxSession: MailboxSession, id: UnparsedMailboxId, onDestroy: RemoveEmailsOnDestroy): SMono[DeletionResult] = {
+ MailboxGet.parse(mailboxIdFactory)(id)
+ .fold(e => SMono.raiseError(e),
+ id => SMono.fromCallable(() => doDelete(mailboxSession, id, onDestroy))
+ .subscribeOn(Schedulers.elastic())
+ .`then`(SMono.just[DeletionResult](DeletionSuccess(id))))
+
}
- private def delete(mailboxSession: MailboxSession, id: MailboxId, onDestroy: RemoveEmailsOnDestroy): Unit = {
+ private def doDelete(mailboxSession: MailboxSession, id: MailboxId, onDestroy: RemoveEmailsOnDestroy): Unit = {
val mailbox = mailboxManager.getMailbox(id, mailboxSession)
if (isASystemMailbox(mailbox)) {
@@ -363,7 +365,7 @@ class MailboxSetMethod @Inject()(serializer: MailboxSerializer,
jsObject: JsObject,
processingContext: ProcessingContext): (CreationResult, ProcessingContext) = {
parseCreate(jsObject)
- .flatMap(mailboxCreationRequest => resolvePath(mailboxSession, mailboxCreationRequest, processingContext)
+ .flatMap(mailboxCreationRequest => resolvePath(mailboxSession, mailboxCreationRequest)
.flatMap(path => createMailbox(mailboxSession = mailboxSession,
path = path,
mailboxCreationRequest = mailboxCreationRequest)))
@@ -441,14 +443,16 @@ class MailboxSetMethod @Inject()(serializer: MailboxSerializer,
}
private def resolvePath(mailboxSession: MailboxSession,
- mailboxCreationRequest: MailboxCreationRequest,
- processingContext: ProcessingContext): Either[Exception, MailboxPath] = {
+ mailboxCreationRequest: MailboxCreationRequest): Either[Exception, MailboxPath] = {
if (mailboxCreationRequest.name.value.contains(mailboxSession.getPathDelimiter)) {
return Left(new MailboxNameException(s"The mailbox '${mailboxCreationRequest.name.value}' contains an illegal character: '${mailboxSession.getPathDelimiter}'"))
}
mailboxCreationRequest.parentId
.map(maybeParentId => for {
- parentId <- processingContext.resolveMailboxId(maybeParentId, mailboxIdFactory)
+ parentId <- Try(mailboxIdFactory.fromString(maybeParentId.value))
+ .toEither
+ .left
+ .map(e => new IllegalArgumentException(e.getMessage, e))
parentPath <- retrievePath(parentId, mailboxSession)
} yield {
parentPath.child(mailboxCreationRequest.name, mailboxSession.getPathDelimiter)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseGetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseGetMethod.scala
index 2978afd..ebbc939 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseGetMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseGetMethod.scala
@@ -24,14 +24,13 @@ import javax.inject.Inject
import org.apache.james.jmap.api.vacation.{VacationRepository, AccountId => JavaAccountId}
import org.apache.james.jmap.http.SessionSupplier
import org.apache.james.jmap.json.{ResponseSerializer, VacationSerializer}
-import org.apache.james.jmap.mail.VacationResponse.UnparsedVacationResponseId
+import org.apache.james.jmap.mail.VacationResponse.{UNPARSED_SINGLETON, UnparsedVacationResponseId}
import org.apache.james.jmap.mail.{VacationResponse, VacationResponseGetRequest, VacationResponseGetResponse, VacationResponseNotFound}
import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
import org.apache.james.jmap.model.DefaultCapabilities.{CORE_CAPABILITY, MAIL_CAPABILITY, VACATION_RESPONSE_CAPABILITY}
import org.apache.james.jmap.model.Invocation.{Arguments, MethodCallId, MethodName}
import org.apache.james.jmap.model.State.INSTANCE
import org.apache.james.jmap.model.{AccountId, Capabilities, ErrorCode, Invocation, MissingCapabilityException, Properties}
-import org.apache.james.jmap.routes.ProcessingContext
import org.apache.james.mailbox.MailboxSession
import org.apache.james.metrics.api.MetricFactory
import play.api.libs.json.{JsError, JsObject, JsSuccess}
@@ -67,7 +66,7 @@ class VacationResponseGetMethod @Inject()(vacationRepository: VacationRepository
{
val requestedProperties: Properties = request.properties.getOrElse(VacationResponse.allProperties)
(requestedProperties -- VacationResponse.allProperties match {
- case invalidProperties if invalidProperties.isEmpty() => getVacationResponse(request, invocation.processingContext, mailboxSession)
+ case invalidProperties if invalidProperties.isEmpty() => getVacationResponse(request, mailboxSession)
.reduce(VacationResponseGetResult.empty, VacationResponseGetResult.merge)
.map(vacationResult => vacationResult.asResponse(request.accountId))
.map(vacationResponseGetResponse => Invocation(
@@ -97,16 +96,16 @@ class VacationResponseGetMethod @Inject()(vacationRepository: VacationRepository
}
private def getVacationResponse(vacationResponseGetRequest: VacationResponseGetRequest,
- processingContext: ProcessingContext,
mailboxSession: MailboxSession): SFlux[VacationResponseGetResult] =
vacationResponseGetRequest.ids match {
case None => getVacationSingleton(mailboxSession)
.map(VacationResponseGetResult.found)
.flux()
case Some(ids) => SFlux.fromIterable(ids.value)
- .flatMap(id => processingContext.resolveVacationResponseId(id)
- .fold(_ => SMono.just(VacationResponseGetResult.notFound(id)),
- _ => getVacationSingleton(mailboxSession).map(VacationResponseGetResult.found)))
+ .flatMap(id => id match {
+ case UNPARSED_SINGLETON => getVacationSingleton(mailboxSession).map(VacationResponseGetResult.found)
+ case _ => SMono.just(VacationResponseGetResult.notFound(id))
+ })
}
private def getVacationSingleton(mailboxSession: MailboxSession): SMono[VacationResponse] = {
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/ProcessingContext.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/ProcessingContext.scala
index 8506f2f..a814df4 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/ProcessingContext.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/ProcessingContext.scala
@@ -23,13 +23,9 @@ import eu.timepit.refined.numeric.NonNegative
import eu.timepit.refined.refineV
import eu.timepit.refined.types.numeric.NonNegInt
import org.apache.james.jmap.json.BackReferenceDeserializer
-import org.apache.james.jmap.mail.MailboxSetRequest.UnparsedMailboxId
-import org.apache.james.jmap.mail.VacationResponse.{UnparsedVacationResponseId, VACATION_RESPONSE_ID}
-import org.apache.james.jmap.model.Id.Id
import org.apache.james.jmap.model.Invocation.{Arguments, MethodCallId, MethodName}
import org.apache.james.jmap.model.{ClientId, Id, Invocation, ServerId}
-import org.apache.james.mailbox.model.MailboxId
-import play.api.libs.json.{JsArray, JsError, JsObject, JsResult, JsSuccess, JsValue, Reads}
+import play.api.libs.json.{JsArray, JsError, JsObject, JsResult, JsString, JsSuccess, JsValue, Reads}
import scala.util.Try
@@ -115,7 +111,7 @@ case class JsonPath(parts: List[JsonPathPart]) {
}
}
- private def readWildcard(jsValue: JsValue) = jsValue match {
+ private def readWildcard(jsValue: JsValue): JsResult[JsValue] = jsValue match {
case JsArray(arrayItems) =>
val evaluationResults: List[JsResult[JsValue]] = arrayItems.toList.map(evaluate)
@@ -146,8 +142,7 @@ case class InvalidResultReferenceException(message: String) extends IllegalArgum
case class ProcessingContext(private val creationIds: Map[ClientId, ServerId], private val invocations: Map[MethodCallId, Invocation]) {
- def recordCreatedId(clientId: ClientId, serverId: ServerId): ProcessingContext = ProcessingContext(creationIds + (clientId -> serverId), invocations)
- private def retrieveServerId(clientId: ClientId): Option[ServerId] = creationIds.get(clientId)
+ def recordCreatedId(clientId: ClientId, serverId: ServerId): ProcessingContext = ProcessingContext(creationIds + (clientId -> serverId), invocations)
def recordInvocation(invocation: Invocation): ProcessingContext = ProcessingContext(creationIds, invocations + (invocation.methodCallId -> invocation))
@@ -163,6 +158,9 @@ case class ProcessingContext(private val creationIds: Map[ClientId, ServerId], p
private def backReferenceResolver(): Reads[JsValue] = {
case JsArray(value) => resolveBackReferences(value)
case JsObject(underlying) => resolveBackReference(underlying)
+ case JsString(value) if value.startsWith("#") => resolveCreationId(value)
+ .fold(_ => JsSuccess(JsString(value)),
+ serverId => JsSuccess(JsString(serverId.value.value)))
case others: JsValue => JsSuccess(others)
}
@@ -174,7 +172,7 @@ case class ProcessingContext(private val creationIds: Map[ClientId, ServerId], p
}
private def resolveBackReference(underlying: collection.Map[String, JsValue]): JsResult[JsObject] = {
- val resolutions = underlying.map(resolveBackReference)
+ val resolutions = underlying.map(resolveBackReference(_))
val firstError = resolutions.flatMap({
case Left(jsError) => Some(jsError)
@@ -196,7 +194,12 @@ case class ProcessingContext(private val creationIds: Map[ClientId, ServerId], p
BackReferenceDeserializer.deserializeBackReference(entry._2) match {
case JsSuccess(backReference, _) => resolveBackReference(newEntry, backReference)
// If the JSON object is not a back-reference continue parsing (it could be a creationId)
- case JsError(_) => propagateBackReferenceResolution(entry)
+ case JsError(_) =>
+ backReferenceResolver().reads(entry._2)
+ .fold(e => Left(JsError(e)),
+ json => resolveCreationId(entry._1)
+ .fold(_ => Right((entry._1, json)),
+ serverId => Right((serverId.value.value, json))))
}
} else {
propagateBackReferenceResolution(entry)
@@ -225,17 +228,9 @@ case class ProcessingContext(private val creationIds: Map[ClientId, ServerId], p
.map(backReference.resolve)
.getOrElse(JsError("Back reference could not be resolved"))
- def resolveMailboxId(unparsedMailboxId: UnparsedMailboxId, mailboxIdFactory: MailboxId.Factory): Either[IllegalArgumentException, MailboxId] =
- Id.validate(unparsedMailboxId.value)
- .flatMap(id => resolveServerId(ClientId(id)))
- .flatMap(serverId => parseMailboxId(mailboxIdFactory, serverId))
-
- private def parseMailboxId(mailboxIdFactory: MailboxId.Factory, serverId: ServerId) =
- try {
- Right(mailboxIdFactory.fromString(serverId.value.value))
- } catch {
- case e: IllegalArgumentException => Left(e)
- }
+ private def resolveCreationId(creationId: String): Either[IllegalArgumentException, ServerId] =
+ Id.validate(creationId)
+ .flatMap(id => resolveServerId(ClientId(id)))
private def resolveServerId(id: ClientId): Either[IllegalArgumentException, ServerId] =
id.retrieveOriginalClientId
@@ -244,10 +239,6 @@ case class ProcessingContext(private val creationIds: Map[ClientId, ServerId], p
.getOrElse(Left[IllegalArgumentException, ServerId](new IllegalArgumentException(s"$id was not used in previously defined creationIds")))))
.getOrElse(Right(ServerId(id.value)))
- def resolveVacationResponseId(unparsedVacationId: UnparsedVacationResponseId): Either[IllegalArgumentException, Id] =
- if (unparsedVacationId.equals(VACATION_RESPONSE_ID)) {
- Right(VACATION_RESPONSE_ID)
- } else {
- Left(new IllegalArgumentException(s"$unparsedVacationId is not a valid VacationResponse ID"))
- }
+ private def retrieveServerId(clientId: ClientId): Option[ServerId] = creationIds.get(clientId)
+
}
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxGetSerializationTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxGetSerializationTest.scala
index de24595..c2841fc 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxGetSerializationTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/MailboxGetSerializationTest.scala
@@ -24,7 +24,7 @@ import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
import org.apache.james.jmap.json.Fixture._
import org.apache.james.jmap.json.MailboxGetSerializationTest._
import org.apache.james.jmap.json.MailboxSerializationTest.MAILBOX
-import org.apache.james.jmap.mail.MailboxSetRequest.UnparsedMailboxId
+import org.apache.james.jmap.mail.MailboxGet.UnparsedMailboxId
import org.apache.james.jmap.mail._
import org.apache.james.jmap.model.{AccountId, Properties}
import org.apache.james.mailbox.model.{MailboxId, TestId}
@@ -47,22 +47,6 @@ object MailboxGetSerializationTest {
class MailboxGetSerializationTest extends AnyWordSpec with Matchers {
"Deserialize MailboxGetRequest" should {
- "succeed on invalid mailboxId" in {
- // as they are unparsed
- val expectedRequestObject = MailboxGetRequest(
- accountId = ACCOUNT_ID,
- ids = Some(Ids(List("ab#?"))),
- properties = None)
-
- SERIALIZER.deserializeMailboxGetRequest(
- """
- |{
- | "accountId": "aHR0cHM6Ly93d3cuYmFzZTY0ZW5jb2RlLm9yZy8",
- | "ids": ["ab#?"]
- |}
- |""".stripMargin) should equal(JsSuccess(expectedRequestObject))
- }
-
"succeed when properties are missing" in {
val expectedRequestObject = MailboxGetRequest(
accountId = ACCOUNT_ID,
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/VacationResponseGetSerializationTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/VacationResponseGetSerializationTest.scala
index e3028aa..35a0e4e 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/VacationResponseGetSerializationTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/VacationResponseGetSerializationTest.scala
@@ -27,14 +27,11 @@ import org.apache.james.jmap.json.VacationResponseSerializationTest.VACATION_RES
import org.apache.james.jmap.mail.VacationResponse.UnparsedVacationResponseId
import org.apache.james.jmap.mail.{VacationResponse, VacationResponseGetRequest, VacationResponseGetResponse, VacationResponseIds, VacationResponseNotFound}
import org.apache.james.jmap.model.{AccountId, Properties}
-import org.apache.james.mailbox.model.{MailboxId, TestId}
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import play.api.libs.json.{JsSuccess, Json}
object VacationResponseGetSerializationTest {
- private val FACTORY: MailboxId.Factory = new TestId.Factory
-
private val ACCOUNT_ID: AccountId = AccountId(id)
private val SINGLETON_ID: UnparsedVacationResponseId = "singleton"
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org