Change descriptor name from string to symbol on the wire to adhere to section 1.5 of the AMQP 1.0 spec (#1423)

This commit is contained in:
Rick Parker 2017-09-07 17:05:39 +01:00 committed by GitHub
parent 879b1a6393
commit 6bf2871819
30 changed files with 36 additions and 29 deletions

View File

@ -27,7 +27,7 @@ fun SerializerFactory.addToWhitelist(vararg types: Class<*>) {
abstract class AbstractAMQPSerializationScheme : SerializationScheme { abstract class AbstractAMQPSerializationScheme : SerializationScheme {
internal companion object { internal companion object {
private val pluginRegistries: List<CordaPluginRegistry> by lazy { private val pluginRegistries: List<CordaPluginRegistry> by lazy {
ServiceLoader.load(CordaPluginRegistry::class.java, this.javaClass.classLoader).toList() ServiceLoader.load(CordaPluginRegistry::class.java, this::class.java.classLoader).toList()
} }
fun registerCustomSerializers(factory: SerializerFactory) { fun registerCustomSerializers(factory: SerializerFactory) {

View File

@ -1,6 +1,7 @@
package net.corda.nodeapi.internal.serialization.amqp package net.corda.nodeapi.internal.serialization.amqp
import org.apache.qpid.proton.amqp.Binary import org.apache.qpid.proton.amqp.Binary
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data import org.apache.qpid.proton.codec.Data
import java.lang.reflect.Type import java.lang.reflect.Type
@ -10,7 +11,7 @@ import java.lang.reflect.Type
* [ByteArray] is automatically marshalled to/from the Proton-J wrapper, [Binary]. * [ByteArray] is automatically marshalled to/from the Proton-J wrapper, [Binary].
*/ */
class AMQPPrimitiveSerializer(clazz: Class<*>) : AMQPSerializer<Any> { class AMQPPrimitiveSerializer(clazz: Class<*>) : AMQPSerializer<Any> {
override val typeDescriptor: String = SerializerFactory.primitiveTypeName(clazz)!! override val typeDescriptor = Symbol.valueOf(SerializerFactory.primitiveTypeName(clazz)!!)
override val type: Type = clazz override val type: Type = clazz
// NOOP since this is a primitive type. // NOOP since this is a primitive type.

View File

@ -1,5 +1,6 @@
package net.corda.nodeapi.internal.serialization.amqp package net.corda.nodeapi.internal.serialization.amqp
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data import org.apache.qpid.proton.codec.Data
import java.lang.reflect.Type import java.lang.reflect.Type
@ -18,7 +19,7 @@ interface AMQPSerializer<out T> {
* *
* This should be unique enough that we can use one global cache of [AMQPSerializer]s and use this as the look up key. * This should be unique enough that we can use one global cache of [AMQPSerializer]s and use this as the look up key.
*/ */
val typeDescriptor: String val typeDescriptor: Symbol
/** /**
* Add anything required to the AMQP schema via [SerializationOutput.writeTypeNotations] and any dependent serializers * Add anything required to the AMQP schema via [SerializationOutput.writeTypeNotations] and any dependent serializers

View File

@ -1,5 +1,6 @@
package net.corda.nodeapi.internal.serialization.amqp package net.corda.nodeapi.internal.serialization.amqp
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data import org.apache.qpid.proton.codec.Data
import java.io.NotSerializableException import java.io.NotSerializableException
import java.lang.reflect.Type import java.lang.reflect.Type
@ -31,12 +32,12 @@ open class ArraySerializer(override val type: Type, factory: SerializerFactory)
"${type.componentType().typeName}$arrayType" "${type.componentType().typeName}$arrayType"
} }
override val typeDescriptor by lazy { "$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}" } override val typeDescriptor by lazy { Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}") }
internal val elementType: Type by lazy { type.componentType() } internal val elementType: Type by lazy { type.componentType() }
internal open val typeName by lazy { calcTypeName(type) } internal open val typeName by lazy { calcTypeName(type) }
internal val typeNotation: TypeNotation by lazy { internal val typeNotation: TypeNotation by lazy {
RestrictedType(typeName, null, emptyList(), "list", Descriptor(typeDescriptor, null), emptyList()) RestrictedType(typeName, null, emptyList(), "list", Descriptor(typeDescriptor), emptyList())
} }
override fun writeClassInfo(output: SerializationOutput) { override fun writeClassInfo(output: SerializationOutput) {

View File

@ -1,6 +1,7 @@
package net.corda.nodeapi.internal.serialization.amqp package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.NonEmptySet
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data import org.apache.qpid.proton.codec.Data
import java.io.NotSerializableException import java.io.NotSerializableException
import java.lang.reflect.ParameterizedType import java.lang.reflect.ParameterizedType
@ -15,7 +16,7 @@ import kotlin.collections.Set
*/ */
class CollectionSerializer(val declaredType: ParameterizedType, factory: SerializerFactory) : AMQPSerializer<Any> { class CollectionSerializer(val declaredType: ParameterizedType, factory: SerializerFactory) : AMQPSerializer<Any> {
override val type: Type = declaredType as? DeserializedParameterizedType ?: DeserializedParameterizedType.make(SerializerFactory.nameForType(declaredType)) override val type: Type = declaredType as? DeserializedParameterizedType ?: DeserializedParameterizedType.make(SerializerFactory.nameForType(declaredType))
override val typeDescriptor = "$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}" override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}")
companion object { companion object {
// NB: Order matters in this map, the most specific classes should be listed at the end // NB: Order matters in this map, the most specific classes should be listed at the end
@ -58,7 +59,7 @@ class CollectionSerializer(val declaredType: ParameterizedType, factory: Seriali
private val concreteBuilder: (List<*>) -> Collection<*> = findConcreteType(declaredType.rawType as Class<*>) private val concreteBuilder: (List<*>) -> Collection<*> = findConcreteType(declaredType.rawType as Class<*>)
private val typeNotation: TypeNotation = RestrictedType(SerializerFactory.nameForType(declaredType), null, emptyList(), "list", Descriptor(typeDescriptor, null), emptyList()) private val typeNotation: TypeNotation = RestrictedType(SerializerFactory.nameForType(declaredType), null, emptyList(), "list", Descriptor(typeDescriptor), emptyList())
override fun writeClassInfo(output: SerializationOutput) { override fun writeClassInfo(output: SerializationOutput) {
if (output.writeTypeNotations(typeNotation)) { if (output.writeTypeNotations(typeNotation)) {

View File

@ -1,6 +1,7 @@
package net.corda.nodeapi.internal.serialization.amqp package net.corda.nodeapi.internal.serialization.amqp
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.nameForType import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.nameForType
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data import org.apache.qpid.proton.codec.Data
import java.lang.reflect.Type import java.lang.reflect.Type
@ -49,8 +50,8 @@ abstract class CustomSerializer<T> : AMQPSerializer<T> {
override fun isSerializerFor(clazz: Class<*>): Boolean = clazz == this.clazz override fun isSerializerFor(clazz: Class<*>): Boolean = clazz == this.clazz
override val type: Type get() = clazz override val type: Type get() = clazz
override val typeDescriptor: String = "$DESCRIPTOR_DOMAIN:${fingerprintForDescriptors(superClassSerializer.typeDescriptor, nameForType(clazz))}" override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForDescriptors(superClassSerializer.typeDescriptor.toString(), nameForType(clazz))}")
private val typeNotation: TypeNotation = RestrictedType(SerializerFactory.nameForType(clazz), null, emptyList(), SerializerFactory.nameForType(superClassSerializer.type), Descriptor(typeDescriptor, null), emptyList()) private val typeNotation: TypeNotation = RestrictedType(SerializerFactory.nameForType(clazz), null, emptyList(), SerializerFactory.nameForType(superClassSerializer.type), Descriptor(typeDescriptor), emptyList())
override fun writeClassInfo(output: SerializationOutput) { override fun writeClassInfo(output: SerializationOutput) {
output.writeTypeNotations(typeNotation) output.writeTypeNotations(typeNotation)
} }
@ -72,7 +73,7 @@ abstract class CustomSerializer<T> : AMQPSerializer<T> {
*/ */
abstract class CustomSerializerImp<T>(protected val clazz: Class<T>, protected val withInheritance: Boolean) : CustomSerializer<T>() { abstract class CustomSerializerImp<T>(protected val clazz: Class<T>, protected val withInheritance: Boolean) : CustomSerializer<T>() {
override val type: Type get() = clazz override val type: Type get() = clazz
override val typeDescriptor: String = "$DESCRIPTOR_DOMAIN:${nameForType(clazz)}" override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${nameForType(clazz)}")
override fun writeClassInfo(output: SerializationOutput) {} override fun writeClassInfo(output: SerializationOutput) {}
override val descriptor: Descriptor = Descriptor(typeDescriptor) override val descriptor: Descriptor = Descriptor(typeDescriptor)
override fun isSerializerFor(clazz: Class<*>): Boolean = if (withInheritance) this.clazz.isAssignableFrom(clazz) else this.clazz == clazz override fun isSerializerFor(clazz: Class<*>): Boolean = if (withInheritance) this.clazz.isAssignableFrom(clazz) else this.clazz == clazz

View File

@ -1,8 +1,9 @@
package net.corda.nodeapi.internal.serialization.amqp package net.corda.nodeapi.internal.serialization.amqp
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data import org.apache.qpid.proton.codec.Data
import java.lang.reflect.Type
import java.io.NotSerializableException import java.io.NotSerializableException
import java.lang.reflect.Type
/** /**
* Our definition of an enum with the AMQP spec is a list (of two items, a string and an int) that is * Our definition of an enum with the AMQP spec is a list (of two items, a string and an int) that is
@ -10,13 +11,13 @@ import java.io.NotSerializableException
*/ */
class EnumSerializer(declaredType: Type, declaredClass: Class<*>, factory: SerializerFactory) : AMQPSerializer<Any> { class EnumSerializer(declaredType: Type, declaredClass: Class<*>, factory: SerializerFactory) : AMQPSerializer<Any> {
override val type: Type = declaredType override val type: Type = declaredType
override val typeDescriptor = "$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}" override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}")
private val typeNotation: TypeNotation private val typeNotation: TypeNotation
init { init {
typeNotation = RestrictedType( typeNotation = RestrictedType(
SerializerFactory.nameForType(declaredType), SerializerFactory.nameForType(declaredType),
null, emptyList(), "list", Descriptor(typeDescriptor, null), null, emptyList(), "list", Descriptor(typeDescriptor),
declaredClass.enumConstants.zip(IntRange(0, declaredClass.enumConstants.size)).map { declaredClass.enumConstants.zip(IntRange(0, declaredClass.enumConstants.size)).map {
Choice(it.first.toString(), it.second.toString()) Choice(it.first.toString(), it.second.toString())
}) })

View File

@ -1,5 +1,6 @@
package net.corda.nodeapi.internal.serialization.amqp package net.corda.nodeapi.internal.serialization.amqp
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data import org.apache.qpid.proton.codec.Data
import java.io.NotSerializableException import java.io.NotSerializableException
import java.lang.reflect.ParameterizedType import java.lang.reflect.ParameterizedType
@ -17,7 +18,7 @@ private typealias MapCreationFunction = (Map<*, *>) -> Map<*, *>
*/ */
class MapSerializer(private val declaredType: ParameterizedType, factory: SerializerFactory) : AMQPSerializer<Any> { class MapSerializer(private val declaredType: ParameterizedType, factory: SerializerFactory) : AMQPSerializer<Any> {
override val type: Type = declaredType as? DeserializedParameterizedType ?: DeserializedParameterizedType.make(SerializerFactory.nameForType(declaredType)) override val type: Type = declaredType as? DeserializedParameterizedType ?: DeserializedParameterizedType.make(SerializerFactory.nameForType(declaredType))
override val typeDescriptor = "$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}" override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}")
companion object { companion object {
// NB: Order matters in this map, the most specific classes should be listed at the end // NB: Order matters in this map, the most specific classes should be listed at the end
@ -60,7 +61,7 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: Serial
private val concreteBuilder: MapCreationFunction = findConcreteType(declaredType.rawType as Class<*>) private val concreteBuilder: MapCreationFunction = findConcreteType(declaredType.rawType as Class<*>)
private val typeNotation: TypeNotation = RestrictedType(SerializerFactory.nameForType(declaredType), null, emptyList(), "map", Descriptor(typeDescriptor, null), emptyList()) private val typeNotation: TypeNotation = RestrictedType(SerializerFactory.nameForType(declaredType), null, emptyList(), "map", Descriptor(typeDescriptor), emptyList())
override fun writeClassInfo(output: SerializationOutput) { override fun writeClassInfo(output: SerializationOutput) {
if (output.writeTypeNotations(typeNotation)) { if (output.writeTypeNotations(typeNotation)) {

View File

@ -3,6 +3,7 @@ package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.utilities.debug import net.corda.core.utilities.debug
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.nameForType import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.nameForType
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data import org.apache.qpid.proton.codec.Data
import java.io.NotSerializableException import java.io.NotSerializableException
import java.lang.reflect.Type import java.lang.reflect.Type
@ -24,10 +25,10 @@ open class ObjectSerializer(val clazz: Type, factory: SerializerFactory) : AMQPS
private val typeName = nameForType(clazz) private val typeName = nameForType(clazz)
override val typeDescriptor = "$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}" override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}")
private val interfaces = interfacesForSerialization(clazz, factory) // We restrict to only those annotated or whitelisted private val interfaces = interfacesForSerialization(clazz, factory) // We restrict to only those annotated or whitelisted
open internal val typeNotation : TypeNotation by lazy {CompositeType(typeName, null, generateProvides(), Descriptor(typeDescriptor, null), generateFields()) } open internal val typeNotation: TypeNotation by lazy { CompositeType(typeName, null, generateProvides(), Descriptor(typeDescriptor), generateFields()) }
override fun writeClassInfo(output: SerializationOutput) { override fun writeClassInfo(output: SerializationOutput) {
if (output.writeTypeNotations(typeNotation)) { if (output.writeTypeNotations(typeNotation)) {

View File

@ -2,10 +2,11 @@ package net.corda.nodeapi.internal.serialization.amqp
import com.google.common.hash.Hasher import com.google.common.hash.Hasher
import com.google.common.hash.Hashing import com.google.common.hash.Hashing
import net.corda.core.utilities.toBase64
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.toBase64
import org.apache.qpid.proton.amqp.DescribedType 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.UnsignedInteger
import org.apache.qpid.proton.amqp.UnsignedLong import org.apache.qpid.proton.amqp.UnsignedLong
import org.apache.qpid.proton.codec.Data import org.apache.qpid.proton.codec.Data
@ -13,7 +14,6 @@ import org.apache.qpid.proton.codec.DescribedTypeConstructor
import java.io.NotSerializableException import java.io.NotSerializableException
import java.lang.reflect.* import java.lang.reflect.*
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
@ -106,7 +106,9 @@ data class Schema(val types: List<TypeNotation>) : DescribedType {
override fun toString(): String = types.joinToString("\n") override fun toString(): String = types.joinToString("\n")
} }
data class Descriptor(val name: String?, val code: UnsignedLong? = null) : DescribedType { data class Descriptor(val name: Symbol?, val code: UnsignedLong? = null) : DescribedType {
constructor(name: String?) : this(Symbol.valueOf(name))
companion object : DescribedTypeConstructor<Descriptor> { companion object : DescribedTypeConstructor<Descriptor> {
val DESCRIPTOR = DescriptorRegistry.OBJECT_DESCRIPTOR.amqpDescriptor val DESCRIPTOR = DescriptorRegistry.OBJECT_DESCRIPTOR.amqpDescriptor
@ -122,7 +124,7 @@ data class Descriptor(val name: String?, val code: UnsignedLong? = null) : Descr
override fun newInstance(described: Any?): Descriptor { override fun newInstance(described: Any?): Descriptor {
val list = described as? List<*> ?: throw IllegalStateException("Was expecting a list") val list = described as? List<*> ?: throw IllegalStateException("Was expecting a list")
return Descriptor(list[0] as? String, list[1] as? UnsignedLong) return Descriptor(list[0] as? Symbol, list[1] as? UnsignedLong)
} }
} }

View File

@ -18,16 +18,12 @@ data class schemaAndDescriptor(val schema: Schema, val typeDescriptor: Any)
/** /**
* Factory of serializers designed to be shared across threads and invocations. * Factory of serializers designed to be shared across threads and invocations.
*/ */
// TODO: object references - need better fingerprinting?
// TODO: class references? (e.g. cheat with repeated descriptors using a long encoding, like object ref proposal)
// TODO: Inner classes etc. Should we allow? Currently not considered.
// TODO: support for intern-ing of deserialized objects for some core types (e.g. PublicKey) for memory efficiency // TODO: support for intern-ing of deserialized objects for some core types (e.g. PublicKey) for memory efficiency
// TODO: maybe support for caching of serialized form of some core types for performance // TODO: maybe support for caching of serialized form of some core types for performance
// TODO: profile for performance in general // TODO: profile for performance in general
// TODO: use guava caches etc so not unbounded // TODO: use guava caches etc so not unbounded
// TODO: do we need to support a transient annotation to exclude certain properties? // TODO: do we need to support a transient annotation to exclude certain properties?
// TODO: allow definition of well known types that are left out of the schema. // TODO: allow definition of well known types that are left out of the schema.
// TODO: found a document that states textual descriptors are Symbols. Adjust schema class appropriately.
// TODO: document and alert to the fact that classes cannot default superclass/interface properties otherwise they are "erased" due to matching with constructor. // TODO: document and alert to the fact that classes cannot default superclass/interface properties otherwise they are "erased" due to matching with constructor.
// TODO: type name prefixes for interfaces and abstract classes? Or use label? // TODO: type name prefixes for interfaces and abstract classes? Or use label?
// TODO: generic types should define restricted type alias with source of the wildcarded version, I think, if we're to generate classes from schema // TODO: generic types should define restricted type alias with source of the wildcarded version, I think, if we're to generate classes from schema

View File

@ -1,5 +1,6 @@
package net.corda.nodeapi.internal.serialization.amqp package net.corda.nodeapi.internal.serialization.amqp
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data import org.apache.qpid.proton.codec.Data
import java.lang.reflect.Type import java.lang.reflect.Type
@ -9,12 +10,13 @@ import java.lang.reflect.Type
* want converting back to that singleton instance on the receiving JVM. * want converting back to that singleton instance on the receiving JVM.
*/ */
class SingletonSerializer(override val type: Class<*>, val singleton: Any, factory: SerializerFactory) : AMQPSerializer<Any> { class SingletonSerializer(override val type: Class<*>, val singleton: Any, factory: SerializerFactory) : AMQPSerializer<Any> {
override val typeDescriptor = "$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}" override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${fingerprintForType(type, factory)}")
private val interfaces = interfacesForSerialization(type, factory) private val interfaces = interfacesForSerialization(type, factory)
private fun generateProvides(): List<String> = interfaces.map { it.typeName } private fun generateProvides(): List<String> = interfaces.map { it.typeName }
internal val typeNotation: TypeNotation = RestrictedType(type.typeName, "Singleton", generateProvides(), "boolean", Descriptor(typeDescriptor, null), emptyList()) internal val typeNotation: TypeNotation = RestrictedType(type.typeName, "Singleton", generateProvides(), "boolean", Descriptor(typeDescriptor), emptyList())
override fun writeClassInfo(output: SerializationOutput) { override fun writeClassInfo(output: SerializationOutput) {
output.writeTypeNotations(typeNotation) output.writeTypeNotations(typeNotation)

View File

@ -1,8 +1,7 @@
package net.corda.nodeapi.internal.serialization.amqp package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SerializedBytes
import org.junit.Test import org.junit.Test
import java.io.File import java.io.File
import java.io.NotSerializableException import java.io.NotSerializableException