mirror of
https://github.com/corda/corda.git
synced 2024-12-19 13:08:04 +00:00
Merge pull request #1147 from corda/feature/kat/serialiserSynthesiserMerge
Serialiser / Synthesiser merge
This commit is contained in:
commit
fa0cdeed2d
@ -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 {
|
||||||
|
@ -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
|
||||||
@ -44,6 +48,7 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
|
|||||||
private val serializersByType = ConcurrentHashMap<Type, AMQPSerializer<Any>>()
|
private val serializersByType = ConcurrentHashMap<Type, AMQPSerializer<Any>>()
|
||||||
private val serializersByDescriptor = ConcurrentHashMap<Any, AMQPSerializer<Any>>()
|
private val serializersByDescriptor = ConcurrentHashMap<Any, AMQPSerializer<Any>>()
|
||||||
private val customSerializers = CopyOnWriteArrayList<CustomSerializer<out Any>>()
|
private val customSerializers = CopyOnWriteArrayList<CustomSerializer<out Any>>()
|
||||||
|
private val classCarpenter = ClassCarpenter()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Look up, and manufacture if necessary, a serializer for the given type.
|
* Look up, and manufacture if necessary, a serializer for the given type.
|
||||||
@ -167,15 +172,30 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processSchema(schema: Schema) {
|
private fun processSchema(schema: Schema, sentinal: Boolean = false) {
|
||||||
|
val carpenterSchemas = CarpenterSchemas.newInstance()
|
||||||
for (typeNotation in schema.types) {
|
for (typeNotation in schema.types) {
|
||||||
processSchemaEntry(typeNotation)
|
try {
|
||||||
|
processSchemaEntry(typeNotation, classCarpenter.classloader)
|
||||||
|
}
|
||||||
|
catch (e: ClassNotFoundException) {
|
||||||
|
if (sentinal || (typeNotation !is CompositeType)) throw e
|
||||||
|
typeNotation.carpenterSchema(
|
||||||
|
classLoaders = listOf (classCarpenter.classloader), carpenterSchemas = carpenterSchemas)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (carpenterSchemas.isNotEmpty()) {
|
||||||
|
val mc = MetaCarpenter(carpenterSchemas, classCarpenter)
|
||||||
|
mc.build()
|
||||||
|
processSchema(schema, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processSchemaEntry(typeNotation: TypeNotation) {
|
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,16 +208,17 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processCompositeType(typeNotation: CompositeType) {
|
private fun processCompositeType(typeNotation: CompositeType,
|
||||||
|
cl: ClassLoader = DeserializedParameterizedType::class.java.classLoader) {
|
||||||
serializersByDescriptor.computeIfAbsent(typeNotation.descriptor.name!!) {
|
serializersByDescriptor.computeIfAbsent(typeNotation.descriptor.name!!) {
|
||||||
// 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeClassSerializer(clazz: Class<*>, type: Type, declaredType: Type): AMQPSerializer<Any> {
|
private fun makeClassSerializer(clazz: Class<*>, type: Type, declaredType: Type): AMQPSerializer<Any> =
|
||||||
return serializersByType.computeIfAbsent(type) {
|
serializersByType.computeIfAbsent(type) {
|
||||||
if (isPrimitive(clazz)) {
|
if (isPrimitive(clazz)) {
|
||||||
AMQPPrimitiveSerializer(clazz)
|
AMQPPrimitiveSerializer(clazz)
|
||||||
} else {
|
} else {
|
||||||
@ -215,7 +236,6 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun findCustomSerializer(clazz: Class<*>, declaredType: Type): AMQPSerializer<Any>? {
|
internal fun findCustomSerializer(clazz: Class<*>, declaredType: Type): AMQPSerializer<Any>? {
|
||||||
@ -270,6 +290,7 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
|
|||||||
|
|
||||||
private val primitiveTypeNames: Map<Class<*>, String> = mapOf(
|
private val primitiveTypeNames: Map<Class<*>, String> = mapOf(
|
||||||
Character::class.java to "char",
|
Character::class.java to "char",
|
||||||
|
Char::class.java to "char",
|
||||||
Boolean::class.java to "boolean",
|
Boolean::class.java to "boolean",
|
||||||
Byte::class.java to "byte",
|
Byte::class.java to "byte",
|
||||||
UnsignedByte::class.java to "ubyte",
|
UnsignedByte::class.java to "ubyte",
|
||||||
@ -284,7 +305,6 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
|
|||||||
Decimal32::class.java to "decimal32",
|
Decimal32::class.java to "decimal32",
|
||||||
Decimal64::class.java to "decimal62",
|
Decimal64::class.java to "decimal62",
|
||||||
Decimal128::class.java to "decimal128",
|
Decimal128::class.java to "decimal128",
|
||||||
Char::class.java to "char",
|
|
||||||
Date::class.java to "timestamp",
|
Date::class.java to "timestamp",
|
||||||
UUID::class.java to "uuid",
|
UUID::class.java to "uuid",
|
||||||
ByteArray::class.java to "binary",
|
ByteArray::class.java to "binary",
|
||||||
@ -296,7 +316,7 @@ class SerializerFactory(val whitelist: ClassWhitelist = AllWhitelist) {
|
|||||||
fun nameForType(type: Type) : String = when (type) {
|
fun nameForType(type: Type) : String = when (type) {
|
||||||
is Class<*> -> {
|
is Class<*> -> {
|
||||||
primitiveTypeName(type) ?: if (type.isArray) {
|
primitiveTypeName(type) ?: if (type.isArray) {
|
||||||
"${nameForType(type.componentType)}${if(type.componentType.isPrimitive)"[p]" else "[]"}"
|
"${nameForType(type.componentType)}${if(type.componentType.isPrimitive) "[p]" else "[]"}"
|
||||||
} else type.name
|
} else type.name
|
||||||
}
|
}
|
||||||
is ParameterizedType -> "${nameForType(type.rawType)}<${type.actualTypeArguments.joinToString { nameForType(it) }}>"
|
is ParameterizedType -> "${nameForType(type.rawType)}<${type.actualTypeArguments.joinToString { nameForType(it) }}>"
|
||||||
@ -304,7 +324,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 +351,7 @@ 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)
|
DeserializedParameterizedType.make(name, cl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import net.corda.nodeapi.internal.serialization.amqp.Field as AMQPField
|
|||||||
import net.corda.nodeapi.internal.serialization.amqp.Schema as AMQPSchema
|
import net.corda.nodeapi.internal.serialization.amqp.Schema as AMQPSchema
|
||||||
|
|
||||||
fun AMQPSchema.carpenterSchema(
|
fun AMQPSchema.carpenterSchema(
|
||||||
loaders : List<ClassLoader> = listOf<ClassLoader>(ClassLoader.getSystemClassLoader()))
|
loaders: List<ClassLoader> = listOf<ClassLoader>(ClassLoader.getSystemClassLoader()))
|
||||||
: CarpenterSchemas {
|
: CarpenterSchemas {
|
||||||
val rtn = CarpenterSchemas.newInstance()
|
val rtn = CarpenterSchemas.newInstance()
|
||||||
|
|
||||||
@ -20,12 +20,14 @@ fun AMQPSchema.carpenterSchema(
|
|||||||
* if we can load the class then we MUST know about all of it's composite elements
|
* if we can load the class then we MUST know about all of it's composite elements
|
||||||
*/
|
*/
|
||||||
private fun CompositeType.validatePropertyTypes(
|
private fun CompositeType.validatePropertyTypes(
|
||||||
classLoaders: List<ClassLoader> = listOf<ClassLoader> (ClassLoader.getSystemClassLoader())){
|
classLoaders: List<ClassLoader> = listOf<ClassLoader>(ClassLoader.getSystemClassLoader())) {
|
||||||
fields.forEach {
|
fields.forEach {
|
||||||
if (!it.validateType(classLoaders)) throw UncarpentableException (name, it.name, it.type)
|
if (!it.validateType(classLoaders)) throw UncarpentableException(name, it.name, it.type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
@ -40,9 +42,9 @@ private fun CompositeType.validatePropertyTypes(
|
|||||||
* on the class path. For testing purposes schema generation can be forced
|
* on the class path. For testing purposes schema generation can be forced
|
||||||
*/
|
*/
|
||||||
fun CompositeType.carpenterSchema(
|
fun CompositeType.carpenterSchema(
|
||||||
classLoaders: List<ClassLoader> = listOf<ClassLoader> (ClassLoader.getSystemClassLoader()),
|
classLoaders: List<ClassLoader> = listOf<ClassLoader>(ClassLoader.getSystemClassLoader()),
|
||||||
carpenterSchemas: CarpenterSchemas,
|
carpenterSchemas: CarpenterSchemas,
|
||||||
force : Boolean = false) {
|
force: Boolean = false) {
|
||||||
if (classLoaders.exists(name)) {
|
if (classLoaders.exists(name)) {
|
||||||
validatePropertyTypes(classLoaders)
|
validatePropertyTypes(classLoaders)
|
||||||
if (!force) return
|
if (!force) return
|
||||||
@ -60,9 +62,8 @@ fun CompositeType.carpenterSchema(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
providesList.add (classLoaders.loadIfExists(it))
|
providesList.add(classLoaders.loadIfExists(it))
|
||||||
}
|
} catch (e: ClassNotFoundException) {
|
||||||
catch (e: ClassNotFoundException) {
|
|
||||||
carpenterSchemas.addDepPair(this, name, it)
|
carpenterSchemas.addDepPair(this, name, it)
|
||||||
isCreatable = false
|
isCreatable = false
|
||||||
}
|
}
|
||||||
@ -73,15 +74,14 @@ fun CompositeType.carpenterSchema(
|
|||||||
fields.forEach {
|
fields.forEach {
|
||||||
try {
|
try {
|
||||||
m[it.name] = FieldFactory.newInstance(it.mandatory, it.name, it.getTypeAsClass(classLoaders))
|
m[it.name] = FieldFactory.newInstance(it.mandatory, it.name, it.getTypeAsClass(classLoaders))
|
||||||
}
|
} catch (e: ClassNotFoundException) {
|
||||||
catch (e: ClassNotFoundException) {
|
|
||||||
carpenterSchemas.addDepPair(this, name, it.typeAsString())
|
carpenterSchemas.addDepPair(this, name, it.typeAsString())
|
||||||
isCreatable = false
|
isCreatable = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCreatable) {
|
if (isCreatable) {
|
||||||
carpenterSchemas.carpenterSchemas.add (CarpenterSchemaFactory.newInstance(
|
carpenterSchemas.carpenterSchemas.add(CarpenterSchemaFactory.newInstance(
|
||||||
name = name,
|
name = name,
|
||||||
fields = m,
|
fields = m,
|
||||||
interfaces = providesList,
|
interfaces = providesList,
|
||||||
@ -89,33 +89,48 @@ fun CompositeType.carpenterSchema(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// map a pair of (typename, mandatory) to the corresponding class type
|
||||||
|
// where the mandatory AMQP flag maps to the types nullability
|
||||||
|
val typeStrToType: Map<Pair<String, Boolean>, Class<out Any?>> = mapOf(
|
||||||
|
Pair("int", true) to Int::class.javaPrimitiveType!!,
|
||||||
|
Pair("int", false) to Integer::class.javaObjectType,
|
||||||
|
Pair("short", true) to Short::class.javaPrimitiveType!!,
|
||||||
|
Pair("short", false) to Short::class.javaObjectType,
|
||||||
|
Pair("long", true) to Long::class.javaPrimitiveType!!,
|
||||||
|
Pair("long", false) to Long::class.javaObjectType,
|
||||||
|
Pair("char", true) to Char::class.javaPrimitiveType!!,
|
||||||
|
Pair("char", false) to java.lang.Character::class.java,
|
||||||
|
Pair("boolean", true) to Boolean::class.javaPrimitiveType!!,
|
||||||
|
Pair("boolean", false) to Boolean::class.javaObjectType,
|
||||||
|
Pair("double", true) to Double::class.javaPrimitiveType!!,
|
||||||
|
Pair("double", false) to Double::class.javaObjectType,
|
||||||
|
Pair("float", true) to Float::class.javaPrimitiveType!!,
|
||||||
|
Pair("float", false) to Float::class.javaObjectType,
|
||||||
|
Pair("byte", true) to Byte::class.javaPrimitiveType!!,
|
||||||
|
Pair("byte", false) to Byte::class.javaObjectType
|
||||||
|
)
|
||||||
|
|
||||||
fun AMQPField.getTypeAsClass(
|
fun AMQPField.getTypeAsClass(
|
||||||
classLoaders: List<ClassLoader> = listOf<ClassLoader> (ClassLoader.getSystemClassLoader())
|
classLoaders: List<ClassLoader> = listOf<ClassLoader>(ClassLoader.getSystemClassLoader())
|
||||||
) = when (type) {
|
) = typeStrToType[Pair(type, mandatory)] ?: when (type) {
|
||||||
"int" -> Int::class.javaPrimitiveType!!
|
"string" -> String::class.java
|
||||||
"string" -> String::class.java
|
"*" -> classLoaders.loadIfExists(requires[0])
|
||||||
"short" -> Short::class.javaPrimitiveType!!
|
else -> classLoaders.loadIfExists(type)
|
||||||
"long" -> Long::class.javaPrimitiveType!!
|
|
||||||
"char" -> Char::class.javaPrimitiveType!!
|
|
||||||
"boolean" -> Boolean::class.javaPrimitiveType!!
|
|
||||||
"double" -> Double::class.javaPrimitiveType!!
|
|
||||||
"float" -> Float::class.javaPrimitiveType!!
|
|
||||||
"*" -> classLoaders.loadIfExists(requires[0])
|
|
||||||
else -> classLoaders.loadIfExists(type)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun AMQPField.validateType(
|
fun AMQPField.validateType(
|
||||||
classLoaders: List<ClassLoader> = listOf<ClassLoader> (ClassLoader.getSystemClassLoader())
|
classLoaders: List<ClassLoader> = listOf<ClassLoader>(ClassLoader.getSystemClassLoader())
|
||||||
) = when (type) {
|
) = when (type) {
|
||||||
"int", "string", "short", "long", "char", "boolean", "double", "float" -> true
|
"byte", "int", "string", "short", "long", "char", "boolean", "double", "float" -> true
|
||||||
"*" -> classLoaders.exists(requires[0])
|
"*" -> classLoaders.exists(requires[0])
|
||||||
else -> classLoaders.exists (type)
|
else -> classLoaders.exists(type)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<ClassLoader>.exists (clazz: String) =
|
private fun List<ClassLoader>.exists(clazz: String) = this.find {
|
||||||
this.find { try { it.loadClass(clazz); true } catch (e: ClassNotFoundException) { false } } != null
|
try { it.loadClass(clazz); true } catch (e: ClassNotFoundException) { false }
|
||||||
|
} != null
|
||||||
|
|
||||||
private fun List<ClassLoader>.loadIfExists (clazz: String) : Class<*> {
|
private fun List<ClassLoader>.loadIfExists(clazz: String): Class<*> {
|
||||||
this.forEach {
|
this.forEach {
|
||||||
try {
|
try {
|
||||||
return it.loadClass(clazz)
|
return it.loadClass(clazz)
|
||||||
|
@ -129,7 +129,7 @@ class ClassCarpenter {
|
|||||||
private fun generateClass(classSchema: Schema): Class<*> {
|
private fun generateClass(classSchema: Schema): Class<*> {
|
||||||
return generate(classSchema) { cw, schema ->
|
return generate(classSchema) { cw, schema ->
|
||||||
val superName = schema.superclass?.jvmName ?: "java/lang/Object"
|
val superName = schema.superclass?.jvmName ?: "java/lang/Object"
|
||||||
var interfaces = schema.interfaces.map { it.name.jvm }.toMutableList()
|
val interfaces = schema.interfaces.map { it.name.jvm }.toMutableList()
|
||||||
|
|
||||||
if (SimpleFieldAccess::class.java !in schema.interfaces) interfaces.add(SimpleFieldAccess::class.java.name.jvm)
|
if (SimpleFieldAccess::class.java !in schema.interfaces) interfaces.add(SimpleFieldAccess::class.java.name.jvm)
|
||||||
|
|
||||||
|
@ -42,6 +42,9 @@ data class CarpenterSchemas (
|
|||||||
|
|
||||||
val size
|
val size
|
||||||
get() = carpenterSchemas.size
|
get() = carpenterSchemas.size
|
||||||
|
|
||||||
|
fun isEmpty() = carpenterSchemas.isEmpty()
|
||||||
|
fun isNotEmpty() = carpenterSchemas.isNotEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -53,8 +56,7 @@ data class CarpenterSchemas (
|
|||||||
* @property cc a reference to the actual class carpenter we're using to constuct classes
|
* @property cc a reference to the actual class carpenter we're using to constuct classes
|
||||||
* @property objects a list of carpented classes loaded into the carpenters class loader
|
* @property objects a list of carpented classes loaded into the carpenters class loader
|
||||||
*/
|
*/
|
||||||
abstract class MetaCarpenterBase (val schemas : CarpenterSchemas) {
|
abstract class MetaCarpenterBase (val schemas : CarpenterSchemas, val cc : ClassCarpenter = ClassCarpenter()) {
|
||||||
private val cc = ClassCarpenter()
|
|
||||||
val objects = mutableMapOf<String, Class<*>>()
|
val objects = mutableMapOf<String, Class<*>>()
|
||||||
|
|
||||||
fun step(newObject: Schema) {
|
fun step(newObject: Schema) {
|
||||||
@ -80,9 +82,13 @@ 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,
|
||||||
|
cc : ClassCarpenter = ClassCarpenter()) : MetaCarpenterBase(schemas, cc) {
|
||||||
override fun build() {
|
override fun build() {
|
||||||
while (schemas.carpenterSchemas.isNotEmpty()) {
|
while (schemas.carpenterSchemas.isNotEmpty()) {
|
||||||
val newObject = schemas.carpenterSchemas.removeAt(0)
|
val newObject = schemas.carpenterSchemas.removeAt(0)
|
||||||
@ -91,7 +97,8 @@ class MetaCarpenter(schemas: CarpenterSchemas) : MetaCarpenterBase(schemas) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestMetaCarpenter(schemas: CarpenterSchemas) : MetaCarpenterBase(schemas) {
|
class TestMetaCarpenter(schemas : CarpenterSchemas,
|
||||||
|
cc : ClassCarpenter = ClassCarpenter()) : MetaCarpenterBase(schemas, cc) {
|
||||||
override fun build() {
|
override fun build() {
|
||||||
if (schemas.carpenterSchemas.isEmpty()) return
|
if (schemas.carpenterSchemas.isEmpty()) return
|
||||||
step (schemas.carpenterSchemas.removeAt(0))
|
step (schemas.carpenterSchemas.removeAt(0))
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
package net.corda.nodeapi.internal.serialization.amqp
|
||||||
|
|
||||||
|
import org.apache.qpid.proton.codec.Data
|
||||||
|
|
||||||
|
class TestSerializationOutput(
|
||||||
|
private val verbose: Boolean,
|
||||||
|
serializerFactory: SerializerFactory = SerializerFactory()) : SerializationOutput(serializerFactory) {
|
||||||
|
|
||||||
|
override fun writeSchema(schema: Schema, data: Data) {
|
||||||
|
if (verbose) println(schema)
|
||||||
|
super.writeSchema(schema, data)
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ import kotlin.test.assertTrue
|
|||||||
class DeserializeAndReturnEnvelopeTests {
|
class DeserializeAndReturnEnvelopeTests {
|
||||||
|
|
||||||
fun testName(): String = Thread.currentThread().stackTrace[2].methodName
|
fun testName(): String = Thread.currentThread().stackTrace[2].methodName
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
|
inline fun classTestName(clazz: String) = "${this.javaClass.name}\$${testName()}\$$clazz"
|
||||||
|
|
||||||
|
@ -0,0 +1,408 @@
|
|||||||
|
package net.corda.nodeapi.internal.serialization.amqp
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import kotlin.test.*
|
||||||
|
import net.corda.nodeapi.internal.serialization.carpenter.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 receiver doesn't have some or all elements of a schema present on it's classpath
|
||||||
|
*/
|
||||||
|
class DeserializeNeedingCarpentrySimpleTypesTest {
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* If you want to see the schema encoded into the envelope after serialisation change this to true
|
||||||
|
*/
|
||||||
|
private const val VERBOSE = false
|
||||||
|
}
|
||||||
|
|
||||||
|
val sf = SerializerFactory()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singleInt() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
|
||||||
|
"int" to NonNullableField(Integer::class.javaPrimitiveType!!)
|
||||||
|
)))
|
||||||
|
|
||||||
|
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(1))
|
||||||
|
val db = DeserializationInput(sf).deserialize(sb)
|
||||||
|
|
||||||
|
assertEquals(1, db::class.java.getMethod("getInt").invoke(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singleIntNullable() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
|
||||||
|
"int" to NullableField(Integer::class.java)
|
||||||
|
)))
|
||||||
|
|
||||||
|
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(1))
|
||||||
|
val db = DeserializationInput(sf).deserialize(sb)
|
||||||
|
|
||||||
|
assertEquals(1, db::class.java.getMethod("getInt").invoke(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singleIntNullableNull() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
|
||||||
|
"int" to NullableField(Integer::class.java)
|
||||||
|
)))
|
||||||
|
|
||||||
|
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null))
|
||||||
|
val db = DeserializationInput(sf).deserialize(sb)
|
||||||
|
|
||||||
|
assertEquals(null, db::class.java.getMethod("getInt").invoke(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singleChar() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
|
||||||
|
"char" to NonNullableField(Character::class.javaPrimitiveType!!)
|
||||||
|
)))
|
||||||
|
|
||||||
|
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance('a'))
|
||||||
|
val db = DeserializationInput(sf).deserialize(sb)
|
||||||
|
|
||||||
|
assertEquals('a', db::class.java.getMethod("getChar").invoke(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singleCharNullable() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
|
||||||
|
"char" to NullableField(Character::class.javaObjectType)
|
||||||
|
)))
|
||||||
|
|
||||||
|
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance('a'))
|
||||||
|
val db = DeserializationInput(sf).deserialize(sb)
|
||||||
|
|
||||||
|
assertEquals('a', db::class.java.getMethod("getChar").invoke(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singleCharNullableNull() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
|
||||||
|
"char" to NullableField(java.lang.Character::class.java)
|
||||||
|
)))
|
||||||
|
|
||||||
|
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null))
|
||||||
|
val db = DeserializationInput(sf).deserialize(sb)
|
||||||
|
|
||||||
|
assertEquals(null, db::class.java.getMethod("getChar").invoke(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singleLong() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
|
||||||
|
"long" to NonNullableField(Long::class.javaPrimitiveType!!)
|
||||||
|
)))
|
||||||
|
|
||||||
|
val l : Long = 1
|
||||||
|
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(l))
|
||||||
|
val db = DeserializationInput(sf).deserialize(sb)
|
||||||
|
|
||||||
|
assertEquals(l, (db::class.java.getMethod("getLong").invoke(db)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singleLongNullable() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
|
||||||
|
"long" to NullableField(Long::class.javaObjectType)
|
||||||
|
)))
|
||||||
|
|
||||||
|
val l : Long = 1
|
||||||
|
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(l))
|
||||||
|
val db = DeserializationInput(sf).deserialize(sb)
|
||||||
|
|
||||||
|
assertEquals(l, (db::class.java.getMethod("getLong").invoke(db)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singleLongNullableNull() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
|
||||||
|
"long" to NullableField(Long::class.javaObjectType)
|
||||||
|
)))
|
||||||
|
|
||||||
|
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null))
|
||||||
|
val db = DeserializationInput(sf).deserialize(sb)
|
||||||
|
|
||||||
|
assertEquals(null, (db::class.java.getMethod("getLong").invoke(db)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singleBoolean() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
|
||||||
|
"boolean" to NonNullableField(Boolean::class.javaPrimitiveType!!)
|
||||||
|
)))
|
||||||
|
|
||||||
|
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(true))
|
||||||
|
val db = DeserializationInput(sf).deserialize(sb)
|
||||||
|
|
||||||
|
assertEquals(true, db::class.java.getMethod("getBoolean").invoke(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singleBooleanNullable() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
|
||||||
|
"boolean" to NullableField(Boolean::class.javaObjectType)
|
||||||
|
)))
|
||||||
|
|
||||||
|
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(true))
|
||||||
|
val db = DeserializationInput(sf).deserialize(sb)
|
||||||
|
|
||||||
|
assertEquals(true, db::class.java.getMethod("getBoolean").invoke(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singleBooleanNullableNull() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
|
||||||
|
"boolean" to NullableField(Boolean::class.javaObjectType)
|
||||||
|
)))
|
||||||
|
|
||||||
|
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null))
|
||||||
|
val db = DeserializationInput(sf).deserialize(sb)
|
||||||
|
|
||||||
|
assertEquals(null, db::class.java.getMethod("getBoolean").invoke(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singleDouble() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
|
||||||
|
"double" to NonNullableField(Double::class.javaPrimitiveType!!)
|
||||||
|
)))
|
||||||
|
|
||||||
|
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(10.0))
|
||||||
|
val db = DeserializationInput(sf).deserialize(sb)
|
||||||
|
|
||||||
|
assertEquals(10.0, db::class.java.getMethod("getDouble").invoke(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singleDoubleNullable() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
|
||||||
|
"double" to NullableField(Double::class.javaObjectType)
|
||||||
|
)))
|
||||||
|
|
||||||
|
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(10.0))
|
||||||
|
val db = DeserializationInput(sf).deserialize(sb)
|
||||||
|
|
||||||
|
assertEquals(10.0, db::class.java.getMethod("getDouble").invoke(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singleDoubleNullableNull() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
|
||||||
|
"double" to NullableField(Double::class.javaObjectType)
|
||||||
|
)))
|
||||||
|
|
||||||
|
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null))
|
||||||
|
val db = DeserializationInput(sf).deserialize(sb)
|
||||||
|
|
||||||
|
assertEquals(null, db::class.java.getMethod("getDouble").invoke(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singleShort() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
|
||||||
|
"short" to NonNullableField(Short::class.javaPrimitiveType!!)
|
||||||
|
)))
|
||||||
|
|
||||||
|
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(3.toShort()))
|
||||||
|
val db = DeserializationInput(sf).deserialize(sb)
|
||||||
|
|
||||||
|
assertEquals(3.toShort(), db::class.java.getMethod("getShort").invoke(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singleShortNullable() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
|
||||||
|
"short" to NullableField(Short::class.javaObjectType)
|
||||||
|
)))
|
||||||
|
|
||||||
|
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(3.toShort()))
|
||||||
|
val db = DeserializationInput(sf).deserialize(sb)
|
||||||
|
|
||||||
|
assertEquals(3.toShort(), db::class.java.getMethod("getShort").invoke(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singleShortNullableNull() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
|
||||||
|
"short" to NullableField(Short::class.javaObjectType)
|
||||||
|
)))
|
||||||
|
|
||||||
|
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null))
|
||||||
|
val db = DeserializationInput(sf).deserialize(sb)
|
||||||
|
|
||||||
|
assertEquals(null, db::class.java.getMethod("getShort").invoke(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singleFloat() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
|
||||||
|
"float" to NonNullableField(Float::class.javaPrimitiveType!!)
|
||||||
|
)))
|
||||||
|
|
||||||
|
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(10.0F))
|
||||||
|
val db = DeserializationInput(sf).deserialize(sb)
|
||||||
|
|
||||||
|
assertEquals(10.0F, db::class.java.getMethod("getFloat").invoke(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singleFloatNullable() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
|
||||||
|
"float" to NullableField(Float::class.javaObjectType)
|
||||||
|
)))
|
||||||
|
|
||||||
|
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(10.0F))
|
||||||
|
val db = DeserializationInput(sf).deserialize(sb)
|
||||||
|
|
||||||
|
assertEquals(10.0F, db::class.java.getMethod("getFloat").invoke(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singleFloatNullableNull() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
|
||||||
|
"float" to NullableField(Float::class.javaObjectType)
|
||||||
|
)))
|
||||||
|
|
||||||
|
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null))
|
||||||
|
val db = DeserializationInput(sf).deserialize(sb)
|
||||||
|
|
||||||
|
assertEquals(null, db::class.java.getMethod("getFloat").invoke(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singleByte() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
|
||||||
|
"byte" to NonNullableField(Byte::class.javaPrimitiveType!!)
|
||||||
|
)))
|
||||||
|
|
||||||
|
val b : Byte = 0b0101
|
||||||
|
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(b))
|
||||||
|
val db = DeserializationInput(sf).deserialize(sb)
|
||||||
|
|
||||||
|
assertEquals(b, db::class.java.getMethod("getByte").invoke(db))
|
||||||
|
assertEquals(0b0101, (db::class.java.getMethod("getByte").invoke(db) as Byte))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singleByteNullable() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
|
||||||
|
"byte" to NullableField(Byte::class.javaObjectType)
|
||||||
|
)))
|
||||||
|
|
||||||
|
val b : Byte = 0b0101
|
||||||
|
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(b))
|
||||||
|
val db = DeserializationInput(sf).deserialize(sb)
|
||||||
|
|
||||||
|
assertEquals(b, db::class.java.getMethod("getByte").invoke(db))
|
||||||
|
assertEquals(0b0101, (db::class.java.getMethod("getByte").invoke(db) as Byte))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun singleByteNullableNull() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("single", mapOf(
|
||||||
|
"byte" to NullableField(Byte::class.javaObjectType)
|
||||||
|
)))
|
||||||
|
|
||||||
|
val sb = TestSerializationOutput(VERBOSE, sf).serialize(clazz.constructors.first().newInstance(null))
|
||||||
|
val db = DeserializationInput(sf).deserialize(sb)
|
||||||
|
|
||||||
|
assertEquals(null, db::class.java.getMethod("getByte").invoke(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun simpleTypeKnownInterface() {
|
||||||
|
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 = TestSerializationOutput(VERBOSE, sf).serialize(classInstance)
|
||||||
|
val deserializedObj = DeserializationInput(sf).deserialize(serialisedBytes)
|
||||||
|
|
||||||
|
assertTrue(deserializedObj is I)
|
||||||
|
assertEquals(testVal, (deserializedObj as I).getName())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun manyTypes() {
|
||||||
|
val manyClass = ClassCarpenter().build (ClassSchema("many", mapOf(
|
||||||
|
"intA" to NonNullableField (Int::class.java),
|
||||||
|
"intB" to NullableField (Integer::class.java),
|
||||||
|
"intC" to NullableField (Integer::class.java),
|
||||||
|
"strA" to NonNullableField (String::class.java),
|
||||||
|
"strB" to NullableField (String::class.java),
|
||||||
|
"strC" to NullableField (String::class.java),
|
||||||
|
"charA" to NonNullableField (Char::class.java),
|
||||||
|
"charB" to NullableField (Character::class.javaObjectType),
|
||||||
|
"charC" to NullableField (Character::class.javaObjectType),
|
||||||
|
"shortA" to NonNullableField (Short::class.javaPrimitiveType!!),
|
||||||
|
"shortB" to NullableField (Short::class.javaObjectType),
|
||||||
|
"shortC" to NullableField (Short::class.javaObjectType),
|
||||||
|
"longA" to NonNullableField (Long::class.javaPrimitiveType!!),
|
||||||
|
"longB" to NullableField(Long::class.javaObjectType),
|
||||||
|
"longC" to NullableField(Long::class.javaObjectType),
|
||||||
|
"booleanA" to NonNullableField (Boolean::class.javaPrimitiveType!!),
|
||||||
|
"booleanB" to NullableField (Boolean::class.javaObjectType),
|
||||||
|
"booleanC" to NullableField (Boolean::class.javaObjectType),
|
||||||
|
"doubleA" to NonNullableField (Double::class.javaPrimitiveType!!),
|
||||||
|
"doubleB" to NullableField (Double::class.javaObjectType),
|
||||||
|
"doubleC" to NullableField (Double::class.javaObjectType),
|
||||||
|
"floatA" to NonNullableField (Float::class.javaPrimitiveType!!),
|
||||||
|
"floatB" to NullableField (Float::class.javaObjectType),
|
||||||
|
"floatC" to NullableField (Float::class.javaObjectType),
|
||||||
|
"byteA" to NonNullableField (Byte::class.javaPrimitiveType!!),
|
||||||
|
"byteB" to NullableField (Byte::class.javaObjectType),
|
||||||
|
"byteC" to NullableField (Byte::class.javaObjectType))))
|
||||||
|
|
||||||
|
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(
|
||||||
|
manyClass.constructors.first().newInstance(
|
||||||
|
1, 2, null,
|
||||||
|
"a", "b", null,
|
||||||
|
'c', 'd', null,
|
||||||
|
3.toShort(), 4.toShort(), null,
|
||||||
|
100.toLong(), 200.toLong(), null,
|
||||||
|
true, false, null,
|
||||||
|
10.0, 20.0, null,
|
||||||
|
10.0F, 20.0F, null,
|
||||||
|
0b0101.toByte(), 0b1010.toByte(), null))
|
||||||
|
|
||||||
|
val deserializedObj = DeserializationInput(sf).deserialize(serialisedBytes)
|
||||||
|
|
||||||
|
assertEquals(1, deserializedObj::class.java.getMethod("getIntA").invoke(deserializedObj))
|
||||||
|
assertEquals(2, deserializedObj::class.java.getMethod("getIntB").invoke(deserializedObj))
|
||||||
|
assertEquals(null, deserializedObj::class.java.getMethod("getIntC").invoke(deserializedObj))
|
||||||
|
assertEquals("a", deserializedObj::class.java.getMethod("getStrA").invoke(deserializedObj))
|
||||||
|
assertEquals("b", deserializedObj::class.java.getMethod("getStrB").invoke(deserializedObj))
|
||||||
|
assertEquals(null, deserializedObj::class.java.getMethod("getStrC").invoke(deserializedObj))
|
||||||
|
assertEquals('c', deserializedObj::class.java.getMethod("getCharA").invoke(deserializedObj))
|
||||||
|
assertEquals('d', deserializedObj::class.java.getMethod("getCharB").invoke(deserializedObj))
|
||||||
|
assertEquals(null, deserializedObj::class.java.getMethod("getCharC").invoke(deserializedObj))
|
||||||
|
assertEquals(3.toShort(), deserializedObj::class.java.getMethod("getShortA").invoke(deserializedObj))
|
||||||
|
assertEquals(4.toShort(), deserializedObj::class.java.getMethod("getShortB").invoke(deserializedObj))
|
||||||
|
assertEquals(null, deserializedObj::class.java.getMethod("getShortC").invoke(deserializedObj))
|
||||||
|
assertEquals(100.toLong(), deserializedObj::class.java.getMethod("getLongA").invoke(deserializedObj))
|
||||||
|
assertEquals(200.toLong(), deserializedObj::class.java.getMethod("getLongB").invoke(deserializedObj))
|
||||||
|
assertEquals(null, deserializedObj::class.java.getMethod("getLongC").invoke(deserializedObj))
|
||||||
|
assertEquals(true, deserializedObj::class.java.getMethod("getBooleanA").invoke(deserializedObj))
|
||||||
|
assertEquals(false, deserializedObj::class.java.getMethod("getBooleanB").invoke(deserializedObj))
|
||||||
|
assertEquals(null, deserializedObj::class.java.getMethod("getBooleanC").invoke(deserializedObj))
|
||||||
|
assertEquals(10.0, deserializedObj::class.java.getMethod("getDoubleA").invoke(deserializedObj))
|
||||||
|
assertEquals(20.0, deserializedObj::class.java.getMethod("getDoubleB").invoke(deserializedObj))
|
||||||
|
assertEquals(null, deserializedObj::class.java.getMethod("getDoubleC").invoke(deserializedObj))
|
||||||
|
assertEquals(10.0F, deserializedObj::class.java.getMethod("getFloatA").invoke(deserializedObj))
|
||||||
|
assertEquals(20.0F, deserializedObj::class.java.getMethod("getFloatB").invoke(deserializedObj))
|
||||||
|
assertEquals(null, deserializedObj::class.java.getMethod("getFloatC").invoke(deserializedObj))
|
||||||
|
assertEquals(0b0101.toByte(), deserializedObj::class.java.getMethod("getByteA").invoke(deserializedObj))
|
||||||
|
assertEquals(0b1010.toByte(), deserializedObj::class.java.getMethod("getByteB").invoke(deserializedObj))
|
||||||
|
assertEquals(null, deserializedObj::class.java.getMethod("getByteC").invoke(deserializedObj))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,220 @@
|
|||||||
|
package net.corda.nodeapi.internal.serialization.amqp
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import kotlin.test.*
|
||||||
|
import net.corda.nodeapi.internal.serialization.carpenter.*
|
||||||
|
|
||||||
|
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 receiver doesn't have some or all elements of a schema present on it's classpath
|
||||||
|
*/
|
||||||
|
class DeserializeNeedingCarpentryTests {
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* If you want to see the schema encoded into the envelope after serialisation change this to true
|
||||||
|
*/
|
||||||
|
private const val VERBOSE = false
|
||||||
|
}
|
||||||
|
|
||||||
|
val sf = SerializerFactory()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun verySimpleType() {
|
||||||
|
val testVal = 10
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("oneType", mapOf("a" to NonNullableField(Int::class.java))))
|
||||||
|
val classInstance = clazz.constructors[0].newInstance(testVal)
|
||||||
|
|
||||||
|
val serialisedBytes = TestSerializationOutput(VERBOSE, sf).serialize(classInstance)
|
||||||
|
val deserializedObj = DeserializationInput(sf).deserialize(serialisedBytes)
|
||||||
|
|
||||||
|
assertNotEquals(clazz::class.java, deserializedObj::class.java)
|
||||||
|
assertEquals (testVal, deserializedObj::class.java.getMethod("getA").invoke(deserializedObj))
|
||||||
|
|
||||||
|
val deserializedObj2 = DeserializationInput(sf).deserialize(serialisedBytes)
|
||||||
|
|
||||||
|
assertEquals(deserializedObj::class.java, deserializedObj2::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun repeatedTypesAreRecognised() {
|
||||||
|
val testValA = 10
|
||||||
|
val testValB = 20
|
||||||
|
val testValC = 20
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("oneType", mapOf("a" to NonNullableField(Int::class.java))))
|
||||||
|
val concreteA = clazz.constructors[0].newInstance(testValA)
|
||||||
|
val concreteB = clazz.constructors[0].newInstance(testValB)
|
||||||
|
val concreteC = clazz.constructors[0].newInstance(testValC)
|
||||||
|
|
||||||
|
val deserialisedA = DeserializationInput(sf).deserialize(TestSerializationOutput(VERBOSE, sf).serialize(concreteA))
|
||||||
|
|
||||||
|
assertEquals (testValA, deserialisedA::class.java.getMethod("getA").invoke(deserialisedA))
|
||||||
|
|
||||||
|
val deserialisedB = DeserializationInput(sf).deserialize(TestSerializationOutput(VERBOSE, sf).serialize(concreteB))
|
||||||
|
|
||||||
|
assertEquals (testValB, deserialisedA::class.java.getMethod("getA").invoke(deserialisedB))
|
||||||
|
assertEquals (deserialisedA::class.java, deserialisedB::class.java)
|
||||||
|
|
||||||
|
// C is deseriliased with a different factory, meaning a different class carpenter, so the type
|
||||||
|
// won't already exist and it will be carpented a second time showing that when A and B are the
|
||||||
|
// same underlying class that we didn't create a second instance of the class with the
|
||||||
|
// second deserialisation
|
||||||
|
val lsf = SerializerFactory()
|
||||||
|
val deserialisedC = DeserializationInput(lsf).deserialize(TestSerializationOutput(VERBOSE, lsf).serialize(concreteC))
|
||||||
|
assertEquals (testValC, deserialisedC::class.java.getMethod("getA").invoke(deserialisedC))
|
||||||
|
assertNotEquals (deserialisedA::class.java, deserialisedC::class.java)
|
||||||
|
assertNotEquals (deserialisedB::class.java, deserialisedC::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun simpleTypeKnownInterface() {
|
||||||
|
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 = TestSerializationOutput(VERBOSE, sf).serialize(classInstance)
|
||||||
|
val deserializedObj = DeserializationInput(sf).deserialize(serialisedBytes)
|
||||||
|
|
||||||
|
assertTrue(deserializedObj is I)
|
||||||
|
assertEquals(testVal, (deserializedObj as I).getName())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun arrayOfTypes() {
|
||||||
|
val clazz = ClassCarpenter().build(ClassSchema("oneType", mapOf("a" to NonNullableField(Int::class.java))))
|
||||||
|
|
||||||
|
data class Outer (val a : Array<Any>)
|
||||||
|
|
||||||
|
val outer = Outer (arrayOf (
|
||||||
|
clazz.constructors[0].newInstance(1),
|
||||||
|
clazz.constructors[0].newInstance(2),
|
||||||
|
clazz.constructors[0].newInstance(3)))
|
||||||
|
|
||||||
|
val deserializedObj = DeserializationInput(sf).deserialize(TestSerializationOutput(VERBOSE, sf).serialize(outer))
|
||||||
|
|
||||||
|
assertNotEquals((deserializedObj.a[0])::class.java, (outer.a[0])::class.java)
|
||||||
|
assertNotEquals((deserializedObj.a[1])::class.java, (outer.a[1])::class.java)
|
||||||
|
assertNotEquals((deserializedObj.a[2])::class.java, (outer.a[2])::class.java)
|
||||||
|
|
||||||
|
assertEquals((deserializedObj.a[0])::class.java, (deserializedObj.a[1])::class.java)
|
||||||
|
assertEquals((deserializedObj.a[0])::class.java, (deserializedObj.a[2])::class.java)
|
||||||
|
assertEquals((deserializedObj.a[1])::class.java, (deserializedObj.a[2])::class.java)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
outer.a[0]::class.java.getMethod("getA").invoke(outer.a[0]),
|
||||||
|
deserializedObj.a[0]::class.java.getMethod("getA").invoke(deserializedObj.a[0]))
|
||||||
|
assertEquals(
|
||||||
|
outer.a[1]::class.java.getMethod("getA").invoke(outer.a[1]),
|
||||||
|
deserializedObj.a[1]::class.java.getMethod("getA").invoke(deserializedObj.a[1]))
|
||||||
|
assertEquals(
|
||||||
|
outer.a[2]::class.java.getMethod("getA").invoke(outer.a[2]),
|
||||||
|
deserializedObj.a[2]::class.java.getMethod("getA").invoke(deserializedObj.a[2]))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun reusedClasses() {
|
||||||
|
val cc = ClassCarpenter()
|
||||||
|
|
||||||
|
val innerType = cc.build(ClassSchema("inner", mapOf("a" to NonNullableField(Int::class.java))))
|
||||||
|
val outerType = cc.build(ClassSchema("outer", mapOf("a" to NonNullableField(innerType))))
|
||||||
|
val inner = innerType.constructors[0].newInstance(1)
|
||||||
|
val outer = outerType.constructors[0].newInstance(innerType.constructors[0].newInstance(2))
|
||||||
|
|
||||||
|
val serializedI = TestSerializationOutput(VERBOSE, sf).serialize(inner)
|
||||||
|
val deserialisedI = DeserializationInput(sf).deserialize(serializedI)
|
||||||
|
val serialisedO = TestSerializationOutput(VERBOSE, sf).serialize(outer)
|
||||||
|
val deserialisedO = DeserializationInput(sf).deserialize(serialisedO)
|
||||||
|
|
||||||
|
// ensure out carpented version of inner is reused
|
||||||
|
assertEquals (deserialisedI::class.java,
|
||||||
|
(deserialisedO::class.java.getMethod("getA").invoke(deserialisedO))::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 = TestSerializationOutput(VERBOSE, sf).serialize(classInstance)
|
||||||
|
val deserializedObj = DeserializationInput(sf).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 = TestSerializationOutput(VERBOSE, sf).serialize(classInstance)
|
||||||
|
val deserializedObj = DeserializationInput(sf).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 unknownClass = ClassCarpenter().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 = TestSerializationOutput(VERBOSE, sf).serialize(toSerialise)
|
||||||
|
val deserializedObj = DeserializationInput(sf).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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 = TestSerializationOutput(VERBOSE, sf).serialize(
|
||||||
|
concreteClass.constructors.first().newInstance(12, "timmy"))
|
||||||
|
val deserializedObj = DeserializationInput(sf).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))
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
package net.corda.nodeapi.internal.serialization.amqp
|
package net.corda.nodeapi.internal.serialization.amqp
|
||||||
|
|
||||||
import org.apache.qpid.proton.codec.Data
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
@ -9,15 +8,6 @@ import kotlin.test.assertEquals
|
|||||||
// char property of the class would've been treated as an Integer and given to the constructor
|
// char property of the class would've been treated as an Integer and given to the constructor
|
||||||
// as such
|
// as such
|
||||||
class DeserializeSimpleTypesTests {
|
class DeserializeSimpleTypesTests {
|
||||||
class TestSerializationOutput(
|
|
||||||
private val verbose: Boolean,
|
|
||||||
serializerFactory: SerializerFactory = SerializerFactory()) : SerializationOutput(serializerFactory) {
|
|
||||||
|
|
||||||
override fun writeSchema(schema: Schema, data: Data) {
|
|
||||||
if (verbose) println(schema)
|
|
||||||
super.writeSchema(schema, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
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.TypeNotation
|
||||||
|
import net.corda.nodeapi.internal.serialization.amqp.CompositeType
|
||||||
|
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"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user