Ability to write single outer schema. Some groundwork for using Descriptor code for custom serializers (but lots of places still do descriptor?.name, which needs replacing with a toShortString() implementation)

This commit is contained in:
rick.parker 2022-10-22 15:18:14 +01:00
parent 3332973183
commit 84bf3d5639
33 changed files with 172 additions and 142 deletions

View File

@ -397,7 +397,7 @@ fun SerializationContext.withWhitelist(classes: List<Class<*>>): SerializationCo
// EXPERIMENTAL
@KeepForDJVM
class ExternalSchema()
data class ExternalSchema(val flush: Boolean = false, val externalSchemaForDeserialization: Any? = null, val typeNotations: MutableList<List<Any>> = ArrayList())
@KeepForDJVM
class IntegerFingerprints() {

View File

@ -1,5 +1,6 @@
package net.corda.finance.flows
import net.corda.core.serialization.ExternalSchema
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.SerializationFactory
import net.corda.core.serialization.SerializedBytes
@ -59,14 +60,15 @@ class CompatibilityTest {
assertEquals(1, commands.size)
assertTrue(commands.first().value is Cash.Commands.Issue)
val context = SerializationDefaults.STORAGE_CONTEXT.withExternalSchema(ExternalSchema()).withIntegerFingerprint()
val newWtx = SerializationFactory.defaultFactory.asCurrent {
withCurrentContext(SerializationDefaults.STORAGE_CONTEXT) {
withCurrentContext(context) {
WireTransaction(transaction.tx.componentGroups.map { cg: ComponentGroup ->
ComponentGroup(cg.groupIndex, cg.components.map { bytes ->
val componentInput = DeserializationInput(serializerFactory)
val component = componentInput.deserialize(SerializedBytes<Any>(bytes.bytes), SerializationDefaults.STORAGE_CONTEXT)
val componentOutput = SerializationOutput(serializerFactory)
val componentOutputBytes = componentOutput.serialize(component, SerializationDefaults.STORAGE_CONTEXT.withIntegerFingerprint()).bytes
val componentOutputBytes = componentOutput.serialize(component, context).bytes
OpaqueBytes(componentOutputBytes)
})
})
@ -76,8 +78,8 @@ class CompatibilityTest {
// Serialize back and check that representation is byte-to-byte identical to what it was originally.
val output = SerializationOutput(serializerFactory)
val outByteArray = output.serialize(newTransaction, SerializationDefaults.STORAGE_CONTEXT.withEncoding(CordaSerializationEncoding.SNAPPY)
.withIntegerFingerprint()).bytes
val outByteArray = output.serialize(newTransaction, context.withExternalSchema(context.externalSchema!!.copy(flush = true))
.withEncoding(CordaSerializationEncoding.SNAPPY)).bytes
//val (serializedBytes, schema) = output.serializeAndReturnSchema(transaction, SerializationDefaults.STORAGE_CONTEXT)
println("Output size = ${outByteArray.size}")

View File

@ -2,12 +2,12 @@ package net.corda.serialization.djvm.serializers
import net.corda.core.serialization.SerializationContext
import net.corda.serialization.internal.amqp.AMQPSerializer
import net.corda.serialization.internal.amqp.Descriptor
import net.corda.serialization.internal.amqp.DeserializationInput
import net.corda.serialization.internal.amqp.SerializationOutput
import net.corda.serialization.internal.amqp.SerializationSchemas
import net.corda.serialization.internal.amqp.typeDescriptorFor
import org.apache.qpid.proton.amqp.Binary
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data
import java.lang.reflect.Type
import java.util.function.Function
@ -16,8 +16,7 @@ class PrimitiveSerializer(
override val type: Class<*>,
private val sandboxBasicInput: Function<in Any?, out Any?>
) : AMQPSerializer<Any> {
override val typeDescriptor: Symbol = typeDescriptorFor(type)
override val descriptor: Descriptor = Descriptor(typeDescriptorFor(type))
override fun readObject(
obj: Any, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext
): Any {

View File

@ -7,6 +7,7 @@ import net.corda.serialization.djvm.deserializers.CreateCollection
import net.corda.serialization.djvm.toSandboxAnyClass
import net.corda.serialization.internal.amqp.AMQPSerializer
import net.corda.serialization.internal.amqp.CustomSerializer
import net.corda.serialization.internal.amqp.Descriptor
import net.corda.serialization.internal.amqp.DeserializationInput
import net.corda.serialization.internal.amqp.LocalSerializerFactory
import net.corda.serialization.internal.amqp.Schema
@ -15,7 +16,6 @@ import net.corda.serialization.internal.amqp.SerializationSchemas
import net.corda.serialization.internal.amqp.redescribe
import net.corda.serialization.internal.model.LocalTypeInformation
import net.corda.serialization.internal.model.TypeIdentifier
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
@ -92,14 +92,14 @@ private class ConcreteCollectionSerializer(
) : AMQPSerializer<Any> {
override val type: ParameterizedType = declaredType
override val typeDescriptor: Symbol by lazy {
factory.createDescriptor(
override val descriptor: Descriptor by lazy {
Descriptor(factory.createDescriptor(
LocalTypeInformation.ACollection(
observedType = declaredType,
typeIdentifier = TypeIdentifier.forGenericType(declaredType),
elementType = factory.getTypeInformation(declaredType.actualTypeArguments[0])
)
)
))
}
override fun readObject(

View File

@ -20,7 +20,6 @@ import net.corda.serialization.internal.amqp.SerializationSchemas
import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.amqp.typeDescriptorFor
import net.corda.serialization.internal.model.TypeIdentifier
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
@ -65,8 +64,7 @@ class SandboxCorDappCustomSerializer(
private val deserializationAlias: TypeIdentifier get() =
TypeIdentifier.Erased(AMQPTypeIdentifiers.nameForType(type).replace("sandbox.", ""), 0)
override val typeDescriptor: Symbol = typeDescriptorFor(type)
override val descriptor: Descriptor = Descriptor(typeDescriptor)
override val descriptor: Descriptor = Descriptor(typeDescriptorFor(type))
override val deserializationAliases: Set<TypeIdentifier> = singleton(deserializationAlias)
/**

View File

@ -9,6 +9,7 @@ import net.corda.serialization.djvm.toSandboxAnyClass
import net.corda.serialization.internal.amqp.AMQPNotSerializableException
import net.corda.serialization.internal.amqp.AMQPSerializer
import net.corda.serialization.internal.amqp.CustomSerializer
import net.corda.serialization.internal.amqp.Descriptor
import net.corda.serialization.internal.amqp.DeserializationInput
import net.corda.serialization.internal.amqp.LocalSerializerFactory
import net.corda.serialization.internal.amqp.Schema
@ -17,7 +18,6 @@ import net.corda.serialization.internal.amqp.SerializationSchemas
import net.corda.serialization.internal.model.EnumTransforms
import net.corda.serialization.internal.model.LocalTypeInformation
import net.corda.serialization.internal.model.TypeIdentifier
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data
import java.lang.reflect.Type
import java.util.function.Function
@ -76,8 +76,8 @@ private class ConcreteEnumSerializer(
) : AMQPSerializer<Any> {
override val type: Class<*> = declaredType
override val typeDescriptor: Symbol by lazy {
factory.createDescriptor(
override val descriptor: Descriptor by lazy {
Descriptor(factory.createDescriptor(
/*
* Partially populated, providing just the information
* required by the fingerprinter.
@ -90,7 +90,7 @@ private class ConcreteEnumSerializer(
emptyList(),
EnumTransforms.empty
)
)
))
}
override fun readObject(

View File

@ -6,6 +6,7 @@ import net.corda.serialization.djvm.deserializers.CreateMap
import net.corda.serialization.djvm.toSandboxAnyClass
import net.corda.serialization.internal.amqp.AMQPSerializer
import net.corda.serialization.internal.amqp.CustomSerializer
import net.corda.serialization.internal.amqp.Descriptor
import net.corda.serialization.internal.amqp.DeserializationInput
import net.corda.serialization.internal.amqp.LocalSerializerFactory
import net.corda.serialization.internal.amqp.Schema
@ -14,7 +15,6 @@ import net.corda.serialization.internal.amqp.SerializationSchemas
import net.corda.serialization.internal.amqp.redescribe
import net.corda.serialization.internal.model.LocalTypeInformation
import net.corda.serialization.internal.model.TypeIdentifier
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
@ -82,15 +82,15 @@ private class ConcreteMapSerializer(
) : AMQPSerializer<Any> {
override val type: ParameterizedType = declaredType
override val typeDescriptor: Symbol by lazy {
factory.createDescriptor(
override val descriptor: Descriptor by lazy {
Descriptor(factory.createDescriptor(
LocalTypeInformation.AMap(
observedType = declaredType,
typeIdentifier = TypeIdentifier.forGenericType(declaredType),
keyType = factory.getTypeInformation(declaredType.actualTypeArguments[0]),
valueType = factory.getTypeInformation(declaredType.actualTypeArguments[1])
)
)
))
}
override fun readObject(

View File

@ -62,7 +62,7 @@ class DeserializeEnumWithEvolutionTest : TestBase(KOTLIN) {
}
private fun SerializedBytes<*>.devolve(context: SerializationContext): SerializedBytes<Any> {
val envelope = DeserializationInput.getEnvelope(this, context.encodingWhitelist).apply {
val envelope = DeserializationInput.getEnvelope(this, context).apply {
val schemaTypes = schema.types.map(::devolveType)
with(schema.types as MutableList<TypeNotation>) {
clear()

View File

@ -47,7 +47,7 @@ class DeserializeRemoteCustomisedEnumTest : TestBase(KOTLIN) {
*/
@Suppress("unchecked_cast")
private fun SerializedBytes<Broken>.rewriteEnumAsWorking(): SerializedBytes<Working> {
val envelope = DeserializationInput.getEnvelope(this).apply {
val envelope = DeserializationInput.getEnvelope(this, AMQP_STORAGE_CONTEXT).apply {
val restrictedType = schema.types[0] as RestrictedType
(schema.types as MutableList<TypeNotation>)[0] = restrictedType.copy(
name = toWorking(restrictedType.name)
@ -92,7 +92,7 @@ class DeserializeRemoteCustomisedEnumTest : TestBase(KOTLIN) {
*/
@Suppress("unchecked_cast")
private fun SerializedBytes<BrokenContainer>.rewriteContainerAsWorking(): SerializedBytes<WorkingContainer> {
val envelope = DeserializationInput.getEnvelope(this).apply {
val envelope = DeserializationInput.getEnvelope(this, AMQP_STORAGE_CONTEXT).apply {
val compositeType = schema.types[0] as CompositeType
(schema.types as MutableList<TypeNotation>)[0] = compositeType.copy(
name = toWorking(compositeType.name),

View File

@ -27,7 +27,7 @@ class SafeDeserialisationTest : TestBase(KOTLIN) {
val innocent = InnocentData(MESSAGE, NUMBER)
val innocentData = innocent.serialize()
val envelope = DeserializationInput.getEnvelope(innocentData, context.encodingWhitelist).apply {
val envelope = DeserializationInput.getEnvelope(innocentData, context).apply {
val innocentType = schema.types[0] as CompositeType
(schema.types as MutableList<TypeNotation>)[0] = innocentType.copy(
name = innocentType.name.replace("Innocent", "VeryEvil")

View File

@ -12,6 +12,9 @@ import org.apache.qpid.proton.amqp.UnsignedLong
*/
const val DESCRIPTOR_TOP_32BITS: Long = 0xc562L shl (32 + 16)
const val DESCRIPTOR_USER_START: Long = 256L
const val DESCRIPTOR_CUSTOM_SERIALIZER_START: Long = 32L
/**
* AMQP descriptor ID's for our custom types.
*

View File

@ -12,7 +12,7 @@ import java.lang.reflect.Type
* [ByteArray] is automatically marshalled to/from the Proton-J wrapper, [Binary].
*/
class AMQPPrimitiveSerializer(clazz: Class<*>) : AMQPSerializer<Any> {
override val typeDescriptor = Symbol.valueOf(AMQPTypeIdentifiers.primitiveTypeName(clazz))!!
override val descriptor: Descriptor = Descriptor(Symbol.valueOf(AMQPTypeIdentifiers.primitiveTypeName(clazz))!!)
override val type: Type = clazz
// NOOP since this is a primitive type.

View File

@ -2,7 +2,6 @@ package net.corda.serialization.internal.amqp
import net.corda.core.KeepForDJVM
import net.corda.core.serialization.SerializationContext
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data
import java.lang.reflect.Type
@ -22,7 +21,8 @@ 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.
*/
val typeDescriptor: Symbol
//val typeDescriptor: Symbol
val descriptor: Descriptor
/**
* Add anything required to the AMQP schema via [SerializationOutput.writeTypeNotations] and any dependent serializers

View File

@ -7,7 +7,6 @@ import net.corda.core.utilities.debug
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.trace
import net.corda.serialization.internal.model.resolveAgainst
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data
import java.lang.reflect.Type
@ -56,15 +55,13 @@ open class ArraySerializer(override val type: Type, factory: LocalSerializerFact
}
}
override val typeDescriptor: Symbol by lazy {
factory.createDescriptor(type)
}
internal val elementType: Type by lazy { type.componentType().resolveAgainst(type) }
internal open val typeName by lazy { calcTypeName(type) }
override val descriptor: Descriptor by lazy { Descriptor(factory.createDescriptor(type)) }
internal val typeNotation: TypeNotation by lazy {
RestrictedType(typeName, null, emptyList(), "list", Descriptor(typeDescriptor), emptyList())
RestrictedType(typeName, null, emptyList(), "list", Descriptor(factory.createDescriptor(type)), emptyList())
}
override fun writeClassInfo(output: SerializationOutput) {

View File

@ -5,7 +5,6 @@ import net.corda.core.serialization.SerializationContext
import net.corda.core.utilities.NonEmptySet
import net.corda.serialization.internal.model.LocalTypeInformation
import net.corda.serialization.internal.model.TypeIdentifier
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data
import java.io.NotSerializableException
import java.lang.reflect.ParameterizedType
@ -22,8 +21,8 @@ import java.util.TreeSet
class CollectionSerializer(private val declaredType: ParameterizedType, factory: LocalSerializerFactory) : AMQPSerializer<Any> {
override val type: Type = declaredType
override val typeDescriptor: Symbol by lazy {
factory.createDescriptor(type)
override val descriptor: Descriptor by lazy {
Descriptor(factory.createDescriptor(type))
}
companion object {
@ -90,7 +89,7 @@ class CollectionSerializer(private val declaredType: ParameterizedType, factory:
private val concreteBuilder: (List<*>) -> Collection<*> = findConcreteType(declaredType.rawType as Class<*>)
private val typeNotation: TypeNotation = RestrictedType(AMQPTypeIdentifiers.nameForType(declaredType), null, emptyList(), "list", Descriptor(typeDescriptor), emptyList())
private val typeNotation: TypeNotation = RestrictedType(AMQPTypeIdentifiers.nameForType(declaredType), null, emptyList(), "list", descriptor, emptyList())
private val outboundType = resolveTypeVariables(declaredType.actualTypeArguments[0], null)
private val inboundType = declaredType.actualTypeArguments[0]

View File

@ -4,7 +4,6 @@ import com.google.common.reflect.TypeToken
import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationCustomSerializer
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data
import java.lang.reflect.Type
import kotlin.reflect.jvm.javaType
@ -62,8 +61,7 @@ class CorDappCustomSerializer(
override val type = types[CORDAPP_TYPE]
val proxyType = types[PROXY_TYPE]
override val typeDescriptor: Symbol = typeDescriptorFor(type)
val descriptor: Descriptor = Descriptor(typeDescriptor)
override val descriptor: Descriptor = Descriptor(typeDescriptorFor(type))
private val proxySerializer: ObjectSerializer by lazy {
ObjectSerializer.make(factory.getTypeInformation(proxyType), factory)
}

View File

@ -35,7 +35,6 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T>, SerializerFor {
*/
open val deserializationAliases: Set<TypeIdentifier> = emptySet()
protected abstract val descriptor: Descriptor
/**
* This exists purely for documentation and cross-platform purposes. It is not used by our serialization / deserialization
* code path.
@ -82,12 +81,16 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T>, SerializerFor {
override val type: Type get() = clazz
override val typeDescriptor: Symbol by lazy {
override fun writeClassInfo(output: SerializationOutput) {
output.writeTypeNotations(typeNotation)
}
override val descriptor: Descriptor by lazy {
val fingerprint = FingerprintWriter()
.write(superClassSerializer.typeDescriptor)
.write(superClassSerializer.descriptor.name!!)
.write(AMQPTypeIdentifiers.nameForType(clazz))
.fingerprint
Symbol.valueOf("$DESCRIPTOR_DOMAIN:$fingerprint")
Descriptor(Symbol.valueOf("$DESCRIPTOR_DOMAIN:$fingerprint"))
}
private val typeNotation: TypeNotation = RestrictedType(
@ -95,16 +98,10 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T>, SerializerFor {
null,
emptyList(),
AMQPTypeIdentifiers.nameForType(superClassSerializer.type),
Descriptor(typeDescriptor),
descriptor,
emptyList()
)
override fun writeClassInfo(output: SerializationOutput) {
output.writeTypeNotations(typeNotation)
}
override val descriptor: Descriptor = Descriptor(typeDescriptor)
override fun writeDescribedObject(obj: T, data: Data, type: Type, output: SerializationOutput,
context: SerializationContext
) {
@ -124,9 +121,8 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T>, SerializerFor {
*/
abstract class CustomSerializerImp<T : Any>(protected val clazz: Class<T>, protected val withInheritance: Boolean) : CustomSerializer<T>() {
override val type: Type get() = clazz
override val typeDescriptor: Symbol = typeDescriptorFor(clazz)
override fun writeClassInfo(output: SerializationOutput) {}
override val descriptor: Descriptor = Descriptor(typeDescriptor)
override val descriptor: Descriptor = Descriptor(typeDescriptorFor(clazz))
override fun isSerializerFor(clazz: Class<*>): Boolean = if (withInheritance) this.clazz.isAssignableFrom(clazz) else this.clazz == clazz
}

View File

@ -102,16 +102,16 @@ class CachingCustomSerializerRegistry(
"All serializers should be registered before the cache comes into use.")
}
descriptorBasedSerializerRegistry.getOrBuild(customSerializer.typeDescriptor.toString()) {
descriptorBasedSerializerRegistry.getOrBuild(customSerializer.descriptor.name.toString()) {
customSerializers += customSerializer
for (additional in customSerializer.additionalSerializers) {
register(additional)
}
for (alias in customSerializer.deserializationAliases) {
val aliasDescriptor = typeDescriptorFor(alias)
if (aliasDescriptor != customSerializer.typeDescriptor) {
descriptorBasedSerializerRegistry[aliasDescriptor.toString()] = customSerializer
val aliasDescriptor = Descriptor(typeDescriptorFor(alias))
if (aliasDescriptor != customSerializer.descriptor) {
descriptorBasedSerializerRegistry[aliasDescriptor.name.toString()] = customSerializer
}
}
@ -128,7 +128,7 @@ class CachingCustomSerializerRegistry(
"All serializers must be registered before the cache comes into use.")
}
descriptorBasedSerializerRegistry.getOrBuild(customSerializer.typeDescriptor.toString()) {
descriptorBasedSerializerRegistry.getOrBuild(customSerializer.descriptor.name.toString()) {
customSerializers += customSerializer
customSerializer
}

View File

@ -2,8 +2,8 @@ package net.corda.serialization.internal.amqp
import net.corda.core.KeepForDJVM
import net.corda.core.internal.VisibleForTesting
import net.corda.core.serialization.EncodingWhitelist
import net.corda.core.serialization.AMQP_ENVELOPE_CACHE_PROPERTY
import net.corda.core.serialization.EncodingWhitelist
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializedBytes
import net.corda.core.utilities.ByteSequence
@ -11,7 +11,6 @@ import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.trace
import net.corda.serialization.internal.ByteBufferInputStream
import net.corda.serialization.internal.CordaSerializationEncoding
import net.corda.serialization.internal.NullEncodingWhitelist
import net.corda.serialization.internal.SectionId
import net.corda.serialization.internal.encodingNotPermittedFormat
import net.corda.serialization.internal.model.TypeIdentifier
@ -21,7 +20,6 @@ import org.apache.qpid.proton.amqp.UnsignedInteger
import org.apache.qpid.proton.codec.Data
import java.io.InputStream
import java.io.NotSerializableException
import java.lang.Exception
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.lang.reflect.TypeVariable
@ -73,8 +71,8 @@ class DeserializationInput constructor(
}
@Throws(AMQPNoTypeNotSerializableException::class)
fun getEnvelope(byteSequence: ByteSequence, encodingWhitelist: EncodingWhitelist = NullEncodingWhitelist): Envelope {
return withDataBytes(byteSequence, encodingWhitelist) { dataBytes ->
fun getEnvelope(byteSequence: ByteSequence, context: SerializationContext): Envelope {
return withDataBytes(byteSequence, context.encodingWhitelist) { dataBytes ->
val data = Data.Factory.create()
val expectedSize = dataBytes.remaining()
if (data.decode(dataBytes) != expectedSize.toLong()) {
@ -82,14 +80,17 @@ class DeserializationInput constructor(
"Unexpected size of data",
"Blob is corrupted!.")
}
Envelope.get(data)
val envelope = Envelope.get(data)
if (context.externalSchema == null || envelope.schema.types.isNotEmpty()) {
envelope
} else {
Envelope(envelope.obj, context.externalSchema?.externalSchemaForDeserialization as? Schema
?: Schema.newInstance(listOf(context.externalSchema?.typeNotations?.flatten()
?.toList() as List<TypeNotation>)), TransformsSchema.newInstance(null))
}
}
}
}
@VisibleForTesting
@Throws(AMQPNoTypeNotSerializableException::class)
fun getEnvelope(byteSequence: ByteSequence, context: SerializationContext) = getEnvelope(byteSequence, context.encodingWhitelist)
@Throws(
AMQPNotSerializableException::class,
@ -134,8 +135,8 @@ class DeserializationInput constructor(
@Suppress("unchecked_cast")
val envelope = (context.properties[AMQP_ENVELOPE_CACHE_PROPERTY] as? MutableMap<IdentityKey, Envelope>)
?.computeIfAbsent(IdentityKey(bytes)) { key ->
getEnvelope(key.bytes, context.encodingWhitelist)
} ?: getEnvelope(bytes, context.encodingWhitelist)
getEnvelope(key.bytes, context)
} ?: getEnvelope(bytes, context)
logger.trace { "deserialize blob scheme=\"${envelope.schema}\"" }
@ -148,7 +149,7 @@ class DeserializationInput constructor(
clazz: Class<T>,
context: SerializationContext
): ObjectAndEnvelope<T> = des {
val envelope = getEnvelope(bytes, context.encodingWhitelist)
val envelope = getEnvelope(bytes, context)
// Now pick out the obj and schema from the envelope.
ObjectAndEnvelope(doReadObject(envelope, clazz, context), envelope)
}

View File

@ -3,7 +3,6 @@ package net.corda.serialization.internal.amqp
import net.corda.core.serialization.SerializationContext
import net.corda.serialization.internal.model.BaseLocalTypes
import org.apache.qpid.proton.codec.Data
import java.lang.UnsupportedOperationException
import java.lang.reflect.Type
/**
@ -38,7 +37,7 @@ class EnumEvolutionSerializer(
private val baseLocalTypes: BaseLocalTypes,
private val conversions: Map<String, String>,
private val ordinals: Map<String, Int>) : AMQPSerializer<Any> {
override val typeDescriptor = factory.createDescriptor(type)
override val descriptor: Descriptor = Descriptor(factory.createDescriptor(type))
override fun readObject(obj: Any, schemas: SerializationSchemas, input: DeserializationInput,
context: SerializationContext

View File

@ -11,13 +11,13 @@ import java.lang.reflect.Type
class EnumSerializer(declaredType: Type, declaredClass: Class<*>, factory: LocalSerializerFactory) : AMQPSerializer<Any> {
override val type: Type = declaredType
private val typeNotation: TypeNotation
override val typeDescriptor = factory.createDescriptor(type)
override val descriptor: Descriptor = Descriptor(factory.createDescriptor(type))
init {
@Suppress("unchecked_cast")
typeNotation = RestrictedType(
AMQPTypeIdentifiers.nameForType(declaredType),
null, emptyList(), "list", Descriptor(typeDescriptor),
null, emptyList(), "list", descriptor,
(declaredClass as Class<out Enum<*>>).enumConstants.zip(IntRange(0, declaredClass.enumConstants.size)).map {
Choice(it.first.name, it.second.toString())
})

View File

@ -19,6 +19,7 @@ data class Envelope(val obj: Any?, val schema: Schema, val transformsSchema: Tra
val DESCRIPTOR_OBJECT = Descriptor(null, DESCRIPTOR)
// described list should either be two or three elements long
private const val ENVELOPE_WITHOUT_SCHEMA = 1
private const val ENVELOPE_WITHOUT_TRANSFORMS = 2
private const val ENVELOPE_WITH_TRANSFORMS = 3
private const val ENVELOPE_WITH_TRANSFORMS_AND_EXTERNAL_SCHEMA = 3
@ -36,27 +37,39 @@ data class Envelope(val obj: Any?, val schema: Schema, val transformsSchema: Tra
}
val list = describedType.described as List<*>
// We need to cope with objects serialised without the schema header element in the
// envelope
val schema: Any? = when (list.size) {
ENVELOPE_WITHOUT_SCHEMA -> Schema(emptyList())
ENVELOPE_WITHOUT_TRANSFORMS -> Schema.get(list[SCHEMA_IDX]!!)
ENVELOPE_WITH_TRANSFORMS -> Schema.get(list[SCHEMA_IDX]!!)
ENVELOPE_WITH_TRANSFORMS_AND_EXTERNAL_SCHEMA -> Schema.get(list[SCHEMA_IDX]!!)
else -> throw AMQPNoTypeNotSerializableException(
"Malformed list, bad length of ${list.size} (should be 1, 2, 3 or 4)")
}
// We need to cope with objects serialised without the transforms header element in the
// envelope
val transformSchema: Any? = when (list.size) {
ENVELOPE_WITHOUT_SCHEMA -> null
ENVELOPE_WITHOUT_TRANSFORMS -> null
ENVELOPE_WITH_TRANSFORMS -> list[TRANSFORMS_SCHEMA_IDX]
ENVELOPE_WITH_TRANSFORMS_AND_EXTERNAL_SCHEMA -> list[TRANSFORMS_SCHEMA_IDX]
else -> throw AMQPNoTypeNotSerializableException(
"Malformed list, bad length of ${list.size} (should be 2, 3 or 4)")
"Malformed list, bad length of ${list.size} (should be 1, 2, 3 or 4)")
}
/*
// We need to cope with objects serialised without the external schema header element in the
// envelope
val externalSchema: Any? = when (list.size) {
ENVELOPE_WITHOUT_SCHEMA -> null
ENVELOPE_WITHOUT_TRANSFORMS -> null
ENVELOPE_WITH_TRANSFORMS -> null
ENVELOPE_WITH_TRANSFORMS_AND_EXTERNAL_SCHEMA -> list[EXTERNAL_SCHEMA_IDX]
else -> throw AMQPNoTypeNotSerializableException(
"Malformed list, bad length of ${list.size} (should be 2, 3 or 4)")
}
"Malformed list, bad length of ${list.size} (should be 1, 2, 3 or 4)")
}*/
return newInstance(listOf(list[BLOB_IDX], Schema.get(list[SCHEMA_IDX]!!),
return newInstance(listOf(list[BLOB_IDX], schema,
TransformsSchema.newInstance(transformSchema)))
}

View File

@ -5,12 +5,16 @@ import net.corda.core.serialization.ClassWhitelist
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.core.utilities.trace
import net.corda.serialization.internal.model.*
import net.corda.serialization.internal.model.TypeIdentifier.*
import net.corda.serialization.internal.model.DefaultCacheProvider
import net.corda.serialization.internal.model.FingerPrinter
import net.corda.serialization.internal.model.LocalTypeInformation
import net.corda.serialization.internal.model.LocalTypeModel
import net.corda.serialization.internal.model.TypeIdentifier
import net.corda.serialization.internal.model.TypeIdentifier.Parameterised
import org.apache.qpid.proton.amqp.Symbol
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.util.*
import java.util.Optional
import java.util.function.Function
import java.util.function.Predicate
import javax.annotation.concurrent.ThreadSafe
@ -146,7 +150,7 @@ class DefaultLocalSerializerFactory(
private fun makeAndCache(typeIdentifier: TypeIdentifier, build: () -> AMQPSerializer<Any>) =
serializersByTypeId.getOrPut(typeIdentifier) {
build().also { serializer ->
descriptorBasedSerializerRegistry[serializer.typeDescriptor.toString()] = serializer
descriptorBasedSerializerRegistry[serializer.descriptor.name.toString()] = serializer
}
}

View File

@ -6,7 +6,6 @@ import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.SerializationContext
import net.corda.serialization.internal.model.LocalTypeInformation
import net.corda.serialization.internal.model.TypeIdentifier
import org.apache.qpid.proton.amqp.Symbol
import org.apache.qpid.proton.codec.Data
import java.io.NotSerializableException
import java.lang.reflect.ParameterizedType
@ -29,7 +28,7 @@ private typealias MapCreationFunction = (Map<*, *>) -> Map<*, *>
class MapSerializer(private val declaredType: ParameterizedType, factory: LocalSerializerFactory) : AMQPSerializer<Any> {
override val type: Type = declaredType
override val typeDescriptor: Symbol = factory.createDescriptor(type)
override val descriptor: Descriptor = Descriptor(factory.createDescriptor(type))
companion object {
// NB: Order matters in this map, the most specific classes should be listed at the end
@ -90,7 +89,7 @@ class MapSerializer(private val declaredType: ParameterizedType, factory: LocalS
private val concreteBuilder: MapCreationFunction = findConcreteType(declaredType.rawType as Class<*>)
private val typeNotation: TypeNotation = RestrictedType(AMQPTypeIdentifiers.nameForType(declaredType), null, emptyList(), "map", Descriptor(typeDescriptor), emptyList())
private val typeNotation: TypeNotation = RestrictedType(AMQPTypeIdentifiers.nameForType(declaredType), null, emptyList(), "map", descriptor, emptyList())
private val inboundKeyType = declaredType.actualTypeArguments[0]
private val outboundKeyType = resolveTypeVariables(inboundKeyType, null)

View File

@ -102,12 +102,14 @@ interface ObjectSerializer : AMQPSerializer<Any> {
class ComposableObjectSerializer(
override val type: Type,
override val typeDescriptor: Symbol,
typeDescriptor: Symbol,
override val propertySerializers: Map<PropertyName, PropertySerializer>,
override val fields: List<Field>,
private val reader: ComposableObjectReader,
private val writer: ComposableObjectWriter): ObjectSerializer {
override val descriptor: Descriptor = Descriptor(typeDescriptor)
override fun writeClassInfo(output: SerializationOutput) = writer.writeClassInfo(output)
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput, context: SerializationContext, debugIndent: Int) =
@ -178,10 +180,12 @@ class ComposableObjectReader(
class AbstractObjectSerializer(
override val type: Type,
override val typeDescriptor: Symbol,
typeDescriptor: Symbol,
override val propertySerializers: Map<PropertyName, PropertySerializer>,
override val fields: List<Field>,
private val writer: ComposableObjectWriter): ObjectSerializer {
override val descriptor: Descriptor = Descriptor(typeDescriptor)
override fun writeClassInfo(output: SerializationOutput) =
writer.writeClassInfo(output)
@ -194,10 +198,12 @@ class AbstractObjectSerializer(
class EvolutionObjectSerializer(
override val type: Type,
override val typeDescriptor: Symbol,
typeDescriptor: Symbol,
override val propertySerializers: Map<PropertyName, PropertySerializer>,
private val reader: ComposableObjectReader): ObjectSerializer {
override val descriptor: Descriptor = Descriptor(typeDescriptor)
companion object {
fun make(localTypeInformation: LocalTypeInformation.Composable,
remoteTypeInformation: RemoteTypeInformation.Composable,

View File

@ -97,7 +97,7 @@ class DefaultRemoteSerializerFactory(
// Obtain a serializer and descriptor for the local type.
val localSerializer = localSerializerFactory.get(localTypeInformation)
val localDescriptor = localSerializer.typeDescriptor.toString()
val localDescriptor = localSerializer.descriptor.name.toString()
return when {
// If descriptors match, we can return the local serializer straight away.

View File

@ -35,7 +35,7 @@ fun Descriptor.maybeConvertToInteger(context: SerializationContext): Descriptor
val descriptorMappings = context.integerFingerprints?.descriptorMappings
if (descriptorMappings == null) return this
return context.integerFingerprints!!.descriptorMappings.computeIfAbsent(this) {
Descriptor(null, UnsignedLong(32L + descriptorMappings.size or DESCRIPTOR_TOP_32BITS))
Descriptor(null, UnsignedLong(DESCRIPTOR_USER_START + descriptorMappings.size or DESCRIPTOR_TOP_32BITS))
} as Descriptor
}

View File

@ -80,9 +80,22 @@ open class SerializationOutput constructor(
data.withDescribed(Envelope.DESCRIPTOR_OBJECT, context) {
withList {
writeObject(obj, this, context)
val schema = Schema(schemaHistory.map { it.maybeConvertDescriptorToInteger(context) }.toList())
if (context.externalSchema == null) {
val types = schemaHistory.map { it.maybeConvertDescriptorToInteger(context) }.toList()
val schema = Schema(types)
writeSchema(schema, this)
writeTransformSchema(TransformsSchema.build(schema, serializerFactory), this)
} else {
context.externalSchema?.typeNotations?.add(schemaHistory.map { it.maybeConvertDescriptorToInteger(context) }.toList())
if (context.externalSchema!!.flush) {
val descriptors: MutableSet<Descriptor> = HashSet()
val schema = Schema((context.externalSchema?.typeNotations?.flatten()?.toList() as List<TypeNotation>).filter {
descriptors.add(it.descriptor)
})
writeSchema(schema, this)
writeTransformSchema(TransformsSchema.build(schema, serializerFactory), this)
}
}
}
}
return SerializedBytes(byteArrayOutput {

View File

@ -11,13 +11,13 @@ import java.lang.reflect.Type
* want converting back to that singleton instance on the receiving JVM.
*/
class SingletonSerializer(override val type: Class<*>, val singleton: Any, factory: LocalSerializerFactory) : AMQPSerializer<Any> {
override val typeDescriptor = factory.createDescriptor(type)
override val descriptor: Descriptor = Descriptor(factory.createDescriptor(type))
private val interfaces = (factory.getTypeInformation(type) as LocalTypeInformation.Singleton).interfaces
private fun generateProvides(): List<String> = interfaces.map { it.typeIdentifier.name }
internal val typeNotation: TypeNotation = RestrictedType(type.typeName, "Singleton", generateProvides(), "boolean", Descriptor(typeDescriptor), emptyList())
internal val typeNotation: TypeNotation = RestrictedType(type.typeName, "Singleton", generateProvides(), "boolean", descriptor, emptyList())
override fun writeClassInfo(output: SerializationOutput) {
output.writeTypeNotations(typeNotation)

View File

@ -248,7 +248,7 @@ private class FingerPrintingState(
val customTypeDescriptor = customSerializerRegistry.findCustomSerializer(
clazz = observedClass,
declaredType = observedGenericType
)?.typeDescriptor?.toString()
)?.descriptor?.name?.toString()
if (customTypeDescriptor != null) writer.write(customTypeDescriptor)
else defaultAction()
}

View File

@ -9,6 +9,6 @@ class SerializationCompatibilityTests {
@Test(timeout=300_000)
fun `fingerprint is stable`() {
val factory = testDefaultFactoryNoEvolution().apply { register(ThrowableSerializer(this)) }
assertThat(factory.get(Exception::class.java).typeDescriptor.toString()).isEqualTo("net.corda:ApZ2a/36VVskaoDZMbiZ8A==")
assertThat(factory.get(Exception::class.java).descriptor.name.toString()).isEqualTo("net.corda:ApZ2a/36VVskaoDZMbiZ8A==")
}
}

View File

@ -3,6 +3,7 @@ package net.corda.serialization.internal.amqp
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationContext.UseCase.Testing
import net.corda.core.serialization.SerializedBytes
import net.corda.serialization.internal.AMQP_STORAGE_CONTEXT
import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.SerializationContextImpl
import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput
@ -51,7 +52,7 @@ class EnumToStringFallbackTest {
*/
@Suppress("unchecked_cast")
private fun SerializedBytes<BrokenContainer>.rewriteAsWorking(): SerializedBytes<WorkingContainer> {
val envelope = DeserializationInput.getEnvelope(this).apply {
val envelope = DeserializationInput.getEnvelope(this, AMQP_STORAGE_CONTEXT).apply {
val compositeType = schema.types[0] as CompositeType
(schema.types as MutableList<TypeNotation>)[0] = compositeType.copy(
name = toWorking(compositeType.name),

View File

@ -22,7 +22,9 @@ import net.corda.serialization.internal.SerializationFactoryImpl
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
import net.corda.serialization.internal.amqp.DeserializationInput
import net.corda.serialization.internal.amqp.amqpMagic
import picocli.CommandLine.*
import picocli.CommandLine.ITypeConverter
import picocli.CommandLine.Option
import picocli.CommandLine.Parameters
import java.io.PrintStream
import java.net.MalformedURLException
import java.net.URL
@ -59,7 +61,7 @@ class BlobInspector : CordaCliWrapper("blob-inspector", "Convert AMQP serialised
initialiseSerialization()
if (schema) {
val envelope = DeserializationInput.getEnvelope(bytes.sequence(), SerializationDefaults.STORAGE_CONTEXT.encodingWhitelist)
val envelope = DeserializationInput.getEnvelope(bytes.sequence(), SerializationDefaults.STORAGE_CONTEXT)
out.println(envelope.schema)
out.println()
out.println(envelope.transformsSchema)