From 8a37ace0c83fcf887bb4666a2c1b21a752202a0d Mon Sep 17 00:00:00 2001 From: Gerard de Leeuw Date: Tue, 28 May 2024 13:58:45 +0200 Subject: [PATCH 1/5] Add kotlin.serialization implementation of Serializer --- .../kotlin/QueryGatewayExtensions.kt | 28 +- .../kotlin/serialization/ArrayResponseType.kt | 98 ++++++ .../kotlin/serialization/AxonSerializers.kt | 333 ++++++++++++++++++ .../kotlin/serialization/KotlinSerializer.kt | 182 ++++++++++ .../kotlin/serializer/AxonSerializersTest.kt | 150 ++++++++ .../kotlin/serializer/KotlinSerializerTest.kt | 58 +++ pom.xml | 28 +- 7 files changed, 854 insertions(+), 23 deletions(-) create mode 100644 kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/ArrayResponseType.kt create mode 100644 kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/AxonSerializers.kt create mode 100644 kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt create mode 100644 kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/AxonSerializersTest.kt create mode 100644 kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerTest.kt diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/QueryGatewayExtensions.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/QueryGatewayExtensions.kt index fdfa12d3..ec417e53 100644 --- a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/QueryGatewayExtensions.kt +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/QueryGatewayExtensions.kt @@ -41,7 +41,7 @@ import java.util.stream.Stream * @see ResponseTypes * @since 0.1.0 */ -inline fun QueryGateway.queryMany(query: Q): CompletableFuture> { +inline fun QueryGateway.queryMany(query: Q): CompletableFuture> { return this.query(query, ResponseTypes.multipleInstancesOf(R::class.java)) } @@ -57,7 +57,7 @@ inline fun QueryGateway.queryMany(query: Q): CompletableF * @see ResponseTypes * @since 0.1.0 */ -inline fun QueryGateway.queryMany(queryName: String, query: Q): CompletableFuture> { +inline fun QueryGateway.queryMany(queryName: String, query: Q): CompletableFuture> { return this.query(queryName, query, ResponseTypes.multipleInstancesOf(R::class.java)) } @@ -72,7 +72,7 @@ inline fun QueryGateway.queryMany(queryName: String, quer * @see ResponseTypes * @since 0.1.0 */ -inline fun QueryGateway.query(query: Q): CompletableFuture { +inline fun QueryGateway.query(query: Q): CompletableFuture { return this.query(query, ResponseTypes.instanceOf(R::class.java)) } @@ -88,7 +88,7 @@ inline fun QueryGateway.query(query: Q): CompletableFutur * @see ResponseTypes * @since 0.1.0 */ -inline fun QueryGateway.query(queryName: String, query: Q): CompletableFuture { +inline fun QueryGateway.query(queryName: String, query: Q): CompletableFuture { return this.query(queryName, query, ResponseTypes.instanceOf(R::class.java)) } @@ -103,7 +103,7 @@ inline fun QueryGateway.query(queryName: String, query: Q * @see ResponseTypes * @since 0.1.0 */ -inline fun QueryGateway.queryOptional(query: Q): CompletableFuture> { +inline fun QueryGateway.queryOptional(query: Q): CompletableFuture> { return this.query(query, ResponseTypes.optionalInstanceOf(R::class.java)) } @@ -119,7 +119,7 @@ inline fun QueryGateway.queryOptional(query: Q): Completa * @see ResponseTypes * @since 0.1.0 */ -inline fun QueryGateway.queryOptional(queryName: String, query: Q): CompletableFuture> { +inline fun QueryGateway.queryOptional(queryName: String, query: Q): CompletableFuture> { return this.query(queryName, query, ResponseTypes.optionalInstanceOf(R::class.java)) } @@ -136,7 +136,7 @@ inline fun QueryGateway.queryOptional(queryName: String, * @see ResponseTypes * @since 0.2.0 */ -inline fun QueryGateway.scatterGather(query: Q, timeout: Long, +inline fun QueryGateway.scatterGather(query: Q, timeout: Long, timeUnit: TimeUnit): Stream { return this.scatterGather(query, ResponseTypes.instanceOf(R::class.java), timeout, timeUnit) } @@ -155,7 +155,7 @@ inline fun QueryGateway.scatterGather(query: Q, timeout: * @see ResponseTypes * @since 0.2.0 */ -inline fun QueryGateway.scatterGather(queryName: String, query: Q, timeout: Long, +inline fun QueryGateway.scatterGather(queryName: String, query: Q, timeout: Long, timeUnit: TimeUnit): Stream { return this.scatterGather(queryName, query, ResponseTypes.instanceOf(R::class.java), timeout, timeUnit) } @@ -173,7 +173,7 @@ inline fun QueryGateway.scatterGather(queryName: String, * @see ResponseTypes * @since 0.2.0 */ -inline fun QueryGateway.scatterGatherMany(query: Q, timeout: Long, +inline fun QueryGateway.scatterGatherMany(query: Q, timeout: Long, timeUnit: TimeUnit): Stream> { return this.scatterGather(query, ResponseTypes.multipleInstancesOf(R::class.java), timeout, timeUnit) } @@ -192,7 +192,7 @@ inline fun QueryGateway.scatterGatherMany(query: Q, timeo * @see ResponseTypes * @since 0.2.0 */ -inline fun QueryGateway.scatterGatherMany(queryName: String, query: Q, timeout: Long, +inline fun QueryGateway.scatterGatherMany(queryName: String, query: Q, timeout: Long, timeUnit: TimeUnit): Stream> { return this.scatterGather(queryName, query, ResponseTypes.multipleInstancesOf(R::class.java), timeout, timeUnit) } @@ -210,7 +210,7 @@ inline fun QueryGateway.scatterGatherMany(queryName: Stri * @see ResponseTypes * @since 0.2.0 */ -inline fun QueryGateway.scatterGatherOptional(query: Q, timeout: Long, +inline fun QueryGateway.scatterGatherOptional(query: Q, timeout: Long, timeUnit: TimeUnit): Stream> { return this.scatterGather(query, ResponseTypes.optionalInstanceOf(R::class.java), timeout, timeUnit) } @@ -229,7 +229,7 @@ inline fun QueryGateway.scatterGatherOptional(query: Q, t * @see ResponseTypes * @since 0.2.0 */ -inline fun QueryGateway.scatterGatherOptional(queryName: String, query: Q, timeout: Long, +inline fun QueryGateway.scatterGatherOptional(queryName: String, query: Q, timeout: Long, timeUnit: TimeUnit): Stream> { return this.scatterGather(queryName, query, ResponseTypes.optionalInstanceOf(R::class.java), timeout, timeUnit) } @@ -246,7 +246,7 @@ inline fun QueryGateway.scatterGatherOptional(queryName: * @see ResponseTypes * @since 0.3.0 */ -inline fun QueryGateway.subscriptionQuery(query: Q): SubscriptionQueryResult = +inline fun QueryGateway.subscriptionQuery(query: Q): SubscriptionQueryResult = this.subscriptionQuery(query, ResponseTypes.instanceOf(I::class.java), ResponseTypes.instanceOf(U::class.java)) /** @@ -262,5 +262,5 @@ inline fun QueryGateway.subscriptionQuery(quer * @see ResponseTypes * @since 0.3.0 */ -inline fun QueryGateway.subscriptionQuery(queryName: String, query: Q): SubscriptionQueryResult = +inline fun QueryGateway.subscriptionQuery(queryName: String, query: Q): SubscriptionQueryResult = this.subscriptionQuery(queryName, query, ResponseTypes.instanceOf(I::class.java), ResponseTypes.instanceOf(U::class.java)) \ No newline at end of file diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/ArrayResponseType.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/ArrayResponseType.kt new file mode 100644 index 00000000..9e549dca --- /dev/null +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/ArrayResponseType.kt @@ -0,0 +1,98 @@ +package org.axonframework.extensions.kotlin.serialization + +import org.axonframework.common.ReflectionUtils +import org.axonframework.common.TypeReflectionUtils +import org.axonframework.messaging.responsetypes.AbstractResponseType +import org.axonframework.messaging.responsetypes.InstanceResponseType +import org.axonframework.messaging.responsetypes.ResponseType +import java.lang.reflect.Type +import java.util.concurrent.Future + +/** + * A [ResponseType] implementation that will match with query handlers which return a multiple instances of the + * expected response type. If matching succeeds, the [ResponseType.convert] function will be called, which + * will cast the query handler it's response to an [Array] with element type [E]. + * + * @param E The element type which will be matched against and converted to + * @author Gerard de Leeuw + * @see org.axonframework.messaging.responsetypes.MultipleInstancesResponseType + */ +class ArrayResponseType(elementType: Class) : AbstractResponseType>(elementType) { + + companion object { + /** + * Indicates that the response matches with the [Type] while returning an iterable result. + * + * @see ResponseType.MATCH + * + * @see ResponseType.NO_MATCH + */ + const val ITERABLE_MATCH = 1024 + } + + private val instanceResponseType: InstanceResponseType = InstanceResponseType(elementType) + + /** + * Match the query handler's response [Type] with this implementation's [E]. + * Will return true in the following scenarios: + * + * * If the response type is an [Array] + * * If the response type is a [E] + * + * If there is no match at all, it will return false to indicate a non-match. + * + * @param responseType the response [Type] of the query handler which is matched against + * @return true for [Array] or [E] and [ResponseType.NO_MATCH] for non-matches + */ + override fun matches(responseType: Type): Boolean = + matchRank(responseType) > NO_MATCH + + /** + * Match the query handler's response [Type] with this implementation's [E]. + * Will return a value greater than 0 in the following scenarios: + * + * * [ITERABLE_MATCH]: If the response type is an [Array] + * * [ResponseType.MATCH]: If the response type is a [E] + * + * If there is no match at all, it will return [ResponseType.NO_MATCH] to indicate a non-match. + * + * @param responseType the response [Type] of the query handler which is matched against + * @return [ITERABLE_MATCH] for [Array], [ResponseType.MATCH] for [E] and [ResponseType.NO_MATCH] for non-matches + */ + override fun matchRank(responseType: Type): Int = when { + isMatchingArray(responseType) -> ITERABLE_MATCH + else -> instanceResponseType.matchRank(responseType) + } + + /** + * Converts the given [response] of type [Object] into an [Array] with element type [E] from + * this [ResponseType] instance. Should only be called if [ResponseType.matches] returns true. + * Will throw an [IllegalArgumentException] if the given response + * is not convertible to an [Array] of the expected response type. + * + * @param response the [Object] to convert into an [Array] with element type [E] + * @return an [Array] with element type [E], based on the given [response] + */ + override fun convert(response: Any): Array { + val responseType: Class<*> = response.javaClass + if (responseType.isArray) { + @Suppress("UNCHECKED_CAST") + return response as Array + } + throw IllegalArgumentException( + "Retrieved response [$responseType] is not convertible to an array with the expected element type [$expectedResponseType]" + ) + } + + @Suppress("UNCHECKED_CAST") + override fun responseMessagePayloadType(): Class> = + Array::class.java as Class> + + override fun toString(): String = "ArrayResponseType[$expectedResponseType]" + + private fun isMatchingArray(responseType: Type): Boolean { + val unwrapped = ReflectionUtils.unwrapIfType(responseType, Future::class.java) + val iterableType = TypeReflectionUtils.getExactSuperType(unwrapped, Array::class.java) + return iterableType != null && isParameterizedTypeOfExpectedType(iterableType) + } +} diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/AxonSerializers.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/AxonSerializers.kt new file mode 100644 index 00000000..51001b8d --- /dev/null +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/AxonSerializers.kt @@ -0,0 +1,333 @@ +package org.axonframework.extensions.kotlin.serialization + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.PolymorphicSerializer +import kotlinx.serialization.SerializationException +import kotlinx.serialization.builtins.MapSerializer +import kotlinx.serialization.builtins.SetSerializer +import kotlinx.serialization.builtins.nullable +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.descriptors.element +import kotlinx.serialization.encoding.CompositeDecoder +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.decodeStructure +import kotlinx.serialization.encoding.encodeStructure +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.polymorphic +import kotlinx.serialization.modules.subclass +import org.axonframework.eventhandling.GapAwareTrackingToken +import org.axonframework.eventhandling.GlobalSequenceTrackingToken +import org.axonframework.eventhandling.MergedTrackingToken +import org.axonframework.eventhandling.MultiSourceTrackingToken +import org.axonframework.eventhandling.ReplayToken +import org.axonframework.eventhandling.TrackingToken +import org.axonframework.eventhandling.scheduling.ScheduleToken +import org.axonframework.eventhandling.scheduling.java.SimpleScheduleToken +import org.axonframework.eventhandling.scheduling.quartz.QuartzScheduleToken +import org.axonframework.eventhandling.tokenstore.ConfigToken +import org.axonframework.messaging.responsetypes.InstanceResponseType +import org.axonframework.messaging.responsetypes.MultipleInstancesResponseType +import org.axonframework.messaging.responsetypes.OptionalResponseType +import org.axonframework.messaging.responsetypes.ResponseType +import kotlin.reflect.KClass + +private val trackingTokenSerializer = PolymorphicSerializer(TrackingToken::class).nullable + +val AxonSerializersModule = SerializersModule { + contextual(ConfigToken::class) { ConfigTokenSerializer } + contextual(GapAwareTrackingToken::class) { GapAwareTrackingTokenSerializer } + contextual(MultiSourceTrackingToken::class) { MultiSourceTrackingTokenSerializer } + contextual(MergedTrackingToken::class) { MergedTrackingTokenSerializer } + contextual(ReplayToken::class) { ReplayTokenSerializer } + contextual(GlobalSequenceTrackingToken::class) { GlobalSequenceTrackingTokenSerializer } + polymorphic(TrackingToken::class) { + subclass(ConfigTokenSerializer) + subclass(GapAwareTrackingTokenSerializer) + subclass(MultiSourceTrackingTokenSerializer) + subclass(MergedTrackingTokenSerializer) + subclass(ReplayTokenSerializer) + subclass(GlobalSequenceTrackingTokenSerializer) + } + + contextual(SimpleScheduleToken::class) { SimpleScheduleTokenSerializer } + contextual(QuartzScheduleToken::class) { QuartzScheduleTokenSerializer } + polymorphic(ScheduleToken::class) { + subclass(SimpleScheduleTokenSerializer) + subclass(QuartzScheduleTokenSerializer) + } + + contextual(InstanceResponseType::class) { InstanceResponseTypeSerializer } + contextual(OptionalResponseType::class) { OptionalResponseTypeSerializer } + contextual(MultipleInstancesResponseType::class) { MultipleInstancesResponseTypeSerializer } + contextual(ArrayResponseType::class) { ArrayResponseTypeSerializer } + polymorphic(ResponseType::class) { + subclass(InstanceResponseTypeSerializer) + subclass(OptionalResponseTypeSerializer) + subclass(MultipleInstancesResponseTypeSerializer) + subclass(ArrayResponseTypeSerializer) + } +} + +object ConfigTokenSerializer : KSerializer { + + private val mapSerializer = MapSerializer(String.serializer(), String.serializer()) + override val descriptor = buildClassSerialDescriptor(ConfigToken::class.java.name) { + element>("config") + } + + override fun deserialize(decoder: Decoder): ConfigToken = decoder.decodeStructure(descriptor) { + var config: Map? = null + while (true) { + val index = decodeElementIndex(descriptor) + if (index == CompositeDecoder.DECODE_DONE) break + when (index) { + 0 -> config = decodeSerializableElement(descriptor, index, mapSerializer) + } + } + ConfigToken( + config ?: throw SerializationException("Element 'config' is missing"), + ) + } + + override fun serialize(encoder: Encoder, value: ConfigToken) = encoder.encodeStructure(descriptor) { + encodeSerializableElement(descriptor, 0, mapSerializer, value.config) + } +} + +object GapAwareTrackingTokenSerializer : KSerializer { + + private val setSerializer = SetSerializer(Long.serializer()) + override val descriptor = buildClassSerialDescriptor(GapAwareTrackingToken::class.java.name) { + element("index") + element("gaps", setSerializer.descriptor) + } + + override fun deserialize(decoder: Decoder) = decoder.decodeStructure(descriptor) { + var gapIndex: Long? = null + var gaps: Set? = null + while (true) { + val index = decodeElementIndex(descriptor) + if (index == CompositeDecoder.DECODE_DONE) break + when (index) { + 0 -> gapIndex = decodeLongElement(descriptor, index) + 1 -> gaps = decodeSerializableElement(descriptor, index, setSerializer) + } + } + GapAwareTrackingToken( + gapIndex ?: throw SerializationException("Element 'gapIndex' is missing"), + gaps ?: throw SerializationException("Element 'gaps' is missing"), + ) + } + + override fun serialize(encoder: Encoder, value: GapAwareTrackingToken) = encoder.encodeStructure(descriptor) { + encodeLongElement(descriptor, 0, value.index) + encodeSerializableElement(descriptor, 1, setSerializer, value.gaps) + } +} + +object MultiSourceTrackingTokenSerializer : KSerializer { + + private val mapSerializer = MapSerializer(String.serializer(), trackingTokenSerializer) + override val descriptor = buildClassSerialDescriptor(MultiSourceTrackingToken::class.java.name) { + element>("trackingTokens") + } + + override fun deserialize(decoder: Decoder) = decoder.decodeStructure(descriptor) { + var trackingTokens: Map? = null + while (true) { + val index = decodeElementIndex(descriptor) + if (index == CompositeDecoder.DECODE_DONE) break + when (index) { + 0 -> trackingTokens = decodeSerializableElement(descriptor, index, mapSerializer) + } + } + MultiSourceTrackingToken( + trackingTokens ?: throw SerializationException("Element 'trackingTokens' is missing"), + ) + } + + override fun serialize(encoder: Encoder, value: MultiSourceTrackingToken) = encoder.encodeStructure(descriptor) { + encodeSerializableElement(descriptor, 0, mapSerializer, value.trackingTokens) + } +} + +object MergedTrackingTokenSerializer : KSerializer { + + override val descriptor = buildClassSerialDescriptor(MergedTrackingToken::class.java.name) { + element("lowerSegmentToken") + element("upperSegmentToken") + } + + override fun deserialize(decoder: Decoder) = decoder.decodeStructure(descriptor) { + var lowerSegmentToken: TrackingToken? = null + var upperSegmentToken: TrackingToken? = null + while (true) { + val index = decodeElementIndex(descriptor) + if (index == CompositeDecoder.DECODE_DONE) break + when (index) { + 0 -> lowerSegmentToken = decodeSerializableElement(descriptor, index, trackingTokenSerializer) + 1 -> upperSegmentToken = decodeSerializableElement(descriptor, index, trackingTokenSerializer) + } + } + MergedTrackingToken( + lowerSegmentToken ?: throw SerializationException("Element 'lowerSegmentToken' is missing"), + upperSegmentToken ?: throw SerializationException("Element 'upperSegmentToken' is missing"), + ) + } + + override fun serialize(encoder: Encoder, value: MergedTrackingToken) = encoder.encodeStructure(descriptor) { + encodeSerializableElement(descriptor, 0, trackingTokenSerializer, value.lowerSegmentToken()) + encodeSerializableElement(descriptor, 1, trackingTokenSerializer, value.upperSegmentToken()) + } +} + +object ReplayTokenSerializer : KSerializer { + + override val descriptor = buildClassSerialDescriptor(ReplayToken::class.java.name) { + element("tokenAtReset") + element("currentToken") + } + + override fun deserialize(decoder: Decoder) = decoder.decodeStructure(descriptor) { + var tokenAtReset: TrackingToken? = null + var currentToken: TrackingToken? = null + while (true) { + val index = decodeElementIndex(descriptor) + if (index == CompositeDecoder.DECODE_DONE) break + when (index) { + 0 -> tokenAtReset = decodeSerializableElement(descriptor, index, trackingTokenSerializer) + 1 -> currentToken = decodeSerializableElement(descriptor, index, trackingTokenSerializer) + } + } + ReplayToken( + tokenAtReset ?: throw SerializationException("Element 'tokenAtReset' is missing"), + currentToken, + ) + } + + @OptIn(ExperimentalSerializationApi::class) + override fun serialize(encoder: Encoder, value: ReplayToken) = encoder.encodeStructure(descriptor) { + encodeSerializableElement(descriptor, 0, trackingTokenSerializer, value.tokenAtReset) + encodeNullableSerializableElement(descriptor, 1, trackingTokenSerializer, value.currentToken) + } +} + +object GlobalSequenceTrackingTokenSerializer : KSerializer { + + override val descriptor = buildClassSerialDescriptor(GlobalSequenceTrackingToken::class.java.name) { + element("globalIndex") + } + + override fun deserialize(decoder: Decoder) = decoder.decodeStructure(descriptor) { + var globalIndex: Long? = null + while (true) { + val index = decodeElementIndex(descriptor) + if (index == CompositeDecoder.DECODE_DONE) break + when (index) { + 0 -> globalIndex = decodeLongElement(descriptor, index) + } + } + GlobalSequenceTrackingToken( + globalIndex ?: throw SerializationException("Element 'globalIndex' is missing"), + ) + } + + override fun serialize(encoder: Encoder, value: GlobalSequenceTrackingToken) = encoder.encodeStructure(descriptor) { + encodeLongElement(descriptor, 0, value.globalIndex) + } +} + +object SimpleScheduleTokenSerializer : KSerializer { + + override val descriptor = buildClassSerialDescriptor(SimpleScheduleToken::class.java.name) { + element("tokenId") + } + + override fun deserialize(decoder: Decoder) = decoder.decodeStructure(descriptor) { + var tokenId: String? = null + while (true) { + val index = decodeElementIndex(descriptor) + if (index == CompositeDecoder.DECODE_DONE) break + when (index) { + 0 -> tokenId = decodeStringElement(descriptor, index) + } + } + SimpleScheduleToken( + tokenId ?: throw SerializationException("Element 'tokenId' is missing"), + ) + } + + override fun serialize(encoder: Encoder, value: SimpleScheduleToken) = encoder.encodeStructure(descriptor) { + encodeStringElement(descriptor, 0, value.tokenId) + } +} + +object QuartzScheduleTokenSerializer : KSerializer { + + override val descriptor = buildClassSerialDescriptor(QuartzScheduleToken::class.java.name) { + element("jobIdentifier") + element("groupIdentifier") + } + + override fun deserialize(decoder: Decoder) = decoder.decodeStructure(descriptor) { + var jobIdentifier: String? = null + var groupIdentifier: String? = null + while (true) { + val index = decodeElementIndex(descriptor) + if (index == CompositeDecoder.DECODE_DONE) break + when (index) { + 0 -> jobIdentifier = decodeStringElement(descriptor, index) + 1 -> groupIdentifier = decodeStringElement(descriptor, index) + } + } + QuartzScheduleToken( + jobIdentifier ?: throw SerializationException("Element 'jobIdentifier' is missing"), + groupIdentifier ?: throw SerializationException("Element 'groupIdentifier' is missing"), + ) + } + + override fun serialize(encoder: Encoder, value: QuartzScheduleToken) = encoder.encodeStructure(descriptor) { + encodeStringElement(descriptor, 0, value.jobIdentifier) + encodeStringElement(descriptor, 1, value.groupIdentifier) + } +} + +abstract class ResponseTypeSerializer>(kClass: KClass, private val factory: (Class<*>) -> R) : KSerializer { + + override val descriptor = buildClassSerialDescriptor(kClass.java.name) { + element("expectedResponseType") + } + + override fun deserialize(decoder: Decoder) = decoder.decodeStructure(descriptor) { + var expectedResponseType: Class<*>? = null + while (true) { + val index = decodeElementIndex(descriptor) + if (index == CompositeDecoder.DECODE_DONE) break + when (index) { + 0 -> expectedResponseType = Class.forName(decodeStringElement(descriptor, index)) + } + } + factory( + expectedResponseType ?: throw SerializationException("Element 'expectedResponseType' is missing") + ) + } + + override fun serialize(encoder: Encoder, value: R) = encoder.encodeStructure(descriptor) { + encodeStringElement(descriptor, 0, value.expectedResponseType.name) + } +} + +object InstanceResponseTypeSerializer : KSerializer>, + ResponseTypeSerializer>(InstanceResponseType::class, { InstanceResponseType(it) }) + +object OptionalResponseTypeSerializer : KSerializer>, + ResponseTypeSerializer>(OptionalResponseType::class, { OptionalResponseType(it) }) + +object MultipleInstancesResponseTypeSerializer : KSerializer>, + ResponseTypeSerializer>(MultipleInstancesResponseType::class, { MultipleInstancesResponseType(it) }) + +object ArrayResponseTypeSerializer : KSerializer>, + ResponseTypeSerializer>(ArrayResponseType::class, { ArrayResponseType(it) }) diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt new file mode 100644 index 00000000..e8c71487 --- /dev/null +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt @@ -0,0 +1,182 @@ +package org.axonframework.extensions.kotlin.serialization + +import kotlinx.serialization.BinaryFormat +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialFormat +import kotlinx.serialization.SerializationException +import kotlinx.serialization.StringFormat +import kotlinx.serialization.builtins.ArraySerializer +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.SetSerializer +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.serializer +import org.axonframework.serialization.AnnotationRevisionResolver +import org.axonframework.serialization.ChainingConverter +import org.axonframework.serialization.Converter +import org.axonframework.serialization.RevisionResolver +import org.axonframework.serialization.SerializedObject +import org.axonframework.serialization.SerializedType +import org.axonframework.serialization.Serializer +import org.axonframework.serialization.SimpleSerializedObject +import org.axonframework.serialization.SimpleSerializedType +import org.axonframework.serialization.UnknownSerializedType +import java.util.concurrent.ConcurrentHashMap +import org.axonframework.serialization.SerializationException as AxonSerializationException + +/** + * Implementation of Axon Serializer that uses a kotlinx.serialization implementation. + * + * @see kotlinx.serialization.Serializer + * @see org.axonframework.serialization.Serializer + * + * @since 4.9.1 + * @author Gerard de Leeuw + */ +class KotlinSerializer( + private val serialFormat: SerialFormat, + private val revisionResolver: RevisionResolver = AnnotationRevisionResolver(), + private val converter: Converter = ChainingConverter(), +) : Serializer { + + private val serializerCache: MutableMap, KSerializer<*>> = ConcurrentHashMap() + private val unknownSerializedType = UnknownSerializedType::class.java + + override fun getConverter(): Converter = converter + + override fun canSerializeTo(expectedRepresentation: Class): Boolean = when (serialFormat) { + is StringFormat -> converter.canConvert(String::class.java, expectedRepresentation) + is BinaryFormat -> converter.canConvert(ByteArray::class.java, expectedRepresentation) + else -> false + } + + override fun serialize(value: Any?, expectedRepresentation: Class): SerializedObject { + try { + val serializedType = typeForValue(value) + val classForType = classForType(serializedType) + + val serializer = serializerFor(classForType, serializedType) + val serialized: SerializedObject<*> = when (serialFormat) { + is StringFormat -> SimpleSerializedObject( + serialFormat.encodeToString(serializer, value), + String::class.java, + serializedType + ) + + is BinaryFormat -> SimpleSerializedObject( + serialFormat.encodeToByteArray(serializer, value), + ByteArray::class.java, + serializedType + ) + + else -> throw SerializationException("Unsupported serial format: $serialFormat") + } + + return converter.convert(serialized, expectedRepresentation) + } catch (ex: SerializationException) { + throw AxonSerializationException("Cannot serialize type ${value?.javaClass?.name} to representation $expectedRepresentation.", ex) + } + } + + @Suppress("UNCHECKED_CAST") + override fun deserialize(serializedObject: SerializedObject): T? { + try { + if (serializedObject.type == SerializedType.emptyType()) { + return null + } + + val classForType = classForType(serializedObject.type) + if (unknownSerializedType.isAssignableFrom(classForType)) { + return UnknownSerializedType(this, serializedObject) as T + } + + val serializer = serializerFor(classForType, serializedObject.type) as KSerializer + return when (serialFormat) { + is StringFormat -> { + val json = converter.convert(serializedObject, String::class.java).data + when { + json.isEmpty() -> null + else -> serialFormat.decodeFromString(serializer, json) + } + } + + is BinaryFormat -> { + val bytes = converter.convert(serializedObject, ByteArray::class.java).data + when { + bytes.isEmpty() -> null + else -> serialFormat.decodeFromByteArray(serializer, bytes) + } + } + + else -> throw SerializationException("Unsupported serial format: $serialFormat") + } + } catch (ex: SerializationException) { + throw AxonSerializationException("Could not deserialize from content type ${serializedObject.contentType} to type ${serializedObject.type}", ex) + } + } + + override fun classForType(type: SerializedType): Class<*> = when (val unwrapped = type.unwrap()) { + SerializedType.emptyType() -> Void.TYPE + else -> try { + Class.forName(unwrapped.name) + } catch (ex: ClassNotFoundException) { + unknownSerializedType + } + } + + override fun typeForClass(type: Class<*>?): SerializedType = + if (type == null || Void.TYPE == type || Void::class.java.isAssignableFrom(type)) { + SimpleSerializedType.emptyType() + } else { + SimpleSerializedType(type.name, revisionResolver.revisionOf(type)) + } + + private fun typeForValue(value: Any?): SerializedType = when (value) { + null -> SimpleSerializedType.emptyType() + Unit -> SimpleSerializedType.emptyType() + is Class<*> -> typeForClass(value) + is Array<*> -> typeForClass(value.javaClass.componentType).wrap("Array") + is List<*> -> typeForClass(value.findCommonInterfaceOfEntries()).wrap("List") + is Set<*> -> typeForClass(value.findCommonInterfaceOfEntries()).wrap("Set") + else -> typeForClass(value.javaClass) + } + + private fun SerializedType.wrap(wrapper: String) = SimpleSerializedType("$wrapper:$name", revision) + private fun SerializedType.unwrap() = SimpleSerializedType(name.substringAfter(':'), revision) + + @Suppress("UNCHECKED_CAST", "OPT_IN_USAGE") + private fun serializerFor(type: Class<*>, serializedType: SerializedType): KSerializer { + val serializer = when { + Void.TYPE == type || Void::class.java.isAssignableFrom(type) -> Unit.serializer() + unknownSerializedType.isAssignableFrom(type) -> throw ClassNotFoundException("Can't load class for type $this") + else -> serializerCache.computeIfAbsent(type, serialFormat.serializersModule::serializer) + } as KSerializer + + return when (serializedType.name.substringBefore(':')) { + "Array" -> ArraySerializer(serializer) + "List" -> ListSerializer(serializer) + "Set" -> SetSerializer(serializer) + else -> serializer + } as KSerializer + } +} + +private fun Iterable<*>.findCommonInterfaceOfEntries(): Class<*>? { + val firstElement = firstOrNull() ?: return null + + val commonTypeInIterable = fold(firstElement.javaClass as Class<*>) { resultingClass, element -> + if (element == null) { + // Element is null, so we just assume the type of the previous elements + resultingClass + } else if (resultingClass.isAssignableFrom(element.javaClass)) { + // Element matches the elements we've seen so far, so we can keep the type + resultingClass + } else { + // Element does not match the elements we've seen so far, so we look for a common + // interface and continue with that + element.javaClass.interfaces.firstOrNull { it.isAssignableFrom(resultingClass) } + ?: throw SerializationException("Cannot find interface for serialization") + } + } + + return commonTypeInIterable +} diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/AxonSerializersTest.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/AxonSerializersTest.kt new file mode 100644 index 00000000..d13dac44 --- /dev/null +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/AxonSerializersTest.kt @@ -0,0 +1,150 @@ +package org.axonframework.extensions.kotlin.serializer + +import kotlinx.serialization.json.Json +import org.axonframework.eventhandling.GapAwareTrackingToken +import org.axonframework.eventhandling.GlobalSequenceTrackingToken +import org.axonframework.eventhandling.MergedTrackingToken +import org.axonframework.eventhandling.MultiSourceTrackingToken +import org.axonframework.eventhandling.ReplayToken +import org.axonframework.eventhandling.TrackingToken +import org.axonframework.eventhandling.scheduling.ScheduleToken +import org.axonframework.eventhandling.scheduling.java.SimpleScheduleToken +import org.axonframework.eventhandling.scheduling.quartz.QuartzScheduleToken +import org.axonframework.eventhandling.tokenstore.ConfigToken +import org.axonframework.extensions.kotlin.serialization.ArrayResponseType +import org.axonframework.extensions.kotlin.serialization.AxonSerializersModule +import org.axonframework.extensions.kotlin.serialization.KotlinSerializer +import org.axonframework.messaging.responsetypes.InstanceResponseType +import org.axonframework.messaging.responsetypes.MultipleInstancesResponseType +import org.axonframework.messaging.responsetypes.OptionalResponseType +import org.axonframework.messaging.responsetypes.ResponseType +import org.axonframework.serialization.Serializer +import org.axonframework.serialization.SimpleSerializedObject +import org.axonframework.serialization.SimpleSerializedType +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +internal class AxonSerializersTest { + + private val serializer = KotlinSerializer(Json { serializersModule = AxonSerializersModule }) + + @Test + fun configToken() { + val token = ConfigToken(mapOf("important-property" to "important-value", "other-property" to "other-value")) + val json = """{"config":{"important-property":"important-value","other-property":"other-value"}}""" + assertEquals(json, serializer.serialize(token, String::class.java).data) + assertEquals(token, serializer.deserializeTrackingToken(token.javaClass.name, json)) + } + + @Test + fun gapAwareTrackingToken() { + val token = GapAwareTrackingToken(10, setOf(7, 8, 9)) + val json = """{"index":10,"gaps":[7,8,9]}""" + assertEquals(json, serializer.serialize(token, String::class.java).data) + assertEquals(token, serializer.deserializeTrackingToken(token.javaClass.name, json)) + } + + @Test + fun multiSourceTrackingToken() { + val token = MultiSourceTrackingToken(mapOf("primary" to GlobalSequenceTrackingToken(5), "secondary" to GlobalSequenceTrackingToken(10))) + val json = """{"trackingTokens":{"primary":{"type":"org.axonframework.eventhandling.GlobalSequenceTrackingToken","globalIndex":5},"secondary":{"type":"org.axonframework.eventhandling.GlobalSequenceTrackingToken","globalIndex":10}}}""" + assertEquals(json, serializer.serialize(token, String::class.java).data) + assertEquals(token, serializer.deserializeTrackingToken(token.javaClass.name, json)) + } + + @Test + fun mergedTrackingToken() { + val token = MergedTrackingToken(GlobalSequenceTrackingToken(5), GlobalSequenceTrackingToken(10)) + val json = """{"lowerSegmentToken":{"type":"org.axonframework.eventhandling.GlobalSequenceTrackingToken","globalIndex":5},"upperSegmentToken":{"type":"org.axonframework.eventhandling.GlobalSequenceTrackingToken","globalIndex":10}}""" + assertEquals(json, serializer.serialize(token, String::class.java).data) + assertEquals(token, serializer.deserializeTrackingToken(token.javaClass.name, json)) + } + + @Test + fun replayToken() { + val token = ReplayToken.createReplayToken(GlobalSequenceTrackingToken(15), GlobalSequenceTrackingToken(10)) + val json = """{"tokenAtReset":{"type":"org.axonframework.eventhandling.GlobalSequenceTrackingToken","globalIndex":15},"currentToken":{"type":"org.axonframework.eventhandling.GlobalSequenceTrackingToken","globalIndex":10}}""" + assertEquals(json, serializer.serialize(token, String::class.java).data) + assertEquals(token, serializer.deserializeTrackingToken(token.javaClass.name, json)) + } + + @Test + fun `replay token with currentToken with null value`() { + val token = ReplayToken.createReplayToken(GlobalSequenceTrackingToken(5), null) + val json = """{"tokenAtReset":{"type":"org.axonframework.eventhandling.GlobalSequenceTrackingToken","globalIndex":5},"currentToken":null}""" + assertEquals(json, serializer.serialize(token, String::class.java).data) + assertEquals(token, serializer.deserializeTrackingToken(token.javaClass.name, json)) + } + + @Test + fun globalSequenceTrackingToken() { + val token = GlobalSequenceTrackingToken(5) + val json = """{"globalIndex":5}""" + assertEquals(json, serializer.serialize(token, String::class.java).data) + assertEquals(token, serializer.deserializeTrackingToken(token.javaClass.name, json)) + } + + @Test + fun simpleScheduleToken() { + val token = SimpleScheduleToken("my-token-id") + val json = """{"tokenId":"my-token-id"}""" + assertEquals(json, serializer.serialize(token, String::class.java).data) + assertEquals(token, serializer.deserializeScheduleToken(token.javaClass.name, json)) + } + + @Test + fun quartzScheduleToken() { + val token = QuartzScheduleToken("my-job-id", "my-group-id") + val json = """{"jobIdentifier":"my-job-id","groupIdentifier":"my-group-id"}""" + assertEquals(json, serializer.serialize(token, String::class.java).data) + assertEquals(token, serializer.deserializeScheduleToken(token.javaClass.name, json)) + } + + @Test + fun instanceResponseType() { + val responseType = InstanceResponseType(String::class.java) + val json = """{"expectedResponseType":"java.lang.String"}""" + assertEquals(json, serializer.serialize(responseType, String::class.java).data) + assertEquals(responseType, serializer.deserializeResponseType(responseType.javaClass.name, json)) + } + + @Test + fun optionalResponseType() { + val responseType = OptionalResponseType(String::class.java) + val json = """{"expectedResponseType":"java.lang.String"}""" + assertEquals(json, serializer.serialize(responseType, String::class.java).data) + assertEquals(responseType, serializer.deserializeResponseType(responseType.javaClass.name, json)) + } + + @Test + fun multipleInstancesResponseType() { + val responseType = MultipleInstancesResponseType(String::class.java) + val json = """{"expectedResponseType":"java.lang.String"}""" + assertEquals(json, serializer.serialize(responseType, String::class.java).data) + assertEquals(responseType, serializer.deserializeResponseType(responseType.javaClass.name, json)) + } + + @Test + fun arrayResponseType() { + val responseType = ArrayResponseType(String::class.java) + val json = """{"expectedResponseType":"java.lang.String"}""" + assertEquals(json, serializer.serialize(responseType, String::class.java).data) + assertEquals(responseType, serializer.deserializeResponseType(responseType.javaClass.name, json)) + } + + private fun Serializer.deserializeTrackingToken(tokenType: String, json: String): TrackingToken = + deserializeJson(tokenType, json) + + private fun Serializer.deserializeScheduleToken(tokenType: String, json: String): ScheduleToken = + deserializeJson(tokenType, json) + + private fun Serializer.deserializeResponseType(responseType: String, json: String): ResponseType<*> = + deserializeJson(responseType, json) + + private fun Serializer.deserializeJson(type: String, json: String): T { + val serializedType = SimpleSerializedType(type, null) + val serializedToken = SimpleSerializedObject(json, String::class.java, serializedType) + return deserialize(serializedToken) + } + +} diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerTest.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerTest.kt new file mode 100644 index 00000000..ab86ceb3 --- /dev/null +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerTest.kt @@ -0,0 +1,58 @@ +package org.axonframework.extensions.kotlin.serializer + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import org.axonframework.extensions.kotlin.serialization.KotlinSerializer +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class KotlinSerializerTest { + private val sut = KotlinSerializer(Json) + + @Test + fun testStandardList() { + val items = listOf( + TypeOne("a", 1), + TypeOne("b", 2), + ) + val actual = sut.serialize(items, String::class.java) + val expected = """[{"name":"a","foo":1},{"name":"b","foo":2}]""" + assertEquals("List:org.axonframework.extensions.kotlin.serializer.KotlinSerializerTest\$TypeOne", actual.type.name) + assertEquals(expected, actual.data) + } + + @Test + fun testPolymorphicList() { + val items = listOf( + TypeOne("a", 1), + TypeOne("b", 2), + TypeTwo("c", listOf(3, 4)), + TypeOne("d", 5), + ) + val actual = sut.serialize(items, String::class.java) + val expected = """[{"type":"one","name":"a","foo":1},{"type":"one","name":"b","foo":2},{"type":"two","name":"c","bar":[3,4]},{"type":"one","name":"d","foo":5}]""" + assertEquals("List:org.axonframework.extensions.kotlin.serializer.KotlinSerializerTest\$SuperType", actual.type.name) + assertEquals(expected, actual.data) + } + + @Serializable + sealed interface SuperType { + val name: String + } + + @Serializable + @SerialName("one") + data class TypeOne( + override val name: String, + val foo: Int, + ) : SuperType + + @Serializable + @SerialName("two") + data class TypeTwo( + override val name: String, + val bar: List, + ) : SuperType + +} diff --git a/pom.xml b/pom.xml index 08468e60..cce29736 100644 --- a/pom.xml +++ b/pom.xml @@ -55,18 +55,18 @@ 4.9.0 - 1.7.21 + 1.9.24 - 2.16.0 + 2.17.1 + 1.6.3 - 3.0.5 2.0.13 2.13.3 5.10.1 1.12.4 - 1.7.20 + 1.9.20 0.8.11 3.7.1 3.3.2 @@ -99,11 +99,6 @@ pom import - - io.github.microutils - kotlin-logging-jvm - ${kotlin-logging.version} - org.axonframework @@ -121,6 +116,11 @@ jackson-module-kotlin ${jackson-kotlin.version} + + org.jetbrains.kotlinx + kotlinx-serialization-json + ${kotlin-serialization.version} + org.junit.jupiter @@ -163,6 +163,10 @@ com.fasterxml.jackson.module jackson-module-kotlin + + org.jetbrains.kotlinx + kotlinx-serialization-json + org.junit.jupiter @@ -238,6 +242,7 @@ no-arg all-open + kotlinx-serialization @@ -281,6 +286,11 @@ kotlin-maven-noarg ${kotlin.version} + + org.jetbrains.kotlin + kotlin-maven-serialization + ${kotlin.version} + From 1cdf9fc5e0d8a219f6be7976dc6f23419748de3d Mon Sep 17 00:00:00 2001 From: Gerard de Leeuw Date: Wed, 29 May 2024 11:07:41 +0200 Subject: [PATCH 2/5] Add tests for more formats --- kotlin/pom.xml | 8 ++++ .../serializer/KotlinSerializerCborTest.kt | 40 +++++++++++++++++++ ...zerTest.kt => KotlinSerializerJsonTest.kt} | 28 ++----------- .../KotlinSerializerProtobufTest.kt | 40 +++++++++++++++++++ .../extensions/kotlin/serializer/TestTypes.kt | 23 +++++++++++ pom.xml | 37 ++++++++++++++++- 6 files changed, 149 insertions(+), 27 deletions(-) create mode 100644 kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerCborTest.kt rename kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/{KotlinSerializerTest.kt => KotlinSerializerJsonTest.kt} (67%) create mode 100644 kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerProtobufTest.kt create mode 100644 kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/TestTypes.kt diff --git a/kotlin/pom.xml b/kotlin/pom.xml index b19a8051..5d5f1ee7 100644 --- a/kotlin/pom.xml +++ b/kotlin/pom.xml @@ -59,6 +59,14 @@ org.jetbrains.kotlin kotlin-maven-plugin + + org.apache.maven.plugins + maven-compiler-plugin + + 17 + 17 + + diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerCborTest.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerCborTest.kt new file mode 100644 index 00000000..6f845789 --- /dev/null +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerCborTest.kt @@ -0,0 +1,40 @@ +package org.axonframework.extensions.kotlin.serializer + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.cbor.Cbor +import org.axonframework.extensions.kotlin.serialization.KotlinSerializer +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.util.HexFormat + +class KotlinSerializerCborTest { + @OptIn(ExperimentalSerializationApi::class) + private val sut = KotlinSerializer(Cbor) + private val format = HexFormat.of() + + @Test + fun testStandardList() { + val items = listOf( + TypeOne("a", 1), + TypeOne("b", 2), + ) + val actual = sut.serialize(items, ByteArray::class.java) + val expected = "9fbf646e616d65616163666f6f01ffbf646e616d65616263666f6f02ffff" + assertEquals("List:org.axonframework.extensions.kotlin.serializer.TypeOne", actual.type.name) + assertEquals(expected, format.formatHex(actual.data)) + } + + @Test + fun testPolymorphicList() { + val items = listOf( + TypeOne("a", 1), + TypeOne("b", 2), + TypeTwo("c", listOf(3, 4)), + TypeOne("d", 5), + ) + val actual = sut.serialize(items, ByteArray::class.java) + val expected = "9f9f636f6e65bf646e616d65616163666f6f01ffff9f636f6e65bf646e616d65616263666f6f02ffff9f6374776fbf646e616d656163636261729f0304ffffff9f636f6e65bf646e616d65616463666f6f05ffffff" + assertEquals("List:org.axonframework.extensions.kotlin.serializer.SuperType", actual.type.name) + assertEquals(expected, format.formatHex(actual.data)) + } +} diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerTest.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerJsonTest.kt similarity index 67% rename from kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerTest.kt rename to kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerJsonTest.kt index ab86ceb3..42795d63 100644 --- a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerTest.kt +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerJsonTest.kt @@ -1,13 +1,11 @@ package org.axonframework.extensions.kotlin.serializer -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import org.axonframework.extensions.kotlin.serialization.KotlinSerializer import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -class KotlinSerializerTest { +class KotlinSerializerJsonTest { private val sut = KotlinSerializer(Json) @Test @@ -18,7 +16,7 @@ class KotlinSerializerTest { ) val actual = sut.serialize(items, String::class.java) val expected = """[{"name":"a","foo":1},{"name":"b","foo":2}]""" - assertEquals("List:org.axonframework.extensions.kotlin.serializer.KotlinSerializerTest\$TypeOne", actual.type.name) + assertEquals("List:org.axonframework.extensions.kotlin.serializer.TypeOne", actual.type.name) assertEquals(expected, actual.data) } @@ -32,27 +30,7 @@ class KotlinSerializerTest { ) val actual = sut.serialize(items, String::class.java) val expected = """[{"type":"one","name":"a","foo":1},{"type":"one","name":"b","foo":2},{"type":"two","name":"c","bar":[3,4]},{"type":"one","name":"d","foo":5}]""" - assertEquals("List:org.axonframework.extensions.kotlin.serializer.KotlinSerializerTest\$SuperType", actual.type.name) + assertEquals("List:org.axonframework.extensions.kotlin.serializer.SuperType", actual.type.name) assertEquals(expected, actual.data) } - - @Serializable - sealed interface SuperType { - val name: String - } - - @Serializable - @SerialName("one") - data class TypeOne( - override val name: String, - val foo: Int, - ) : SuperType - - @Serializable - @SerialName("two") - data class TypeTwo( - override val name: String, - val bar: List, - ) : SuperType - } diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerProtobufTest.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerProtobufTest.kt new file mode 100644 index 00000000..275b6883 --- /dev/null +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerProtobufTest.kt @@ -0,0 +1,40 @@ +package org.axonframework.extensions.kotlin.serializer + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.protobuf.ProtoBuf +import org.axonframework.extensions.kotlin.serialization.KotlinSerializer +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.util.HexFormat + +class KotlinSerializerProtobufTest { + @OptIn(ExperimentalSerializationApi::class) + private val sut = KotlinSerializer(ProtoBuf) + private val format = HexFormat.of() + + @Test + fun testStandardList() { + val items = listOf( + TypeOne("a", 1), + TypeOne("b", 2), + ) + val actual = sut.serialize(items, ByteArray::class.java) + val expected = "02050a01611001050a01621002" + assertEquals("List:org.axonframework.extensions.kotlin.serializer.TypeOne", actual.type.name) + assertEquals(expected, format.formatHex(actual.data)) + } + + @Test + fun testPolymorphicList() { + val items = listOf( + TypeOne("a", 1), + TypeOne("b", 2), + TypeTwo("c", listOf(3, 4)), + TypeOne("d", 5), + ) + val actual = sut.serialize(items, ByteArray::class.java) + val expected = "040c0a036f6e6512050a016110010c0a036f6e6512050a016210020e0a0374776f12070a0163100310040c0a036f6e6512050a01641005" + assertEquals("List:org.axonframework.extensions.kotlin.serializer.SuperType", actual.type.name) + assertEquals(expected, format.formatHex(actual.data)) + } +} diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/TestTypes.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/TestTypes.kt new file mode 100644 index 00000000..5936beca --- /dev/null +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/TestTypes.kt @@ -0,0 +1,23 @@ +package org.axonframework.extensions.kotlin.serializer + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +sealed interface SuperType { + val name: String +} + +@Serializable +@SerialName("one") +data class TypeOne( + override val name: String, + val foo: Int, +) : SuperType + +@Serializable +@SerialName("two") +data class TypeTwo( + override val name: String, + val bar: List, +) : SuperType diff --git a/pom.xml b/pom.xml index cce29736..ea999e14 100644 --- a/pom.xml +++ b/pom.xml @@ -118,7 +118,7 @@ org.jetbrains.kotlinx - kotlinx-serialization-json + kotlinx-serialization-core ${kotlin-serialization.version} @@ -145,6 +145,24 @@ ${slf4j.version} test + + org.jetbrains.kotlinx + kotlinx-serialization-json + ${kotlin-serialization.version} + test + + + org.jetbrains.kotlinx + kotlinx-serialization-cbor + ${kotlin-serialization.version} + test + + + org.jetbrains.kotlinx + kotlinx-serialization-protobuf + ${kotlin-serialization.version} + test + @@ -165,7 +183,7 @@ org.jetbrains.kotlinx - kotlinx-serialization-json + kotlinx-serialization-core @@ -188,6 +206,21 @@ slf4j-simple test + + org.jetbrains.kotlinx + kotlinx-serialization-json + test + + + org.jetbrains.kotlinx + kotlinx-serialization-cbor + test + + + org.jetbrains.kotlinx + kotlinx-serialization-protobuf + test + javax.xml.bind From 8026eeedb1adb898533cbbc981b69c1c00109f4c Mon Sep 17 00:00:00 2001 From: Gerard de Leeuw Date: Wed, 29 May 2024 11:10:53 +0200 Subject: [PATCH 3/5] revert accidental change --- kotlin/pom.xml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/kotlin/pom.xml b/kotlin/pom.xml index 5d5f1ee7..b19a8051 100644 --- a/kotlin/pom.xml +++ b/kotlin/pom.xml @@ -59,14 +59,6 @@ org.jetbrains.kotlin kotlin-maven-plugin - - org.apache.maven.plugins - maven-compiler-plugin - - 17 - 17 - - From c1816259d2498d5a365f6d3a7257efdb3fc8af12 Mon Sep 17 00:00:00 2001 From: Gerard de Leeuw Date: Wed, 29 May 2024 13:39:14 +0200 Subject: [PATCH 4/5] fix JDK 8 and JDK 11 compatibility --- .../kotlin/serializer/KotlinSerializerCborTest.kt | 6 ++---- .../kotlin/serializer/KotlinSerializerProtobufTest.kt | 6 ++---- .../extensions/kotlin/serializer/hexExtensions.kt | 5 +++++ .../kotlin/serializer/{TestTypes.kt => testTypes.kt} | 0 4 files changed, 9 insertions(+), 8 deletions(-) create mode 100644 kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/hexExtensions.kt rename kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/{TestTypes.kt => testTypes.kt} (100%) diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerCborTest.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerCborTest.kt index 6f845789..7390dcb1 100644 --- a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerCborTest.kt +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerCborTest.kt @@ -5,12 +5,10 @@ import kotlinx.serialization.cbor.Cbor import org.axonframework.extensions.kotlin.serialization.KotlinSerializer import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import java.util.HexFormat class KotlinSerializerCborTest { @OptIn(ExperimentalSerializationApi::class) private val sut = KotlinSerializer(Cbor) - private val format = HexFormat.of() @Test fun testStandardList() { @@ -21,7 +19,7 @@ class KotlinSerializerCborTest { val actual = sut.serialize(items, ByteArray::class.java) val expected = "9fbf646e616d65616163666f6f01ffbf646e616d65616263666f6f02ffff" assertEquals("List:org.axonframework.extensions.kotlin.serializer.TypeOne", actual.type.name) - assertEquals(expected, format.formatHex(actual.data)) + assertEquals(expected, actual.data.toHex()) } @Test @@ -35,6 +33,6 @@ class KotlinSerializerCborTest { val actual = sut.serialize(items, ByteArray::class.java) val expected = "9f9f636f6e65bf646e616d65616163666f6f01ffff9f636f6e65bf646e616d65616263666f6f02ffff9f6374776fbf646e616d656163636261729f0304ffffff9f636f6e65bf646e616d65616463666f6f05ffffff" assertEquals("List:org.axonframework.extensions.kotlin.serializer.SuperType", actual.type.name) - assertEquals(expected, format.formatHex(actual.data)) + assertEquals(expected, actual.data.toHex()) } } diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerProtobufTest.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerProtobufTest.kt index 275b6883..a72d0b01 100644 --- a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerProtobufTest.kt +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerProtobufTest.kt @@ -5,12 +5,10 @@ import kotlinx.serialization.protobuf.ProtoBuf import org.axonframework.extensions.kotlin.serialization.KotlinSerializer import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import java.util.HexFormat class KotlinSerializerProtobufTest { @OptIn(ExperimentalSerializationApi::class) private val sut = KotlinSerializer(ProtoBuf) - private val format = HexFormat.of() @Test fun testStandardList() { @@ -21,7 +19,7 @@ class KotlinSerializerProtobufTest { val actual = sut.serialize(items, ByteArray::class.java) val expected = "02050a01611001050a01621002" assertEquals("List:org.axonframework.extensions.kotlin.serializer.TypeOne", actual.type.name) - assertEquals(expected, format.formatHex(actual.data)) + assertEquals(expected, actual.data.toHex()) } @Test @@ -35,6 +33,6 @@ class KotlinSerializerProtobufTest { val actual = sut.serialize(items, ByteArray::class.java) val expected = "040c0a036f6e6512050a016110010c0a036f6e6512050a016210020e0a0374776f12070a0163100310040c0a036f6e6512050a01641005" assertEquals("List:org.axonframework.extensions.kotlin.serializer.SuperType", actual.type.name) - assertEquals(expected, format.formatHex(actual.data)) + assertEquals(expected, actual.data.toHex()) } } diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/hexExtensions.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/hexExtensions.kt new file mode 100644 index 00000000..862b99ce --- /dev/null +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/hexExtensions.kt @@ -0,0 +1,5 @@ +package org.axonframework.extensions.kotlin.serializer + +// copied from https://www.baeldung.com/kotlin/byte-arrays-to-hex-strings#1-formatter +fun ByteArray.toHex(): String = + joinToString("") { eachByte -> "%02x".format(eachByte) } \ No newline at end of file diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/TestTypes.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/testTypes.kt similarity index 100% rename from kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/TestTypes.kt rename to kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/testTypes.kt From 1ca85f8f77f67f31decf863156a00d1f31adfaf5 Mon Sep 17 00:00:00 2001 From: Gerard de Leeuw Date: Wed, 29 May 2024 15:53:34 +0200 Subject: [PATCH 5/5] Add copyright --- .../kotlin/serialization/ArrayResponseType.kt | 15 +++++++++++++++ .../kotlin/serialization/AxonSerializers.kt | 19 ++++++++++++++++--- .../kotlin/serialization/KotlinSerializer.kt | 15 +++++++++++++++ .../kotlin/serializer/AxonSerializersTest.kt | 15 +++++++++++++++ .../serializer/KotlinSerializerCborTest.kt | 15 +++++++++++++++ .../serializer/KotlinSerializerJsonTest.kt | 15 +++++++++++++++ .../KotlinSerializerProtobufTest.kt | 15 +++++++++++++++ .../kotlin/serializer/hexExtensions.kt | 15 +++++++++++++++ .../extensions/kotlin/serializer/testTypes.kt | 15 +++++++++++++++ 9 files changed, 136 insertions(+), 3 deletions(-) diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/ArrayResponseType.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/ArrayResponseType.kt index 9e549dca..60e127c9 100644 --- a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/ArrayResponseType.kt +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/ArrayResponseType.kt @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2010-2024. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.axonframework.extensions.kotlin.serialization import org.axonframework.common.ReflectionUtils diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/AxonSerializers.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/AxonSerializers.kt index 51001b8d..564d1ac2 100644 --- a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/AxonSerializers.kt +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/AxonSerializers.kt @@ -1,6 +1,20 @@ +/* + * Copyright (c) 2010-2024. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.axonframework.extensions.kotlin.serialization -import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.PolymorphicSerializer import kotlinx.serialization.SerializationException @@ -208,10 +222,9 @@ object ReplayTokenSerializer : KSerializer { ) } - @OptIn(ExperimentalSerializationApi::class) override fun serialize(encoder: Encoder, value: ReplayToken) = encoder.encodeStructure(descriptor) { encodeSerializableElement(descriptor, 0, trackingTokenSerializer, value.tokenAtReset) - encodeNullableSerializableElement(descriptor, 1, trackingTokenSerializer, value.currentToken) + encodeSerializableElement(descriptor, 1, trackingTokenSerializer, value.currentToken) } } diff --git a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt index e8c71487..68727fb7 100644 --- a/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt +++ b/kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2010-2024. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.axonframework.extensions.kotlin.serialization import kotlinx.serialization.BinaryFormat diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/AxonSerializersTest.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/AxonSerializersTest.kt index d13dac44..3ca88c5b 100644 --- a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/AxonSerializersTest.kt +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/AxonSerializersTest.kt @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2010-2024. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.axonframework.extensions.kotlin.serializer import kotlinx.serialization.json.Json diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerCborTest.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerCborTest.kt index 7390dcb1..ca4fef3f 100644 --- a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerCborTest.kt +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerCborTest.kt @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2010-2024. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.axonframework.extensions.kotlin.serializer import kotlinx.serialization.ExperimentalSerializationApi diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerJsonTest.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerJsonTest.kt index 42795d63..7defa704 100644 --- a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerJsonTest.kt +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerJsonTest.kt @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2010-2024. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.axonframework.extensions.kotlin.serializer import kotlinx.serialization.json.Json diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerProtobufTest.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerProtobufTest.kt index a72d0b01..46fd1825 100644 --- a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerProtobufTest.kt +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerProtobufTest.kt @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2010-2024. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.axonframework.extensions.kotlin.serializer import kotlinx.serialization.ExperimentalSerializationApi diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/hexExtensions.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/hexExtensions.kt index 862b99ce..fa91835e 100644 --- a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/hexExtensions.kt +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/hexExtensions.kt @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2010-2024. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.axonframework.extensions.kotlin.serializer // copied from https://www.baeldung.com/kotlin/byte-arrays-to-hex-strings#1-formatter diff --git a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/testTypes.kt b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/testTypes.kt index 5936beca..fa909dca 100644 --- a/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/testTypes.kt +++ b/kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/testTypes.kt @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2010-2024. Axon Framework + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.axonframework.extensions.kotlin.serializer import kotlinx.serialization.SerialName