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