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/11/10 07:54:57 UTC

[james-project] branch master updated (75256a2 -> b114f1f)

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

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


    from 75256a2  JAMES-3600 Fix check Content-Length when ProvisioningTest (#738)
     new 6cf2419  JAMES-3539 Add PushSubscription/set update expires method
     new b114f1f  JAMES-3539 PushSubscriptionRepository::updateExpireTime should return PushSubscriptionExpiredTime

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../CassandraPushSubscriptionRepository.java       |   6 +-
 .../PushSubscriptionRepository.java                |   3 +-
 .../MemoryPushSubscriptionRepository.java          |  13 +-
 .../james/jmap/api/model/PushSubscription.scala    |   6 +-
 .../PushSubscriptionRepositoryContract.scala       |  12 +
 .../PushSubscriptionSetMethodContract.scala        | 248 ++++++++++++++++++++-
 .../james/jmap/core/PushSubscriptionSet.scala      |  41 +++-
 .../jmap/json/PushSubscriptionSerializer.scala     |   2 +-
 .../PushSubscriptionSetCreatePerformer.scala       |   5 +-
 .../method/PushSubscriptionUpdatePerformer.scala   |  22 +-
 10 files changed, 328 insertions(+), 30 deletions(-)

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


[james-project] 01/02: JAMES-3539 Add PushSubscription/set update expires method

Posted by bt...@apache.org.
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 6cf241938fcc4c838bba3a21185463d9db6c02f9
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Tue Nov 2 11:51:36 2021 +0700

    JAMES-3539 Add PushSubscription/set update expires method
---
 .../james/jmap/api/model/PushSubscription.scala    |   6 +-
 .../PushSubscriptionSetMethodContract.scala        | 248 ++++++++++++++++++++-
 .../james/jmap/core/PushSubscriptionSet.scala      |  41 +++-
 .../jmap/json/PushSubscriptionSerializer.scala     |   2 +-
 .../PushSubscriptionSetCreatePerformer.scala       |   5 +-
 .../method/PushSubscriptionUpdatePerformer.scala   |  22 +-
 6 files changed, 303 insertions(+), 21 deletions(-)

diff --git a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/model/PushSubscription.scala b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/model/PushSubscription.scala
index 63a4108..69d4f77 100644
--- a/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/model/PushSubscription.scala
+++ b/server/data/data-jmap/src/main/scala/org/apache/james/jmap/api/model/PushSubscription.scala
@@ -150,7 +150,7 @@ case class PushSubscriptionNotFoundException(id: PushSubscriptionId) extends Run
 object ExpireTimeInvalidException {
   val TIME_FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssX")
 }
-case class ExpireTimeInvalidException(expires: ZonedDateTime, message: String) extends RuntimeException(s"`${expires.format(TIME_FORMATTER)}` $message")
+case class ExpireTimeInvalidException(expires: ZonedDateTime, message: String) extends IllegalStateException(s"`${expires.format(TIME_FORMATTER)}` $message")
 
-case class DeviceClientIdInvalidException(deviceClientId: DeviceClientId, message: String) extends RuntimeException(s"`${deviceClientId.value}` $message")
-case class InvalidPushSubscriptionKeys(keys: PushSubscriptionKeys) extends RuntimeException
\ No newline at end of file
+case class DeviceClientIdInvalidException(deviceClientId: DeviceClientId, message: String) extends IllegalArgumentException(s"`${deviceClientId.value}` $message")
+case class InvalidPushSubscriptionKeys(keys: PushSubscriptionKeys) extends IllegalArgumentException
\ No newline at end of file
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/PushSubscriptionSetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/PushSubscriptionSetMethodContract.scala
index 03d3aa8..ac5a01d 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/PushSubscriptionSetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/PushSubscriptionSetMethodContract.scala
@@ -1109,7 +1109,8 @@ trait PushSubscriptionSetMethodContract {
            |                "notCreated": {
            |                    "4f29": {
            |                        "type": "invalidArguments",
-           |                        "description": "`$invalidExpire` expires must be greater than now"
+           |                        "description": "`$invalidExpire` expires must be greater than now",
+           |                        "properties": ["expires"]
            |                    }
            |                }
            |            },
@@ -1814,6 +1815,251 @@ trait PushSubscriptionSetMethodContract {
   }
 
   @Test
+  def updateValidExpiresShouldSucceed(server: GuiceJamesServer): Unit = {
+    val probe = server.getProbe(classOf[PushSubscriptionProbe])
+    val pushSubscription = probe
+      .createPushSubscription(username = BOB,
+        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
+        deviceId = DeviceClientId("12c6d086"),
+        types = Seq(MailboxTypeName, EmailDeliveryTypeName, EmailTypeName))
+
+    val validExpiresString = UTCDate(ZonedDateTime.now().plusDays(1)).asUTC.format(TIME_FORMATTER)
+    val request: String =
+      s"""{
+         |    "using": ["urn:ietf:params:jmap:core"],
+         |    "methodCalls": [
+         |      [
+         |        "PushSubscription/set",
+         |        {
+         |            "update": {
+         |                "${pushSubscription.id.serialise}": {
+         |                 "expires": "$validExpiresString"
+         |                }
+         |              }
+         |        },
+         |        "c1"
+         |      ]
+         |    ]
+         |  }""".stripMargin
+
+    val response: String = `given`
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .isEqualTo(
+        s"""{
+           |    "sessionState": "${SESSION_STATE.value}",
+           |    "methodResponses": [
+           |        [
+           |            "PushSubscription/set",
+           |            {
+           |                "updated": {
+           |                    "${pushSubscription.id.serialise}": {}
+           |                }
+           |            },
+           |            "c1"
+           |        ]
+           |    ]
+           |}""".stripMargin)
+
+    assertThat(probe.retrievePushSubscription(BOB, pushSubscription.id)
+      .expires.value.format(TIME_FORMATTER))
+      .isEqualTo(validExpiresString)
+  }
+
+  @Test
+  def updateInvalidExpiresStringShouldFail(server: GuiceJamesServer): Unit = {
+    val probe = server.getProbe(classOf[PushSubscriptionProbe])
+    val pushSubscription = probe
+      .createPushSubscription(username = BOB,
+        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
+        deviceId = DeviceClientId("12c6d086"),
+        types = Seq(MailboxTypeName, EmailDeliveryTypeName, EmailTypeName))
+
+    val invalidExpiresString = "whatever"
+    val request: String =
+      s"""{
+         |    "using": ["urn:ietf:params:jmap:core"],
+         |    "methodCalls": [
+         |      [
+         |        "PushSubscription/set",
+         |        {
+         |            "update": {
+         |                "${pushSubscription.id.serialise}": {
+         |                 "expires": "$invalidExpiresString"
+         |                }
+         |              }
+         |        },
+         |        "c1"
+         |      ]
+         |    ]
+         |  }""".stripMargin
+
+    val response: String = `given`
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .isEqualTo(
+        s"""{
+           |	"sessionState": "${SESSION_STATE.value}",
+           |	"methodResponses": [
+           |		[
+           |			"PushSubscription/set",
+           |			{
+           |				"notUpdated": {
+           |					"${pushSubscription.id.serialise}": {
+           |						"type": "invalidArguments",
+           |						"description": "This string can not be parsed to UTCDate",
+           |						"properties": ["expires"]
+           |					}
+           |				}
+           |			},
+           |			"c1"
+           |		]
+           |	]
+           |}""".stripMargin)
+  }
+
+  @Test
+  def updateWithBiggerExpiresThanServerLimitShouldSetToServerLimitAndExplicitlyReturned(server: GuiceJamesServer): Unit = {
+    val probe = server.getProbe(classOf[PushSubscriptionProbe])
+    val pushSubscription = probe
+      .createPushSubscription(username = BOB,
+        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
+        deviceId = DeviceClientId("12c6d086"),
+        types = Seq(MailboxTypeName, EmailDeliveryTypeName, EmailTypeName))
+
+    val biggerExpiresString = UTCDate(ZonedDateTime.now().plusDays(10)).asUTC.format(TIME_FORMATTER)
+    val request: String =
+      s"""{
+         |    "using": ["urn:ietf:params:jmap:core"],
+         |    "methodCalls": [
+         |      [
+         |        "PushSubscription/set",
+         |        {
+         |            "update": {
+         |                "${pushSubscription.id.serialise}": {
+         |                 "expires": "$biggerExpiresString"
+         |                }
+         |              }
+         |        },
+         |        "c1"
+         |      ]
+         |    ]
+         |  }""".stripMargin
+
+    val response: String = `given`
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    val fixedExpires = probe.retrievePushSubscription(BOB, pushSubscription.id)
+      .expires.value.format(TIME_FORMATTER)
+
+    assertThatJson(response)
+      .isEqualTo(
+        s"""{
+           |    "sessionState": "${SESSION_STATE.value}",
+           |    "methodResponses": [
+           |        [
+           |            "PushSubscription/set",
+           |            {
+           |                "updated": {
+           |                    "${pushSubscription.id.serialise}": {
+           |                        "expires": "$fixedExpires"
+           |                    }
+           |                }
+           |            },
+           |            "c1"
+           |        ]
+           |    ]
+           |}""".stripMargin)
+  }
+
+  @Test
+  def updateOutdatedExpiresShouldFail(server: GuiceJamesServer): Unit = {
+    val probe = server.getProbe(classOf[PushSubscriptionProbe])
+    val pushSubscription = probe
+      .createPushSubscription(username = BOB,
+        url = PushSubscriptionServerURL(new URL("https://example.com/push/?device=X8980fc&client=12c6d086")),
+        deviceId = DeviceClientId("12c6d086"),
+        types = Seq(MailboxTypeName, EmailDeliveryTypeName, EmailTypeName))
+
+    val invalidExpiresString = UTCDate(ZonedDateTime.now().minusDays(1)).asUTC.format(TIME_FORMATTER)
+    val request: String =
+      s"""{
+         |    "using": ["urn:ietf:params:jmap:core"],
+         |    "methodCalls": [
+         |      [
+         |        "PushSubscription/set",
+         |        {
+         |            "update": {
+         |                "${pushSubscription.id.serialise}": {
+         |                 "expires": "$invalidExpiresString"
+         |                }
+         |              }
+         |        },
+         |        "c1"
+         |      ]
+         |    ]
+         |  }""".stripMargin
+
+    val response: String = `given`
+      .body(request)
+    .when
+      .post
+    .`then`
+      .statusCode(SC_OK)
+      .contentType(JSON)
+      .extract
+      .body
+      .asString
+
+    assertThatJson(response)
+      .isEqualTo(
+        s"""{
+           |	"sessionState": "${SESSION_STATE.value}",
+           |	"methodResponses": [
+           |		[
+           |			"PushSubscription/set",
+           |			{
+           |				"notUpdated": {
+           |					"${pushSubscription.id.serialise}": {
+           |						"type": "invalidArguments",
+           |						"description": "`$invalidExpiresString` expires must be greater than now",
+           |						"properties": ["expires"]
+           |					}
+           |				}
+           |			},
+           |			"c1"
+           |		]
+           |	]
+           |}""".stripMargin)
+  }
+
+  @Test
   def updateShouldFailWhenUnknownProperty(server: GuiceJamesServer): Unit = {
     val probe = server.getProbe(classOf[PushSubscriptionProbe])
     val pushSubscription = probe
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/PushSubscriptionSet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/PushSubscriptionSet.scala
index 1b4fcd5..cef464d 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/PushSubscriptionSet.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/core/PushSubscriptionSet.scala
@@ -19,6 +19,7 @@
 
 package org.apache.james.jmap.core
 
+import java.time.ZonedDateTime
 import java.util.UUID
 
 import cats.implicits._
@@ -35,7 +36,7 @@ import org.apache.james.jmap.mail.{InvalidPropertyException, InvalidUpdateExcept
 import org.apache.james.jmap.method.WithoutAccountId
 import play.api.libs.json.{JsArray, JsObject, JsString, JsValue}
 
-import scala.util.Try
+import scala.util.{Failure, Success, Try}
 
 case class PushSubscriptionSetRequest(create: Option[Map[PushSubscriptionCreationId, JsObject]],
                                       update: Option[Map[UnparsedPushSubscriptionId, PushSubscriptionPatchObject]],
@@ -57,11 +58,7 @@ case class UnparsedPushSubscriptionId(id: Id) {
     }).map(uuid => PushSubscriptionId(uuid))
 }
 
-object PushSubscriptionUpdateResponse {
-  def empty: PushSubscriptionUpdateResponse = PushSubscriptionUpdateResponse(JsObject(Map[String, JsValue]()))
-}
-
-case class PushSubscriptionUpdateResponse(value: JsObject)
+case class PushSubscriptionUpdateResponse(expires: Option[UTCDate])
 
 object PushSubscriptionPatchObject {
   type KeyConstraint = NonEmpty
@@ -80,6 +77,7 @@ case class PushSubscriptionPatchObject(value: Map[String, JsValue]) {
     case (property, newValue) => property match {
       case "verificationCode" => VerificationCodeUpdate.parse(newValue)
       case "types" => TypesUpdate.parse(newValue, typeStateFactory)
+      case "expires" => ExpiresUpdate.parse(newValue)
       case property => PushSubscriptionPatchObject.notFound(property)
     }
   })
@@ -98,6 +96,12 @@ case class PushSubscriptionPatchObject(value: Map[String, JsValue]) {
         case _ => None
       }).headOption
 
+    val expiresUpdate: Option[ExpiresUpdate] = updates
+      .flatMap(x => x match {
+        case Right(ExpiresUpdate(newExpires)) => Some(ExpiresUpdate(newExpires))
+        case _ => None
+      }).headOption
+
     val typesUpdate: Option[TypesUpdate] = updates
       .flatMap(x => x match {
         case Right(TypesUpdate(newTypes)) => Some(TypesUpdate(newTypes))
@@ -108,7 +112,8 @@ case class PushSubscriptionPatchObject(value: Map[String, JsValue]) {
       .map(e => Left(e))
       .getOrElse(scala.Right(ValidatedPushSubscriptionPatchObject(
         verificationCodeUpdate = verificationCodeUpdate.map(_.newVerificationCode),
-        typesUpdate = typesUpdate.map(_.types))))
+        typesUpdate = typesUpdate.map(_.types),
+        expiresUpdate = expiresUpdate.map(expiresUpdate => PushSubscriptionExpiredTime(expiresUpdate.newExpires.asUTC)))))
   }
 }
 
@@ -134,22 +139,38 @@ object TypesUpdate {
   }
 }
 
+object ExpiresUpdate {
+  def parse(jsValue: JsValue): Either[PatchUpdateValidationException, Update] = jsValue match {
+    case JsString(aString) => toZonedDateTime(aString) match {
+      case Success(value) => Right(ExpiresUpdate(UTCDate(value)))
+      case Failure(e) => Left(InvalidUpdateException("expires", "This string can not be parsed to UTCDate"))
+    }
+    case _ => Left(InvalidUpdateException("expires", "Expecting a JSON string as an argument"))
+  }
+
+  private def toZonedDateTime(string: String): Try[ZonedDateTime] = Try(ZonedDateTime.parse(string))
+}
+
 sealed trait Update
 case class VerificationCodeUpdate(newVerificationCode: VerificationCode) extends Update
 case class TypesUpdate(types: Set[TypeName]) extends Update
+case class ExpiresUpdate(newExpires: UTCDate) extends Update
 
 object ValidatedPushSubscriptionPatchObject {
   val verificationCodeProperty: NonEmptyString = "verificationCode"
   val typesProperty: NonEmptyString = "types"
+  val expiresUpdate: NonEmptyString = "expires"
 }
 
 case class ValidatedPushSubscriptionPatchObject(verificationCodeUpdate: Option[VerificationCode],
-                                                typesUpdate: Option[Set[TypeName]]) {
-  val shouldUpdate: Boolean = verificationCodeUpdate.isDefined || typesUpdate.isDefined
+                                                typesUpdate: Option[Set[TypeName]],
+                                                expiresUpdate: Option[PushSubscriptionExpiredTime]) {
+  val shouldUpdate: Boolean = verificationCodeUpdate.isDefined || typesUpdate.isDefined || expiresUpdate.isDefined
 
   val updatedProperties: Properties = Properties(Set(
     verificationCodeUpdate.map(_ => ValidatedPushSubscriptionPatchObject.verificationCodeProperty),
-    typesUpdate.map(_ => ValidatedPushSubscriptionPatchObject.typesProperty))
+    typesUpdate.map(_ => ValidatedPushSubscriptionPatchObject.typesProperty),
+    expiresUpdate.map(_ => ValidatedPushSubscriptionPatchObject.expiresUpdate))
     .flatMap(_.toList))
 }
 
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/PushSubscriptionSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/PushSubscriptionSerializer.scala
index 1e3d2a3..920290d 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/PushSubscriptionSerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/PushSubscriptionSerializer.scala
@@ -85,7 +85,7 @@ class PushSubscriptionSerializer @Inject()(typeStateFactory: TypeStateFactory) {
   private implicit val pushSubscriptionGetResponseWrites: OWrites[PushSubscriptionGetResponse] = Json.writes[PushSubscriptionGetResponse]
 
   private implicit val pushSubscriptionCreationResponseWrites: Writes[PushSubscriptionCreationResponse] = Json.writes[PushSubscriptionCreationResponse]
-  private implicit val pushSubscriptionUpdateResponseWrites: Writes[PushSubscriptionUpdateResponse] = Json.valueWrites[PushSubscriptionUpdateResponse]
+  private implicit val pushSubscriptionUpdateResponseWrites: Writes[PushSubscriptionUpdateResponse] = Json.writes[PushSubscriptionUpdateResponse]
 
   private implicit val pushSubscriptionMapSetErrorForCreationWrites: Writes[Map[PushSubscriptionCreationId, SetError]] =
     mapWrites[PushSubscriptionCreationId, SetError](_.serialise, setErrorWrites)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionSetCreatePerformer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionSetCreatePerformer.scala
index 4fa1e55..d1940db 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionSetCreatePerformer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionSetCreatePerformer.scala
@@ -2,11 +2,12 @@ package org.apache.james.jmap.method
 
 import java.nio.charset.StandardCharsets
 
+import eu.timepit.refined.auto._
 import javax.inject.Inject
 import org.apache.james.jmap.api.model.{DeviceClientIdInvalidException, ExpireTimeInvalidException, PushSubscriptionCreationRequest, PushSubscriptionExpiredTime, PushSubscriptionId, PushSubscriptionKeys, PushSubscriptionServerURL, VerificationCode}
 import org.apache.james.jmap.api.pushsubscription.PushSubscriptionRepository
 import org.apache.james.jmap.core.SetError.SetErrorDescription
-import org.apache.james.jmap.core.{PushSubscriptionCreation, PushSubscriptionCreationId, PushSubscriptionCreationParseException, PushSubscriptionCreationResponse, PushSubscriptionSetRequest, SetError}
+import org.apache.james.jmap.core.{Properties, PushSubscriptionCreation, PushSubscriptionCreationId, PushSubscriptionCreationParseException, PushSubscriptionCreationResponse, PushSubscriptionSetRequest, SetError}
 import org.apache.james.jmap.json.{PushSerializer, PushSubscriptionSerializer}
 import org.apache.james.jmap.method.PushSubscriptionSetCreatePerformer.{CreationFailure, CreationResult, CreationResults, CreationSuccess}
 import org.apache.james.jmap.pushsubscription.{PushRequest, PushTTL, WebPushClient}
@@ -23,7 +24,7 @@ object PushSubscriptionSetCreatePerformer {
   case class CreationFailure(clientId: PushSubscriptionCreationId, e: Throwable) extends CreationResult {
     def asMessageSetError: SetError = e match {
       case e: PushSubscriptionCreationParseException => e.setError
-      case e: ExpireTimeInvalidException => SetError.invalidArguments(SetErrorDescription(e.getMessage))
+      case e: ExpireTimeInvalidException => SetError.invalidArguments(SetErrorDescription(e.getMessage), Some(Properties("expires")))
       case e: DeviceClientIdInvalidException => SetError.invalidArguments(SetErrorDescription(e.getMessage))
       case e: IllegalArgumentException => SetError.invalidArguments(SetErrorDescription(e.getMessage))
       case _ => SetError.serverFail(SetErrorDescription(e.getMessage))
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionUpdatePerformer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionUpdatePerformer.scala
index f3a42e5..49fd7e6 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionUpdatePerformer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/PushSubscriptionUpdatePerformer.scala
@@ -23,10 +23,10 @@ import com.google.common.collect.ImmutableSet
 import eu.timepit.refined.auto._
 import javax.inject.Inject
 import org.apache.james.jmap.api.change.TypeStateFactory
-import org.apache.james.jmap.api.model.{PushSubscription, PushSubscriptionId, PushSubscriptionNotFoundException, TypeName, VerificationCode}
+import org.apache.james.jmap.api.model.{ExpireTimeInvalidException, PushSubscription, PushSubscriptionExpiredTime, PushSubscriptionId, PushSubscriptionNotFoundException, TypeName, VerificationCode}
 import org.apache.james.jmap.api.pushsubscription.PushSubscriptionRepository
 import org.apache.james.jmap.core.SetError.SetErrorDescription
-import org.apache.james.jmap.core.{Properties, PushSubscriptionPatchObject, PushSubscriptionSetRequest, PushSubscriptionUpdateResponse, SetError, UnparsedPushSubscriptionId, ValidatedPushSubscriptionPatchObject}
+import org.apache.james.jmap.core.{Properties, PushSubscriptionPatchObject, PushSubscriptionSetRequest, PushSubscriptionUpdateResponse, SetError, UTCDate, UnparsedPushSubscriptionId, ValidatedPushSubscriptionPatchObject}
 import org.apache.james.jmap.mail.{InvalidPropertyException, InvalidUpdateException, UnsupportedPropertyUpdatedException}
 import org.apache.james.jmap.method.PushSubscriptionSetUpdatePerformer.{PushSubscriptionUpdateFailure, PushSubscriptionUpdateResult, PushSubscriptionUpdateResults, PushSubscriptionUpdateSuccess, WrongVerificationCodeException}
 import org.apache.james.mailbox.MailboxSession
@@ -37,7 +37,7 @@ import scala.jdk.CollectionConverters._
 object PushSubscriptionSetUpdatePerformer {
   case class WrongVerificationCodeException() extends RuntimeException()
   sealed trait PushSubscriptionUpdateResult
-  case class PushSubscriptionUpdateSuccess(id: PushSubscriptionId) extends PushSubscriptionUpdateResult
+  case class PushSubscriptionUpdateSuccess(id: PushSubscriptionId, serverExpires: Option[UTCDate] = None) extends PushSubscriptionUpdateResult
   case class PushSubscriptionUpdateFailure(id: UnparsedPushSubscriptionId, exception: Throwable) extends PushSubscriptionUpdateResult {
     def asSetError: SetError = exception match {
       case _: WrongVerificationCodeException => SetError.invalidProperties(SetErrorDescription("Wrong verification code"), Some(Properties("verificationCode")))
@@ -46,13 +46,14 @@ object PushSubscriptionSetUpdatePerformer {
       case e: InvalidUpdateException => SetError.invalidArguments(SetErrorDescription(s"${e.cause}"), Some(Properties(e.property)))
       case e: IllegalArgumentException => SetError.invalidArguments(SetErrorDescription(e.getMessage), None)
       case e: PushSubscriptionNotFoundException => SetError.notFound(SetErrorDescription(e.getMessage))
+      case e: ExpireTimeInvalidException => SetError.invalidArguments(SetErrorDescription(e.getMessage), Some(Properties("expires")))
       case _ => SetError.serverFail(SetErrorDescription(exception.getMessage))
     }
   }
   case class PushSubscriptionUpdateResults(results: Seq[PushSubscriptionUpdateResult]) {
     def updated: Map[PushSubscriptionId, PushSubscriptionUpdateResponse] =
       results.flatMap(result => result match {
-        case success: PushSubscriptionUpdateSuccess => Some((success.id, PushSubscriptionUpdateResponse.empty))
+        case success: PushSubscriptionUpdateSuccess => Some((success.id, PushSubscriptionUpdateResponse(success.serverExpires)))
         case _ => None
       }).toMap
     def notUpdated: Map[UnparsedPushSubscriptionId, SetError] = results.flatMap(result => result match {
@@ -94,6 +95,9 @@ class PushSubscriptionUpdatePerformer @Inject()(pushSubscriptionRepository: Push
             .getOrElse(SMono.empty),
           validatedPatch.typesUpdate
             .map(types => updateTypes(pushSubscription, types, mailboxSession))
+            .getOrElse(SMono.empty),
+          validatedPatch.expiresUpdate
+            .map(expires => updateExpires(pushSubscription, expires, mailboxSession))
             .getOrElse(SMono.empty))
           .last())
     } else {
@@ -111,4 +115,14 @@ class PushSubscriptionUpdatePerformer @Inject()(pushSubscriptionRepository: Push
   private def updateTypes(pushSubscription: PushSubscription, types: Set[TypeName], mailboxSession: MailboxSession): SMono[PushSubscriptionUpdateResult] =
     SMono(pushSubscriptionRepository.updateTypes(mailboxSession.getUser, pushSubscription.id, types.asJava))
       .`then`(SMono.just(PushSubscriptionUpdateSuccess(pushSubscription.id)))
+
+  private def updateExpires(pushSubscription: PushSubscription, inputExpires: PushSubscriptionExpiredTime, mailboxSession: MailboxSession): SMono[PushSubscriptionUpdateResult] =
+    SMono(pushSubscriptionRepository.updateExpireTime(mailboxSession.getUser, pushSubscription.id, inputExpires.value))
+      .map(toPushSubscriptionUpdate(pushSubscription, inputExpires, _))
+
+  private def toPushSubscriptionUpdate(pushSubscription: PushSubscription, inputExpires: PushSubscriptionExpiredTime, updatedExpires: PushSubscriptionExpiredTime): PushSubscriptionUpdateResult =
+    PushSubscriptionUpdateSuccess(pushSubscription.id, Some(updatedExpires)
+      .filter(updatedExpires => !updatedExpires.equals(inputExpires))
+      .map(_.value)
+      .map(UTCDate(_)))
 }

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


[james-project] 02/02: JAMES-3539 PushSubscriptionRepository::updateExpireTime should return PushSubscriptionExpiredTime

Posted by bt...@apache.org.
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 b114f1f139c7bd2e1a3b1af07c0f06bb29de0a75
Author: Quan Tran <hq...@linagora.com>
AuthorDate: Tue Nov 2 12:17:04 2021 +0700

    JAMES-3539 PushSubscriptionRepository::updateExpireTime should return PushSubscriptionExpiredTime
    
    This change allows knowing which how expires is updated
---
 .../CassandraPushSubscriptionRepository.java                |  6 +++---
 .../api/pushsubscription/PushSubscriptionRepository.java    |  3 ++-
 .../pushsubscription/MemoryPushSubscriptionRepository.java  | 13 ++++++++-----
 .../PushSubscriptionRepositoryContract.scala                | 12 ++++++++++++
 4 files changed, 25 insertions(+), 9 deletions(-)

diff --git a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionRepository.java b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionRepository.java
index a6d2c16..66856e4 100644
--- a/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionRepository.java
+++ b/server/data/data-jmap-cassandra/src/main/java/org/apache/james/jmap/cassandra/pushsubscription/CassandraPushSubscriptionRepository.java
@@ -79,7 +79,7 @@ public class CassandraPushSubscriptionRepository implements PushSubscriptionRepo
     }
 
     @Override
-    public Publisher<Void> updateExpireTime(Username username, PushSubscriptionId id, ZonedDateTime newExpire) {
+    public Publisher<PushSubscriptionExpiredTime> updateExpireTime(Username username, PushSubscriptionId id, ZonedDateTime newExpire) {
         return Mono.just(newExpire)
             .handle((inputTime, sink) -> {
                 if (newExpire.isBefore(ZonedDateTime.now(clock))) {
@@ -89,8 +89,8 @@ public class CassandraPushSubscriptionRepository implements PushSubscriptionRepo
             .then(retrieveByPushSubscriptionId(username, id)
                 .flatMap(subscription -> dao.insert(username,
                     subscription.withExpires(evaluateExpiresTime(Optional.of(newExpire), clock))))
-                .switchIfEmpty(Mono.error(() -> new PushSubscriptionNotFoundException(id)))
-                .then());
+                .map(PushSubscription::expires)
+                .switchIfEmpty(Mono.error(() -> new PushSubscriptionNotFoundException(id))));
     }
 
     @Override
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepository.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepository.java
index bb9c563..f8fa49f 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepository.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepository.java
@@ -25,6 +25,7 @@ import java.util.Set;
 import org.apache.james.core.Username;
 import org.apache.james.jmap.api.model.PushSubscription;
 import org.apache.james.jmap.api.model.PushSubscriptionCreationRequest;
+import org.apache.james.jmap.api.model.PushSubscriptionExpiredTime;
 import org.apache.james.jmap.api.model.PushSubscriptionId;
 import org.apache.james.jmap.api.model.TypeName;
 import org.reactivestreams.Publisher;
@@ -32,7 +33,7 @@ import org.reactivestreams.Publisher;
 public interface PushSubscriptionRepository {
     Publisher<PushSubscription> save(Username username, PushSubscriptionCreationRequest pushSubscriptionCreationRequest);
 
-    Publisher<Void> updateExpireTime(Username username, PushSubscriptionId id, ZonedDateTime newExpire);
+    Publisher<PushSubscriptionExpiredTime> updateExpireTime(Username username, PushSubscriptionId id, ZonedDateTime newExpire);
 
     Publisher<Void> updateTypes(Username username, PushSubscriptionId id, Set<TypeName> types);
 
diff --git a/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/pushsubscription/MemoryPushSubscriptionRepository.java b/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/pushsubscription/MemoryPushSubscriptionRepository.java
index c6b42ed..aff9a18 100644
--- a/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/pushsubscription/MemoryPushSubscriptionRepository.java
+++ b/server/data/data-jmap/src/main/java/org/apache/james/jmap/memory/pushsubscription/MemoryPushSubscriptionRepository.java
@@ -84,7 +84,7 @@ public class MemoryPushSubscriptionRepository implements PushSubscriptionReposit
     }
 
     @Override
-    public Publisher<Void> updateExpireTime(Username username, PushSubscriptionId id, ZonedDateTime newExpire) {
+    public Publisher<PushSubscriptionExpiredTime> updateExpireTime(Username username, PushSubscriptionId id, ZonedDateTime newExpire) {
         return Mono.just(newExpire)
             .handle((inputTime, sink) -> {
                 if (newExpire.isBefore(ZonedDateTime.now(clock))) {
@@ -92,10 +92,13 @@ public class MemoryPushSubscriptionRepository implements PushSubscriptionReposit
                 }
             })
             .then(Mono.justOrEmpty(table.get(username, id))
-                .doOnNext(pushSubscription -> table.put(username, id,
-                    pushSubscription.withExpires(evaluateExpiresTime(Optional.of(newExpire), clock))))
-                .switchIfEmpty(Mono.error(() -> new PushSubscriptionNotFoundException(id)))
-                .then());
+                .mapNotNull(pushSubscription -> {
+                    PushSubscription value = pushSubscription.withExpires(evaluateExpiresTime(Optional.of(newExpire), clock));
+                    table.put(username, id, value);
+                    return value;
+                })
+                .map(PushSubscription::expires)
+                .switchIfEmpty(Mono.error(() -> new PushSubscriptionNotFoundException(id))));
     }
 
     @Override
diff --git a/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepositoryContract.scala b/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepositoryContract.scala
index 7c87498..326626a 100644
--- a/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepositoryContract.scala
+++ b/server/data/data-jmap/src/test/scala/org/apache/james/jmap/api/pushsubscription/PushSubscriptionRepositoryContract.scala
@@ -188,6 +188,18 @@ trait PushSubscriptionRepositoryContract {
   }
 
   @Test
+  def updateWithExpiresBiggerThanMaxExpiresShouldReturnServerFixedExpires(): Unit = {
+    val validRequest = PushSubscriptionCreationRequest(
+      deviceClientId = DeviceClientId("1"),
+      url = PushSubscriptionServerURL(new URL("https://example.com/push")),
+      types = Seq(CustomTypeName1))
+    val pushSubscriptionId = SMono.fromPublisher(testee.save(ALICE, validRequest)).block().id
+    val fixedExpires = SMono.fromPublisher(testee.updateExpireTime(ALICE, pushSubscriptionId, MAX_EXPIRE.plusDays(1))).block()
+
+    assertThat(fixedExpires).isEqualTo(PushSubscriptionExpiredTime(MAX_EXPIRE))
+  }
+
+  @Test
   def updateWithValidTypesShouldSucceed(): Unit = {
     val validRequest = PushSubscriptionCreationRequest(
       deviceClientId = DeviceClientId("1"),

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