Ensure that described properties are associated with a descriptor.

This commit is contained in:
Chris Rankin 2019-08-12 18:12:58 +01:00
parent 8f0c7c947a
commit e4f38d1945
6 changed files with 42 additions and 13 deletions
serialization-deterministic
serialization/src/main/kotlin/net/corda/serialization/internal

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

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

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

@ -123,11 +123,13 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T>, SerializerFor {
/**
* Additional base features for a custom serializer for a particular class, that excludes subclasses.
*/
@KeepForDJVM
abstract class Is<T : Any>(clazz: Class<T>) : CustomSerializerImp<T>(clazz, false)
/**
* Additional base features for a custom serializer for all implementations of a particular interface or super class.
*/
@KeepForDJVM
abstract class Implements<T : Any>(clazz: Class<T>) : CustomSerializerImp<T>(clazz, true)
/**
@ -137,6 +139,7 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T>, 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<T : Any, P : Any>(clazz: Class<T>,
protected val proxyClass: Class<P>,
protected val factory: LocalSerializerFactory,
@ -195,6 +198,7 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T>, 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<T : Any>(clazz: Class<T>, withInheritance: Boolean = false,
private val maker: (String) -> T = clazz.getConstructor(String::class.java).let { `constructor` ->
{ string -> `constructor`.newInstance(string) }

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

@ -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, " +