mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
CORDA-3536: Load DJVM serialization types more precisely to avoid runtime warnings. (#5896)
* Load DJVM serialization types more precisely to avoid runtime warnings. * Remove unnecessary string concatenation from debug and trace log messages.
This commit is contained in:
parent
a4cada4a2e
commit
c8a21cb8d2
@ -30,7 +30,7 @@ snakeYamlVersion=1.19
|
||||
caffeineVersion=2.7.0
|
||||
metricsVersion=4.1.0
|
||||
metricsNewRelicVersion=1.1.1
|
||||
djvmVersion=1.0-RC06
|
||||
djvmVersion=1.0-RC07
|
||||
deterministicRtVersion=1.0-RC02
|
||||
openSourceBranch=https://github.com/corda/corda/blob/release/os/4.4
|
||||
openSourceSamplesBranch=https://github.com/corda/samples/blob/release-V4
|
||||
|
@ -0,0 +1,9 @@
|
||||
package net.corda.serialization.djvm.deserializers
|
||||
|
||||
import java.util.function.Predicate
|
||||
|
||||
class CheckEnum : Predicate<Class<*>> {
|
||||
override fun test(clazz: Class<*>): Boolean {
|
||||
return clazz.isEnum
|
||||
}
|
||||
}
|
@ -52,11 +52,14 @@ import java.math.BigInteger
|
||||
import java.util.Date
|
||||
import java.util.UUID
|
||||
import java.util.function.Function
|
||||
import java.util.function.Predicate
|
||||
|
||||
class SandboxSerializationSchemeBuilder(
|
||||
private val classLoader: SandboxClassLoader,
|
||||
private val sandboxBasicInput: Function<in Any?, out Any?>,
|
||||
private val rawTaskFactory: Function<in Any, out Function<in Any?, out Any?>>,
|
||||
private val taskFactory: Function<Class<out Function<*, *>>, out Function<in Any?, out Any?>>,
|
||||
private val predicateFactory: Function<Class<out Predicate<*>>, out Predicate<in Any?>>,
|
||||
private val customSerializerClassNames: Set<String>,
|
||||
private val serializationWhitelistNames: Set<String>,
|
||||
private val serializerFactoryFactory: SerializerFactoryFactory
|
||||
@ -66,7 +69,6 @@ class SandboxSerializationSchemeBuilder(
|
||||
}
|
||||
|
||||
private fun getSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||
val taskFactory = rawTaskFactory.compose(classLoader.createSandboxFunction())
|
||||
return serializerFactoryFactory.make(context).apply {
|
||||
register(SandboxBitSetSerializer(classLoader, taskFactory, this))
|
||||
register(SandboxCertPathSerializer(classLoader, taskFactory, this))
|
||||
@ -100,7 +102,7 @@ class SandboxSerializationSchemeBuilder(
|
||||
register(SandboxCharacterSerializer(classLoader, sandboxBasicInput))
|
||||
register(SandboxCollectionSerializer(classLoader, taskFactory, this))
|
||||
register(SandboxMapSerializer(classLoader, taskFactory, this))
|
||||
register(SandboxEnumSerializer(classLoader, taskFactory, this))
|
||||
register(SandboxEnumSerializer(classLoader, taskFactory, predicateFactory, this))
|
||||
register(SandboxPublicKeySerializer(classLoader, taskFactory))
|
||||
register(SandboxToStringSerializer(BigDecimal::class.java, classLoader, rawTaskFactory, sandboxBasicInput))
|
||||
register(SandboxToStringSerializer(BigInteger::class.java, classLoader, rawTaskFactory, sandboxBasicInput))
|
||||
|
@ -14,9 +14,11 @@ import net.corda.serialization.internal.amqp.SerializerFactory
|
||||
import net.corda.serialization.internal.amqp.SerializerFactoryFactory
|
||||
import net.corda.serialization.internal.amqp.WhitelistBasedTypeModelConfiguration
|
||||
import net.corda.serialization.internal.amqp.createClassCarpenter
|
||||
import net.corda.serialization.internal.model.BaseLocalTypes
|
||||
import net.corda.serialization.internal.model.ClassCarpentingTypeLoader
|
||||
import net.corda.serialization.internal.model.ConfigurableLocalTypeModel
|
||||
import net.corda.serialization.internal.model.SchemaBuildingRemoteTypeCarpenter
|
||||
import net.corda.serialization.internal.amqp.SerializerFactoryBuilder
|
||||
import net.corda.serialization.internal.model.TypeLoader
|
||||
import net.corda.serialization.internal.model.TypeModellingFingerPrinter
|
||||
import java.lang.Boolean
|
||||
@ -36,7 +38,8 @@ import java.util.function.Predicate
|
||||
* This has all been lovingly copied from [SerializerFactoryBuilder].
|
||||
*/
|
||||
class SandboxSerializerFactoryFactory(
|
||||
private val primitiveSerializerFactory: Function<Class<*>, AMQPSerializer<Any>>
|
||||
private val primitiveSerializerFactory: Function<Class<*>, AMQPSerializer<Any>>,
|
||||
private val localTypes: BaseLocalTypes
|
||||
) : SerializerFactoryFactory {
|
||||
|
||||
override fun make(context: SerializationContext): SerializerFactory {
|
||||
@ -65,7 +68,11 @@ class SandboxSerializerFactoryFactory(
|
||||
)
|
||||
|
||||
val localTypeModel = ConfigurableLocalTypeModel(
|
||||
WhitelistBasedTypeModelConfiguration(context.whitelist, customSerializerRegistry)
|
||||
WhitelistBasedTypeModelConfiguration(
|
||||
whitelist = context.whitelist,
|
||||
customSerializerRegistry = customSerializerRegistry,
|
||||
baseTypes = localTypes
|
||||
)
|
||||
)
|
||||
|
||||
val fingerPrinter = TypeModellingFingerPrinter(customSerializerRegistry)
|
||||
|
@ -7,14 +7,21 @@ import net.corda.core.serialization.SerializationFactory
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.serialization.internal.SerializationEnvironment
|
||||
import net.corda.core.utilities.ByteSequence
|
||||
import net.corda.djvm.rewiring.createRawPredicateFactory
|
||||
import net.corda.djvm.rewiring.createSandboxPredicate
|
||||
import net.corda.djvm.rewiring.SandboxClassLoader
|
||||
import net.corda.serialization.djvm.deserializers.CheckEnum
|
||||
import net.corda.serialization.djvm.deserializers.DescribeEnum
|
||||
import net.corda.serialization.djvm.serializers.PrimitiveSerializer
|
||||
import net.corda.serialization.internal.GlobalTransientClassWhiteList
|
||||
import net.corda.serialization.internal.SerializationContextImpl
|
||||
import net.corda.serialization.internal.SerializationFactoryImpl
|
||||
import net.corda.serialization.internal.amqp.AMQPSerializer
|
||||
import net.corda.serialization.internal.amqp.amqpMagic
|
||||
import net.corda.serialization.internal.model.BaseLocalTypes
|
||||
import java.util.EnumSet
|
||||
import java.util.function.Function
|
||||
import java.util.function.Predicate
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun SandboxClassLoader.toSandboxAnyClass(clazz: Class<*>): Class<Any> {
|
||||
@ -42,20 +49,40 @@ fun createSandboxSerializationEnv(
|
||||
encoding = null
|
||||
)
|
||||
|
||||
val rawTaskFactory = classLoader.createRawTaskFactory()
|
||||
val sandboxBasicInput = classLoader.createBasicInput()
|
||||
val rawTaskFactory = classLoader.createRawTaskFactory()
|
||||
val taskFactory = rawTaskFactory.compose(classLoader.createSandboxFunction())
|
||||
val predicateFactory = classLoader.createRawPredicateFactory().compose(classLoader.createSandboxPredicate())
|
||||
|
||||
val primitiveSerializerFactory: Function<Class<*>, AMQPSerializer<Any>> = Function { clazz ->
|
||||
PrimitiveSerializer(clazz, sandboxBasicInput)
|
||||
}
|
||||
@Suppress("unchecked_cast")
|
||||
val isEnumPredicate = predicateFactory.apply(CheckEnum::class.java) as Predicate<Class<*>>
|
||||
@Suppress("unchecked_cast")
|
||||
val enumConstants = taskFactory.apply(DescribeEnum::class.java) as Function<Class<*>, Array<out Any>>
|
||||
|
||||
val sandboxLocalTypes = BaseLocalTypes(
|
||||
collectionClass = classLoader.toSandboxClass(Collection::class.java),
|
||||
enumSetClass = classLoader.toSandboxClass(EnumSet::class.java),
|
||||
exceptionClass = classLoader.toSandboxClass(Exception::class.java),
|
||||
mapClass = classLoader.toSandboxClass(Map::class.java),
|
||||
stringClass = classLoader.toSandboxClass(String::class.java),
|
||||
isEnum = isEnumPredicate,
|
||||
enumConstants = enumConstants
|
||||
)
|
||||
val schemeBuilder = SandboxSerializationSchemeBuilder(
|
||||
classLoader = classLoader,
|
||||
sandboxBasicInput = sandboxBasicInput,
|
||||
rawTaskFactory = rawTaskFactory,
|
||||
taskFactory = taskFactory,
|
||||
predicateFactory = predicateFactory,
|
||||
customSerializerClassNames = customSerializerClassNames,
|
||||
serializationWhitelistNames = serializationWhitelistNames,
|
||||
serializerFactoryFactory = SandboxSerializerFactoryFactory(primitiveSerializerFactory)
|
||||
serializerFactoryFactory = SandboxSerializerFactoryFactory(
|
||||
primitiveSerializerFactory = primitiveSerializerFactory,
|
||||
localTypes = sandboxLocalTypes
|
||||
)
|
||||
)
|
||||
val factory = SerializationFactoryImpl(mutableMapOf()).apply {
|
||||
registerScheme(schemeBuilder.buildFor(p2pContext))
|
||||
|
@ -35,9 +35,9 @@ class SandboxCollectionSerializer(
|
||||
|
||||
private val unsupportedTypes: Set<Class<Any>> = listOf(
|
||||
EnumSet::class.java
|
||||
).map {
|
||||
).mapTo(LinkedHashSet()) {
|
||||
classLoader.toSandboxAnyClass(it)
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
// The order matters here - the first match should be the most specific one.
|
||||
// Kotlin preserves the ordering for us by associating into a LinkedHashMap.
|
||||
@ -95,9 +95,9 @@ private class ConcreteCollectionSerializer(
|
||||
override val typeDescriptor: Symbol by lazy {
|
||||
factory.createDescriptor(
|
||||
LocalTypeInformation.ACollection(
|
||||
observedType = declaredType.rawType,
|
||||
observedType = declaredType,
|
||||
typeIdentifier = TypeIdentifier.forGenericType(declaredType),
|
||||
elementType =factory.getTypeInformation(declaredType.actualTypeArguments[0])
|
||||
elementType = factory.getTypeInformation(declaredType.actualTypeArguments[0])
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -109,7 +109,7 @@ private class ConcreteCollectionSerializer(
|
||||
context: SerializationContext
|
||||
): Any {
|
||||
val inboundType = type.actualTypeArguments[0]
|
||||
return ifThrowsAppend({ type.typeName }) {
|
||||
return ifThrowsAppend(type::getTypeName) {
|
||||
val args = (obj as List<*>).map {
|
||||
input.readObjectOrNull(redescribe(it, inboundType), schemas, inboundType, context)
|
||||
}.toTypedArray()
|
||||
|
@ -2,6 +2,7 @@ package net.corda.serialization.djvm.serializers
|
||||
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.djvm.rewiring.SandboxClassLoader
|
||||
import net.corda.serialization.djvm.deserializers.CheckEnum
|
||||
import net.corda.serialization.djvm.deserializers.DescribeEnum
|
||||
import net.corda.serialization.djvm.toSandboxAnyClass
|
||||
import net.corda.serialization.internal.amqp.AMQPNotSerializableException
|
||||
@ -19,23 +20,32 @@ import org.apache.qpid.proton.amqp.Symbol
|
||||
import org.apache.qpid.proton.codec.Data
|
||||
import java.lang.reflect.Type
|
||||
import java.util.function.Function
|
||||
import java.util.function.Predicate
|
||||
|
||||
class SandboxEnumSerializer(
|
||||
classLoader: SandboxClassLoader,
|
||||
taskFactory: Function<Class<out Function<*, *>>, out Function<in Any?, out Any?>>,
|
||||
predicateFactory: Function<Class<out Predicate<*>>, out Predicate<in Any?>>,
|
||||
private val localFactory: LocalSerializerFactory
|
||||
) : CustomSerializer.Implements<Any>(clazz = classLoader.toSandboxAnyClass(Enum::class.java)) {
|
||||
@Suppress("unchecked_cast")
|
||||
private val describer: Function<Class<*>, Array<Any>>
|
||||
private val describeEnum: Function<Class<*>, Array<Any>>
|
||||
= taskFactory.apply(DescribeEnum::class.java) as Function<Class<*>, Array<Any>>
|
||||
@Suppress("unchecked_cast")
|
||||
private val isEnum: Predicate<Class<*>>
|
||||
= predicateFactory.apply(CheckEnum::class.java) as Predicate<Class<*>>
|
||||
|
||||
override val schemaForDocumentation: Schema = Schema(emptyList())
|
||||
|
||||
override fun isSerializerFor(clazz: Class<*>): Boolean {
|
||||
return super.isSerializerFor(clazz) && isEnum.test(clazz)
|
||||
}
|
||||
|
||||
override fun specialiseFor(declaredType: Type): AMQPSerializer<Any>? {
|
||||
if (declaredType !is Class<*>) {
|
||||
return null
|
||||
}
|
||||
val members = describer.apply(declaredType)
|
||||
val members = describeEnum.apply(declaredType)
|
||||
return ConcreteEnumSerializer(declaredType, members, localFactory)
|
||||
}
|
||||
|
||||
@ -68,7 +78,7 @@ private class ConcreteEnumSerializer(
|
||||
LocalTypeInformation.AnEnum(
|
||||
declaredType,
|
||||
TypeIdentifier.forGenericType(declaredType),
|
||||
members.map { it.toString() },
|
||||
members.map(Any::toString),
|
||||
emptyList(),
|
||||
EnumTransforms.empty
|
||||
)
|
||||
|
@ -85,9 +85,9 @@ private class ConcreteMapSerializer(
|
||||
override val typeDescriptor: Symbol by lazy {
|
||||
factory.createDescriptor(
|
||||
LocalTypeInformation.AMap(
|
||||
observedType = declaredType.rawType,
|
||||
observedType = declaredType,
|
||||
typeIdentifier = TypeIdentifier.forGenericType(declaredType),
|
||||
keyType =factory.getTypeInformation(declaredType.actualTypeArguments[0]),
|
||||
keyType = factory.getTypeInformation(declaredType.actualTypeArguments[0]),
|
||||
valueType = factory.getTypeInformation(declaredType.actualTypeArguments[1])
|
||||
)
|
||||
)
|
||||
@ -101,7 +101,7 @@ private class ConcreteMapSerializer(
|
||||
): Any {
|
||||
val inboundKeyType = type.actualTypeArguments[0]
|
||||
val inboundValueType = type.actualTypeArguments[1]
|
||||
return ifThrowsAppend({ type.typeName }) {
|
||||
return ifThrowsAppend(type::getTypeName) {
|
||||
val entries = (obj as Map<*, *>).map {
|
||||
arrayOf(
|
||||
input.readObjectOrNull(redescribe(it.key, inboundKeyType), schemas, inboundKeyType, context),
|
||||
|
@ -7,6 +7,10 @@ import net.corda.serialization.djvm.SandboxType.KOTLIN
|
||||
import net.corda.serialization.internal.SerializationFactoryImpl
|
||||
import net.corda.serialization.internal.amqp.SerializerFactory
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.ACollection
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.AnEnum
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.AMap
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.Abstract
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.Atomic
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.Opaque
|
||||
import org.apache.qpid.proton.amqp.Decimal128
|
||||
@ -19,11 +23,10 @@ import org.apache.qpid.proton.amqp.UnsignedLong
|
||||
import org.apache.qpid.proton.amqp.UnsignedShort
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import java.util.Date
|
||||
import java.util.EnumSet
|
||||
import java.util.UUID
|
||||
|
||||
@ExtendWith(LocalSerialization::class)
|
||||
class LocalTypeModelTest : TestBase(KOTLIN) {
|
||||
private val serializerFactory: SerializerFactory get() {
|
||||
val factory = SerializationFactory.defaultFactory as SerializationFactoryImpl
|
||||
@ -47,7 +50,7 @@ class LocalTypeModelTest : TestBase(KOTLIN) {
|
||||
@Test
|
||||
fun testString() = sandbox {
|
||||
_contextSerializationEnv.set(createSandboxSerializationEnv(classLoader))
|
||||
assertLocalType<Opaque>(sandbox<String>(classLoader))
|
||||
assertLocalType<Atomic>(sandbox<String>(classLoader))
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -158,4 +161,31 @@ class LocalTypeModelTest : TestBase(KOTLIN) {
|
||||
_contextSerializationEnv.set(createSandboxSerializationEnv(classLoader))
|
||||
assertLocalType<Opaque>(sandbox<Date>(classLoader))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCollection() = sandbox {
|
||||
_contextSerializationEnv.set(createSandboxSerializationEnv(classLoader))
|
||||
assertLocalType<ACollection>(sandbox<Collection<*>>(classLoader))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEnum() = sandbox {
|
||||
_contextSerializationEnv.set(createSandboxSerializationEnv(classLoader))
|
||||
assertLocalType<AnEnum>(sandbox<ExampleEnum>(classLoader))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEnumSet() = sandbox {
|
||||
_contextSerializationEnv.set(createSandboxSerializationEnv(classLoader))
|
||||
assertLocalType<Abstract>(sandbox<EnumSet<*>>(classLoader))
|
||||
|
||||
val exampleEnumSet = EnumSet.noneOf(ExampleEnum::class.java)
|
||||
assertLocalType<Opaque>(classLoader.toSandboxClass(exampleEnumSet::class.java))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMap() = sandbox {
|
||||
_contextSerializationEnv.set(createSandboxSerializationEnv(classLoader))
|
||||
assertLocalType<AMap>(sandbox<Map<*,*>>(classLoader))
|
||||
}
|
||||
}
|
@ -71,27 +71,43 @@ abstract class TestBase(type: SandboxType) {
|
||||
SandboxType.JAVA -> TESTING_LIBRARIES.filter { isDirectory(it) }
|
||||
}
|
||||
|
||||
fun sandbox(action: SandboxRuntimeContext.() -> Unit) {
|
||||
return sandbox(WARNING, emptySet(), emptySet(), action)
|
||||
inline fun sandbox(crossinline action: SandboxRuntimeContext.() -> Unit) {
|
||||
sandbox(Consumer { ctx -> action(ctx) })
|
||||
}
|
||||
|
||||
fun sandbox(visibleAnnotations: Set<Class<out Annotation>>, action: SandboxRuntimeContext.() -> Unit) {
|
||||
return sandbox(WARNING, visibleAnnotations, emptySet(), action)
|
||||
fun sandbox(action: Consumer<SandboxRuntimeContext>) {
|
||||
sandbox(WARNING, emptySet(), emptySet(), action)
|
||||
}
|
||||
|
||||
inline fun sandbox(visibleAnnotations: Set<Class<out Annotation>>, crossinline action: SandboxRuntimeContext.() -> Unit) {
|
||||
sandbox(visibleAnnotations, Consumer { ctx -> action(ctx) })
|
||||
}
|
||||
|
||||
fun sandbox(visibleAnnotations: Set<Class<out Annotation>>, action: Consumer<SandboxRuntimeContext>) {
|
||||
sandbox(WARNING, visibleAnnotations, emptySet(), action)
|
||||
}
|
||||
|
||||
inline fun sandbox(
|
||||
visibleAnnotations: Set<Class<out Annotation>>,
|
||||
sandboxOnlyAnnotations: Set<String>,
|
||||
crossinline action: SandboxRuntimeContext.() -> Unit
|
||||
) {
|
||||
sandbox(visibleAnnotations, sandboxOnlyAnnotations, Consumer { ctx -> action(ctx) })
|
||||
}
|
||||
|
||||
fun sandbox(
|
||||
visibleAnnotations: Set<Class<out Annotation>>,
|
||||
sandboxOnlyAnnotations: Set<String>,
|
||||
action: SandboxRuntimeContext.() -> Unit
|
||||
action: Consumer<SandboxRuntimeContext>
|
||||
) {
|
||||
return sandbox(WARNING, visibleAnnotations, sandboxOnlyAnnotations, action)
|
||||
sandbox(WARNING, visibleAnnotations, sandboxOnlyAnnotations, action)
|
||||
}
|
||||
|
||||
fun sandbox(
|
||||
minimumSeverityLevel: Severity,
|
||||
visibleAnnotations: Set<Class<out Annotation>>,
|
||||
sandboxOnlyAnnotations: Set<String>,
|
||||
action: SandboxRuntimeContext.() -> Unit
|
||||
action: Consumer<SandboxRuntimeContext>
|
||||
) {
|
||||
var thrownException: Throwable? = null
|
||||
thread(start = false) {
|
||||
@ -100,9 +116,7 @@ abstract class TestBase(type: SandboxType) {
|
||||
it.setMinimumSeverityLevel(minimumSeverityLevel)
|
||||
it.setSandboxOnlyAnnotations(sandboxOnlyAnnotations)
|
||||
it.setVisibleAnnotations(visibleAnnotations)
|
||||
})).use(Consumer { ctx ->
|
||||
ctx.action()
|
||||
})
|
||||
})).use(action)
|
||||
}
|
||||
}.apply {
|
||||
uncaughtExceptionHandler = Thread.UncaughtExceptionHandler { _, ex ->
|
||||
|
@ -4,6 +4,8 @@ import net.corda.core.CordaThrowable
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.debug
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.serialization.internal.model.DefaultCacheProvider
|
||||
import net.corda.serialization.internal.model.TypeIdentifier
|
||||
import java.lang.reflect.Type
|
||||
@ -93,10 +95,10 @@ class CachingCustomSerializerRegistry(
|
||||
* that expects to find getters and a constructor with a parameter for each property.
|
||||
*/
|
||||
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.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.")
|
||||
}
|
||||
|
||||
@ -119,7 +121,7 @@ class CachingCustomSerializerRegistry(
|
||||
}
|
||||
|
||||
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.isNotEmpty()) {
|
||||
logger.warn("Attempting to register custom serializer ${customSerializer.type} in an active cache." +
|
||||
@ -153,8 +155,7 @@ class CachingCustomSerializerRegistry(
|
||||
(declaredSuperClass == null
|
||||
|| !customSerializer.isSerializerFor(declaredSuperClass)
|
||||
|| !customSerializer.revealSubclassesInSchema) -> {
|
||||
logger.debug("action=\"Using custom serializer\", class=${clazz.typeName}, " +
|
||||
"declaredType=${declaredType.typeName}")
|
||||
logger.debug { "action=\"Using custom serializer\", class=${clazz.typeName}, declaredType=${declaredType.typeName}" }
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
customSerializer as? AMQPSerializer<Any>
|
||||
|
@ -7,6 +7,7 @@ import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.utilities.ByteSequence
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.serialization.internal.*
|
||||
import net.corda.serialization.internal.model.TypeIdentifier
|
||||
import org.apache.qpid.proton.amqp.Binary
|
||||
@ -119,7 +120,7 @@ class DeserializationInput constructor(
|
||||
des {
|
||||
val envelope = getEnvelope(bytes, context.encodingWhitelist)
|
||||
|
||||
logger.trace("deserialize blob scheme=\"${envelope.schema}\"")
|
||||
logger.trace { "deserialize blob scheme=\"${envelope.schema}\"" }
|
||||
|
||||
doReadObject(envelope, clazz, context)
|
||||
}
|
||||
|
@ -3,8 +3,10 @@ package net.corda.serialization.internal.amqp
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.internal.MissingSerializerException
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.serialization.internal.model.*
|
||||
import java.io.NotSerializableException
|
||||
import java.util.Collections.singletonList
|
||||
|
||||
/**
|
||||
* A factory that knows how to create serializers to deserialize values sent to us by remote parties.
|
||||
@ -65,7 +67,7 @@ class DefaultRemoteSerializerFactory(
|
||||
): AMQPSerializer<Any> =
|
||||
// If we have seen this descriptor before, we assume we have seen everything in this schema before.
|
||||
descriptorBasedSerializerRegistry.getOrBuild(typeDescriptor) {
|
||||
logger.trace("get Serializer descriptor=$typeDescriptor")
|
||||
logger.trace { "get Serializer descriptor=$typeDescriptor" }
|
||||
|
||||
// Interpret all of the types in the schema into RemoteTypeInformation, and reflect that into LocalTypeInformation.
|
||||
val remoteTypeInformationMap = remoteTypeModel.interpret(schema)
|
||||
@ -75,7 +77,7 @@ class DefaultRemoteSerializerFactory(
|
||||
// This will save us having to re-interpret the entire schema on re-entry when deserialising individual property values.
|
||||
val serializers = reflected.mapValues { (descriptor, remoteLocalPair) ->
|
||||
descriptorBasedSerializerRegistry.getOrBuild(descriptor) {
|
||||
getUncached(remoteLocalPair.remoteTypeInformation, remoteLocalPair.localTypeInformation)
|
||||
getUncached(remoteLocalPair.remoteTypeInformation, remoteLocalPair.localTypeInformation, context)
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,7 +90,8 @@ class DefaultRemoteSerializerFactory(
|
||||
|
||||
private fun getUncached(
|
||||
remoteTypeInformation: RemoteTypeInformation,
|
||||
localTypeInformation: LocalTypeInformation
|
||||
localTypeInformation: LocalTypeInformation,
|
||||
context: SerializationContext
|
||||
): AMQPSerializer<Any> {
|
||||
val remoteDescriptor = remoteTypeInformation.typeDescriptor
|
||||
|
||||
@ -109,6 +112,13 @@ class DefaultRemoteSerializerFactory(
|
||||
evolutionSerializerFactory.getEvolutionSerializer(remoteTypeInformation, localTypeInformation)
|
||||
?: localSerializer
|
||||
|
||||
// The type descriptors are never going to match when we deserialise into
|
||||
// the DJVM's sandbox, but we don't want the node logs to fill up with
|
||||
// Big 'n Scary warnings either. Assume that the local serializer is fine
|
||||
// provided the local type is the same one we expect when loading the
|
||||
// remote class.
|
||||
remoteTypeInformation.isCompatibleWith(localTypeInformation, context) -> localSerializer
|
||||
|
||||
// Descriptors don't match, and something is probably broken, but we let the framework do what it can with the local
|
||||
// serialiser (BlobInspectorTest uniquely breaks if we throw an exception here, and passes if we just warn and continue).
|
||||
else -> {
|
||||
@ -134,7 +144,7 @@ ${localTypeInformation.prettyPrint(false)}
|
||||
}
|
||||
|
||||
return remoteInformation.mapValues { (_, remoteInformation) ->
|
||||
RemoteAndLocalTypeInformation(remoteInformation, localInformationByIdentifier[remoteInformation.typeIdentifier]!!)
|
||||
RemoteAndLocalTypeInformation(remoteInformation, localInformationByIdentifier.getValue(remoteInformation.typeIdentifier))
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,7 +155,16 @@ ${localTypeInformation.prettyPrint(false)}
|
||||
}
|
||||
|
||||
private fun RemoteTypeInformation.isDeserialisableWithoutEvolutionTo(localTypeInformation: LocalTypeInformation) =
|
||||
this is RemoteTypeInformation.Parameterised &&
|
||||
this is RemoteTypeInformation.Parameterised &&
|
||||
(localTypeInformation is LocalTypeInformation.ACollection ||
|
||||
localTypeInformation is LocalTypeInformation.AMap)
|
||||
|
||||
private fun RemoteTypeInformation.isCompatibleWith(
|
||||
localTypeInformation: LocalTypeInformation,
|
||||
context: SerializationContext
|
||||
): Boolean {
|
||||
val localTypes = typeLoader.load(singletonList(this), context)
|
||||
return localTypes.size == 1
|
||||
&& localTypeInformation.observedType == localTypes.values.first()
|
||||
}
|
||||
}
|
@ -3,9 +3,6 @@ package net.corda.serialization.internal.amqp
|
||||
import net.corda.core.KeepForDJVM
|
||||
import net.corda.core.serialization.CordaSerializationTransformEnumDefault
|
||||
import net.corda.core.serialization.CordaSerializationTransformRename
|
||||
import net.corda.serialization.internal.NotSerializableDetailedException
|
||||
import net.corda.serialization.internal.model.EnumTransforms
|
||||
import net.corda.serialization.internal.model.InvalidEnumTransformsException
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation
|
||||
import org.apache.qpid.proton.amqp.DescribedType
|
||||
import org.apache.qpid.proton.codec.DescribedTypeConstructor
|
||||
|
@ -2,18 +2,27 @@ package net.corda.serialization.internal.amqp
|
||||
|
||||
import com.google.common.primitives.Primitives
|
||||
import net.corda.core.serialization.ClassWhitelist
|
||||
import net.corda.serialization.internal.model.BaseLocalTypes
|
||||
import net.corda.serialization.internal.model.LocalTypeModelConfiguration
|
||||
import org.apache.qpid.proton.amqp.*
|
||||
import java.lang.reflect.Type
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
import java.util.EnumSet
|
||||
import java.util.UUID
|
||||
import java.util.function.Function
|
||||
import java.util.function.Predicate
|
||||
|
||||
/**
|
||||
* [LocalTypeModelConfiguration] based on a [ClassWhitelist]
|
||||
*/
|
||||
class WhitelistBasedTypeModelConfiguration(
|
||||
private val whitelist: ClassWhitelist,
|
||||
private val customSerializerRegistry: CustomSerializerRegistry)
|
||||
: LocalTypeModelConfiguration {
|
||||
private val customSerializerRegistry: CustomSerializerRegistry,
|
||||
override val baseTypes: BaseLocalTypes
|
||||
) : LocalTypeModelConfiguration {
|
||||
constructor(whitelist: ClassWhitelist, customSerializerRegistry: CustomSerializerRegistry)
|
||||
: this(whitelist, customSerializerRegistry, DEFAULT_BASE_TYPES)
|
||||
|
||||
override fun isExcluded(type: Type): Boolean = whitelist.isNotWhitelisted(type.asClass())
|
||||
override fun isOpaque(type: Type): Boolean = Primitives.unwrap(type.asClass()) in opaqueTypes ||
|
||||
customSerializerRegistry.findCustomSerializer(type.asClass(), type) != null
|
||||
@ -43,3 +52,13 @@ private val opaqueTypes = setOf(
|
||||
String::class.java,
|
||||
Symbol::class.java
|
||||
)
|
||||
|
||||
private val DEFAULT_BASE_TYPES = BaseLocalTypes(
|
||||
collectionClass = Collection::class.java,
|
||||
enumSetClass = EnumSet::class.java,
|
||||
exceptionClass = Exception::class.java,
|
||||
mapClass = Map::class.java,
|
||||
stringClass = String::class.java,
|
||||
isEnum = Predicate { clazz -> clazz.isEnum },
|
||||
enumConstants = Function { clazz -> clazz.enumConstants }
|
||||
)
|
@ -7,11 +7,24 @@ import net.corda.core.serialization.ConstructorForDeserialization
|
||||
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
|
||||
import net.corda.serialization.internal.NotSerializableDetailedException
|
||||
import net.corda.serialization.internal.amqp.*
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.Abstract
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.AnArray
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.AnEnum
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.AnInterface
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.Atomic
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.ACollection
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.AMap
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.Composable
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.Cycle
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.NonComposable
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.Opaque
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.Singleton
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.Top
|
||||
import net.corda.serialization.internal.model.LocalTypeInformation.Unknown
|
||||
import java.io.NotSerializableException
|
||||
import java.lang.reflect.Method
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.lang.reflect.Type
|
||||
import java.util.*
|
||||
import kotlin.collections.LinkedHashMap
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
@ -35,8 +48,10 @@ import kotlin.reflect.jvm.javaType
|
||||
internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
|
||||
var resolutionContext: Type? = null,
|
||||
var visited: Set<TypeIdentifier> = emptySet(),
|
||||
val cycles: MutableList<LocalTypeInformation.Cycle> = mutableListOf(),
|
||||
val cycles: MutableList<Cycle> = mutableListOf(),
|
||||
var validateProperties: Boolean = true) {
|
||||
private val baseTypes = lookup.baseTypes
|
||||
|
||||
/**
|
||||
* If we are examining the type of a read-only property, or a type flagged as [Opaque], then we do not need to warn
|
||||
* if the [LocalTypeInformation] for that type (or any of its related types) is [LocalTypeInformation.NonComposable].
|
||||
@ -55,7 +70,7 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
|
||||
* Recursively build [LocalTypeInformation] for the given [Type] and [TypeIdentifier]
|
||||
*/
|
||||
fun build(type: Type, typeIdentifier: TypeIdentifier): LocalTypeInformation =
|
||||
if (typeIdentifier in visited) LocalTypeInformation.Cycle(type, typeIdentifier).apply { cycles.add(this) }
|
||||
if (typeIdentifier in visited) Cycle(type, typeIdentifier).apply { cycles.add(this) }
|
||||
else lookup.findOrBuild(type, typeIdentifier) { isOpaque ->
|
||||
val previous = visited
|
||||
try {
|
||||
@ -78,15 +93,16 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
|
||||
private fun buildIfNotFound(type: Type, typeIdentifier: TypeIdentifier, isOpaque: Boolean): LocalTypeInformation {
|
||||
val rawType = type.asClass()
|
||||
return when (typeIdentifier) {
|
||||
is TypeIdentifier.TopType -> LocalTypeInformation.Top
|
||||
is TypeIdentifier.UnknownType -> LocalTypeInformation.Unknown
|
||||
is TypeIdentifier.TopType -> Top
|
||||
is TypeIdentifier.UnknownType -> Unknown
|
||||
is TypeIdentifier.Unparameterised,
|
||||
is TypeIdentifier.Erased -> buildForClass(rawType, typeIdentifier, isOpaque)
|
||||
is TypeIdentifier.ArrayOf -> {
|
||||
LocalTypeInformation.AnArray(
|
||||
type,
|
||||
typeIdentifier,
|
||||
resolveAndBuild(type.componentType()))
|
||||
AnArray(
|
||||
type,
|
||||
typeIdentifier,
|
||||
resolveAndBuild(type.componentType())
|
||||
)
|
||||
}
|
||||
is TypeIdentifier.Parameterised -> buildForParameterised(rawType, type as ParameterizedType, typeIdentifier, isOpaque)
|
||||
}
|
||||
@ -94,38 +110,41 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
|
||||
|
||||
private fun buildForClass(type: Class<*>, typeIdentifier: TypeIdentifier, isOpaque: Boolean): LocalTypeInformation = withContext(type) {
|
||||
when {
|
||||
Collection::class.java.isAssignableFrom(type) &&
|
||||
!EnumSet::class.java.isAssignableFrom(type) -> LocalTypeInformation.ACollection(type, typeIdentifier, LocalTypeInformation.Unknown)
|
||||
Map::class.java.isAssignableFrom(type) -> LocalTypeInformation.AMap(type, typeIdentifier, LocalTypeInformation.Unknown, LocalTypeInformation.Unknown)
|
||||
type == String::class.java -> LocalTypeInformation.Atomic(String::class.java, typeIdentifier)
|
||||
type.kotlin.javaPrimitiveType != null ->LocalTypeInformation.Atomic(type, typeIdentifier)
|
||||
type.isEnum -> LocalTypeInformation.AnEnum(
|
||||
baseTypes.collectionClass.isAssignableFrom(type) &&
|
||||
!baseTypes.enumSetClass.isAssignableFrom(type) -> ACollection(type, typeIdentifier, Unknown)
|
||||
baseTypes.mapClass.isAssignableFrom(type) -> AMap(type, typeIdentifier, Unknown, Unknown)
|
||||
type === baseTypes.stringClass -> Atomic(type, typeIdentifier)
|
||||
type.kotlin.javaPrimitiveType != null -> Atomic(type, typeIdentifier)
|
||||
baseTypes.isEnum.test(type) -> baseTypes.enumConstants.apply(type).let { enumConstants ->
|
||||
AnEnum(
|
||||
type,
|
||||
typeIdentifier,
|
||||
type.enumConstants.map { it.toString() },
|
||||
enumConstants.map(Any::toString),
|
||||
buildInterfaceInformation(type),
|
||||
getEnumTransforms(type))
|
||||
type.kotlinObjectInstance != null -> LocalTypeInformation.Singleton(
|
||||
getEnumTransforms(type, enumConstants)
|
||||
)
|
||||
}
|
||||
type.kotlinObjectInstance != null -> Singleton(
|
||||
type,
|
||||
typeIdentifier,
|
||||
buildSuperclassInformation(type),
|
||||
buildInterfaceInformation(type))
|
||||
type.isInterface -> buildInterface(type, typeIdentifier, emptyList())
|
||||
type.isAbstractClass -> buildAbstract(type, typeIdentifier, emptyList())
|
||||
isOpaque -> LocalTypeInformation.Opaque(
|
||||
isOpaque -> Opaque(
|
||||
type,
|
||||
typeIdentifier,
|
||||
suppressValidation { buildNonAtomic(type, type, typeIdentifier, emptyList()) })
|
||||
Exception::class.java.isAssignableFrom(type.asClass()) -> suppressValidation {
|
||||
baseTypes.exceptionClass.isAssignableFrom(type.asClass()) -> suppressValidation {
|
||||
buildNonAtomic(type, type, typeIdentifier, emptyList())
|
||||
}
|
||||
else -> buildNonAtomic(type, type, typeIdentifier, emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
private fun getEnumTransforms(type: Class<*>): EnumTransforms {
|
||||
private fun getEnumTransforms(type: Class<*>, enumConstants: Array<out Any>): EnumTransforms {
|
||||
try {
|
||||
val constants = type.enumConstants.asSequence().mapIndexed { index, constant -> constant.toString() to index }.toMap()
|
||||
val constants = enumConstants.asSequence().mapIndexed { index, constant -> constant.toString() to index }.toMap()
|
||||
return EnumTransforms.build(TransformsAnnotationProcessor.getTransformsSchema(type), constants)
|
||||
} catch (e: InvalidEnumTransformsException) {
|
||||
throw NotSerializableDetailedException(type.name, e.message!!)
|
||||
@ -138,16 +157,16 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
|
||||
typeIdentifier: TypeIdentifier.Parameterised,
|
||||
isOpaque: Boolean): LocalTypeInformation = withContext(type) {
|
||||
when {
|
||||
Collection::class.java.isAssignableFrom(rawType) &&
|
||||
!EnumSet::class.java.isAssignableFrom(rawType) ->
|
||||
LocalTypeInformation.ACollection(type, typeIdentifier, buildTypeParameterInformation(type)[0])
|
||||
Map::class.java.isAssignableFrom(rawType) -> {
|
||||
baseTypes.collectionClass.isAssignableFrom(rawType) &&
|
||||
!baseTypes.enumSetClass.isAssignableFrom(rawType) ->
|
||||
ACollection(type, typeIdentifier, buildTypeParameterInformation(type)[0])
|
||||
baseTypes.mapClass.isAssignableFrom(rawType) -> {
|
||||
val (keyType, valueType) = buildTypeParameterInformation(type)
|
||||
LocalTypeInformation.AMap(type, typeIdentifier, keyType, valueType)
|
||||
AMap(type, typeIdentifier, keyType, valueType)
|
||||
}
|
||||
rawType.isInterface -> buildInterface(type, typeIdentifier, buildTypeParameterInformation(type))
|
||||
rawType.isAbstractClass -> buildAbstract(type, typeIdentifier, buildTypeParameterInformation(type))
|
||||
isOpaque -> LocalTypeInformation.Opaque(rawType,
|
||||
isOpaque -> Opaque(rawType,
|
||||
typeIdentifier,
|
||||
suppressValidation { buildNonAtomic(rawType, type, typeIdentifier, buildTypeParameterInformation(type)) })
|
||||
else -> buildNonAtomic(rawType, type, typeIdentifier, buildTypeParameterInformation(type))
|
||||
@ -155,23 +174,25 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
|
||||
}
|
||||
|
||||
private fun buildAbstract(type: Type, typeIdentifier: TypeIdentifier,
|
||||
typeParameters: List<LocalTypeInformation>): LocalTypeInformation.Abstract =
|
||||
LocalTypeInformation.Abstract(
|
||||
type,
|
||||
typeIdentifier,
|
||||
buildReadOnlyProperties(type.asClass()),
|
||||
buildSuperclassInformation(type),
|
||||
buildInterfaceInformation(type),
|
||||
typeParameters)
|
||||
typeParameters: List<LocalTypeInformation>): Abstract =
|
||||
Abstract(
|
||||
type,
|
||||
typeIdentifier,
|
||||
buildReadOnlyProperties(type.asClass()),
|
||||
buildSuperclassInformation(type),
|
||||
buildInterfaceInformation(type),
|
||||
typeParameters
|
||||
)
|
||||
|
||||
private fun buildInterface(type: Type, typeIdentifier: TypeIdentifier,
|
||||
typeParameters: List<LocalTypeInformation>): LocalTypeInformation.AnInterface =
|
||||
LocalTypeInformation.AnInterface(
|
||||
type,
|
||||
typeIdentifier,
|
||||
buildReadOnlyProperties(type.asClass()),
|
||||
buildInterfaceInformation(type),
|
||||
typeParameters)
|
||||
typeParameters: List<LocalTypeInformation>): AnInterface =
|
||||
AnInterface(
|
||||
type,
|
||||
typeIdentifier,
|
||||
buildReadOnlyProperties(type.asClass()),
|
||||
buildInterfaceInformation(type),
|
||||
typeParameters
|
||||
)
|
||||
|
||||
private inline fun <T> withContext(newContext: Type, block: LocalTypeInformationBuilder.() -> T): T {
|
||||
val previous = resolutionContext
|
||||
@ -196,11 +217,11 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
|
||||
private fun buildNonAtomic(rawType: Class<*>, type: Type, typeIdentifier: TypeIdentifier, typeParameterInformation: List<LocalTypeInformation>): LocalTypeInformation {
|
||||
val superclassInformation = buildSuperclassInformation(type)
|
||||
val interfaceInformation = buildInterfaceInformation(type)
|
||||
val observedConstructor = constructorForDeserialization(type) ?: return LocalTypeInformation.NonComposable(
|
||||
val observedConstructor = constructorForDeserialization(type) ?: return NonComposable(
|
||||
observedType = type,
|
||||
typeIdentifier = typeIdentifier,
|
||||
constructor = null,
|
||||
properties = if (rawType == Class::class.java) {
|
||||
properties = if (rawType === Class::class.java) {
|
||||
// Do NOT drill down into the internals of java.lang.Class.
|
||||
emptyMap()
|
||||
} else {
|
||||
@ -220,7 +241,7 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
|
||||
if (!propertiesSatisfyConstructor(constructorInformation, properties)) {
|
||||
val missingConstructorProperties = missingMandatoryConstructorProperties(constructorInformation, properties)
|
||||
val missingParameters = missingConstructorProperties.map(LocalConstructorParameterInformation::name)
|
||||
return LocalTypeInformation.NonComposable(
|
||||
return NonComposable(
|
||||
observedType = type,
|
||||
typeIdentifier = typeIdentifier,
|
||||
constructor = constructorInformation,
|
||||
@ -229,16 +250,16 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
|
||||
interfaces = interfaceInformation,
|
||||
typeParameters = typeParameterInformation,
|
||||
nonComposableSubtypes = missingConstructorProperties
|
||||
.filterIsInstanceTo(LinkedHashSet(), LocalTypeInformation.NonComposable::class.java),
|
||||
.filterIsInstanceTo(LinkedHashSet(), NonComposable::class.java),
|
||||
reason = "Mandatory constructor parameters $missingParameters are missing from the readable properties ${properties.keys}",
|
||||
remedy = "Either provide getters or readable fields for $missingParameters, or provide a custom serializer for this type"
|
||||
)
|
||||
}
|
||||
|
||||
val nonComposableProperties = properties.filterValues { it.type is LocalTypeInformation.NonComposable }
|
||||
val nonComposableProperties = properties.filterValues { it.type is NonComposable }
|
||||
|
||||
if (nonComposableProperties.isNotEmpty()) {
|
||||
return LocalTypeInformation.NonComposable(
|
||||
return NonComposable(
|
||||
observedType = type,
|
||||
typeIdentifier = typeIdentifier,
|
||||
constructor = constructorInformation,
|
||||
@ -247,7 +268,7 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
|
||||
interfaces = interfaceInformation,
|
||||
typeParameters = typeParameterInformation,
|
||||
nonComposableSubtypes = nonComposableProperties.values.mapTo(LinkedHashSet()) {
|
||||
it.type as LocalTypeInformation.NonComposable
|
||||
it.type as NonComposable
|
||||
},
|
||||
reason = nonComposablePropertiesErrorReason(nonComposableProperties),
|
||||
remedy = "Either ensure that the properties ${nonComposableProperties.keys} are serializable, or provide a custom serializer for this type"
|
||||
@ -260,7 +281,7 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
|
||||
EvolutionConstructorInformation(evolutionConstructorInformation, evolutionProperties)
|
||||
}
|
||||
|
||||
return LocalTypeInformation.Composable(type, typeIdentifier, constructorInformation, evolutionConstructors, properties,
|
||||
return Composable(type, typeIdentifier, constructorInformation, evolutionConstructors, properties,
|
||||
superclassInformation, interfaceInformation, typeParameterInformation)
|
||||
}
|
||||
|
||||
@ -268,13 +289,13 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
|
||||
private fun propertiesSatisfyConstructor(constructorInformation: LocalConstructorInformation, properties: Map<PropertyName, LocalPropertyInformation>): Boolean {
|
||||
if (!constructorInformation.hasParameters) return true
|
||||
|
||||
val indicesAddressedByProperties = properties.values.asSequence().mapNotNull {
|
||||
val indicesAddressedByProperties = properties.values.asSequence().mapNotNullTo(LinkedHashSet()) {
|
||||
when (it) {
|
||||
is LocalPropertyInformation.ConstructorPairedProperty -> it.constructorSlot.parameterIndex
|
||||
is LocalPropertyInformation.PrivateConstructorPairedProperty -> it.constructorSlot.parameterIndex
|
||||
else -> null
|
||||
}
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
return (constructorInformation.parameters.indices).none { index ->
|
||||
constructorInformation.parameters[index].isMandatory && index !in indicesAddressedByProperties
|
||||
@ -287,13 +308,13 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
|
||||
): List<LocalConstructorParameterInformation> {
|
||||
if (!constructorInformation.hasParameters) return emptyList()
|
||||
|
||||
val indicesAddressedByProperties = properties.values.asSequence().mapNotNull {
|
||||
val indicesAddressedByProperties = properties.values.asSequence().mapNotNullTo(LinkedHashSet()) {
|
||||
when (it) {
|
||||
is LocalPropertyInformation.ConstructorPairedProperty -> it.constructorSlot.parameterIndex
|
||||
is LocalPropertyInformation.PrivateConstructorPairedProperty -> it.constructorSlot.parameterIndex
|
||||
else -> null
|
||||
}
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
return (constructorInformation.parameters.indices).mapNotNull { index ->
|
||||
val parameter = constructorInformation.parameters[index]
|
||||
@ -306,7 +327,7 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
|
||||
|
||||
private fun nonComposablePropertiesErrorReason(nonComposableProperties: Map<PropertyName, LocalPropertyInformation>): String {
|
||||
val reasons = nonComposableProperties.entries.joinToString("\n") { (key, value) ->
|
||||
"$key [${value.type.observedType}]: ${(value.type as LocalTypeInformation.NonComposable).reason}"
|
||||
"$key [${value.type.observedType}]: ${(value.type as NonComposable).reason}"
|
||||
.replace("\n", "\n ")
|
||||
}
|
||||
return "Has properties ${nonComposableProperties.keys} of types that are not serializable:\n" + reasons
|
||||
@ -374,7 +395,7 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
|
||||
}
|
||||
|
||||
val property = makeConstructorPairedProperty(
|
||||
constructorParameterIndices[normalisedName]!!,
|
||||
constructorParameterIndices.getValue(normalisedName),
|
||||
descriptor,
|
||||
constructorInformation)
|
||||
if (property == null) null else normalisedName to property
|
||||
|
@ -1,6 +1,8 @@
|
||||
package net.corda.serialization.internal.model
|
||||
|
||||
import java.lang.reflect.*
|
||||
import java.util.function.Function
|
||||
import java.util.function.Predicate
|
||||
|
||||
/**
|
||||
* Provides a means for looking up [LocalTypeInformation] by [Type] and [TypeIdentifier], falling back to building it
|
||||
@ -24,6 +26,12 @@ interface LocalTypeLookup {
|
||||
* because it is not whitelisted.
|
||||
*/
|
||||
fun isExcluded(type: Type): Boolean
|
||||
|
||||
/**
|
||||
* These classes are used by [LocalTypeInformationBuilder] to
|
||||
* build the correct [LocalTypeInformation] subclasses.
|
||||
*/
|
||||
val baseTypes: BaseLocalTypes
|
||||
}
|
||||
|
||||
/**
|
||||
@ -72,6 +80,8 @@ class ConfigurableLocalTypeModel(private val typeModelConfiguration: LocalTypeMo
|
||||
|
||||
override fun isExcluded(type: Type): Boolean = typeModelConfiguration.isExcluded(type)
|
||||
|
||||
override val baseTypes = typeModelConfiguration.baseTypes
|
||||
|
||||
/**
|
||||
* Merge the local cache back into the global cache, once we've finished traversal (and patched all cycles).
|
||||
*/
|
||||
@ -111,4 +121,20 @@ interface LocalTypeModelConfiguration {
|
||||
* [LocalTypeInformation], usually because they are not included in a whitelist.
|
||||
*/
|
||||
fun isExcluded(type: Type): Boolean
|
||||
|
||||
/**
|
||||
* These classes are used by [LocalTypeInformationBuilder] to
|
||||
* build the correct [LocalTypeInformation] subclasses.
|
||||
*/
|
||||
val baseTypes: BaseLocalTypes
|
||||
}
|
||||
|
||||
class BaseLocalTypes(
|
||||
val collectionClass: Class<*>,
|
||||
val enumSetClass: Class<*>,
|
||||
val exceptionClass: Class<*>,
|
||||
val mapClass: Class<*>,
|
||||
val stringClass: Class<*>,
|
||||
val isEnum: Predicate<Class<*>>,
|
||||
val enumConstants: Function<Class<*>, Array<out Any>>
|
||||
)
|
||||
|
@ -2,8 +2,6 @@ package net.corda.serialization.internal.model
|
||||
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.serialization.internal.carpenter.*
|
||||
import java.io.NotSerializableException
|
||||
import java.lang.ClassCastException
|
||||
import java.lang.reflect.Type
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user