mirror of
https://github.com/corda/corda.git
synced 2024-12-21 22:07:55 +00:00
Move the implementation of the FlowLogicRefFactory internal to the node as it is an implementation detail, not an API.
This commit is contained in:
parent
489661a289
commit
bfa7d50d37
@ -1,194 +1,30 @@
|
||||
package net.corda.core.flows
|
||||
|
||||
import com.google.common.primitives.Primitives
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.lang.reflect.Type
|
||||
import java.util.*
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KParameter
|
||||
import kotlin.reflect.jvm.javaConstructor
|
||||
import kotlin.reflect.jvm.javaType
|
||||
|
||||
/**
|
||||
* A class for conversion to and from [FlowLogic] and [FlowLogicRef] instances.
|
||||
*
|
||||
* Validation of types is performed on the way in and way out in case this object is passed between JVMs which might have differing
|
||||
* whitelists.
|
||||
*
|
||||
* TODO: Ways to populate whitelist of "blessed" flows per node/party
|
||||
* TODO: Ways to populate argument types whitelist. Per node/party or global?
|
||||
* TODO: Align with API related logic for passing in FlowLogic references (FlowRef)
|
||||
* TODO: Actual support for AppContext / AttachmentsClassLoader
|
||||
* The public factory interface for creating validated FlowLogicRef instances as part of the scheduling framework.
|
||||
* Typically this would be used from within the nextScheduledActivity method of a QueryableState to specify
|
||||
* the flow to run at the scheduled time.
|
||||
*/
|
||||
class FlowLogicRefFactory(val flowWhitelist: Map<String, Set<String>>) : SingletonSerializeAsToken() {
|
||||
constructor() : this(mapOf())
|
||||
|
||||
// Pending real dependence on AppContext for class loading etc
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun validateFlowClassName(className: String, appContext: AppContext) {
|
||||
// TODO: make this specific to the attachments in the [AppContext] by including [SecureHash] in whitelist check
|
||||
require(flowWhitelist.containsKey(className)) { "${FlowLogic::class.java.simpleName} of ${FlowLogicRef::class.java.simpleName} must have type on the whitelist: $className" }
|
||||
}
|
||||
|
||||
// Pending real dependence on AppContext for class loading etc
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun validateArgClassName(className: String, argClassName: String, appContext: AppContext) {
|
||||
// TODO: consider more carefully what to whitelist and how to secure flows
|
||||
// For now automatically accept standard java.lang.* and kotlin.* types.
|
||||
// All other types require manual specification at FlowLogicRefFactory construction time.
|
||||
if (argClassName.startsWith("java.lang.") || argClassName.startsWith("kotlin.")) {
|
||||
return
|
||||
}
|
||||
// TODO: make this specific to the attachments in the [AppContext] by including [SecureHash] in whitelist check
|
||||
require(flowWhitelist[className]!!.contains(argClassName)) { "Args to $className must have types on the args whitelist: $argClassName, but it has ${flowWhitelist[className]}" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [FlowLogicRef] for the Kotlin primary constructor of a named [FlowLogic]
|
||||
*/
|
||||
fun createKotlin(flowLogicClassName: String, args: Map<String, Any?>, attachments: List<SecureHash> = emptyList()): FlowLogicRef {
|
||||
val context = AppContext(attachments)
|
||||
validateFlowClassName(flowLogicClassName, context)
|
||||
for (arg in args.values.filterNotNull()) {
|
||||
validateArgClassName(flowLogicClassName, arg.javaClass.name, context)
|
||||
}
|
||||
val clazz = Class.forName(flowLogicClassName)
|
||||
require(FlowLogic::class.java.isAssignableFrom(clazz)) { "$flowLogicClassName is not a FlowLogic" }
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val logic = clazz as Class<FlowLogic<FlowLogic<*>>>
|
||||
return createKotlin(logic, args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [FlowLogicRef] by assuming a single constructor and the given args.
|
||||
*/
|
||||
fun create(type: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef {
|
||||
// TODO: This is used via RPC but it's probably better if we pass in argument names and values explicitly
|
||||
// to avoid requiring only a single constructor.
|
||||
val argTypes = args.map { it?.javaClass }
|
||||
val constructor = try {
|
||||
type.kotlin.constructors.single { ctor ->
|
||||
// Get the types of the arguments, always boxed (as that's what we get in the invocation).
|
||||
val ctorTypes = ctor.javaConstructor!!.parameterTypes.map { Primitives.wrap(it) }
|
||||
if (argTypes.size != ctorTypes.size)
|
||||
return@single false
|
||||
for ((argType, ctorType) in argTypes.zip(ctorTypes)) {
|
||||
if (argType == null) continue // Try and find a match based on the other arguments.
|
||||
if (!ctorType.isAssignableFrom(argType)) return@single false
|
||||
}
|
||||
true
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
throw IllegalFlowLogicException(type, "due to ambiguous match against the constructors: $argTypes")
|
||||
} catch (e: NoSuchElementException) {
|
||||
throw IllegalFlowLogicException(type, "due to missing constructor for arguments: $argTypes")
|
||||
}
|
||||
|
||||
// Build map of args from array
|
||||
val argsMap = args.zip(constructor.parameters).map { Pair(it.second.name!!, it.first) }.toMap()
|
||||
return createKotlin(type, argsMap)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [FlowLogicRef] by trying to find a Kotlin constructor that matches the given args.
|
||||
*
|
||||
* TODO: Rethink language specific naming.
|
||||
*/
|
||||
fun createKotlin(type: Class<out FlowLogic<*>>, args: Map<String, Any?>): FlowLogicRef {
|
||||
// TODO: we need to capture something about the class loader or "application context" into the ref,
|
||||
// perhaps as some sort of ThreadLocal style object. For now, just create an empty one.
|
||||
val appContext = AppContext(emptyList())
|
||||
validateFlowClassName(type.name, appContext)
|
||||
// Check we can find a constructor and populate the args to it, but don't call it
|
||||
createConstructor(appContext, type, args)
|
||||
return FlowLogicRef(type.name, appContext, args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [FlowLogicRef] by trying to find a Java constructor that matches the given args.
|
||||
*/
|
||||
private fun createJava(type: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef {
|
||||
// Build map for each
|
||||
val argsMap = HashMap<String, Any?>(args.size)
|
||||
var index = 0
|
||||
args.forEach { argsMap["arg${index++}"] = it }
|
||||
return createKotlin(type, argsMap)
|
||||
}
|
||||
|
||||
fun toFlowLogic(ref: FlowLogicRef): FlowLogic<*> {
|
||||
validateFlowClassName(ref.flowLogicClassName, ref.appContext)
|
||||
val klass = Class.forName(ref.flowLogicClassName, true, ref.appContext.classLoader).asSubclass(FlowLogic::class.java)
|
||||
return createConstructor(ref.appContext, klass, ref.args)()
|
||||
}
|
||||
|
||||
private fun createConstructor(appContext: AppContext, clazz: Class<out FlowLogic<*>>, args: Map<String, Any?>): () -> FlowLogic<*> {
|
||||
for (constructor in clazz.kotlin.constructors) {
|
||||
val params = buildParams(appContext, clazz, constructor, args) ?: continue
|
||||
// If we get here then we matched every parameter
|
||||
return { constructor.callBy(params) }
|
||||
}
|
||||
throw IllegalFlowLogicException(clazz, "as could not find matching constructor for: $args")
|
||||
}
|
||||
|
||||
private fun buildParams(appContext: AppContext, clazz: Class<out FlowLogic<*>>, constructor: KFunction<FlowLogic<*>>, args: Map<String, Any?>): HashMap<KParameter, Any?>? {
|
||||
val params = hashMapOf<KParameter, Any?>()
|
||||
val usedKeys = hashSetOf<String>()
|
||||
for (parameter in constructor.parameters) {
|
||||
if (!tryBuildParam(args, parameter, params)) {
|
||||
return null
|
||||
} else {
|
||||
usedKeys += parameter.name!!
|
||||
}
|
||||
}
|
||||
if ((args.keys - usedKeys).isNotEmpty()) {
|
||||
// Not all args were used
|
||||
return null
|
||||
}
|
||||
params.values.forEach { if (it is Any) validateArgClassName(clazz.name, it.javaClass.name, appContext) }
|
||||
return params
|
||||
}
|
||||
|
||||
private fun tryBuildParam(args: Map<String, Any?>, parameter: KParameter, params: HashMap<KParameter, Any?>): Boolean {
|
||||
val containsKey = parameter.name in args
|
||||
// OK to be missing if optional
|
||||
return (parameter.isOptional && !containsKey) || (containsKey && paramCanBeBuilt(args, parameter, params))
|
||||
}
|
||||
|
||||
private fun paramCanBeBuilt(args: Map<String, Any?>, parameter: KParameter, params: HashMap<KParameter, Any?>): Boolean {
|
||||
val value = args[parameter.name]
|
||||
params[parameter] = value
|
||||
return (value is Any && parameterAssignableFrom(parameter.type.javaType, value)) || parameter.type.isMarkedNullable
|
||||
}
|
||||
|
||||
private fun parameterAssignableFrom(type: Type, value: Any): Boolean {
|
||||
if (type is Class<*>) {
|
||||
if (type.isPrimitive) {
|
||||
return Primitives.unwrap(value.javaClass) == type
|
||||
} else {
|
||||
return type.isAssignableFrom(value.javaClass)
|
||||
}
|
||||
} else if (type is ParameterizedType) {
|
||||
return parameterAssignableFrom(type.rawType, value)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
interface FlowLogicRefFactory {
|
||||
fun create(type: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
class IllegalFlowLogicException(type: Class<*>, msg: String) : IllegalArgumentException("${FlowLogicRef::class.java.simpleName} cannot be constructed for ${FlowLogic::class.java.simpleName} of type ${type.name} $msg")
|
||||
|
||||
/**
|
||||
* A class representing a [FlowLogic] instance which would be possible to safely pass out of the contract sandbox.
|
||||
* A handle interface representing a [FlowLogic] instance which would be possible to safely pass out of the contract sandbox.
|
||||
* Use FlowLogicRefFactory to construct a concrete security checked instance.
|
||||
*
|
||||
* Only allows a String reference to the FlowLogic class, and only allows restricted argument types as per [FlowLogicRefFactory].
|
||||
*/
|
||||
// TODO: align this with the existing [FlowRef] in the bank-side API (probably replace some of the API classes)
|
||||
@CordaSerializable
|
||||
data class FlowLogicRef internal constructor(val flowLogicClassName: String, val appContext: AppContext, val args: Map<String, Any?>)
|
||||
interface FlowLogicRef
|
||||
|
||||
|
||||
/**
|
||||
* This is just some way to track what attachments need to be in the class loader, but may later include some app
|
||||
|
@ -12,7 +12,6 @@ import net.corda.core.crypto.Party
|
||||
import net.corda.core.crypto.X509Utilities
|
||||
import net.corda.core.flows.FlowInitiator
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.flows.FlowVersion
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.messaging.RPCOps
|
||||
@ -46,6 +45,7 @@ import net.corda.node.services.network.PersistentNetworkMapService
|
||||
import net.corda.node.services.persistence.*
|
||||
import net.corda.node.services.schema.HibernateObserver
|
||||
import net.corda.node.services.schema.NodeSchemaService
|
||||
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
|
||||
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
||||
import net.corda.node.services.statemachine.StateMachineManager
|
||||
import net.corda.node.services.statemachine.flowVersion
|
||||
@ -134,7 +134,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
|
||||
// Internal only
|
||||
override val monitoringService: MonitoringService = MonitoringService(MetricRegistry())
|
||||
override val flowLogicRefFactory: FlowLogicRefFactory get() = flowLogicFactory
|
||||
override val flowLogicRefFactory: FlowLogicRefFactoryInternal get() = flowLogicFactory
|
||||
|
||||
override fun <T> startFlow(logic: FlowLogic<T>, flowInitiator: FlowInitiator): FlowStateMachineImpl<T> {
|
||||
return serverThread.fetchFrom { smm.add(logic, flowInitiator) }
|
||||
@ -173,7 +173,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
lateinit var net: MessagingService
|
||||
lateinit var netMapCache: NetworkMapCacheInternal
|
||||
lateinit var scheduler: NodeSchedulerService
|
||||
lateinit var flowLogicFactory: FlowLogicRefFactory
|
||||
lateinit var flowLogicFactory: FlowLogicRefFactoryInternal
|
||||
lateinit var schemas: SchemaService
|
||||
val customServices: ArrayList<Any> = ArrayList()
|
||||
protected val runOnStop: ArrayList<Runnable> = ArrayList()
|
||||
@ -381,7 +381,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
}
|
||||
}
|
||||
|
||||
private fun initialiseFlowLogicFactory(): FlowLogicRefFactory {
|
||||
private fun initialiseFlowLogicFactory(): FlowLogicRefFactoryInternal {
|
||||
val flowWhitelist = HashMap<String, Set<String>>()
|
||||
|
||||
for ((flowClass, extraArgumentTypes) in defaultFlowWhiteList) {
|
||||
@ -398,7 +398,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
}
|
||||
}
|
||||
|
||||
return FlowLogicRefFactory(flowWhitelist)
|
||||
return FlowLogicRefFactoryImpl(flowWhitelist)
|
||||
}
|
||||
|
||||
private fun makePluginServices(tokenizableServices: MutableList<Any>): List<Any> {
|
||||
|
@ -2,10 +2,7 @@ package net.corda.node.services.api
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import net.corda.core.flows.FlowInitiator
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.flows.FlowStateMachine
|
||||
import net.corda.core.flows.*
|
||||
import net.corda.core.messaging.SingleMessageRecipient
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.PluginServiceHub
|
||||
@ -50,6 +47,11 @@ interface NetworkMapCacheInternal : NetworkMapCache {
|
||||
|
||||
}
|
||||
|
||||
interface FlowLogicRefFactoryInternal : FlowLogicRefFactory {
|
||||
val flowWhitelist: Map<String, Set<String>>
|
||||
fun toFlowLogic(ref: FlowLogicRef): FlowLogic<*>
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
sealed class NetworkCacheError : Exception() {
|
||||
/** Indicates a failure to deregister, because of a rejected request from the remote node */
|
||||
@ -62,7 +64,7 @@ abstract class ServiceHubInternal : PluginServiceHub {
|
||||
}
|
||||
|
||||
abstract val monitoringService: MonitoringService
|
||||
abstract val flowLogicRefFactory: FlowLogicRefFactory
|
||||
abstract val flowLogicRefFactory: FlowLogicRefFactoryInternal
|
||||
abstract val schemaService: SchemaService
|
||||
abstract override val networkMapCache: NetworkMapCacheInternal
|
||||
abstract val schedulerService: SchedulerService
|
||||
|
@ -9,11 +9,11 @@ import net.corda.core.contracts.ScheduledStateRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.flows.FlowInitiator
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.node.services.api.FlowLogicRefFactoryInternal
|
||||
import net.corda.node.services.api.SchedulerService
|
||||
import net.corda.node.services.api.ServiceHubInternal
|
||||
import net.corda.node.utilities.*
|
||||
@ -44,7 +44,7 @@ import javax.annotation.concurrent.ThreadSafe
|
||||
*/
|
||||
@ThreadSafe
|
||||
class NodeSchedulerService(private val services: ServiceHubInternal,
|
||||
private val flowLogicRefFactory: FlowLogicRefFactory,
|
||||
private val flowLogicRefFactory: FlowLogicRefFactoryInternal,
|
||||
private val schedulerTimerExecutor: Executor = Executors.newSingleThreadExecutor(),
|
||||
private val unfinishedSchedules: ReusableLatch = ReusableLatch())
|
||||
: SchedulerService, SingletonSerializeAsToken() {
|
||||
|
@ -0,0 +1,191 @@
|
||||
package net.corda.node.services.statemachine
|
||||
|
||||
import com.google.common.primitives.Primitives
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.AppContext
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowLogicRef
|
||||
import net.corda.core.flows.IllegalFlowLogicException
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.node.services.api.FlowLogicRefFactoryInternal
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.lang.reflect.Type
|
||||
import java.util.*
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KParameter
|
||||
import kotlin.reflect.jvm.javaConstructor
|
||||
import kotlin.reflect.jvm.javaType
|
||||
|
||||
/**
|
||||
* The internal concrete implementation of the FlowLogicRef marker interface.
|
||||
*/
|
||||
@CordaSerializable
|
||||
data class FlowLogicRefImpl internal constructor(val flowLogicClassName: String, val appContext: AppContext, val args: Map<String, Any?>) : FlowLogicRef
|
||||
|
||||
/**
|
||||
* A class for conversion to and from [FlowLogic] and [FlowLogicRef] instances.
|
||||
*
|
||||
* Validation of types is performed on the way in and way out in case this object is passed between JVMs which might have differing
|
||||
* whitelists.
|
||||
*
|
||||
* TODO: Ways to populate whitelist of "blessed" flows per node/party
|
||||
* TODO: Ways to populate argument types whitelist. Per node/party or global?
|
||||
* TODO: Align with API related logic for passing in FlowLogic references (FlowRef)
|
||||
* TODO: Actual support for AppContext / AttachmentsClassLoader
|
||||
*/
|
||||
class FlowLogicRefFactoryImpl(override val flowWhitelist: Map<String, Set<String>>) : SingletonSerializeAsToken(), FlowLogicRefFactoryInternal {
|
||||
constructor() : this(mapOf())
|
||||
|
||||
// Pending real dependence on AppContext for class loading etc
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun validateFlowClassName(className: String, appContext: AppContext) {
|
||||
// TODO: make this specific to the attachments in the [AppContext] by including [SecureHash] in whitelist check
|
||||
require(flowWhitelist.containsKey(className)) { "${FlowLogic::class.java.simpleName} of ${FlowLogicRef::class.java.simpleName} must have type on the whitelist: $className" }
|
||||
}
|
||||
|
||||
// Pending real dependence on AppContext for class loading etc
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun validateArgClassName(className: String, argClassName: String, appContext: AppContext) {
|
||||
// TODO: consider more carefully what to whitelist and how to secure flows
|
||||
// For now automatically accept standard java.lang.* and kotlin.* types.
|
||||
// All other types require manual specification at FlowLogicRefFactory construction time.
|
||||
if (argClassName.startsWith("java.lang.") || argClassName.startsWith("kotlin.")) {
|
||||
return
|
||||
}
|
||||
// TODO: make this specific to the attachments in the [AppContext] by including [SecureHash] in whitelist check
|
||||
require(flowWhitelist[className]!!.contains(argClassName)) { "Args to $className must have types on the args whitelist: $argClassName, but it has ${flowWhitelist[className]}" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [FlowLogicRef] for the Kotlin primary constructor of a named [FlowLogic]
|
||||
*/
|
||||
fun createKotlin(flowLogicClassName: String, args: Map<String, Any?>, attachments: List<SecureHash> = emptyList()): FlowLogicRef {
|
||||
val context = AppContext(attachments)
|
||||
validateFlowClassName(flowLogicClassName, context)
|
||||
for (arg in args.values.filterNotNull()) {
|
||||
validateArgClassName(flowLogicClassName, arg.javaClass.name, context)
|
||||
}
|
||||
val clazz = Class.forName(flowLogicClassName)
|
||||
require(FlowLogic::class.java.isAssignableFrom(clazz)) { "$flowLogicClassName is not a FlowLogic" }
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val logic = clazz as Class<FlowLogic<FlowLogic<*>>>
|
||||
return createKotlin(logic, args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [FlowLogicRef] by assuming a single constructor and the given args.
|
||||
*/
|
||||
override fun create(type: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef {
|
||||
// TODO: This is used via RPC but it's probably better if we pass in argument names and values explicitly
|
||||
// to avoid requiring only a single constructor.
|
||||
val argTypes = args.map { it?.javaClass }
|
||||
val constructor = try {
|
||||
type.kotlin.constructors.single { ctor ->
|
||||
// Get the types of the arguments, always boxed (as that's what we get in the invocation).
|
||||
val ctorTypes = ctor.javaConstructor!!.parameterTypes.map { Primitives.wrap(it) }
|
||||
if (argTypes.size != ctorTypes.size)
|
||||
return@single false
|
||||
for ((argType, ctorType) in argTypes.zip(ctorTypes)) {
|
||||
if (argType == null) continue // Try and find a match based on the other arguments.
|
||||
if (!ctorType.isAssignableFrom(argType)) return@single false
|
||||
}
|
||||
true
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
throw IllegalFlowLogicException(type, "due to ambiguous match against the constructors: $argTypes")
|
||||
} catch (e: NoSuchElementException) {
|
||||
throw IllegalFlowLogicException(type, "due to missing constructor for arguments: $argTypes")
|
||||
}
|
||||
|
||||
// Build map of args from array
|
||||
val argsMap = args.zip(constructor.parameters).map { Pair(it.second.name!!, it.first) }.toMap()
|
||||
return createKotlin(type, argsMap)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [FlowLogicRef] by trying to find a Kotlin constructor that matches the given args.
|
||||
*
|
||||
* TODO: Rethink language specific naming.
|
||||
*/
|
||||
fun createKotlin(type: Class<out FlowLogic<*>>, args: Map<String, Any?>): FlowLogicRef {
|
||||
// TODO: we need to capture something about the class loader or "application context" into the ref,
|
||||
// perhaps as some sort of ThreadLocal style object. For now, just create an empty one.
|
||||
val appContext = AppContext(emptyList())
|
||||
validateFlowClassName(type.name, appContext)
|
||||
// Check we can find a constructor and populate the args to it, but don't call it
|
||||
createConstructor(appContext, type, args)
|
||||
return FlowLogicRefImpl(type.name, appContext, args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [FlowLogicRef] by trying to find a Java constructor that matches the given args.
|
||||
*/
|
||||
private fun createJava(type: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef {
|
||||
// Build map for each
|
||||
val argsMap = HashMap<String, Any?>(args.size)
|
||||
var index = 0
|
||||
args.forEach { argsMap["arg${index++}"] = it }
|
||||
return createKotlin(type, argsMap)
|
||||
}
|
||||
|
||||
override fun toFlowLogic(ref: FlowLogicRef): FlowLogic<*> {
|
||||
if (ref !is FlowLogicRefImpl) throw IllegalFlowLogicException(ref.javaClass, "FlowLogicRef was not created via correct FlowLogicRefFactory interface")
|
||||
validateFlowClassName(ref.flowLogicClassName, ref.appContext)
|
||||
val klass = Class.forName(ref.flowLogicClassName, true, ref.appContext.classLoader).asSubclass(FlowLogic::class.java)
|
||||
return createConstructor(ref.appContext, klass, ref.args)()
|
||||
}
|
||||
|
||||
private fun createConstructor(appContext: AppContext, clazz: Class<out FlowLogic<*>>, args: Map<String, Any?>): () -> FlowLogic<*> {
|
||||
for (constructor in clazz.kotlin.constructors) {
|
||||
val params = buildParams(appContext, clazz, constructor, args) ?: continue
|
||||
// If we get here then we matched every parameter
|
||||
return { constructor.callBy(params) }
|
||||
}
|
||||
throw IllegalFlowLogicException(clazz, "as could not find matching constructor for: $args")
|
||||
}
|
||||
|
||||
private fun buildParams(appContext: AppContext, clazz: Class<out FlowLogic<*>>, constructor: KFunction<FlowLogic<*>>, args: Map<String, Any?>): HashMap<KParameter, Any?>? {
|
||||
val params = hashMapOf<KParameter, Any?>()
|
||||
val usedKeys = hashSetOf<String>()
|
||||
for (parameter in constructor.parameters) {
|
||||
if (!tryBuildParam(args, parameter, params)) {
|
||||
return null
|
||||
} else {
|
||||
usedKeys += parameter.name!!
|
||||
}
|
||||
}
|
||||
if ((args.keys - usedKeys).isNotEmpty()) {
|
||||
// Not all args were used
|
||||
return null
|
||||
}
|
||||
params.values.forEach { if (it is Any) validateArgClassName(clazz.name, it.javaClass.name, appContext) }
|
||||
return params
|
||||
}
|
||||
|
||||
private fun tryBuildParam(args: Map<String, Any?>, parameter: KParameter, params: HashMap<KParameter, Any?>): Boolean {
|
||||
val containsKey = parameter.name in args
|
||||
// OK to be missing if optional
|
||||
return (parameter.isOptional && !containsKey) || (containsKey && paramCanBeBuilt(args, parameter, params))
|
||||
}
|
||||
|
||||
private fun paramCanBeBuilt(args: Map<String, Any?>, parameter: KParameter, params: HashMap<KParameter, Any?>): Boolean {
|
||||
val value = args[parameter.name]
|
||||
params[parameter] = value
|
||||
return (value is Any && parameterAssignableFrom(parameter.type.javaType, value)) || parameter.type.isMarkedNullable
|
||||
}
|
||||
|
||||
private fun parameterAssignableFrom(type: Type, value: Any): Boolean {
|
||||
if (type is Class<*>) {
|
||||
if (type.isPrimitive) {
|
||||
return Primitives.unwrap(value.javaClass) == type
|
||||
} else {
|
||||
return type.isAssignableFrom(value.javaClass)
|
||||
}
|
||||
} else if (type is ParameterizedType) {
|
||||
return parameterAssignableFrom(type.rawType, value)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,14 @@
|
||||
package net.corda.core.flows;
|
||||
package net.corda.node.services.events;
|
||||
|
||||
import org.junit.*;
|
||||
import net.corda.core.flows.FlowLogic;
|
||||
import net.corda.core.flows.FlowLogicRefFactory;
|
||||
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class FlowLogicRefFromJavaTest {
|
||||
|
||||
@ -55,7 +61,7 @@ public class FlowLogicRefFromJavaTest {
|
||||
argsList.add(ParamType1.class.getName());
|
||||
argsList.add(ParamType2.class.getName());
|
||||
whiteList.put(JavaFlowLogic.class.getName(), argsList);
|
||||
FlowLogicRefFactory factory = new FlowLogicRefFactory(whiteList);
|
||||
FlowLogicRefFactory factory = new FlowLogicRefFactoryImpl(whiteList);
|
||||
factory.create(JavaFlowLogic.class, new ParamType1(1), new ParamType2("Hello Jack"));
|
||||
}
|
||||
|
||||
@ -63,7 +69,7 @@ public class FlowLogicRefFromJavaTest {
|
||||
public void testNoArg() {
|
||||
Map<String, Set<String>> whiteList = new HashMap<>();
|
||||
whiteList.put(JavaNoArgFlowLogic.class.getName(), new HashSet<>());
|
||||
FlowLogicRefFactory factory = new FlowLogicRefFactory(whiteList);
|
||||
FlowLogicRefFactory factory = new FlowLogicRefFactoryImpl(whiteList);
|
||||
factory.create(JavaNoArgFlowLogic.class);
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@ import com.codahale.metrics.MetricRegistry
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.flows.FlowInitiator
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowLogicRefFactory
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.*
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
@ -13,6 +12,7 @@ import net.corda.node.serialization.NodeClock
|
||||
import net.corda.node.services.api.*
|
||||
import net.corda.node.services.messaging.MessagingService
|
||||
import net.corda.node.services.schema.NodeSchemaService
|
||||
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
|
||||
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
||||
import net.corda.node.services.statemachine.StateMachineManager
|
||||
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
|
||||
@ -30,7 +30,7 @@ open class MockServiceHubInternal(
|
||||
val mapCache: NetworkMapCacheInternal? = MockNetworkMapCache(),
|
||||
val scheduler: SchedulerService? = null,
|
||||
val overrideClock: Clock? = NodeClock(),
|
||||
val flowFactory: FlowLogicRefFactory? = FlowLogicRefFactory(),
|
||||
val flowFactory: FlowLogicRefFactoryInternal? = FlowLogicRefFactoryImpl(),
|
||||
val schemas: SchemaService? = NodeSchemaService(),
|
||||
val customTransactionVerifierService: TransactionVerifierService? = InMemoryTransactionVerifierService(2)
|
||||
) : ServiceHubInternal() {
|
||||
@ -56,7 +56,7 @@ open class MockServiceHubInternal(
|
||||
get() = throw UnsupportedOperationException()
|
||||
|
||||
override val monitoringService: MonitoringService = MonitoringService(MetricRegistry())
|
||||
override val flowLogicRefFactory: FlowLogicRefFactory
|
||||
override val flowLogicRefFactory: FlowLogicRefFactoryInternal
|
||||
get() = flowFactory ?: throw UnsupportedOperationException()
|
||||
override val schemaService: SchemaService
|
||||
get() = schemas ?: throw UnsupportedOperationException()
|
||||
|
@ -1,6 +1,8 @@
|
||||
package net.corda.core.flows
|
||||
package net.corda.node.services.events
|
||||
|
||||
import net.corda.core.days
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.time.Duration
|
||||
@ -34,12 +36,12 @@ class FlowLogicRefTest {
|
||||
override fun call() = Unit
|
||||
}
|
||||
|
||||
lateinit var factory: FlowLogicRefFactory
|
||||
lateinit var factory: FlowLogicRefFactoryImpl
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
// We have to allow Java boxed primitives but Kotlin warns we shouldn't be using them
|
||||
factory = FlowLogicRefFactory(mapOf(Pair(KotlinFlowLogic::class.java.name, setOf(ParamType1::class.java.name, ParamType2::class.java.name)),
|
||||
factory = FlowLogicRefFactoryImpl(mapOf(Pair(KotlinFlowLogic::class.java.name, setOf(ParamType1::class.java.name, ParamType2::class.java.name)),
|
||||
Pair(KotlinNoArgFlowLogic::class.java.name, setOf())))
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import net.corda.core.utilities.ALICE_KEY
|
||||
import net.corda.core.utilities.DUMMY_NOTARY
|
||||
import net.corda.node.services.MockServiceHubInternal
|
||||
import net.corda.node.services.persistence.DBCheckpointStorage
|
||||
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
|
||||
import net.corda.node.services.statemachine.StateMachineManager
|
||||
import net.corda.node.services.vault.NodeVaultService
|
||||
import net.corda.node.utilities.AffinityExecutor
|
||||
@ -45,7 +46,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
|
||||
|
||||
// We have to allow Java boxed primitives but Kotlin warns we shouldn't be using them
|
||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
||||
val factory = FlowLogicRefFactory(mapOf(Pair(TestFlowLogic::class.java.name, setOf(NodeSchedulerServiceTest::class.java.name, Integer::class.java.name))))
|
||||
val factory = FlowLogicRefFactoryImpl(mapOf(Pair(TestFlowLogic::class.java.name, setOf(NodeSchedulerServiceTest::class.java.name, Integer::class.java.name))))
|
||||
|
||||
lateinit var services: MockServiceHubInternal
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user