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:
Katelyn Baker 2017-11-08 16:23:38 +00:00
parent ce859178eb
commit 81eb0c1898
32 changed files with 539 additions and 55 deletions

View File

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

View File

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

View File

@ -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")

View File

@ -0,0 +1,8 @@
package net.corda.core.serialization
@Target(AnnotationTarget.CLASS)
annotation class CordaCustomSerializer
@Target(AnnotationTarget.CLASS)
annotation class CordaCustomSerializerProxy

View File

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

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

View File

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

View File

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

View File

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

View File

@ -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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -239,4 +239,4 @@ private fun readMap(buffer: BufferInput<out BufferInput<*>>, serializer: Seriali
put(serializer.readObject(buffer), serializer.readObject(buffer))
}
}
}
}

View File

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

View File

@ -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())

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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