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:56 UTC
[james-project] 28/33: JAMES-3491 Add EmailDelivery 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 4c1ac1b344238cf36beda63e4bfd8043a35e0291
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Feb 5 14:25:04 2021 +0700
JAMES-3491 Add EmailDelivery push notification support
---
.../jmap/rfc8621/contract/WebSocketContract.scala | 128 ++++++++++++++++++++-
.../james/jmap/change/JmapEventSerializer.scala | 9 +-
.../james/jmap/change/MailboxChangeListener.scala | 3 +
.../org/apache/james/jmap/change/StateChange.scala | 4 +-
.../change/StateChangeEventSerializerTest.scala | 33 +++++-
5 files changed, 168 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 e477607..c8897ba 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
@@ -577,7 +577,7 @@ trait WebSocketContract {
val mailboxState: String = jmapGuiceProbe.getLatestMailboxState(accountId).getValue.toString
val mailboxStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Mailbox":"$mailboxState"}}}"""
- val emailStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Email":"$emailState"}}}"""
+ val emailStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"EmailDelivery":"$emailState","Email":"$emailState"}}}"""
assertThat(response.toOption.get.asJava)
.hasSize(3) // email notification + mailbox notification + API response
@@ -586,6 +586,130 @@ trait WebSocketContract {
@Test
@Timeout(180)
+ // For client compatibility purposes
+ def emailDeliveryShouldNotIncludeFlagUpdatesAndDeletes(server: GuiceJamesServer): Unit = {
+ val bobPath = MailboxPath.inbox(BOB)
+ val accountId: AccountId = AccountId.fromUsername(BOB)
+ val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
+
+ Thread.sleep(100)
+
+ val response: Either[String, List[String]] =
+ authenticatedRequest(server)
+ .response(asWebSocket[Identity, List[String]] {
+ ws =>
+ ws.send(WebSocketFrame.text(
+ s"""{
+ | "@type": "Request",
+ | "requestId": "req-36",
+ | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [
+ | ["Email/set", {
+ | "accountId": "$ACCOUNT_ID",
+ | "create": {
+ | "aaaaaa":{
+ | "mailboxIds": {
+ | "${mailboxId.serialize}": true
+ | }
+ | }
+ | }
+ | }, "c1"]]
+ |}""".stripMargin))
+
+ val responseAsJson = Json.parse(ws.receive()
+ .map { case t: Text =>
+ t.payload
+ })
+ .\("methodResponses")
+ .\(0).\(1)
+ .\("created")
+ .\("aaaaaa")
+
+ val messageId = responseAsJson
+ .\("id")
+ .get.asInstanceOf[JsString].value
+
+ Thread.sleep(100)
+
+ ws.send(WebSocketFrame.text(
+ """{
+ | "@type": "WebSocketPushEnable",
+ | "dataTypes": ["Mailbox", "Email", "VacationResponse", "Thread", "Identity", "EmailSubmission", "EmailDelivery"]
+ |}""".stripMargin))
+
+ Thread.sleep(100)
+
+ ws.send(WebSocketFrame.text(
+ s"""{
+ | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+ | "@type": "Request",
+ | "methodCalls": [
+ | ["Email/set", {
+ | "accountId": "$ACCOUNT_ID",
+ | "update": {
+ | "$messageId":{
+ | "keywords": {
+ | "music": true
+ | }
+ | }
+ | }
+ | }, "c1"]]
+ |}""".stripMargin))
+
+ val stateChange1 = ws.receive()
+ .map { case t: Text =>
+ t.payload
+ }
+ val response1 =
+ ws.receive()
+ .map { case t: Text =>
+ t.payload
+ }
+
+ Thread.sleep(100)
+
+ ws.send(WebSocketFrame.text(
+ s"""{
+ | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+ | "@type": "Request",
+ | "methodCalls": [
+ | ["Email/set", {
+ | "accountId": "$ACCOUNT_ID",
+ | "destroy": ["$messageId"]
+ | }, "c1"]]
+ |}""".stripMargin))
+
+ Thread.sleep(100)
+
+ val stateChange2 = ws.receive()
+ .map { case t: Text =>
+ t.payload
+ }
+ val stateChange3 =
+ ws.receive()
+ .map { case t: Text =>
+ t.payload
+ }
+ val response2 =
+ ws.receive()
+ .map { case t: Text =>
+ t.payload
+ }
+
+ List(response1, response2, stateChange1, stateChange2, stateChange3)
+ })
+ .send(backend)
+ .body
+
+ assertThat(response.toOption.get.asJava)
+ .hasSize(5) // update flags response + email state change notif + destroy response + email state change notif + mailbox state change notif (count)
+ assertThat(response.toOption.get.filter(s => s.startsWith("{\"@type\":\"StateChange\"")).asJava)
+ .hasSize(3)
+ .noneMatch(s => s.contains("EmailDelivery"))
+ }
+
+ @Test
+ @Timeout(180)
def dataTypesShouldDefaultToAll(server: GuiceJamesServer): Unit = {
val bobPath = MailboxPath.inbox(BOB)
val accountId: AccountId = AccountId.fromUsername(BOB)
@@ -647,7 +771,7 @@ trait WebSocketContract {
val mailboxState: String = jmapGuiceProbe.getLatestMailboxState(accountId).getValue.toString
val mailboxStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Mailbox":"$mailboxState"}}}"""
- val emailStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"Email":"$emailState"}}}"""
+ val emailStateChange: String = s"""{"@type":"StateChange","changed":{"$ACCOUNT_ID":{"EmailDelivery":"$emailState","Email":"$emailState"}}}"""
assertThat(response.toOption.get.asJava)
.hasSize(3) // email notification + mailbox notification + API response
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 ea5461e..7c38b41 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
@@ -43,19 +43,22 @@ object StateChangeEventDTO {
getEventId = event.eventId.getId.toString,
getUsername = event.username.asString(),
getMailboxState = event.mailboxState.map(_.value).map(_.toString).toJava,
- getEmailState = event.emailState.map(_.value).map(_.toString).toJava)
+ getEmailState = event.emailState.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("mailboxState") getMailboxState: Optional[String],
- @JsonProperty("emailState") getEmailState: Optional[String]) extends EventDTO {
+ @JsonProperty("emailState") getEmailState: Optional[String],
+ @JsonProperty("emailDeliveryState") getEmailDeliveryState: Optional[String]) extends EventDTO {
def toDomainObject: StateChangeEvent = StateChangeEvent(
eventId = EventId.of(getEventId),
username = Username.of(getUsername),
mailboxState = getMailboxState.toScala.map(State.fromStringUnchecked),
- emailState = getEmailState.toScala.map(State.fromStringUnchecked))
+ emailState = getEmailState.toScala.map(State.fromStringUnchecked),
+ emailDeliveryState = getEmailDeliveryState.toScala.map(State.fromStringUnchecked))
}
case class JmapEventSerializer() extends EventSerializer {
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 6e57c4f..ad68fb2 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
@@ -127,11 +127,14 @@ case class MailboxChangeListener @Inject() (@Named(InjectionKeys.JMAP) eventBus:
eventId = EventId.random(),
username = Username.of(emailChange.getAccountId.getIdentifier),
emailState = Some(State.fromJava(emailChange.getState)),
+ emailDeliveryState = Some(State.fromJava(emailChange.getState))
+ .filter(_ => !emailChange.getCreated.isEmpty),
mailboxState = None)
case mailboxChange: MailboxChange => StateChangeEvent(
eventId = EventId.random(),
username = Username.of(mailboxChange.getAccountId.getIdentifier),
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 faffe49..c53792a 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
@@ -69,13 +69,15 @@ case class TypeState(changes: Map[TypeName, State]) {
case class StateChangeEvent(eventId: EventId,
username: Username,
mailboxState: Option[State],
- emailState: Option[State]) extends Event {
+ emailState: Option[State],
+ emailDeliveryState: Option[State]) extends Event {
def asStateChange: StateChange =
StateChange(Map(AccountId.from(username).fold(
failure => throw new IllegalArgumentException(failure),
success => success) ->
TypeState(
MailboxTypeName.asMap(mailboxState) ++
+ EmailDeliveryTypeName.asMap(emailDeliveryState) ++
EmailTypeName.asMap(emailState))))
override val getUsername: Username = username
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 cab2643..7f51aa8 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
@@ -21,24 +21,42 @@ package org.apache.james.jmap.change
import org.apache.james.JsonSerializationVerifier
import org.apache.james.core.Username
import org.apache.james.events.Event.EventId
-import org.apache.james.jmap.change.StateChangeEventSerializerTest.{EVENT, EVENT_JSON}
+import org.apache.james.jmap.change.StateChangeEventSerializerTest.{EVENT, EVENT_JSON, EVENT_JSON_NO_DELIVERY, EVENT_NO_DELIVERY}
import org.apache.james.jmap.core.State
import org.apache.james.json.JsonGenericSerializer
import org.apache.james.json.JsonGenericSerializer.UnknownTypeException
-import org.assertj.core.api.Assertions.assertThatThrownBy
+import org.assertj.core.api.Assertions.{assertThat, assertThatThrownBy}
import org.junit.jupiter.api.Test
object StateChangeEventSerializerTest {
val EVENT_ID: EventId = EventId.of("6e0dd59d-660e-4d9b-b22f-0354479f47b4")
val USERNAME: Username = Username.of("bob")
- val EVENT: StateChangeEvent = StateChangeEvent(EVENT_ID, USERNAME, Some(State.INSTANCE), Some(State.fromStringUnchecked("2d9f1b12-b35a-43e6-9af2-0106fb53a943")))
+ val EVENT: StateChangeEvent = StateChangeEvent(eventId = EVENT_ID,
+ username = USERNAME,
+ mailboxState = Some(State.INSTANCE),
+ emailState = Some(State.fromStringUnchecked("2d9f1b12-b35a-43e6-9af2-0106fb53a943")),
+ emailDeliveryState = Some(State.fromStringUnchecked("2d9f1b12-0000-1111-3333-0106fb53a943")))
val EVENT_JSON: String =
"""{
| "eventId":"6e0dd59d-660e-4d9b-b22f-0354479f47b4",
| "username":"bob",
| "mailboxState":"2c9f1b12-b35a-43e6-9af2-0106fb53a943",
| "emailState":"2d9f1b12-b35a-43e6-9af2-0106fb53a943",
+ | "emailDeliveryState":"2d9f1b12-0000-1111-3333-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)
+ val EVENT_JSON_NO_DELIVERY: String =
+ """{
+ | "eventId":"6e0dd59d-660e-4d9b-b22f-0354479f47b4",
+ | "username":"bob",
+ | "mailboxState":"2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+ | "emailState":"2d9f1b12-b35a-43e6-9af2-0106fb53a943",
| "type":"org.apache.james.jmap.change.StateChangeEvent"
|}""".stripMargin
}
@@ -67,4 +85,13 @@ class StateChangeEventSerializerTest {
| "type":"org.apache.james.jmap.change.Unknown"
|}""".stripMargin))
.isInstanceOf(classOf[UnknownTypeException])
+
+ @Test
+ def shouldDeserializeWhenAnOptionalFieldIsMissing(): Unit =
+ assertThat(
+ JsonGenericSerializer
+ .forModules(StateChangeEventDTO.dtoModule)
+ .withoutNestedType()
+ .deserialize(EVENT_JSON_NO_DELIVERY.stripMargin))
+ .isEqualTo(EVENT_NO_DELIVERY)
}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org