diff --git a/pom/parent/pom.xml b/pom/parent/pom.xml
index e553b9d..a49ed45 100644
--- a/pom/parent/pom.xml
+++ b/pom/parent/pom.xml
@@ -25,9 +25,8 @@
1.11.3
- 1.11.4.4
+ 1.11.5.0
4.10.0
-
@@ -102,8 +101,6 @@
kotlin-maven-plugin
${java.version}
- 1.9
- 1.9
-Xjsr305=strict
diff --git a/serializer/core/pom.xml b/serializer/core/pom.xml
index 955a914..e56f2aa 100644
--- a/serializer/core/pom.xml
+++ b/serializer/core/pom.xml
@@ -12,11 +12,6 @@
axon-avro-serializer-core
-
- io.toolisticon.kotlin.avro
- avro-kotlin-serialization
-
-
org.axonframework
axon-messaging
@@ -44,17 +39,43 @@
slf4j-simple
test
-
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+ ${java.version}
+
+ -Xjsr305=strict
+
+
+ spring
+ jpa
+ no-arg
+ all-open
+ kotlinx-serialization
+
+
+
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-serialization
+ ${kotlin.version}
+
+
+
org.apache.avro
avro-maven-plugin
-
-
diff --git a/serializer/core/src/main/kotlin/AvroSerializer.kt b/serializer/core/src/main/kotlin/AvroSerializer.kt
index 3a062ad..bee0c7c 100644
--- a/serializer/core/src/main/kotlin/AvroSerializer.kt
+++ b/serializer/core/src/main/kotlin/AvroSerializer.kt
@@ -1,12 +1,16 @@
package io.holixon.axon.avro.serializer
import io.holixon.axon.avro.serializer.converter.*
-import io.holixon.axon.avro.serializer.strategy.*
+import io.holixon.axon.avro.serializer.strategy.InstanceResponseTypeStrategy
+import io.holixon.axon.avro.serializer.strategy.MetaDataStrategy
+import io.holixon.axon.avro.serializer.strategy.MultipleInstancesResponseTypeStrategy
import io.toolisticon.kotlin.avro.AvroKotlin
-import io.toolisticon.kotlin.avro.repository.AvroSchemaResolver
import io.toolisticon.kotlin.avro.repository.AvroSchemaResolverMap
-import io.toolisticon.kotlin.avro.repository.plus
import io.toolisticon.kotlin.avro.serialization.AvroKotlinSerialization
+import io.toolisticon.kotlin.avro.serialization.strategy.GenericRecordSerializationStrategy
+import io.toolisticon.kotlin.avro.serialization.strategy.KotlinxDataClassStrategy
+import io.toolisticon.kotlin.avro.serialization.strategy.KotlinxEnumClassStrategy
+import io.toolisticon.kotlin.avro.serialization.strategy.SpecificRecordBaseStrategy
import io.toolisticon.kotlin.avro.value.SingleObjectEncodedBytes
import mu.KLogging
import org.apache.avro.generic.GenericData
@@ -19,11 +23,9 @@ import java.util.function.Supplier
class AvroSerializer private constructor(
private val converter: Converter,
private val revisionResolver: RevisionResolver,
- private val serializationStrategies: List,
- private val deserializationStrategies: List
+ private val serializationStrategies: List,
) : Serializer {
-
companion object : KLogging() {
private val axonSchemaResolver: AvroSchemaResolverMap = AvroSchemaResolverMap(
@@ -38,12 +40,7 @@ class AvroSerializer private constructor(
fun builder() = Builder()
operator fun invoke(builder: Builder): AvroSerializer {
-
- val schemaResolver = if (builder.builderAvroSchemaResolver is AvroSchemaResolverMap) {
- builder.builderAvroSchemaResolver + axonSchemaResolver
- } else {
- builder.builderAvroSchemaResolver + axonSchemaResolver
- }
+ axonSchemaResolver.values.forEach(builder.avroKotlinSerialization::registerSchema)
val converter = if (builder.converter is ChainingConverter) {
logger.debug { "" }
@@ -71,7 +68,7 @@ class AvroSerializer private constructor(
registerConverter(ListRecordToSingleObjectEncodedConverter())
registerConverter(ByteArrayToSingleObjectEncodedConverter())
- registerConverter(SingleObjectEncodedToGenericRecordConverter(schemaResolver))
+ registerConverter(SingleObjectEncodedToGenericRecordConverter(builder.avroKotlinSerialization))
// JSON handling in inverted order: GenericRecord -> String
registerConverter(JsonStringToStringConverter())
@@ -108,30 +105,17 @@ class AvroSerializer private constructor(
multipleInstancesResponseTypeStrategy,
specificRecordBaseStrategy
),
- deserializationStrategies = listOf(
- instanceResponseTypeStrategy,
- kotlinxDataClassStrategy,
- kotlinxEnumClassStrategy,
- metaDataStrategy,
- multipleInstancesResponseTypeStrategy,
- specificRecordBaseStrategy
- ),
)
}
}
class Builder {
- internal lateinit var builderAvroSchemaResolver: AvroSchemaResolver
+ internal var avroKotlinSerialization = AvroKotlinSerialization()
internal var converter: Converter = ChainingConverter()
internal var revisionResolver: RevisionResolver = AnnotationRevisionResolver()
internal val contentTypeConverters: MutableList> = mutableListOf()
- internal var avroKotlinSerialization = AvroKotlinSerialization()
internal var genericDataSupplier: Supplier = Supplier { AvroKotlin.genericData }
- fun avroSchemaResolver(avroSchemaResolver: AvroSchemaResolver) = apply {
- builderAvroSchemaResolver = avroSchemaResolver
- }
-
fun addContentTypeConverter(contentTypeConverter: ContentTypeConverter<*, *>) = apply {
this.contentTypeConverters.add(contentTypeConverter)
}
@@ -145,19 +129,16 @@ class AvroSerializer private constructor(
}
fun build(): AvroSerializer {
- require(this::builderAvroSchemaResolver.isInitialized) { "AvroSchemaResolver must be provided." }
-
return AvroSerializer(this)
}
}
-
override fun serialize(data: Any?, expectedRepresentation: Class): SerializedObject {
requireNotNull(data) {
"Can't serialize null."
}
- val strategy = serializationStrategies.firstOrNull { it.canSerialize(data::class.java) }.also {
+ val strategy = serializationStrategies.firstOrNull { it.test(data::class) }.also {
if (it != null) {
logger.debug { "Using strategy ${it::class.java.name} for ${data::class.java}." }
} else {
@@ -186,7 +167,7 @@ class AvroSerializer private constructor(
override fun deserialize(serializedObject: SerializedObject): T {
val serializedType = classForType(serializedObject.type)
- val strategy = deserializationStrategies.firstOrNull { it.canDeserialize(serializedType) }.also {
+ val strategy = serializationStrategies.firstOrNull { it.test(serializedType.kotlin) }.also {
if (it != null) {
logger.debug { "Using strategy ${it::class.java.name} for $serializedType." }
} else {
@@ -197,7 +178,7 @@ class AvroSerializer private constructor(
@Suppress("UNCHECKED_CAST", "IfThenToElvis")
return if (strategy != null) {
strategy.deserialize(
- serializedType = serializedType,
+ serializedType = serializedType.kotlin,
data = converter.convert(serializedObject, GenericRecord::class.java).data
)
} else {
diff --git a/serializer/core/src/main/kotlin/strategy/AvroDeserializationStrategy.kt b/serializer/core/src/main/kotlin/strategy/AvroDeserializationStrategy.kt
deleted file mode 100644
index 9e63718..0000000
--- a/serializer/core/src/main/kotlin/strategy/AvroDeserializationStrategy.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package io.holixon.axon.avro.serializer.strategy
-
-import org.apache.avro.generic.GenericRecord
-
-interface AvroDeserializationStrategy {
-
- fun canDeserialize(serializedType: Class<*>) : Boolean
-
- fun deserialize(serializedType: Class<*>, data: GenericRecord) : T
-
-}
diff --git a/serializer/core/src/main/kotlin/strategy/AvroSerializationStrategy.kt b/serializer/core/src/main/kotlin/strategy/AvroSerializationStrategy.kt
deleted file mode 100644
index cab65e1..0000000
--- a/serializer/core/src/main/kotlin/strategy/AvroSerializationStrategy.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package io.holixon.axon.avro.serializer.strategy
-
-import org.apache.avro.generic.GenericRecord
-
-interface AvroSerializationStrategy {
-
- fun canSerialize(serializedType: Class<*>): Boolean
-
- fun serialize(data: Any): GenericRecord
-}
diff --git a/serializer/core/src/main/kotlin/strategy/InstanceResponseTypeStrategy.kt b/serializer/core/src/main/kotlin/strategy/InstanceResponseTypeStrategy.kt
index bc25804..b817c41 100644
--- a/serializer/core/src/main/kotlin/strategy/InstanceResponseTypeStrategy.kt
+++ b/serializer/core/src/main/kotlin/strategy/InstanceResponseTypeStrategy.kt
@@ -3,30 +3,29 @@ package io.holixon.axon.avro.serializer.strategy
import _ktx.ResourceKtx
import io.toolisticon.kotlin.avro.AvroKotlin
import io.toolisticon.kotlin.avro.model.wrapper.AvroSchema
+import io.toolisticon.kotlin.avro.serialization.strategy.GenericRecordSerializationStrategy
import io.toolisticon.kotlin.avro.value.Name.Companion.toName
import org.apache.avro.generic.GenericRecord
import org.apache.avro.util.Utf8
import org.axonframework.messaging.responsetypes.InstanceResponseType
+import kotlin.reflect.KClass
@Suppress("UNCHECKED_CAST")
-class InstanceResponseTypeStrategy() : AvroDeserializationStrategy, AvroSerializationStrategy {
+class InstanceResponseTypeStrategy() : GenericRecordSerializationStrategy {
companion object {
val SCHEMA = AvroSchema.of(resource = ResourceKtx.resourceUrl("schema/AvroInstanceResponseType.avsc"))
const val FIELD = "expectedResponseType"
val FIELD_SCHEMA = SCHEMA.getField(FIELD.toName())!!.schema
}
+ override fun test(serializedType: KClass<*>): Boolean = InstanceResponseType::class.java == serializedType
- override fun canDeserialize(serializedType: Class<*>): Boolean = InstanceResponseType::class.java == serializedType
-
- override fun deserialize(serializedType: Class<*>, data: GenericRecord): T {
+ override fun deserialize(serializedType: KClass<*>, data: GenericRecord): T {
val className = data.get(FIELD) as Utf8
return InstanceResponseType(Class.forName(className.toString())) as T
}
- override fun canSerialize(serializedType: Class<*>): Boolean = InstanceResponseType::class.java == serializedType
-
- override fun serialize(data: Any): GenericRecord {
+ override fun serialize(data: T): GenericRecord {
require(data is InstanceResponseType<*>)
return AvroKotlin.createGenericRecord(SCHEMA) {
put(FIELD, data.expectedResponseType.canonicalName)
diff --git a/serializer/core/src/main/kotlin/strategy/KotlinxDataClassStrategy.kt b/serializer/core/src/main/kotlin/strategy/KotlinxDataClassStrategy.kt
deleted file mode 100644
index cc3c723..0000000
--- a/serializer/core/src/main/kotlin/strategy/KotlinxDataClassStrategy.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-package io.holixon.axon.avro.serializer.strategy
-
-import io.toolisticon.kotlin.avro.serialization.AvroKotlinSerialization
-import kotlinx.serialization.Serializable
-import org.apache.avro.generic.GenericRecord
-
-class KotlinxDataClassStrategy(
- private val avroKotlinSerialization: AvroKotlinSerialization
-) : AvroSerializationStrategy, AvroDeserializationStrategy {
-
- override fun canDeserialize(serializedType: Class<*>): Boolean = isKotlinxDataClass(serializedType)
-
- @Suppress("UNCHECKED_CAST")
- override fun deserialize(serializedType: Class<*>, data: GenericRecord): T {
- return avroKotlinSerialization.fromRecord(record = data, type = serializedType.kotlin) as T
- }
-
- override fun canSerialize(serializedType: Class<*>): Boolean = isKotlinxDataClass(serializedType)
-
- override fun serialize(data: Any): GenericRecord {
- return avroKotlinSerialization.toRecord(data = data)
- }
-
- private fun isKotlinxDataClass(serializedType: Class<*>): Boolean {
- // TODO: can this check be replaced by some convenience magic from kotlinx.serialization
- return serializedType.kotlin.isData
- && serializedType.annotations.any { it is Serializable }
- }
-}
diff --git a/serializer/core/src/main/kotlin/strategy/KotlinxEnumClassStrategy.kt b/serializer/core/src/main/kotlin/strategy/KotlinxEnumClassStrategy.kt
deleted file mode 100644
index 26cca00..0000000
--- a/serializer/core/src/main/kotlin/strategy/KotlinxEnumClassStrategy.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-package io.holixon.axon.avro.serializer.strategy
-
-import io.toolisticon.kotlin.avro.serialization.AvroKotlinSerialization
-import kotlinx.serialization.Serializable
-import org.apache.avro.generic.GenericData
-import org.apache.avro.generic.GenericRecord
-
-class KotlinxEnumClassStrategy(
- private val avroKotlinSerialization: AvroKotlinSerialization
-) : AvroSerializationStrategy, AvroDeserializationStrategy {
-
- override fun canSerialize(serializedType: Class<*>): Boolean = isKotlinxEnumClass(serializedType)
- override fun canDeserialize(serializedType: Class<*>): Boolean = isKotlinxEnumClass(serializedType)
-
- @Suppress("UNCHECKED_CAST")
- override fun deserialize(serializedType: Class<*>, data: GenericRecord): T {
- return avroKotlinSerialization.fromRecord(record = data, type = serializedType.kotlin) as T
- }
-
- override fun serialize(data: Any): GenericRecord {
- return avroKotlinSerialization.toRecord(data = data)
- }
-
- private fun isKotlinxEnumClass(serializedType: Class<*>) : Boolean {
- // TODO: can this check be replaced by some convenience magic from kotlinx.serialization
- return serializedType.isEnum
- && serializedType.annotations.any { it is Serializable }
- }
-}
diff --git a/serializer/core/src/main/kotlin/strategy/MetaDataStrategy.kt b/serializer/core/src/main/kotlin/strategy/MetaDataStrategy.kt
index 5f32285..c9ded9c 100644
--- a/serializer/core/src/main/kotlin/strategy/MetaDataStrategy.kt
+++ b/serializer/core/src/main/kotlin/strategy/MetaDataStrategy.kt
@@ -3,30 +3,30 @@ package io.holixon.axon.avro.serializer.strategy
import _ktx.ResourceKtx
import io.toolisticon.kotlin.avro.AvroKotlin
import io.toolisticon.kotlin.avro.model.wrapper.AvroSchema
+import io.toolisticon.kotlin.avro.serialization.strategy.GenericRecordSerializationStrategy
import io.toolisticon.kotlin.avro.value.Name.Companion.toName
import org.apache.avro.generic.GenericData
import org.apache.avro.generic.GenericRecord
import org.axonframework.messaging.MetaData
+import kotlin.reflect.KClass
class MetaDataStrategy(
private val genericData: GenericData
-) : AvroSerializationStrategy, AvroDeserializationStrategy {
+) : GenericRecordSerializationStrategy {
companion object {
val SCHEMA = AvroSchema.of(resource = ResourceKtx.resourceUrl("schema/AvroMetaData.avsc"))
const val FIELD_VALUES = "values"
val SCHEMA_VALUES = SCHEMA.getField(FIELD_VALUES.toName())!!.schema
}
- override fun canDeserialize(serializedType: Class<*>): Boolean = MetaData::class.java == serializedType
+ override fun test(serializedType: KClass<*>): Boolean = MetaData::class.java == serializedType
@Suppress("UNCHECKED_CAST")
- override fun deserialize(serializedType: Class<*>, data: GenericRecord): T {
+ override fun deserialize(serializedType: KClass<*>, data: GenericRecord): T {
return MetaData.from(data.get(FIELD_VALUES) as Map) as T
}
- override fun canSerialize(serializedType: Class<*>): Boolean = MetaData::class.java == serializedType
-
- override fun serialize(data: Any): GenericRecord {
+ override fun serialize(data: T): GenericRecord {
require(isSchemaCompliant(data)) { "Data: $data not compliant with schema=$SCHEMA" }
return AvroKotlin.createGenericRecord(SCHEMA) {
put(FIELD_VALUES, data)
diff --git a/serializer/core/src/main/kotlin/strategy/MultipleInstancesResponseTypeStrategy.kt b/serializer/core/src/main/kotlin/strategy/MultipleInstancesResponseTypeStrategy.kt
index 18942cf..5ddbb88 100644
--- a/serializer/core/src/main/kotlin/strategy/MultipleInstancesResponseTypeStrategy.kt
+++ b/serializer/core/src/main/kotlin/strategy/MultipleInstancesResponseTypeStrategy.kt
@@ -3,14 +3,16 @@ package io.holixon.axon.avro.serializer.strategy
import _ktx.ResourceKtx
import io.toolisticon.kotlin.avro.AvroKotlin
import io.toolisticon.kotlin.avro.model.wrapper.AvroSchema
+import io.toolisticon.kotlin.avro.serialization.strategy.GenericRecordSerializationStrategy
import io.toolisticon.kotlin.avro.value.Name.Companion.toName
import org.apache.avro.generic.GenericData
import org.apache.avro.generic.GenericRecord
import org.apache.avro.util.Utf8
import org.axonframework.messaging.responsetypes.MultipleInstancesResponseType
+import kotlin.reflect.KClass
@Suppress("UNCHECKED_CAST")
-class MultipleInstancesResponseTypeStrategy : AvroDeserializationStrategy, AvroSerializationStrategy {
+class MultipleInstancesResponseTypeStrategy : GenericRecordSerializationStrategy {
companion object {
val SCHEMA = AvroSchema.of(resource = ResourceKtx.resourceUrl("schema/AvroMultipleInstancesResponseType.avsc"))
const val FIELD = "expectedResponseType"
@@ -18,17 +20,16 @@ class MultipleInstancesResponseTypeStrategy : AvroDeserializationStrategy, AvroS
}
- override fun canDeserialize(serializedType: Class<*>): Boolean = MultipleInstancesResponseType::class.java == serializedType
+ override fun test(serializedType: KClass<*>): Boolean = MultipleInstancesResponseType::class == serializedType
- override fun deserialize(serializedType: Class<*>, data: GenericRecord): T {
+ override fun deserialize(serializedType: KClass<*>, data: GenericRecord): T {
+ // TODO we shouldn't bother the avro type utf8
val className = data.get(FIELD) as Utf8
return MultipleInstancesResponseType(Class.forName(className.toString())) as T
}
- override fun canSerialize(serializedType: Class<*>): Boolean = MultipleInstancesResponseType::class.java == serializedType
-
- override fun serialize(data: Any): GenericRecord {
+ override fun serialize(data: T): GenericRecord {
require(data is MultipleInstancesResponseType<*>)
return AvroKotlin.createGenericRecord(SCHEMA) {
put(FIELD, data.expectedResponseType.canonicalName)
diff --git a/serializer/core/src/main/kotlin/strategy/SpecificRecordBaseStrategy.kt b/serializer/core/src/main/kotlin/strategy/SpecificRecordBaseStrategy.kt
deleted file mode 100644
index 4477628..0000000
--- a/serializer/core/src/main/kotlin/strategy/SpecificRecordBaseStrategy.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package io.holixon.axon.avro.serializer.strategy
-
-import io.toolisticon.kotlin.avro.codec.SpecificRecordCodec
-import org.apache.avro.generic.GenericRecord
-import org.apache.avro.specific.SpecificRecordBase
-
-class SpecificRecordBaseStrategy : AvroSerializationStrategy, AvroDeserializationStrategy {
- private val converter = SpecificRecordCodec.specificRecordToGenericRecordConverter()
-
- override fun canDeserialize(serializedType: Class<*>): Boolean = isGeneratedSpecificRecordBase(serializedType)
-
- override fun deserialize(serializedType: Class<*>, data: GenericRecord): T {
- @Suppress("UNCHECKED_CAST")
- return SpecificRecordCodec.genericRecordToSpecificRecordConverter(serializedType).convert(data) as T
- }
-
- override fun canSerialize(serializedType: Class<*>): Boolean = isGeneratedSpecificRecordBase(serializedType)
-
- override fun serialize(data: Any): GenericRecord = converter.convert(data as SpecificRecordBase)
-
- private fun isGeneratedSpecificRecordBase(serializedType: Class<*>): Boolean =
- SpecificRecordBase::class.java.isAssignableFrom(serializedType)
-}
diff --git a/serializer/core/src/test/kotlin/Avro4kEnumSerializationTest.kt b/serializer/core/src/test/kotlin/Avro4kEnumSerializationTest.kt
deleted file mode 100644
index a4874a0..0000000
--- a/serializer/core/src/test/kotlin/Avro4kEnumSerializationTest.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package io.holixon.axon.avro.serializer
-
-import com.github.avrokotlin.avro4k.Avro
-import kotlinx.serialization.Serializable
-import org.junit.jupiter.api.Test
-
-@Serializable
-enum class FindAllQuery {
- INSTANCE
-}
-
-internal class Avro4kEnumSerializationTest {
-
- @Test
- fun name() {
- println(Avro.default.schema(FindAllQuery.serializer()))
- }
-}
diff --git a/serializer/core/src/test/kotlin/AvroKotlinSerializationTest.kt b/serializer/core/src/test/kotlin/AvroKotlinSerializationTest.kt
new file mode 100644
index 0000000..2795367
--- /dev/null
+++ b/serializer/core/src/test/kotlin/AvroKotlinSerializationTest.kt
@@ -0,0 +1,48 @@
+package io.holixon.axon.avro.serializer
+
+import io.holixon.axon.avro.serializer._test.BarString
+import io.holixon.axon.avro.serializer._test.barStringSchema
+import io.toolisticon.kotlin.avro.serialization.AvroKotlinSerialization
+import io.toolisticon.kotlin.avro.serialization.isKotlinxDataClass
+import io.toolisticon.kotlin.avro.serialization.isSerializable
+import io.toolisticon.kotlin.avro.serialization.kserializer
+import kotlinx.serialization.InternalSerializationApi
+import kotlinx.serialization.serializer
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+
+internal class AvroKotlinSerializationTest {
+
+ private val avro = AvroKotlinSerialization()
+
+ @Test
+ fun `get schema from BarString`() {
+ assertThat(avro.cachedSchemaClasses()).isEmpty()
+ assertThat(avro.cachedSerializerClasses()).isEmpty()
+
+ val schema = avro.schema(BarString::class)
+
+ assertThat(schema.fingerprint).isEqualTo(barStringSchema.fingerprint)
+
+ assertThat(avro.cachedSchemaClasses()).containsExactly(BarString::class)
+ assertThat(avro.cachedSerializerClasses()).containsExactly(BarString::class)
+
+ assertThat(avro[barStringSchema.fingerprint]).isEqualTo(schema)
+
+ val data = BarString("foo")
+ val encoded = avro.singleObjectEncoder().encode(data)
+
+ val decoded = avro.singleObjectDecoder().decode(encoded)
+
+ assertThat(decoded).isEqualTo(data)
+ }
+
+
+ @Test
+ fun `barString is kotlinx serializable`() {
+ assertThat(BarString::class.isSerializable()).isTrue()
+ assertThat(BarString::class.isKotlinxDataClass()).isTrue()
+ assertThat(BarString::class.kserializer()).isNotNull
+ assertThat(avro.schema(BarString::class)).isNotNull
+ }
+}
diff --git a/serializer/core/src/test/kotlin/AvroSerializerTest.kt b/serializer/core/src/test/kotlin/AvroSerializerTest.kt
index e273898..c79848b 100644
--- a/serializer/core/src/test/kotlin/AvroSerializerTest.kt
+++ b/serializer/core/src/test/kotlin/AvroSerializerTest.kt
@@ -1,8 +1,10 @@
package io.holixon.axon.avro.serializer
import bankaccount.event.BankAccountCreated
+import io.holixon.axon.avro.serializer._test.BarString
import io.toolisticon.kotlin.avro.AvroKotlin.avroSchemaResolver
import io.toolisticon.kotlin.avro.codec.SpecificRecordCodec
+import io.toolisticon.kotlin.avro.model.wrapper.AvroSchema
import io.toolisticon.kotlin.avro.serialization.AvroKotlinSerialization
import io.toolisticon.kotlin.avro.value.SingleObjectEncodedBytes
import org.apache.avro.generic.GenericRecord
@@ -12,15 +14,16 @@ import org.axonframework.serialization.SimpleSerializedType
import org.javamoney.moneta.Money
import org.junit.jupiter.api.Test
-
internal class AvroSerializerTest {
- private val schemaResolver = avroSchemaResolver(TestFixtures.BankAccountCreatedFixture.SCHEMA.get())
+
+ private val avroKotlinSerialization = AvroKotlinSerialization()
+ .registerSchema(TestFixtures.BankAccountCreatedFixture.SCHEMA)
@Test
fun `canSerializeTo - genericRecord`() {
val serializer = AvroSerializer.builder()
- .avroSchemaResolver(schemaResolver)
+ .avroKotlinSerialization(avroKotlinSerialization)
.build()
assertThat(serializer.canSerializeTo(GenericRecord::class.java)).isTrue()
@@ -28,9 +31,8 @@ internal class AvroSerializerTest {
@Test
fun `canSerializeTo - string`() {
-
val serializer = AvroSerializer.builder()
- .avroSchemaResolver(schemaResolver)
+ .avroKotlinSerialization(avroKotlinSerialization)
.build()
assertThat(serializer.canSerializeTo(String::class.java)).isTrue()
@@ -39,9 +41,8 @@ internal class AvroSerializerTest {
@Test
fun `canSerializeTo - singleObjectEncoded`() {
-
val serializer = AvroSerializer.builder()
- .avroSchemaResolver(schemaResolver)
+ .avroKotlinSerialization(avroKotlinSerialization)
.build()
assertThat(serializer.canSerializeTo(SingleObjectEncodedBytes::class.java)).isTrue()
@@ -50,9 +51,8 @@ internal class AvroSerializerTest {
@Test
fun `serialize specific record`() {
- val schemaResolver = avroSchemaResolver(BankAccountCreated.getClassSchema())
val serializer = AvroSerializer.builder()
- .avroSchemaResolver(schemaResolver)
+ .avroKotlinSerialization(avroKotlinSerialization.registerSchema(AvroSchema(BankAccountCreated.getClassSchema())))
.build()
val data = BankAccountCreated.newBuilder()
@@ -64,14 +64,16 @@ internal class AvroSerializerTest {
val bytes = serialized.data
- assertThat(SpecificRecordCodec.specificRecordSingleObjectDecoder(schemaResolver).decode(bytes)).isEqualTo(data)
+ assertThat(SpecificRecordCodec.specificRecordSingleObjectDecoder(avroKotlinSerialization).decode(bytes)).isEqualTo(data)
}
@Test
fun `deserialize singleObjectEncoded to specificRecord`() {
- val schemaResolver = avroSchemaResolver(BankAccountCreated.getClassSchema())
val serializer = AvroSerializer.builder()
- .avroSchemaResolver(schemaResolver)
+ .avroKotlinSerialization(
+ avroKotlinSerialization
+ .registerSchema(AvroSchema(BankAccountCreated.getClassSchema()))
+ )
.build()
// val data = BankAccountCreated.newBuilder()
@@ -99,10 +101,13 @@ internal class AvroSerializerTest {
val bar = BarString("hello world")
val avro = AvroKotlinSerialization()
- val schemaResolver = avroSchemaResolver(avro.schema(BarString::class).get())
+ val schema = avro.schema(bar::class)
+ println(schema)
+
+ val schemaResolver = avroSchemaResolver(avro.schema(BarString::class))
+
val serializer = AvroSerializer.builder()
- .avroSchemaResolver(schemaResolver)
- .avroKotlinSerialization(AvroKotlinSerialization())
+ .avroKotlinSerialization(avro)
.build()
val serialized = serializer.serialize(bar, ByteArray::class.java)
diff --git a/serializer/core/src/test/kotlin/BarString.kt b/serializer/core/src/test/kotlin/BarString.kt
deleted file mode 100644
index 6ab1ab8..0000000
--- a/serializer/core/src/test/kotlin/BarString.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package io.holixon.axon.avro.serializer
-
-import kotlinx.serialization.Serializable
-
-// FIXME: REMOVE!
-@Serializable
-data class BarString(val name: String)
diff --git a/serializer/core/src/test/kotlin/_test/BarString.kt b/serializer/core/src/test/kotlin/_test/BarString.kt
new file mode 100644
index 0000000..94c2594
--- /dev/null
+++ b/serializer/core/src/test/kotlin/_test/BarString.kt
@@ -0,0 +1,16 @@
+package io.holixon.axon.avro.serializer._test
+
+import io.toolisticon.kotlin.avro.model.wrapper.AvroSchema
+import kotlinx.serialization.Serializable
+import org.apache.avro.SchemaBuilder
+
+@Serializable
+data class BarString(val name: String)
+
+val barStringSchema = AvroSchema(
+ SchemaBuilder.record("BarString")
+ .namespace("io.holixon.axon.avro.serializer._test")
+ .fields()
+ .requiredString("name")
+ .endRecord())
+
diff --git a/serializer/spring-autoconfigure/pom.xml b/serializer/spring-autoconfigure/pom.xml
index 2e86688..2ea0e1f 100644
--- a/serializer/spring-autoconfigure/pom.xml
+++ b/serializer/spring-autoconfigure/pom.xml
@@ -23,6 +23,13 @@
spring-boot-autoconfigure
provided
+
+
+ org.jetbrains.kotlinx
+ kotlinx-serialization-core-jvm
+ ${kotlinx-serialization.version}
+
+
org.springframework
spring-web
diff --git a/serializer/spring-autoconfigure/src/main/kotlin/AvroSchemaScanner.kt b/serializer/spring-autoconfigure/src/main/kotlin/AvroSchemaScanner.kt
index 0f7e598..35b8f69 100644
--- a/serializer/spring-autoconfigure/src/main/kotlin/AvroSchemaScanner.kt
+++ b/serializer/spring-autoconfigure/src/main/kotlin/AvroSchemaScanner.kt
@@ -1,8 +1,7 @@
package io.holixon.axon.avro.serializer.spring
-import com.github.avrokotlin.avro4k.Avro
import io.toolisticon.kotlin.avro.model.wrapper.AvroSchema
-import kotlinx.serialization.KSerializer
+import io.toolisticon.kotlin.avro.serialization.AvroKotlinSerialization
import kotlinx.serialization.Serializable
import mu.KLogging
import org.apache.avro.specific.SpecificRecordBase
@@ -10,14 +9,12 @@ import org.springframework.context.annotation.ClassPathScanningCandidateComponen
import org.springframework.core.io.ResourceLoader
import org.springframework.core.type.filter.AnnotationTypeFilter
import org.springframework.core.type.filter.AssignableTypeFilter
-import kotlin.reflect.full.companionObject
-import kotlin.reflect.full.companionObjectInstance
internal class AvroSchemaScanner(
private val resourceLoader: ResourceLoader,
private val detectKotlinXSerialization: Boolean,
private val detectSpecificRecordBase: Boolean,
- private val avro4k: Avro = Avro.default,
+ private val avro: AvroKotlinSerialization = AvroKotlinSerialization(),
) {
companion object : KLogging()
@@ -49,7 +46,7 @@ internal class AvroSchemaScanner(
logger.info { "Found specific record of type: ${specificRecordClass.name} with schema $schema" }
schema
} else if (candidateClass.isAnnotationPresent(Serializable::class.java) && candidateClass.kotlin.isData) {
- val schema = candidateClass.getSerializerSchema()
+ val schema = avro.schema(candidateClass.kotlin)
logger.info { "Found KotlinX Serialized data class ${candidateClass.name} with schema $schema" }
schema
} else {
@@ -69,8 +66,4 @@ internal class AvroSchemaScanner(
return AvroSchema(schema)
}
- private fun Class<*>.getSerializerSchema(): AvroSchema {
- val serializer = this.kotlin.companionObject?.members?.first { it.name == "serializer" }?.call(this.kotlin.companionObjectInstance) as KSerializer<*>
- return AvroSchema(avro4k.schema(serializer))
- }
}
diff --git a/serializer/spring-autoconfigure/src/main/kotlin/AvroSchemaScannerConfiguration.kt b/serializer/spring-autoconfigure/src/main/kotlin/AvroSchemaScannerConfiguration.kt
index 8145ef0..11339bb 100644
--- a/serializer/spring-autoconfigure/src/main/kotlin/AvroSchemaScannerConfiguration.kt
+++ b/serializer/spring-autoconfigure/src/main/kotlin/AvroSchemaScannerConfiguration.kt
@@ -2,6 +2,7 @@ package io.holixon.axon.avro.serializer.spring
import io.toolisticon.kotlin.avro.model.wrapper.AvroSchema
import io.toolisticon.kotlin.avro.repository.AvroSchemaResolver
+import io.toolisticon.kotlin.avro.repository.AvroSchemaResolverMap
import org.springframework.beans.factory.BeanFactory
import org.springframework.boot.autoconfigure.AutoConfigurationPackages
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
@@ -30,9 +31,9 @@ class AvroSchemaScannerConfiguration {
@Bean
@ConditionalOnMissingBean
- fun defaultAvroSchemaResolver(schemas: List): AvroSchemaResolver {
+ fun defaultAvroSchemaResolver(schemas: List): AvroSchemaResolverMap {
require(schemas.isNotEmpty()) { "Could not find any Avro Schemas. At least one schema is required for the resolver." }
- return io.toolisticon.kotlin.avro.repository.avroSchemaResolver(schemas)
+ return AvroSchemaResolverMap(schemas.associateBy { it.fingerprint })
}
}
diff --git a/serializer/spring-autoconfigure/src/main/kotlin/AxonAvroSerializerConfiguration.kt b/serializer/spring-autoconfigure/src/main/kotlin/AxonAvroSerializerConfiguration.kt
index 0b02945..b4dde24 100644
--- a/serializer/spring-autoconfigure/src/main/kotlin/AxonAvroSerializerConfiguration.kt
+++ b/serializer/spring-autoconfigure/src/main/kotlin/AxonAvroSerializerConfiguration.kt
@@ -2,6 +2,7 @@ package io.holixon.axon.avro.serializer.spring
import io.holixon.axon.avro.serializer.AvroSerializer
import io.toolisticon.kotlin.avro.repository.AvroSchemaResolver
+import io.toolisticon.kotlin.avro.repository.AvroSchemaResolverMap
import io.toolisticon.kotlin.avro.serialization.AvroKotlinSerialization
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
@@ -19,16 +20,27 @@ open class AxonAvroSerializerConfiguration {
const val DEFAULT_SERIALIZER = "defaultSerializer"
}
+ @Bean
+ @ConditionalOnMissingBean(AvroKotlinSerialization::class)
+ fun avroKotlinSerialization(schemaResolver: AvroSchemaResolverMap): AvroKotlinSerialization {
+ // TODO: use correct setup with registered serializers
+ val avro = AvroKotlinSerialization()
+ schemaResolver.values.forEach(avro::registerSchema)
+
+ return avro
+ }
+
/**
* Bean factory for the serializer builder.
*/
@Bean
@ConditionalOnMissingBean(AvroSerializer.Builder::class)
- fun defaultAxonSerializerBuilder(schemaResolver: AvroSchemaResolver): AvroSerializer.Builder = AvroSerializer
- .builder()
- .avroSchemaResolver(schemaResolver)
- .avroKotlinSerialization(AvroKotlinSerialization()) // TODO: use correct setup with registered serializers
+ fun defaultAxonSerializerBuilder(avro: AvroKotlinSerialization): AvroSerializer.Builder {
+ return AvroSerializer
+ .builder()
+ .avroKotlinSerialization(avro)
+ }
/**
* Bean factory for the serializer.
@@ -40,5 +52,5 @@ open class AxonAvroSerializerConfiguration {
@Bean
@ConditionalOnProperty(value = ["\${axon.avro.serializer.rest-enabled}"], havingValue = "true", matchIfMissing = true)
- fun schemaResolverRestResource(schemaResolver: AvroSchemaResolver) = AvroSchemaResolverResource(schemaResolver = schemaResolver)
+ fun schemaResolverRestResource(avro: AvroKotlinSerialization) = AvroSchemaResolverResource(schemaResolver = avro)
}
diff --git a/serializer/spring-autoconfigure/src/test/kotlin/TestFixtures.kt b/serializer/spring-autoconfigure/src/test/kotlin/TestFixtures.kt
new file mode 100644
index 0000000..cbca7ef
--- /dev/null
+++ b/serializer/spring-autoconfigure/src/test/kotlin/TestFixtures.kt
@@ -0,0 +1,37 @@
+package io.holixon.axon.avro.serializer.spring
+
+import io.toolisticon.kotlin.avro.AvroKotlin
+import io.toolisticon.kotlin.avro.model.wrapper.AvroSchema
+import io.toolisticon.kotlin.avro.value.JsonString
+import upcaster.itest.DummyEvent
+
+object TestFixtures {
+ object DummyEvents {
+ val jsonSchema01 = JsonString.of(
+ """
+ {
+ "type": "record",
+ "namespace": "upcaster.itest",
+ "name": "DummyEvent",
+ "revision": "1",
+ "fields": [
+ {
+ "name": "value01",
+ "type": {
+ "type": "string",
+ "avro.java.string": "String"
+ }
+ }
+ ]
+ }
+ """.trimIndent()
+ )
+
+ val SCHEMA_EVENT_01: AvroSchema = AvroSchema.of(jsonSchema01)
+
+ val SCHEMA_EVENT_10: AvroSchema = AvroSchema(DummyEvent.getClassSchema())
+
+ val registry = AvroKotlin.avroSchemaResolver(listOf(SCHEMA_EVENT_01, SCHEMA_EVENT_10))
+
+ }
+}
diff --git a/serializer/spring-autoconfigure/src/test/kotlin/itest/scan/AvroSchemaScanConfigurationITestBase.kt b/serializer/spring-autoconfigure/src/test/kotlin/itest/scan/AvroSchemaScanConfigurationITestBase.kt
index 378d6a5..b0fe708 100644
--- a/serializer/spring-autoconfigure/src/test/kotlin/itest/scan/AvroSchemaScanConfigurationITestBase.kt
+++ b/serializer/spring-autoconfigure/src/test/kotlin/itest/scan/AvroSchemaScanConfigurationITestBase.kt
@@ -103,7 +103,7 @@ abstract class AvroSchemaScanConfigurationITestBase {
@Test
fun `should find schemas in packages by class mixing kotlinx with specific record base`() {
assertThat(avroSchemas).isNotNull
- assertThat(avroSchemas).hasSize(3) // one generated two from DummyEvents.kt
+ assertThat(avroSchemas).hasSize(3) // one generated two from DummyEventsTest.kt
}
}
@@ -116,7 +116,7 @@ abstract class AvroSchemaScanConfigurationITestBase {
@Test
fun `should find schemas in packages by class mixing kotlinx with specific record base`() {
assertThat(avroSchemas).isNotNull
- assertThat(avroSchemas).hasSize(3) // one generated two from DummyEvents.kt
+ assertThat(avroSchemas).hasSize(3) // one generated two from DummyEventsTest.kt
}
}
@@ -129,7 +129,7 @@ abstract class AvroSchemaScanConfigurationITestBase {
@Test
fun `should find schemas in packages by class mixing kotlinx with specific record base`() {
assertThat(avroSchemas).isNotNull
- assertThat(avroSchemas).hasSize(3) // one generated two from DummyEvents.kt
+ assertThat(avroSchemas).hasSize(3) // one generated two from DummyEventsTest.kt
}
}
diff --git a/serializer/spring-autoconfigure/src/test/kotlin/itest/upcaster/AxonAvroUpcasterITest.kt b/serializer/spring-autoconfigure/src/test/kotlin/itest/upcaster/AxonAvroUpcasterITest.kt
index 355bc83..7d403cb 100644
--- a/serializer/spring-autoconfigure/src/test/kotlin/itest/upcaster/AxonAvroUpcasterITest.kt
+++ b/serializer/spring-autoconfigure/src/test/kotlin/itest/upcaster/AxonAvroUpcasterITest.kt
@@ -4,6 +4,8 @@ package io.holixon.axon.avro.serializer.spring.itest.upcaster
import io.holixon.axon.avro.serializer.spring.AxonAvroSerializerConfiguration
import io.holixon.axon.avro.serializer.spring.AxonAvroSerializerSpringBase.PROFILE_ITEST
+import io.holixon.axon.avro.serializer.spring.TestFixtures
+import io.holixon.axon.avro.serializer.spring.TestFixtures.DummyEvents
import io.holixon.axon.avro.serializer.spring.container.AxonServerContainerOld
import io.toolisticon.kotlin.avro.AvroKotlin
import io.toolisticon.kotlin.avro.model.wrapper.AvroSchema
@@ -73,11 +75,11 @@ internal class AxonAvroUpcasterITest {
@Bean
fun dummyEventUpcaster() = object : SingleEventUpcaster() {
override fun canUpcast(intermediateRepresentation: IntermediateEventRepresentation): Boolean {
- TODO() // TODO: solve revision resolution - return intermediateRepresentation.type.name == DummyEvents.SCHEMA_EVENT_01.fullName && intermediateRepresentation.type.revision == DummyEvents.SCHEMA_EVENT_01.avroSchemaRevision
+ TODO() // TODO: solve revision resolution - return intermediateRepresentation.type.name == DummyEvents.SCHEMA_EVENT_01.fullName && intermediateRepresentation.type.revision == DummyEvents.SCHEMA_EVENT_01.avroSchemaRevision
}
override fun doUpcast(intermediateRepresentation: IntermediateEventRepresentation): IntermediateEventRepresentation {
- TODO() // TODO: solve revision resolution -
+ TODO() // TODO: solve revision resolution -
// return intermediateRepresentation.upcast(
// // SimpleSerializedType(DummyEvents.SCHEMA_EVENT_10.fullName, DummyEvents.SCHEMA_EVENT_10.avroSchemaRevision),
// GenericData.Record::class.java,
@@ -103,7 +105,7 @@ internal class AxonAvroUpcasterITest {
@Test
internal fun `upcast from 01 to 10 by adding value10`() {
- val event01 = AvroKotlin.createGenericRecord(AvroSchema( DummyEvents.SCHEMA_EVENT_01)) {
+ val event01 = AvroKotlin.createGenericRecord(DummyEvents.SCHEMA_EVENT_01) {
put("value01", "foo")
}
diff --git a/serializer/spring-autoconfigure/src/test/kotlin/itest/upcaster/DummyEvents.kt b/serializer/spring-autoconfigure/src/test/kotlin/itest/upcaster/DummyEvents.kt
deleted file mode 100644
index c271c23..0000000
--- a/serializer/spring-autoconfigure/src/test/kotlin/itest/upcaster/DummyEvents.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-package io.holixon.axon.avro.serializer.spring.itest.upcaster
-
-import io.toolisticon.kotlin.avro.model.wrapper.AvroSchema
-import io.toolisticon.kotlin.avro.repository.avroSchemaResolver
-import org.apache.avro.Schema
-import upcaster.itest.DummyEvent
-
-object DummyEvents {
-
- val SCHEMA_EVENT_01: Schema = Schema.Parser().parse(
- """
- {
- "type": "record",
- "namespace": "upcaster.itest",
- "name": "DummyEvent",
- "revision": "1",
- "fields": [
- {
- "name": "value01",
- "type": {
- "type": "string",
- "avro.java.string": "String"
- }
- }
- ]
- }
- """.trimIndent()
- )
-
- val SCHEMA_EVENT_10: Schema = DummyEvent.getClassSchema()
-
- val registry = avroSchemaResolver(listOf(SCHEMA_EVENT_01, SCHEMA_EVENT_10).map { AvroSchema(it) })
-}
-
-// FIXME reimplement compatibility checks
-//internal class DummyEventsTest {
-//
-// private val encoder = DefaultGenericDataRecordToSingleObjectEncoder()
-// private val decoder = DefaultSingleObjectToSpecificRecordDecoder(DummyEvents.registry.schemaResolver())
-//
-// @Test
-// internal fun `schema 01 and 10 are incompatibly`() {
-// val record = GenericData.Record(DummyEvents.SCHEMA_EVENT_01).apply {
-// put("value01", "foo")
-// }
-//
-// assertThatThrownBy { decoder.decode(encoder.encode(record)) }
-// .isInstanceOf(IllegalArgumentException::class.java)
-// .hasMessageContaining("[READER_FIELD_MISSING_DEFAULT_VALUE]")
-//
-// }
-//}
diff --git a/serializer/spring-autoconfigure/src/test/kotlin/itest/upcaster/DummyEventsTest.kt b/serializer/spring-autoconfigure/src/test/kotlin/itest/upcaster/DummyEventsTest.kt
new file mode 100644
index 0000000..abcef6a
--- /dev/null
+++ b/serializer/spring-autoconfigure/src/test/kotlin/itest/upcaster/DummyEventsTest.kt
@@ -0,0 +1,23 @@
+package io.holixon.axon.avro.serializer.spring.itest.upcaster
+
+import io.holixon.axon.avro.serializer.spring.TestFixtures.DummyEvents
+import io.toolisticon.kotlin.avro.value.AvroSchemaCompatibilityMap
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+
+
+internal class DummyEventsTest {
+
+//
+// @Test
+// internal fun `schema 01 and 10 are incompatibly`() {
+// val record = GenericData.Record(DummyEvents.SCHEMA_EVENT_01).apply {
+// put("value01", "foo")
+// }
+//
+// assertThatThrownBy { decoder.decode(encoder.encode(record)) }
+// .isInstanceOf(IllegalArgumentException::class.java)
+// .hasMessageContaining("[READER_FIELD_MISSING_DEFAULT_VALUE]")
+//
+// }
+}