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/02/09 04:29:57 UTC

[james-project] 29/33: JAMES-3491 Add VacationResponse push notification support

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 438191a3bc3d7d4569395a586ac68c0fb97f90e4
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Feb 5 16:01:17 2021 +0700

    JAMES-3491 Add VacationResponse push notification support
---
 .../jmap/rfc8621/contract/WebSocketContract.scala  | 55 ++++++++++++++++++++++
 .../james/jmap/change/JmapEventSerializer.scala    |  3 ++
 .../james/jmap/change/MailboxChangeListener.scala  |  2 +
 .../org/apache/james/jmap/change/StateChange.scala |  9 +++-
 .../jmap/method/VacationResponseSetMethod.scala    | 24 ++++++++--
 .../change/StateChangeEventSerializerTest.scala    |  5 +-
 .../jmap/change/StateChangeListenerTest.scala      | 12 +++--
 7 files changed, 101 insertions(+), 9 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/WebSocketContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebSocketContract.scala
index c8897ba..b8f443a 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebSocketContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/WebSocketContract.scala
@@ -515,6 +515,61 @@ trait WebSocketContract {
 
   @Test
   @Timeout(180)
+  def pushShouldHandleVacationResponses(server: GuiceJamesServer): Unit = {
+    val response: Either[String, List[String]] =
+      authenticatedRequest(server)
+        .response(asWebSocket[Identity, List[String]] {
+          ws =>
+            ws.send(WebSocketFrame.text(
+              """{
+                |  "@type": "WebSocketPushEnable",
+                |  "dataTypes": ["VacationResponse"]
+                |}""".stripMargin))
+
+            Thread.sleep(100)
+
+            ws.send(WebSocketFrame.text(
+              s"""{
+                 |  "@type": "Request",
+                 |  "requestId": "req-36",
+                 |  "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail", "urn:ietf:params:jmap:vacationresponse"],
+                 |  "methodCalls": [
+                 |    ["VacationResponse/set", {
+                 |      "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+                 |      "update": {
+                 |        "singleton": {
+                 |          "isEnabled": true,
+                 |          "fromDate": "2014-10-30T14:12:00Z",
+                 |          "toDate": "2014-11-30T14:12:00Z",
+                 |          "subject": "I am in vacation",
+                 |          "textBody": "I'm currently enjoying life. Please disturb me later",
+                 |          "htmlBody": "I'm currently enjoying <b>life</b>. <br/>Please disturb me later"
+                 |        }
+                 |      }
+                 |    }, "c1"]]
+                 |}""".stripMargin))
+
+            List(
+              ws.receive()
+                .map { case t: Text =>
+                  t.payload
+                },
+              ws.receive()
+                .map { case t: Text =>
+                  t.payload
+                })
+        })
+        .send(backend)
+        .body
+
+    assertThat(response.toOption.get.asJava).hasSize(2) // vacation notification + API response
+    assertThat(response.toOption.get.filter(s => s.contains(""""@type":"StateChange"""")).asJava)
+      .hasSize(1)
+      .allMatch(s => s.contains("VacationResponse"))
+  }
+
+  @Test
+  @Timeout(180)
   // For client compatibility purposes
   def specifiedUnHandledDataTypesShouldNotBeRejected(server: GuiceJamesServer): Unit = {
     val bobPath = MailboxPath.inbox(BOB)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/JmapEventSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/JmapEventSerializer.scala
index 7c38b41..b794bc2 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/JmapEventSerializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/JmapEventSerializer.scala
@@ -44,18 +44,21 @@ object StateChangeEventDTO {
     getUsername = event.username.asString(),
     getMailboxState = event.mailboxState.map(_.value).map(_.toString).toJava,
     getEmailState = event.emailState.map(_.value).map(_.toString).toJava,
+    getVacationResponseState = event.vacationResponseState.map(_.value).map(_.toString).toJava,
     getEmailDeliveryState = event.emailDeliveryState.map(_.value).map(_.toString).toJava)
 }
 
 case class StateChangeEventDTO(@JsonProperty("type") getType: String,
                                @JsonProperty("eventId") getEventId: String,
                                @JsonProperty("username") getUsername: String,
+                               @JsonProperty("vacationResponseState") getVacationResponseState: Optional[String],
                                @JsonProperty("mailboxState") getMailboxState: Optional[String],
                                @JsonProperty("emailState") getEmailState: Optional[String],
                                @JsonProperty("emailDeliveryState") getEmailDeliveryState: Optional[String]) extends EventDTO {
   def toDomainObject: StateChangeEvent = StateChangeEvent(
     eventId = EventId.of(getEventId),
     username = Username.of(getUsername),
+    vacationResponseState = getVacationResponseState.toScala.map(State.fromStringUnchecked),
     mailboxState = getMailboxState.toScala.map(State.fromStringUnchecked),
     emailState = getEmailState.toScala.map(State.fromStringUnchecked),
     emailDeliveryState = getEmailDeliveryState.toScala.map(State.fromStringUnchecked))
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/MailboxChangeListener.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/MailboxChangeListener.scala
index ad68fb2..8a5f7e2 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/MailboxChangeListener.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/MailboxChangeListener.scala
@@ -126,6 +126,7 @@ case class MailboxChangeListener @Inject() (@Named(InjectionKeys.JMAP) eventBus:
     case emailChange: EmailChange => StateChangeEvent(
       eventId = EventId.random(),
       username = Username.of(emailChange.getAccountId.getIdentifier),
+      vacationResponseState = None,
       emailState = Some(State.fromJava(emailChange.getState)),
       emailDeliveryState = Some(State.fromJava(emailChange.getState))
         .filter(_ => !emailChange.getCreated.isEmpty),
@@ -133,6 +134,7 @@ case class MailboxChangeListener @Inject() (@Named(InjectionKeys.JMAP) eventBus:
     case mailboxChange: MailboxChange => StateChangeEvent(
       eventId = EventId.random(),
       username = Username.of(mailboxChange.getAccountId.getIdentifier),
+      vacationResponseState = None,
       emailState = None,
       emailDeliveryState = None,
       mailboxState = Some(State.fromJava(mailboxChange.getState)))
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/StateChange.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/StateChange.scala
index c53792a..08c2811 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/StateChange.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/change/StateChange.scala
@@ -68,6 +68,7 @@ case class TypeState(changes: Map[TypeName, State]) {
 
 case class StateChangeEvent(eventId: EventId,
                             username: Username,
+                            vacationResponseState: Option[State],
                             mailboxState: Option[State],
                             emailState: Option[State],
                             emailDeliveryState: Option[State]) extends Event {
@@ -76,13 +77,17 @@ case class StateChangeEvent(eventId: EventId,
       failure => throw new IllegalArgumentException(failure),
       success => success) ->
       TypeState(
-        MailboxTypeName.asMap(mailboxState) ++
+        VacationResponseTypeName.asMap(vacationResponseState) ++
+          MailboxTypeName.asMap(mailboxState) ++
           EmailDeliveryTypeName.asMap(emailDeliveryState) ++
           EmailTypeName.asMap(emailState))))
 
   override val getUsername: Username = username
 
-  override val isNoop: Boolean = mailboxState.isEmpty && emailState.isEmpty
+  override val isNoop: Boolean = mailboxState.isEmpty &&
+    emailState.isEmpty &&
+    vacationResponseState.isEmpty &&
+    emailDeliveryState.isEmpty
 
   override val getEventId: EventId = eventId
 }
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseSetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseSetMethod.scala
index 4f0ff2a..feccf06 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseSetMethod.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/VacationResponseSetMethod.scala
@@ -19,11 +19,17 @@
 
 package org.apache.james.jmap.method
 
+import java.util.UUID
+
 import eu.timepit.refined.auto._
-import javax.inject.Inject
+import javax.inject.{Inject, Named}
+import org.apache.james.events.Event.EventId
+import org.apache.james.events.EventBus
+import org.apache.james.jmap.InjectionKeys
 import org.apache.james.jmap.api.model.AccountId
 import org.apache.james.jmap.api.vacation.{VacationPatch, VacationRepository}
-import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_MAIL, JMAP_VACATION_RESPONSE}
+import org.apache.james.jmap.change.{AccountIdRegistrationKey, StateChangeEvent}
+import org.apache.james.jmap.core.CapabilityIdentifier.{CapabilityIdentifier, JMAP_CORE, JMAP_VACATION_RESPONSE}
 import org.apache.james.jmap.core.Invocation.{Arguments, MethodName}
 import org.apache.james.jmap.core.SetError.SetErrorDescription
 import org.apache.james.jmap.core.{Invocation, State}
@@ -70,7 +76,8 @@ object VacationResponseSetMethod {
   val VACATION_RESPONSE_PATCH_OBJECT_KEY = "singleton"
 }
 
-class VacationResponseSetMethod @Inject()(vacationRepository: VacationRepository,
+class VacationResponseSetMethod @Inject()(@Named(InjectionKeys.JMAP) eventBus: EventBus,
+                                          vacationRepository: VacationRepository,
                                           val metricFactory: MetricFactory,
                                           val sessionSupplier: SessionSupplier) extends MethodRequiringAccountId[VacationResponseSetRequest] {
   override val methodName: MethodName = MethodName("VacationResponse/set")
@@ -79,6 +86,17 @@ class VacationResponseSetMethod @Inject()(vacationRepository: VacationRepository
   override def doProcess(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession, request: VacationResponseSetRequest): SMono[InvocationWithContext] = {
     update(mailboxSession, request)
       .map(updateResult => createResponse(invocation.invocation, request, updateResult))
+      .flatMap(next => {
+        val event = StateChangeEvent(eventId = EventId.random(),
+          mailboxState = None,
+          emailState = None,
+          emailDeliveryState = None,
+          username = mailboxSession.getUser,
+          vacationResponseState = Some(State(UUID.randomUUID())))
+        val accountId = AccountId.fromUsername(mailboxSession.getUser)
+        SMono(eventBus.dispatch(event, AccountIdRegistrationKey(accountId)))
+          .`then`(SMono.just(next))
+      })
       .map(InvocationWithContext(_, invocation.processingContext))
   }
 
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/StateChangeEventSerializerTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/StateChangeEventSerializerTest.scala
index 7f51aa8..07da1c5 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/StateChangeEventSerializerTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/StateChangeEventSerializerTest.scala
@@ -36,6 +36,7 @@ object StateChangeEventSerializerTest {
     username = USERNAME,
     mailboxState = Some(State.INSTANCE),
     emailState = Some(State.fromStringUnchecked("2d9f1b12-b35a-43e6-9af2-0106fb53a943")),
+    vacationResponseState = Some(State.fromStringUnchecked("2d9f1b12-3333-4444-5555-0106fb53a943")),
     emailDeliveryState = Some(State.fromStringUnchecked("2d9f1b12-0000-1111-3333-0106fb53a943")))
   val EVENT_JSON: String =
     """{
@@ -44,13 +45,15 @@ object StateChangeEventSerializerTest {
       |  "mailboxState":"2c9f1b12-b35a-43e6-9af2-0106fb53a943",
       |  "emailState":"2d9f1b12-b35a-43e6-9af2-0106fb53a943",
       |  "emailDeliveryState":"2d9f1b12-0000-1111-3333-0106fb53a943",
+      |  "vacationResponseState":"2d9f1b12-3333-4444-5555-0106fb53a943",
       |  "type":"org.apache.james.jmap.change.StateChangeEvent"
       |}""".stripMargin
   val EVENT_NO_DELIVERY: StateChangeEvent = StateChangeEvent(eventId = EVENT_ID,
     username = USERNAME,
     mailboxState = Some(State.INSTANCE),
     emailState = Some(State.fromStringUnchecked("2d9f1b12-b35a-43e6-9af2-0106fb53a943")),
-    emailDeliveryState = None)
+    emailDeliveryState = None,
+    vacationResponseState = None)
   val EVENT_JSON_NO_DELIVERY: String =
     """{
       |  "eventId":"6e0dd59d-660e-4d9b-b22f-0354479f47b4",
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/StateChangeListenerTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/StateChangeListenerTest.scala
index efa5775..2b504bf 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/StateChangeListenerTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/change/StateChangeListenerTest.scala
@@ -40,7 +40,9 @@ class StateChangeListenerTest {
     val event = StateChangeEvent(eventId = eventId,
       username = Username.of("bob"),
       mailboxState = Some(mailboxState),
-      emailState = Some(emailState))
+      emailState = Some(emailState),
+      vacationResponseState = None,
+      emailDeliveryState = None)
     val listener = StateChangeListener(Set(MailboxTypeName, EmailTypeName), sink)
 
     SMono(listener.reactiveEvent(event)).subscribeOn(Schedulers.elastic()).block()
@@ -58,7 +60,9 @@ class StateChangeListenerTest {
     val event = StateChangeEvent(eventId = eventId,
       username = Username.of("bob"),
       mailboxState = Some(mailboxState),
-      emailState = Some(emailState))
+      emailState = Some(emailState),
+      vacationResponseState = None,
+      emailDeliveryState = None)
     val listener = StateChangeListener(Set(MailboxTypeName), sink)
 
     SMono(listener.reactiveEvent(event)).subscribeOn(Schedulers.elastic()).block()
@@ -75,7 +79,9 @@ class StateChangeListenerTest {
     val event = StateChangeEvent(eventId = eventId,
       username = Username.of("bob"),
       mailboxState = None,
-      emailState = Some(emailState))
+      emailState = Some(emailState),
+      vacationResponseState = None,
+      emailDeliveryState = None)
     val listener = StateChangeListener(Set(MailboxTypeName), sink)
 
     SMono(listener.reactiveEvent(event)).subscribeOn(Schedulers.elastic()).block()


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