From 1d131eced54c79443dcbff93dded0f0441b57cf1 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Wed, 16 Aug 2017 22:15:07 +0100 Subject: [PATCH] Add Evolvability to Serializer If we attempt to deserialize a class and find that since it's serialization the definition has changed we need to create a serializer capable of evolving the serialised data and constructing an instance of the new type We currently cope with * Removing members * Adding nullable members * Adding non nullable members if a constructor is provided that allows us to set the old arguments and defaults the new (mandatory) fields * Reordering paramters --- .../serialization/amqp/ObjectSerializer.kt | 1 + .../serialization/amqp/SerializationHelper.kt | 1 + .../serialization/amqp/SerializerFactory.kt | 6 +++ .../serialization/amqp/EvolvabilityTests.kt | 37 ++++++++++++++++++ .../amqp/EvolvabilityTests.test1 | Bin 0 -> 248 bytes 5 files changed, 45 insertions(+) create mode 100644 node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt create mode 100644 node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.test1 diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt index 555246474f..dbd184fe8a 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/ObjectSerializer.kt @@ -17,6 +17,7 @@ class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPSerial internal val propertySerializers: Collection init { + println ("Object Serializer") val kotlinConstructor = constructorForDeserialization(clazz) javaConstructor = kotlinConstructor?.javaConstructor javaConstructor?.isAccessible = true diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt index b719c956f4..a36a128b5d 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt @@ -68,6 +68,7 @@ internal fun propertiesForSerialization(kotlinConstructor: KFunction): Boolean = !(clazz.isInterface || Modifier.isAbstract(clazz.modifiers)) private fun propertiesForSerializationFromConstructor(kotlinConstructor: KFunction, type: Type, factory: SerializerFactory): Collection { + println ("propertiesForSerializationFromConstructor - $type") val clazz = (kotlinConstructor.returnType.classifier as KClass<*>).javaObjectType // Kotlin reflection doesn't work with Java getters the way you might expect, so we drop back to good ol' beans. val properties = Introspector.getBeanInfo(clazz).propertyDescriptors.filter { it.name != "class" }.groupBy { it.name }.mapValues { it.value[0] } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt index ad8d0c066e..9f3bd806a7 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt @@ -56,11 +56,14 @@ class SerializerFactory(val whitelist: ClassWhitelist, cl : ClassLoader) { */ @Throws(NotSerializableException::class) fun get(actualClass: Class<*>?, declaredType: Type): AMQPSerializer { + println ("get - $declaredType") val declaredClass = declaredType.asClass() ?: throw NotSerializableException( "Declared types of $declaredType are not supported.") val actualType: Type = inferTypeVariables(actualClass, declaredClass, declaredType) ?: declaredType + println ("actual type - $actualType") + val serializer = if (Collection::class.java.isAssignableFrom(declaredClass)) { serializersByType.computeIfAbsent(declaredType) { CollectionSerializer(declaredType as? ParameterizedType ?: DeserializedParameterizedType( @@ -182,9 +185,11 @@ class SerializerFactory(val whitelist: ClassWhitelist, cl : ClassLoader) { private fun processSchema(schema: Schema, sentinel: Boolean = false) { val carpenterSchemas = CarpenterSchemas.newInstance() for (typeNotation in schema.types) { + println("processSchema: ${typeNotation.descriptor} ${typeNotation.name}") try { processSchemaEntry(typeNotation) } catch (e: ClassNotFoundException) { + println("poop") if (sentinel || (typeNotation !is CompositeType)) throw e typeNotation.carpenterSchema(classloader, carpenterSchemas = carpenterSchemas) } @@ -212,6 +217,7 @@ class SerializerFactory(val whitelist: ClassWhitelist, cl : ClassLoader) { private fun processCompositeType(typeNotation: CompositeType) { // TODO: class loader logic, and compare the schema. + println("processCompositeType") val type = typeForName(typeNotation.name, classloader) get(type.asClass() ?: throw NotSerializableException("Unable to build composite type for $type"), type) } diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt new file mode 100644 index 0000000000..c04ee8ac89 --- /dev/null +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.kt @@ -0,0 +1,37 @@ +package net.corda.nodeapi.internal.serialization.amqp + +import net.corda.core.serialization.SerializedBytes +import net.corda.core.utilities.toHexString +import org.junit.Test +import java.io.File + + +class EvolvabilityTests { + + @Test + fun test1() { + val sf = SerializerFactory() + + // Basis for the serialised version + // data class C (val a: Int) + // var sc = SerializationOutput(sf).serialize(C(1)) + + data class C (val a: Int, val b: Int) + + val path = EvolvabilityTests::class.java.getResource("EvolvabilityTests.test1") + println ("PATH = $path") + val f = File(path.toURI()) + + println (sf) +// var sc = SerializationOutput(sf).serialize(C(1)) +// f.writeBytes(sc.bytes) + val sc2 = f.readBytes() + + var deserializedC = DeserializationInput().deserialize(SerializedBytes(sc2)) + + println (deserializedC.a) + + + + } +} \ No newline at end of file diff --git a/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.test1 b/node-api/src/test/resources/net/corda/nodeapi/internal/serialization/amqp/EvolvabilityTests.test1 new file mode 100644 index 0000000000000000000000000000000000000000..7edad410a9352154449088ea7679e7e18f64eef8 GIT binary patch literal 248 zcmYe!FG@*dWME)u0Ahv%w-^`?JYiy3sFaslqL&Pkvnus=ck@m3aS!u%O|6KG43F?g z&5U%kwLQSh7{UnG#&lpU!5Iox=fH Zgkx9^2(T|?OkBtewB2