Integration of the serialiser and carpenter working

Tests added
This commit is contained in:
Katelyn Baker 2017-07-14 17:42:58 +01:00
parent 4cfa376d7d
commit a5106d74a8
6 changed files with 49 additions and 35 deletions

View File

@ -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 {

View File

@ -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) {

View File

@ -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)
} }
} }

View File

@ -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

View File

@ -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))

View File

@ -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))
}
} }