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
This commit is contained in:
Katelyn Baker 2017-08-16 22:15:07 +01:00
parent db07d717e0
commit 1d131eced5
5 changed files with 45 additions and 0 deletions
node-api/src
main/kotlin/net/corda/nodeapi/internal/serialization/amqp
test
kotlin/net/corda/nodeapi/internal/serialization/amqp
resources/net/corda/nodeapi/internal/serialization/amqp

@ -17,6 +17,7 @@ class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPSerial
internal val propertySerializers: Collection<PropertySerializer>
init {
println ("Object Serializer")
val kotlinConstructor = constructorForDeserialization(clazz)
javaConstructor = kotlinConstructor?.javaConstructor
javaConstructor?.isAccessible = true

@ -68,6 +68,7 @@ internal fun <T : Any> propertiesForSerialization(kotlinConstructor: KFunction<T
private fun isConcrete(clazz: Class<*>): Boolean = !(clazz.isInterface || Modifier.isAbstract(clazz.modifiers))
private fun <T : Any> propertiesForSerializationFromConstructor(kotlinConstructor: KFunction<T>, type: Type, factory: SerializerFactory): Collection<PropertySerializer> {
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] }

@ -56,11 +56,14 @@ class SerializerFactory(val whitelist: ClassWhitelist, cl : ClassLoader) {
*/
@Throws(NotSerializableException::class)
fun get(actualClass: Class<*>?, declaredType: Type): AMQPSerializer<Any> {
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)
}

@ -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<C>(sc2))
println (deserializedC.a)
}
}