mirror of
https://github.com/corda/corda.git
synced 2024-12-21 05:53:23 +00:00
Merge pull request #1803 from corda/mnesbit-CordaServices-startFlows
Start Flows from services
This commit is contained in:
commit
2564b7da1d
@ -17,6 +17,7 @@ import java.net.URL
|
|||||||
* @property contractClassNames List of contracts
|
* @property contractClassNames List of contracts
|
||||||
* @property initiatedFlows List of initiatable flow classes
|
* @property initiatedFlows List of initiatable flow classes
|
||||||
* @property rpcFlows List of RPC initiable flows classes
|
* @property rpcFlows List of RPC initiable flows classes
|
||||||
|
* @property serviceFlows List of [CordaService] initiable flows classes
|
||||||
* @property schedulableFlows List of flows startable by the scheduler
|
* @property schedulableFlows List of flows startable by the scheduler
|
||||||
* @property servies List of RPC services
|
* @property servies List of RPC services
|
||||||
* @property serializationWhitelists List of Corda plugin registries
|
* @property serializationWhitelists List of Corda plugin registries
|
||||||
@ -28,6 +29,7 @@ interface Cordapp {
|
|||||||
val contractClassNames: List<String>
|
val contractClassNames: List<String>
|
||||||
val initiatedFlows: List<Class<out FlowLogic<*>>>
|
val initiatedFlows: List<Class<out FlowLogic<*>>>
|
||||||
val rpcFlows: List<Class<out FlowLogic<*>>>
|
val rpcFlows: List<Class<out FlowLogic<*>>>
|
||||||
|
val serviceFlows: List<Class<out FlowLogic<*>>>
|
||||||
val schedulableFlows: List<Class<out FlowLogic<*>>>
|
val schedulableFlows: List<Class<out FlowLogic<*>>>
|
||||||
val services: List<Class<out SerializeAsToken>>
|
val services: List<Class<out SerializeAsToken>>
|
||||||
val serializationWhitelists: List<SerializationWhitelist>
|
val serializationWhitelists: List<SerializationWhitelist>
|
||||||
|
@ -20,6 +20,10 @@ sealed class FlowInitiator : Principal {
|
|||||||
data class Peer(val party: Party) : FlowInitiator() {
|
data class Peer(val party: Party) : FlowInitiator() {
|
||||||
override fun getName(): String = party.name.toString()
|
override fun getName(): String = party.name.toString()
|
||||||
}
|
}
|
||||||
|
/** Started by a CordaService. */
|
||||||
|
data class Service(val serviceClassName: String) : FlowInitiator() {
|
||||||
|
override fun getName(): String = serviceClassName
|
||||||
|
}
|
||||||
/** Started as scheduled activity. */
|
/** Started as scheduled activity. */
|
||||||
data class Scheduled(val scheduledState: ScheduledStateRef) : FlowInitiator() {
|
data class Scheduled(val scheduledState: ScheduledStateRef) : FlowInitiator() {
|
||||||
override fun getName(): String = "Scheduler"
|
override fun getName(): String = "Scheduler"
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
package net.corda.core.flows
|
||||||
|
|
||||||
|
import kotlin.annotation.AnnotationTarget.CLASS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any [FlowLogic] which is to be started by the [AppServiceHub] interface from
|
||||||
|
* within a [CordaService] must have this annotation. If it's missing the
|
||||||
|
* flow will not be allowed to start and an exception will be thrown.
|
||||||
|
*/
|
||||||
|
@Target(CLASS)
|
||||||
|
@MustBeDocumented
|
||||||
|
annotation class StartableByService
|
@ -12,6 +12,7 @@ data class CordappImpl(
|
|||||||
override val contractClassNames: List<String>,
|
override val contractClassNames: List<String>,
|
||||||
override val initiatedFlows: List<Class<out FlowLogic<*>>>,
|
override val initiatedFlows: List<Class<out FlowLogic<*>>>,
|
||||||
override val rpcFlows: List<Class<out FlowLogic<*>>>,
|
override val rpcFlows: List<Class<out FlowLogic<*>>>,
|
||||||
|
override val serviceFlows: List<Class<out FlowLogic<*>>>,
|
||||||
override val schedulableFlows: List<Class<out FlowLogic<*>>>,
|
override val schedulableFlows: List<Class<out FlowLogic<*>>>,
|
||||||
override val services: List<Class<out SerializeAsToken>>,
|
override val services: List<Class<out SerializeAsToken>>,
|
||||||
override val serializationWhitelists: List<SerializationWhitelist>,
|
override val serializationWhitelists: List<SerializationWhitelist>,
|
||||||
|
30
core/src/main/kotlin/net/corda/core/node/AppServiceHub.kt
Normal file
30
core/src/main/kotlin/net/corda/core/node/AppServiceHub.kt
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package net.corda.core.node
|
||||||
|
|
||||||
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.messaging.FlowHandle
|
||||||
|
import net.corda.core.messaging.FlowProgressHandle
|
||||||
|
import rx.Observable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [CordaService] annotated class requires a constructor taking a
|
||||||
|
* single parameter of type [AppServiceHub].
|
||||||
|
* With the [AppServiceHub] parameter a [CordaService] is able to access to privileged operations.
|
||||||
|
* In particular such a [CordaService] can initiate and track flows marked with [net.corda.core.flows.StartableByService].
|
||||||
|
*/
|
||||||
|
interface AppServiceHub : ServiceHub {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the given flow with the given arguments. [flow] must be annotated
|
||||||
|
* with [net.corda.core.flows.StartableByService].
|
||||||
|
* TODO it is assumed here that the flow object has an appropriate classloader.
|
||||||
|
*/
|
||||||
|
fun <T> startFlow(flow: FlowLogic<T>): FlowHandle<T>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the given flow with the given arguments, returning an [Observable] with a single observation of the
|
||||||
|
* result of running the flow. [flow] must be annotated with [net.corda.core.flows.StartableByService].
|
||||||
|
* TODO it is assumed here that the flow object has an appropriate classloader.
|
||||||
|
*/
|
||||||
|
fun <T> startTrackedFlow(flow: FlowLogic<T>): FlowProgressHandle<T>
|
||||||
|
|
||||||
|
}
|
@ -7,7 +7,7 @@ import kotlin.annotation.AnnotationTarget.CLASS
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotate any class that needs to be a long-lived service within the node, such as an oracle, with this annotation.
|
* Annotate any class that needs to be a long-lived service within the node, such as an oracle, with this annotation.
|
||||||
* Such a class needs to have a constructor with a single parameter of type [ServiceHub]. This constructor will be invoked
|
* Such a class needs to have a constructor with a single parameter of type [AppServiceHub]. This constructor will be invoked
|
||||||
* during node start to initialise the service. The service hub provided can be used to get information about the node
|
* during node start to initialise the service. The service hub provided can be used to get information about the node
|
||||||
* that may be necessary for the service. Corda services are created as singletons within the node and are available
|
* that may be necessary for the service. Corda services are created as singletons within the node and are available
|
||||||
* to flows via [ServiceHub.cordaService].
|
* to flows via [ServiceHub.cordaService].
|
||||||
|
@ -20,9 +20,8 @@ import net.corda.core.internal.concurrent.flatMap
|
|||||||
import net.corda.core.internal.concurrent.openFuture
|
import net.corda.core.internal.concurrent.openFuture
|
||||||
import net.corda.core.internal.toX509CertHolder
|
import net.corda.core.internal.toX509CertHolder
|
||||||
import net.corda.core.internal.uncheckedCast
|
import net.corda.core.internal.uncheckedCast
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.*
|
||||||
import net.corda.core.messaging.RPCOps
|
import net.corda.core.node.AppServiceHub
|
||||||
import net.corda.core.messaging.SingleMessageRecipient
|
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.node.services.*
|
import net.corda.core.node.services.*
|
||||||
@ -264,6 +263,49 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This customizes the ServiceHub for each CordaService that is initiating flows
|
||||||
|
*/
|
||||||
|
private class AppServiceHubImpl<T : SerializeAsToken>(val serviceHub: ServiceHubInternal): AppServiceHub, ServiceHub by serviceHub {
|
||||||
|
lateinit var serviceInstance: T
|
||||||
|
override fun <T> startTrackedFlow(flow: FlowLogic<T>): FlowProgressHandle<T> {
|
||||||
|
val stateMachine = startFlowChecked(flow)
|
||||||
|
return FlowProgressHandleImpl(
|
||||||
|
id = stateMachine.id,
|
||||||
|
returnValue = stateMachine.resultFuture,
|
||||||
|
progress = stateMachine.logic.track()?.updates ?: Observable.empty()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T> startFlow(flow: FlowLogic<T>): FlowHandle<T> {
|
||||||
|
val stateMachine = startFlowChecked(flow)
|
||||||
|
return FlowHandleImpl(id = stateMachine.id, returnValue = stateMachine.resultFuture)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T> startFlowChecked(flow: FlowLogic<T>): FlowStateMachineImpl<T> {
|
||||||
|
val logicType = flow.javaClass
|
||||||
|
require(logicType.isAnnotationPresent(StartableByService::class.java)) { "${logicType.name} was not designed for starting by a CordaService" }
|
||||||
|
val currentUser = FlowInitiator.Service(serviceInstance.javaClass.name)
|
||||||
|
return serviceHub.startFlow(flow, currentUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is AppServiceHubImpl<*>) return false
|
||||||
|
|
||||||
|
if (serviceHub != other.serviceHub) return false
|
||||||
|
if (serviceInstance != other.serviceInstance) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = serviceHub.hashCode()
|
||||||
|
result = 31 * result + serviceInstance.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use this method to install your Corda services in your tests. This is automatically done by the node when it
|
* Use this method to install your Corda services in your tests. This is automatically done by the node when it
|
||||||
* starts up for all classes it finds which are annotated with [CordaService].
|
* starts up for all classes it finds which are annotated with [CordaService].
|
||||||
@ -276,8 +318,16 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
|||||||
val constructor = serviceClass.getDeclaredConstructor(ServiceHub::class.java, PublicKey::class.java).apply { isAccessible = true }
|
val constructor = serviceClass.getDeclaredConstructor(ServiceHub::class.java, PublicKey::class.java).apply { isAccessible = true }
|
||||||
constructor.newInstance(services, myNotaryIdentity!!.owningKey)
|
constructor.newInstance(services, myNotaryIdentity!!.owningKey)
|
||||||
} else {
|
} else {
|
||||||
val constructor = serviceClass.getDeclaredConstructor(ServiceHub::class.java).apply { isAccessible = true }
|
try {
|
||||||
constructor.newInstance(services)
|
val extendedServiceConstructor = serviceClass.getDeclaredConstructor(AppServiceHub::class.java).apply { isAccessible = true }
|
||||||
|
val serviceContext = AppServiceHubImpl<T>(services)
|
||||||
|
serviceContext.serviceInstance = extendedServiceConstructor.newInstance(serviceContext)
|
||||||
|
serviceContext.serviceInstance
|
||||||
|
} catch (ex: NoSuchMethodException) {
|
||||||
|
val constructor = serviceClass.getDeclaredConstructor(ServiceHub::class.java).apply { isAccessible = true }
|
||||||
|
log.warn("${serviceClass.name} is using legacy CordaService constructor with ServiceHub parameter. Upgrade to an AppServiceHub parameter to enable updated API features.")
|
||||||
|
constructor.newInstance(services)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e: InvocationTargetException) {
|
} catch (e: InvocationTargetException) {
|
||||||
throw ServiceInstantiationException(e.cause)
|
throw ServiceInstantiationException(e.cause)
|
||||||
|
@ -169,6 +169,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List<URL>)
|
|||||||
listOf(),
|
listOf(),
|
||||||
listOf(),
|
listOf(),
|
||||||
listOf(),
|
listOf(),
|
||||||
|
listOf(),
|
||||||
setOf(),
|
setOf(),
|
||||||
ContractUpgradeFlow.javaClass.protectionDomain.codeSource.location // Core JAR location
|
ContractUpgradeFlow.javaClass.protectionDomain.codeSource.location // Core JAR location
|
||||||
)
|
)
|
||||||
@ -180,6 +181,7 @@ class CordappLoader private constructor(private val cordappJarPaths: List<URL>)
|
|||||||
CordappImpl(findContractClassNames(scanResult),
|
CordappImpl(findContractClassNames(scanResult),
|
||||||
findInitiatedFlows(scanResult),
|
findInitiatedFlows(scanResult),
|
||||||
findRPCFlows(scanResult),
|
findRPCFlows(scanResult),
|
||||||
|
findServiceFlows(scanResult),
|
||||||
findSchedulableFlows(scanResult),
|
findSchedulableFlows(scanResult),
|
||||||
findServices(scanResult),
|
findServices(scanResult),
|
||||||
findPlugins(it),
|
findPlugins(it),
|
||||||
@ -207,14 +209,18 @@ class CordappLoader private constructor(private val cordappJarPaths: List<URL>)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findRPCFlows(scanResult: ScanResult): List<Class<out FlowLogic<*>>> {
|
private fun Class<out FlowLogic<*>>.isUserInvokable(): Boolean {
|
||||||
fun Class<out FlowLogic<*>>.isUserInvokable(): Boolean {
|
return Modifier.isPublic(modifiers) && !isLocalClass && !isAnonymousClass && (!isMemberClass || Modifier.isStatic(modifiers))
|
||||||
return Modifier.isPublic(modifiers) && !isLocalClass && !isAnonymousClass && (!isMemberClass || Modifier.isStatic(modifiers))
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
private fun findRPCFlows(scanResult: ScanResult): List<Class<out FlowLogic<*>>> {
|
||||||
return scanResult.getClassesWithAnnotation(FlowLogic::class, StartableByRPC::class).filter { it.isUserInvokable() }
|
return scanResult.getClassesWithAnnotation(FlowLogic::class, StartableByRPC::class).filter { it.isUserInvokable() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun findServiceFlows(scanResult: ScanResult): List<Class<out FlowLogic<*>>> {
|
||||||
|
return scanResult.getClassesWithAnnotation(FlowLogic::class, StartableByService::class)
|
||||||
|
}
|
||||||
|
|
||||||
private fun findSchedulableFlows(scanResult: ScanResult): List<Class<out FlowLogic<*>>> {
|
private fun findSchedulableFlows(scanResult: ScanResult): List<Class<out FlowLogic<*>>> {
|
||||||
return scanResult.getClassesWithAnnotation(FlowLogic::class, SchedulableFlow::class)
|
return scanResult.getClassesWithAnnotation(FlowLogic::class, SchedulableFlow::class)
|
||||||
}
|
}
|
||||||
|
@ -111,6 +111,7 @@ class FlowWatchPrintingSubscriber(private val toStream: RenderPrintWriter) : Sub
|
|||||||
is FlowInitiator.Shell -> "Shell" // TODO Change when we will have more information on shell user.
|
is FlowInitiator.Shell -> "Shell" // TODO Change when we will have more information on shell user.
|
||||||
is FlowInitiator.Peer -> flowInitiator.party.name.organisation
|
is FlowInitiator.Peer -> flowInitiator.party.name.organisation
|
||||||
is FlowInitiator.RPC -> "RPC: " + flowInitiator.username
|
is FlowInitiator.RPC -> "RPC: " + flowInitiator.username
|
||||||
|
is FlowInitiator.Service -> "Service: " + flowInitiator.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
132
node/src/test/kotlin/net/corda/node/internal/CordaServiceTest.kt
Normal file
132
node/src/test/kotlin/net/corda/node/internal/CordaServiceTest.kt
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
package net.corda.node.internal
|
||||||
|
|
||||||
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
|
import net.corda.core.flows.FlowInitiator
|
||||||
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.flows.StartableByService
|
||||||
|
import net.corda.core.node.AppServiceHub
|
||||||
|
import net.corda.core.node.ServiceHub
|
||||||
|
import net.corda.core.node.services.CordaService
|
||||||
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
|
import net.corda.core.utilities.ProgressTracker
|
||||||
|
import net.corda.finance.DOLLARS
|
||||||
|
import net.corda.finance.flows.CashIssueFlow
|
||||||
|
import net.corda.node.internal.cordapp.DummyRPCFlow
|
||||||
|
import net.corda.node.services.transactions.ValidatingNotaryService
|
||||||
|
import net.corda.nodeapi.internal.ServiceInfo
|
||||||
|
import net.corda.testing.DUMMY_NOTARY
|
||||||
|
import net.corda.testing.node.MockNetwork
|
||||||
|
import net.corda.testing.setCordappPackages
|
||||||
|
import net.corda.testing.unsetCordappPackages
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
@StartableByService
|
||||||
|
class DummyServiceFlow : FlowLogic<FlowInitiator>() {
|
||||||
|
companion object {
|
||||||
|
object TEST_STEP : ProgressTracker.Step("Custom progress step")
|
||||||
|
}
|
||||||
|
override val progressTracker: ProgressTracker = ProgressTracker(TEST_STEP)
|
||||||
|
|
||||||
|
@Suspendable
|
||||||
|
override fun call(): FlowInitiator {
|
||||||
|
// We call a subFlow, otehrwise there is no chance to subscribe to the ProgressTracker
|
||||||
|
subFlow(CashIssueFlow(100.DOLLARS, OpaqueBytes.of(1), serviceHub.networkMapCache.notaryIdentities.first()))
|
||||||
|
progressTracker.currentStep = TEST_STEP
|
||||||
|
return stateMachine.flowInitiator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CordaService
|
||||||
|
class TestCordaService(val appServiceHub: AppServiceHub): SingletonSerializeAsToken() {
|
||||||
|
fun startServiceFlow() {
|
||||||
|
val handle = appServiceHub.startFlow(DummyServiceFlow())
|
||||||
|
val initiator = handle.returnValue.get()
|
||||||
|
initiator as FlowInitiator.Service
|
||||||
|
assertEquals(this.javaClass.name, initiator.serviceClassName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startServiceFlowAndTrack() {
|
||||||
|
val handle = appServiceHub.startTrackedFlow(DummyServiceFlow())
|
||||||
|
val count = AtomicInteger(0)
|
||||||
|
val subscriber = handle.progress.subscribe { count.incrementAndGet() }
|
||||||
|
handle.returnValue.get()
|
||||||
|
// Simply prove some progress was made.
|
||||||
|
// The actual number is currently 11, but don't want to hard code an implementation detail.
|
||||||
|
assertTrue(count.get() > 1)
|
||||||
|
subscriber.unsubscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@CordaService
|
||||||
|
class TestCordaService2(val appServiceHub: AppServiceHub): SingletonSerializeAsToken() {
|
||||||
|
fun startInvalidRPCFlow() {
|
||||||
|
val handle = appServiceHub.startFlow(DummyRPCFlow())
|
||||||
|
handle.returnValue.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@CordaService
|
||||||
|
class LegacyCordaService(val simpleServiceHub: ServiceHub): SingletonSerializeAsToken() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class CordaServiceTest {
|
||||||
|
lateinit var mockNet: MockNetwork
|
||||||
|
lateinit var notaryNode: StartedNode<MockNetwork.MockNode>
|
||||||
|
lateinit var nodeA: StartedNode<MockNetwork.MockNode>
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun start() {
|
||||||
|
setCordappPackages("net.corda.node.internal","net.corda.finance")
|
||||||
|
mockNet = MockNetwork(threadPerNode = true)
|
||||||
|
notaryNode = mockNet.createNode(
|
||||||
|
legalName = DUMMY_NOTARY.name,
|
||||||
|
advertisedServices = *arrayOf(ServiceInfo(ValidatingNotaryService.type)))
|
||||||
|
nodeA = mockNet.createNode(notaryNode.network.myAddress)
|
||||||
|
mockNet.startNodes()
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun cleanUp() {
|
||||||
|
mockNet.stopNodes()
|
||||||
|
unsetCordappPackages()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Can find distinct services on node`() {
|
||||||
|
val service = nodeA.services.cordaService(TestCordaService::class.java)
|
||||||
|
val service2 = nodeA.services.cordaService(TestCordaService2::class.java)
|
||||||
|
val legacyService = nodeA.services.cordaService(LegacyCordaService::class.java)
|
||||||
|
assertEquals(TestCordaService::class.java, service.javaClass)
|
||||||
|
assertEquals(TestCordaService2::class.java, service2.javaClass)
|
||||||
|
assertNotEquals(service.appServiceHub, service2.appServiceHub) // Each gets a customised AppServiceHub
|
||||||
|
assertEquals(LegacyCordaService::class.java, legacyService.javaClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Can start StartableByService flows`() {
|
||||||
|
val service = nodeA.services.cordaService(TestCordaService::class.java)
|
||||||
|
service.startServiceFlow()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Can't start StartableByRPC flows`() {
|
||||||
|
val service = nodeA.services.cordaService(TestCordaService2::class.java)
|
||||||
|
assertFailsWith<IllegalArgumentException> { service.startInvalidRPCFlow() }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Test flow with progress tracking`() {
|
||||||
|
val service = nodeA.services.cordaService(TestCordaService::class.java)
|
||||||
|
service.startServiceFlowAndTrack()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package net.corda.node.internal.cordapp
|
package net.corda.node.internal.cordapp
|
||||||
|
|
||||||
|
import co.paralleluniverse.fibers.Suspendable
|
||||||
import net.corda.core.flows.*
|
import net.corda.core.flows.*
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@ -7,21 +8,25 @@ import java.nio.file.Paths
|
|||||||
|
|
||||||
@InitiatingFlow
|
@InitiatingFlow
|
||||||
class DummyFlow : FlowLogic<Unit>() {
|
class DummyFlow : FlowLogic<Unit>() {
|
||||||
|
@Suspendable
|
||||||
override fun call() { }
|
override fun call() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
@InitiatedBy(DummyFlow::class)
|
@InitiatedBy(DummyFlow::class)
|
||||||
class LoaderTestFlow(unusedSession: FlowSession) : FlowLogic<Unit>() {
|
class LoaderTestFlow(unusedSession: FlowSession) : FlowLogic<Unit>() {
|
||||||
|
@Suspendable
|
||||||
override fun call() { }
|
override fun call() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
@SchedulableFlow
|
@SchedulableFlow
|
||||||
class DummySchedulableFlow : FlowLogic<Unit>() {
|
class DummySchedulableFlow : FlowLogic<Unit>() {
|
||||||
|
@Suspendable
|
||||||
override fun call() { }
|
override fun call() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
@StartableByRPC
|
@StartableByRPC
|
||||||
class DummyRPCFlow : FlowLogic<Unit>() {
|
class DummyRPCFlow : FlowLogic<Unit>() {
|
||||||
|
@Suspendable
|
||||||
override fun call() { }
|
override fun call() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ class MockCordappProvider(cordappLoader: CordappLoader) : CordappProviderImpl(co
|
|||||||
val cordappRegistry = mutableListOf<Pair<Cordapp, AttachmentId>>()
|
val cordappRegistry = mutableListOf<Pair<Cordapp, AttachmentId>>()
|
||||||
|
|
||||||
fun addMockCordapp(contractClassName: ContractClassName, services: ServiceHub) {
|
fun addMockCordapp(contractClassName: ContractClassName, services: ServiceHub) {
|
||||||
val cordapp = CordappImpl(listOf(contractClassName), emptyList(), emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), Paths.get(".").toUri().toURL())
|
val cordapp = CordappImpl(listOf(contractClassName), emptyList(), emptyList(), emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), Paths.get(".").toUri().toURL())
|
||||||
if (cordappRegistry.none { it.first.contractClassNames.contains(contractClassName) }) {
|
if (cordappRegistry.none { it.first.contractClassNames.contains(contractClassName) }) {
|
||||||
cordappRegistry.add(Pair(cordapp, findOrImportAttachment(contractClassName.toByteArray(), services)))
|
cordappRegistry.add(Pair(cordapp, findOrImportAttachment(contractClassName.toByteArray(), services)))
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user