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:
Chris Rankin 2020-01-28 09:26:13 +00:00 committed by Christian Sailer
parent a4cada4a2e
commit c8a21cb8d2
18 changed files with 288 additions and 107 deletions

View File

@ -30,7 +30,7 @@ snakeYamlVersion=1.19
caffeineVersion=2.7.0 caffeineVersion=2.7.0
metricsVersion=4.1.0 metricsVersion=4.1.0
metricsNewRelicVersion=1.1.1 metricsNewRelicVersion=1.1.1
djvmVersion=1.0-RC06 djvmVersion=1.0-RC07
deterministicRtVersion=1.0-RC02 deterministicRtVersion=1.0-RC02
openSourceBranch=https://github.com/corda/corda/blob/release/os/4.4 openSourceBranch=https://github.com/corda/corda/blob/release/os/4.4
openSourceSamplesBranch=https://github.com/corda/samples/blob/release-V4 openSourceSamplesBranch=https://github.com/corda/samples/blob/release-V4

View File

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

View File

@ -52,11 +52,14 @@ import java.math.BigInteger
import java.util.Date import java.util.Date
import java.util.UUID import java.util.UUID
import java.util.function.Function import java.util.function.Function
import java.util.function.Predicate
class SandboxSerializationSchemeBuilder( class SandboxSerializationSchemeBuilder(
private val classLoader: SandboxClassLoader, private val classLoader: SandboxClassLoader,
private val sandboxBasicInput: Function<in Any?, out Any?>, private val sandboxBasicInput: Function<in Any?, out Any?>,
private val rawTaskFactory: Function<in Any, out 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 customSerializerClassNames: Set<String>,
private val serializationWhitelistNames: Set<String>, private val serializationWhitelistNames: Set<String>,
private val serializerFactoryFactory: SerializerFactoryFactory private val serializerFactoryFactory: SerializerFactoryFactory
@ -66,7 +69,6 @@ class SandboxSerializationSchemeBuilder(
} }
private fun getSerializerFactory(context: SerializationContext): SerializerFactory { private fun getSerializerFactory(context: SerializationContext): SerializerFactory {
val taskFactory = rawTaskFactory.compose(classLoader.createSandboxFunction())
return serializerFactoryFactory.make(context).apply { return serializerFactoryFactory.make(context).apply {
register(SandboxBitSetSerializer(classLoader, taskFactory, this)) register(SandboxBitSetSerializer(classLoader, taskFactory, this))
register(SandboxCertPathSerializer(classLoader, taskFactory, this)) register(SandboxCertPathSerializer(classLoader, taskFactory, this))
@ -100,7 +102,7 @@ class SandboxSerializationSchemeBuilder(
register(SandboxCharacterSerializer(classLoader, sandboxBasicInput)) register(SandboxCharacterSerializer(classLoader, sandboxBasicInput))
register(SandboxCollectionSerializer(classLoader, taskFactory, this)) register(SandboxCollectionSerializer(classLoader, taskFactory, this))
register(SandboxMapSerializer(classLoader, taskFactory, this)) register(SandboxMapSerializer(classLoader, taskFactory, this))
register(SandboxEnumSerializer(classLoader, taskFactory, this)) register(SandboxEnumSerializer(classLoader, taskFactory, predicateFactory, this))
register(SandboxPublicKeySerializer(classLoader, taskFactory)) register(SandboxPublicKeySerializer(classLoader, taskFactory))
register(SandboxToStringSerializer(BigDecimal::class.java, classLoader, rawTaskFactory, sandboxBasicInput)) register(SandboxToStringSerializer(BigDecimal::class.java, classLoader, rawTaskFactory, sandboxBasicInput))
register(SandboxToStringSerializer(BigInteger::class.java, classLoader, rawTaskFactory, sandboxBasicInput)) register(SandboxToStringSerializer(BigInteger::class.java, classLoader, rawTaskFactory, sandboxBasicInput))

View File

@ -14,9 +14,11 @@ import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.amqp.SerializerFactoryFactory import net.corda.serialization.internal.amqp.SerializerFactoryFactory
import net.corda.serialization.internal.amqp.WhitelistBasedTypeModelConfiguration import net.corda.serialization.internal.amqp.WhitelistBasedTypeModelConfiguration
import net.corda.serialization.internal.amqp.createClassCarpenter 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.ClassCarpentingTypeLoader
import net.corda.serialization.internal.model.ConfigurableLocalTypeModel import net.corda.serialization.internal.model.ConfigurableLocalTypeModel
import net.corda.serialization.internal.model.SchemaBuildingRemoteTypeCarpenter 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.TypeLoader
import net.corda.serialization.internal.model.TypeModellingFingerPrinter import net.corda.serialization.internal.model.TypeModellingFingerPrinter
import java.lang.Boolean import java.lang.Boolean
@ -36,7 +38,8 @@ import java.util.function.Predicate
* This has all been lovingly copied from [SerializerFactoryBuilder]. * This has all been lovingly copied from [SerializerFactoryBuilder].
*/ */
class SandboxSerializerFactoryFactory( class SandboxSerializerFactoryFactory(
private val primitiveSerializerFactory: Function<Class<*>, AMQPSerializer<Any>> private val primitiveSerializerFactory: Function<Class<*>, AMQPSerializer<Any>>,
private val localTypes: BaseLocalTypes
) : SerializerFactoryFactory { ) : SerializerFactoryFactory {
override fun make(context: SerializationContext): SerializerFactory { override fun make(context: SerializationContext): SerializerFactory {
@ -65,7 +68,11 @@ class SandboxSerializerFactoryFactory(
) )
val localTypeModel = ConfigurableLocalTypeModel( val localTypeModel = ConfigurableLocalTypeModel(
WhitelistBasedTypeModelConfiguration(context.whitelist, customSerializerRegistry) WhitelistBasedTypeModelConfiguration(
whitelist = context.whitelist,
customSerializerRegistry = customSerializerRegistry,
baseTypes = localTypes
)
) )
val fingerPrinter = TypeModellingFingerPrinter(customSerializerRegistry) val fingerPrinter = TypeModellingFingerPrinter(customSerializerRegistry)

View File

@ -7,14 +7,21 @@ import net.corda.core.serialization.SerializationFactory
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.internal.SerializationEnvironment import net.corda.core.serialization.internal.SerializationEnvironment
import net.corda.core.utilities.ByteSequence 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.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.djvm.serializers.PrimitiveSerializer
import net.corda.serialization.internal.GlobalTransientClassWhiteList import net.corda.serialization.internal.GlobalTransientClassWhiteList
import net.corda.serialization.internal.SerializationContextImpl import net.corda.serialization.internal.SerializationContextImpl
import net.corda.serialization.internal.SerializationFactoryImpl import net.corda.serialization.internal.SerializationFactoryImpl
import net.corda.serialization.internal.amqp.AMQPSerializer import net.corda.serialization.internal.amqp.AMQPSerializer
import net.corda.serialization.internal.amqp.amqpMagic 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.Function
import java.util.function.Predicate
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun SandboxClassLoader.toSandboxAnyClass(clazz: Class<*>): Class<Any> { inline fun SandboxClassLoader.toSandboxAnyClass(clazz: Class<*>): Class<Any> {
@ -42,20 +49,40 @@ fun createSandboxSerializationEnv(
encoding = null encoding = null
) )
val rawTaskFactory = classLoader.createRawTaskFactory()
val sandboxBasicInput = classLoader.createBasicInput() 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 -> val primitiveSerializerFactory: Function<Class<*>, AMQPSerializer<Any>> = Function { clazz ->
PrimitiveSerializer(clazz, sandboxBasicInput) 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( val schemeBuilder = SandboxSerializationSchemeBuilder(
classLoader = classLoader, classLoader = classLoader,
sandboxBasicInput = sandboxBasicInput, sandboxBasicInput = sandboxBasicInput,
rawTaskFactory = rawTaskFactory, rawTaskFactory = rawTaskFactory,
taskFactory = taskFactory,
predicateFactory = predicateFactory,
customSerializerClassNames = customSerializerClassNames, customSerializerClassNames = customSerializerClassNames,
serializationWhitelistNames = serializationWhitelistNames, serializationWhitelistNames = serializationWhitelistNames,
serializerFactoryFactory = SandboxSerializerFactoryFactory(primitiveSerializerFactory) serializerFactoryFactory = SandboxSerializerFactoryFactory(
primitiveSerializerFactory = primitiveSerializerFactory,
localTypes = sandboxLocalTypes
)
) )
val factory = SerializationFactoryImpl(mutableMapOf()).apply { val factory = SerializationFactoryImpl(mutableMapOf()).apply {
registerScheme(schemeBuilder.buildFor(p2pContext)) registerScheme(schemeBuilder.buildFor(p2pContext))

View File

@ -35,9 +35,9 @@ class SandboxCollectionSerializer(
private val unsupportedTypes: Set<Class<Any>> = listOf( private val unsupportedTypes: Set<Class<Any>> = listOf(
EnumSet::class.java EnumSet::class.java
).map { ).mapTo(LinkedHashSet()) {
classLoader.toSandboxAnyClass(it) classLoader.toSandboxAnyClass(it)
}.toSet() }
// The order matters here - the first match should be the most specific one. // The order matters here - the first match should be the most specific one.
// Kotlin preserves the ordering for us by associating into a LinkedHashMap. // Kotlin preserves the ordering for us by associating into a LinkedHashMap.
@ -95,9 +95,9 @@ private class ConcreteCollectionSerializer(
override val typeDescriptor: Symbol by lazy { override val typeDescriptor: Symbol by lazy {
factory.createDescriptor( factory.createDescriptor(
LocalTypeInformation.ACollection( LocalTypeInformation.ACollection(
observedType = declaredType.rawType, observedType = declaredType,
typeIdentifier = TypeIdentifier.forGenericType(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 context: SerializationContext
): Any { ): Any {
val inboundType = type.actualTypeArguments[0] val inboundType = type.actualTypeArguments[0]
return ifThrowsAppend({ type.typeName }) { return ifThrowsAppend(type::getTypeName) {
val args = (obj as List<*>).map { val args = (obj as List<*>).map {
input.readObjectOrNull(redescribe(it, inboundType), schemas, inboundType, context) input.readObjectOrNull(redescribe(it, inboundType), schemas, inboundType, context)
}.toTypedArray() }.toTypedArray()

View File

@ -2,6 +2,7 @@ package net.corda.serialization.djvm.serializers
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.djvm.rewiring.SandboxClassLoader 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.deserializers.DescribeEnum
import net.corda.serialization.djvm.toSandboxAnyClass import net.corda.serialization.djvm.toSandboxAnyClass
import net.corda.serialization.internal.amqp.AMQPNotSerializableException 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 org.apache.qpid.proton.codec.Data
import java.lang.reflect.Type import java.lang.reflect.Type
import java.util.function.Function import java.util.function.Function
import java.util.function.Predicate
class SandboxEnumSerializer( class SandboxEnumSerializer(
classLoader: SandboxClassLoader, classLoader: SandboxClassLoader,
taskFactory: Function<Class<out Function<*, *>>, out Function<in Any?, out Any?>>, taskFactory: Function<Class<out Function<*, *>>, out Function<in Any?, out Any?>>,
predicateFactory: Function<Class<out Predicate<*>>, out Predicate<in Any?>>,
private val localFactory: LocalSerializerFactory private val localFactory: LocalSerializerFactory
) : CustomSerializer.Implements<Any>(clazz = classLoader.toSandboxAnyClass(Enum::class.java)) { ) : CustomSerializer.Implements<Any>(clazz = classLoader.toSandboxAnyClass(Enum::class.java)) {
@Suppress("unchecked_cast") @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>> = 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 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>? { override fun specialiseFor(declaredType: Type): AMQPSerializer<Any>? {
if (declaredType !is Class<*>) { if (declaredType !is Class<*>) {
return null return null
} }
val members = describer.apply(declaredType) val members = describeEnum.apply(declaredType)
return ConcreteEnumSerializer(declaredType, members, localFactory) return ConcreteEnumSerializer(declaredType, members, localFactory)
} }
@ -68,7 +78,7 @@ private class ConcreteEnumSerializer(
LocalTypeInformation.AnEnum( LocalTypeInformation.AnEnum(
declaredType, declaredType,
TypeIdentifier.forGenericType(declaredType), TypeIdentifier.forGenericType(declaredType),
members.map { it.toString() }, members.map(Any::toString),
emptyList(), emptyList(),
EnumTransforms.empty EnumTransforms.empty
) )

View File

@ -85,9 +85,9 @@ private class ConcreteMapSerializer(
override val typeDescriptor: Symbol by lazy { override val typeDescriptor: Symbol by lazy {
factory.createDescriptor( factory.createDescriptor(
LocalTypeInformation.AMap( LocalTypeInformation.AMap(
observedType = declaredType.rawType, observedType = declaredType,
typeIdentifier = TypeIdentifier.forGenericType(declaredType), typeIdentifier = TypeIdentifier.forGenericType(declaredType),
keyType =factory.getTypeInformation(declaredType.actualTypeArguments[0]), keyType = factory.getTypeInformation(declaredType.actualTypeArguments[0]),
valueType = factory.getTypeInformation(declaredType.actualTypeArguments[1]) valueType = factory.getTypeInformation(declaredType.actualTypeArguments[1])
) )
) )
@ -101,7 +101,7 @@ private class ConcreteMapSerializer(
): Any { ): Any {
val inboundKeyType = type.actualTypeArguments[0] val inboundKeyType = type.actualTypeArguments[0]
val inboundValueType = type.actualTypeArguments[1] val inboundValueType = type.actualTypeArguments[1]
return ifThrowsAppend({ type.typeName }) { return ifThrowsAppend(type::getTypeName) {
val entries = (obj as Map<*, *>).map { val entries = (obj as Map<*, *>).map {
arrayOf( arrayOf(
input.readObjectOrNull(redescribe(it.key, inboundKeyType), schemas, inboundKeyType, context), input.readObjectOrNull(redescribe(it.key, inboundKeyType), schemas, inboundKeyType, context),

View File

@ -7,6 +7,10 @@ import net.corda.serialization.djvm.SandboxType.KOTLIN
import net.corda.serialization.internal.SerializationFactoryImpl import net.corda.serialization.internal.SerializationFactoryImpl
import net.corda.serialization.internal.amqp.SerializerFactory import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.model.LocalTypeInformation 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.Atomic
import net.corda.serialization.internal.model.LocalTypeInformation.Opaque import net.corda.serialization.internal.model.LocalTypeInformation.Opaque
import org.apache.qpid.proton.amqp.Decimal128 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.apache.qpid.proton.amqp.UnsignedShort
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import java.util.Date import java.util.Date
import java.util.EnumSet
import java.util.UUID import java.util.UUID
@ExtendWith(LocalSerialization::class)
class LocalTypeModelTest : TestBase(KOTLIN) { class LocalTypeModelTest : TestBase(KOTLIN) {
private val serializerFactory: SerializerFactory get() { private val serializerFactory: SerializerFactory get() {
val factory = SerializationFactory.defaultFactory as SerializationFactoryImpl val factory = SerializationFactory.defaultFactory as SerializationFactoryImpl
@ -47,7 +50,7 @@ class LocalTypeModelTest : TestBase(KOTLIN) {
@Test @Test
fun testString() = sandbox { fun testString() = sandbox {
_contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader))
assertLocalType<Opaque>(sandbox<String>(classLoader)) assertLocalType<Atomic>(sandbox<String>(classLoader))
} }
@Test @Test
@ -158,4 +161,31 @@ class LocalTypeModelTest : TestBase(KOTLIN) {
_contextSerializationEnv.set(createSandboxSerializationEnv(classLoader)) _contextSerializationEnv.set(createSandboxSerializationEnv(classLoader))
assertLocalType<Opaque>(sandbox<Date>(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))
}
} }

View File

@ -71,27 +71,43 @@ abstract class TestBase(type: SandboxType) {
SandboxType.JAVA -> TESTING_LIBRARIES.filter { isDirectory(it) } SandboxType.JAVA -> TESTING_LIBRARIES.filter { isDirectory(it) }
} }
fun sandbox(action: SandboxRuntimeContext.() -> Unit) { inline fun sandbox(crossinline action: SandboxRuntimeContext.() -> Unit) {
return sandbox(WARNING, emptySet(), emptySet(), action) sandbox(Consumer { ctx -> action(ctx) })
} }
fun sandbox(visibleAnnotations: Set<Class<out Annotation>>, action: SandboxRuntimeContext.() -> Unit) { fun sandbox(action: Consumer<SandboxRuntimeContext>) {
return sandbox(WARNING, visibleAnnotations, emptySet(), action) 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( fun sandbox(
visibleAnnotations: Set<Class<out Annotation>>, visibleAnnotations: Set<Class<out Annotation>>,
sandboxOnlyAnnotations: Set<String>, sandboxOnlyAnnotations: Set<String>,
action: SandboxRuntimeContext.() -> Unit action: Consumer<SandboxRuntimeContext>
) { ) {
return sandbox(WARNING, visibleAnnotations, sandboxOnlyAnnotations, action) sandbox(WARNING, visibleAnnotations, sandboxOnlyAnnotations, action)
} }
fun sandbox( fun sandbox(
minimumSeverityLevel: Severity, minimumSeverityLevel: Severity,
visibleAnnotations: Set<Class<out Annotation>>, visibleAnnotations: Set<Class<out Annotation>>,
sandboxOnlyAnnotations: Set<String>, sandboxOnlyAnnotations: Set<String>,
action: SandboxRuntimeContext.() -> Unit action: Consumer<SandboxRuntimeContext>
) { ) {
var thrownException: Throwable? = null var thrownException: Throwable? = null
thread(start = false) { thread(start = false) {
@ -100,9 +116,7 @@ abstract class TestBase(type: SandboxType) {
it.setMinimumSeverityLevel(minimumSeverityLevel) it.setMinimumSeverityLevel(minimumSeverityLevel)
it.setSandboxOnlyAnnotations(sandboxOnlyAnnotations) it.setSandboxOnlyAnnotations(sandboxOnlyAnnotations)
it.setVisibleAnnotations(visibleAnnotations) it.setVisibleAnnotations(visibleAnnotations)
})).use(Consumer { ctx -> })).use(action)
ctx.action()
})
} }
}.apply { }.apply {
uncaughtExceptionHandler = Thread.UncaughtExceptionHandler { _, ex -> uncaughtExceptionHandler = Thread.UncaughtExceptionHandler { _, ex ->

View File

@ -4,6 +4,8 @@ import net.corda.core.CordaThrowable
import net.corda.core.internal.uncheckedCast import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.contextLogger 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.DefaultCacheProvider
import net.corda.serialization.internal.model.TypeIdentifier import net.corda.serialization.internal.model.TypeIdentifier
import java.lang.reflect.Type 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. * that expects to find getters and a constructor with a parameter for each property.
*/ */
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.isNotEmpty()) { 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.")
} }
@ -119,7 +121,7 @@ class CachingCustomSerializerRegistry(
} }
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.isNotEmpty()) { 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." +
@ -153,8 +155,7 @@ class CachingCustomSerializerRegistry(
(declaredSuperClass == null (declaredSuperClass == null
|| !customSerializer.isSerializerFor(declaredSuperClass) || !customSerializer.isSerializerFor(declaredSuperClass)
|| !customSerializer.revealSubclassesInSchema) -> { || !customSerializer.revealSubclassesInSchema) -> {
logger.debug("action=\"Using custom serializer\", class=${clazz.typeName}, " + logger.debug { "action=\"Using custom serializer\", class=${clazz.typeName}, declaredType=${declaredType.typeName}" }
"declaredType=${declaredType.typeName}")
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
customSerializer as? AMQPSerializer<Any> customSerializer as? AMQPSerializer<Any>

View File

@ -7,6 +7,7 @@ import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.trace
import net.corda.serialization.internal.* import net.corda.serialization.internal.*
import net.corda.serialization.internal.model.TypeIdentifier import net.corda.serialization.internal.model.TypeIdentifier
import org.apache.qpid.proton.amqp.Binary import org.apache.qpid.proton.amqp.Binary
@ -119,7 +120,7 @@ class DeserializationInput constructor(
des { des {
val envelope = getEnvelope(bytes, context.encodingWhitelist) val envelope = getEnvelope(bytes, context.encodingWhitelist)
logger.trace("deserialize blob scheme=\"${envelope.schema}\"") logger.trace { "deserialize blob scheme=\"${envelope.schema}\"" }
doReadObject(envelope, clazz, context) doReadObject(envelope, clazz, context)
} }

View File

@ -3,8 +3,10 @@ package net.corda.serialization.internal.amqp
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.internal.MissingSerializerException import net.corda.core.serialization.internal.MissingSerializerException
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.trace
import net.corda.serialization.internal.model.* import net.corda.serialization.internal.model.*
import java.io.NotSerializableException 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. * 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> = ): AMQPSerializer<Any> =
// If we have seen this descriptor before, we assume we have seen everything in this schema before. // If we have seen this descriptor before, we assume we have seen everything in this schema before.
descriptorBasedSerializerRegistry.getOrBuild(typeDescriptor) { 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. // Interpret all of the types in the schema into RemoteTypeInformation, and reflect that into LocalTypeInformation.
val remoteTypeInformationMap = remoteTypeModel.interpret(schema) 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. // 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) -> val serializers = reflected.mapValues { (descriptor, remoteLocalPair) ->
descriptorBasedSerializerRegistry.getOrBuild(descriptor) { descriptorBasedSerializerRegistry.getOrBuild(descriptor) {
getUncached(remoteLocalPair.remoteTypeInformation, remoteLocalPair.localTypeInformation) getUncached(remoteLocalPair.remoteTypeInformation, remoteLocalPair.localTypeInformation, context)
} }
} }
@ -88,7 +90,8 @@ class DefaultRemoteSerializerFactory(
private fun getUncached( private fun getUncached(
remoteTypeInformation: RemoteTypeInformation, remoteTypeInformation: RemoteTypeInformation,
localTypeInformation: LocalTypeInformation localTypeInformation: LocalTypeInformation,
context: SerializationContext
): AMQPSerializer<Any> { ): AMQPSerializer<Any> {
val remoteDescriptor = remoteTypeInformation.typeDescriptor val remoteDescriptor = remoteTypeInformation.typeDescriptor
@ -109,6 +112,13 @@ class DefaultRemoteSerializerFactory(
evolutionSerializerFactory.getEvolutionSerializer(remoteTypeInformation, localTypeInformation) evolutionSerializerFactory.getEvolutionSerializer(remoteTypeInformation, localTypeInformation)
?: localSerializer ?: 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 // 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). // serialiser (BlobInspectorTest uniquely breaks if we throw an exception here, and passes if we just warn and continue).
else -> { else -> {
@ -134,7 +144,7 @@ ${localTypeInformation.prettyPrint(false)}
} }
return remoteInformation.mapValues { (_, remoteInformation) -> 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) = private fun RemoteTypeInformation.isDeserialisableWithoutEvolutionTo(localTypeInformation: LocalTypeInformation) =
this is RemoteTypeInformation.Parameterised && this is RemoteTypeInformation.Parameterised &&
(localTypeInformation is LocalTypeInformation.ACollection || (localTypeInformation is LocalTypeInformation.ACollection ||
localTypeInformation is LocalTypeInformation.AMap) 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()
}
} }

View File

@ -3,9 +3,6 @@ package net.corda.serialization.internal.amqp
import net.corda.core.KeepForDJVM import net.corda.core.KeepForDJVM
import net.corda.core.serialization.CordaSerializationTransformEnumDefault import net.corda.core.serialization.CordaSerializationTransformEnumDefault
import net.corda.core.serialization.CordaSerializationTransformRename 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 net.corda.serialization.internal.model.LocalTypeInformation
import org.apache.qpid.proton.amqp.DescribedType import org.apache.qpid.proton.amqp.DescribedType
import org.apache.qpid.proton.codec.DescribedTypeConstructor import org.apache.qpid.proton.codec.DescribedTypeConstructor

View File

@ -2,18 +2,27 @@ package net.corda.serialization.internal.amqp
import com.google.common.primitives.Primitives import com.google.common.primitives.Primitives
import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.ClassWhitelist
import net.corda.serialization.internal.model.BaseLocalTypes
import net.corda.serialization.internal.model.LocalTypeModelConfiguration import net.corda.serialization.internal.model.LocalTypeModelConfiguration
import org.apache.qpid.proton.amqp.* import org.apache.qpid.proton.amqp.*
import java.lang.reflect.Type 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] * [LocalTypeModelConfiguration] based on a [ClassWhitelist]
*/ */
class WhitelistBasedTypeModelConfiguration( class WhitelistBasedTypeModelConfiguration(
private val whitelist: ClassWhitelist, private val whitelist: ClassWhitelist,
private val customSerializerRegistry: CustomSerializerRegistry) private val customSerializerRegistry: CustomSerializerRegistry,
: LocalTypeModelConfiguration { 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 isExcluded(type: Type): Boolean = whitelist.isNotWhitelisted(type.asClass())
override fun isOpaque(type: Type): Boolean = Primitives.unwrap(type.asClass()) in opaqueTypes || override fun isOpaque(type: Type): Boolean = Primitives.unwrap(type.asClass()) in opaqueTypes ||
customSerializerRegistry.findCustomSerializer(type.asClass(), type) != null customSerializerRegistry.findCustomSerializer(type.asClass(), type) != null
@ -42,4 +51,14 @@ private val opaqueTypes = setOf(
ByteArray::class.java, ByteArray::class.java,
String::class.java, String::class.java,
Symbol::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 }
) )

View File

@ -7,11 +7,24 @@ import net.corda.core.serialization.ConstructorForDeserialization
import net.corda.core.serialization.DeprecatedConstructorForDeserialization import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.serialization.internal.NotSerializableDetailedException import net.corda.serialization.internal.NotSerializableDetailedException
import net.corda.serialization.internal.amqp.* 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.io.NotSerializableException
import java.lang.reflect.Method import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type import java.lang.reflect.Type
import java.util.*
import kotlin.collections.LinkedHashMap import kotlin.collections.LinkedHashMap
import kotlin.reflect.KFunction import kotlin.reflect.KFunction
import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.findAnnotation
@ -35,8 +48,10 @@ import kotlin.reflect.jvm.javaType
internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup, internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
var resolutionContext: Type? = null, var resolutionContext: Type? = null,
var visited: Set<TypeIdentifier> = emptySet(), var visited: Set<TypeIdentifier> = emptySet(),
val cycles: MutableList<LocalTypeInformation.Cycle> = mutableListOf(), val cycles: MutableList<Cycle> = mutableListOf(),
var validateProperties: Boolean = true) { 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 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]. * 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] * Recursively build [LocalTypeInformation] for the given [Type] and [TypeIdentifier]
*/ */
fun build(type: Type, typeIdentifier: TypeIdentifier): LocalTypeInformation = 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 -> else lookup.findOrBuild(type, typeIdentifier) { isOpaque ->
val previous = visited val previous = visited
try { try {
@ -78,15 +93,16 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
private fun buildIfNotFound(type: Type, typeIdentifier: TypeIdentifier, isOpaque: Boolean): LocalTypeInformation { private fun buildIfNotFound(type: Type, typeIdentifier: TypeIdentifier, isOpaque: Boolean): LocalTypeInformation {
val rawType = type.asClass() val rawType = type.asClass()
return when (typeIdentifier) { return when (typeIdentifier) {
is TypeIdentifier.TopType -> LocalTypeInformation.Top is TypeIdentifier.TopType -> Top
is TypeIdentifier.UnknownType -> LocalTypeInformation.Unknown is TypeIdentifier.UnknownType -> Unknown
is TypeIdentifier.Unparameterised, is TypeIdentifier.Unparameterised,
is TypeIdentifier.Erased -> buildForClass(rawType, typeIdentifier, isOpaque) is TypeIdentifier.Erased -> buildForClass(rawType, typeIdentifier, isOpaque)
is TypeIdentifier.ArrayOf -> { is TypeIdentifier.ArrayOf -> {
LocalTypeInformation.AnArray( AnArray(
type, type,
typeIdentifier, typeIdentifier,
resolveAndBuild(type.componentType())) resolveAndBuild(type.componentType())
)
} }
is TypeIdentifier.Parameterised -> buildForParameterised(rawType, type as ParameterizedType, typeIdentifier, isOpaque) 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) { private fun buildForClass(type: Class<*>, typeIdentifier: TypeIdentifier, isOpaque: Boolean): LocalTypeInformation = withContext(type) {
when { when {
Collection::class.java.isAssignableFrom(type) && baseTypes.collectionClass.isAssignableFrom(type) &&
!EnumSet::class.java.isAssignableFrom(type) -> LocalTypeInformation.ACollection(type, typeIdentifier, LocalTypeInformation.Unknown) !baseTypes.enumSetClass.isAssignableFrom(type) -> ACollection(type, typeIdentifier, Unknown)
Map::class.java.isAssignableFrom(type) -> LocalTypeInformation.AMap(type, typeIdentifier, LocalTypeInformation.Unknown, LocalTypeInformation.Unknown) baseTypes.mapClass.isAssignableFrom(type) -> AMap(type, typeIdentifier, Unknown, Unknown)
type == String::class.java -> LocalTypeInformation.Atomic(String::class.java, typeIdentifier) type === baseTypes.stringClass -> Atomic(type, typeIdentifier)
type.kotlin.javaPrimitiveType != null ->LocalTypeInformation.Atomic(type, typeIdentifier) type.kotlin.javaPrimitiveType != null -> Atomic(type, typeIdentifier)
type.isEnum -> LocalTypeInformation.AnEnum( baseTypes.isEnum.test(type) -> baseTypes.enumConstants.apply(type).let { enumConstants ->
AnEnum(
type, type,
typeIdentifier, typeIdentifier,
type.enumConstants.map { it.toString() }, enumConstants.map(Any::toString),
buildInterfaceInformation(type), buildInterfaceInformation(type),
getEnumTransforms(type)) getEnumTransforms(type, enumConstants)
type.kotlinObjectInstance != null -> LocalTypeInformation.Singleton( )
}
type.kotlinObjectInstance != null -> Singleton(
type, type,
typeIdentifier, typeIdentifier,
buildSuperclassInformation(type), buildSuperclassInformation(type),
buildInterfaceInformation(type)) buildInterfaceInformation(type))
type.isInterface -> buildInterface(type, typeIdentifier, emptyList()) type.isInterface -> buildInterface(type, typeIdentifier, emptyList())
type.isAbstractClass -> buildAbstract(type, typeIdentifier, emptyList()) type.isAbstractClass -> buildAbstract(type, typeIdentifier, emptyList())
isOpaque -> LocalTypeInformation.Opaque( isOpaque -> Opaque(
type, type,
typeIdentifier, typeIdentifier,
suppressValidation { buildNonAtomic(type, type, typeIdentifier, emptyList()) }) suppressValidation { buildNonAtomic(type, type, typeIdentifier, emptyList()) })
Exception::class.java.isAssignableFrom(type.asClass()) -> suppressValidation { baseTypes.exceptionClass.isAssignableFrom(type.asClass()) -> suppressValidation {
buildNonAtomic(type, type, typeIdentifier, emptyList()) buildNonAtomic(type, type, typeIdentifier, emptyList())
} }
else -> 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 { 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) return EnumTransforms.build(TransformsAnnotationProcessor.getTransformsSchema(type), constants)
} catch (e: InvalidEnumTransformsException) { } catch (e: InvalidEnumTransformsException) {
throw NotSerializableDetailedException(type.name, e.message!!) throw NotSerializableDetailedException(type.name, e.message!!)
@ -138,16 +157,16 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
typeIdentifier: TypeIdentifier.Parameterised, typeIdentifier: TypeIdentifier.Parameterised,
isOpaque: Boolean): LocalTypeInformation = withContext(type) { isOpaque: Boolean): LocalTypeInformation = withContext(type) {
when { when {
Collection::class.java.isAssignableFrom(rawType) && baseTypes.collectionClass.isAssignableFrom(rawType) &&
!EnumSet::class.java.isAssignableFrom(rawType) -> !baseTypes.enumSetClass.isAssignableFrom(rawType) ->
LocalTypeInformation.ACollection(type, typeIdentifier, buildTypeParameterInformation(type)[0]) ACollection(type, typeIdentifier, buildTypeParameterInformation(type)[0])
Map::class.java.isAssignableFrom(rawType) -> { baseTypes.mapClass.isAssignableFrom(rawType) -> {
val (keyType, valueType) = buildTypeParameterInformation(type) val (keyType, valueType) = buildTypeParameterInformation(type)
LocalTypeInformation.AMap(type, typeIdentifier, keyType, valueType) AMap(type, typeIdentifier, keyType, valueType)
} }
rawType.isInterface -> buildInterface(type, typeIdentifier, buildTypeParameterInformation(type)) rawType.isInterface -> buildInterface(type, typeIdentifier, buildTypeParameterInformation(type))
rawType.isAbstractClass -> buildAbstract(type, typeIdentifier, buildTypeParameterInformation(type)) rawType.isAbstractClass -> buildAbstract(type, typeIdentifier, buildTypeParameterInformation(type))
isOpaque -> LocalTypeInformation.Opaque(rawType, isOpaque -> Opaque(rawType,
typeIdentifier, typeIdentifier,
suppressValidation { buildNonAtomic(rawType, type, typeIdentifier, buildTypeParameterInformation(type)) }) suppressValidation { buildNonAtomic(rawType, type, typeIdentifier, buildTypeParameterInformation(type)) })
else -> 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, private fun buildAbstract(type: Type, typeIdentifier: TypeIdentifier,
typeParameters: List<LocalTypeInformation>): LocalTypeInformation.Abstract = typeParameters: List<LocalTypeInformation>): Abstract =
LocalTypeInformation.Abstract( Abstract(
type, type,
typeIdentifier, typeIdentifier,
buildReadOnlyProperties(type.asClass()), buildReadOnlyProperties(type.asClass()),
buildSuperclassInformation(type), buildSuperclassInformation(type),
buildInterfaceInformation(type), buildInterfaceInformation(type),
typeParameters) typeParameters
)
private fun buildInterface(type: Type, typeIdentifier: TypeIdentifier, private fun buildInterface(type: Type, typeIdentifier: TypeIdentifier,
typeParameters: List<LocalTypeInformation>): LocalTypeInformation.AnInterface = typeParameters: List<LocalTypeInformation>): AnInterface =
LocalTypeInformation.AnInterface( AnInterface(
type, type,
typeIdentifier, typeIdentifier,
buildReadOnlyProperties(type.asClass()), buildReadOnlyProperties(type.asClass()),
buildInterfaceInformation(type), buildInterfaceInformation(type),
typeParameters) typeParameters
)
private inline fun <T> withContext(newContext: Type, block: LocalTypeInformationBuilder.() -> T): T { private inline fun <T> withContext(newContext: Type, block: LocalTypeInformationBuilder.() -> T): T {
val previous = resolutionContext 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 { private fun buildNonAtomic(rawType: Class<*>, type: Type, typeIdentifier: TypeIdentifier, typeParameterInformation: List<LocalTypeInformation>): LocalTypeInformation {
val superclassInformation = buildSuperclassInformation(type) val superclassInformation = buildSuperclassInformation(type)
val interfaceInformation = buildInterfaceInformation(type) val interfaceInformation = buildInterfaceInformation(type)
val observedConstructor = constructorForDeserialization(type) ?: return LocalTypeInformation.NonComposable( val observedConstructor = constructorForDeserialization(type) ?: return NonComposable(
observedType = type, observedType = type,
typeIdentifier = typeIdentifier, typeIdentifier = typeIdentifier,
constructor = null, 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. // Do NOT drill down into the internals of java.lang.Class.
emptyMap() emptyMap()
} else { } else {
@ -220,7 +241,7 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
if (!propertiesSatisfyConstructor(constructorInformation, properties)) { if (!propertiesSatisfyConstructor(constructorInformation, properties)) {
val missingConstructorProperties = missingMandatoryConstructorProperties(constructorInformation, properties) val missingConstructorProperties = missingMandatoryConstructorProperties(constructorInformation, properties)
val missingParameters = missingConstructorProperties.map(LocalConstructorParameterInformation::name) val missingParameters = missingConstructorProperties.map(LocalConstructorParameterInformation::name)
return LocalTypeInformation.NonComposable( return NonComposable(
observedType = type, observedType = type,
typeIdentifier = typeIdentifier, typeIdentifier = typeIdentifier,
constructor = constructorInformation, constructor = constructorInformation,
@ -229,16 +250,16 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
interfaces = interfaceInformation, interfaces = interfaceInformation,
typeParameters = typeParameterInformation, typeParameters = typeParameterInformation,
nonComposableSubtypes = missingConstructorProperties 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}", 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" 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()) { if (nonComposableProperties.isNotEmpty()) {
return LocalTypeInformation.NonComposable( return NonComposable(
observedType = type, observedType = type,
typeIdentifier = typeIdentifier, typeIdentifier = typeIdentifier,
constructor = constructorInformation, constructor = constructorInformation,
@ -247,7 +268,7 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
interfaces = interfaceInformation, interfaces = interfaceInformation,
typeParameters = typeParameterInformation, typeParameters = typeParameterInformation,
nonComposableSubtypes = nonComposableProperties.values.mapTo(LinkedHashSet()) { nonComposableSubtypes = nonComposableProperties.values.mapTo(LinkedHashSet()) {
it.type as LocalTypeInformation.NonComposable it.type as NonComposable
}, },
reason = nonComposablePropertiesErrorReason(nonComposableProperties), reason = nonComposablePropertiesErrorReason(nonComposableProperties),
remedy = "Either ensure that the properties ${nonComposableProperties.keys} are serializable, or provide a custom serializer for this type" 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) EvolutionConstructorInformation(evolutionConstructorInformation, evolutionProperties)
} }
return LocalTypeInformation.Composable(type, typeIdentifier, constructorInformation, evolutionConstructors, properties, return Composable(type, typeIdentifier, constructorInformation, evolutionConstructors, properties,
superclassInformation, interfaceInformation, typeParameterInformation) superclassInformation, interfaceInformation, typeParameterInformation)
} }
@ -268,13 +289,13 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
private fun propertiesSatisfyConstructor(constructorInformation: LocalConstructorInformation, properties: Map<PropertyName, LocalPropertyInformation>): Boolean { private fun propertiesSatisfyConstructor(constructorInformation: LocalConstructorInformation, properties: Map<PropertyName, LocalPropertyInformation>): Boolean {
if (!constructorInformation.hasParameters) return true if (!constructorInformation.hasParameters) return true
val indicesAddressedByProperties = properties.values.asSequence().mapNotNull { val indicesAddressedByProperties = properties.values.asSequence().mapNotNullTo(LinkedHashSet()) {
when (it) { when (it) {
is LocalPropertyInformation.ConstructorPairedProperty -> it.constructorSlot.parameterIndex is LocalPropertyInformation.ConstructorPairedProperty -> it.constructorSlot.parameterIndex
is LocalPropertyInformation.PrivateConstructorPairedProperty -> it.constructorSlot.parameterIndex is LocalPropertyInformation.PrivateConstructorPairedProperty -> it.constructorSlot.parameterIndex
else -> null else -> null
} }
}.toSet() }
return (constructorInformation.parameters.indices).none { index -> return (constructorInformation.parameters.indices).none { index ->
constructorInformation.parameters[index].isMandatory && index !in indicesAddressedByProperties constructorInformation.parameters[index].isMandatory && index !in indicesAddressedByProperties
@ -287,13 +308,13 @@ internal data class LocalTypeInformationBuilder(val lookup: LocalTypeLookup,
): List<LocalConstructorParameterInformation> { ): List<LocalConstructorParameterInformation> {
if (!constructorInformation.hasParameters) return emptyList() if (!constructorInformation.hasParameters) return emptyList()
val indicesAddressedByProperties = properties.values.asSequence().mapNotNull { val indicesAddressedByProperties = properties.values.asSequence().mapNotNullTo(LinkedHashSet()) {
when (it) { when (it) {
is LocalPropertyInformation.ConstructorPairedProperty -> it.constructorSlot.parameterIndex is LocalPropertyInformation.ConstructorPairedProperty -> it.constructorSlot.parameterIndex
is LocalPropertyInformation.PrivateConstructorPairedProperty -> it.constructorSlot.parameterIndex is LocalPropertyInformation.PrivateConstructorPairedProperty -> it.constructorSlot.parameterIndex
else -> null else -> null
} }
}.toSet() }
return (constructorInformation.parameters.indices).mapNotNull { index -> return (constructorInformation.parameters.indices).mapNotNull { index ->
val parameter = constructorInformation.parameters[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 { private fun nonComposablePropertiesErrorReason(nonComposableProperties: Map<PropertyName, LocalPropertyInformation>): String {
val reasons = nonComposableProperties.entries.joinToString("\n") { (key, value) -> 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 ") .replace("\n", "\n ")
} }
return "Has properties ${nonComposableProperties.keys} of types that are not serializable:\n" + reasons 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( val property = makeConstructorPairedProperty(
constructorParameterIndices[normalisedName]!!, constructorParameterIndices.getValue(normalisedName),
descriptor, descriptor,
constructorInformation) constructorInformation)
if (property == null) null else normalisedName to property if (property == null) null else normalisedName to property

View File

@ -1,6 +1,8 @@
package net.corda.serialization.internal.model package net.corda.serialization.internal.model
import java.lang.reflect.* 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 * 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. * because it is not whitelisted.
*/ */
fun isExcluded(type: Type): Boolean 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 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). * 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. * [LocalTypeInformation], usually because they are not included in a whitelist.
*/ */
fun isExcluded(type: Type): Boolean 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>>
)

View File

@ -2,8 +2,6 @@ package net.corda.serialization.internal.model
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.serialization.internal.carpenter.* import net.corda.serialization.internal.carpenter.*
import java.io.NotSerializableException
import java.lang.ClassCastException
import java.lang.reflect.Type import java.lang.reflect.Type
/** /**