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 <[email protected]>
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: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to