mirror of
https://github.com/corda/corda.git
synced 2025-06-17 06:38:21 +00:00
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:
@ -17,6 +17,7 @@ class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPSerial
|
|||||||
internal val propertySerializers: Collection<PropertySerializer>
|
internal val propertySerializers: Collection<PropertySerializer>
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
println ("Object Serializer")
|
||||||
val kotlinConstructor = constructorForDeserialization(clazz)
|
val kotlinConstructor = constructorForDeserialization(clazz)
|
||||||
javaConstructor = kotlinConstructor?.javaConstructor
|
javaConstructor = kotlinConstructor?.javaConstructor
|
||||||
javaConstructor?.isAccessible = true
|
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 isConcrete(clazz: Class<*>): Boolean = !(clazz.isInterface || Modifier.isAbstract(clazz.modifiers))
|
||||||
|
|
||||||
private fun <T : Any> propertiesForSerializationFromConstructor(kotlinConstructor: KFunction<T>, type: Type, factory: SerializerFactory): Collection<PropertySerializer> {
|
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
|
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.
|
// 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] }
|
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)
|
@Throws(NotSerializableException::class)
|
||||||
fun get(actualClass: Class<*>?, declaredType: Type): AMQPSerializer<Any> {
|
fun get(actualClass: Class<*>?, declaredType: Type): AMQPSerializer<Any> {
|
||||||
|
println ("get - $declaredType")
|
||||||
val declaredClass = declaredType.asClass() ?: throw NotSerializableException(
|
val declaredClass = declaredType.asClass() ?: throw NotSerializableException(
|
||||||
"Declared types of $declaredType are not supported.")
|
"Declared types of $declaredType are not supported.")
|
||||||
|
|
||||||
val actualType: Type = inferTypeVariables(actualClass, declaredClass, declaredType) ?: declaredType
|
val actualType: Type = inferTypeVariables(actualClass, declaredClass, declaredType) ?: declaredType
|
||||||
|
|
||||||
|
println ("actual type - $actualType")
|
||||||
|
|
||||||
val serializer = if (Collection::class.java.isAssignableFrom(declaredClass)) {
|
val serializer = if (Collection::class.java.isAssignableFrom(declaredClass)) {
|
||||||
serializersByType.computeIfAbsent(declaredType) {
|
serializersByType.computeIfAbsent(declaredType) {
|
||||||
CollectionSerializer(declaredType as? ParameterizedType ?: DeserializedParameterizedType(
|
CollectionSerializer(declaredType as? ParameterizedType ?: DeserializedParameterizedType(
|
||||||
@ -182,9 +185,11 @@ class SerializerFactory(val whitelist: ClassWhitelist, cl : ClassLoader) {
|
|||||||
private fun processSchema(schema: Schema, sentinel: Boolean = false) {
|
private fun processSchema(schema: Schema, sentinel: Boolean = false) {
|
||||||
val carpenterSchemas = CarpenterSchemas.newInstance()
|
val carpenterSchemas = CarpenterSchemas.newInstance()
|
||||||
for (typeNotation in schema.types) {
|
for (typeNotation in schema.types) {
|
||||||
|
println("processSchema: ${typeNotation.descriptor} ${typeNotation.name}")
|
||||||
try {
|
try {
|
||||||
processSchemaEntry(typeNotation)
|
processSchemaEntry(typeNotation)
|
||||||
} catch (e: ClassNotFoundException) {
|
} catch (e: ClassNotFoundException) {
|
||||||
|
println("poop")
|
||||||
if (sentinel || (typeNotation !is CompositeType)) throw e
|
if (sentinel || (typeNotation !is CompositeType)) throw e
|
||||||
typeNotation.carpenterSchema(classloader, carpenterSchemas = carpenterSchemas)
|
typeNotation.carpenterSchema(classloader, carpenterSchemas = carpenterSchemas)
|
||||||
}
|
}
|
||||||
@ -212,6 +217,7 @@ class SerializerFactory(val whitelist: ClassWhitelist, cl : ClassLoader) {
|
|||||||
|
|
||||||
private fun processCompositeType(typeNotation: CompositeType) {
|
private fun processCompositeType(typeNotation: CompositeType) {
|
||||||
// TODO: class loader logic, and compare the schema.
|
// TODO: class loader logic, and compare the schema.
|
||||||
|
println("processCompositeType")
|
||||||
val type = typeForName(typeNotation.name, classloader)
|
val type = typeForName(typeNotation.name, classloader)
|
||||||
get(type.asClass() ?: throw NotSerializableException("Unable to build composite type for $type"), type)
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
Reference in New Issue
Block a user