mirror of
https://github.com/corda/corda.git
synced 2025-06-02 07:30:53 +00:00
Integration of the serialiser and carpenter working
Tests added
This commit is contained in:
parent
4cfa376d7d
commit
a5106d74a8
@ -14,6 +14,7 @@ import java.lang.reflect.ParameterizedType
|
|||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
import java.lang.reflect.TypeVariable
|
import java.lang.reflect.TypeVariable
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
import net.corda.nodeapi.internal.serialization.carpenter.Field as CarpenterField
|
import net.corda.nodeapi.internal.serialization.carpenter.Field as CarpenterField
|
||||||
import net.corda.nodeapi.internal.serialization.carpenter.Schema as CarpenterSchema
|
import net.corda.nodeapi.internal.serialization.carpenter.Schema as CarpenterSchema
|
||||||
|
|
||||||
@ -170,8 +171,6 @@ data class Field(val name: String, val type: String, val requires: List<String>,
|
|||||||
sb.append("/>")
|
sb.append("/>")
|
||||||
return sb.toString()
|
return sb.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun typeAsString() = if (type =="*") requires[0] else type
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class TypeNotation : DescribedType {
|
sealed class TypeNotation : DescribedType {
|
||||||
|
@ -69,7 +69,6 @@ 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) {
|
||||||
|
@ -174,21 +174,16 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
|
|||||||
private fun processSchema(
|
private fun processSchema(
|
||||||
schema: Schema,
|
schema: Schema,
|
||||||
cl: ClassLoader = DeserializedParameterizedType::class.java.classLoader) {
|
cl: ClassLoader = DeserializedParameterizedType::class.java.classLoader) {
|
||||||
println ("processSchema cl ${cl::class.java}")
|
|
||||||
|
|
||||||
val retry = cl != DeserializedParameterizedType::class.java.classLoader
|
|
||||||
val carpenterSchemas = CarpenterSchemas.newInstance()
|
val carpenterSchemas = CarpenterSchemas.newInstance()
|
||||||
for (typeNotation in schema.types) {
|
for (typeNotation in schema.types) {
|
||||||
try {
|
try {
|
||||||
processSchemaEntry(typeNotation, cl)
|
processSchemaEntry(typeNotation, cl)
|
||||||
}
|
}
|
||||||
catch (e: java.lang.ClassNotFoundException) {
|
catch (e: java.lang.ClassNotFoundException) {
|
||||||
println (" CLASS NOT FOUND - $retry")
|
if ((cl != DeserializedParameterizedType::class.java.classLoader)
|
||||||
if (retry) {
|
|| (typeNotation !is CompositeType)) throw e
|
||||||
throw e
|
typeNotation.carpenterSchema(carpenterSchemas = carpenterSchemas)
|
||||||
}
|
|
||||||
println ("add schema ${typeNotation.name}")
|
|
||||||
(typeNotation as CompositeType).carpenterSchema(carpenterSchemas = carpenterSchemas)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,9 +212,7 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
|
|||||||
|
|
||||||
private fun processCompositeType(typeNotation: CompositeType,
|
private fun processCompositeType(typeNotation: CompositeType,
|
||||||
cl: ClassLoader = DeserializedParameterizedType::class.java.classLoader) {
|
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, cl)
|
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)
|
||||||
@ -361,7 +354,6 @@ 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 {
|
||||||
println ("typeForName: name = $name")
|
|
||||||
DeserializedParameterizedType.make(name, cl)
|
DeserializedParameterizedType.make(name, cl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,8 @@ private fun CompositeType.validatePropertyTypes(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun AMQPField.typeAsString() = if (type =="*") requires[0] else type
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* based upon this AMQP schema either
|
* based upon this AMQP schema either
|
||||||
* a) add the corresponding carpenter schema to the [carpenterSchemas] param
|
* a) add the corresponding carpenter schema to the [carpenterSchemas] param
|
||||||
|
@ -297,10 +297,8 @@ 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(
|
||||||
@ -311,6 +309,7 @@ class ClassCarpenter {
|
|||||||
// If we're trying to carpent a class that prior to serialisation / deserialisation
|
// If we're trying to carpent a class that prior to serialisation / deserialisation
|
||||||
// was made by a carpenter then we can ignore this (it will implement a plain get
|
// was made by a carpenter then we can ignore this (it will implement a plain get
|
||||||
// method from SimpleFieldAccess).
|
// method from SimpleFieldAccess).
|
||||||
|
// method from SimpleFieldAccess)
|
||||||
if (fieldNameFromItf.isEmpty() && SimpleFieldAccess::class.java in schema.interfaces) return@forEach
|
if (fieldNameFromItf.isEmpty() && SimpleFieldAccess::class.java in schema.interfaces) return@forEach
|
||||||
|
|
||||||
if ((schema is ClassSchema) and (fieldNameFromItf !in allFields))
|
if ((schema is ClassSchema) and (fieldNameFromItf !in allFields))
|
||||||
|
@ -4,19 +4,24 @@ import org.junit.Test
|
|||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
import net.corda.core.serialization.carpenter.ClassCarpenter
|
import net.corda.core.serialization.carpenter.ClassCarpenter
|
||||||
import net.corda.core.serialization.carpenter.ClassSchema
|
import net.corda.core.serialization.carpenter.ClassSchema
|
||||||
|
import net.corda.core.serialization.carpenter.InterfaceSchema
|
||||||
import net.corda.core.serialization.carpenter.NonNullableField
|
import net.corda.core.serialization.carpenter.NonNullableField
|
||||||
|
|
||||||
interface I {
|
interface I {
|
||||||
fun getName() : String
|
fun getName() : String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These tests work by having the class carpenter build the classes we serialise and then deserialise. Because
|
||||||
|
* those classes don't exist within the system's Class Loader the deserialiser will be forced to carpent
|
||||||
|
* versions of them up using its own internal class carpenter (each carpenter houses it's own loader). This
|
||||||
|
* replicates the situation where a reciever doesn't have some or all elements of a schema present on it's classpath
|
||||||
|
*/
|
||||||
class DeserializeNeedingCarpentryTests {
|
class DeserializeNeedingCarpentryTests {
|
||||||
@Test
|
@Test
|
||||||
fun verySimpleType() {
|
fun verySimpleType() {
|
||||||
val testVal = 10
|
val testVal = 10
|
||||||
val cc = ClassCarpenter()
|
val clazz = ClassCarpenter().build(ClassSchema("oneType", mapOf("a" to NonNullableField(Int::class.java))))
|
||||||
val schema = ClassSchema("oneType", mapOf("a" to NonNullableField(Int::class.java)))
|
|
||||||
val clazz = cc.build (schema)
|
|
||||||
val classInstance = clazz.constructors[0].newInstance(testVal)
|
val classInstance = clazz.constructors[0].newInstance(testVal)
|
||||||
|
|
||||||
val serialisedBytes = SerializationOutput().serialize(classInstance)
|
val serialisedBytes = SerializationOutput().serialize(classInstance)
|
||||||
@ -27,11 +32,10 @@ class DeserializeNeedingCarpentryTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun simpleTypeKnownInterface() {
|
fun simpleTypeKnownInterface() {
|
||||||
val cc = ClassCarpenter()
|
val clazz = ClassCarpenter().build (ClassSchema(
|
||||||
val schema = ClassSchema("oneType", mapOf("name" to NonNullableField(String::class.java)),
|
"oneType", mapOf("name" to NonNullableField(String::class.java)),
|
||||||
interfaces = listOf (I::class.java))
|
interfaces = listOf (I::class.java)))
|
||||||
val clazz = cc.build (schema)
|
val testVal = "Some Person"
|
||||||
val testVal = "Andrew Person"
|
|
||||||
val classInstance = clazz.constructors[0].newInstance(testVal)
|
val classInstance = clazz.constructors[0].newInstance(testVal)
|
||||||
|
|
||||||
val serialisedBytes = SerializationOutput().serialize(classInstance)
|
val serialisedBytes = SerializationOutput().serialize(classInstance)
|
||||||
@ -44,13 +48,11 @@ class DeserializeNeedingCarpentryTests {
|
|||||||
@Test
|
@Test
|
||||||
fun nestedTypes() {
|
fun nestedTypes() {
|
||||||
val cc = ClassCarpenter()
|
val cc = ClassCarpenter()
|
||||||
val nestedClass = cc.build (
|
val nestedClass = cc.build (ClassSchema("nestedType",
|
||||||
ClassSchema("nestedType",
|
mapOf("name" to NonNullableField(String::class.java))))
|
||||||
mapOf("name" to NonNullableField(String::class.java))))
|
|
||||||
|
|
||||||
val outerClass = cc.build (
|
val outerClass = cc.build (ClassSchema("outerType",
|
||||||
ClassSchema("outerType",
|
mapOf("inner" to NonNullableField(nestedClass))))
|
||||||
mapOf("inner" to NonNullableField(nestedClass))))
|
|
||||||
|
|
||||||
val classInstance = outerClass.constructors.first().newInstance(nestedClass.constructors.first().newInstance("name"))
|
val classInstance = outerClass.constructors.first().newInstance(nestedClass.constructors.first().newInstance("name"))
|
||||||
val serialisedBytes = SerializationOutput().serialize(classInstance)
|
val serialisedBytes = SerializationOutput().serialize(classInstance)
|
||||||
@ -63,9 +65,8 @@ class DeserializeNeedingCarpentryTests {
|
|||||||
@Test
|
@Test
|
||||||
fun repeatedNestedTypes() {
|
fun repeatedNestedTypes() {
|
||||||
val cc = ClassCarpenter()
|
val cc = ClassCarpenter()
|
||||||
val nestedClass = cc.build (
|
val nestedClass = cc.build (ClassSchema("nestedType",
|
||||||
ClassSchema("nestedType",
|
mapOf("name" to NonNullableField(String::class.java))))
|
||||||
mapOf("name" to NonNullableField(String::class.java))))
|
|
||||||
|
|
||||||
data class outer(val a: Any, val b: Any)
|
data class outer(val a: Any, val b: Any)
|
||||||
|
|
||||||
@ -82,8 +83,7 @@ class DeserializeNeedingCarpentryTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun listOfType() {
|
fun listOfType() {
|
||||||
val cc = ClassCarpenter()
|
val unknownClass = ClassCarpenter().build (ClassSchema("unknownClass", mapOf(
|
||||||
val unknownClass = cc.build (ClassSchema("unknownClass", mapOf(
|
|
||||||
"v1" to NonNullableField(Int::class.java),
|
"v1" to NonNullableField(Int::class.java),
|
||||||
"v2" to NonNullableField(Int::class.java))))
|
"v2" to NonNullableField(Int::class.java))))
|
||||||
|
|
||||||
@ -102,4 +102,27 @@ class DeserializeNeedingCarpentryTests {
|
|||||||
assertEquals(sentinel++, it::class.java.getMethod("getV2").invoke(it))
|
assertEquals(sentinel++, it::class.java.getMethod("getV2").invoke(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun unknownInterface() {
|
||||||
|
val cc = ClassCarpenter()
|
||||||
|
|
||||||
|
val interfaceClass = cc.build (InterfaceSchema(
|
||||||
|
"gen.Interface",
|
||||||
|
mapOf("age" to NonNullableField (Int::class.java))))
|
||||||
|
|
||||||
|
val concreteClass = cc.build (ClassSchema ("gen.Class", mapOf(
|
||||||
|
"age" to NonNullableField (Int::class.java),
|
||||||
|
"name" to NonNullableField(String::class.java)),
|
||||||
|
interfaces = listOf (I::class.java, interfaceClass)))
|
||||||
|
|
||||||
|
val serialisedBytes = SerializationOutput().serialize(
|
||||||
|
concreteClass.constructors.first().newInstance(12, "timmy"))
|
||||||
|
val deserializedObj = DeserializationInput().deserialize(serialisedBytes)
|
||||||
|
|
||||||
|
assertTrue(deserializedObj is I)
|
||||||
|
assertEquals("timmy", (deserializedObj as I).getName())
|
||||||
|
assertEquals("timmy", deserializedObj::class.java.getMethod("getName").invoke(deserializedObj))
|
||||||
|
assertEquals(12, deserializedObj::class.java.getMethod("getAge").invoke(deserializedObj))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user