CORDA-1747 - Client RPC classloader and Java Generics fixes (#3553)

* fix for spring boot rpc and  it work with deterministic serialization

* really really fix tests

* up log level

* reenable allWarningsAsErrors
This commit is contained in:
Stefano Franz
2018-07-11 13:28:01 +01:00
committed by GitHub
parent 2833ec2a88
commit 9503c9684e
22 changed files with 327 additions and 71 deletions

View File

@ -382,20 +382,20 @@ artifactory {
defaults { defaults {
// Root project applies the plugin (for this block) but does not need to be published // Root project applies the plugin (for this block) but does not need to be published
if(project != rootProject) { if (project != rootProject) {
publications(project.extensions.publish.name()) publications(project.extensions.publish.name())
} }
} }
} }
} }
task generateApi(type: net.corda.plugins.GenerateApi){ task generateApi(type: net.corda.plugins.GenerateApi) {
baseName = "api-corda" baseName = "api-corda"
} }
// This exists to reduce CI build time when the envvar is set (can save up to 40 minutes) // This exists to reduce CI build time when the envvar is set (can save up to 40 minutes)
if(file('corda-docs-only-build').exists() || (System.getenv('CORDA_DOCS_ONLY_BUILD') != null)) { if (file('corda-docs-only-build').exists() || (System.getenv('CORDA_DOCS_ONLY_BUILD') != null)) {
if(file('corda-docs-only-build').exists()) { if (file('corda-docs-only-build').exists()) {
logger.info("Tests are disabled due to presence of file 'corda-docs-only-build' in the project root") logger.info("Tests are disabled due to presence of file 'corda-docs-only-build' in the project root")
} else { } else {
logger.info("Tests are disabled due to the presence of envvar CORDA_DOCS_ONLY_BUILD") logger.info("Tests are disabled due to the presence of envvar CORDA_DOCS_ONLY_BUILD")
@ -407,7 +407,7 @@ if(file('corda-docs-only-build').exists() || (System.getenv('CORDA_DOCS_ONLY_BUI
} }
it.afterEvaluate { it.afterEvaluate {
if(it.tasks.findByName("integrationTest") != null) { if (it.tasks.findByName("integrationTest") != null) {
integrationTest { integrationTest {
exclude '*/**' exclude '*/**'
} }
@ -415,7 +415,7 @@ if(file('corda-docs-only-build').exists() || (System.getenv('CORDA_DOCS_ONLY_BUI
} }
it.afterEvaluate { it.afterEvaluate {
if(it.tasks.findByName("smokeTest") != null) { if (it.tasks.findByName("smokeTest") != null) {
smokeTest { smokeTest {
exclude '*/**' exclude '*/**'
} }

View File

@ -10,6 +10,7 @@ import net.corda.core.serialization.internal.SerializationEnvironmentImpl
import net.corda.core.serialization.internal.nodeSerializationEnv import net.corda.core.serialization.internal.nodeSerializationEnv
import net.corda.serialization.internal.* import net.corda.serialization.internal.*
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
import net.corda.serialization.internal.amqp.AccessOrderLinkedHashMap
import net.corda.serialization.internal.amqp.SerializerFactory import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.amqp.amqpMagic import net.corda.serialization.internal.amqp.amqpMagic
import net.corda.serialization.internal.amqp.custom.RxNotificationSerializer import net.corda.serialization.internal.amqp.custom.RxNotificationSerializer
@ -21,12 +22,12 @@ import java.util.concurrent.ConcurrentHashMap
*/ */
class AMQPClientSerializationScheme( class AMQPClientSerializationScheme(
cordappCustomSerializers: Set<SerializationCustomSerializer<*,*>>, cordappCustomSerializers: Set<SerializationCustomSerializer<*,*>>,
serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory> serializerFactoriesForContexts: AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) { ) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) {
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, ConcurrentHashMap()) constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, AccessOrderLinkedHashMap { 128 })
@Suppress("UNUSED") @Suppress("UNUSED")
constructor() : this(emptySet(), ConcurrentHashMap()) constructor() : this(emptySet(), AccessOrderLinkedHashMap { 128 })
companion object { companion object {
/** Call from main only. */ /** Call from main only. */
@ -52,7 +53,7 @@ class AMQPClientSerializationScheme(
} }
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory { override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
return SerializerFactory(context.whitelist, ClassLoader.getSystemClassLoader(), context.lenientCarpenterEnabled).apply { return SerializerFactory(context.whitelist, context.deserializationClassLoader, context.lenientCarpenterEnabled).apply {
register(RpcClientObservableSerializer) register(RpcClientObservableSerializer)
register(RpcClientCordaFutureSerializer(this)) register(RpcClientCordaFutureSerializer(this))
register(RxNotificationSerializer(this)) register(RxNotificationSerializer(this))

View File

@ -2,12 +2,13 @@ package net.corda.deterministic.common
import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationContext.UseCase.* import net.corda.core.serialization.SerializationContext.UseCase.P2P
import net.corda.core.serialization.SerializationCustomSerializer import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.core.serialization.internal.SerializationEnvironmentImpl import net.corda.core.serialization.internal.SerializationEnvironmentImpl
import net.corda.core.serialization.internal._contextSerializationEnv import net.corda.core.serialization.internal._contextSerializationEnv
import net.corda.serialization.internal.* import net.corda.serialization.internal.*
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
import net.corda.serialization.internal.amqp.AccessOrderLinkedHashMap
import net.corda.serialization.internal.amqp.SerializerFactory import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.amqp.amqpMagic import net.corda.serialization.internal.amqp.amqpMagic
import org.junit.rules.TestRule import org.junit.rules.TestRule
@ -21,13 +22,13 @@ class LocalSerializationRule(private val label: String) : TestRule {
private companion object { private companion object {
private val AMQP_P2P_CONTEXT = SerializationContextImpl( private val AMQP_P2P_CONTEXT = SerializationContextImpl(
amqpMagic, amqpMagic,
LocalSerializationRule::class.java.classLoader, LocalSerializationRule::class.java.classLoader,
GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()), GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()),
emptyMap(), emptyMap(),
true, true,
P2P, P2P,
null null
) )
} }
@ -59,7 +60,7 @@ class LocalSerializationRule(private val label: String) : TestRule {
private fun createTestSerializationEnv(): SerializationEnvironmentImpl { private fun createTestSerializationEnv(): SerializationEnvironmentImpl {
val factory = SerializationFactoryImpl(mutableMapOf()).apply { val factory = SerializationFactoryImpl(mutableMapOf()).apply {
registerScheme(AMQPSerializationScheme(emptySet(), mutableMapOf())) registerScheme(AMQPSerializationScheme(emptySet(), AccessOrderLinkedHashMap(128)))
} }
return object : SerializationEnvironmentImpl(factory, AMQP_P2P_CONTEXT) { return object : SerializationEnvironmentImpl(factory, AMQP_P2P_CONTEXT) {
override fun toString() = "testSerializationEnv($label)" override fun toString() = "testSerializationEnv($label)"
@ -67,8 +68,8 @@ class LocalSerializationRule(private val label: String) : TestRule {
} }
private class AMQPSerializationScheme( private class AMQPSerializationScheme(
cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>, cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory> serializerFactoriesForContexts: AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) { ) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) {
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory { override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
throw UnsupportedOperationException() throw UnsupportedOperationException()

View File

@ -174,7 +174,6 @@ interface SerializationContext {
/** /**
* Helper method to return a new context based on this context with the deserialization class loader changed. * Helper method to return a new context based on this context with the deserialization class loader changed.
*/ */
@DeleteForDJVM
fun withClassLoader(classLoader: ClassLoader): SerializationContext fun withClassLoader(classLoader: ClassLoader): SerializationContext
/** /**

View File

@ -6,6 +6,7 @@ import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationCustomSerializer import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.serialization.internal.CordaSerializationMagic import net.corda.serialization.internal.CordaSerializationMagic
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
import net.corda.serialization.internal.amqp.AccessOrderLinkedHashMap
import net.corda.serialization.internal.amqp.SerializerFactory import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.amqp.custom.RxNotificationSerializer import net.corda.serialization.internal.amqp.custom.RxNotificationSerializer
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@ -16,11 +17,11 @@ import java.util.concurrent.ConcurrentHashMap
*/ */
class AMQPServerSerializationScheme( class AMQPServerSerializationScheme(
cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>, cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory> serializerFactoriesForContexts: AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) { ) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) {
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, ConcurrentHashMap()) constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, AccessOrderLinkedHashMap { 128 })
constructor() : this(emptySet(), ConcurrentHashMap()) constructor() : this(emptySet(), AccessOrderLinkedHashMap { 128 })
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory { override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
throw UnsupportedOperationException() throw UnsupportedOperationException()

View File

@ -13,6 +13,7 @@ import net.corda.node.internal.serialization.testutils.serializationContext
import net.corda.node.serialization.amqp.RpcServerObservableSerializer import net.corda.node.serialization.amqp.RpcServerObservableSerializer
import net.corda.node.services.messaging.ObservableSubscription import net.corda.node.services.messaging.ObservableSubscription
import net.corda.nodeapi.RPCApi import net.corda.nodeapi.RPCApi
import net.corda.serialization.internal.amqp.AccessOrderLinkedHashMap
import net.corda.serialization.internal.amqp.DeserializationInput import net.corda.serialization.internal.amqp.DeserializationInput
import net.corda.serialization.internal.amqp.SerializationOutput import net.corda.serialization.internal.amqp.SerializationOutput
import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.SimpleString
@ -59,7 +60,7 @@ class RoundTripObservableSerializerTests {
@Test @Test
fun roundTripTest1() { fun roundTripTest1() {
val serializationScheme = AMQPRoundTripRPCSerializationScheme( val serializationScheme = AMQPRoundTripRPCSerializationScheme(
serializationContext, emptySet(), ConcurrentHashMap()) serializationContext, emptySet(), AccessOrderLinkedHashMap { 128 })
// Fake up a message ID, needs to be used on both "sides". The server setting it in the subscriptionMap, // Fake up a message ID, needs to be used on both "sides". The server setting it in the subscriptionMap,
// the client as a property of the deserializer which, in the actual RPC client, is pulled off of // the client as a property of the deserializer which, in the actual RPC client, is pulled off of

View File

@ -11,6 +11,7 @@ import net.corda.serialization.internal.CordaSerializationMagic
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
import net.corda.serialization.internal.amqp.SerializerFactory import net.corda.serialization.internal.amqp.SerializerFactory
import net.corda.serialization.internal.AllWhitelist import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.amqp.AccessOrderLinkedHashMap
import net.corda.client.rpc.internal.ObservableContext as ClientObservableContext import net.corda.client.rpc.internal.ObservableContext as ClientObservableContext
/** /**
@ -22,7 +23,7 @@ import net.corda.client.rpc.internal.ObservableContext as ClientObservableContex
class AMQPRoundTripRPCSerializationScheme( class AMQPRoundTripRPCSerializationScheme(
private val serializationContext: SerializationContext, private val serializationContext: SerializationContext,
cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>, cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>) serializerFactoriesForContexts: AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>)
: AbstractAMQPSerializationScheme( : AbstractAMQPSerializationScheme(
cordappCustomSerializers, serializerFactoriesForContexts cordappCustomSerializers, serializerFactoriesForContexts
) { ) {

View File

@ -0,0 +1,39 @@
package net.corda.serialization.internal.amqp.custom
import net.corda.serialization.internal.amqp.SerializerFactory
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.CoreMatchers.nullValue
import org.junit.Assert
import org.junit.Test
import org.mockito.Mockito
import java.util.*
class OptionalSerializerTest {
@Test
fun `should convert optional with item to proxy`() {
val opt = Optional.of("GenericTestString")
val proxy = OptionalSerializer(Mockito.mock(SerializerFactory::class.java)).toProxy(opt)
Assert.assertThat(proxy.item, `is`<Any>("GenericTestString"))
}
@Test
fun `should convert optional without item to empty proxy`() {
val opt = Optional.ofNullable<String>(null)
val proxy = OptionalSerializer(Mockito.mock(SerializerFactory::class.java)).toProxy(opt)
Assert.assertThat(proxy.item, `is`(nullValue()))
}
@Test
fun `should convert proxy without item to empty optional `() {
val proxy = OptionalSerializer.OptionalProxy(null)
val opt = OptionalSerializer(Mockito.mock(SerializerFactory::class.java)).fromProxy(proxy)
Assert.assertThat(opt.isPresent, `is`(false))
}
@Test
fun `should convert proxy with item to empty optional `() {
val proxy = OptionalSerializer.OptionalProxy("GenericTestString")
val opt = OptionalSerializer(Mockito.mock(SerializerFactory::class.java)).fromProxy(proxy)
Assert.assertThat(opt.get(), `is`<Any>("GenericTestString"))
}
}

View File

@ -49,6 +49,7 @@ task patchSerialization(type: Zip, dependsOn: serializationJarTask) {
exclude 'net/corda/serialization/internal/DefaultWhitelist*' exclude 'net/corda/serialization/internal/DefaultWhitelist*'
exclude 'net/corda/serialization/internal/amqp/AMQPSerializerFactories*' exclude 'net/corda/serialization/internal/amqp/AMQPSerializerFactories*'
exclude 'net/corda/serialization/internal/amqp/AMQPStreams*' exclude 'net/corda/serialization/internal/amqp/AMQPStreams*'
exclude 'net/corda/serialization/internal/amqp/AMQPSerializationThreadContext*'
} }
reproducibleFileOrder = true reproducibleFileOrder = true

View File

@ -0,0 +1,6 @@
@file:JvmName("AMQPSerializationThreadContext")
package net.corda.serialization.internal.amqp
fun getContextClassLoader(): ClassLoader {
return ClassLoader.getSystemClassLoader()
}

View File

@ -12,10 +12,12 @@ import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.* import net.corda.core.serialization.*
import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.serialization.internal.* import net.corda.serialization.internal.CordaSerializationMagic
import net.corda.serialization.internal.DefaultWhitelist
import net.corda.serialization.internal.MutableClassWhitelist
import net.corda.serialization.internal.SerializationScheme
import java.lang.reflect.Modifier import java.lang.reflect.Modifier
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap
val AMQP_ENABLED get() = SerializationDefaults.P2P_CONTEXT.preferredSerializationVersion == amqpMagic val AMQP_ENABLED get() = SerializationDefaults.P2P_CONTEXT.preferredSerializationVersion == amqpMagic
@ -37,12 +39,12 @@ interface SerializerFactoryFactory {
@KeepForDJVM @KeepForDJVM
abstract class AbstractAMQPSerializationScheme( abstract class AbstractAMQPSerializationScheme(
private val cordappCustomSerializers: Set<SerializationCustomSerializer<*,*>>, private val cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
private val serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>, private val serializerFactoriesForContexts: AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>,
val sff: SerializerFactoryFactory = createSerializerFactoryFactory() val sff: SerializerFactoryFactory = createSerializerFactoryFactory()
) : SerializationScheme { ) : SerializationScheme {
@DeleteForDJVM @DeleteForDJVM
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, ConcurrentHashMap()) constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, AccessOrderLinkedHashMap(128))
// TODO: This method of initialisation for the Whitelist and plugin serializers will have to change // TODO: This method of initialisation for the Whitelist and plugin serializers will have to change
// when we have per-cordapp contexts and dynamic app reloading but for now it's the easiest way // when we have per-cordapp contexts and dynamic app reloading but for now it's the easiest way
@ -59,26 +61,27 @@ abstract class AbstractAMQPSerializationScheme(
val scanSpec: String? = System.getProperty(SCAN_SPEC_PROP_NAME) val scanSpec: String? = System.getProperty(SCAN_SPEC_PROP_NAME)
if (scanSpec == null) { if (scanSpec == null) {
logger.info ("scanSpec not set, not scanning for Custom Serializers") logger.debug("scanSpec not set, not scanning for Custom Serializers")
emptyList() emptyList()
} else { } else {
logger.info ("scanSpec = \"$scanSpec\", scanning for Custom Serializers") logger.debug("scanSpec = \"$scanSpec\", scanning for Custom Serializers")
scanClasspathForSerializers(scanSpec) scanClasspathForSerializers(scanSpec)
} }
} }
@StubOutForDJVM @StubOutForDJVM
private fun scanClasspathForSerializers(scanSpec: String): List<SerializationCustomSerializer<*, *>> = private fun scanClasspathForSerializers(scanSpec: String): List<SerializationCustomSerializer<*, *>> =
this::class.java.classLoader.let { cl -> this::class.java.classLoader.let { cl ->
FastClasspathScanner(scanSpec).addClassLoader(cl).scan() FastClasspathScanner(scanSpec).addClassLoader(cl).scan()
.getNamesOfClassesImplementing(SerializationCustomSerializer::class.java) .getNamesOfClassesImplementing(SerializationCustomSerializer::class.java)
.map { cl.loadClass(it).asSubclass(SerializationCustomSerializer::class.java) } .map { cl.loadClass(it).asSubclass(SerializationCustomSerializer::class.java) }
.filterNot { Modifier.isAbstract(it.modifiers) } .filterNot { Modifier.isAbstract(it.modifiers) }
.map { it.kotlin.objectOrNewInstance() } .map { it.kotlin.objectOrNewInstance() }
} }
@DeleteForDJVM @DeleteForDJVM
val List<Cordapp>.customSerializers get() = flatMap { it.serializationCustomSerializers }.toSet() val List<Cordapp>.customSerializers
get() = flatMap { it.serializationCustomSerializers }.toSet()
} }
// Parameter "context" is unused directly but passed in by reflection. Removing it will cause failures. // Parameter "context" is unused directly but passed in by reflection. Removing it will cause failures.
@ -100,6 +103,7 @@ abstract class AbstractAMQPSerializationScheme(
register(net.corda.serialization.internal.amqp.custom.ZoneIdSerializer(this)) register(net.corda.serialization.internal.amqp.custom.ZoneIdSerializer(this))
register(net.corda.serialization.internal.amqp.custom.OffsetTimeSerializer(this)) register(net.corda.serialization.internal.amqp.custom.OffsetTimeSerializer(this))
register(net.corda.serialization.internal.amqp.custom.OffsetDateTimeSerializer(this)) register(net.corda.serialization.internal.amqp.custom.OffsetDateTimeSerializer(this))
register(net.corda.serialization.internal.amqp.custom.OptionalSerializer(this))
register(net.corda.serialization.internal.amqp.custom.YearSerializer(this)) register(net.corda.serialization.internal.amqp.custom.YearSerializer(this))
register(net.corda.serialization.internal.amqp.custom.YearMonthSerializer(this)) register(net.corda.serialization.internal.amqp.custom.YearMonthSerializer(this))
register(net.corda.serialization.internal.amqp.custom.MonthDaySerializer(this)) register(net.corda.serialization.internal.amqp.custom.MonthDaySerializer(this))
@ -126,7 +130,7 @@ abstract class AbstractAMQPSerializationScheme(
factory.registerExternal(CorDappCustomSerializer(customSerializer, factory)) factory.registerExternal(CorDappCustomSerializer(customSerializer, factory))
} }
} else { } else {
logger.info("Custom Serializer list loaded - not scanning classpath") logger.debug("Custom Serializer list loaded - not scanning classpath")
cordappCustomSerializers.forEach { customSerializer -> cordappCustomSerializers.forEach { customSerializer ->
factory.registerExternal(CorDappCustomSerializer(customSerializer, factory)) factory.registerExternal(CorDappCustomSerializer(customSerializer, factory))
} }
@ -153,32 +157,41 @@ abstract class AbstractAMQPSerializationScheme(
protected abstract fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory protected abstract fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory
// Not used as a simple direct import to facilitate testing // Not used as a simple direct import to facilitate testing
open val publicKeySerializer : CustomSerializer<*> = net.corda.serialization.internal.amqp.custom.PublicKeySerializer open val publicKeySerializer: CustomSerializer<*> = net.corda.serialization.internal.amqp.custom.PublicKeySerializer
private fun getSerializerFactory(context: SerializationContext): SerializerFactory { private fun getSerializerFactory(context: SerializationContext): SerializerFactory {
return serializerFactoriesForContexts.computeIfAbsent(Pair(context.whitelist, context.deserializationClassLoader)) { return synchronized(serializerFactoriesForContexts) {
when (context.useCase) { serializerFactoriesForContexts.computeIfAbsent(Pair(context.whitelist, context.deserializationClassLoader)) {
SerializationContext.UseCase.Checkpoint -> when (context.useCase) {
throw IllegalStateException("AMQP should not be used for checkpoint serialization.") SerializationContext.UseCase.Checkpoint ->
SerializationContext.UseCase.RPCClient -> throw IllegalStateException("AMQP should not be used for checkpoint serialization.")
rpcClientSerializerFactory(context) SerializationContext.UseCase.RPCClient ->
SerializationContext.UseCase.RPCServer -> rpcClientSerializerFactory(context)
rpcServerSerializerFactory(context) SerializationContext.UseCase.RPCServer ->
else -> sff.make(context) rpcServerSerializerFactory(context)
}.also { else -> sff.make(context)
registerCustomSerializers(context, it) }.also {
registerCustomSerializers(context, it)
}
} }
} }
} }
override fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T { override fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T {
val serializerFactory = getSerializerFactory(context) var contextToUse = context
if (context.useCase == SerializationContext.UseCase.RPCClient) {
contextToUse = context.withClassLoader(getContextClassLoader())
}
val serializerFactory = getSerializerFactory(contextToUse)
return DeserializationInput(serializerFactory).deserialize(byteSequence, clazz, context) return DeserializationInput(serializerFactory).deserialize(byteSequence, clazz, context)
} }
override fun <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T> { override fun <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T> {
val serializerFactory = getSerializerFactory(context) var contextToUse = context
if (context.useCase == SerializationContext.UseCase.RPCClient) {
contextToUse = context.withClassLoader(getContextClassLoader())
}
val serializerFactory = getSerializerFactory(contextToUse)
return SerializationOutput(serializerFactory).serialize(obj, context) return SerializationOutput(serializerFactory).serialize(obj, context)
} }

View File

@ -0,0 +1,6 @@
@file:JvmName("AMQPSerializationThreadContext")
package net.corda.serialization.internal.amqp
fun getContextClassLoader(): ClassLoader {
return Thread.currentThread().contextClassLoader
}

View File

@ -0,0 +1,11 @@
package net.corda.serialization.internal.amqp
import net.corda.core.KeepForDJVM
@KeepForDJVM
class AccessOrderLinkedHashMap<K, V>(private val maxSize: Int) : LinkedHashMap<K, V>(16, 0.75f, true) {
constructor(loader: () -> Int) : this(loader.invoke())
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<K, V>?): Boolean {
return this.size > maxSize
}
}

View File

@ -276,7 +276,7 @@ internal fun <T : Any> propertiesForSerializationFromConstructor(
"in the Java compiler. Alternately, provide a proxy serializer " + "in the Java compiler. Alternately, provide a proxy serializer " +
"(SerializationCustomSerializer) if recompiling isn't an option") "(SerializationCustomSerializer) if recompiling isn't an option")
Pair(PrivatePropertyReader(field, type), field.genericType) Pair(PrivatePropertyReader(field, type), resolveTypeVariables(field.genericType, type))
} }
this += PropertyAccessorConstructor( this += PropertyAccessorConstructor(

View File

@ -140,7 +140,7 @@ open class SerializerFactory(
} }
} }
Enum::class.java.isAssignableFrom(actualClass ?: declaredClass) -> { Enum::class.java.isAssignableFrom(actualClass ?: declaredClass) -> {
logger.debug("class=[${actualClass?.simpleName} | $declaredClass] is an enumeration " logger.info("class=[${actualClass?.simpleName} | $declaredClass] is an enumeration "
+ "declaredType=${declaredType.typeName} " + "declaredType=${declaredType.typeName} "
+ "isEnum=${declaredType::class.java.isEnum}") + "isEnum=${declaredType::class.java.isEnum}")

View File

@ -0,0 +1,24 @@
package net.corda.serialization.internal.amqp.custom
import net.corda.core.KeepForDJVM
import net.corda.serialization.internal.amqp.CustomSerializer
import net.corda.serialization.internal.amqp.SerializerFactory
import java.time.OffsetTime
import java.util.*
/**
* A serializer for [OffsetTime] that uses a proxy object to write out the time and zone offset.
*/
class OptionalSerializer(factory: SerializerFactory) : CustomSerializer.Proxy<Optional<*>, OptionalSerializer.OptionalProxy>(Optional::class.java, OptionalProxy::class.java, factory) {
public override fun toProxy(obj: java.util.Optional<*>): OptionalProxy {
return OptionalProxy(obj.orElse(null))
}
public override fun fromProxy(proxy: OptionalProxy): Optional<*> {
return Optional.ofNullable(proxy.item)
}
@KeepForDJVM
data class OptionalProxy(val item: Any?)
}

View File

@ -0,0 +1,37 @@
package net.corda.serialization.internal.amqp;
import net.corda.core.serialization.CordaSerializable;
import java.util.Objects;
@CordaSerializable
public class DummyOptional<T> {
private final T item;
public boolean isPresent() {
return item != null;
}
public T get() {
return item;
}
public DummyOptional(T item) {
this.item = item;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DummyOptional<?> that = (DummyOptional<?>) o;
return Objects.equals(item, that.item);
}
@Override
public int hashCode() {
return Objects.hash(item);
}
}

View File

@ -0,0 +1,76 @@
package net.corda.serialization.internal.amqp
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.deserialize
import net.corda.core.utilities.ByteSequence
import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.CordaSerializationMagic
import net.corda.serialization.internal.SerializationContextImpl
import net.corda.serialization.internal.amqp.testutils.serializationProperties
import net.corda.testing.core.SerializationEnvironmentRule
import org.hamcrest.CoreMatchers
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.Matchers
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
import java.net.URLClassLoader
import java.util.concurrent.ThreadLocalRandom
import java.util.stream.IntStream
class AbstractAMQPSerializationSchemeTest {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
@Test
fun `number of cached factories must be bounded by maxFactories`() {
val genesisContext = SerializationContextImpl(
ByteSequence.of(byteArrayOf('c'.toByte(), 'o'.toByte(), 'r'.toByte(), 'd'.toByte(), 'a'.toByte(), 0.toByte(), 0.toByte(), 1.toByte())),
ClassLoader.getSystemClassLoader(),
AllWhitelist,
serializationProperties,
false,
SerializationContext.UseCase.RPCClient,
null)
val factory = TestSerializerFactory(TESTING_CONTEXT.whitelist, TESTING_CONTEXT.deserializationClassLoader)
val maxFactories = 512
val backingMap = AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>({ maxFactories })
val scheme = object : AbstractAMQPSerializationScheme(emptySet(), backingMap, createSerializerFactoryFactory()) {
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
return factory
}
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
return factory
}
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
return true
}
}
IntStream.range(0, 2048).parallel().forEach {
val context = if (ThreadLocalRandom.current().nextBoolean()) {
genesisContext.withClassLoader(URLClassLoader(emptyArray()))
} else {
genesisContext
}
val testString = "TEST${ThreadLocalRandom.current().nextInt()}"
val serialized = scheme.serialize(testString, context)
val deserialized = serialized.deserialize(context = context, serializationFactory = testSerialization.serializationFactory)
Assert.assertThat(testString, `is`(deserialized))
Assert.assertThat(backingMap.size, `is`(Matchers.lessThanOrEqualTo(maxFactories)))
}
Assert.assertThat(backingMap.size, CoreMatchers.`is`(Matchers.lessThanOrEqualTo(maxFactories)))
}
}

View File

@ -1,15 +1,11 @@
package net.corda.serialization.internal.amqp package net.corda.serialization.internal.amqp
import org.junit.Test
import net.corda.core.serialization.ClassWhitelist import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.SerializationCustomSerializer import net.corda.core.serialization.SerializationCustomSerializer
import net.corda.serialization.internal.AllWhitelist import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.amqp.testutils.deserializeAndReturnEnvelope import net.corda.serialization.internal.amqp.testutils.*
import net.corda.serialization.internal.amqp.testutils.deserialize
import net.corda.serialization.internal.amqp.testutils.serializeAndReturnSchema
import net.corda.serialization.internal.amqp.testutils.serialize
import net.corda.serialization.internal.amqp.testutils.testDefaultFactory
import org.assertj.core.api.Assertions import org.assertj.core.api.Assertions
import org.junit.Test
import java.io.NotSerializableException import java.io.NotSerializableException
import kotlin.test.assertEquals import kotlin.test.assertEquals

View File

@ -0,0 +1,36 @@
package net.corda.serialization.internal.amqp
import net.corda.serialization.internal.AllWhitelist
import net.corda.serialization.internal.amqp.custom.OptionalSerializer
import net.corda.serialization.internal.amqp.testutils.TestSerializationOutput
import net.corda.serialization.internal.amqp.testutils.deserialize
import net.corda.serialization.internal.amqp.testutils.testDefaultFactory
import org.hamcrest.Matchers.`is`
import org.hamcrest.Matchers.equalTo
import org.junit.Assert
import org.junit.Test
import java.util.*
class OptionalSerializationTests {
@Test
fun setupEnclosedSerializationTest() {
@Test
fun `java optionals should serialize`() {
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
factory.register(OptionalSerializer(factory))
val obj = Optional.ofNullable("YES")
val bytes = TestSerializationOutput(true, factory).serialize(obj)
val deserializerFactory = testDefaultFactory().apply {
register(OptionalSerializer(this))
}
val deserialized = DeserializationInput(factory).deserialize(bytes)
val deserialized2 = DeserializationInput(deserializerFactory).deserialize(bytes)
Assert.assertThat(deserialized, `is`(equalTo(deserialized2)))
Assert.assertThat(obj, `is`(equalTo(deserialized2)))
}
`java optionals should serialize`()
}
}

View File

@ -510,6 +510,12 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
serdes(obj, SerializerFactory(EmptyWhitelist, ClassLoader.getSystemClassLoader())) serdes(obj, SerializerFactory(EmptyWhitelist, ClassLoader.getSystemClassLoader()))
} }
@Test
fun `generics from java are supported`() {
val obj = DummyOptional<String>("YES")
serdes(obj, SerializerFactory(EmptyWhitelist, ClassLoader.getSystemClassLoader()))
}
@Test @Test
fun `test throwables serialize`() { fun `test throwables serialize`() {
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader()) val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
@ -969,6 +975,7 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
class Spike private constructor(val a: String) { class Spike private constructor(val a: String) {
constructor() : this("a") constructor() : this("a")
override fun equals(other: Any?): Boolean = other is Spike && other.a == this.a override fun equals(other: Any?): Boolean = other is Spike && other.a == this.a
override fun hashCode(): Int = a.hashCode() override fun hashCode(): Int = a.hashCode()
} }

View File

@ -2,11 +2,11 @@ package net.corda.serialization.internal.amqp
import net.corda.core.serialization.* import net.corda.core.serialization.*
import net.corda.core.utilities.ByteSequence import net.corda.core.utilities.ByteSequence
import net.corda.serialization.internal.*
import net.corda.serialization.internal.BuiltInExceptionsWhitelist import net.corda.serialization.internal.BuiltInExceptionsWhitelist
import net.corda.serialization.internal.CordaSerializationMagic
import net.corda.serialization.internal.GlobalTransientClassWhiteList import net.corda.serialization.internal.GlobalTransientClassWhiteList
import net.corda.serialization.internal.SerializationContextImpl
import org.junit.Test import org.junit.Test
import java.util.concurrent.ConcurrentHashMap
import kotlin.test.assertEquals import kotlin.test.assertEquals
// Make sure all serialization calls in this test don't get stomped on by anything else // Make sure all serialization calls in this test don't get stomped on by anything else
@ -46,7 +46,7 @@ class TestSerializerFactoryFactory : SerializerFactoryFactoryImpl() {
} }
} }
class AMQPTestSerializationScheme : AbstractAMQPSerializationScheme(emptySet(), ConcurrentHashMap(), TestSerializerFactoryFactory()) { class AMQPTestSerializationScheme : AbstractAMQPSerializationScheme(emptySet(), AccessOrderLinkedHashMap { 128 }, TestSerializerFactoryFactory()) {
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory { override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
throw UnsupportedOperationException() throw UnsupportedOperationException()
} }
@ -91,7 +91,7 @@ class SerializationSchemaTests {
val c = C(1) val c = C(1)
val testSerializationFactory = TestSerializationFactory() val testSerializationFactory = TestSerializationFactory()
val expectedCustomSerializerCount = 40 val expectedCustomSerializerCount = 41
assertEquals(0, testFactory.registerCount) assertEquals(0, testFactory.registerCount)
c.serialize(testSerializationFactory, TESTING_CONTEXT) c.serialize(testSerializationFactory, TESTING_CONTEXT)