mirror of
https://github.com/corda/corda.git
synced 2025-01-18 10:46:38 +00:00
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:
parent
2833ec2a88
commit
9503c9684e
12
build.gradle
12
build.gradle
@ -382,20 +382,20 @@ artifactory {
|
||||
|
||||
defaults {
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task generateApi(type: net.corda.plugins.GenerateApi){
|
||||
task generateApi(type: net.corda.plugins.GenerateApi) {
|
||||
baseName = "api-corda"
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
if (file('corda-docs-only-build').exists() || (System.getenv('CORDA_DOCS_ONLY_BUILD') != null)) {
|
||||
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")
|
||||
} else {
|
||||
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 {
|
||||
if(it.tasks.findByName("integrationTest") != null) {
|
||||
if (it.tasks.findByName("integrationTest") != null) {
|
||||
integrationTest {
|
||||
exclude '*/**'
|
||||
}
|
||||
@ -415,7 +415,7 @@ if(file('corda-docs-only-build').exists() || (System.getenv('CORDA_DOCS_ONLY_BUI
|
||||
}
|
||||
|
||||
it.afterEvaluate {
|
||||
if(it.tasks.findByName("smokeTest") != null) {
|
||||
if (it.tasks.findByName("smokeTest") != null) {
|
||||
smokeTest {
|
||||
exclude '*/**'
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
||||
import net.corda.core.serialization.internal.nodeSerializationEnv
|
||||
import net.corda.serialization.internal.*
|
||||
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.amqpMagic
|
||||
import net.corda.serialization.internal.amqp.custom.RxNotificationSerializer
|
||||
@ -21,12 +22,12 @@ import java.util.concurrent.ConcurrentHashMap
|
||||
*/
|
||||
class AMQPClientSerializationScheme(
|
||||
cordappCustomSerializers: Set<SerializationCustomSerializer<*,*>>,
|
||||
serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>
|
||||
serializerFactoriesForContexts: AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>
|
||||
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) {
|
||||
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, ConcurrentHashMap())
|
||||
constructor(cordapps: List<Cordapp>) : this(cordapps.customSerializers, AccessOrderLinkedHashMap { 128 })
|
||||
|
||||
@Suppress("UNUSED")
|
||||
constructor() : this(emptySet(), ConcurrentHashMap())
|
||||
constructor() : this(emptySet(), AccessOrderLinkedHashMap { 128 })
|
||||
|
||||
companion object {
|
||||
/** Call from main only. */
|
||||
@ -52,7 +53,7 @@ class AMQPClientSerializationScheme(
|
||||
}
|
||||
|
||||
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(RpcClientCordaFutureSerializer(this))
|
||||
register(RxNotificationSerializer(this))
|
||||
|
@ -2,12 +2,13 @@ package net.corda.deterministic.common
|
||||
|
||||
import net.corda.core.serialization.ClassWhitelist
|
||||
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.internal.SerializationEnvironmentImpl
|
||||
import net.corda.core.serialization.internal._contextSerializationEnv
|
||||
import net.corda.serialization.internal.*
|
||||
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.amqpMagic
|
||||
import org.junit.rules.TestRule
|
||||
@ -21,13 +22,13 @@ class LocalSerializationRule(private val label: String) : TestRule {
|
||||
|
||||
private companion object {
|
||||
private val AMQP_P2P_CONTEXT = SerializationContextImpl(
|
||||
amqpMagic,
|
||||
LocalSerializationRule::class.java.classLoader,
|
||||
GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()),
|
||||
emptyMap(),
|
||||
true,
|
||||
P2P,
|
||||
null
|
||||
amqpMagic,
|
||||
LocalSerializationRule::class.java.classLoader,
|
||||
GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()),
|
||||
emptyMap(),
|
||||
true,
|
||||
P2P,
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
@ -59,7 +60,7 @@ class LocalSerializationRule(private val label: String) : TestRule {
|
||||
|
||||
private fun createTestSerializationEnv(): SerializationEnvironmentImpl {
|
||||
val factory = SerializationFactoryImpl(mutableMapOf()).apply {
|
||||
registerScheme(AMQPSerializationScheme(emptySet(), mutableMapOf()))
|
||||
registerScheme(AMQPSerializationScheme(emptySet(), AccessOrderLinkedHashMap(128)))
|
||||
}
|
||||
return object : SerializationEnvironmentImpl(factory, AMQP_P2P_CONTEXT) {
|
||||
override fun toString() = "testSerializationEnv($label)"
|
||||
@ -67,8 +68,8 @@ class LocalSerializationRule(private val label: String) : TestRule {
|
||||
}
|
||||
|
||||
private class AMQPSerializationScheme(
|
||||
cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
|
||||
serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>
|
||||
cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
|
||||
serializerFactoriesForContexts: AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>
|
||||
) : AbstractAMQPSerializationScheme(cordappCustomSerializers, serializerFactoriesForContexts) {
|
||||
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||
throw UnsupportedOperationException()
|
||||
|
@ -174,7 +174,6 @@ interface SerializationContext {
|
||||
/**
|
||||
* Helper method to return a new context based on this context with the deserialization class loader changed.
|
||||
*/
|
||||
@DeleteForDJVM
|
||||
fun withClassLoader(classLoader: ClassLoader): SerializationContext
|
||||
|
||||
/**
|
||||
|
@ -6,6 +6,7 @@ import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import net.corda.serialization.internal.CordaSerializationMagic
|
||||
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.custom.RxNotificationSerializer
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
@ -16,11 +17,11 @@ import java.util.concurrent.ConcurrentHashMap
|
||||
*/
|
||||
class AMQPServerSerializationScheme(
|
||||
cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
|
||||
serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>
|
||||
serializerFactoriesForContexts: AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>
|
||||
) : 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 {
|
||||
throw UnsupportedOperationException()
|
||||
|
@ -13,6 +13,7 @@ import net.corda.node.internal.serialization.testutils.serializationContext
|
||||
import net.corda.node.serialization.amqp.RpcServerObservableSerializer
|
||||
import net.corda.node.services.messaging.ObservableSubscription
|
||||
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.SerializationOutput
|
||||
import org.apache.activemq.artemis.api.core.SimpleString
|
||||
@ -59,7 +60,7 @@ class RoundTripObservableSerializerTests {
|
||||
@Test
|
||||
fun roundTripTest1() {
|
||||
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,
|
||||
// the client as a property of the deserializer which, in the actual RPC client, is pulled off of
|
||||
|
@ -11,6 +11,7 @@ import net.corda.serialization.internal.CordaSerializationMagic
|
||||
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
|
||||
import net.corda.serialization.internal.amqp.SerializerFactory
|
||||
import net.corda.serialization.internal.AllWhitelist
|
||||
import net.corda.serialization.internal.amqp.AccessOrderLinkedHashMap
|
||||
import net.corda.client.rpc.internal.ObservableContext as ClientObservableContext
|
||||
|
||||
/**
|
||||
@ -22,7 +23,7 @@ import net.corda.client.rpc.internal.ObservableContext as ClientObservableContex
|
||||
class AMQPRoundTripRPCSerializationScheme(
|
||||
private val serializationContext: SerializationContext,
|
||||
cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
|
||||
serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>)
|
||||
serializerFactoriesForContexts: AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>)
|
||||
: AbstractAMQPSerializationScheme(
|
||||
cordappCustomSerializers, serializerFactoriesForContexts
|
||||
) {
|
||||
|
@ -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"))
|
||||
}
|
||||
}
|
@ -49,6 +49,7 @@ task patchSerialization(type: Zip, dependsOn: serializationJarTask) {
|
||||
exclude 'net/corda/serialization/internal/DefaultWhitelist*'
|
||||
exclude 'net/corda/serialization/internal/amqp/AMQPSerializerFactories*'
|
||||
exclude 'net/corda/serialization/internal/amqp/AMQPStreams*'
|
||||
exclude 'net/corda/serialization/internal/amqp/AMQPSerializationThreadContext*'
|
||||
}
|
||||
|
||||
reproducibleFileOrder = true
|
||||
|
@ -0,0 +1,6 @@
|
||||
@file:JvmName("AMQPSerializationThreadContext")
|
||||
package net.corda.serialization.internal.amqp
|
||||
|
||||
fun getContextClassLoader(): ClassLoader {
|
||||
return ClassLoader.getSystemClassLoader()
|
||||
}
|
@ -12,10 +12,12 @@ import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.utilities.ByteSequence
|
||||
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.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
val AMQP_ENABLED get() = SerializationDefaults.P2P_CONTEXT.preferredSerializationVersion == amqpMagic
|
||||
|
||||
@ -37,12 +39,12 @@ interface SerializerFactoryFactory {
|
||||
|
||||
@KeepForDJVM
|
||||
abstract class AbstractAMQPSerializationScheme(
|
||||
private val cordappCustomSerializers: Set<SerializationCustomSerializer<*,*>>,
|
||||
private val serializerFactoriesForContexts: MutableMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>,
|
||||
val sff: SerializerFactoryFactory = createSerializerFactoryFactory()
|
||||
private val cordappCustomSerializers: Set<SerializationCustomSerializer<*, *>>,
|
||||
private val serializerFactoriesForContexts: AccessOrderLinkedHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>,
|
||||
val sff: SerializerFactoryFactory = createSerializerFactoryFactory()
|
||||
) : SerializationScheme {
|
||||
@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
|
||||
// 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)
|
||||
|
||||
if (scanSpec == null) {
|
||||
logger.info ("scanSpec not set, not scanning for Custom Serializers")
|
||||
logger.debug("scanSpec not set, not scanning for Custom Serializers")
|
||||
emptyList()
|
||||
} else {
|
||||
logger.info ("scanSpec = \"$scanSpec\", scanning for Custom Serializers")
|
||||
logger.debug("scanSpec = \"$scanSpec\", scanning for Custom Serializers")
|
||||
scanClasspathForSerializers(scanSpec)
|
||||
}
|
||||
}
|
||||
|
||||
@StubOutForDJVM
|
||||
private fun scanClasspathForSerializers(scanSpec: String): List<SerializationCustomSerializer<*, *>> =
|
||||
this::class.java.classLoader.let { cl ->
|
||||
FastClasspathScanner(scanSpec).addClassLoader(cl).scan()
|
||||
.getNamesOfClassesImplementing(SerializationCustomSerializer::class.java)
|
||||
.map { cl.loadClass(it).asSubclass(SerializationCustomSerializer::class.java) }
|
||||
.filterNot { Modifier.isAbstract(it.modifiers) }
|
||||
.map { it.kotlin.objectOrNewInstance() }
|
||||
}
|
||||
this::class.java.classLoader.let { cl ->
|
||||
FastClasspathScanner(scanSpec).addClassLoader(cl).scan()
|
||||
.getNamesOfClassesImplementing(SerializationCustomSerializer::class.java)
|
||||
.map { cl.loadClass(it).asSubclass(SerializationCustomSerializer::class.java) }
|
||||
.filterNot { Modifier.isAbstract(it.modifiers) }
|
||||
.map { it.kotlin.objectOrNewInstance() }
|
||||
}
|
||||
|
||||
@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.
|
||||
@ -100,6 +103,7 @@ abstract class AbstractAMQPSerializationScheme(
|
||||
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.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.YearMonthSerializer(this))
|
||||
register(net.corda.serialization.internal.amqp.custom.MonthDaySerializer(this))
|
||||
@ -126,7 +130,7 @@ abstract class AbstractAMQPSerializationScheme(
|
||||
factory.registerExternal(CorDappCustomSerializer(customSerializer, factory))
|
||||
}
|
||||
} else {
|
||||
logger.info("Custom Serializer list loaded - not scanning classpath")
|
||||
logger.debug("Custom Serializer list loaded - not scanning classpath")
|
||||
cordappCustomSerializers.forEach { customSerializer ->
|
||||
factory.registerExternal(CorDappCustomSerializer(customSerializer, factory))
|
||||
}
|
||||
@ -153,32 +157,41 @@ abstract class AbstractAMQPSerializationScheme(
|
||||
protected abstract fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory
|
||||
|
||||
// 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 {
|
||||
return serializerFactoriesForContexts.computeIfAbsent(Pair(context.whitelist, context.deserializationClassLoader)) {
|
||||
when (context.useCase) {
|
||||
SerializationContext.UseCase.Checkpoint ->
|
||||
throw IllegalStateException("AMQP should not be used for checkpoint serialization.")
|
||||
SerializationContext.UseCase.RPCClient ->
|
||||
rpcClientSerializerFactory(context)
|
||||
SerializationContext.UseCase.RPCServer ->
|
||||
rpcServerSerializerFactory(context)
|
||||
else -> sff.make(context)
|
||||
}.also {
|
||||
registerCustomSerializers(context, it)
|
||||
return synchronized(serializerFactoriesForContexts) {
|
||||
serializerFactoriesForContexts.computeIfAbsent(Pair(context.whitelist, context.deserializationClassLoader)) {
|
||||
when (context.useCase) {
|
||||
SerializationContext.UseCase.Checkpoint ->
|
||||
throw IllegalStateException("AMQP should not be used for checkpoint serialization.")
|
||||
SerializationContext.UseCase.RPCClient ->
|
||||
rpcClientSerializerFactory(context)
|
||||
SerializationContext.UseCase.RPCServer ->
|
||||
rpcServerSerializerFactory(context)
|
||||
else -> sff.make(context)
|
||||
}.also {
|
||||
registerCustomSerializers(context, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,6 @@
|
||||
@file:JvmName("AMQPSerializationThreadContext")
|
||||
package net.corda.serialization.internal.amqp
|
||||
|
||||
fun getContextClassLoader(): ClassLoader {
|
||||
return Thread.currentThread().contextClassLoader
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -276,7 +276,7 @@ internal fun <T : Any> propertiesForSerializationFromConstructor(
|
||||
"in the Java compiler. Alternately, provide a proxy serializer " +
|
||||
"(SerializationCustomSerializer) if recompiling isn't an option")
|
||||
|
||||
Pair(PrivatePropertyReader(field, type), field.genericType)
|
||||
Pair(PrivatePropertyReader(field, type), resolveTypeVariables(field.genericType, type))
|
||||
}
|
||||
|
||||
this += PropertyAccessorConstructor(
|
||||
|
@ -140,7 +140,7 @@ open class SerializerFactory(
|
||||
}
|
||||
}
|
||||
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} "
|
||||
+ "isEnum=${declaredType::class.java.isEnum}")
|
||||
|
||||
|
@ -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?)
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,15 +1,11 @@
|
||||
package net.corda.serialization.internal.amqp
|
||||
|
||||
import org.junit.Test
|
||||
import net.corda.core.serialization.ClassWhitelist
|
||||
import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import net.corda.serialization.internal.AllWhitelist
|
||||
import net.corda.serialization.internal.amqp.testutils.deserializeAndReturnEnvelope
|
||||
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 net.corda.serialization.internal.amqp.testutils.*
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.Test
|
||||
import java.io.NotSerializableException
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
|
@ -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`()
|
||||
}
|
||||
}
|
@ -510,6 +510,12 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
|
||||
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
|
||||
fun `test throwables serialize`() {
|
||||
val factory = SerializerFactory(AllWhitelist, ClassLoader.getSystemClassLoader())
|
||||
@ -969,6 +975,7 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
|
||||
|
||||
class Spike private constructor(val a: String) {
|
||||
constructor() : this("a")
|
||||
|
||||
override fun equals(other: Any?): Boolean = other is Spike && other.a == this.a
|
||||
override fun hashCode(): Int = a.hashCode()
|
||||
}
|
||||
|
@ -2,11 +2,11 @@ package net.corda.serialization.internal.amqp
|
||||
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.utilities.ByteSequence
|
||||
import net.corda.serialization.internal.*
|
||||
import net.corda.serialization.internal.BuiltInExceptionsWhitelist
|
||||
import net.corda.serialization.internal.CordaSerializationMagic
|
||||
import net.corda.serialization.internal.GlobalTransientClassWhiteList
|
||||
import net.corda.serialization.internal.SerializationContextImpl
|
||||
import org.junit.Test
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
// 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 {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
@ -91,7 +91,7 @@ class SerializationSchemaTests {
|
||||
|
||||
val c = C(1)
|
||||
val testSerializationFactory = TestSerializationFactory()
|
||||
val expectedCustomSerializerCount = 40
|
||||
val expectedCustomSerializerCount = 41
|
||||
|
||||
assertEquals(0, testFactory.registerCount)
|
||||
c.serialize(testSerializationFactory, TESTING_CONTEXT)
|
||||
|
Loading…
Reference in New Issue
Block a user