mirror of
https://github.com/corda/corda.git
synced 2025-06-18 07:08:15 +00:00
Add the carpenter into the deserializer
Deseializing any blob that contains a type unknown to the classloader will cause a class object to be carpented up and a suitable object returned
This commit is contained in:
@ -69,6 +69,7 @@ private fun isConcrete(clazz: Class<*>): Boolean = !(clazz.isInterface || Modifi
|
|||||||
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> {
|
||||||
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.
|
||||||
|
println (Introspector.getBeanInfo(clazz).propertyDescriptors)
|
||||||
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] }
|
||||||
val rc: MutableList<PropertySerializer> = ArrayList(kotlinConstructor.parameters.size)
|
val rc: MutableList<PropertySerializer> = ArrayList(kotlinConstructor.parameters.size)
|
||||||
for (param in kotlinConstructor.parameters) {
|
for (param in kotlinConstructor.parameters) {
|
||||||
|
@ -5,6 +5,10 @@ import com.google.common.reflect.TypeResolver
|
|||||||
import net.corda.core.serialization.ClassWhitelist
|
import net.corda.core.serialization.ClassWhitelist
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
import net.corda.nodeapi.internal.serialization.AllWhitelist
|
||||||
|
import net.corda.nodeapi.internal.serialization.carpenter.CarpenterSchemas
|
||||||
|
import net.corda.nodeapi.internal.serialization.carpenter.ClassCarpenter
|
||||||
|
import net.corda.nodeapi.internal.serialization.carpenter.MetaCarpenter
|
||||||
|
import net.corda.nodeapi.internal.serialization.carpenter.carpenterSchema
|
||||||
import org.apache.qpid.proton.amqp.*
|
import org.apache.qpid.proton.amqp.*
|
||||||
import java.io.NotSerializableException
|
import java.io.NotSerializableException
|
||||||
import java.lang.reflect.GenericArrayType
|
import java.lang.reflect.GenericArrayType
|
||||||
@ -167,15 +171,38 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processSchema(schema: Schema) {
|
private fun processSchema(
|
||||||
|
schema: Schema,
|
||||||
|
cl: ClassLoader = DeserializedParameterizedType::class.java.classLoader) {
|
||||||
|
println ("processSchema cl ${cl::class.java}")
|
||||||
|
|
||||||
|
val retry = cl != DeserializedParameterizedType::class.java.classLoader
|
||||||
|
val carpenterSchemas = CarpenterSchemas.newInstance()
|
||||||
for (typeNotation in schema.types) {
|
for (typeNotation in schema.types) {
|
||||||
processSchemaEntry(typeNotation)
|
try {
|
||||||
|
processSchemaEntry(typeNotation, cl)
|
||||||
|
}
|
||||||
|
catch (e: java.lang.ClassNotFoundException) {
|
||||||
|
println (" CLASS NOT FOUND - $retry")
|
||||||
|
if (retry) {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
println ("add schema ${typeNotation.name}")
|
||||||
|
(typeNotation as CompositeType).carpenterSchema(carpenterSchemas = carpenterSchemas)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processSchemaEntry(typeNotation: TypeNotation) {
|
if (carpenterSchemas.isEmpty()) return
|
||||||
|
val mc = MetaCarpenter(carpenterSchemas)
|
||||||
|
mc.build()
|
||||||
|
|
||||||
|
processSchema(schema, mc.classloader)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processSchemaEntry(typeNotation: TypeNotation,
|
||||||
|
cl: ClassLoader = DeserializedParameterizedType::class.java.classLoader) {
|
||||||
when (typeNotation) {
|
when (typeNotation) {
|
||||||
is CompositeType -> processCompositeType(typeNotation) // java.lang.Class (whether a class or interface)
|
is CompositeType -> processCompositeType(typeNotation, cl) // java.lang.Class (whether a class or interface)
|
||||||
is RestrictedType -> processRestrictedType(typeNotation) // Collection / Map, possibly with generics
|
is RestrictedType -> processRestrictedType(typeNotation) // Collection / Map, possibly with generics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -188,10 +215,13 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processCompositeType(typeNotation: CompositeType) {
|
private fun processCompositeType(typeNotation: CompositeType,
|
||||||
|
cl: ClassLoader = DeserializedParameterizedType::class.java.classLoader) {
|
||||||
|
println ("processCompositeType ${typeNotation.name}")
|
||||||
serializersByDescriptor.computeIfAbsent(typeNotation.descriptor.name!!) {
|
serializersByDescriptor.computeIfAbsent(typeNotation.descriptor.name!!) {
|
||||||
|
println (" no such type")
|
||||||
// TODO: class loader logic, and compare the schema.
|
// TODO: class loader logic, and compare the schema.
|
||||||
val type = typeForName(typeNotation.name)
|
val type = typeForName(typeNotation.name, cl)
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -304,7 +334,9 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
|
|||||||
else -> throw NotSerializableException("Unable to render type $type to a string.")
|
else -> throw NotSerializableException("Unable to render type $type to a string.")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun typeForName(name: String): Type {
|
private fun typeForName(
|
||||||
|
name: String,
|
||||||
|
cl: ClassLoader = DeserializedParameterizedType::class.java.classLoader): Type {
|
||||||
return if (name.endsWith("[]")) {
|
return if (name.endsWith("[]")) {
|
||||||
val elementType = typeForName(name.substring(0, name.lastIndex - 1))
|
val elementType = typeForName(name.substring(0, name.lastIndex - 1))
|
||||||
if (elementType is ParameterizedType || elementType is GenericArrayType) {
|
if (elementType is ParameterizedType || elementType is GenericArrayType) {
|
||||||
@ -329,7 +361,8 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
|
|||||||
else -> throw NotSerializableException("Not able to deserialize array type: $name")
|
else -> throw NotSerializableException("Not able to deserialize array type: $name")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DeserializedParameterizedType.make(name)
|
println ("typeForName: name = $name")
|
||||||
|
DeserializedParameterizedType.make(name, cl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -297,8 +297,10 @@ class ClassCarpenter {
|
|||||||
// fact that we didn't implement the interface we said we would at the moment the missing method is
|
// fact that we didn't implement the interface we said we would at the moment the missing method is
|
||||||
// actually called, which is a bit too dynamic for my tastes.
|
// actually called, which is a bit too dynamic for my tastes.
|
||||||
val allFields = schema.fieldsIncludingSuperclasses()
|
val allFields = schema.fieldsIncludingSuperclasses()
|
||||||
|
println (" interfaces = ${schema.interfaces}")
|
||||||
for (itf in schema.interfaces) {
|
for (itf in schema.interfaces) {
|
||||||
itf.methods.forEach {
|
itf.methods.forEach {
|
||||||
|
println (" param ${it.name}")
|
||||||
val fieldNameFromItf = when {
|
val fieldNameFromItf = when {
|
||||||
it.name.startsWith("get") -> it.name.substring(3).decapitalize()
|
it.name.startsWith("get") -> it.name.substring(3).decapitalize()
|
||||||
else -> throw InterfaceMismatchException(
|
else -> throw InterfaceMismatchException(
|
||||||
|
@ -42,6 +42,8 @@ data class CarpenterSchemas (
|
|||||||
|
|
||||||
val size
|
val size
|
||||||
get() = carpenterSchemas.size
|
get() = carpenterSchemas.size
|
||||||
|
|
||||||
|
fun isEmpty() = carpenterSchemas.isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,6 +82,9 @@ abstract class MetaCarpenterBase (val schemas : CarpenterSchemas) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract fun build()
|
abstract fun build()
|
||||||
|
|
||||||
|
val classloader : ClassLoader
|
||||||
|
get() = cc.classloader
|
||||||
}
|
}
|
||||||
|
|
||||||
class MetaCarpenter(schemas: CarpenterSchemas) : MetaCarpenterBase(schemas) {
|
class MetaCarpenter(schemas: CarpenterSchemas) : MetaCarpenterBase(schemas) {
|
||||||
|
@ -0,0 +1,105 @@
|
|||||||
|
package net.corda.core.serialization.amqp
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import kotlin.test.*
|
||||||
|
import net.corda.core.serialization.carpenter.ClassCarpenter
|
||||||
|
import net.corda.core.serialization.carpenter.ClassSchema
|
||||||
|
import net.corda.core.serialization.carpenter.NonNullableField
|
||||||
|
|
||||||
|
interface I {
|
||||||
|
fun getName() : String
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeserializeNeedingCarpentryTests {
|
||||||
|
@Test
|
||||||
|
fun verySimpleType() {
|
||||||
|
val testVal = 10
|
||||||
|
val cc = ClassCarpenter()
|
||||||
|
val schema = ClassSchema("oneType", mapOf("a" to NonNullableField(Int::class.java)))
|
||||||
|
val clazz = cc.build (schema)
|
||||||
|
val classInstance = clazz.constructors[0].newInstance(testVal)
|
||||||
|
|
||||||
|
val serialisedBytes = SerializationOutput().serialize(classInstance)
|
||||||
|
val deserializedObj = DeserializationInput().deserialize(serialisedBytes)
|
||||||
|
|
||||||
|
assertEquals (testVal, deserializedObj::class.java.getMethod("getA").invoke(deserializedObj))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun simpleTypeKnownInterface() {
|
||||||
|
val cc = ClassCarpenter()
|
||||||
|
val schema = ClassSchema("oneType", mapOf("name" to NonNullableField(String::class.java)),
|
||||||
|
interfaces = listOf (I::class.java))
|
||||||
|
val clazz = cc.build (schema)
|
||||||
|
val testVal = "Andrew Person"
|
||||||
|
val classInstance = clazz.constructors[0].newInstance(testVal)
|
||||||
|
|
||||||
|
val serialisedBytes = SerializationOutput().serialize(classInstance)
|
||||||
|
val deserializedObj = DeserializationInput().deserialize(serialisedBytes)
|
||||||
|
|
||||||
|
assertTrue(deserializedObj is I)
|
||||||
|
assertEquals(testVal, (deserializedObj as I).getName())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun nestedTypes() {
|
||||||
|
val cc = ClassCarpenter()
|
||||||
|
val nestedClass = cc.build (
|
||||||
|
ClassSchema("nestedType",
|
||||||
|
mapOf("name" to NonNullableField(String::class.java))))
|
||||||
|
|
||||||
|
val outerClass = cc.build (
|
||||||
|
ClassSchema("outerType",
|
||||||
|
mapOf("inner" to NonNullableField(nestedClass))))
|
||||||
|
|
||||||
|
val classInstance = outerClass.constructors.first().newInstance(nestedClass.constructors.first().newInstance("name"))
|
||||||
|
val serialisedBytes = SerializationOutput().serialize(classInstance)
|
||||||
|
val deserializedObj = DeserializationInput().deserialize(serialisedBytes)
|
||||||
|
|
||||||
|
val inner = deserializedObj::class.java.getMethod("getInner").invoke(deserializedObj)
|
||||||
|
assertEquals("name", inner::class.java.getMethod("getName").invoke(inner))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun repeatedNestedTypes() {
|
||||||
|
val cc = ClassCarpenter()
|
||||||
|
val nestedClass = cc.build (
|
||||||
|
ClassSchema("nestedType",
|
||||||
|
mapOf("name" to NonNullableField(String::class.java))))
|
||||||
|
|
||||||
|
data class outer(val a: Any, val b: Any)
|
||||||
|
|
||||||
|
val classInstance = outer (
|
||||||
|
nestedClass.constructors.first().newInstance("foo"),
|
||||||
|
nestedClass.constructors.first().newInstance("bar"))
|
||||||
|
|
||||||
|
val serialisedBytes = SerializationOutput().serialize(classInstance)
|
||||||
|
val deserializedObj = DeserializationInput().deserialize(serialisedBytes)
|
||||||
|
|
||||||
|
assertEquals ("foo", deserializedObj.a::class.java.getMethod("getName").invoke(deserializedObj.a))
|
||||||
|
assertEquals ("bar", deserializedObj.b::class.java.getMethod("getName").invoke(deserializedObj.b))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun listOfType() {
|
||||||
|
val cc = ClassCarpenter()
|
||||||
|
val unknownClass = cc.build (ClassSchema("unknownClass", mapOf(
|
||||||
|
"v1" to NonNullableField(Int::class.java),
|
||||||
|
"v2" to NonNullableField(Int::class.java))))
|
||||||
|
|
||||||
|
data class outer (val l : List<Any>)
|
||||||
|
val toSerialise = outer (listOf (
|
||||||
|
unknownClass.constructors.first().newInstance(1, 2),
|
||||||
|
unknownClass.constructors.first().newInstance(3, 4),
|
||||||
|
unknownClass.constructors.first().newInstance(5, 6),
|
||||||
|
unknownClass.constructors.first().newInstance(7, 8)))
|
||||||
|
|
||||||
|
val serialisedBytes = SerializationOutput().serialize(toSerialise)
|
||||||
|
val deserializedObj = DeserializationInput().deserialize(serialisedBytes)
|
||||||
|
var sentinel = 1
|
||||||
|
deserializedObj.l.forEach {
|
||||||
|
assertEquals(sentinel++, it::class.java.getMethod("getV1").invoke(it))
|
||||||
|
assertEquals(sentinel++, it::class.java.getMethod("getV2").invoke(it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,9 @@
|
|||||||
package net.corda.nodeapi.internal.serialization.carpenter
|
package net.corda.nodeapi.internal.serialization.carpenter
|
||||||
|
|
||||||
import net.corda.nodeapi.internal.serialization.amqp.*
|
|
||||||
import net.corda.nodeapi.internal.serialization.amqp.Schema
|
|
||||||
import net.corda.nodeapi.internal.serialization.amqp.Field
|
import net.corda.nodeapi.internal.serialization.amqp.Field
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.Schema
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.SerializationOutput
|
||||||
|
|
||||||
fun mangleName(name: String) = "${name}__carpenter"
|
fun mangleName(name: String) = "${name}__carpenter"
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user