mirror of
https://github.com/corda/corda.git
synced 2024-12-23 06:42:33 +00:00
CORDA-3745: Modify DJVM serializers to support Enum Evolution. (#6189)
This commit is contained in:
parent
2ce76e407d
commit
107819f5b5
node/src/main/kotlin/net/corda/node/services/transactions
serialization-djvm/src
main/kotlin/net/corda/serialization/djvm
test/kotlin/net/corda/serialization/djvm
serialization/src/main/kotlin/net/corda/serialization/internal
@ -4,6 +4,10 @@ import net.corda.core.internal.BasicVerifier
|
|||||||
import net.corda.core.internal.Verifier
|
import net.corda.core.internal.Verifier
|
||||||
import net.corda.core.serialization.ConstructorForDeserialization
|
import net.corda.core.serialization.ConstructorForDeserialization
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
import net.corda.core.serialization.CordaSerializationTransformEnumDefault
|
||||||
|
import net.corda.core.serialization.CordaSerializationTransformEnumDefaults
|
||||||
|
import net.corda.core.serialization.CordaSerializationTransformRename
|
||||||
|
import net.corda.core.serialization.CordaSerializationTransformRenames
|
||||||
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
|
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.core.transactions.LedgerTransaction
|
import net.corda.core.transactions.LedgerTransaction
|
||||||
@ -38,6 +42,10 @@ class DeterministicVerifierFactoryService(
|
|||||||
whitelist = Whitelist.MINIMAL,
|
whitelist = Whitelist.MINIMAL,
|
||||||
visibleAnnotations = setOf(
|
visibleAnnotations = setOf(
|
||||||
CordaSerializable::class.java,
|
CordaSerializable::class.java,
|
||||||
|
CordaSerializationTransformEnumDefault::class.java,
|
||||||
|
CordaSerializationTransformEnumDefaults::class.java,
|
||||||
|
CordaSerializationTransformRename::class.java,
|
||||||
|
CordaSerializationTransformRenames::class.java,
|
||||||
ConstructorForDeserialization::class.java,
|
ConstructorForDeserialization::class.java,
|
||||||
DeprecatedConstructorForDeserialization::class.java
|
DeprecatedConstructorForDeserialization::class.java
|
||||||
),
|
),
|
||||||
|
@ -98,7 +98,8 @@ class SandboxSerializerFactoryFactory(
|
|||||||
localSerializerFactory = localSerializerFactory,
|
localSerializerFactory = localSerializerFactory,
|
||||||
classLoader = classLoader,
|
classLoader = classLoader,
|
||||||
mustPreserveDataWhenEvolving = context.preventDataLoss,
|
mustPreserveDataWhenEvolving = context.preventDataLoss,
|
||||||
primitiveTypes = primitiveTypes
|
primitiveTypes = primitiveTypes,
|
||||||
|
baseTypes = localTypes
|
||||||
)
|
)
|
||||||
|
|
||||||
val remoteSerializerFactory = DefaultRemoteSerializerFactory(
|
val remoteSerializerFactory = DefaultRemoteSerializerFactory(
|
||||||
|
@ -61,8 +61,9 @@ fun createSandboxSerializationEnv(
|
|||||||
@Suppress("unchecked_cast")
|
@Suppress("unchecked_cast")
|
||||||
val isEnumPredicate = predicateFactory.apply(CheckEnum::class.java) as Predicate<Class<*>>
|
val isEnumPredicate = predicateFactory.apply(CheckEnum::class.java) as Predicate<Class<*>>
|
||||||
@Suppress("unchecked_cast")
|
@Suppress("unchecked_cast")
|
||||||
val enumConstants = taskFactory.apply(DescribeEnum::class.java)
|
val enumConstants = taskFactory.apply(DescribeEnum::class.java) as Function<Class<*>, Array<out Any>>
|
||||||
.andThen(taskFactory.apply(GetEnumNames::class.java))
|
@Suppress("unchecked_cast")
|
||||||
|
val enumConstantNames = enumConstants.andThen(taskFactory.apply(GetEnumNames::class.java))
|
||||||
.andThen { (it as Array<out Any>).map(Any::toString) } as Function<Class<*>, List<String>>
|
.andThen { (it as Array<out Any>).map(Any::toString) } as Function<Class<*>, List<String>>
|
||||||
|
|
||||||
val sandboxLocalTypes = BaseLocalTypes(
|
val sandboxLocalTypes = BaseLocalTypes(
|
||||||
@ -72,7 +73,8 @@ fun createSandboxSerializationEnv(
|
|||||||
mapClass = classLoader.toSandboxClass(Map::class.java),
|
mapClass = classLoader.toSandboxClass(Map::class.java),
|
||||||
stringClass = classLoader.toSandboxClass(String::class.java),
|
stringClass = classLoader.toSandboxClass(String::class.java),
|
||||||
isEnum = isEnumPredicate,
|
isEnum = isEnumPredicate,
|
||||||
enumConstants = enumConstants
|
enumConstants = enumConstants,
|
||||||
|
enumConstantNames = enumConstantNames
|
||||||
)
|
)
|
||||||
val schemeBuilder = SandboxSerializationSchemeBuilder(
|
val schemeBuilder = SandboxSerializationSchemeBuilder(
|
||||||
classLoader = classLoader,
|
classLoader = classLoader,
|
||||||
|
@ -0,0 +1,155 @@
|
|||||||
|
package net.corda.serialization.djvm
|
||||||
|
|
||||||
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
import net.corda.core.serialization.CordaSerializationTransformEnumDefault
|
||||||
|
import net.corda.core.serialization.CordaSerializationTransformEnumDefaults
|
||||||
|
import net.corda.core.serialization.CordaSerializationTransformRename
|
||||||
|
import net.corda.core.serialization.CordaSerializationTransformRenames
|
||||||
|
import net.corda.core.serialization.SerializationContext
|
||||||
|
import net.corda.core.serialization.SerializedBytes
|
||||||
|
import net.corda.core.serialization.internal._contextSerializationEnv
|
||||||
|
import net.corda.core.serialization.serialize
|
||||||
|
import net.corda.serialization.djvm.EvolvedEnum.ONE
|
||||||
|
import net.corda.serialization.djvm.EvolvedEnum.TWO
|
||||||
|
import net.corda.serialization.djvm.EvolvedEnum.THREE
|
||||||
|
import net.corda.serialization.djvm.EvolvedEnum.FOUR
|
||||||
|
import net.corda.serialization.djvm.OriginalEnum.One
|
||||||
|
import net.corda.serialization.djvm.OriginalEnum.Two
|
||||||
|
import net.corda.serialization.djvm.SandboxType.KOTLIN
|
||||||
|
import net.corda.serialization.internal.amqp.CompositeType
|
||||||
|
import net.corda.serialization.internal.amqp.DeserializationInput
|
||||||
|
import net.corda.serialization.internal.amqp.RestrictedType
|
||||||
|
import net.corda.serialization.internal.amqp.Transform
|
||||||
|
import net.corda.serialization.internal.amqp.TransformTypes
|
||||||
|
import net.corda.serialization.internal.amqp.TypeNotation
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext
|
||||||
|
import org.junit.jupiter.api.fail
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest
|
||||||
|
import org.junit.jupiter.params.provider.Arguments
|
||||||
|
import org.junit.jupiter.params.provider.ArgumentsProvider
|
||||||
|
import org.junit.jupiter.params.provider.ArgumentsSource
|
||||||
|
import java.util.EnumMap
|
||||||
|
import java.util.function.Function
|
||||||
|
import java.util.stream.Stream
|
||||||
|
|
||||||
|
@ExtendWith(LocalSerialization::class)
|
||||||
|
class DeserializeEnumWithEvolutionTest : TestBase(KOTLIN) {
|
||||||
|
class EvolutionArgumentProvider : ArgumentsProvider {
|
||||||
|
override fun provideArguments(context: ExtensionContext?): Stream<out Arguments> {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(ONE, One),
|
||||||
|
Arguments.of(TWO, Two),
|
||||||
|
Arguments.of(THREE, One),
|
||||||
|
Arguments.of(FOUR, Two)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.devolve() = replace("Evolved", "Original")
|
||||||
|
|
||||||
|
private fun devolveType(type: TypeNotation): TypeNotation {
|
||||||
|
return when (type) {
|
||||||
|
is CompositeType -> type.copy(
|
||||||
|
name = type.name.devolve(),
|
||||||
|
fields = type.fields.map { it.copy(type = it.type.devolve()) }
|
||||||
|
)
|
||||||
|
is RestrictedType -> type.copy(name = type.name.devolve())
|
||||||
|
else -> type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun SerializedBytes<*>.devolve(context: SerializationContext): SerializedBytes<Any> {
|
||||||
|
val envelope = DeserializationInput.getEnvelope(this, context.encodingWhitelist).apply {
|
||||||
|
val schemaTypes = schema.types.map(::devolveType)
|
||||||
|
with(schema.types as MutableList<TypeNotation>) {
|
||||||
|
clear()
|
||||||
|
addAll(schemaTypes)
|
||||||
|
}
|
||||||
|
|
||||||
|
val transforms = transformsSchema.types.asSequence().associateTo(LinkedHashMap()) {
|
||||||
|
it.key.devolve() to it.value
|
||||||
|
}
|
||||||
|
with(transformsSchema.types as MutableMap<String, EnumMap<TransformTypes, MutableList<Transform>>>) {
|
||||||
|
clear()
|
||||||
|
putAll(transforms)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SerializedBytes(envelope.write())
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ArgumentsSource(EvolutionArgumentProvider::class)
|
||||||
|
fun `test deserialising evolved enum`(value: EvolvedEnum, expected: OriginalEnum) {
|
||||||
|
val context = (_contextSerializationEnv.get() ?: fail("No serialization environment!")).p2pContext
|
||||||
|
|
||||||
|
val evolvedData = value.serialize()
|
||||||
|
val originalData = evolvedData.devolve(context)
|
||||||
|
|
||||||
|
sandbox {
|
||||||
|
_contextSerializationEnv.set(createSandboxSerializationEnv(classLoader))
|
||||||
|
val sandboxOriginal = originalData.deserializeFor(classLoader)
|
||||||
|
assertEquals("sandbox." + OriginalEnum::class.java.name, sandboxOriginal::class.java.name)
|
||||||
|
assertEquals(expected.toString(), sandboxOriginal.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ArgumentsSource(EvolutionArgumentProvider::class)
|
||||||
|
fun `test deserialising data with evolved enum`(value: EvolvedEnum, expected: OriginalEnum) {
|
||||||
|
val context = (_contextSerializationEnv.get() ?: fail("No serialization environment!")).p2pContext
|
||||||
|
|
||||||
|
val evolvedData = EvolvedData(value).serialize()
|
||||||
|
val originalData = evolvedData.devolve(context)
|
||||||
|
|
||||||
|
sandbox {
|
||||||
|
_contextSerializationEnv.set(createSandboxSerializationEnv(classLoader))
|
||||||
|
val sandboxOriginal = originalData.deserializeFor(classLoader)
|
||||||
|
|
||||||
|
val taskFactory = classLoader.createRawTaskFactory()
|
||||||
|
val result = taskFactory.compose(classLoader.createSandboxFunction())
|
||||||
|
.apply(ShowOriginalData::class.java)
|
||||||
|
.apply(sandboxOriginal) ?: fail("Result cannot be null")
|
||||||
|
assertThat(result.toString())
|
||||||
|
.isEqualTo(ShowOriginalData().apply(OriginalData(expected)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShowOriginalData : Function<OriginalData, String> {
|
||||||
|
override fun apply(input: OriginalData): String {
|
||||||
|
return with(input) {
|
||||||
|
"Name='${value.name}', Ordinal='${value.ordinal}'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CordaSerializable
|
||||||
|
enum class OriginalEnum {
|
||||||
|
One,
|
||||||
|
Two
|
||||||
|
}
|
||||||
|
|
||||||
|
@CordaSerializable
|
||||||
|
data class OriginalData(val value: OriginalEnum)
|
||||||
|
|
||||||
|
@CordaSerializable
|
||||||
|
@CordaSerializationTransformRenames(
|
||||||
|
CordaSerializationTransformRename(from = "One", to = "ONE"),
|
||||||
|
CordaSerializationTransformRename(from = "Two", to = "TWO")
|
||||||
|
)
|
||||||
|
@CordaSerializationTransformEnumDefaults(
|
||||||
|
CordaSerializationTransformEnumDefault(new = "THREE", old = "One"),
|
||||||
|
CordaSerializationTransformEnumDefault(new = "FOUR", old = "Two")
|
||||||
|
)
|
||||||
|
enum class EvolvedEnum {
|
||||||
|
ONE,
|
||||||
|
TWO,
|
||||||
|
THREE,
|
||||||
|
FOUR
|
||||||
|
}
|
||||||
|
|
||||||
|
@CordaSerializable
|
||||||
|
data class EvolvedData(val value: EvolvedEnum)
|
@ -4,22 +4,14 @@ import net.corda.core.serialization.SerializedBytes
|
|||||||
import net.corda.core.serialization.internal._contextSerializationEnv
|
import net.corda.core.serialization.internal._contextSerializationEnv
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.serialization.djvm.SandboxType.KOTLIN
|
import net.corda.serialization.djvm.SandboxType.KOTLIN
|
||||||
import net.corda.serialization.internal.SectionId
|
|
||||||
import net.corda.serialization.internal.amqp.CompositeType
|
import net.corda.serialization.internal.amqp.CompositeType
|
||||||
import net.corda.serialization.internal.amqp.DeserializationInput
|
import net.corda.serialization.internal.amqp.DeserializationInput
|
||||||
import net.corda.serialization.internal.amqp.Envelope
|
|
||||||
import net.corda.serialization.internal.amqp.TypeNotation
|
import net.corda.serialization.internal.amqp.TypeNotation
|
||||||
import net.corda.serialization.internal.amqp.alsoAsByteBuffer
|
|
||||||
import net.corda.serialization.internal.amqp.amqpMagic
|
|
||||||
import net.corda.serialization.internal.amqp.withDescribed
|
|
||||||
import net.corda.serialization.internal.amqp.withList
|
|
||||||
import org.apache.qpid.proton.codec.Data
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.assertThrows
|
import org.junit.jupiter.api.assertThrows
|
||||||
import org.junit.jupiter.api.extension.ExtendWith
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
import org.junit.jupiter.api.fail
|
import org.junit.jupiter.api.fail
|
||||||
import java.io.ByteArrayOutputStream
|
|
||||||
import java.util.function.Function
|
import java.util.function.Function
|
||||||
|
|
||||||
@ExtendWith(LocalSerialization::class)
|
@ExtendWith(LocalSerialization::class)
|
||||||
@ -37,12 +29,8 @@ class SafeDeserialisationTest : TestBase(KOTLIN) {
|
|||||||
val innocentData = innocent.serialize()
|
val innocentData = innocent.serialize()
|
||||||
val envelope = DeserializationInput.getEnvelope(innocentData, context.encodingWhitelist).apply {
|
val envelope = DeserializationInput.getEnvelope(innocentData, context.encodingWhitelist).apply {
|
||||||
val innocentType = schema.types[0] as CompositeType
|
val innocentType = schema.types[0] as CompositeType
|
||||||
(schema.types as MutableList<TypeNotation>)[0] = CompositeType(
|
(schema.types as MutableList<TypeNotation>)[0] = innocentType.copy(
|
||||||
name = innocentType.name.replace("Innocent", "VeryEvil"),
|
name = innocentType.name.replace("Innocent", "VeryEvil")
|
||||||
label = innocentType.label,
|
|
||||||
provides = innocentType.provides,
|
|
||||||
descriptor = innocentType.descriptor,
|
|
||||||
fields = innocentType.fields
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val evilData = SerializedBytes<Any>(envelope.write())
|
val evilData = SerializedBytes<Any>(envelope.write())
|
||||||
@ -68,23 +56,6 @@ class SafeDeserialisationTest : TestBase(KOTLIN) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Envelope.write(): ByteArray {
|
|
||||||
val data = Data.Factory.create()
|
|
||||||
data.withDescribed(Envelope.DESCRIPTOR_OBJECT) {
|
|
||||||
withList {
|
|
||||||
putObject(obj)
|
|
||||||
putObject(schema)
|
|
||||||
putObject(transformsSchema)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ByteArrayOutputStream().use {
|
|
||||||
amqpMagic.writeTo(it)
|
|
||||||
SectionId.DATA_AND_STOP.writeTo(it)
|
|
||||||
it.alsoAsByteBuffer(data.encodedSize().toInt(), data::encode)
|
|
||||||
it.toByteArray()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ShowInnocentData : Function<InnocentData, String> {
|
class ShowInnocentData : Function<InnocentData, String> {
|
||||||
override fun apply(data: InnocentData): String {
|
override fun apply(data: InnocentData): String {
|
||||||
return "${data::class.java.name}: ${data.message}, ${data.number}"
|
return "${data::class.java.name}: ${data.message}, ${data.number}"
|
||||||
|
@ -2,6 +2,10 @@ package net.corda.serialization.djvm
|
|||||||
|
|
||||||
import net.corda.core.serialization.ConstructorForDeserialization
|
import net.corda.core.serialization.ConstructorForDeserialization
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
|
import net.corda.core.serialization.CordaSerializationTransformEnumDefault
|
||||||
|
import net.corda.core.serialization.CordaSerializationTransformEnumDefaults
|
||||||
|
import net.corda.core.serialization.CordaSerializationTransformRename
|
||||||
|
import net.corda.core.serialization.CordaSerializationTransformRenames
|
||||||
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
|
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
|
||||||
import net.corda.djvm.SandboxConfiguration
|
import net.corda.djvm.SandboxConfiguration
|
||||||
import net.corda.djvm.SandboxRuntimeContext
|
import net.corda.djvm.SandboxRuntimeContext
|
||||||
@ -51,6 +55,10 @@ abstract class TestBase(type: SandboxType) {
|
|||||||
whitelist = MINIMAL,
|
whitelist = MINIMAL,
|
||||||
visibleAnnotations = setOf(
|
visibleAnnotations = setOf(
|
||||||
CordaSerializable::class.java,
|
CordaSerializable::class.java,
|
||||||
|
CordaSerializationTransformEnumDefault::class.java,
|
||||||
|
CordaSerializationTransformEnumDefaults::class.java,
|
||||||
|
CordaSerializationTransformRename::class.java,
|
||||||
|
CordaSerializationTransformRenames::class.java,
|
||||||
ConstructorForDeserialization::class.java,
|
ConstructorForDeserialization::class.java,
|
||||||
DeprecatedConstructorForDeserialization::class.java
|
DeprecatedConstructorForDeserialization::class.java
|
||||||
),
|
),
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
@file:JvmName("TestHelpers")
|
||||||
|
package net.corda.serialization.djvm
|
||||||
|
|
||||||
|
import net.corda.serialization.internal.SectionId
|
||||||
|
import net.corda.serialization.internal.amqp.Envelope
|
||||||
|
import net.corda.serialization.internal.amqp.alsoAsByteBuffer
|
||||||
|
import net.corda.serialization.internal.amqp.amqpMagic
|
||||||
|
import net.corda.serialization.internal.amqp.withDescribed
|
||||||
|
import net.corda.serialization.internal.amqp.withList
|
||||||
|
import org.apache.qpid.proton.codec.Data
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
|
fun Envelope.write(): ByteArray {
|
||||||
|
val data = Data.Factory.create()
|
||||||
|
data.withDescribed(Envelope.DESCRIPTOR_OBJECT) {
|
||||||
|
withList {
|
||||||
|
putObject(obj)
|
||||||
|
putObject(schema)
|
||||||
|
putObject(transformsSchema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ByteArrayOutputStream().use {
|
||||||
|
amqpMagic.writeTo(it)
|
||||||
|
SectionId.DATA_AND_STOP.writeTo(it)
|
||||||
|
it.alsoAsByteBuffer(data.encodedSize().toInt(), data::encode)
|
||||||
|
it.toByteArray()
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package net.corda.serialization.internal.amqp
|
package net.corda.serialization.internal.amqp
|
||||||
|
|
||||||
import net.corda.core.serialization.SerializationContext
|
import net.corda.core.serialization.SerializationContext
|
||||||
|
import net.corda.serialization.internal.model.BaseLocalTypes
|
||||||
import org.apache.qpid.proton.codec.Data
|
import org.apache.qpid.proton.codec.Data
|
||||||
import java.lang.UnsupportedOperationException
|
import java.lang.UnsupportedOperationException
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
@ -34,6 +35,7 @@ import java.lang.reflect.Type
|
|||||||
class EnumEvolutionSerializer(
|
class EnumEvolutionSerializer(
|
||||||
override val type: Type,
|
override val type: Type,
|
||||||
factory: LocalSerializerFactory,
|
factory: LocalSerializerFactory,
|
||||||
|
private val baseLocalTypes: BaseLocalTypes,
|
||||||
private val conversions: Map<String, String>,
|
private val conversions: Map<String, String>,
|
||||||
private val ordinals: Map<String, Int>) : AMQPSerializer<Any> {
|
private val ordinals: Map<String, Int>) : AMQPSerializer<Any> {
|
||||||
override val typeDescriptor = factory.createDescriptor(type)
|
override val typeDescriptor = factory.createDescriptor(type)
|
||||||
@ -46,7 +48,7 @@ class EnumEvolutionSerializer(
|
|||||||
val converted = conversions[enumName] ?: throw AMQPNotSerializableException(type, "No rule to evolve enum constant $type::$enumName")
|
val converted = conversions[enumName] ?: throw AMQPNotSerializableException(type, "No rule to evolve enum constant $type::$enumName")
|
||||||
val ordinal = ordinals[converted] ?: throw AMQPNotSerializableException(type, "Ordinal not found for enum value $type::$converted")
|
val ordinal = ordinals[converted] ?: throw AMQPNotSerializableException(type, "Ordinal not found for enum value $type::$converted")
|
||||||
|
|
||||||
return type.asClass().enumConstants[ordinal]
|
return baseLocalTypes.enumConstants.apply(type.asClass())[ordinal]
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeClassInfo(output: SerializationOutput) {
|
override fun writeClassInfo(output: SerializationOutput) {
|
||||||
|
@ -40,7 +40,8 @@ class DefaultEvolutionSerializerFactory(
|
|||||||
private val localSerializerFactory: LocalSerializerFactory,
|
private val localSerializerFactory: LocalSerializerFactory,
|
||||||
private val classLoader: ClassLoader,
|
private val classLoader: ClassLoader,
|
||||||
private val mustPreserveDataWhenEvolving: Boolean,
|
private val mustPreserveDataWhenEvolving: Boolean,
|
||||||
override val primitiveTypes: Map<Class<*>, Class<*>>
|
override val primitiveTypes: Map<Class<*>, Class<*>>,
|
||||||
|
private val baseTypes: BaseLocalTypes
|
||||||
): EvolutionSerializerFactory {
|
): EvolutionSerializerFactory {
|
||||||
// Invert the "primitive -> boxed primitive" mapping.
|
// Invert the "primitive -> boxed primitive" mapping.
|
||||||
private val primitiveBoxedTypes: Map<Class<*>, Class<*>>
|
private val primitiveBoxedTypes: Map<Class<*>, Class<*>>
|
||||||
@ -172,7 +173,7 @@ class DefaultEvolutionSerializerFactory(
|
|||||||
if (constantsAreReordered(localOrdinals, convertedOrdinals)) throw EvolutionSerializationException(this,
|
if (constantsAreReordered(localOrdinals, convertedOrdinals)) throw EvolutionSerializationException(this,
|
||||||
"Constants have been reordered, additions must be appended to the end")
|
"Constants have been reordered, additions must be appended to the end")
|
||||||
|
|
||||||
return EnumEvolutionSerializer(localTypeInformation.observedType, localSerializerFactory, conversions, localOrdinals)
|
return EnumEvolutionSerializer(localTypeInformation.observedType, localSerializerFactory, baseTypes, conversions, localOrdinals)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun constantsAreReordered(localOrdinals: Map<String, Int>, convertedOrdinals: Map<Int, String>): Boolean =
|
private fun constantsAreReordered(localOrdinals: Map<String, Int>, convertedOrdinals: Map<Int, String>): Boolean =
|
||||||
|
@ -97,10 +97,8 @@ object SerializerFactoryBuilder {
|
|||||||
mustPreserveDataWhenEvolving: Boolean): SerializerFactory {
|
mustPreserveDataWhenEvolving: Boolean): SerializerFactory {
|
||||||
val customSerializerRegistry = CachingCustomSerializerRegistry(descriptorBasedSerializerRegistry)
|
val customSerializerRegistry = CachingCustomSerializerRegistry(descriptorBasedSerializerRegistry)
|
||||||
|
|
||||||
val localTypeModel = ConfigurableLocalTypeModel(
|
val typeModelConfiguration = WhitelistBasedTypeModelConfiguration(whitelist, customSerializerRegistry)
|
||||||
WhitelistBasedTypeModelConfiguration(
|
val localTypeModel = ConfigurableLocalTypeModel(typeModelConfiguration)
|
||||||
whitelist,
|
|
||||||
customSerializerRegistry))
|
|
||||||
|
|
||||||
val fingerPrinter = overrideFingerPrinter ?:
|
val fingerPrinter = overrideFingerPrinter ?:
|
||||||
TypeModellingFingerPrinter(customSerializerRegistry)
|
TypeModellingFingerPrinter(customSerializerRegistry)
|
||||||
@ -124,7 +122,8 @@ object SerializerFactoryBuilder {
|
|||||||
localSerializerFactory,
|
localSerializerFactory,
|
||||||
classCarpenter.classloader,
|
classCarpenter.classloader,
|
||||||
mustPreserveDataWhenEvolving,
|
mustPreserveDataWhenEvolving,
|
||||||
javaPrimitiveTypes
|
javaPrimitiveTypes,
|
||||||
|
typeModelConfiguration.baseTypes
|
||||||
) else NoEvolutionSerializerFactory
|
) else NoEvolutionSerializerFactory
|
||||||
|
|
||||||
val remoteSerializerFactory = DefaultRemoteSerializerFactory(
|
val remoteSerializerFactory = DefaultRemoteSerializerFactory(
|
||||||
|
@ -46,7 +46,7 @@ abstract class Transform : DescribedType {
|
|||||||
* descendants of this class
|
* descendants of this class
|
||||||
*/
|
*/
|
||||||
override fun newInstance(obj: Any?): Transform {
|
override fun newInstance(obj: Any?): Transform {
|
||||||
val described = Transform.checkDescribed(obj) as List<*>
|
val described = checkDescribed(obj) as List<*>
|
||||||
return when (described[0]) {
|
return when (described[0]) {
|
||||||
EnumDefaultSchemaTransform.typeName -> EnumDefaultSchemaTransform.newInstance(described)
|
EnumDefaultSchemaTransform.typeName -> EnumDefaultSchemaTransform.newInstance(described)
|
||||||
RenameSchemaTransform.typeName -> RenameSchemaTransform.newInstance(described)
|
RenameSchemaTransform.typeName -> RenameSchemaTransform.newInstance(described)
|
||||||
@ -195,18 +195,24 @@ object TransformsAnnotationProcessor {
|
|||||||
* Obtain all of the transforms applied for the given [Class].
|
* Obtain all of the transforms applied for the given [Class].
|
||||||
*/
|
*/
|
||||||
fun getTransformsSchema(type: Class<*>): TransformsMap {
|
fun getTransformsSchema(type: Class<*>): TransformsMap {
|
||||||
val result = TransformsMap(TransformTypes::class.java)
|
return when {
|
||||||
// We only have transforms for enums at present.
|
// This only detects Enum classes that are outside the DJVM sandbox.
|
||||||
if (!type.isEnum) return result
|
type.isEnum -> getEnumTransformsSchema(type)
|
||||||
|
|
||||||
|
// We only have transforms for enums at present.
|
||||||
|
else -> TransformsMap(TransformTypes::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getEnumTransformsSchema(type: Class<*>): TransformsMap {
|
||||||
|
val result = TransformsMap(TransformTypes::class.java)
|
||||||
supportedTransforms.forEach { supportedTransform ->
|
supportedTransforms.forEach { supportedTransform ->
|
||||||
val annotationContainer = type.getAnnotation(supportedTransform.type) ?: return@forEach
|
val annotationContainer = type.getAnnotation(supportedTransform.type) ?: return@forEach
|
||||||
result.processAnnotations(
|
result.processAnnotations(
|
||||||
type,
|
type,
|
||||||
supportedTransform.enum,
|
supportedTransform.enum,
|
||||||
supportedTransform.getAnnotations(annotationContainer))
|
supportedTransform.getAnnotations(annotationContainer))
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +61,8 @@ private val DEFAULT_BASE_TYPES = BaseLocalTypes(
|
|||||||
mapClass = Map::class.java,
|
mapClass = Map::class.java,
|
||||||
stringClass = String::class.java,
|
stringClass = String::class.java,
|
||||||
isEnum = Predicate { clazz -> clazz.isEnum },
|
isEnum = Predicate { clazz -> clazz.isEnum },
|
||||||
enumConstants = Function { clazz ->
|
enumConstants = Function { clazz -> clazz.enumConstants },
|
||||||
|
enumConstantNames = Function { clazz ->
|
||||||
(clazz as Class<out Enum<*>>).enumConstants.map(Enum<*>::name)
|
(clazz as Class<out Enum<*>>).enumConstants.map(Enum<*>::name)
|
||||||
}
|
}
|
||||||
)
|
)
|
@ -115,13 +115,13 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
|
|||||||
baseTypes.mapClass.isAssignableFrom(type) -> AMap(type, typeIdentifier, Unknown, Unknown)
|
baseTypes.mapClass.isAssignableFrom(type) -> AMap(type, typeIdentifier, Unknown, Unknown)
|
||||||
type === baseTypes.stringClass -> Atomic(type, typeIdentifier)
|
type === baseTypes.stringClass -> Atomic(type, typeIdentifier)
|
||||||
type.kotlin.javaPrimitiveType != null -> Atomic(type, typeIdentifier)
|
type.kotlin.javaPrimitiveType != null -> Atomic(type, typeIdentifier)
|
||||||
baseTypes.isEnum.test(type) -> baseTypes.enumConstants.apply(type).let { enumConstants ->
|
baseTypes.isEnum.test(type) -> baseTypes.enumConstantNames.apply(type).let { enumConstantNames ->
|
||||||
AnEnum(
|
AnEnum(
|
||||||
type,
|
type,
|
||||||
typeIdentifier,
|
typeIdentifier,
|
||||||
enumConstants,
|
enumConstantNames,
|
||||||
buildInterfaceInformation(type),
|
buildInterfaceInformation(type),
|
||||||
getEnumTransforms(type, enumConstants)
|
getEnumTransforms(type, enumConstantNames)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
type.kotlinObjectInstance != null -> Singleton(
|
type.kotlinObjectInstance != null -> Singleton(
|
||||||
@ -145,7 +145,7 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
|
|||||||
private fun getEnumTransforms(type: Class<*>, enumConstants: List<String>): EnumTransforms {
|
private fun getEnumTransforms(type: Class<*>, enumConstants: List<String>): EnumTransforms {
|
||||||
try {
|
try {
|
||||||
val constants = enumConstants.asSequence().mapIndexed { index, constant -> constant to index }.toMap()
|
val constants = enumConstants.asSequence().mapIndexed { index, constant -> constant to index }.toMap()
|
||||||
return EnumTransforms.build(TransformsAnnotationProcessor.getTransformsSchema(type), constants)
|
return EnumTransforms.build(TransformsAnnotationProcessor.getEnumTransformsSchema(type), constants)
|
||||||
} catch (e: InvalidEnumTransformsException) {
|
} catch (e: InvalidEnumTransformsException) {
|
||||||
throw NotSerializableDetailedException(type.name, e.message!!)
|
throw NotSerializableDetailedException(type.name, e.message!!)
|
||||||
}
|
}
|
||||||
|
@ -136,5 +136,6 @@ class BaseLocalTypes(
|
|||||||
val mapClass: Class<*>,
|
val mapClass: Class<*>,
|
||||||
val stringClass: Class<*>,
|
val stringClass: Class<*>,
|
||||||
val isEnum: Predicate<Class<*>>,
|
val isEnum: Predicate<Class<*>>,
|
||||||
val enumConstants: Function<Class<*>, List<String>>
|
val enumConstants: Function<Class<*>, Array<out Any>>,
|
||||||
|
val enumConstantNames: Function<Class<*>, List<String>>
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user