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]

Reply via email to