From ef9a2c79c6247cbae3b45906cfd6860e666267d8 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Tue, 14 Apr 2020 16:08:02 +0200 Subject: [PATCH] JacksonCborSerializer should use CBOR ofc, #28918 * When we updated to Jackson 2.10 (prior Akka 2.6.0) the new JsonFactoryBuilder was used. That doesn't preserve the formatParserFeatures and formatGeneratorFeatures from the base CBORFactory and therefore the format was plain JSON. * rolling update compatibility --- .../main/paradox/project/rolling-update.md | 29 +++++++++++++++++++ .../src/main/resources/reference.conf | 21 +++++++++++++- .../jackson/JacksonObjectMapperProvider.scala | 23 ++++++++++----- .../jackson/JacksonSerializerSpec.scala | 27 +++++++++++++++++ 4 files changed, 91 insertions(+), 9 deletions(-) diff --git a/akka-docs/src/main/paradox/project/rolling-update.md b/akka-docs/src/main/paradox/project/rolling-update.md index a83c91bf46..8c5cbbe5c8 100644 --- a/akka-docs/src/main/paradox/project/rolling-update.md +++ b/akka-docs/src/main/paradox/project/rolling-update.md @@ -86,3 +86,32 @@ has been prepared to accept those shorter forms but still emits the old long man This means that a rolling update will have to go through at least one of 2.6.2, 2.6.3 or 2.6.4 when upgrading to 2.6.5 or higher or else cluster nodes will not be able to communicate during the rolling update. + +### 2.6.5 JacksonCborSerializer + +Issue: [#28918](https://github.com/akka/akka/issues/28918). JacksonCborSerializer was using plain JSON format +instead of CBOR. + +If you have `jackson-cbor` in your `serialization-bindings` a rolling upgrade will have to go through 2.6.5 when +upgrading to 2.6.5 or higher. + +In Akka 2.6.5 the `jackson-cbor` binding will still serialize to JSON format to support rolling update from 2.6.4. +It also adds a new binding to be able to deserialize CBOR format when rolling update from 2.6.5 to 2.6.6. +In Akka 2.6.6 the `jackson-cbor` binding will serialize to CBOR and that can be deserialized by 2.6.5. Old +data, such as persistent events, can still be deserialized. + +You can start using CBOR format already with Akka 2.6.5 without waiting for the 2.6.6 release. First, perform +a rolling update to Akka 2.6.5 using default configuration. Then change the configuration to: + +``` +akka.actor { + serializers { + jackson-cbor = "akka.serialization.jackson.JacksonCborSerializer" + } + serialization-identifiers { + jackson-cbor = 33 + } +} +``` + +Perform a second rolling update with the new configuration. diff --git a/akka-serialization-jackson/src/main/resources/reference.conf b/akka-serialization-jackson/src/main/resources/reference.conf index b3d1f42a7d..1a254fbd75 100644 --- a/akka-serialization-jackson/src/main/resources/reference.conf +++ b/akka-serialization-jackson/src/main/resources/reference.conf @@ -175,6 +175,13 @@ akka.serialization.jackson { # override the settings in 'akka.serialization.jackson' jackson-cbor {} + # Issue #28918 for compatibility with data serialized with JacksonCborSerializer in + # Akka 2.6.4 or earlier, which was plain JSON format. + jackson-cbor-264 = ${akka.serialization.jackson.jackson-cbor} + + # Issue #28918 temporary in Akka 2.6.5 to support rolling update to 2.6.6. + jackson-cbor-265 = ${akka.serialization.jackson.jackson-cbor} + } #//#features @@ -195,11 +202,23 @@ akka.serialization.jackson.jackson-json.compression { akka.actor { serializers { jackson-json = "akka.serialization.jackson.JacksonJsonSerializer" - jackson-cbor = "akka.serialization.jackson.JacksonCborSerializer" + jackson-cbor = "akka.serialization.jackson.JacksonJsonSerializer" + + # Issue #28918 for compatibility with data serialized with JacksonCborSerializer in + # Akka 2.6.4 or earlier, which was plain JSON format. + jackson-cbor-264 = "akka.serialization.jackson.JacksonJsonSerializer" + # Issue #28918 temporary in Akka 2.6.5 to support rolling update to 2.6.6. + jackson-cbor-265 = "akka.serialization.jackson.JacksonCborSerializer" } serialization-identifiers { jackson-json = 31 jackson-cbor = 32 + + # Issue #28918 for compatibility with data serialized with JacksonCborSerializer in + # Akka 2.6.4 or earlier, which was plain JSON format. + jackson-cbor-264 = 32 + # Issue #28918 temporary in Akka 2.6.5 to support rolling update to 2.6.6. + jackson-cbor-265 = 33 } serialization-bindings { # Define bindings for classes or interfaces use Jackson serializer, e.g. diff --git a/akka-serialization-jackson/src/main/scala/akka/serialization/jackson/JacksonObjectMapperProvider.scala b/akka-serialization-jackson/src/main/scala/akka/serialization/jackson/JacksonObjectMapperProvider.scala index ad2cf03f0c..b101ae7738 100644 --- a/akka-serialization-jackson/src/main/scala/akka/serialization/jackson/JacksonObjectMapperProvider.scala +++ b/akka-serialization-jackson/src/main/scala/akka/serialization/jackson/JacksonObjectMapperProvider.scala @@ -11,6 +11,7 @@ import scala.collection.immutable import scala.compat.java8.OptionConverters._ import scala.util.Failure import scala.util.Success + import akka.actor.ActorSystem import akka.actor.ClassicActorSystemProvider import akka.actor.DynamicAccess @@ -70,9 +71,15 @@ object JacksonObjectMapperProvider extends ExtensionId[JacksonObjectMapperProvid config: Config, baseJsonFactory: Option[JsonFactory]): JsonFactory = { - val jsonFactoryBuilder = baseJsonFactory match { - case Some(jsonFactory) => new JsonFactoryBuilder(jsonFactory) - case None => new JsonFactoryBuilder() + val jsonFactory: JsonFactory = baseJsonFactory match { + case Some(factory) => + // Issue #28918 not possible to use new JsonFactoryBuilder(jsonFactory) here. + // It doesn't preserve the formatParserFeatures and formatGeneratorFeatures in + // CBORFactor. Therefore we use JsonFactory and configure the features with mappedFeature + // instead of using JsonFactoryBuilder (new in Jackson 2.10.0). + factory + case None => + new JsonFactoryBuilder().build() } val configuredStreamReadFeatures = @@ -82,7 +89,7 @@ object JacksonObjectMapperProvider extends ExtensionId[JacksonObjectMapperProvid val streamReadFeatures = objectMapperFactory.overrideConfiguredStreamReadFeatures(bindingName, configuredStreamReadFeatures) streamReadFeatures.foreach { - case (feature, value) => jsonFactoryBuilder.configure(feature, value) + case (feature, value) => jsonFactory.configure(feature.mappedFeature, value) } val configuredStreamWriteFeatures = @@ -92,7 +99,7 @@ object JacksonObjectMapperProvider extends ExtensionId[JacksonObjectMapperProvid val streamWriteFeatures = objectMapperFactory.overrideConfiguredStreamWriteFeatures(bindingName, configuredStreamWriteFeatures) streamWriteFeatures.foreach { - case (feature, value) => jsonFactoryBuilder.configure(feature, value) + case (feature, value) => jsonFactory.configure(feature.mappedFeature, value) } val configuredJsonReadFeatures = @@ -102,7 +109,7 @@ object JacksonObjectMapperProvider extends ExtensionId[JacksonObjectMapperProvid val jsonReadFeatures = objectMapperFactory.overrideConfiguredJsonReadFeatures(bindingName, configuredJsonReadFeatures) jsonReadFeatures.foreach { - case (feature, value) => jsonFactoryBuilder.configure(feature, value) + case (feature, value) => jsonFactory.configure(feature.mappedFeature, value) } val configuredJsonWriteFeatures = @@ -112,10 +119,10 @@ object JacksonObjectMapperProvider extends ExtensionId[JacksonObjectMapperProvid val jsonWriteFeatures = objectMapperFactory.overrideConfiguredJsonWriteFeatures(bindingName, configuredJsonWriteFeatures) jsonWriteFeatures.foreach { - case (feature, value) => jsonFactoryBuilder.configure(feature, value) + case (feature, value) => jsonFactory.configure(feature.mappedFeature, value) } - jsonFactoryBuilder.build() + jsonFactory } private def configureObjectMapperFeatures( diff --git a/akka-serialization-jackson/src/test/scala/akka/serialization/jackson/JacksonSerializerSpec.scala b/akka-serialization-jackson/src/test/scala/akka/serialization/jackson/JacksonSerializerSpec.scala index c1010dbf0d..31f6acc375 100644 --- a/akka-serialization-jackson/src/test/scala/akka/serialization/jackson/JacksonSerializerSpec.scala +++ b/akka-serialization-jackson/src/test/scala/akka/serialization/jackson/JacksonSerializerSpec.scala @@ -4,6 +4,7 @@ package akka.serialization.jackson +import java.nio.charset.StandardCharsets import java.time.Duration import java.time.Instant import java.time.LocalDateTime @@ -17,6 +18,7 @@ import java.util.logging.FileHandler import scala.collection.immutable import scala.concurrent.duration.FiniteDuration import scala.concurrent.duration._ + import akka.actor.ActorRef import akka.actor.ActorSystem import akka.actor.Address @@ -569,6 +571,17 @@ class JacksonJsonSerializerSpec extends JacksonSerializerSpec("jackson-json") { """)(sys => checkSerialization(Elephant("Dumbo", 1), sys)) } } + + // issue #28918 + "cbor compatibility for reading json" in { + val msg = SimpleCommand("abc") + val jsonSerializer = serializerFor(msg) + jsonSerializer.identifier should ===(31) + val manifest = jsonSerializer.manifest(msg) + val bytes = jsonSerializer.toBinary(msg) + val deserialized = serialization().deserialize(bytes, 32, manifest).get + deserialized should be(msg) + } } } @@ -626,6 +639,20 @@ abstract class JacksonSerializerSpec(serializerName: String) val manifest = serializer.manifest(obj) val serializerId = serializer.identifier val blob = serializeToBinary(obj) + + // Issue #28918, check that CBOR format is used (not JSON). + if (blob.length > 0) { + serializer match { + case _: JacksonJsonSerializer => + if (!JacksonSerializer.isGZipped(blob)) + new String(blob.take(1), StandardCharsets.UTF_8) should ===("{") + case _: JacksonCborSerializer => + new String(blob.take(1), StandardCharsets.UTF_8) should !==("{") + case _ => + throw new IllegalArgumentException(s"Unexpected serializer $serializer") + } + } + val deserialized = deserializeFromBinary(blob, serializerId, manifest, sys) deserialized should ===(obj) }