Introducing StartableByRPC and SchedulableFlow annotations, needed by flows started via RPC and schedulable flows respectively.

CordaPluginRegistry.requiredFlows is no longer needed as a result.
This commit is contained in:
Shams Asari
2017-05-10 11:28:25 +01:00
parent b155764023
commit 48f58b6dbc
52 changed files with 401 additions and 434 deletions

View File

@ -9,11 +9,12 @@ import net.corda.core.serialization.CordaSerializable
* the flow to run at the scheduled time.
*/
interface FlowLogicRefFactory {
fun create(type: Class<out FlowLogic<*>>, vararg args: Any?): FlowLogicRef
fun create(flowClass: 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")
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 handle interface representing a [FlowLogic] instance which would be possible to safely pass out of the contract sandbox.

View File

@ -0,0 +1,14 @@
package net.corda.core.flows
import java.lang.annotation.Inherited
import kotlin.annotation.AnnotationTarget.CLASS
/**
* Any [FlowLogic] which is schedulable and is designed to be invoked by a [net.corda.core.contracts.SchedulableState]
* must have this annotation. If it's missing [FlowLogicRefFactory.create] will throw an exception when it comes time
* to schedule the next activity in [net.corda.core.contracts.SchedulableState.nextScheduledActivity].
*/
@Target(CLASS)
@Inherited
@MustBeDocumented
annotation class SchedulableFlow

View File

@ -0,0 +1,15 @@
package net.corda.core.flows
import java.lang.annotation.Inherited
import kotlin.annotation.AnnotationTarget.CLASS
/**
* Any [FlowLogic] which is to be started by the RPC interface ([net.corda.core.messaging.CordaRPCOps.startFlowDynamic]
* and [net.corda.core.messaging.CordaRPCOps.startTrackedFlowDynamic]) must have this annotation. If it's missing the
* flow will not be allowed to start and an exception will be thrown.
*/
@Target(CLASS)
@Inherited
@MustBeDocumented
// TODO Consider a different name, something along the lines of SchedulableFlow
annotation class StartableByRPC

View File

@ -148,14 +148,14 @@ interface CordaRPCOps : RPCOps {
fun networkMapUpdates(): Pair<List<NodeInfo>, Observable<NetworkMapCache.MapChange>>
/**
* Start the given flow with the given arguments.
* Start the given flow with the given arguments. [logicType] must be annotated with [net.corda.core.flows.StartableByRPC].
*/
@RPCReturnsObservables
fun <T : Any> startFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowHandle<T>
/**
* Start the given flow with the given arguments, returning an [Observable] with a single observation of the
* result of running the flow.
* result of running the flow. [logicType] must be annotated with [net.corda.core.flows.StartableByRPC].
*/
@RPCReturnsObservables
fun <T : Any> startTrackedFlowDynamic(logicType: Class<out FlowLogic<T>>, vararg args: Any?): FlowProgressHandle<T>

View File

@ -8,42 +8,38 @@ import java.util.function.Function
* Implement this interface on a class advertised in a META-INF/services/net.corda.core.node.CordaPluginRegistry file
* to extend a Corda node with additional application services.
*/
abstract class CordaPluginRegistry(
/**
* List of lambdas returning JAX-RS objects. They may only depend on the RPC interface, as the webserver should
* potentially be able to live in a process separate from the node itself.
*/
open val webApis: List<Function<CordaRPCOps, out Any>> = emptyList(),
abstract class CordaPluginRegistry {
/**
* List of lambdas returning JAX-RS objects. They may only depend on the RPC interface, as the webserver should
* potentially be able to live in a process separate from the node itself.
*/
open val webApis: List<Function<CordaRPCOps, out Any>> get() = emptyList()
/**
* Map of static serving endpoints to the matching resource directory. All endpoints will be prefixed with "/web" and postfixed with "\*.
* Resource directories can be either on disk directories (especially when debugging) in the form "a/b/c". Serving from a JAR can
* be specified with: javaClass.getResource("<folder-in-jar>").toExternalForm()
*/
open val staticServeDirs: Map<String, String> = emptyMap(),
/**
* Map of static serving endpoints to the matching resource directory. All endpoints will be prefixed with "/web" and postfixed with "\*.
* Resource directories can be either on disk directories (especially when debugging) in the form "a/b/c". Serving from a JAR can
* be specified with: javaClass.getResource("<folder-in-jar>").toExternalForm()
*/
open val staticServeDirs: Map<String, String> get() = emptyMap()
/**
* A Map with an entry for each consumed Flow used by the webAPIs.
* The key of each map entry should contain the FlowLogic<T> class name.
* The associated map values are the union of all concrete class names passed to the Flow constructor.
* Standard java.lang.* and kotlin.* types do not need to be included explicitly.
* This is used to extend the white listed Flows that can be initiated from the ServiceHub invokeFlowAsync method.
*/
open val requiredFlows: Map<String, Set<String>> = emptyMap(),
@Suppress("unused")
@Deprecated("This is no longer needed. Instead annotate any flows that need to be invoked via RPC with " +
"@StartableByRPC and any scheduled flows with @SchedulableFlow", level = DeprecationLevel.ERROR)
open val requiredFlows: Map<String, Set<String>> get() = emptyMap()
/**
* List of lambdas constructing additional long lived services to be hosted within the node.
* They expect a single [PluginServiceHub] parameter as input.
* The [PluginServiceHub] will be fully constructed before the plugin service is created and will
* allow access to the Flow factory and Flow initiation entry points there.
*/
open val servicePlugins: List<Function<PluginServiceHub, out Any>> get() = emptyList()
/**
* List of lambdas constructing additional long lived services to be hosted within the node.
* They expect a single [PluginServiceHub] parameter as input.
* The [PluginServiceHub] will be fully constructed before the plugin service is created and will
* allow access to the Flow factory and Flow initiation entry points there.
*/
open val servicePlugins: List<Function<PluginServiceHub, out Any>> = emptyList()
) {
/**
* Optionally whitelist types for use in object serialization, as we lock down the types that can be serialized.
*
* For example, if you add a new [ContractState] it needs to be whitelisted. You can do that either by
* adding the @CordaSerializable annotation or via this method.
* For example, if you add a new [net.corda.core.contracts.ContractState] it needs to be whitelisted. You can do that
* either by adding the [net.corda.core.serialization.CordaSerializable] annotation or via this method.
**
* @return true if you register types, otherwise you will be filtered out of the list of plugins considered in future.
*/

View File

@ -2,6 +2,7 @@ package net.corda.flows
import net.corda.core.contracts.*
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import java.security.PublicKey
@ -15,6 +16,7 @@ import java.security.PublicKey
* use the new updated state for future transactions.
*/
@InitiatingFlow
@StartableByRPC
class ContractUpgradeFlow<OldState : ContractState, out NewState : ContractState>(
originalState: StateAndRef<OldState>,
newContractClass: Class<out UpgradedContract<OldState, NewState>>

View File

@ -1,5 +1,6 @@
package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.asset.Cash
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
@ -9,6 +10,7 @@ import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.startFlow
import net.corda.core.node.services.unconsumedStates
import net.corda.core.serialization.OpaqueBytes
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.Emoji
import net.corda.flows.CashIssueFlow
import net.corda.flows.ContractUpgradeFlow
@ -27,7 +29,6 @@ import org.junit.Before
import org.junit.Test
import java.security.PublicKey
import java.util.*
import java.util.concurrent.ExecutionException
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
@ -69,10 +70,10 @@ class ContractUpgradeFlowTest {
requireNotNull(atx)
requireNotNull(btx)
// The request is expected to be rejected because party B haven't authorise the upgrade yet.
// The request is expected to be rejected because party B hasn't authorised the upgrade yet.
val rejectedFuture = a.services.startFlow(ContractUpgradeFlow(atx!!.tx.outRef(0), DummyContractV2::class.java)).resultFuture
mockNet.runNetwork()
assertFailsWith(ExecutionException::class) { rejectedFuture.get() }
assertFailsWith(FlowSessionException::class) { rejectedFuture.getOrThrow() }
// Party B authorise the contract state upgrade.
b.services.vaultService.authoriseContractUpgrade(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)
@ -81,7 +82,7 @@ class ContractUpgradeFlowTest {
val resultFuture = a.services.startFlow(ContractUpgradeFlow(atx.tx.outRef(0), DummyContractV2::class.java)).resultFuture
mockNet.runNetwork()
val result = resultFuture.get()
val result = resultFuture.getOrThrow()
fun check(node: MockNetwork.MockNode) {
val nodeStx = node.database.transaction {
@ -124,12 +125,12 @@ class ContractUpgradeFlowTest {
.toSignedTransaction()
val user = rpcTestUser.copy(permissions = setOf(
startFlowPermission<FinalityFlow>(),
startFlowPermission<FinalityInvoker>(),
startFlowPermission<ContractUpgradeFlow<*, *>>()
))
val rpcA = startProxy(a, user)
val rpcB = startProxy(b, user)
val handle = rpcA.startFlow(::FinalityFlow, stx, setOf(a.info.legalIdentity, b.info.legalIdentity))
val handle = rpcA.startFlow(::FinalityInvoker, stx, setOf(a.info.legalIdentity, b.info.legalIdentity))
mockNet.runNetwork()
handle.returnValue.getOrThrow()
@ -143,7 +144,7 @@ class ContractUpgradeFlowTest {
DummyContractV2::class.java).returnValue
mockNet.runNetwork()
assertFailsWith(ExecutionException::class) { rejectedFuture.get() }
assertFailsWith(FlowSessionException::class) { rejectedFuture.getOrThrow() }
// Party B authorise the contract state upgrade.
rpcB.authoriseContractUpgrade(btx!!.tx.outRef<ContractState>(0), DummyContractV2::class.java)
@ -154,7 +155,7 @@ class ContractUpgradeFlowTest {
DummyContractV2::class.java).returnValue
mockNet.runNetwork()
val result = resultFuture.get()
val result = resultFuture.getOrThrow()
// Check results.
listOf(a, b).forEach {
val signedTX = a.database.transaction { a.services.storageService.validatedTransactions.getTransaction(result.ref.txhash) }
@ -210,4 +211,11 @@ class ContractUpgradeFlowTest {
// Dummy Cash contract for testing.
override val legalContractReference = SecureHash.sha256("")
}
@StartableByRPC
class FinalityInvoker(val transaction: SignedTransaction,
val extraRecipients: Set<Party>) : FlowLogic<List<SignedTransaction>>() {
@Suspendable
override fun call(): List<SignedTransaction> = subFlow(FinalityFlow(transaction, extraRecipients))
}
}