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
The following commit(s) were added to refs/heads/master by this push:
new 8910509 JAMES-3581 Avoid breaking changes upon JMAP event
deserialization
8910509 is described below
commit 8910509946289fa35ac68dd977f8a06888257922
Author: Benoit Tellier <[email protected]>
AuthorDate: Thu May 13 11:04:22 2021 +0700
JAMES-3581 Avoid breaking changes upon JMAP event deserialization
This enables a rolling upgrade without any backward compatibility
considerations to be
performed
---
.../james/jmap/change/JmapEventSerializer.scala | 30 +++++++++++++++++++---
.../change/StateChangeEventSerializerTest.scala | 19 +++++++++++++-
2 files changed, 44 insertions(+), 5 deletions(-)
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 f1a300b..0a29227 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
@@ -19,14 +19,18 @@
package org.apache.james.jmap.change
+import java.util.Optional
+
import com.fasterxml.jackson.annotation.JsonProperty
+import javax.inject.Inject
import org.apache.james.core.Username
import org.apache.james.events.Event.EventId
import org.apache.james.events.{Event, EventSerializer}
+import org.apache.james.jmap.core.{State, UuidState}
import org.apache.james.json.JsonGenericSerializer
-import javax.inject.Inject
import scala.jdk.CollectionConverters._
+import scala.jdk.OptionConverters._
case class StateChangeEventDTOFactory @Inject()(typeStateFactory:
TypeStateFactory) {
val dtoModule: EventDTOModule[StateChangeEvent, StateChangeEventDTO] =
EventDTOModule.forEvent(classOf[StateChangeEvent])
@@ -40,18 +44,36 @@ case class StateChangeEventDTOFactory
@Inject()(typeStateFactory: TypeStateFacto
getType = classOf[StateChangeEvent].getCanonicalName,
getEventId = event.eventId.getId.toString,
getUsername = event.username.asString,
- getTypeStates = event.map.map(element => element._1.asString() ->
element._2.serialize).asJava)
+ getEmailState = Optional.empty(),
+ getMailboxState = Optional.empty(),
+ getVacationResponseState = Optional.empty(),
+ getTypeStates = Optional.of(event.map.map(element => element._1.asString()
-> element._2.serialize).asJava))
}
case class StateChangeEventDTO(@JsonProperty("type") getType: String,
@JsonProperty("eventId") getEventId: String,
@JsonProperty("username") getUsername: String,
- @JsonProperty("typeStates") getTypeStates:
java.util.Map[String, String]) extends EventDTO {
+ // Optionals are used here to maintain backward
compatibility with old format
+ @JsonProperty("vacationResponseState")
getVacationResponseState: Optional[String],
+ @JsonProperty("mailboxState") getMailboxState:
Optional[String],
+ @JsonProperty("emailState") getEmailState:
Optional[String],
+ @JsonProperty("typeStates") getTypeStates:
Optional[java.util.Map[String, String]]) extends EventDTO {
def toDomainObject(typeStateFactory: TypeStateFactory): StateChangeEvent =
StateChangeEvent(
eventId = EventId.of(getEventId),
username = Username.of(getUsername),
- map = getTypeStates.asScala.flatMap(element =>
typeStateFactory.parse(element._1).toOption
+ map = typeStatesFromMap(typeStateFactory))
+
+ private def typeStatesFromMap(typeStateFactory: TypeStateFactory):
Map[TypeName, State] =
+ getTypeStates.toScala.map(typeStates => typeStates.asScala.flatMap(element
=> typeStateFactory.parse(element._1).toOption
.flatMap(typeName => typeName.parseState(element._2).toOption.map(state
=> typeName -> state))).toMap)
+ .getOrElse(fallbackToOldFormat())
+
+ private def fallbackToOldFormat(): Map[TypeName, State] =
+ List(
+
getEmailState.toScala.map(UuidState.fromStringUnchecked).map(EmailTypeName ->
_),
+
getMailboxState.toScala.map(UuidState.fromStringUnchecked).map(MailboxTypeName
-> _),
+
getVacationResponseState.toScala.map(UuidState.fromStringUnchecked).map(VacationResponseTypeName
-> _))
+ .flatten.toMap
}
case class JmapEventSerializer @Inject()(stateChangeEventDTOFactory:
StateChangeEventDTOFactory) extends EventSerializer {
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 d48b409..ce4aa40 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,7 +21,7 @@ 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_EMPTY_TYPE_STATE_MAP, EVENT_JSON, EVENT_JSON_EMPTY_TYPE_STATE_MAP,
EVENT_JSON_NO_DELIVERY, EVENT_NO_DELIVERY}
+import org.apache.james.jmap.change.StateChangeEventSerializerTest.{EVENT,
EVENT_EMPTY_TYPE_STATE_MAP, EVENT_ID, EVENT_JSON,
EVENT_JSON_EMPTY_TYPE_STATE_MAP, EVENT_JSON_NO_DELIVERY, EVENT_NO_DELIVERY,
USERNAME}
import org.apache.james.jmap.core.UuidState
import org.apache.james.json.JsonGenericSerializer
import org.apache.james.json.JsonGenericSerializer.UnknownTypeException
@@ -112,6 +112,23 @@ class StateChangeEventSerializerTest {
.isInstanceOf(classOf[UnknownTypeException])
@Test
+ def shouldDeserializePreviousFormat(): Unit =
+ assertThat(JsonGenericSerializer
+ .forModules(stateChangeEventDTOFactory.dtoModule)
+ .withoutNestedType()
+ .deserialize("""{
+ | "eventId":
"6e0dd59d-660e-4d9b-b22f-0354479f47b4",
+ | "username": "bob",
+ | "type":
"org.apache.james.jmap.change.StateChangeEvent",
+ | "emailState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
+ | "mailboxState":
"2c9f1b12-aaaa-bbbb-cccc-0106fb53a943"
+ |}""".stripMargin))
+ .isEqualTo(StateChangeEvent(eventId = EVENT_ID,
+ username = USERNAME,
+ map = Map(MailboxTypeName
->UuidState.fromStringUnchecked("2c9f1b12-aaaa-bbbb-cccc-0106fb53a943"),
+ EmailTypeName ->
UuidState.fromStringUnchecked("2c9f1b12-b35a-43e6-9af2-0106fb53a943"))))
+
+ @Test
def shouldDeserializeWhenAnOptionalFieldIsMissing(): Unit =
assertThat(
JsonGenericSerializer
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]