mirror of
https://github.com/corda/corda.git
synced 2025-02-02 09:18:13 +00:00
CORDA-780 / CORDA-786 - Enable AMQP for P2P and Storage Contexts
Add plugable mechanism for CorDapps such that they can add their own custom serializers
This commit is contained in:
parent
ce859178eb
commit
81eb0c1898
@ -41,7 +41,7 @@ class KryoClientSerializationScheme : AbstractKryoSerializationScheme() {
|
||||
return SerializationEnvironmentImpl(
|
||||
SerializationFactoryImpl().apply {
|
||||
registerScheme(KryoClientSerializationScheme())
|
||||
registerScheme(AMQPClientSerializationScheme())
|
||||
registerScheme(AMQPClientSerializationScheme(emptyList()))
|
||||
},
|
||||
KRYO_P2P_CONTEXT,
|
||||
rpcClientContext = KRYO_RPC_CLIENT_CONTEXT)
|
||||
|
@ -3,6 +3,7 @@ package net.corda.core.cordapp
|
||||
import net.corda.core.DoNotImplement
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.schemas.MappedSchema
|
||||
import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import net.corda.core.serialization.SerializationWhitelist
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
import java.net.URL
|
||||
@ -22,6 +23,8 @@ import java.net.URL
|
||||
* @property schedulableFlows List of flows startable by the scheduler
|
||||
* @property services List of RPC services
|
||||
* @property serializationWhitelists List of Corda plugin registries
|
||||
* @property serializationCustomSerializerProxies List of Proxy classes used by the custom serializers
|
||||
* @property serializationCustomSerializers List of serializers
|
||||
* @property customSchemas List of custom schemas
|
||||
* @property jarPath The path to the JAR for this CorDapp
|
||||
*/
|
||||
@ -35,6 +38,8 @@ interface Cordapp {
|
||||
val schedulableFlows: List<Class<out FlowLogic<*>>>
|
||||
val services: List<Class<out SerializeAsToken>>
|
||||
val serializationWhitelists: List<SerializationWhitelist>
|
||||
val serializationCustomSerializerProxies: List<Class<*>>
|
||||
val serializationCustomSerializers: List<Class<out SerializationCustomSerializer>>
|
||||
val customSchemas: Set<MappedSchema>
|
||||
val jarPath: URL
|
||||
val cordappClasses: List<String>
|
||||
|
@ -3,6 +3,7 @@ package net.corda.core.internal.cordapp
|
||||
import net.corda.core.cordapp.Cordapp
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.schemas.MappedSchema
|
||||
import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import net.corda.core.serialization.SerializationWhitelist
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
import java.io.File
|
||||
@ -16,6 +17,8 @@ data class CordappImpl(
|
||||
override val schedulableFlows: List<Class<out FlowLogic<*>>>,
|
||||
override val services: List<Class<out SerializeAsToken>>,
|
||||
override val serializationWhitelists: List<SerializationWhitelist>,
|
||||
override val serializationCustomSerializerProxies: List<Class<*>>,
|
||||
override val serializationCustomSerializers: List<Class<out SerializationCustomSerializer>>,
|
||||
override val customSchemas: Set<MappedSchema>,
|
||||
override val jarPath: URL) : Cordapp {
|
||||
override val name: String = File(jarPath.toURI()).name.removeSuffix(".jar")
|
||||
|
@ -0,0 +1,8 @@
|
||||
package net.corda.core.serialization
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
annotation class CordaCustomSerializer
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
annotation class CordaCustomSerializerProxy
|
||||
|
@ -0,0 +1,46 @@
|
||||
package net.corda.core.serialization
|
||||
|
||||
import java.lang.reflect.Type
|
||||
|
||||
/**
|
||||
* Allows CorDapps to provide custom serializers for third party libraries where those libraries cannot
|
||||
* be recompiled with the -parmater flag rendering their classes natively serializable by Corda. In this case
|
||||
* a proxy serializer can be written that extends this type whose purpose is to move between those an
|
||||
* unserializable types and an intermediate representation
|
||||
*
|
||||
* NOTE: The proxy object must be specified as a seperate class. However, this can be defined within the
|
||||
* scope of the serializer. Also, that class must be annotated with the [CordaCustomSerializerProxy]
|
||||
* annotation
|
||||
*
|
||||
* For instances of this class to be discoverable they must be annotated with the [CordaCustomSerializer]
|
||||
* annotation.
|
||||
*
|
||||
* Failing to apply either annotation will result in the class not being loaded by Corda and thus serialization
|
||||
* failing
|
||||
*
|
||||
* @property type the type of the object that this class is proxying
|
||||
* @property ptype the type of the proxy object used as an intermediate representation of [type]
|
||||
*/
|
||||
interface SerializationCustomSerializer {
|
||||
/**
|
||||
* Should facilitate the conversion of the third party object into the serializable
|
||||
* local class specified by [ptype]
|
||||
*/
|
||||
fun toProxy(obj: Any) : Any
|
||||
|
||||
/**
|
||||
* Should facilitate the conversion of the proxy object into a new instance of the
|
||||
* unserializable type
|
||||
*/
|
||||
fun fromProxy(proxy: Any) : Any
|
||||
|
||||
/**
|
||||
* Should be set to the type of the object being proxied
|
||||
*/
|
||||
val type: Type
|
||||
|
||||
/**
|
||||
* Should be set to the proxy objects type
|
||||
*/
|
||||
val ptype: Type
|
||||
}
|
69
docs/source/cordapp-custom-serializers.rst
Normal file
69
docs/source/cordapp-custom-serializers.rst
Normal file
@ -0,0 +1,69 @@
|
||||
Pluggable Serializers for CorDapps
|
||||
==================================
|
||||
|
||||
To be serializable by Corda Java classes must be compiled with the -parameter switch to enable mathcing of ass property
|
||||
to constructor parameter. However, when this isn't possible CorDapps can provide custom proxy serialiszers that Corda
|
||||
can use to move from types it cannot serialiser to an interim represtnation that it can with the transformation to and
|
||||
from this proxy object being handled by the supplied serialiser.
|
||||
|
||||
Serializer Location
|
||||
-------------------
|
||||
Custom serializers should be placed in the plugins directory fo a CorDapp or a sub directory (placing it in a sub
|
||||
directory however does require that directory be added to the list of locations scanned within the jar)
|
||||
|
||||
Writing a Custom Serializer
|
||||
--------------------------
|
||||
|
||||
Serializers must
|
||||
* Inherit from net.corda.core.serialization.SerializationCustomSerializer
|
||||
* Be annotated with the @CordaCustomSerializer annotation
|
||||
* Provide a proxy class to transform the objectto and from
|
||||
* Have that proxy class annotated with the @CordaCustomSerializerProxy annotation
|
||||
|
||||
Serializers inheriting from SerializationCustomSerializer have to implement two methods and two types
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
Consider this example class
|
||||
|
||||
.. sourcecode:: java
|
||||
public final class Example {
|
||||
private final Int a
|
||||
private final Int b
|
||||
|
||||
private Example(Int a, Int b) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
public static Example of (int[] a) { return Example(a[0], a[1]); }
|
||||
|
||||
public int getA() { return a; }
|
||||
public int getB() { return b; }
|
||||
}
|
||||
|
||||
This would require a serialiser as follows
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
@CordaCustomSerializer
|
||||
class ExampleSerializer : SerializationCustomSerializer {
|
||||
@CordaCustomSerializerProxy
|
||||
data class Proxy(val a: Int, val b: Int)
|
||||
|
||||
override fun toProxy(obj: Any): Any = Proxy((obj as Example).a, obj.b)
|
||||
|
||||
override fun fromProxy(proxy: Any): Any {
|
||||
val constructorArg = IntArray(2);
|
||||
constructorArg[0] = (proxy as Proxy).a
|
||||
constructorArg[1] = proxy.b
|
||||
return Example.create(constructorArg)
|
||||
}
|
||||
|
||||
override val type: Type get() = Example::class.java
|
||||
override val ptype: Type get() = Proxy::class.java
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -12,6 +12,20 @@ Unreleased
|
||||
That is the ability to alter an enum constant and, as long as certain rules are followed and the correct
|
||||
annotations applied, have older and newer instances of that enumeration be understood.
|
||||
|
||||
* **AMQP Enabled**
|
||||
|
||||
AMQP Serialization is now enabled for both peer to peer communication and writing states to the vault. This change
|
||||
brings a stable format Corda can support internally throughout it's lifetime that meets the needs of Corda and our
|
||||
users.
|
||||
|
||||
* **CorDapp Custom Serializers**
|
||||
|
||||
To allow interop with third party libraries that cannot be recompiled we add functionality that allows custom serialises
|
||||
to be written for those classes provided. If needed, a proxy object can be created as an interim step that allows Corda's internal
|
||||
serialisers to operate on those types.
|
||||
|
||||
A good example of this is the SIMM valuation demo which has a number of such serializers defined in the plugin/customserializers package
|
||||
|
||||
Release 2.0
|
||||
----------
|
||||
Following quickly on the heels of the release of Corda 1.0, Corda version 2.0 consolidates
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
package net.corda.nodeapi.internal.serialization.amqp
|
||||
|
||||
import net.corda.core.cordapp.Cordapp
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.utilities.ByteSequence
|
||||
import net.corda.nodeapi.internal.serialization.DefaultWhitelist
|
||||
@ -24,7 +25,8 @@ fun SerializerFactory.addToWhitelist(vararg types: Class<*>) {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractAMQPSerializationScheme : SerializationScheme {
|
||||
abstract class AbstractAMQPSerializationScheme(val cordappLoader: List<Cordapp>) : SerializationScheme {
|
||||
|
||||
companion object {
|
||||
private val serializationWhitelists: List<SerializationWhitelist> by lazy {
|
||||
ServiceLoader.load(SerializationWhitelist::class.java, this::class.java.classLoader).toList() + DefaultWhitelist
|
||||
@ -62,8 +64,15 @@ abstract class AbstractAMQPSerializationScheme : SerializationScheme {
|
||||
register(net.corda.nodeapi.internal.serialization.amqp.custom.EnumSetSerializer(this))
|
||||
register(net.corda.nodeapi.internal.serialization.amqp.custom.ContractAttachmentSerializer(this))
|
||||
}
|
||||
for (whitelistProvider in serializationWhitelists)
|
||||
for (whitelistProvider in serializationWhitelists) {
|
||||
factory.addToWhitelist(*whitelistProvider.whitelist.toTypedArray())
|
||||
}
|
||||
|
||||
cordappLoader.forEach { ca ->
|
||||
ca.serializationCustomSerializers.forEach {
|
||||
factory.registerExternal(CorDappCustomSerializer(it.newInstance(), factory))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val serializerFactoriesForContexts = ConcurrentHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>()
|
||||
@ -97,11 +106,11 @@ abstract class AbstractAMQPSerializationScheme : SerializationScheme {
|
||||
return SerializationOutput(serializerFactory).serialize(obj)
|
||||
}
|
||||
|
||||
protected fun canDeserializeVersion(byteSequence: ByteSequence): Boolean = AMQP_ENABLED && byteSequence == AmqpHeaderV1_0
|
||||
protected fun canDeserializeVersion(byteSequence: ByteSequence): Boolean = byteSequence == AmqpHeaderV1_0
|
||||
}
|
||||
|
||||
// TODO: This will eventually cover server RPC as well and move to node module, but for now this is not implemented
|
||||
class AMQPServerSerializationScheme : AbstractAMQPSerializationScheme() {
|
||||
class AMQPServerSerializationScheme(cordapps: List<Cordapp> = emptyList()) : AbstractAMQPSerializationScheme(cordapps) {
|
||||
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
@ -118,7 +127,7 @@ class AMQPServerSerializationScheme : AbstractAMQPSerializationScheme() {
|
||||
}
|
||||
|
||||
// TODO: This will eventually cover client RPC as well and move to client module, but for now this is not implemented
|
||||
class AMQPClientSerializationScheme : AbstractAMQPSerializationScheme() {
|
||||
class AMQPClientSerializationScheme(cordapps: List<Cordapp> = emptyList()) : AbstractAMQPSerializationScheme(cordapps) {
|
||||
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
@ -0,0 +1,41 @@
|
||||
package net.corda.nodeapi.internal.serialization.amqp
|
||||
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory.Companion.nameForType
|
||||
import org.apache.qpid.proton.amqp.Symbol
|
||||
import org.apache.qpid.proton.codec.Data
|
||||
import java.lang.reflect.Type
|
||||
|
||||
class CorDappCustomSerializer(
|
||||
private val serialiser: SerializationCustomSerializer,
|
||||
factory: SerializerFactory)
|
||||
: AMQPSerializer<Any>, SerializerFor {
|
||||
override val revealSubclassesInSchema: Boolean get() = false
|
||||
override val type: Type get() = serialiser.type
|
||||
override val typeDescriptor = Symbol.valueOf("$DESCRIPTOR_DOMAIN:${nameForType(type)}")
|
||||
val descriptor: Descriptor = Descriptor(typeDescriptor)
|
||||
|
||||
private val proxySerializer: ObjectSerializer by lazy { ObjectSerializer(serialiser.ptype, factory) }
|
||||
|
||||
override fun writeClassInfo(output: SerializationOutput) {}
|
||||
|
||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
||||
val proxy = serialiser.toProxy(obj)
|
||||
|
||||
data.withDescribed(descriptor) {
|
||||
data.withList {
|
||||
for (property in proxySerializer.propertySerializers) {
|
||||
property.writeProperty(proxy, this, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun readObject(obj: Any, schema: Schema, input: DeserializationInput): Any {
|
||||
return serialiser.fromProxy(uncheckedCast(proxySerializer.readObject(obj, schema, input)))
|
||||
}
|
||||
|
||||
override fun isSerializerFor(clazz: Class<*>): Boolean = clazz == type
|
||||
}
|
||||
|
@ -6,22 +6,27 @@ import org.apache.qpid.proton.amqp.Symbol
|
||||
import org.apache.qpid.proton.codec.Data
|
||||
import java.lang.reflect.Type
|
||||
|
||||
interface SerializerFor {
|
||||
/**
|
||||
* This method should return true if the custom serializer can serialize an instance of the class passed as the
|
||||
* parameter.
|
||||
*/
|
||||
fun isSerializerFor(clazz: Class<*>): Boolean
|
||||
|
||||
val revealSubclassesInSchema: Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for serializers of core platform types that do not conform to the usual serialization rules and thus
|
||||
* cannot be automatically serialized.
|
||||
*/
|
||||
abstract class CustomSerializer<T : Any> : AMQPSerializer<T> {
|
||||
abstract class CustomSerializer<T : Any> : AMQPSerializer<T>, SerializerFor {
|
||||
/**
|
||||
* This is a collection of custom serializers that this custom serializer depends on. e.g. for proxy objects
|
||||
* that refer to other custom types etc.
|
||||
*/
|
||||
open val additionalSerializers: Iterable<CustomSerializer<out Any>> = emptyList()
|
||||
|
||||
/**
|
||||
* This method should return true if the custom serializer can serialize an instance of the class passed as the
|
||||
* parameter.
|
||||
*/
|
||||
abstract fun isSerializerFor(clazz: Class<*>): Boolean
|
||||
|
||||
protected abstract val descriptor: Descriptor
|
||||
/**
|
||||
@ -33,7 +38,7 @@ abstract class CustomSerializer<T : Any> : AMQPSerializer<T> {
|
||||
/**
|
||||
* Whether subclasses using this serializer via inheritance should have a mapping in the schema.
|
||||
*/
|
||||
open val revealSubclassesInSchema: Boolean = false
|
||||
override val revealSubclassesInSchema: Boolean get() = false
|
||||
|
||||
override fun writeObject(obj: Any, data: Data, type: Type, output: SerializationOutput) {
|
||||
data.withDescribed(descriptor) {
|
||||
|
@ -82,11 +82,13 @@ private fun <T : Any> propertiesForSerializationFromConstructor(kotlinConstructo
|
||||
for (param in kotlinConstructor.parameters) {
|
||||
val name = param.name ?: throw NotSerializableException("Constructor parameter of $clazz has no name.")
|
||||
val matchingProperty = properties[name] ?:
|
||||
throw NotSerializableException("No property matching constructor parameter named '$name' of '$clazz'." +
|
||||
" If using Java, check that you have the -parameters option specified in the Java compiler.")
|
||||
throw NotSerializableException("No property matching constructor parameter named '$name' of '$clazz'. " +
|
||||
"If using Java, check that you have the -parameters option specified in the Java compiler. " +
|
||||
"Alternately, provide a proxy serializer (SerializationCustomSerializer) if recompiling isn't an option")
|
||||
// Check that the method has a getter in java.
|
||||
val getter = matchingProperty.readMethod ?: throw NotSerializableException("Property has no getter method for $name of $clazz." +
|
||||
" If using Java and the parameter name looks anonymous, check that you have the -parameters option specified in the Java compiler.")
|
||||
val getter = matchingProperty.readMethod ?: throw NotSerializableException("Property has no getter method for $name of $clazz. " +
|
||||
"If using Java and the parameter name looks anonymous, check that you have the -parameters option specified in the Java compiler." +
|
||||
"Alternately, provide a proxy serializer (SerializationCustomSerializer) if recompiling isn't an option")
|
||||
val returnType = resolveTypeVariables(getter.genericReturnType, type)
|
||||
if (constructorParamTakesReturnTypeOfGetter(returnType, getter.genericReturnType, param)) {
|
||||
rc += PropertySerializer.make(name, getter, returnType, factory)
|
||||
|
@ -36,7 +36,7 @@ data class FactorySchemaAndDescriptor(val schemas: SerializationSchemas, val typ
|
||||
open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
|
||||
private val serializersByType = ConcurrentHashMap<Type, AMQPSerializer<Any>>()
|
||||
private val serializersByDescriptor = ConcurrentHashMap<Any, AMQPSerializer<Any>>()
|
||||
private val customSerializers = CopyOnWriteArrayList<CustomSerializer<out Any>>()
|
||||
private val customSerializers = CopyOnWriteArrayList<SerializerFor>()
|
||||
val transformsCache = ConcurrentHashMap<String, EnumMap<TransformTypes, MutableList<Transform>>>()
|
||||
|
||||
open val classCarpenter = ClassCarpenter(cl, whitelist)
|
||||
@ -196,6 +196,13 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
|
||||
}
|
||||
}
|
||||
|
||||
fun registerExternal(customSerializer: CorDappCustomSerializer) {
|
||||
if (!serializersByDescriptor.containsKey(customSerializer.typeDescriptor)) {
|
||||
customSerializers += customSerializer
|
||||
serializersByDescriptor[customSerializer.typeDescriptor] = customSerializer
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over an AMQP schema, for each type ascertain weather it's on ClassPath of [classloader] amd
|
||||
* if not use the [ClassCarpenter] to generate a class to use in it's place
|
||||
@ -267,11 +274,13 @@ open class SerializerFactory(val whitelist: ClassWhitelist, cl: ClassLoader) {
|
||||
for (customSerializer in customSerializers) {
|
||||
if (customSerializer.isSerializerFor(clazz)) {
|
||||
val declaredSuperClass = declaredType.asClass()?.superclass
|
||||
if (declaredSuperClass == null || !customSerializer.isSerializerFor(declaredSuperClass) || !customSerializer.revealSubclassesInSchema) {
|
||||
return customSerializer
|
||||
return if (declaredSuperClass == null
|
||||
|| !customSerializer.isSerializerFor(declaredSuperClass)
|
||||
|| !customSerializer.revealSubclassesInSchema) {
|
||||
customSerializer as? AMQPSerializer<Any>
|
||||
} else {
|
||||
// Make a subclass serializer for the subclass and return that...
|
||||
return CustomSerializer.SubClass(clazz, uncheckedCast(customSerializer))
|
||||
CustomSerializer.SubClass(clazz, uncheckedCast(customSerializer))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,92 @@
|
||||
package net.corda.nodeapi.internal.serialization.amqp
|
||||
|
||||
import org.junit.Test
|
||||
import net.corda.core.serialization.CordaCustomSerializer
|
||||
import net.corda.core.serialization.CordaCustomSerializerProxy
|
||||
import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import java.lang.reflect.Type
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class CorDappSerializerTests {
|
||||
data class NeedsProxy (val a: String)
|
||||
|
||||
@CordaCustomSerializer
|
||||
class NeedsProxyProxySerializer : SerializationCustomSerializer {
|
||||
@CordaCustomSerializerProxy
|
||||
data class Proxy(val proxy_a_: String)
|
||||
|
||||
override val type: Type get() = NeedsProxy::class.java
|
||||
override val ptype: Type get() = Proxy::class.java
|
||||
|
||||
override fun fromProxy(proxy: Any) : Any {
|
||||
println ("NeedsProxyProxySerialiser - fromProxy")
|
||||
return NeedsProxy((proxy as Proxy).proxy_a_)
|
||||
}
|
||||
override fun toProxy(obj: Any) : Any {
|
||||
println ("NeedsProxyProxySerialiser - to Proxy")
|
||||
return Proxy((obj as NeedsProxy).a)
|
||||
}
|
||||
}
|
||||
|
||||
// Standard proxy serialiser used internally, here for comparison purposes
|
||||
class InternalProxySerialiser(factory: SerializerFactory) :
|
||||
CustomSerializer.Proxy<NeedsProxy, InternalProxySerialiser.Proxy> (
|
||||
NeedsProxy::class.java,
|
||||
InternalProxySerialiser.Proxy::class.java,
|
||||
factory) {
|
||||
data class Proxy(val proxy_a_: String)
|
||||
|
||||
override fun toProxy(obj: NeedsProxy): Proxy {
|
||||
println ("InternalProxySerialiser - toProxy")
|
||||
return Proxy(obj.a)
|
||||
}
|
||||
|
||||
override fun fromProxy(proxy: Proxy): NeedsProxy {
|
||||
println ("InternalProxySerialiser - fromProxy")
|
||||
return NeedsProxy(proxy.proxy_a_)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `type uses proxy`() {
|
||||
val internalProxyFactory = testDefaultFactory()
|
||||
val proxyFactory = testDefaultFactory()
|
||||
val defaultFactory = testDefaultFactory()
|
||||
|
||||
val msg = "help"
|
||||
|
||||
proxyFactory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), proxyFactory))
|
||||
internalProxyFactory.register (InternalProxySerialiser(internalProxyFactory))
|
||||
|
||||
val needsProxy = NeedsProxy(msg)
|
||||
|
||||
val bAndSProxy = SerializationOutput(proxyFactory).serializeAndReturnSchema (needsProxy)
|
||||
val bAndSInternal = SerializationOutput(internalProxyFactory).serializeAndReturnSchema (needsProxy)
|
||||
val bAndSDefault = SerializationOutput(defaultFactory).serializeAndReturnSchema (needsProxy)
|
||||
|
||||
val objFromDefault = DeserializationInput(defaultFactory).deserializeAndReturnEnvelope(bAndSDefault.obj)
|
||||
val objFromInternal = DeserializationInput(internalProxyFactory).deserializeAndReturnEnvelope(bAndSInternal.obj)
|
||||
val objFromProxy = DeserializationInput(proxyFactory).deserializeAndReturnEnvelope(bAndSProxy.obj)
|
||||
|
||||
assertEquals(msg, objFromDefault.obj.a)
|
||||
assertEquals(msg, objFromInternal.obj.a)
|
||||
assertEquals(msg, objFromProxy.obj.a)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun proxiedTypeIsNested() {
|
||||
data class A (val a: Int, val b: NeedsProxy)
|
||||
|
||||
val factory = testDefaultFactory()
|
||||
factory.registerExternal (CorDappCustomSerializer(NeedsProxyProxySerializer(), factory))
|
||||
|
||||
val tv1 = 100
|
||||
val tv2 = "pants schmants"
|
||||
val bAndS = SerializationOutput(factory).serializeAndReturnSchema (A(tv1, NeedsProxy(tv2)))
|
||||
|
||||
val objFromDefault = DeserializationInput(factory).deserializeAndReturnEnvelope(bAndS.obj)
|
||||
|
||||
assertEquals(tv1, objFromDefault.obj.a)
|
||||
assertEquals(tv2, objFromDefault.obj.b.a)
|
||||
}
|
||||
}
|
@ -25,7 +25,7 @@ class OverridePKSerializerTest {
|
||||
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
class AMQPTestSerializationScheme : AbstractAMQPSerializationScheme() {
|
||||
class AMQPTestSerializationScheme : AbstractAMQPSerializationScheme(emptyList()) {
|
||||
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
|
||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
@ -593,7 +593,7 @@ class SerializationOutputTests {
|
||||
fun `test transaction state`() {
|
||||
val state = TransactionState(FooState(), FOO_PROGRAM_ID, MEGA_CORP)
|
||||
|
||||
val scheme = AMQPServerSerializationScheme()
|
||||
val scheme = AMQPServerSerializationScheme(emptyList())
|
||||
val func = scheme::class.superclasses.single { it.simpleName == "AbstractAMQPSerializationScheme" }
|
||||
.java.getDeclaredMethod("registerCustomSerializers", SerializerFactory::class.java)
|
||||
func.isAccessible = true
|
||||
|
@ -300,11 +300,11 @@ open class Node(configuration: NodeConfiguration,
|
||||
nodeSerializationEnv = SerializationEnvironmentImpl(
|
||||
SerializationFactoryImpl().apply {
|
||||
registerScheme(KryoServerSerializationScheme())
|
||||
registerScheme(AMQPServerSerializationScheme())
|
||||
registerScheme(AMQPServerSerializationScheme(cordappLoader.cordapps))
|
||||
},
|
||||
KRYO_P2P_CONTEXT.withClassLoader(classloader),
|
||||
p2pContext = AMQP_P2P_CONTEXT.withClassLoader(classloader),
|
||||
rpcServerContext = KRYO_RPC_SERVER_CONTEXT.withClassLoader(classloader),
|
||||
storageContext = KRYO_STORAGE_CONTEXT.withClassLoader(classloader),
|
||||
storageContext = AMQP_STORAGE_CONTEXT.withClassLoader(classloader),
|
||||
checkpointContext = KRYO_CHECKPOINT_CONTEXT.withClassLoader(classloader))
|
||||
}
|
||||
|
||||
|
@ -10,9 +10,8 @@ import net.corda.core.internal.*
|
||||
import net.corda.core.internal.cordapp.CordappImpl
|
||||
import net.corda.core.node.services.CordaService
|
||||
import net.corda.core.schemas.MappedSchema
|
||||
import net.corda.core.serialization.SerializationWhitelist
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.node.internal.classloading.requireAnnotation
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.nodeapi.internal.serialization.DefaultWhitelist
|
||||
@ -175,15 +174,17 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
|
||||
/** A Cordapp representing the core package which is not scanned automatically. */
|
||||
@VisibleForTesting
|
||||
internal val coreCordapp = CordappImpl(
|
||||
listOf(),
|
||||
listOf(),
|
||||
coreRPCFlows,
|
||||
listOf(),
|
||||
listOf(),
|
||||
listOf(),
|
||||
listOf(),
|
||||
setOf(),
|
||||
ContractUpgradeFlow.javaClass.protectionDomain.codeSource.location // Core JAR location
|
||||
contractClassNames = listOf(),
|
||||
initiatedFlows = listOf(),
|
||||
rpcFlows = coreRPCFlows,
|
||||
serviceFlows = listOf(),
|
||||
schedulableFlows = listOf(),
|
||||
services = listOf(),
|
||||
serializationWhitelists = listOf(),
|
||||
serializationCustomSerializerProxies = listOf(),
|
||||
serializationCustomSerializers = listOf(),
|
||||
customSchemas = setOf(),
|
||||
jarPath = ContractUpgradeFlow.javaClass.protectionDomain.codeSource.location // Core JAR location
|
||||
)
|
||||
}
|
||||
|
||||
@ -197,6 +198,8 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
|
||||
findSchedulableFlows(scanResult),
|
||||
findServices(scanResult),
|
||||
findPlugins(it),
|
||||
findSerialiserProxies(scanResult),
|
||||
findSerialzers(scanResult),
|
||||
findCustomSchemas(scanResult),
|
||||
it.url)
|
||||
}
|
||||
@ -247,6 +250,14 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
|
||||
} + DefaultWhitelist // Always add the DefaultWhitelist to the whitelist for an app.
|
||||
}
|
||||
|
||||
private fun findSerialiserProxies(scanResult: RestrictedScanResult) : List<Class<*>> {
|
||||
return scanResult.getClassesWithAnnotation(Class::class, CordaCustomSerializerProxy::class)
|
||||
}
|
||||
|
||||
private fun findSerialzers(scanResult: RestrictedScanResult) : List<Class<out SerializationCustomSerializer>> {
|
||||
return scanResult.getClassesWithAnnotation(SerializationCustomSerializer::class, CordaCustomSerializer::class)
|
||||
}
|
||||
|
||||
private fun findCustomSchemas(scanResult: RestrictedScanResult): Set<MappedSchema> {
|
||||
return scanResult.getClassesWithSuperclass(MappedSchema::class).toSet()
|
||||
}
|
||||
|
@ -239,4 +239,4 @@ private fun readMap(buffer: BufferInput<out BufferInput<*>>, serializer: Seriali
|
||||
put(serializer.readObject(buffer), serializer.readObject(buffer))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ class SimmValuationTest {
|
||||
|
||||
@Test
|
||||
fun `runs SIMM valuation demo`() {
|
||||
driver(isDebug = true, extraCordappPackagesToScan = listOf("net.corda.vega.contracts")) {
|
||||
driver(isDebug = true, extraCordappPackagesToScan = listOf("net.corda.vega.contracts", "net.corda.vega.plugin.customserializers")) {
|
||||
val nodeAFuture = startNode(providedName = nodeALegalName)
|
||||
val nodeBFuture = startNode(providedName = nodeBLegalName)
|
||||
val (nodeA, nodeB) = listOf(nodeAFuture, nodeBFuture).map { it.getOrThrow() }
|
||||
|
@ -19,7 +19,7 @@ val PORTFOLIO_SWAP_PROGRAM_ID = "net.corda.vega.contracts.PortfolioSwap"
|
||||
* given point in time. This state can be consumed to create a new state with a mutated valuation or portfolio.
|
||||
*/
|
||||
data class PortfolioState(val portfolio: List<StateRef>,
|
||||
private val _parties: Pair<AbstractParty, AbstractParty>,
|
||||
val _parties: Pair<AbstractParty, AbstractParty>,
|
||||
val valuationDate: LocalDate,
|
||||
val valuation: PortfolioValuation? = null,
|
||||
override val linearId: UniqueIdentifier = UniqueIdentifier())
|
||||
|
@ -10,6 +10,7 @@ import com.opengamma.strata.market.curve.CurveName
|
||||
import com.opengamma.strata.market.param.CurrencyParameterSensitivities
|
||||
import com.opengamma.strata.market.param.CurrencyParameterSensitivity
|
||||
import com.opengamma.strata.market.param.TenorDateParameterMetadata
|
||||
import com.opengamma.strata.market.param.ParameterMetadata
|
||||
import net.corda.core.serialization.SerializationWhitelist
|
||||
import net.corda.vega.analytics.CordaMarketData
|
||||
import net.corda.vega.analytics.InitialMarginTriple
|
||||
@ -34,6 +35,7 @@ class SimmPluginRegistry : SerializationWhitelist {
|
||||
DoubleArray::class.java,
|
||||
CurveName::class.java,
|
||||
TenorDateParameterMetadata::class.java,
|
||||
Tenor::class.java
|
||||
Tenor::class.java,
|
||||
ParameterMetadata::class.java
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
package net.corda.vega.plugin.customserializers
|
||||
|
||||
import com.opengamma.strata.market.param.CurrencyParameterSensitivities
|
||||
import com.opengamma.strata.market.param.CurrencyParameterSensitivity
|
||||
import net.corda.core.serialization.CordaCustomSerializer
|
||||
import net.corda.core.serialization.CordaCustomSerializerProxy
|
||||
import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import java.lang.reflect.Type
|
||||
|
||||
@CordaCustomSerializer
|
||||
@Suppress("UNUSED")
|
||||
class CurrencyParameterSensitivitiesSerializer : SerializationCustomSerializer {
|
||||
@CordaCustomSerializerProxy
|
||||
data class Proxy(val sensitivities: List<CurrencyParameterSensitivity>)
|
||||
|
||||
override val type: Type get() = CurrencyParameterSensitivities::class.java
|
||||
override val ptype: Type get() = Proxy::class.java
|
||||
|
||||
override fun fromProxy(proxy: Any): Any = CurrencyParameterSensitivities.of ((proxy as Proxy).sensitivities)
|
||||
override fun toProxy(obj: Any): Any = Proxy ((obj as CurrencyParameterSensitivities).sensitivities)
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package net.corda.vega.plugin.customserializers
|
||||
|
||||
import com.opengamma.strata.market.param.CurrencyParameterSensitivity
|
||||
import com.opengamma.strata.market.param.ParameterMetadata
|
||||
import com.opengamma.strata.data.MarketDataName
|
||||
import com.opengamma.strata.collect.array.DoubleArray
|
||||
import com.opengamma.strata.basics.currency.Currency
|
||||
import net.corda.core.serialization.CordaCustomSerializer
|
||||
import net.corda.core.serialization.CordaCustomSerializerProxy
|
||||
import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import java.lang.reflect.Type
|
||||
|
||||
@CordaCustomSerializer
|
||||
@Suppress("UNUSED")
|
||||
class CurrencyParameterSensitivitySerializer : SerializationCustomSerializer {
|
||||
@CordaCustomSerializerProxy
|
||||
data class Proxy(val currency: Currency, val marketDataName: MarketDataName<*>,
|
||||
val parameterMetadata: List<ParameterMetadata>,
|
||||
val sensitivity: DoubleArray)
|
||||
|
||||
override val type: Type get() = CurrencyParameterSensitivity::class.java
|
||||
override val ptype: Type get() = Proxy::class.java
|
||||
|
||||
override fun fromProxy(proxy: Any): Any =
|
||||
CurrencyParameterSensitivity.of(
|
||||
(proxy as Proxy).marketDataName,
|
||||
proxy.parameterMetadata,
|
||||
proxy.currency,
|
||||
proxy.sensitivity)
|
||||
|
||||
override fun toProxy(obj: Any): Any = Proxy ((obj as CurrencyParameterSensitivity).currency,
|
||||
obj.marketDataName, obj.parameterMetadata, obj.sensitivity)
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package net.corda.vega.plugin.customserializers
|
||||
|
||||
import com.opengamma.strata.basics.currency.Currency
|
||||
import net.corda.core.serialization.CordaCustomSerializer
|
||||
import net.corda.core.serialization.CordaCustomSerializerProxy
|
||||
import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import java.lang.reflect.Type
|
||||
|
||||
@CordaCustomSerializer
|
||||
@Suppress("UNUSED")
|
||||
class CurrencySerializer : SerializationCustomSerializer {
|
||||
@CordaCustomSerializerProxy
|
||||
data class Proxy(val currency: String)
|
||||
|
||||
override val type: Type get() = Currency::class.java
|
||||
override val ptype: Type get() = Proxy::class.java
|
||||
|
||||
override fun fromProxy(proxy: Any): Any = Currency.parse((proxy as Proxy).currency)
|
||||
override fun toProxy(obj: Any): Any = Proxy((obj as Currency).toString())
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package net.corda.vega.plugin.customserializers
|
||||
|
||||
import net.corda.core.serialization.CordaCustomSerializer
|
||||
import net.corda.core.serialization.CordaCustomSerializerProxy
|
||||
import net.corda.core.serialization.SerializationCustomSerializer
|
||||
import com.opengamma.strata.collect.array.DoubleArray
|
||||
import java.lang.reflect.Type
|
||||
|
||||
@CordaCustomSerializer
|
||||
@Suppress("UNUSED")
|
||||
class DoubleArraySerializer : SerializationCustomSerializer {
|
||||
@CordaCustomSerializerProxy
|
||||
data class Proxy(val amount: kotlin.DoubleArray)
|
||||
|
||||
override val type: Type get() = DoubleArray::class.java
|
||||
override val ptype: Type get() = Proxy::class.java
|
||||
|
||||
override fun fromProxy(proxy: Any): Any = DoubleArray.copyOf((proxy as Proxy).amount)
|
||||
override fun toProxy(obj: Any): Any = Proxy((obj as DoubleArray).toArray())
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package net.corda.vega.plugin.customserializers
|
||||
|
||||
import com.opengamma.strata.basics.currency.MultiCurrencyAmount
|
||||
import com.opengamma.strata.basics.currency.Currency
|
||||
import net.corda.core.serialization.*
|
||||
import java.lang.reflect.Type
|
||||
|
||||
@CordaCustomSerializer
|
||||
@Suppress("UNUSED")
|
||||
class MultiCurrencyAmountSerializer : SerializationCustomSerializer {
|
||||
@CordaCustomSerializerProxy
|
||||
data class Proxy(val curencies : Map<Currency, Double>)
|
||||
|
||||
override fun toProxy(obj: Any): Any = Proxy((obj as MultiCurrencyAmount).toMap())
|
||||
override fun fromProxy(proxy: Any): Any = MultiCurrencyAmount.of((proxy as Proxy).curencies)
|
||||
|
||||
override val type: Type get() = MultiCurrencyAmount::class.java
|
||||
override val ptype: Type get() = Proxy::class.java
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,23 @@
|
||||
package net.corda.vega.plugin.customserializers
|
||||
|
||||
import com.opengamma.strata.basics.date.Tenor
|
||||
import com.opengamma.strata.market.param.TenorDateParameterMetadata
|
||||
import net.corda.core.serialization.*
|
||||
import java.lang.reflect.Type
|
||||
import java.time.LocalDate
|
||||
|
||||
@CordaCustomSerializer
|
||||
@Suppress("UNUSED")
|
||||
class TenorDateParameterMetadataSerializer : SerializationCustomSerializer {
|
||||
@CordaCustomSerializerProxy
|
||||
data class Proxy(val tenor: Tenor, val date: LocalDate, val identifier: Tenor, val label: String)
|
||||
|
||||
override val type: Type get() = TenorDateParameterMetadata::class.java
|
||||
override val ptype: Type get() = Proxy::class.java
|
||||
|
||||
override fun toProxy(obj: Any): Any = Proxy(
|
||||
(obj as TenorDateParameterMetadata).tenor, obj.date, obj.identifier, obj.label)
|
||||
|
||||
override fun fromProxy(proxy: Any): Any = TenorDateParameterMetadata.of(
|
||||
(proxy as Proxy).date, proxy.tenor, proxy.label)
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package net.corda.vega.plugin.customserializers
|
||||
|
||||
import com.opengamma.strata.basics.date.Tenor
|
||||
import net.corda.core.serialization.*
|
||||
import java.lang.reflect.Type
|
||||
import java.time.Period
|
||||
|
||||
@CordaCustomSerializer
|
||||
@Suppress("UNUSED")
|
||||
class TenorSerializer : SerializationCustomSerializer {
|
||||
@CordaCustomSerializerProxy
|
||||
data class Proxy(val years: Int, val months: Int, val days: Int, val name: String)
|
||||
|
||||
override val type: Type get() = Tenor::class.java
|
||||
override val ptype: Type get() = Proxy::class.java
|
||||
|
||||
override fun toProxy(obj: Any): Any = Proxy(
|
||||
(obj as Tenor).period.years, obj.period.months, obj.period.days, obj.toString())
|
||||
|
||||
override fun fromProxy(proxy: Any): Any = Tenor.of (Period.of((proxy as Proxy).years, proxy.months, proxy.days))
|
||||
}
|
@ -47,6 +47,8 @@ import net.corda.testing.testNodeConfiguration
|
||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||
import org.apache.sshd.common.util.security.SecurityUtils
|
||||
import rx.internal.schedulers.CachedThreadScheduler
|
||||
import org.slf4j.Logger
|
||||
import java.io.Closeable
|
||||
import java.math.BigInteger
|
||||
import java.nio.file.Path
|
||||
import java.security.KeyPair
|
||||
|
@ -62,7 +62,7 @@ fun <T> withTestSerialization(inheritable: Boolean = false, callable: (Serializa
|
||||
|
||||
/**
|
||||
* For example your test class uses [SerializationEnvironmentRule] but you want to turn it off for one method.
|
||||
* Use sparingly, ideally a test class shouldn't mix serialization init mechanisms.
|
||||
* Use sparingly, ideally a test class shouldn't mix serializers init mechanisms.
|
||||
*/
|
||||
fun <T> withoutTestSerialization(callable: () -> T): T {
|
||||
val (property, env) = listOf(_contextSerializationEnv, _inheritableContextSerializationEnv).map { Pair(it, it.get()) }.single { it.second != null }
|
||||
@ -99,13 +99,12 @@ private fun createTestSerializationEnv(label: String) = object : SerializationEn
|
||||
SerializationFactoryImpl().apply {
|
||||
registerScheme(KryoClientSerializationScheme())
|
||||
registerScheme(KryoServerSerializationScheme())
|
||||
registerScheme(AMQPClientSerializationScheme())
|
||||
registerScheme(AMQPServerSerializationScheme())
|
||||
registerScheme(AMQPClientSerializationScheme(emptyList()))
|
||||
registerScheme(AMQPServerSerializationScheme(emptyList()))
|
||||
},
|
||||
if (isAmqpEnabled()) AMQP_P2P_CONTEXT else KRYO_P2P_CONTEXT,
|
||||
AMQP_P2P_CONTEXT,
|
||||
KRYO_RPC_SERVER_CONTEXT,
|
||||
KRYO_RPC_CLIENT_CONTEXT,
|
||||
if (isAmqpEnabled()) AMQP_STORAGE_CONTEXT else KRYO_STORAGE_CONTEXT,
|
||||
AMQP_STORAGE_CONTEXT,
|
||||
KRYO_CHECKPOINT_CONTEXT) {
|
||||
override fun toString() = "testSerializationEnv($label)"
|
||||
}
|
||||
|
@ -14,7 +14,18 @@ class MockCordappProvider(cordappLoader: CordappLoader, attachmentStorage: Attac
|
||||
val cordappRegistry = mutableListOf<Pair<Cordapp, AttachmentId>>()
|
||||
|
||||
fun addMockCordapp(contractClassName: ContractClassName, attachments: MockAttachmentStorage) {
|
||||
val cordapp = CordappImpl(listOf(contractClassName), emptyList(), emptyList(), emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), Paths.get(".").toUri().toURL())
|
||||
val cordapp = CordappImpl(
|
||||
contractClassNames = listOf(contractClassName),
|
||||
initiatedFlows = emptyList(),
|
||||
rpcFlows = emptyList(),
|
||||
serviceFlows = emptyList(),
|
||||
schedulableFlows = emptyList(),
|
||||
services = emptyList(),
|
||||
serializationWhitelists = emptyList(),
|
||||
serializationCustomSerializerProxies = emptyList(),
|
||||
serializationCustomSerializers = emptyList(),
|
||||
customSchemas = emptySet(),
|
||||
jarPath = Paths.get(".").toUri().toURL())
|
||||
if (cordappRegistry.none { it.first.contractClassNames.contains(contractClassName) }) {
|
||||
cordappRegistry.add(Pair(cordapp, findOrImportAttachment(contractClassName.toByteArray(), attachments)))
|
||||
}
|
||||
|
@ -91,11 +91,7 @@ class Verifier {
|
||||
registerScheme(KryoVerifierSerializationScheme)
|
||||
registerScheme(AMQPVerifierSerializationScheme)
|
||||
},
|
||||
/**
|
||||
* Even though default context is set to Kryo P2P, the encoding will be adjusted depending on the incoming
|
||||
* request received, see use of [context] in [main] method.
|
||||
*/
|
||||
KRYO_P2P_CONTEXT)
|
||||
AMQP_P2P_CONTEXT)
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,7 +104,7 @@ class Verifier {
|
||||
override fun rpcServerKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
private object AMQPVerifierSerializationScheme : AbstractAMQPSerializationScheme() {
|
||||
private object AMQPVerifierSerializationScheme : AbstractAMQPSerializationScheme(emptyList()) {
|
||||
override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean {
|
||||
return (byteSequence == AmqpHeaderV1_0 && (target == SerializationContext.UseCase.P2P))
|
||||
}
|
||||
@ -116,4 +112,4 @@ class Verifier {
|
||||
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory = throw UnsupportedOperationException()
|
||||
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory = throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user