From a5106d74a811de6cff7318cd00624ebf52ccbe55 Mon Sep 17 00:00:00 2001 From: Katelyn Baker Date: Fri, 14 Jul 2017 17:42:58 +0100 Subject: [PATCH] Integration of the serialiser and carpenter working Tests added --- .../internal/serialization/amqp/Schema.kt | 3 +- .../serialization/amqp/SerializationHelper.kt | 1 - .../serialization/amqp/SerializerFactory.kt | 14 +---- .../carpenter/AMQPSchemaExtensions.kt | 2 + .../serialization/carpenter/ClassCarpenter.kt | 3 +- .../amqp/DeserializeNeedingCarpentryTests.kt | 61 +++++++++++++------ 6 files changed, 49 insertions(+), 35 deletions(-) diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt index 4eea6f4c20..a67cb8e400 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/Schema.kt @@ -14,6 +14,7 @@ import java.lang.reflect.ParameterizedType import java.lang.reflect.Type import java.lang.reflect.TypeVariable import java.util.* + import net.corda.nodeapi.internal.serialization.carpenter.Field as CarpenterField 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, sb.append("/>") return sb.toString() } - - fun typeAsString() = if (type =="*") requires[0] else type } sealed class TypeNotation : DescribedType { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt index bafd9d5c8c..a8c15461ca 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializationHelper.kt @@ -69,7 +69,6 @@ private fun isConcrete(clazz: Class<*>): Boolean = !(clazz.isInterface || Modifi private fun propertiesForSerializationFromConstructor(kotlinConstructor: KFunction, type: Type, factory: SerializerFactory): Collection { 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. - println (Introspector.getBeanInfo(clazz).propertyDescriptors) val properties = Introspector.getBeanInfo(clazz).propertyDescriptors.filter { it.name != "class" }.groupBy { it.name }.mapValues { it.value[0] } val rc: MutableList = ArrayList(kotlinConstructor.parameters.size) for (param in kotlinConstructor.parameters) { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt index 045d41e9ea..d1b69953b8 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/SerializerFactory.kt @@ -174,21 +174,16 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) { 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) { 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) + if ((cl != DeserializedParameterizedType::class.java.classLoader) + || (typeNotation !is CompositeType)) throw e + typeNotation.carpenterSchema(carpenterSchemas = carpenterSchemas) } } @@ -217,9 +212,7 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) { private fun processCompositeType(typeNotation: CompositeType, cl: ClassLoader = DeserializedParameterizedType::class.java.classLoader) { - println ("processCompositeType ${typeNotation.name}") serializersByDescriptor.computeIfAbsent(typeNotation.descriptor.name!!) { - println (" no such type") // TODO: class loader logic, and compare the schema. val type = typeForName(typeNotation.name, cl) 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 { - println ("typeForName: name = $name") DeserializedParameterizedType.make(name, cl) } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt index f3dac73f33..355e7f4b92 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/AMQPSchemaExtensions.kt @@ -26,6 +26,8 @@ private fun CompositeType.validatePropertyTypes( } } +fun AMQPField.typeAsString() = if (type =="*") requires[0] else type + /** * based upon this AMQP schema either * a) add the corresponding carpenter schema to the [carpenterSchemas] param diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenter.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenter.kt index 8909a2de83..ad7e8d4ca6 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenter.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/carpenter/ClassCarpenter.kt @@ -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 // actually called, which is a bit too dynamic for my tastes. val allFields = schema.fieldsIncludingSuperclasses() - println (" interfaces = ${schema.interfaces}") for (itf in schema.interfaces) { itf.methods.forEach { - println (" param ${it.name}") val fieldNameFromItf = when { it.name.startsWith("get") -> it.name.substring(3).decapitalize() else -> throw InterfaceMismatchException( @@ -311,6 +309,7 @@ class ClassCarpenter { // 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 // method from SimpleFieldAccess). + // method from SimpleFieldAccess) if (fieldNameFromItf.isEmpty() && SimpleFieldAccess::class.java in schema.interfaces) return@forEach if ((schema is ClassSchema) and (fieldNameFromItf !in allFields)) diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt index 80d6ab1c50..29e238361c 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/serialization/amqp/DeserializeNeedingCarpentryTests.kt @@ -4,19 +4,24 @@ 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.InterfaceSchema import net.corda.core.serialization.carpenter.NonNullableField interface I { 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 { @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 clazz = ClassCarpenter().build(ClassSchema("oneType", mapOf("a" to NonNullableField(Int::class.java)))) val classInstance = clazz.constructors[0].newInstance(testVal) val serialisedBytes = SerializationOutput().serialize(classInstance) @@ -27,11 +32,10 @@ class DeserializeNeedingCarpentryTests { @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 clazz = ClassCarpenter().build (ClassSchema( + "oneType", mapOf("name" to NonNullableField(String::class.java)), + interfaces = listOf (I::class.java))) + val testVal = "Some Person" val classInstance = clazz.constructors[0].newInstance(testVal) val serialisedBytes = SerializationOutput().serialize(classInstance) @@ -44,13 +48,11 @@ class DeserializeNeedingCarpentryTests { @Test fun nestedTypes() { val cc = ClassCarpenter() - val nestedClass = cc.build ( - ClassSchema("nestedType", - mapOf("name" to NonNullableField(String::class.java)))) + 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 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) @@ -63,9 +65,8 @@ class DeserializeNeedingCarpentryTests { @Test fun repeatedNestedTypes() { val cc = ClassCarpenter() - val nestedClass = cc.build ( - ClassSchema("nestedType", - mapOf("name" to NonNullableField(String::class.java)))) + val nestedClass = cc.build (ClassSchema("nestedType", + mapOf("name" to NonNullableField(String::class.java)))) data class outer(val a: Any, val b: Any) @@ -82,8 +83,7 @@ class DeserializeNeedingCarpentryTests { @Test fun listOfType() { - val cc = ClassCarpenter() - val unknownClass = cc.build (ClassSchema("unknownClass", mapOf( + val unknownClass = ClassCarpenter().build (ClassSchema("unknownClass", mapOf( "v1" 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)) } } + + @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)) + } }