CORDA-3157: Modify Corda's custom serialiser support for the DJVM.

Modify Corda's custom serialiser support for the DJVM.
This commit is contained in:
Jonathan Locke 2019-08-27 15:20:38 +01:00 committed by GitHub
commit f96105a014
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 199 additions and 51 deletions

View File

@ -22,12 +22,12 @@ dependencies {
// Configure these by hand. It should be a minimal subset of dependencies, // Configure these by hand. It should be a minimal subset of dependencies,
// and without any obviously non-deterministic ones such as Hibernate. // 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. // See publish.dependenciesFrom.defaultScope.
deterministicLibraries project(path: ':core-deterministic', configuration: 'deterministicArtifacts') 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. // 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 "org.iq80.snappy:snappy:$snappy_version"
implementation "com.google.guava:guava:$guava_version" implementation "com.google.guava:guava:$guava_version"
} }

View File

@ -13,11 +13,16 @@ import net.corda.serialization.internal.carpenter.Schema
@Suppress("UNUSED") @Suppress("UNUSED")
fun createSerializerFactoryFactory(): SerializerFactoryFactory = DeterministicSerializerFactoryFactory() fun createSerializerFactoryFactory(): SerializerFactoryFactory = DeterministicSerializerFactoryFactory()
/**
* Creates a [ClassCarpenter] suitable for the DJVM, i.e. one that doesn't work.
*/
fun createClassCarpenter(context: SerializationContext): ClassCarpenter = DummyClassCarpenter(context.whitelist, context.deserializationClassLoader)
private class DeterministicSerializerFactoryFactory : SerializerFactoryFactory { private class DeterministicSerializerFactoryFactory : SerializerFactoryFactory {
override fun make(context: SerializationContext) = override fun make(context: SerializationContext) =
SerializerFactoryBuilder.build( SerializerFactoryBuilder.build(
whitelist = context.whitelist, whitelist = context.whitelist,
classCarpenter = DummyClassCarpenter(context.whitelist, context.deserializationClassLoader)) classCarpenter = createClassCarpenter(context))
} }
private class DummyClassCarpenter( private class DummyClassCarpenter(

View File

@ -3,14 +3,21 @@
package net.corda.serialization.internal.amqp package net.corda.serialization.internal.amqp
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.serialization.internal.carpenter.ClassCarpenter
import net.corda.serialization.internal.carpenter.ClassCarpenterImpl import net.corda.serialization.internal.carpenter.ClassCarpenterImpl
fun createSerializerFactoryFactory(): SerializerFactoryFactory = SerializerFactoryFactoryImpl() fun createSerializerFactoryFactory(): SerializerFactoryFactory = SerializerFactoryFactoryImpl()
fun createClassCarpenter(context: SerializationContext): ClassCarpenter = ClassCarpenterImpl(
whitelist = context.whitelist,
cl = context.deserializationClassLoader,
lenient = context.lenientCarpenterEnabled
)
open class SerializerFactoryFactoryImpl : SerializerFactoryFactory { open class SerializerFactoryFactoryImpl : SerializerFactoryFactory {
override fun make(context: SerializationContext): SerializerFactory { override fun make(context: SerializationContext): SerializerFactory {
return SerializerFactoryBuilder.build(context.whitelist, return SerializerFactoryBuilder.build(context.whitelist,
ClassCarpenterImpl(context.whitelist, context.deserializationClassLoader, context.lenientCarpenterEnabled), createClassCarpenter(context),
mustPreserveDataWhenEvolving = context.preventDataLoss mustPreserveDataWhenEvolving = context.preventDataLoss
) )
} }

View File

@ -29,7 +29,7 @@ object AMQPTypeIdentifiers {
Float::class to "float", Float::class to "float",
Double::class to "double", Double::class to "double",
Decimal32::class to "decimal32", Decimal32::class to "decimal32",
Decimal64::class to "decimal62", Decimal64::class to "decimal64",
Decimal128::class to "decimal128", Decimal128::class to "decimal128",
Date::class to "timestamp", Date::class to "timestamp",
UUID::class to "uuid", UUID::class to "uuid",

View File

@ -88,7 +88,9 @@ open class ArraySerializer(override val type: Type, factory: LocalSerializerFact
context: SerializationContext context: SerializationContext
): Any { ): Any {
if (obj is List<*>) { 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") } else throw AMQPNotSerializableException(type, "Expected a List but found $obj")
} }

View File

@ -1,6 +1,7 @@
package net.corda.serialization.internal.amqp package net.corda.serialization.internal.amqp
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.serialization.internal.amqp.AMQPTypeIdentifiers.isPrimitive
import net.corda.serialization.internal.model.* import net.corda.serialization.internal.model.*
import org.apache.qpid.proton.amqp.Binary import org.apache.qpid.proton.amqp.Binary
import org.apache.qpid.proton.codec.Data 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. * Select the correct strategy for reading properties, based on the property type.
*/ */
fun make(name: String, typeIdentifier: TypeIdentifier, type: Type): PropertyReadStrategy = fun make(name: String, typeIdentifier: TypeIdentifier, type: Type): PropertyReadStrategy =
if (AMQPTypeIdentifiers.isPrimitive(typeIdentifier)) { if (isPrimitive(typeIdentifier)) {
when (typeIdentifier) { when (typeIdentifier) {
in characterTypes -> AMQPCharPropertyReadStrategy in characterTypes -> AMQPCharPropertyReadStrategy
else -> AMQPPropertyReadStrategy else -> AMQPPropertyReadStrategy
@ -47,7 +48,7 @@ interface PropertyWriteStrategy {
fun make(name: String, propertyInformation: LocalPropertyInformation, factory: LocalSerializerFactory): PropertyWriteStrategy { fun make(name: String, propertyInformation: LocalPropertyInformation, factory: LocalSerializerFactory): PropertyWriteStrategy {
val reader = PropertyReader.make(propertyInformation) val reader = PropertyReader.make(propertyInformation)
val type = propertyInformation.type val type = propertyInformation.type
return if (AMQPTypeIdentifiers.isPrimitive(type.typeIdentifier)) { return if (isPrimitive(type.typeIdentifier)) {
when (type.typeIdentifier) { when (type.typeIdentifier) {
in characterTypes -> AMQPCharPropertyWriteStategy(reader) in characterTypes -> AMQPCharPropertyWriteStategy(reader)
else -> AMQPPropertyWriteStrategy(reader) else -> AMQPPropertyWriteStrategy(reader)
@ -199,7 +200,7 @@ class DescribedTypeReadStrategy(name: String,
override fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext): Any? = override fun readProperty(obj: Any?, schemas: SerializationSchemas, input: DeserializationInput, context: SerializationContext): Any? =
ifThrowsAppend({ nameForDebug }) { ifThrowsAppend({ nameForDebug }) {
input.readObjectOrNull(obj, schemas, type, context) input.readObjectOrNull(redescribe(obj, type), schemas, type, context)
} }
} }

View File

@ -29,6 +29,11 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T>, SerializerFor {
*/ */
open val additionalSerializers: Iterable<CustomSerializer<out Any>> = emptyList() open val additionalSerializers: Iterable<CustomSerializer<out Any>> = emptyList()
/**
* This custom serializer is also allowed to deserialize these classes. This allows us
* to deserialize objects into completely different types, e.g. `A` -> `sandbox.A`.
*/
open val deserializationAliases: Set<Class<*>> = emptySet()
protected abstract val descriptor: Descriptor protected abstract val descriptor: Descriptor
/** /**
@ -53,6 +58,14 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T>, SerializerFor {
abstract fun writeDescribedObject(obj: T, data: Data, type: Type, output: SerializationOutput, abstract fun writeDescribedObject(obj: T, data: Data, type: Type, output: SerializationOutput,
context: SerializationContext) context: SerializationContext)
/**
* [CustomSerializerRegistry.findCustomSerializer] will invoke this method on the [CustomSerializer]
* that it selects to give that serializer an opportunity to customise its behaviour. The serializer
* can also return `null` here, in which case [CustomSerializerRegistry] will proceed as if no
* serializer is available for [declaredType].
*/
open fun specialiseFor(declaredType: Type): AMQPSerializer<T>? = this
/** /**
* This custom serializer represents a sort of symbolic link from a subclass to a super class, where the super * This custom serializer represents a sort of symbolic link from a subclass to a super class, where the super
* class custom serializer is responsible for the "on the wire" format but we want to create a reference to the * class custom serializer is responsible for the "on the wire" format but we want to create a reference to the
@ -110,7 +123,7 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T>, SerializerFor {
*/ */
abstract class CustomSerializerImp<T : Any>(protected val clazz: Class<T>, protected val withInheritance: Boolean) : CustomSerializer<T>() { abstract class CustomSerializerImp<T : Any>(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: Symbol = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${AMQPTypeIdentifiers.nameForType(clazz)}") override val typeDescriptor: Symbol = typeDescriptorFor(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
@ -119,11 +132,13 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T>, SerializerFor {
/** /**
* Additional base features for a custom serializer for a particular class, that excludes subclasses. * 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) 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. * 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) abstract class Implements<T : Any>(clazz: Class<T>) : CustomSerializerImp<T>(clazz, true)
/** /**
@ -133,6 +148,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 * The proxy class must use only types which are either native AMQP or other types for which there are pre-registered
* custom serializers. * custom serializers.
*/ */
@KeepForDJVM
abstract class Proxy<T : Any, P : Any>(clazz: Class<T>, abstract class Proxy<T : Any, P : Any>(clazz: Class<T>,
protected val proxyClass: Class<P>, protected val proxyClass: Class<P>,
protected val factory: LocalSerializerFactory, protected val factory: LocalSerializerFactory,
@ -191,6 +207,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 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. * @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, abstract class ToString<T : Any>(clazz: Class<T>, withInheritance: Boolean = false,
private val maker: (String) -> T = clazz.getConstructor(String::class.java).let { `constructor` -> private val maker: (String) -> T = clazz.getConstructor(String::class.java).let { `constructor` ->
{ string -> `constructor`.newInstance(string) } { string -> `constructor`.newInstance(string) }

View File

@ -44,7 +44,7 @@ interface CustomSerializerRegistry {
* *
* @param clazz The actual class to look for a custom serializer for. * @param clazz The actual class to look for a custom serializer for.
* @param declaredType The declared type to look for a custom serializer for. * @param declaredType The declared type to look for a custom serializer for.
* @return The custom serializer handing the class, if found, or `null`. * @return The custom serializer handling the class, if found, or `null`.
* *
* @throws IllegalCustomSerializerException If a custom serializer identifies itself as the serializer for * @throws IllegalCustomSerializerException If a custom serializer identifies itself as the serializer for
* a class annotated with [CordaSerializable], since all such classes should be serializable via standard object * a class annotated with [CordaSerializable], since all such classes should be serializable via standard object
@ -57,8 +57,10 @@ interface CustomSerializerRegistry {
} }
class CachingCustomSerializerRegistry( class CachingCustomSerializerRegistry(
private val descriptorBasedSerializerRegistry: DescriptorBasedSerializerRegistry) private val descriptorBasedSerializerRegistry: DescriptorBasedSerializerRegistry,
: CustomSerializerRegistry { private val allowedFor: Set<Class<*>>
) : CustomSerializerRegistry {
constructor(descriptorBasedSerializerRegistry: DescriptorBasedSerializerRegistry) : this(descriptorBasedSerializerRegistry, emptySet())
companion object { companion object {
val logger = contextLogger() val logger = contextLogger()
@ -84,7 +86,7 @@ class CachingCustomSerializerRegistry(
} }
private val customSerializersCache: MutableMap<CustomSerializerIdentifier, CustomSerializerLookupResult> = DefaultCacheProvider.createCache() private val customSerializersCache: MutableMap<CustomSerializerIdentifier, CustomSerializerLookupResult> = DefaultCacheProvider.createCache()
private var customSerializers: List<SerializerFor> = emptyList() private val customSerializers: MutableList<SerializerFor> = mutableListOf()
/** /**
* Register a custom serializer for any type that cannot be serialized or deserialized by the default serializer * Register a custom serializer for any type that cannot be serialized or deserialized by the default serializer
@ -93,7 +95,7 @@ class CachingCustomSerializerRegistry(
override fun register(customSerializer: CustomSerializer<out Any>) { override fun register(customSerializer: CustomSerializer<out Any>) {
logger.trace("action=\"Registering custom serializer\", class=\"${customSerializer.type}\"") logger.trace("action=\"Registering custom serializer\", class=\"${customSerializer.type}\"")
if (!customSerializersCache.isEmpty()) { if (customSerializersCache.isNotEmpty()) {
logger.warn("Attempting to register custom serializer $customSerializer.type} in an active cache." + logger.warn("Attempting to register custom serializer $customSerializer.type} in an active cache." +
"All serializers should be registered before the cache comes into use.") "All serializers should be registered before the cache comes into use.")
} }
@ -103,14 +105,23 @@ class CachingCustomSerializerRegistry(
for (additional in customSerializer.additionalSerializers) { for (additional in customSerializer.additionalSerializers) {
register(additional) register(additional)
} }
for (alias in customSerializer.deserializationAliases) {
val aliasDescriptor = typeDescriptorFor(alias)
if (aliasDescriptor != customSerializer.typeDescriptor) {
descriptorBasedSerializerRegistry[aliasDescriptor.toString()] = customSerializer
}
}
customSerializer customSerializer
} }
} }
override fun registerExternal(customSerializer: CorDappCustomSerializer) { override fun registerExternal(customSerializer: CorDappCustomSerializer) {
logger.trace("action=\"Registering external serializer\", class=\"${customSerializer.type}\"") logger.trace("action=\"Registering external serializer\", class=\"${customSerializer.type}\"")
if (!customSerializersCache.isEmpty()) { if (customSerializersCache.isNotEmpty()) {
logger.warn("Attempting to register custom serializer ${customSerializer.type} in an active cache." + logger.warn("Attempting to register custom serializer ${customSerializer.type} in an active cache." +
"All serializers must be registered before the cache comes into use.") "All serializers must be registered before the cache comes into use.")
} }
@ -164,13 +175,21 @@ class CachingCustomSerializerRegistry(
throw IllegalCustomSerializerException(declaredSerializers.first(), clazz) throw IllegalCustomSerializerException(declaredSerializers.first(), clazz)
} }
return declaredSerializers.first() return declaredSerializers.first().let {
if (it is CustomSerializer<Any>) {
it.specialiseFor(declaredType)
} else {
it
}
}
} }
private val Class<*>.isCustomSerializationForbidden: Boolean get() = when { private val Class<*>.isCustomSerializationForbidden: Boolean get() = when {
AMQPTypeIdentifiers.isPrimitive(this) -> true AMQPTypeIdentifiers.isPrimitive(this) -> true
isSubClassOf(CordaThrowable::class.java) -> false isSubClassOf(CordaThrowable::class.java) -> false
allowedFor.any { it.isAssignableFrom(this) } -> false
isAnnotationPresent(CordaSerializable::class.java) -> true isAnnotationPresent(CordaSerializable::class.java) -> true
else -> false else -> false
} }
} }

View File

@ -142,12 +142,12 @@ class DeserializationInput constructor(
envelope) envelope)
} }
internal fun readObjectOrNull(obj: Any?, schema: SerializationSchemas, type: Type, context: SerializationContext fun readObjectOrNull(obj: Any?, schema: SerializationSchemas, type: Type, context: SerializationContext
): Any? { ): Any? {
return if (obj == null) null else readObject(obj, schema, type, context) return if (obj == null) null else readObject(obj, schema, type, context)
} }
internal fun readObject(obj: Any, schemas: SerializationSchemas, type: Type, context: SerializationContext): Any = fun readObject(obj: Any, schemas: SerializationSchemas, type: Type, context: SerializationContext): Any =
if (obj is DescribedType && ReferencedObject.DESCRIPTOR == obj.descriptor) { if (obj is DescribedType && ReferencedObject.DESCRIPTOR == obj.descriptor) {
// It must be a reference to an instance that has already been read, cheaply and quickly returning it by reference. // It must be a reference to an instance that has already been read, cheaply and quickly returning it by reference.
val objectIndex = (obj.described as UnsignedInteger).toInt() val objectIndex = (obj.described as UnsignedInteger).toInt()

View File

@ -17,6 +17,13 @@ interface EvolutionSerializerFactory {
fun getEvolutionSerializer( fun getEvolutionSerializer(
remote: RemoteTypeInformation, remote: RemoteTypeInformation,
local: LocalTypeInformation): AMQPSerializer<Any>? local: LocalTypeInformation): AMQPSerializer<Any>?
/**
* A mapping between Java object types and their equivalent Java primitive types.
* Predominantly for the sake of the DJVM sandbox where e.g. `char` will map to
* sandbox.java.lang.Character instead of java.lang.Character.
*/
val primitiveTypes: Map<Class<*>, Class<*>>
} }
class EvolutionSerializationException(remoteTypeInformation: RemoteTypeInformation, reason: String) class EvolutionSerializationException(remoteTypeInformation: RemoteTypeInformation, reason: String)
@ -32,7 +39,9 @@ class EvolutionSerializationException(remoteTypeInformation: RemoteTypeInformati
class DefaultEvolutionSerializerFactory( class DefaultEvolutionSerializerFactory(
private val localSerializerFactory: LocalSerializerFactory, private val localSerializerFactory: LocalSerializerFactory,
private val classLoader: ClassLoader, private val classLoader: ClassLoader,
private val mustPreserveDataWhenEvolving: Boolean): EvolutionSerializerFactory { private val mustPreserveDataWhenEvolving: Boolean,
override val primitiveTypes: Map<Class<*>, Class<*>>
): EvolutionSerializerFactory {
override fun getEvolutionSerializer(remote: RemoteTypeInformation, override fun getEvolutionSerializer(remote: RemoteTypeInformation,
local: LocalTypeInformation): AMQPSerializer<Any>? = local: LocalTypeInformation): AMQPSerializer<Any>? =
@ -77,7 +86,7 @@ class DefaultEvolutionSerializerFactory(
val localClass = localProperty.type.observedType.asClass() val localClass = localProperty.type.observedType.asClass()
val remoteClass = remoteProperty.type.typeIdentifier.getLocalType(classLoader).asClass() val remoteClass = remoteProperty.type.typeIdentifier.getLocalType(classLoader).asClass()
if (!localClass.isAssignableFrom(remoteClass) && remoteClass != localClass.kotlin.javaPrimitiveType) { if (!localClass.isAssignableFrom(remoteClass) && remoteClass != primitiveTypes[localClass]) {
throw EvolutionSerializationException(this, throw EvolutionSerializationException(this,
"Local type $localClass of property $name is not assignable from remote type $remoteClass") "Local type $localClass of property $name is not assignable from remote type $remoteClass")
} }

View File

@ -6,10 +6,13 @@ import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug import net.corda.core.utilities.debug
import net.corda.core.utilities.trace import net.corda.core.utilities.trace
import net.corda.serialization.internal.model.* import net.corda.serialization.internal.model.*
import net.corda.serialization.internal.model.TypeIdentifier.*
import net.corda.serialization.internal.model.TypeIdentifier.Companion.classLoaderFor
import org.apache.qpid.proton.amqp.Symbol import org.apache.qpid.proton.amqp.Symbol
import java.lang.reflect.ParameterizedType import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type import java.lang.reflect.Type
import java.util.* import java.util.*
import java.util.function.Function
import javax.annotation.concurrent.ThreadSafe import javax.annotation.concurrent.ThreadSafe
/** /**
@ -87,6 +90,7 @@ class DefaultLocalSerializerFactory(
private val fingerPrinter: FingerPrinter, private val fingerPrinter: FingerPrinter,
override val classloader: ClassLoader, override val classloader: ClassLoader,
private val descriptorBasedSerializerRegistry: DescriptorBasedSerializerRegistry, private val descriptorBasedSerializerRegistry: DescriptorBasedSerializerRegistry,
private val primitiveSerializerFactory: Function<Class<*>, AMQPSerializer<Any>>,
private val customSerializerRegistry: CustomSerializerRegistry, private val customSerializerRegistry: CustomSerializerRegistry,
private val onlyCustomSerializers: Boolean) private val onlyCustomSerializers: Boolean)
: LocalSerializerFactory { : LocalSerializerFactory {
@ -137,9 +141,18 @@ class DefaultLocalSerializerFactory(
serializersByTypeId.getOrPut(localTypeInformation.typeIdentifier) { serializersByTypeId.getOrPut(localTypeInformation.typeIdentifier) {
val declaredClass = declaredType.asClass() val declaredClass = declaredType.asClass()
// Any Custom Serializer cached for a ParameterizedType can only be
// found by searching for that exact same type. Searching for its raw
// class will not work!
val declaredGenericType = if (declaredType !is ParameterizedType && localTypeInformation.typeIdentifier is Parameterised) {
localTypeInformation.typeIdentifier.getLocalType(classLoaderFor(declaredClass))
} else {
declaredType
}
// can be useful to enable but will be *extremely* chatty if you do // can be useful to enable but will be *extremely* chatty if you do
logger.trace { "Get Serializer for $declaredClass ${declaredType.typeName}" } logger.trace { "Get Serializer for $declaredClass ${declaredGenericType.typeName}" }
customSerializerRegistry.findCustomSerializer(declaredClass, declaredType)?.apply { return@get this } customSerializerRegistry.findCustomSerializer(declaredClass, declaredGenericType)?.apply { return@get this }
return when (localTypeInformation) { return when (localTypeInformation) {
is LocalTypeInformation.ACollection -> makeDeclaredCollection(localTypeInformation) is LocalTypeInformation.ACollection -> makeDeclaredCollection(localTypeInformation)
@ -226,7 +239,7 @@ class DefaultLocalSerializerFactory(
throw AMQPNotSerializableException( throw AMQPNotSerializableException(
type, type,
"Serializer does not support synthetic classes") "Serializer does not support synthetic classes")
AMQPTypeIdentifiers.isPrimitive(typeInformation.typeIdentifier) -> AMQPPrimitiveSerializer(clazz) AMQPTypeIdentifiers.isPrimitive(typeInformation.typeIdentifier) -> primitiveSerializerFactory.apply(clazz)
else -> makeNonCustomSerializer(type, typeInformation, clazz) else -> makeNonCustomSerializer(type, typeInformation, clazz)
} }
} }

View File

@ -3,7 +3,6 @@ package net.corda.serialization.internal.amqp
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.serialization.internal.model.* import net.corda.serialization.internal.model.*
import org.hibernate.type.descriptor.java.ByteTypeDescriptor
import java.io.NotSerializableException import java.io.NotSerializableException
/** /**

View File

@ -3,18 +3,46 @@ package net.corda.serialization.internal.amqp
import net.corda.core.KeepForDJVM import net.corda.core.KeepForDJVM
import net.corda.core.internal.uncheckedCast import net.corda.core.internal.uncheckedCast
import net.corda.serialization.internal.CordaSerializationMagic import net.corda.serialization.internal.CordaSerializationMagic
import org.apache.qpid.proton.amqp.DescribedType import net.corda.serialization.internal.amqp.AMQPTypeIdentifiers.isPrimitive
import org.apache.qpid.proton.amqp.Symbol import net.corda.serialization.internal.model.TypeIdentifier
import org.apache.qpid.proton.amqp.UnsignedInteger import net.corda.serialization.internal.model.TypeIdentifier.TopType
import org.apache.qpid.proton.amqp.UnsignedLong import net.corda.serialization.internal.model.TypeIdentifier.Companion.forGenericType
import org.apache.qpid.proton.amqp.*
import org.apache.qpid.proton.codec.DescribedTypeConstructor import org.apache.qpid.proton.codec.DescribedTypeConstructor
import java.io.NotSerializableException import java.io.NotSerializableException
import net.corda.serialization.internal.carpenter.Field as CarpenterField import java.lang.reflect.Type
import net.corda.serialization.internal.carpenter.Schema as CarpenterSchema
const val DESCRIPTOR_DOMAIN: String = "net.corda" const val DESCRIPTOR_DOMAIN: String = "net.corda"
val amqpMagic = CordaSerializationMagic("corda".toByteArray() + byteArrayOf(1, 0)) val amqpMagic = CordaSerializationMagic("corda".toByteArray() + byteArrayOf(1, 0))
fun typeDescriptorFor(typeId: TypeIdentifier): Symbol = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${AMQPTypeIdentifiers.nameForType(typeId)}")
fun typeDescriptorFor(type: Type): Symbol = typeDescriptorFor(forGenericType(type))
/**
* Repackages a naked, non-primitive [obj] as a [DescribedType]. If [obj] is primitive, [Binary] or already
* an instance of [DescribedType]] then it is returned unchanged. This allows Corda to search for a serializer
* capable of handling instances of [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 * 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. * [toString] representations generate the associated XML form.

View File

@ -7,9 +7,28 @@ import net.corda.serialization.internal.carpenter.ClassCarpenter
import net.corda.serialization.internal.carpenter.ClassCarpenterImpl import net.corda.serialization.internal.carpenter.ClassCarpenterImpl
import net.corda.serialization.internal.model.* import net.corda.serialization.internal.model.*
import java.io.NotSerializableException import java.io.NotSerializableException
import java.util.Collections.unmodifiableMap
import java.util.function.Function
@KeepForDJVM @KeepForDJVM
object SerializerFactoryBuilder { object SerializerFactoryBuilder {
/**
* The standard mapping of Java object types to Java primitive types.
* The DJVM will need to override these, but probably not anyone else.
*/
@Suppress("unchecked_cast")
private val javaPrimitiveTypes: Map<Class<*>, Class<*>> = unmodifiableMap(listOf(
Boolean::class,
Byte::class,
Char::class,
Double::class,
Float::class,
Int::class,
Long::class,
Short::class
).associate {
klazz -> klazz.javaObjectType to klazz.javaPrimitiveType
}) as Map<Class<*>, Class<*>>
@JvmStatic @JvmStatic
fun build(whitelist: ClassWhitelist, classCarpenter: ClassCarpenter): SerializerFactory { fun build(whitelist: ClassWhitelist, classCarpenter: ClassCarpenter): SerializerFactory {
@ -89,17 +108,19 @@ object SerializerFactoryBuilder {
fingerPrinter, fingerPrinter,
classCarpenter.classloader, classCarpenter.classloader,
descriptorBasedSerializerRegistry, descriptorBasedSerializerRegistry,
Function { clazz -> AMQPPrimitiveSerializer(clazz) },
customSerializerRegistry, customSerializerRegistry,
onlyCustomSerializers) onlyCustomSerializers)
val typeLoader = ClassCarpentingTypeLoader( val typeLoader: TypeLoader = ClassCarpentingTypeLoader(
SchemaBuildingRemoteTypeCarpenter(classCarpenter), SchemaBuildingRemoteTypeCarpenter(classCarpenter),
classCarpenter.classloader) classCarpenter.classloader)
val evolutionSerializerFactory = if (allowEvolution) DefaultEvolutionSerializerFactory( val evolutionSerializerFactory = if (allowEvolution) DefaultEvolutionSerializerFactory(
localSerializerFactory, localSerializerFactory,
classCarpenter.classloader, classCarpenter.classloader,
mustPreserveDataWhenEvolving mustPreserveDataWhenEvolving,
javaPrimitiveTypes
) else NoEvolutionSerializerFactory ) else NoEvolutionSerializerFactory
val remoteSerializerFactory = DefaultRemoteSerializerFactory( val remoteSerializerFactory = DefaultRemoteSerializerFactory(
@ -116,15 +137,17 @@ object SerializerFactoryBuilder {
} }
object NoEvolutionSerializerFactory : EvolutionSerializerFactory { object NoEvolutionSerializerFactory : EvolutionSerializerFactory {
override fun getEvolutionSerializer(remoteTypeInformation: RemoteTypeInformation, localTypeInformation: LocalTypeInformation): AMQPSerializer<Any> { override fun getEvolutionSerializer(remote: RemoteTypeInformation, local: LocalTypeInformation): AMQPSerializer<Any> {
throw NotSerializableException(""" throw NotSerializableException("""
Evolution not permitted. Evolution not permitted.
Remote: Remote:
${remoteTypeInformation.prettyPrint(false)} ${remote.prettyPrint(false)}
Local: Local:
${localTypeInformation.prettyPrint(false)} ${local.prettyPrint(false)}
""") """)
} }
override val primitiveTypes: Map<Class<*>, Class<*>> = emptyMap()
} }

View File

@ -1,7 +1,5 @@
package net.corda.serialization.internal.model package net.corda.serialization.internal.model
import net.corda.core.serialization.ClassWhitelist
import net.corda.serialization.internal.amqp.*
import java.lang.reflect.* import java.lang.reflect.*
/** /**
@ -54,7 +52,7 @@ class ConfigurableLocalTypeModel(private val typeModelConfiguration: LocalTypeMo
private val typeInformationCache = DefaultCacheProvider.createCache<TypeIdentifier, LocalTypeInformation>() private val typeInformationCache = DefaultCacheProvider.createCache<TypeIdentifier, LocalTypeInformation>()
/** /**
* We need to provide the [TypeInformationBuilder] with a temporary local cache, so that it doesn't leak * We need to provide the [LocalTypeInformationBuilder] with a temporary local cache, so that it doesn't leak
* [LocalTypeInformation] with unpatched cycles into the global cache where other threads can access them * [LocalTypeInformation] with unpatched cycles into the global cache where other threads can access them
* before we've patched the cycles up. * before we've patched the cycles up.
*/ */

View File

@ -63,6 +63,8 @@ sealed class TypeIdentifier {
// This method has locking. So we memo the value here. // This method has locking. So we memo the value here.
private val systemClassLoader: ClassLoader = ClassLoader.getSystemClassLoader() private val systemClassLoader: ClassLoader = ClassLoader.getSystemClassLoader()
fun classLoaderFor(clazz: Class<*>): ClassLoader = clazz.classLoader ?: systemClassLoader
/** /**
* Obtain the [TypeIdentifier] for an erased Java class. * Obtain the [TypeIdentifier] for an erased Java class.
* *
@ -206,7 +208,11 @@ sealed class TypeIdentifier {
override fun toString() = "Parameterised(${prettyPrint()})" override fun toString() = "Parameterised(${prettyPrint()})"
override fun getLocalType(classLoader: ClassLoader): Type { 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. This will happen
// for "transformative" class loaders, i.e. `A` -> `sandbox.A`.
val rawType = classLoader.loadClass(name)
if (rawType.typeParameters.size != parameters.size) { if (rawType.typeParameters.size != parameters.size) {
throw IncompatibleTypeIdentifierException( throw IncompatibleTypeIdentifierException(
"Class $rawType expects ${rawType.typeParameters.size} type arguments, " + "Class $rawType expects ${rawType.typeParameters.size} type arguments, " +

View File

@ -4,7 +4,9 @@ import com.google.common.hash.Hashing
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.toBase64 import net.corda.core.utilities.toBase64
import net.corda.serialization.internal.amqp.* import net.corda.serialization.internal.amqp.*
import java.io.NotSerializableException import net.corda.serialization.internal.model.TypeIdentifier.*
import net.corda.serialization.internal.model.TypeIdentifier.Companion.classLoaderFor
import java.lang.reflect.ParameterizedType
/** /**
* A fingerprinter that fingerprints [LocalTypeInformation]. * A fingerprinter that fingerprints [LocalTypeInformation].
@ -34,10 +36,14 @@ class TypeModellingFingerPrinter(
private val cache: MutableMap<TypeIdentifier, String> = DefaultCacheProvider.createCache() private val cache: MutableMap<TypeIdentifier, String> = DefaultCacheProvider.createCache()
override fun fingerprint(typeInformation: LocalTypeInformation): String = override fun fingerprint(typeInformation: LocalTypeInformation): String =
cache.computeIfAbsent(typeInformation.typeIdentifier) { /*
FingerPrintingState( * We cannot use ConcurrentMap.computeIfAbsent() here because it requires
customTypeDescriptorLookup, * that the map not be re-entered during the computation function. And
FingerprintWriter(debugEnabled)).fingerprint(typeInformation) * the Fingerprinter cannot guarantee that.
*/
cache.getOrPut(typeInformation.typeIdentifier) {
FingerPrintingState(customTypeDescriptorLookup, FingerprintWriter(debugEnabled))
.fingerprint(typeInformation)
} }
} }
@ -224,7 +230,22 @@ private class FingerPrintingState(
// Give any custom serializers loaded into the factory the chance to supply their own type-descriptors // Give any custom serializers loaded into the factory the chance to supply their own type-descriptors
private fun fingerprintWithCustomSerializerOrElse(type: LocalTypeInformation, defaultAction: () -> Unit) { private fun fingerprintWithCustomSerializerOrElse(type: LocalTypeInformation, defaultAction: () -> Unit) {
val customTypeDescriptor = customSerializerRegistry.findCustomSerializer(type.observedType.asClass(), type.observedType)?.typeDescriptor?.toString() val observedType = type.observedType
val observedClass = observedType.asClass()
// Any Custom Serializer cached for a ParameterizedType can only be
// found by searching for that exact same type. Searching for its raw
// class will not work!
val observedGenericType = if (observedType !is ParameterizedType && type.typeIdentifier is Parameterised) {
type.typeIdentifier.getLocalType(classLoaderFor(observedClass))
} else {
observedType
}
val customTypeDescriptor = customSerializerRegistry.findCustomSerializer(
clazz = observedClass,
declaredType = observedGenericType
)?.typeDescriptor?.toString()
if (customTypeDescriptor != null) writer.write(customTypeDescriptor) if (customTypeDescriptor != null) writer.write(customTypeDescriptor)
else defaultAction() else defaultAction()
} }