Skip to content

Commit

Permalink
fix: moving @migration to java-sdk-protobuf (#1780)
Browse files Browse the repository at this point in the history
* fix: moving @migration to java-sdk-protobuf

* fixing tests

* fixing samples
  • Loading branch information
aludwiko authored Aug 29, 2023
1 parent dfe7e7e commit e40a450
Show file tree
Hide file tree
Showing 12 changed files with 33 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,26 @@
import com.google.protobuf.Any;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import customer.domain.schemaevolution.AddressChangedMigration;
import customer.domain.schemaevolution.NameChangedMigration;
import kalix.javasdk.JsonSupport;
import org.junit.jupiter.api.Test;

import java.util.Base64;
import java.util.Optional;

import static customer.domain.schemaevolution.CustomerEvent.*;
import static org.junit.jupiter.api.Assertions.*;
import static customer.domain.schemaevolution.CustomerEvent.AddressChanged;
import static customer.domain.schemaevolution.CustomerEvent.CustomerCreated;
import static customer.domain.schemaevolution.CustomerEvent.NameChanged;
import static org.junit.jupiter.api.Assertions.assertEquals;

class CustomerEventSerializationTest {

@Test
public void shouldDeserializeWithOptionalField() {
//given
Any serialized = JsonSupport.encodeJson(new CustomerEvent.NameChanged("andre"));

//when
NameChanged deserialized = JsonSupport.decodeJson(NameChanged.class, serialized);

//then
assertEquals("andre", deserialized.newName());
assertEquals(Optional.empty(), deserialized.oldName());
assertEquals(null, deserialized.reason());
}

@Test
public void shouldDeserializeWithMandatoryField() {
//given
Any serialized = JsonSupport.encodeJson(new CustomerEvent.NameChanged("andre"));

//when
NameChanged deserialized = JsonSupport.decodeJson(NameChanged.class, serialized, Optional.of(new NameChangedMigration()));
NameChanged deserialized = JsonSupport.decodeJson(NameChanged.class, serialized);

//then
assertEquals("andre", deserialized.newName());
Expand All @@ -51,7 +37,7 @@ public void shouldDeserializeWithChangedFieldName() {
Any serialized = JsonSupport.encodeJson(new CustomerEvent.AddressChanged(address));

//when
AddressChanged deserialized = JsonSupport.decodeJson(AddressChanged.class, serialized, Optional.of(new AddressChangedMigration()));
AddressChanged deserialized = JsonSupport.decodeJson(AddressChanged.class, serialized);

//then
assertEquals(address, deserialized.newAddress());
Expand All @@ -63,7 +49,7 @@ public void shouldDeserializeWithStructureMigration() {
Any serialized = JsonSupport.encodeJson(new CustomerCreated("[email protected]", "bob", "Wall Street", "New York"));

//when
CustomerEvent.CustomerCreated deserialized = JsonSupport.decodeJson(CustomerEvent.CustomerCreated.class, serialized, Optional.of(new CustomerCreatedMigration()));
CustomerEvent.CustomerCreated deserialized = JsonSupport.decodeJson(CustomerEvent.CustomerCreated.class, serialized);

//then
assertEquals("Wall Street", deserialized.address().street());
Expand All @@ -84,8 +70,7 @@ public void shouldDeserializeCustomerCreated_V0() throws InvalidProtocolBufferEx
Any serializedAny = Any.parseFrom(ByteString.copyFrom(bytes)); // <2>

CustomerEvent.CustomerCreated deserialized = JsonSupport.decodeJson(CustomerEvent.CustomerCreated.class,
serializedAny,
Optional.of(new CustomerCreatedMigration())); // <3>
serializedAny); // <3>

assertEquals("Wall Street", deserialized.address().street());
assertEquals("New York", deserialized.address().city());
Expand Down
30 changes: 11 additions & 19 deletions sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/JsonSupport.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@
import com.google.protobuf.Any;
import com.google.protobuf.ByteString;
import com.google.protobuf.UnsafeByteOperations;
import kalix.javasdk.annotations.Migration;
import kalix.javasdk.impl.ByteStringEncoding;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.Optional;

Expand Down Expand Up @@ -85,7 +87,8 @@ public static ObjectMapper getObjectMapper() {
return objectMapper;
}

private JsonSupport() {};
private JsonSupport() {
}

/**
* Encode the given value as JSON using Jackson and put the encoded string as bytes in a protobuf
Expand Down Expand Up @@ -138,21 +141,6 @@ public static <T> Any encodeJson(T value, String jsonType) {
* @throws IllegalArgumentException if the given value cannot be decoded to a T
*/
public static <T> T decodeJson(Class<T> valueClass, Any any) {
return decodeJson(valueClass, any, Optional.empty());
}

/**
* Decode the given protobuf Any object to an instance of T using Jackson. The object must have
* the JSON string as bytes as value and a type URL starting with "json.kalix.io/".
*
* @param valueClass The type of class to deserialize the object to, the class must have the
* proper Jackson annotations for deserialization.
* @param any The protobuf Any object to deserialize.
* @param jacksonMigration The optional @{@link JsonMigration} implementation used for deserialization.
* @return The decoded object
* @throws IllegalArgumentException if the given value cannot be decoded to a T
*/
public static <T> T decodeJson(Class<T> valueClass, Any any, Optional<? extends JsonMigration> jacksonMigration) {
if (!any.getTypeUrl().startsWith(KALIX_JSON)) {
throw new IllegalArgumentException(
"Protobuf bytes with type url ["
Expand All @@ -163,9 +151,12 @@ public static <T> T decodeJson(Class<T> valueClass, Any any, Optional<? extends
} else {
try {
ByteString decodedBytes = ByteStringEncoding.decodePrimitiveBytes(any.getValue());
if (jacksonMigration.isPresent()) {
if (valueClass.getAnnotation(Migration.class) != null) {
JsonMigration migration = valueClass.getAnnotation(Migration.class)
.value()
.getConstructor()
.newInstance();
int fromVersion = parseVersion(any.getTypeUrl());
JsonMigration migration = jacksonMigration.get();
int currentVersion = migration.currentVersion();
int supportedForwardVersion = migration.supportedForwardVersion();
if (fromVersion < currentVersion) {
Expand All @@ -181,7 +172,8 @@ public static <T> T decodeJson(Class<T> valueClass, Any any, Optional<? extends
} else {
return objectMapper.readValue(decodedBytes.toByteArray(), valueClass);
}
} catch (IOException e) {
} catch (IOException | NoSuchMethodException | InstantiationException | IllegalAccessException |
InvocationTargetException e) {
throw new IllegalArgumentException(
"JSON with type url ["
+ any.getTypeUrl()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@

/**
* Annotation to assign a @{@link JsonMigration} implementation for a given class.
* Can be combined with @{@link TypeName} annotation.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import kalix.javasdk.annotations.Migration;

import java.util.Objects;
import java.util.Optional;


@Migration(DummyClassMigration.class)
public class DummyClass {
public String stringValue;
public int intValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import kalix.javasdk.annotations.Migration;

import java.util.Objects;

@Migration(DummyClass2Migration.class)
public class DummyClass2 {
public String stringValue;
public int intValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class JsonSupportSpec extends AnyWordSpec with Matchers {
val dummyClass = new DummyClass("123", 321, Optional.of("test"))
val any = JsonSupport.encodeJson(dummyClass)
any.getTypeUrl should ===(JsonSupport.KALIX_JSON + classOf[DummyClass].getName)
val decoded = JsonSupport.decodeJson(classOf[DummyClass], any, Optional.of(new DummyClassMigration))
val decoded = JsonSupport.decodeJson(classOf[DummyClass], any)
decoded shouldBe dummyClass
}

Expand All @@ -59,7 +59,7 @@ class JsonSupportSpec extends AnyWordSpec with Matchers {
val any =
Any.newBuilder.setTypeUrl(JsonSupport.KALIX_JSON + classOf[DummyClass].getName).setValue(encodedBytes).build

val decoded = JsonSupport.decodeJson(classOf[DummyClass], any, Optional.of(new DummyClassMigration))
val decoded = JsonSupport.decodeJson(classOf[DummyClass], any)
decoded shouldBe new DummyClass("123", 321, Optional.empty())
}

Expand All @@ -70,7 +70,7 @@ class JsonSupportSpec extends AnyWordSpec with Matchers {
val any =
Any.newBuilder.setTypeUrl(JsonSupport.KALIX_JSON + classOf[DummyClass].getName).setValue(encodedBytes).build

val decoded = JsonSupport.decodeJson(classOf[DummyClass], any, Optional.of(new DummyClassMigration))
val decoded = JsonSupport.decodeJson(classOf[DummyClass], any)
decoded shouldBe new DummyClass("123", 321, Optional.empty())
}

Expand All @@ -80,7 +80,7 @@ class JsonSupportSpec extends AnyWordSpec with Matchers {
val any =
Any.newBuilder.setTypeUrl(JsonSupport.KALIX_JSON + classOf[DummyClass2].getName).setValue(encodedBytes).build

val decoded = JsonSupport.decodeJson(classOf[DummyClass2], any, Optional.of(new DummyClass2Migration))
val decoded = JsonSupport.decodeJson(classOf[DummyClass2], any)
decoded shouldBe new DummyClass2("123", 321, "mandatory-value")
}

Expand All @@ -104,7 +104,7 @@ class JsonSupportSpec extends AnyWordSpec with Matchers {
.setValue(encodedBytes)
.build

val decoded = JsonSupport.decodeJson(classOf[DummyClass], any, Optional.of(new DummyClassMigration))
val decoded = JsonSupport.decodeJson(classOf[DummyClass], any)
decoded shouldBe new DummyClass("123", 321, Optional.of("value"))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import kalix.javasdk.JsonSupport
import kalix.javasdk.annotations.Migration
import kalix.javasdk.annotations.TypeName
import kalix.javasdk.impl.AnySupport.BytesPrimitive
import kalix.javasdk.impl.reflection.MigrationExtractor.extractMigration
import org.slf4j.LoggerFactory

private[kalix] class JsonMessageCodec extends MessageCodec {
Expand Down Expand Up @@ -161,7 +160,7 @@ private[kalix] class StrictJsonMessageCodec(delegate: JsonMessageCodec) extends
if (typeClass == null) {
throw new IllegalStateException(s"Cannot decode ${value.typeUrl} message type. Class mapping not found.")
} else {
JsonSupport.decodeJson(typeClass, any, extractMigration(typeClass))
JsonSupport.decodeJson(typeClass, any)
}
} else {
value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import kalix.javasdk.impl.CommandHandler
import kalix.javasdk.impl.InvocationContext
import kalix.javasdk.impl.JsonMessageCodec
import kalix.javasdk.impl.MethodInvoker
import kalix.javasdk.impl.reflection.MigrationExtractor.extractMigration

class ReflectiveEventSourcedEntityRouter[S, E, ES <: EventSourcedEntity[S, E]](
override protected val entity: ES,
Expand Down Expand Up @@ -109,10 +108,7 @@ class ReflectiveEventSourcedEntityRouter[S, E, ES <: EventSourcedEntity[S, E]](
entity._internalSetCurrentState(s)
case s =>
val deserializedState =
JsonSupport.decodeJson(
entityStateType,
ScalaPbAny.toJavaProto(s.asInstanceOf[ScalaPbAny]),
extractMigration(entityStateType))
JsonSupport.decodeJson(entityStateType, ScalaPbAny.toJavaProto(s.asInstanceOf[ScalaPbAny]))
entity._internalSetCurrentState(deserializedState)
}
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import kalix.javasdk.JsonSupport
import kalix.javasdk.Metadata
import kalix.javasdk.impl.AnySupport
import kalix.javasdk.impl.ErrorHandling.BadRequestException
import kalix.javasdk.impl.reflection.MigrationExtractor.extractMigration

/**
* Extracts method parameters from an invocation context for the purpose of passing them to a reflective invocation call
Expand Down Expand Up @@ -64,7 +63,7 @@ object ParameterExtractors {
val bytes = dm.getField(JavaPbAny.getDescriptor.findFieldByName("value")).asInstanceOf[ByteString]
AnySupport.decodePrimitiveBytes(bytes).toByteArray.asInstanceOf[T]
} else {
JsonSupport.decodeJson(cls, toAny(dm), extractMigration(cls))
JsonSupport.decodeJson(cls, toAny(dm))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import com.google.protobuf.any.{ Any => ScalaPbAny }
import kalix.javasdk.JsonSupport
import kalix.javasdk.impl.CommandHandler
import kalix.javasdk.impl.InvocationContext
import kalix.javasdk.impl.reflection.MigrationExtractor.extractMigration
import kalix.javasdk.valueentity.CommandContext
import kalix.javasdk.valueentity.ValueEntity

Expand Down Expand Up @@ -74,10 +73,7 @@ class ReflectiveValueEntityRouter[S, E <: ValueEntity[S]](
entity._internalSetCurrentState(s)
case s =>
val deserializedState =
JsonSupport.decodeJson(
entityStateType,
ScalaPbAny.toJavaProto(s.asInstanceOf[ScalaPbAny]),
extractMigration(entityStateType))
JsonSupport.decodeJson(entityStateType, ScalaPbAny.toJavaProto(s.asInstanceOf[ScalaPbAny]))
entity._internalSetCurrentState(deserializedState)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ class JsonMessageCodecSpec extends AnyWordSpec with Matchers {
"decode with new schema version" in {
val encoded = messageCodec.encodeJava(SimpleClass("abc", 10))
val decoded =
JsonSupport.decodeJson(classOf[SimpleClassUpdated], encoded, Optional.of(new SimpleClassUpdatedMigration))
JsonSupport.decodeJson(classOf[SimpleClassUpdated], encoded)
decoded shouldBe SimpleClassUpdated("abc", 10, 1)
}

Expand Down

0 comments on commit e40a450

Please sign in to comment.