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 4eb39c15e881a93b255c75ce4a81b0f72fbefda0 Author: Rene Cordier <[email protected]> AuthorDate: Fri Aug 21 10:26:15 2020 +0700 JAMES-3359 Mailbox/set update should support isSubscribed --- .../contract/MailboxSetMethodContract.scala | 266 +++++++++++++++++++++ .../org/apache/james/jmap/mail/MailboxSet.scala | 12 +- .../james/jmap/method/MailboxSetMethod.scala | 26 +- 3 files changed, 301 insertions(+), 3 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 f979028..4adc902 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 @@ -3869,6 +3869,272 @@ trait MailboxSetMethodContract { } @Test + def updateShouldSubscribeMailboxes(server: GuiceJamesServer): Unit = { + val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.forUser(BOB, "mailbox")) + + val request = + s""" + |{ + | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ], + | "methodCalls": [ + | ["Mailbox/set", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "update": { + | "${mailboxId.serialize()}" : { + | "/isSubscribed": true + | } + | } + | }, + | "c2"], + | ["Mailbox/get", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "ids": ["${mailboxId.serialize()}"] + | }, + | "c2"] + | ] + |} + |""".stripMargin + + val response = `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(request) + .when + .post + .`then` + .log().ifValidationFails() + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response).isEqualTo( + s"""{ + | "sessionState": "75128aab4b1b", + | "methodResponses": [ + | ["Mailbox/set", { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "newState": "000001", + | "updated": { + | "${mailboxId.serialize()}": {} + | } + | }, "c2"], + | ["Mailbox/get", { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "state": "000001", + | "list": [{ + | "id": "${mailboxId.serialize()}", + | "name": "mailbox", + | "sortOrder": 1000, + | "totalEmails": 0, + | "unreadEmails": 0, + | "totalThreads": 0, + | "unreadThreads": 0, + | "myRights": { + | "mayReadItems": true, + | "mayAddItems": true, + | "mayRemoveItems": true, + | "maySetSeen": true, + | "maySetKeywords": true, + | "mayCreateChild": true, + | "mayRename": true, + | "mayDelete": true, + | "maySubmit": true + | }, + | "isSubscribed": true + | }], + | "notFound": [] + | }, "c2"] + | ] + |}""".stripMargin) + } + + @Test + def updateShouldUnsubscribeMailboxes(server: GuiceJamesServer): Unit = { + val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.forUser(BOB, "mailbox")) + + val request = + s""" + |{ + | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ], + | "methodCalls": [ + | ["Mailbox/set", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "update": { + | "${mailboxId.serialize()}" : { + | "/isSubscribed": true + | } + | } + | }, + | "c2"], + | ["Mailbox/set", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "update": { + | "${mailboxId.serialize()}" : { + | "/isSubscribed": false + | } + | } + | }, + | "c3"], + | ["Mailbox/get", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "ids": ["${mailboxId.serialize()}"] + | }, + | "c4"] + | ] + |} + |""".stripMargin + + val response = `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(request) + .when + .post + .`then` + .log().ifValidationFails() + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response).isEqualTo( + s"""{ + | "sessionState": "75128aab4b1b", + | "methodResponses": [ + | ["Mailbox/set", { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "newState": "000001", + | "updated": { + | "${mailboxId.serialize()}": {} + | } + | }, "c2"], + | ["Mailbox/set", { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "newState": "000001", + | "updated": { + | "${mailboxId.serialize()}": {} + | } + | }, "c3"], + | ["Mailbox/get", { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "state": "000001", + | "list": [{ + | "id": "${mailboxId.serialize()}", + | "name": "mailbox", + | "sortOrder": 1000, + | "totalEmails": 0, + | "unreadEmails": 0, + | "totalThreads": 0, + | "unreadThreads": 0, + | "myRights": { + | "mayReadItems": true, + | "mayAddItems": true, + | "mayRemoveItems": true, + | "maySetSeen": true, + | "maySetKeywords": true, + | "mayCreateChild": true, + | "mayRename": true, + | "mayDelete": true, + | "maySubmit": true + | }, + | "isSubscribed": false + | }], + | "notFound": [] + | }, "c4"] + | ] + |}""".stripMargin) + } + + @Test + def updateShouldSubscribeMailboxesWhenNull(server: GuiceJamesServer): Unit = { + val mailboxId: MailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.forUser(BOB, "mailbox")) + + val request = + s""" + |{ + | "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail" ], + | "methodCalls": [ + | ["Mailbox/set", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "update": { + | "${mailboxId.serialize()}" : { + | "/isSubscribed": null + | } + | } + | }, + | "c3"], + | ["Mailbox/get", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "ids": ["${mailboxId.serialize()}"] + | }, + | "c4"] + | ] + |} + |""".stripMargin + + val response = `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(request) + .when + .post + .`then` + .log().ifValidationFails() + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response).isEqualTo( + s"""{ + | "sessionState": "75128aab4b1b", + | "methodResponses": [ + | ["Mailbox/set", { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "newState": "000001", + | "updated": { + | "${mailboxId.serialize()}": {} + | } + | }, "c3"], + | ["Mailbox/get", { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "state": "000001", + | "list": [{ + | "id": "${mailboxId.serialize()}", + | "name": "mailbox", + | "sortOrder": 1000, + | "totalEmails": 0, + | "unreadEmails": 0, + | "totalThreads": 0, + | "unreadThreads": 0, + | "myRights": { + | "mayReadItems": true, + | "mayAddItems": true, + | "mayRemoveItems": true, + | "maySetSeen": true, + | "maySetKeywords": true, + | "mayCreateChild": true, + | "mayRename": true, + | "mayDelete": true, + | "maySubmit": true + | }, + | "isSubscribed": true + | }], + | "notFound": [] + | }, "c4"] + | ] + |}""".stripMargin) + } + + @Test def deleteShouldNotRemoveMessageWhenMailboxIsNotEmptyAndOnDestroyRemoveEmailsIsFalse(server: GuiceJamesServer): Unit = { val message: Message = Message.Builder .of 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 3fce790..c4f6bcd 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 @@ -35,7 +35,7 @@ import org.apache.james.jmap.model.State.State import org.apache.james.jmap.model.{AccountId, CapabilityIdentifier} import org.apache.james.mailbox.Role import org.apache.james.mailbox.model.MailboxId -import play.api.libs.json._ +import play.api.libs.json.{JsBoolean, JsError, JsNull, JsObject, JsString, JsSuccess, JsValue} case class MailboxSetRequest(accountId: AccountId, ifInState: Option[State], @@ -90,6 +90,7 @@ case class MailboxPatchObject(value: Map[String, JsValue]) { case "/unreadEmails" => Left(ServerSetPropertyException(MailboxPatchObject.unreadEmailsProperty)) case "/totalEmails" => Left(ServerSetPropertyException(MailboxPatchObject.totalEmailsProperty)) case "/myRights" => Left(ServerSetPropertyException(MailboxPatchObject.myRightsProperty)) + case "/isSubscribed" => IsSubscribedUpdate.parse(newValue) case property => val refinedKey: Either[String, MailboxPatchObjectKey] = refineV(property) refinedKey.fold[Either[PatchUpdateValidationException, Update]]( @@ -177,9 +178,18 @@ object SharedWithResetUpdate { } } +object IsSubscribedUpdate { + def parse(newValue: JsValue): Either[PatchUpdateValidationException, Update] = newValue match { + case JsBoolean(value) => scala.Right(IsSubscribedUpdate(IsSubscribed(value))) + case JsNull => scala.Right(IsSubscribedUpdate(IsSubscribed(true))) + case _ => Left(InvalidUpdateException("/isSubscribed", "Expecting a JSON string as an argument")) + } +} + sealed trait Update case class NameUpdate(newName: String) extends Update case class SharedWithResetUpdate(rights: Rights) extends Update +case class IsSubscribedUpdate(isSubscribed: IsSubscribed) extends Update class PatchUpdateValidationException() extends IllegalArgumentException case class UnsupportedPropertyUpdatedException(property: MailboxPatchObjectKey) extends PatchUpdateValidationException 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 e6bf852..d1915f2 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,7 +23,7 @@ import eu.timepit.refined.auto._ import javax.inject.Inject import org.apache.james.jmap.json.Serializer import org.apache.james.jmap.mail.MailboxSetRequest.{MailboxCreationId, UnparsedMailboxId} -import org.apache.james.jmap.mail.{InvalidPropertyException, InvalidUpdateException, IsSubscribed, MailboxCreationRequest, MailboxCreationResponse, MailboxPatchObject, MailboxRights, MailboxSetError, MailboxSetRequest, MailboxSetResponse, MailboxUpdateResponse, NameUpdate, PatchUpdateValidationException, Properties, RemoveEmailsOnDestroy, ServerSetPropertyException, SetErrorDescription, SharedWithResetUpdate, SortOrder, TotalEmails, TotalThreads, UnreadEmails, UnreadThreads, UnsupportedP [...] +import org.apache.james.jmap.mail.{InvalidPropertyException, InvalidUpdateException, IsSubscribed, IsSubscribedUpdate, MailboxCreationRequest, MailboxCreationResponse, MailboxPatchObject, MailboxRights, MailboxSetError, MailboxSetRequest, MailboxSetResponse, MailboxUpdateResponse, NameUpdate, PatchUpdateValidationException, Properties, RemoveEmailsOnDestroy, ServerSetPropertyException, SetErrorDescription, SharedWithResetUpdate, SortOrder, TotalEmails, TotalThreads, UnreadEmails, UnreadT [...] import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier import org.apache.james.jmap.model.Invocation.{Arguments, MethodName} import org.apache.james.jmap.model.{ClientId, Id, Invocation, ServerId, State} @@ -189,9 +189,31 @@ class MailboxSetMethod @Inject()(serializer: Serializer, case _ => None }).headOption + val maybeIsSubscribedUpdate: Option[IsSubscribedUpdate] = updates + .flatMap(x => x match { + case Right(IsSubscribedUpdate(isSubscribed)) => Some(IsSubscribedUpdate(isSubscribed)) + case _ => None + }).headOption + maybeParseException.map(e => SMono.raiseError[UpdateResult](e)) .getOrElse(updateMailboxPath(maiboxId, maybeNameUpdate, mailboxSession) - .`then`(updateMailboxRights(maiboxId, maybeSharedWithResetUpdate, mailboxSession))) + .`then`(updateMailboxRights(maiboxId, maybeSharedWithResetUpdate, mailboxSession)) + .`then`(updateSubscription(maiboxId, maybeIsSubscribedUpdate, mailboxSession))) + } + + private def updateSubscription(mailboxId: MailboxId, maybeIsSubscribedUpdate: Option[IsSubscribedUpdate], mailboxSession: MailboxSession): SMono[UpdateResult] = { + maybeIsSubscribedUpdate.map(isSubscribedUpdate => { + SMono.fromCallable(() => { + val mailbox = mailboxManager.getMailbox(mailboxId, mailboxSession) + if (isSubscribedUpdate.isSubscribed.value) { + subscriptionManager.subscribe(mailboxSession, mailbox.getMailboxPath.getName) + } else { + subscriptionManager.unsubscribe(mailboxSession, mailbox.getMailboxPath.getName) + } + }).`then`(SMono.just[UpdateResult](UpdateSuccess(mailboxId))) + .subscribeOn(Schedulers.elastic()) + }) + .getOrElse(SMono.just[UpdateResult](UpdateSuccess(mailboxId))) } private def updateMailboxPath(mailboxId: MailboxId, maybeNameUpdate: Option[NameUpdate], mailboxSession: MailboxSession): SMono[UpdateResult] = { --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
