From e4f38d19451a415fef31d2cbd92512c8062514f4 Mon Sep 17 00:00:00 2001 From: Chris Rankin Date: Mon, 12 Aug 2019 18:12:58 +0100 Subject: [PATCH] Ensure that described properties are associated with a descriptor. --- serialization-deterministic/build.gradle | 4 +-- .../internal/amqp/ArraySerializer.kt | 4 ++- .../amqp/ComposableTypePropertySerializer.kt | 10 +++---- .../internal/amqp/CustomSerializer.kt | 4 +++ .../serialization/internal/amqp/Schema.kt | 28 ++++++++++++++++--- .../internal/model/TypeIdentifier.kt | 5 +++- 6 files changed, 42 insertions(+), 13 deletions(-) diff --git a/serialization-deterministic/build.gradle b/serialization-deterministic/build.gradle index 497745157b..229b2e179a 100644 --- a/serialization-deterministic/build.gradle +++ b/serialization-deterministic/build.gradle @@ -22,12 +22,12 @@ dependencies { // Configure these by hand. It should be a minimal subset of dependencies, // and without any obviously non-deterministic ones such as Hibernate. - // This dependency will become "compile" scoped in our published POM. + // These dependencies will become "compile" scoped in our published POM. // See publish.dependenciesFrom.defaultScope. deterministicLibraries project(path: ':core-deterministic', configuration: 'deterministicArtifacts') + deterministicLibraries "org.apache.qpid:proton-j:$protonj_version" // These "implementation" dependencies will become "runtime" scoped in our published POM. - implementation "org.apache.qpid:proton-j:$protonj_version" implementation "org.iq80.snappy:snappy:$snappy_version" implementation "com.google.guava:guava:$guava_version" } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ArraySerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ArraySerializer.kt index 3e2266f63a..63b6ce73b7 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ArraySerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ArraySerializer.kt @@ -88,7 +88,9 @@ open class ArraySerializer(override val type: Type, factory: LocalSerializerFact context: SerializationContext ): Any { if (obj is List<*>) { - return obj.map { input.readObjectOrNull(it, schemas, elementType, context) }.toArrayOfType(elementType) + return obj.map { + input.readObjectOrNull(redescribe(it, elementType), schemas, elementType, context) + }.toArrayOfType(elementType) } else throw AMQPNotSerializableException(type, "Expected a List but found $obj") } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ComposableTypePropertySerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ComposableTypePropertySerializer.kt index aa9764fd1e..f384dfbabd 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ComposableTypePropertySerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/ComposableTypePropertySerializer.kt @@ -1,6 +1,7 @@ package net.corda.serialization.internal.amqp import net.corda.core.serialization.SerializationContext +import net.corda.serialization.internal.amqp.AMQPTypeIdentifiers.isPrimitive import net.corda.serialization.internal.model.* import org.apache.qpid.proton.amqp.Binary import org.apache.qpid.proton.codec.Data @@ -18,7 +19,7 @@ interface PropertyReadStrategy { * Select the correct strategy for reading properties, based on the property type. */ fun make(name: String, typeIdentifier: TypeIdentifier, type: Type): PropertyReadStrategy = - if (AMQPTypeIdentifiers.isPrimitive(typeIdentifier)) { + if (isPrimitive(typeIdentifier)) { when (typeIdentifier) { in characterTypes -> AMQPCharPropertyReadStrategy else -> AMQPPropertyReadStrategy @@ -47,7 +48,7 @@ interface PropertyWriteStrategy { fun make(name: String, propertyInformation: LocalPropertyInformation, factory: LocalSerializerFactory): PropertyWriteStrategy { val reader = PropertyReader.make(propertyInformation) val type = propertyInformation.type - return if (AMQPTypeIdentifiers.isPrimitive(type.typeIdentifier)) { + return if (isPrimitive(type.typeIdentifier)) { when (type.typeIdentifier) { in characterTypes -> AMQPCharPropertyWriteStategy(reader) else -> AMQPPropertyWriteStrategy(reader) @@ -191,15 +192,14 @@ object EvolutionPropertyWriteStrategy : PropertyWriteStrategy { * Read a type that comes with its own [TypeDescriptor], by calling back into [RemoteSerializerFactory] to obtain a suitable * serializer for that descriptor. */ -class DescribedTypeReadStrategy(name: String, - typeIdentifier: TypeIdentifier, +class DescribedTypeReadStrategy(name: String, typeIdentifier: TypeIdentifier, private val type: Type): PropertyReadStrategy { private val nameForDebug = "$name(${typeIdentifier.prettyPrint(false)})" override fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext): Any? = ifThrowsAppend({ nameForDebug }) { - input.readObjectOrNull(obj, schemas, type, context) + input.readObjectOrNull(redescribe(obj, type), schemas, type, context) } } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/CustomSerializer.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/CustomSerializer.kt index ef9134e9e4..d12495e4fd 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/CustomSerializer.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/CustomSerializer.kt @@ -123,11 +123,13 @@ abstract class CustomSerializer : AMQPSerializer, SerializerFor { /** * Additional base features for a custom serializer for a particular class, that excludes subclasses. */ + @KeepForDJVM abstract class Is(clazz: Class) : CustomSerializerImp(clazz, false) /** * Additional base features for a custom serializer for all implementations of a particular interface or super class. */ + @KeepForDJVM abstract class Implements(clazz: Class) : CustomSerializerImp(clazz, true) /** @@ -137,6 +139,7 @@ abstract class CustomSerializer : AMQPSerializer, SerializerFor { * The proxy class must use only types which are either native AMQP or other types for which there are pre-registered * custom serializers. */ + @KeepForDJVM abstract class Proxy(clazz: Class, protected val proxyClass: Class

, protected val factory: LocalSerializerFactory, @@ -195,6 +198,7 @@ abstract class CustomSerializer : AMQPSerializer, SerializerFor { * @param maker A lambda for constructing an instance, that defaults to calling a constructor that expects a string. * @param unmaker A lambda that extracts the string value for an instance, that defaults to the [toString] method. */ + @KeepForDJVM abstract class ToString(clazz: Class, withInheritance: Boolean = false, private val maker: (String) -> T = clazz.getConstructor(String::class.java).let { `constructor` -> { string -> `constructor`.newInstance(string) } diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/Schema.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/Schema.kt index 81584378a6..91d0537e71 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/Schema.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/Schema.kt @@ -3,10 +3,10 @@ package net.corda.serialization.internal.amqp import net.corda.core.KeepForDJVM import net.corda.core.internal.uncheckedCast import net.corda.serialization.internal.CordaSerializationMagic -import org.apache.qpid.proton.amqp.DescribedType -import org.apache.qpid.proton.amqp.Symbol -import org.apache.qpid.proton.amqp.UnsignedInteger -import org.apache.qpid.proton.amqp.UnsignedLong +import net.corda.serialization.internal.amqp.AMQPTypeIdentifiers.isPrimitive +import net.corda.serialization.internal.model.TypeIdentifier.TopType +import net.corda.serialization.internal.model.TypeIdentifier.Companion.forGenericType +import org.apache.qpid.proton.amqp.* import org.apache.qpid.proton.codec.DescribedTypeConstructor import java.io.NotSerializableException import java.lang.reflect.Type @@ -16,6 +16,26 @@ val amqpMagic = CordaSerializationMagic("corda".toByteArray() + byteArrayOf(1, 0 fun typeDescriptorFor(type: Type): Symbol = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${AMQPTypeIdentifiers.nameForType(type)}") +fun redescribe(obj: Any?, type: Type): Any? { + return if (obj == null || obj is DescribedType || obj is Binary || forGenericType(type).run { isPrimitive(this) || this == TopType }) { + obj + } else { + /** + * This must be a primitive [obj] that has a non-primitive [type]. + * Rewrap it with the required descriptor for further deserialization. + */ + RedescribedType(typeDescriptorFor(type), obj) + } +} + +private class RedescribedType( + private val descriptor: Symbol, + private val described: Any? +) : DescribedType { + override fun getDescriptor(): Symbol = descriptor + override fun getDescribed(): Any? = described +} + /** * This and the classes below are OO representations of the AMQP XML schema described in the specification. Their * [toString] representations generate the associated XML form. diff --git a/serialization/src/main/kotlin/net/corda/serialization/internal/model/TypeIdentifier.kt b/serialization/src/main/kotlin/net/corda/serialization/internal/model/TypeIdentifier.kt index 43f0329ab9..6e1dd4953f 100644 --- a/serialization/src/main/kotlin/net/corda/serialization/internal/model/TypeIdentifier.kt +++ b/serialization/src/main/kotlin/net/corda/serialization/internal/model/TypeIdentifier.kt @@ -206,7 +206,10 @@ sealed class TypeIdentifier { override fun toString() = "Parameterised(${prettyPrint()})" override fun getLocalType(classLoader: ClassLoader): Type { - val rawType = Class.forName(name, false, classLoader) + // We need to invoke ClassLoader.loadClass() directly, because + // the JVM will complain if Class.forName() returns a class + // that has a name other than the requested one. + val rawType = classLoader.loadClass(name) if (rawType.typeParameters.size != parameters.size) { throw IncompatibleTypeIdentifierException( "Class $rawType expects ${rawType.typeParameters.size} type arguments, " +