mirror of
https://github.com/corda/corda.git
synced 2025-02-09 12:21:22 +00:00
CORDA-556: Added Cordapp Config and a sample (#2469)
* Added per-cordapp configuration * Added new API for Cordformation cordapp declarations to support per-cordapp configuration * Added a cordapp configuration sample
This commit is contained in:
parent
3802066bf6
commit
174ed3c64b
@ -635,7 +635,7 @@ public static final class net.corda.core.contracts.UniqueIdentifier$Companion ex
|
|||||||
@org.jetbrains.annotations.NotNull public abstract List getServices()
|
@org.jetbrains.annotations.NotNull public abstract List getServices()
|
||||||
##
|
##
|
||||||
public final class net.corda.core.cordapp.CordappContext extends java.lang.Object
|
public final class net.corda.core.cordapp.CordappContext extends java.lang.Object
|
||||||
public <init>(net.corda.core.cordapp.Cordapp, net.corda.core.crypto.SecureHash, ClassLoader)
|
public <init>(net.corda.core.cordapp.Cordapp, net.corda.core.crypto.SecureHash, ClassLoader, net.corda.core.cordapp.CordappConfig)
|
||||||
@org.jetbrains.annotations.Nullable public final net.corda.core.crypto.SecureHash getAttachmentId()
|
@org.jetbrains.annotations.Nullable public final net.corda.core.crypto.SecureHash getAttachmentId()
|
||||||
@org.jetbrains.annotations.NotNull public final ClassLoader getClassLoader()
|
@org.jetbrains.annotations.NotNull public final ClassLoader getClassLoader()
|
||||||
@org.jetbrains.annotations.NotNull public final net.corda.core.cordapp.Cordapp getCordapp()
|
@org.jetbrains.annotations.NotNull public final net.corda.core.cordapp.Cordapp getCordapp()
|
||||||
|
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@ -29,6 +29,8 @@
|
|||||||
<module name="corda-webserver_integrationTest" target="1.8" />
|
<module name="corda-webserver_integrationTest" target="1.8" />
|
||||||
<module name="corda-webserver_main" target="1.8" />
|
<module name="corda-webserver_main" target="1.8" />
|
||||||
<module name="corda-webserver_test" target="1.8" />
|
<module name="corda-webserver_test" target="1.8" />
|
||||||
|
<module name="cordapp-configuration_main" target="1.8" />
|
||||||
|
<module name="cordapp-configuration_test" target="1.8" />
|
||||||
<module name="cordapp_integrationTest" target="1.8" />
|
<module name="cordapp_integrationTest" target="1.8" />
|
||||||
<module name="cordapp_main" target="1.8" />
|
<module name="cordapp_main" target="1.8" />
|
||||||
<module name="cordapp_test" target="1.8" />
|
<module name="cordapp_test" target="1.8" />
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
gradlePluginsVersion=3.0.5
|
gradlePluginsVersion=3.0.6
|
||||||
kotlinVersion=1.2.20
|
kotlinVersion=1.2.20
|
||||||
platformVersion=2
|
platformVersion=2
|
||||||
guavaVersion=21.0
|
guavaVersion=21.0
|
||||||
@ -6,4 +6,4 @@ bouncycastleVersion=1.57
|
|||||||
typesafeConfigVersion=1.3.1
|
typesafeConfigVersion=1.3.1
|
||||||
jsr305Version=3.0.2
|
jsr305Version=3.0.2
|
||||||
artifactoryPluginVersion=4.4.18
|
artifactoryPluginVersion=4.4.18
|
||||||
snakeYamlVersion=1.19
|
snakeYamlVersion=1.19
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
package net.corda.core.cordapp
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown if an exception occurs in accessing or parsing cordapp configuration
|
||||||
|
*/
|
||||||
|
class CordappConfigException(msg: String, e: Throwable) : Exception(msg, e)
|
70
core/src/main/kotlin/net/corda/core/cordapp/CordappConfig.kt
Normal file
70
core/src/main/kotlin/net/corda/core/cordapp/CordappConfig.kt
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package net.corda.core.cordapp
|
||||||
|
|
||||||
|
import net.corda.core.DoNotImplement
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides access to cordapp configuration independent of the configuration provider.
|
||||||
|
*/
|
||||||
|
@DoNotImplement
|
||||||
|
interface CordappConfig {
|
||||||
|
/**
|
||||||
|
* Check if a config exists at path
|
||||||
|
*/
|
||||||
|
fun exists(path: String): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of the configuration at "path".
|
||||||
|
*
|
||||||
|
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
|
||||||
|
*/
|
||||||
|
fun get(path: String): Any
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the int value of the configuration at "path".
|
||||||
|
*
|
||||||
|
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
|
||||||
|
*/
|
||||||
|
fun getInt(path: String): Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the long value of the configuration at "path".
|
||||||
|
*
|
||||||
|
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
|
||||||
|
*/
|
||||||
|
fun getLong(path: String): Long
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the float value of the configuration at "path".
|
||||||
|
*
|
||||||
|
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
|
||||||
|
*/
|
||||||
|
fun getFloat(path: String): Float
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the double value of the configuration at "path".
|
||||||
|
*
|
||||||
|
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
|
||||||
|
*/
|
||||||
|
fun getDouble(path: String): Double
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number value of the configuration at "path".
|
||||||
|
*
|
||||||
|
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
|
||||||
|
*/
|
||||||
|
fun getNumber(path: String): Number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the string value of the configuration at "path".
|
||||||
|
*
|
||||||
|
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
|
||||||
|
*/
|
||||||
|
fun getString(path: String): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the boolean value of the configuration at "path".
|
||||||
|
*
|
||||||
|
* @throws CordappConfigException If the configuration fails to load, parse, or find a value.
|
||||||
|
*/
|
||||||
|
fun getBoolean(path: String): Boolean
|
||||||
|
}
|
@ -2,8 +2,6 @@ package net.corda.core.cordapp
|
|||||||
|
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
|
|
||||||
// TODO: Add per app config
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An app context provides information about where an app was loaded from, access to its classloader,
|
* An app context provides information about where an app was loaded from, access to its classloader,
|
||||||
* and (in the included [Cordapp] object) lists of annotated classes discovered via scanning the JAR.
|
* and (in the included [Cordapp] object) lists of annotated classes discovered via scanning the JAR.
|
||||||
@ -15,5 +13,11 @@ import net.corda.core.crypto.SecureHash
|
|||||||
* @property attachmentId For CorDapps containing [Contract] or [UpgradedContract] implementations this will be populated
|
* @property attachmentId For CorDapps containing [Contract] or [UpgradedContract] implementations this will be populated
|
||||||
* with the attachment containing those class files
|
* with the attachment containing those class files
|
||||||
* @property classLoader the classloader used to load this cordapp's classes
|
* @property classLoader the classloader used to load this cordapp's classes
|
||||||
|
* @property config Configuration for this CorDapp
|
||||||
*/
|
*/
|
||||||
class CordappContext(val cordapp: Cordapp, val attachmentId: SecureHash?, val classLoader: ClassLoader)
|
class CordappContext internal constructor(
|
||||||
|
val cordapp: Cordapp,
|
||||||
|
val attachmentId: SecureHash?,
|
||||||
|
val classLoader: ClassLoader,
|
||||||
|
val config: CordappConfig
|
||||||
|
)
|
||||||
|
@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
package net.corda.core.internal
|
package net.corda.core.internal
|
||||||
|
|
||||||
|
import net.corda.core.cordapp.Cordapp
|
||||||
|
import net.corda.core.cordapp.CordappConfig
|
||||||
|
import net.corda.core.cordapp.CordappContext
|
||||||
import net.corda.core.cordapp.CordappProvider
|
import net.corda.core.cordapp.CordappProvider
|
||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.*
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
@ -375,3 +378,7 @@ inline fun <T : Any> SerializedBytes<T>.sign(keyPair: KeyPair): SignedData<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun ByteBuffer.copyBytes() = ByteArray(remaining()).also { get(it) }
|
fun ByteBuffer.copyBytes() = ByteArray(remaining()).also { get(it) }
|
||||||
|
|
||||||
|
fun createCordappContext(cordapp: Cordapp, attachmentId: SecureHash?, classLoader: ClassLoader, config: CordappConfig): CordappContext {
|
||||||
|
return CordappContext(cordapp, attachmentId, classLoader, config)
|
||||||
|
}
|
@ -2,6 +2,7 @@ package net.corda.core.node
|
|||||||
|
|
||||||
import net.corda.core.DoNotImplement
|
import net.corda.core.DoNotImplement
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
|
import net.corda.core.cordapp.CordappContext
|
||||||
import net.corda.core.cordapp.CordappProvider
|
import net.corda.core.cordapp.CordappProvider
|
||||||
import net.corda.core.crypto.Crypto
|
import net.corda.core.crypto.Crypto
|
||||||
import net.corda.core.crypto.SignableData
|
import net.corda.core.crypto.SignableData
|
||||||
@ -369,4 +370,9 @@ interface ServiceHub : ServicesForResolution {
|
|||||||
* node starts.
|
* node starts.
|
||||||
*/
|
*/
|
||||||
fun registerUnloadHandler(runOnStop: () -> Unit)
|
fun registerUnloadHandler(runOnStop: () -> Unit)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See [CordappProvider.getAppContext]
|
||||||
|
*/
|
||||||
|
fun getAppContext(): CordappContext = cordappProvider.getAppContext()
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,10 @@ from the previous milestone release.
|
|||||||
UNRELEASED
|
UNRELEASED
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
* Per CorDapp configuration is now exposed. ``CordappContext`` now exposes a ``CordappConfig`` object that is populated
|
||||||
|
at CorDapp context creation time from a file source during runtime.
|
||||||
|
|
||||||
|
|
||||||
* Introduced Flow Draining mode, in which a node continues executing existing flows, but does not start new. This is to support graceful node shutdown/restarts.
|
* Introduced Flow Draining mode, in which a node continues executing existing flows, but does not start new. This is to support graceful node shutdown/restarts.
|
||||||
In particular, when this mode is on, new flows through RPC will be rejected, scheduled flows will be ignored, and initial session messages will not be consumed.
|
In particular, when this mode is on, new flows through RPC will be rejected, scheduled flows will be ignored, and initial session messages will not be consumed.
|
||||||
This will ensure that the number of checkpoints will strictly diminish with time, allowing for a clean shutdown.
|
This will ensure that the number of checkpoints will strictly diminish with time, allowing for a clean shutdown.
|
||||||
@ -188,18 +192,9 @@ UNRELEASED
|
|||||||
* Marked ``stateMachine`` on ``FlowLogic`` as ``CordaInternal`` to make clear that is it not part of the public api and is
|
* Marked ``stateMachine`` on ``FlowLogic`` as ``CordaInternal`` to make clear that is it not part of the public api and is
|
||||||
only for internal use
|
only for internal use
|
||||||
|
|
||||||
* Provided experimental support for specifying your own webserver to be used instead of the default development
|
|
||||||
webserver in ``Cordform`` using the ``webserverJar`` argument
|
|
||||||
|
|
||||||
* Created new ``StartedMockNode`` and ``UnstartedMockNode`` classes which are wrappers around our MockNode implementation
|
* Created new ``StartedMockNode`` and ``UnstartedMockNode`` classes which are wrappers around our MockNode implementation
|
||||||
that expose relevant methods for testing without exposing internals, create these using a ``MockNetwork``.
|
that expose relevant methods for testing without exposing internals, create these using a ``MockNetwork``.
|
||||||
|
|
||||||
* The test utils in ``Expect.kt``, ``SerializationTestHelpers.kt``, ``TestConstants.kt`` and ``TestUtils.kt`` have moved
|
|
||||||
from the ``net.corda.testing`` package to the ``net.corda.testing.core`` package, and ``FlowStackSnapshot.kt`` has moved to the
|
|
||||||
``net.corda.testing.services`` package. Moving items out of the ``net.corda.testing.*`` package will help make it clearer which
|
|
||||||
parts of the api are stable. The bash script ``tools\scripts\update-test-packages.sh`` can be used to smooth the upgrade
|
|
||||||
process for existing projects.
|
|
||||||
|
|
||||||
.. _changelog_v1:
|
.. _changelog_v1:
|
||||||
|
|
||||||
Release 1.0
|
Release 1.0
|
||||||
|
@ -159,3 +159,20 @@ Installing the CorDapp JAR
|
|||||||
At runtime, nodes will load any CorDapps present in their ``cordapps`` folder. Therefore in order to install a CorDapp on
|
At runtime, nodes will load any CorDapps present in their ``cordapps`` folder. Therefore in order to install a CorDapp on
|
||||||
a node, the CorDapp JAR must be added to the ``<node_dir>/cordapps/`` folder, where ``node_dir`` is the folder in which
|
a node, the CorDapp JAR must be added to the ``<node_dir>/cordapps/`` folder, where ``node_dir`` is the folder in which
|
||||||
the node's JAR and configuration files are stored.
|
the node's JAR and configuration files are stored.
|
||||||
|
|
||||||
|
CorDapp configuration files
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
CorDapp configuration files should be placed in ``<node_dir>/cordapps/config``. The name of the file should match the
|
||||||
|
name of the JAR of the CorDapp (eg; if your CorDapp is called ``hello-0.1.jar`` the config should be ``config/hello-0.1.conf``).
|
||||||
|
|
||||||
|
Config files are currently only available in the `Typesafe/Lightbend <https://github.com/lightbend/config>`_ config format.
|
||||||
|
These files are loaded when a CorDapp context is created and so can change during runtime.
|
||||||
|
|
||||||
|
CorDapp configuration can be accessed from ``CordappContext::config`` whenever a ``CordappContext`` is available.
|
||||||
|
|
||||||
|
There is an example project that demonstrates in ``samples` called ``cordapp-configuration`` and API documentation in
|
||||||
|
<api/kotlin/corda/net.corda.core.cordapp/index.html>`_.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
|
apply plugin: 'kotlin'
|
||||||
apply plugin: 'maven-publish'
|
apply plugin: 'maven-publish'
|
||||||
apply plugin: 'net.corda.plugins.publish-utils'
|
apply plugin: 'net.corda.plugins.publish-utils'
|
||||||
apply plugin: 'com.jfrog.artifactory'
|
apply plugin: 'com.jfrog.artifactory'
|
||||||
@ -7,11 +8,9 @@ repositories {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
// This tracks the gradle plugins version and not Corda
|
|
||||||
version gradle_plugins_version
|
|
||||||
group 'net.corda.plugins'
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||||
|
|
||||||
// JSR 305: Nullability annotations
|
// JSR 305: Nullability annotations
|
||||||
compile "com.google.code.findbugs:jsr305:$jsr305_version"
|
compile "com.google.code.findbugs:jsr305:$jsr305_version"
|
||||||
|
|
||||||
|
@ -10,7 +10,12 @@ import java.util.function.Consumer;
|
|||||||
public abstract class CordformDefinition {
|
public abstract class CordformDefinition {
|
||||||
private Path nodesDirectory = Paths.get("build", "nodes");
|
private Path nodesDirectory = Paths.get("build", "nodes");
|
||||||
private final List<Consumer<CordformNode>> nodeConfigurers = new ArrayList<>();
|
private final List<Consumer<CordformNode>> nodeConfigurers = new ArrayList<>();
|
||||||
private final List<String> cordappPackages = new ArrayList<>();
|
/**
|
||||||
|
* A list of Cordapp maven coordinates and project name
|
||||||
|
*
|
||||||
|
* If maven coordinates are set project name is ignored
|
||||||
|
*/
|
||||||
|
private final List<CordappDependency> cordappDeps = new ArrayList<>();
|
||||||
|
|
||||||
public Path getNodesDirectory() {
|
public Path getNodesDirectory() {
|
||||||
return nodesDirectory;
|
return nodesDirectory;
|
||||||
@ -28,8 +33,11 @@ public abstract class CordformDefinition {
|
|||||||
nodeConfigurers.add(configurer);
|
nodeConfigurers.add(configurer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getCordappPackages() {
|
/**
|
||||||
return cordappPackages;
|
* Cordapp maven coordinates or project names (ie; net.corda:finance:0.1 or ":finance") to scan for when resolving cordapp JARs
|
||||||
|
*/
|
||||||
|
public List<CordappDependency> getCordappDependencies() {
|
||||||
|
return cordappDeps;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
package net.corda.cordform
|
||||||
|
|
||||||
|
data class CordappDependency(
|
||||||
|
val mavenCoordinates: String? = null,
|
||||||
|
val projectName: String? = null
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
require((mavenCoordinates != null) != (projectName != null), { "Only one of maven coordinates or project name must be set" })
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ buildscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'kotlin'
|
apply plugin: 'kotlin'
|
||||||
|
apply plugin: 'java-gradle-plugin'
|
||||||
apply plugin: 'net.corda.plugins.publish-utils'
|
apply plugin: 'net.corda.plugins.publish-utils'
|
||||||
apply plugin: 'com.jfrog.artifactory'
|
apply plugin: 'com.jfrog.artifactory'
|
||||||
|
|
||||||
@ -33,18 +34,22 @@ sourceSets {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile gradleApi()
|
gradleApi()
|
||||||
|
|
||||||
compile project(":cordapp")
|
compile project(":cordapp")
|
||||||
|
compile project(':cordform-common')
|
||||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||||
|
compile "commons-io:commons-io:2.6"
|
||||||
|
|
||||||
noderunner "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
noderunner "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||||
|
|
||||||
compile project(':cordform-common')
|
testCompile "junit:junit:4.12" // TODO: Unify with core
|
||||||
|
testCompile "org.assertj:assertj-core:3.8.0"
|
||||||
// Docker-compose file generation
|
// Docker-compose file generation
|
||||||
compile "org.yaml:snakeyaml:$snake_yaml_version"
|
compile "org.yaml:snakeyaml:$snake_yaml_version"
|
||||||
}
|
}
|
||||||
|
|
||||||
task createNodeRunner(type: Jar, dependsOn: [classes]) {
|
task createNodeRunner(type: Jar) {
|
||||||
manifest {
|
manifest {
|
||||||
attributes('Main-Class': 'net.corda.plugins.NodeRunnerKt')
|
attributes('Main-Class': 'net.corda.plugins.NodeRunnerKt')
|
||||||
}
|
}
|
||||||
@ -53,12 +58,12 @@ task createNodeRunner(type: Jar, dependsOn: [classes]) {
|
|||||||
from sourceSets.runnodes.output
|
from sourceSets.runnodes.output
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
publish {
|
||||||
|
name project.name
|
||||||
|
}
|
||||||
|
|
||||||
|
processResources {
|
||||||
from(createNodeRunner) {
|
from(createNodeRunner) {
|
||||||
rename { 'net/corda/plugins/runnodes.jar' }
|
rename { 'net/corda/plugins/runnodes.jar' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
publish {
|
|
||||||
name project.name
|
|
||||||
}
|
|
||||||
|
@ -2,11 +2,9 @@ package net.corda.plugins
|
|||||||
|
|
||||||
import groovy.lang.Closure
|
import groovy.lang.Closure
|
||||||
import net.corda.cordform.CordformDefinition
|
import net.corda.cordform.CordformDefinition
|
||||||
import org.apache.tools.ant.filters.FixCrLfFilter
|
|
||||||
import org.gradle.api.DefaultTask
|
import org.gradle.api.DefaultTask
|
||||||
import org.gradle.api.plugins.JavaPluginConvention
|
import org.gradle.api.plugins.JavaPluginConvention
|
||||||
import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
|
import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
|
||||||
import org.gradle.api.tasks.TaskAction
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.lang.reflect.InvocationTargetException
|
import java.lang.reflect.InvocationTargetException
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
@ -111,20 +109,27 @@ open class Baseform : DefaultTask() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun initializeConfiguration() {
|
internal fun initializeConfiguration() {
|
||||||
if (definitionClass != null) {
|
if (definitionClass != null) {
|
||||||
val cd = loadCordformDefinition()
|
val cd = loadCordformDefinition()
|
||||||
// If the user has specified their own directory (even if it's the same default path) then let them know
|
// If the user has specified their own directory (even if it's the same default path) then let them know
|
||||||
// it's not used and should just rely on the one in CordformDefinition
|
// it's not used and should just rely on the one in CordformDefinition
|
||||||
require(directory === defaultDirectory) {
|
require(directory === defaultDirectory) {
|
||||||
|
project.logger.info("User has used '$directory', default directory is '${defaultDirectory}'")
|
||||||
"'directory' cannot be used when 'definitionClass' is specified. Use CordformDefinition.nodesDirectory instead."
|
"'directory' cannot be used when 'definitionClass' is specified. Use CordformDefinition.nodesDirectory instead."
|
||||||
}
|
}
|
||||||
directory = cd.nodesDirectory
|
directory = cd.nodesDirectory
|
||||||
val cordapps = cd.getMatchingCordapps()
|
val cordapps = cd.cordappDependencies
|
||||||
cd.nodeConfigurers.forEach {
|
cd.nodeConfigurers.forEach {
|
||||||
val node = node { }
|
val node = node { }
|
||||||
it.accept(node)
|
it.accept(node)
|
||||||
node.additionalCordapps.addAll(cordapps)
|
cordapps.forEach {
|
||||||
|
if (it.mavenCoordinates != null) {
|
||||||
|
node.cordapp(project.project(it.mavenCoordinates!!))
|
||||||
|
} else {
|
||||||
|
node.cordapp(it.projectName!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
node.rootDir(directory)
|
node.rootDir(directory)
|
||||||
}
|
}
|
||||||
cd.setup { nodeName -> project.projectDir.toPath().resolve(getNodeByName(nodeName)!!.nodeDir.toPath()) }
|
cd.setup { nodeName -> project.projectDir.toPath().resolve(getNodeByName(nodeName)!!.nodeDir.toPath()) }
|
||||||
@ -134,7 +139,6 @@ open class Baseform : DefaultTask() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun bootstrapNetwork() {
|
protected fun bootstrapNetwork() {
|
||||||
val networkBootstrapperClass = loadNetworkBootstrapperClass()
|
val networkBootstrapperClass = loadNetworkBootstrapperClass()
|
||||||
val networkBootstrapper = networkBootstrapperClass.newInstance()
|
val networkBootstrapper = networkBootstrapperClass.newInstance()
|
||||||
@ -148,18 +152,6 @@ open class Baseform : DefaultTask() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CordformDefinition.getMatchingCordapps(): List<File> {
|
|
||||||
val cordappJars = project.configuration("cordapp").files
|
|
||||||
return cordappPackages.map { `package` ->
|
|
||||||
val cordappsWithPackage = cordappJars.filter { it.containsPackage(`package`) }
|
|
||||||
when (cordappsWithPackage.size) {
|
|
||||||
0 -> throw IllegalArgumentException("There are no cordapp dependencies containing the package $`package`")
|
|
||||||
1 -> cordappsWithPackage[0]
|
|
||||||
else -> throw IllegalArgumentException("More than one cordapp dependency contains the package $`package`: $cordappsWithPackage")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun File.containsPackage(`package`: String): Boolean {
|
private fun File.containsPackage(`package`: String): Boolean {
|
||||||
JarInputStream(inputStream()).use {
|
JarInputStream(inputStream()).use {
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
package net.corda.plugins
|
||||||
|
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
open class Cordapp private constructor(val coordinates: String?, val project: Project?) {
|
||||||
|
constructor(coordinates: String) : this(coordinates, null)
|
||||||
|
constructor(cordappProject: Project) : this(null, cordappProject)
|
||||||
|
|
||||||
|
// The configuration text that will be written
|
||||||
|
internal var config: String? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the configuration text that will be written to the cordapp's configuration file
|
||||||
|
*/
|
||||||
|
fun config(config: String) {
|
||||||
|
this.config = config
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads config from the file and later writes it to the cordapp's configuration file
|
||||||
|
*/
|
||||||
|
fun config(configFile: File) {
|
||||||
|
this.config = configFile.readText()
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,6 @@
|
|||||||
package net.corda.plugins
|
package net.corda.plugins
|
||||||
|
|
||||||
import org.apache.tools.ant.filters.FixCrLfFilter
|
import org.apache.tools.ant.filters.FixCrLfFilter
|
||||||
import org.gradle.api.DefaultTask
|
|
||||||
import org.gradle.api.plugins.JavaPluginConvention
|
|
||||||
import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
|
|
||||||
import org.gradle.api.tasks.TaskAction
|
import org.gradle.api.tasks.TaskAction
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
@ -15,18 +12,25 @@ import java.nio.file.Paths
|
|||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
open class Cordform : Baseform() {
|
open class Cordform : Baseform() {
|
||||||
private companion object {
|
internal companion object {
|
||||||
val nodeJarName = "corda.jar"
|
val nodeJarName = "corda.jar"
|
||||||
private val defaultDirectory: Path = Paths.get("build", "nodes")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a node by name.
|
||||||
|
*
|
||||||
|
* @param name The name of the node as specified in the node configuration DSL.
|
||||||
|
* @return A node instance.
|
||||||
|
*/
|
||||||
|
private fun getNodeByName(name: String): Node? = nodes.firstOrNull { it.name == name }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Installs the run script into the nodes directory.
|
* Installs the run script into the nodes directory.
|
||||||
*/
|
*/
|
||||||
private fun installRunScript() {
|
private fun installRunScript() {
|
||||||
project.copy {
|
project.copy {
|
||||||
it.apply {
|
it.apply {
|
||||||
from(Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.jar"))
|
from(Cordformation.getPluginFile(project, "runnodes.jar"))
|
||||||
fileMode = Cordformation.executableFileMode
|
fileMode = Cordformation.executableFileMode
|
||||||
into("$directory/")
|
into("$directory/")
|
||||||
}
|
}
|
||||||
@ -34,7 +38,7 @@ open class Cordform : Baseform() {
|
|||||||
|
|
||||||
project.copy {
|
project.copy {
|
||||||
it.apply {
|
it.apply {
|
||||||
from(Cordformation.getPluginFile(project, "net/corda/plugins/runnodes"))
|
from(Cordformation.getPluginFile(project, "runnodes"))
|
||||||
// Replaces end of line with lf to avoid issues with the bash interpreter and Windows style line endings.
|
// Replaces end of line with lf to avoid issues with the bash interpreter and Windows style line endings.
|
||||||
filter(mapOf("eol" to FixCrLfFilter.CrLf.newInstance("lf")), FixCrLfFilter::class.java)
|
filter(mapOf("eol" to FixCrLfFilter.CrLf.newInstance("lf")), FixCrLfFilter::class.java)
|
||||||
fileMode = Cordformation.executableFileMode
|
fileMode = Cordformation.executableFileMode
|
||||||
@ -44,7 +48,7 @@ open class Cordform : Baseform() {
|
|||||||
|
|
||||||
project.copy {
|
project.copy {
|
||||||
it.apply {
|
it.apply {
|
||||||
from(Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.bat"))
|
from(Cordformation.getPluginFile(project, "runnodes.bat"))
|
||||||
into("$directory/")
|
into("$directory/")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,4 +67,5 @@ open class Cordform : Baseform() {
|
|||||||
bootstrapNetwork()
|
bootstrapNetwork()
|
||||||
nodes.forEach(Node::build)
|
nodes.forEach(Node::build)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package net.corda.plugins
|
|||||||
import org.gradle.api.Plugin
|
import org.gradle.api.Plugin
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Cordformation plugin deploys nodes to a directory in a state ready to be used by a developer for experimentation,
|
* The Cordformation plugin deploys nodes to a directory in a state ready to be used by a developer for experimentation,
|
||||||
@ -13,19 +14,19 @@ class Cordformation : Plugin<Project> {
|
|||||||
const val CORDFORMATION_TYPE = "cordformationInternal"
|
const val CORDFORMATION_TYPE = "cordformationInternal"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a resource file from this plugin's JAR file.
|
* Gets a resource file from this plugin's JAR file by creating an intermediate tmp dir
|
||||||
*
|
*
|
||||||
* @param project The project environment this plugin executes in.
|
|
||||||
* @param filePathInJar The file in the JAR, relative to root, you wish to access.
|
* @param filePathInJar The file in the JAR, relative to root, you wish to access.
|
||||||
* @return A file handle to the file in the JAR.
|
* @return A file handle to the file in the JAR.
|
||||||
*/
|
*/
|
||||||
fun getPluginFile(project: Project, filePathInJar: String): File {
|
fun getPluginFile(project: Project, filePathInJar: String): File {
|
||||||
val archive = project.rootProject.buildscript.configurations
|
val tmpDir = File(project.buildDir, "tmp")
|
||||||
.single { it.name == "classpath" }
|
val outputFile = File(tmpDir, filePathInJar)
|
||||||
.first { it.name.contains("cordformation") }
|
tmpDir.mkdir()
|
||||||
return project.rootProject.resources.text
|
outputFile.outputStream().use {
|
||||||
.fromArchiveEntry(archive, filePathInJar)
|
Cordformation::class.java.getResourceAsStream(filePathInJar).copyTo(it)
|
||||||
.asFile()
|
}
|
||||||
|
return outputFile
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,7 +39,7 @@ class Cordformation : Plugin<Project> {
|
|||||||
fun verifyAndGetRuntimeJar(project: Project, jarName: String): File {
|
fun verifyAndGetRuntimeJar(project: Project, jarName: String): File {
|
||||||
val releaseVersion = project.rootProject.ext<String>("corda_release_version")
|
val releaseVersion = project.rootProject.ext<String>("corda_release_version")
|
||||||
val maybeJar = project.configuration("runtime").filter {
|
val maybeJar = project.configuration("runtime").filter {
|
||||||
"$jarName-$releaseVersion.jar" in it.toString() || "$jarName-enterprise-$releaseVersion.jar" in it.toString()
|
"$jarName-$releaseVersion.jar" in it.toString() || "$jarName-r3-$releaseVersion.jar" in it.toString()
|
||||||
}
|
}
|
||||||
if (maybeJar.isEmpty) {
|
if (maybeJar.isEmpty) {
|
||||||
throw IllegalStateException("No $jarName JAR found. Have you deployed the Corda project to Maven? Looked for \"$jarName-$releaseVersion.jar\"")
|
throw IllegalStateException("No $jarName JAR found. Have you deployed the Corda project to Maven? Looked for \"$jarName-$releaseVersion.jar\"")
|
||||||
|
@ -1,22 +1,28 @@
|
|||||||
package net.corda.plugins
|
package net.corda.plugins
|
||||||
|
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import com.typesafe.config.ConfigObject
|
||||||
import com.typesafe.config.ConfigRenderOptions
|
import com.typesafe.config.ConfigRenderOptions
|
||||||
import com.typesafe.config.ConfigValueFactory
|
import com.typesafe.config.ConfigValueFactory
|
||||||
import com.typesafe.config.ConfigObject
|
|
||||||
import groovy.lang.Closure
|
import groovy.lang.Closure
|
||||||
import net.corda.cordform.CordformNode
|
import net.corda.cordform.CordformNode
|
||||||
import net.corda.cordform.RpcSettings
|
import net.corda.cordform.RpcSettings
|
||||||
|
import org.apache.commons.io.FilenameUtils
|
||||||
|
import org.gradle.api.GradleException
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.artifacts.ProjectDependency
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a node that will be installed.
|
* Represents a node that will be installed.
|
||||||
*/
|
*/
|
||||||
class Node(private val project: Project) : CordformNode() {
|
open class Node @Inject constructor(private val project: Project) : CordformNode() {
|
||||||
|
private data class ResolvedCordapp(val jarFile: File, val config: String?)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
val webJarName = "corda-webserver.jar"
|
val webJarName = "corda-webserver.jar"
|
||||||
@ -30,8 +36,17 @@ class Node(private val project: Project) : CordformNode() {
|
|||||||
* @note Your app will be installed by default and does not need to be included here.
|
* @note Your app will be installed by default and does not need to be included here.
|
||||||
* @note Type is any due to gradle's use of "GStrings" - each value will have "toString" called on it
|
* @note Type is any due to gradle's use of "GStrings" - each value will have "toString" called on it
|
||||||
*/
|
*/
|
||||||
var cordapps = mutableListOf<Any>()
|
var cordapps: MutableList<Any>
|
||||||
internal var additionalCordapps = mutableListOf<File>()
|
get() = internalCordapps as MutableList<Any>
|
||||||
|
@Deprecated("Use cordapp instead - setter will be removed by Corda V4.0")
|
||||||
|
set(value) {
|
||||||
|
value.forEach {
|
||||||
|
cordapp(it.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val internalCordapps = mutableListOf<Cordapp>()
|
||||||
|
private val builtCordapp = Cordapp(project)
|
||||||
internal lateinit var nodeDir: File
|
internal lateinit var nodeDir: File
|
||||||
private set
|
private set
|
||||||
internal lateinit var rootDir: File
|
internal lateinit var rootDir: File
|
||||||
@ -76,8 +91,83 @@ class Node(private val project: Project) : CordformNode() {
|
|||||||
*
|
*
|
||||||
* @param sshdPort The port for SSH server to listen on
|
* @param sshdPort The port for SSH server to listen on
|
||||||
*/
|
*/
|
||||||
fun sshdPort(sshdPort: Int?) {
|
fun sshdPort(sshdPort: Int) {
|
||||||
config = config.withValue("sshd.port", ConfigValueFactory.fromAnyRef(sshdPort))
|
config = config.withValue("sshdAddress",
|
||||||
|
ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$sshdPort"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install a cordapp to this node
|
||||||
|
*
|
||||||
|
* @param coordinates The coordinates of the [Cordapp]
|
||||||
|
* @param configureClosure A groovy closure to configure a [Cordapp] object
|
||||||
|
* @return The created and inserted [Cordapp]
|
||||||
|
*/
|
||||||
|
fun cordapp(coordinates: String, configureClosure: Closure<in Cordapp>): Cordapp {
|
||||||
|
val cordapp = project.configure(Cordapp(coordinates), configureClosure) as Cordapp
|
||||||
|
internalCordapps += cordapp
|
||||||
|
return cordapp
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install a cordapp to this node
|
||||||
|
*
|
||||||
|
* @param cordappProject A project that produces a cordapp JAR
|
||||||
|
* @param configureClosure A groovy closure to configure a [Cordapp] object
|
||||||
|
* @return The created and inserted [Cordapp]
|
||||||
|
*/
|
||||||
|
fun cordapp(cordappProject: Project, configureClosure: Closure<in Cordapp>): Cordapp {
|
||||||
|
val cordapp = project.configure(Cordapp(cordappProject), configureClosure) as Cordapp
|
||||||
|
internalCordapps += cordapp
|
||||||
|
return cordapp
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install a cordapp to this node
|
||||||
|
*
|
||||||
|
* @param cordappProject A project that produces a cordapp JAR
|
||||||
|
* @return The created and inserted [Cordapp]
|
||||||
|
*/
|
||||||
|
fun cordapp(cordappProject: Project): Cordapp {
|
||||||
|
return Cordapp(cordappProject).apply {
|
||||||
|
internalCordapps += this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install a cordapp to this node
|
||||||
|
*
|
||||||
|
* @param coordinates The coordinates of the [Cordapp]
|
||||||
|
* @return The created and inserted [Cordapp]
|
||||||
|
*/
|
||||||
|
fun cordapp(coordinates: String): Cordapp {
|
||||||
|
return Cordapp(coordinates).apply {
|
||||||
|
internalCordapps += this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install a cordapp to this node
|
||||||
|
*
|
||||||
|
* @param configureFunc A lambda to configure a [Cordapp] object
|
||||||
|
* @return The created and inserted [Cordapp]
|
||||||
|
*/
|
||||||
|
fun cordapp(coordinates: String, configureFunc: Cordapp.() -> Unit): Cordapp {
|
||||||
|
return Cordapp(coordinates).apply {
|
||||||
|
configureFunc()
|
||||||
|
internalCordapps += this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the default cordapp automatically added to this node from this project
|
||||||
|
*
|
||||||
|
* @param configureClosure A groovy closure to configure a [Cordapp] object
|
||||||
|
* @return The created and inserted [Cordapp]
|
||||||
|
*/
|
||||||
|
fun projectCordapp(configureClosure: Closure<in Cordapp>): Cordapp {
|
||||||
|
project.configure(builtCordapp, configureClosure) as Cordapp
|
||||||
|
return builtCordapp
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -96,8 +186,8 @@ class Node(private val project: Project) : CordformNode() {
|
|||||||
installWebserverJar()
|
installWebserverJar()
|
||||||
}
|
}
|
||||||
installAgentJar()
|
installAgentJar()
|
||||||
installBuiltCordapp()
|
|
||||||
installCordapps()
|
installCordapps()
|
||||||
|
installConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun buildDocker() {
|
internal fun buildDocker() {
|
||||||
@ -109,7 +199,6 @@ class Node(private val project: Project) : CordformNode() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
installAgentJar()
|
installAgentJar()
|
||||||
installBuiltCordapp()
|
|
||||||
installCordapps()
|
installCordapps()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,19 +249,6 @@ class Node(private val project: Project) : CordformNode() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Installs this project's cordapp to this directory.
|
|
||||||
*/
|
|
||||||
private fun installBuiltCordapp() {
|
|
||||||
val cordappsDir = File(nodeDir, "cordapps")
|
|
||||||
project.copy {
|
|
||||||
it.apply {
|
|
||||||
from(project.tasks.getByName("jar"))
|
|
||||||
into(cordappsDir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Installs the jolokia monitoring agent JAR to the node/drivers directory
|
* Installs the jolokia monitoring agent JAR to the node/drivers directory
|
||||||
*/
|
*/
|
||||||
@ -197,6 +273,14 @@ class Node(private val project: Project) : CordformNode() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun installCordappConfigs(cordapps: Collection<ResolvedCordapp>) {
|
||||||
|
val cordappsDir = project.file(File(nodeDir, "cordapps"))
|
||||||
|
cordappsDir.mkdirs()
|
||||||
|
cordapps.filter { it.config != null }
|
||||||
|
.map { Pair<String, String>("${FilenameUtils.removeExtension(it.jarFile.name)}.conf", it.config!!) }
|
||||||
|
.forEach { project.file(File(cordappsDir, it.first)).writeText(it.second) }
|
||||||
|
}
|
||||||
|
|
||||||
private fun createTempConfigFile(configObject: ConfigObject): File {
|
private fun createTempConfigFile(configObject: ConfigObject): File {
|
||||||
val options = ConfigRenderOptions
|
val options = ConfigRenderOptions
|
||||||
.defaults()
|
.defaults()
|
||||||
@ -217,7 +301,7 @@ class Node(private val project: Project) : CordformNode() {
|
|||||||
/**
|
/**
|
||||||
* Installs the configuration file to the root directory and detokenises it.
|
* Installs the configuration file to the root directory and detokenises it.
|
||||||
*/
|
*/
|
||||||
internal fun installConfig() {
|
fun installConfig() {
|
||||||
configureProperties()
|
configureProperties()
|
||||||
val tmpConfFile = createTempConfigFile(config.root())
|
val tmpConfFile = createTempConfigFile(config.root())
|
||||||
appendOptionalConfig(tmpConfFile)
|
appendOptionalConfig(tmpConfFile)
|
||||||
@ -269,31 +353,57 @@ class Node(private val project: Project) : CordformNode() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Installs other cordapps to this node's cordapps directory.
|
* Installs the jolokia monitoring agent JAR to the node/drivers directory
|
||||||
*/
|
*/
|
||||||
internal fun installCordapps() {
|
private fun installCordapps() {
|
||||||
additionalCordapps.addAll(getCordappList())
|
val cordapps = getCordappList()
|
||||||
val cordappsDir = File(nodeDir, "cordapps")
|
val cordappsDir = File(nodeDir, "cordapps")
|
||||||
project.copy {
|
project.copy {
|
||||||
it.apply {
|
it.apply {
|
||||||
from(additionalCordapps)
|
from(cordapps.map { it.jarFile })
|
||||||
into(cordappsDir)
|
into(project.file(cordappsDir))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
installCordappConfigs(cordapps)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a list of cordapps based on what dependent cordapps were specified.
|
* Gets a list of cordapps based on what dependent cordapps were specified.
|
||||||
*
|
*
|
||||||
* @return List of this node's cordapps.
|
* @return List of this node's cordapps.
|
||||||
*/
|
*/
|
||||||
private fun getCordappList(): Collection<File> {
|
private fun getCordappList(): Collection<ResolvedCordapp> =
|
||||||
// Cordapps can sometimes contain a GString instance which fails the equality test with the Java string
|
internalCordapps.map { cordapp -> resolveCordapp(cordapp) } + resolveBuiltCordapp()
|
||||||
@Suppress("RemoveRedundantCallsOfConversionMethods")
|
|
||||||
val cordapps: List<String> = cordapps.map { it.toString() }
|
private fun resolveCordapp(cordapp: Cordapp): ResolvedCordapp {
|
||||||
return project.configuration("cordapp").files {
|
val cordappConfiguration = project.configuration("cordapp")
|
||||||
cordapps.contains(it.group + ":" + it.name + ":" + it.version)
|
val cordappName = if (cordapp.project != null) cordapp.project.name else cordapp.coordinates
|
||||||
|
val cordappFile = cordappConfiguration.files {
|
||||||
|
when {
|
||||||
|
(it is ProjectDependency) && (cordapp.project != null) -> it.dependencyProject == cordapp.project
|
||||||
|
cordapp.coordinates != null -> {
|
||||||
|
// Cordapps can sometimes contain a GString instance which fails the equality test with the Java string
|
||||||
|
@Suppress("RemoveRedundantCallsOfConversionMethods")
|
||||||
|
val coordinates = cordapp.coordinates.toString()
|
||||||
|
coordinates == (it.group + ":" + it.name + ":" + it.version)
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return when {
|
||||||
|
cordappFile.size == 0 -> throw GradleException("Cordapp $cordappName not found in cordapps configuration.")
|
||||||
|
cordappFile.size > 1 -> throw GradleException("Multiple files found for $cordappName")
|
||||||
|
else -> ResolvedCordapp(cordappFile.single(), cordapp.config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun resolveBuiltCordapp(): ResolvedCordapp {
|
||||||
|
val projectCordappFile = project.tasks.getByName("jar").outputs.files.singleFile
|
||||||
|
return ResolvedCordapp(projectCordappFile, builtCordapp.config)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,35 +109,43 @@ private abstract class JavaCommand(
|
|||||||
|
|
||||||
private class HeadlessJavaCommand(jarName: String, dir: File, debugPort: Int?, monitoringPort: Int?, args: List<String>, jvmArgs: List<String>)
|
private class HeadlessJavaCommand(jarName: String, dir: File, debugPort: Int?, monitoringPort: Int?, args: List<String>, jvmArgs: List<String>)
|
||||||
: JavaCommand(jarName, dir, debugPort, monitoringPort, dir.name, { add("--no-local-shell") }, args, jvmArgs) {
|
: JavaCommand(jarName, dir, debugPort, monitoringPort, dir.name, { add("--no-local-shell") }, args, jvmArgs) {
|
||||||
override fun processBuilder() = ProcessBuilder(command).redirectError(File("error.$nodeName.log")).inheritIO()
|
override fun processBuilder(): ProcessBuilder {
|
||||||
|
println("Running command: ${command.joinToString(" ")}")
|
||||||
|
return ProcessBuilder(command).redirectError(File("error.$nodeName.log")).inheritIO()
|
||||||
|
}
|
||||||
|
|
||||||
override fun getJavaPath() = File(File(System.getProperty("java.home"), "bin"), "java").path
|
override fun getJavaPath() = File(File(System.getProperty("java.home"), "bin"), "java").path
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TerminalWindowJavaCommand(jarName: String, dir: File, debugPort: Int?, monitoringPort: Int?, args: List<String>, jvmArgs: List<String>)
|
private class TerminalWindowJavaCommand(jarName: String, dir: File, debugPort: Int?, monitoringPort: Int?, args: List<String>, jvmArgs: List<String>)
|
||||||
: JavaCommand(jarName, dir, debugPort, monitoringPort, "${dir.name}-$jarName", {}, args, jvmArgs) {
|
: JavaCommand(jarName, dir, debugPort, monitoringPort, "${dir.name}-$jarName", {}, args, jvmArgs) {
|
||||||
override fun processBuilder() = ProcessBuilder(when (os) {
|
override fun processBuilder(): ProcessBuilder {
|
||||||
OS.MACOS -> {
|
val params = when (os) {
|
||||||
listOf("osascript", "-e", """tell app "Terminal"
|
OS.MACOS -> {
|
||||||
|
listOf("osascript", "-e", """tell app "Terminal"
|
||||||
activate
|
activate
|
||||||
delay 0.5
|
delay 0.5
|
||||||
tell app "System Events" to tell process "Terminal" to keystroke "t" using command down
|
tell app "System Events" to tell process "Terminal" to keystroke "t" using command down
|
||||||
delay 0.5
|
delay 0.5
|
||||||
do script "bash -c 'cd \"$dir\" ; \"${command.joinToString("""\" \"""")}\" && exit'" in selected tab of the front window
|
do script "bash -c 'cd \"$dir\" ; \"${command.joinToString("""\" \"""")}\" && exit'" in selected tab of the front window
|
||||||
end tell""")
|
end tell""")
|
||||||
}
|
}
|
||||||
OS.WINDOWS -> {
|
OS.WINDOWS -> {
|
||||||
listOf("cmd", "/C", "start ${command.joinToString(" ") { windowsSpaceEscape(it) }}")
|
listOf("cmd", "/C", "start ${command.joinToString(" ") { windowsSpaceEscape(it) }}")
|
||||||
}
|
}
|
||||||
OS.LINUX -> {
|
OS.LINUX -> {
|
||||||
// Start shell to keep window open unless java terminated normally or due to SIGTERM:
|
// Start shell to keep window open unless java terminated normally or due to SIGTERM:
|
||||||
val command = "${unixCommand()}; [ $? -eq 0 -o $? -eq 143 ] || sh"
|
val command = "${unixCommand()}; [ $? -eq 0 -o $? -eq 143 ] || sh"
|
||||||
if (isTmux()) {
|
if (isTmux()) {
|
||||||
listOf("tmux", "new-window", "-n", nodeName, command)
|
listOf("tmux", "new-window", "-n", nodeName, command)
|
||||||
} else {
|
} else {
|
||||||
listOf("xterm", "-T", nodeName, "-e", command)
|
listOf("xterm", "-T", nodeName, "-e", command)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
println("Running command: ${params.joinToString(" ")}")
|
||||||
|
return ProcessBuilder(params)
|
||||||
|
}
|
||||||
|
|
||||||
private fun unixCommand() = command.map(::quotedFormOf).joinToString(" ")
|
private fun unixCommand() = command.map(::quotedFormOf).joinToString(" ")
|
||||||
override fun getJavaPath(): String = File(File(System.getProperty("java.home"), "bin"), "java").path
|
override fun getJavaPath(): String = File(File(System.getProperty("java.home"), "bin"), "java").path
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
package net.corda.plugins
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
|
import org.apache.commons.io.IOUtils
|
||||||
|
import org.assertj.core.api.Assertions.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.rules.TemporaryFolder
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.nio.file.Files
|
||||||
|
import org.gradle.testkit.runner.GradleRunner
|
||||||
|
import org.gradle.testkit.runner.TaskOutcome
|
||||||
|
|
||||||
|
class CordformTest {
|
||||||
|
@Rule
|
||||||
|
@JvmField
|
||||||
|
val testProjectDir = TemporaryFolder()
|
||||||
|
private var buildFile: File? = null
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
val cordaFinanceJarName = "corda-finance-3.0-SNAPSHOT"
|
||||||
|
val localCordappJarName = "locally-built-cordapp"
|
||||||
|
val notaryNodeName = "Notary Service"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
buildFile = testProjectDir.newFile("build.gradle")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `a node with cordapp dependency`() {
|
||||||
|
val runner = getStandardGradleRunnerFor("DeploySingleNodeWithCordapp.gradle")
|
||||||
|
|
||||||
|
val result = runner.build()
|
||||||
|
|
||||||
|
assertThat(result.task(":deployNodes")!!.outcome).isEqualTo(TaskOutcome.SUCCESS)
|
||||||
|
assertThat(getNodeCordappJar(notaryNodeName, cordaFinanceJarName)).exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `deploy a node with cordapp config`() {
|
||||||
|
val runner = getStandardGradleRunnerFor("DeploySingleNodeWithCordappConfig.gradle")
|
||||||
|
|
||||||
|
val result = runner.build()
|
||||||
|
|
||||||
|
assertThat(result.task(":deployNodes")!!.outcome).isEqualTo(TaskOutcome.SUCCESS)
|
||||||
|
assertThat(getNodeCordappJar(notaryNodeName, cordaFinanceJarName)).exists()
|
||||||
|
assertThat(getNodeCordappConfig(notaryNodeName, cordaFinanceJarName)).exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `deploy the locally built cordapp with cordapp config`() {
|
||||||
|
val runner = getStandardGradleRunnerFor("DeploySingleNodeWithLocallyBuildCordappAndConfig.gradle")
|
||||||
|
|
||||||
|
val result = runner.build()
|
||||||
|
|
||||||
|
assertThat(result.task(":deployNodes")!!.outcome).isEqualTo(TaskOutcome.SUCCESS)
|
||||||
|
assertThat(getNodeCordappJar(notaryNodeName, localCordappJarName)).exists()
|
||||||
|
assertThat(getNodeCordappConfig(notaryNodeName, localCordappJarName)).exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getStandardGradleRunnerFor(buildFileResourceName: String): GradleRunner {
|
||||||
|
createBuildFile(buildFileResourceName)
|
||||||
|
return GradleRunner.create()
|
||||||
|
.withProjectDir(testProjectDir.root)
|
||||||
|
.withArguments("deployNodes", "-s")
|
||||||
|
.withPluginClasspath()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createBuildFile(buildFileResourceName: String) = IOUtils.copy(javaClass.getResourceAsStream(buildFileResourceName), buildFile!!.outputStream())
|
||||||
|
private fun getNodeCordappJar(nodeName: String, cordappJarName: String) = File(testProjectDir.root, "build/nodes/$nodeName/cordapps/$cordappJarName.jar")
|
||||||
|
private fun getNodeCordappConfig(nodeName: String, cordappJarName: String) = File(testProjectDir.root, "build/nodes/$nodeName/cordapps/$cordappJarName.conf")
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
buildscript {
|
||||||
|
ext {
|
||||||
|
corda_group = 'net.corda'
|
||||||
|
corda_release_version = '3.0-SNAPSHOT' // TODO: Set to 3.0.0 when Corda 3 is released
|
||||||
|
jolokia_version = '1.3.7'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'net.corda.plugins.cordformation'
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-dev' }
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
runtime "$corda_group:corda:$corda_release_version"
|
||||||
|
runtime "$corda_group:corda-node-api:$corda_release_version"
|
||||||
|
cordapp "$corda_group:corda-finance:$corda_release_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
task deployNodes(type: net.corda.plugins.Cordform) {
|
||||||
|
node {
|
||||||
|
name 'O=Notary Service,L=Zurich,C=CH'
|
||||||
|
notary = [validating : true]
|
||||||
|
p2pPort 10002
|
||||||
|
rpcPort 10003
|
||||||
|
cordapps = ["$corda_group:corda-finance:$corda_release_version"]
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
buildscript {
|
||||||
|
ext {
|
||||||
|
corda_group = 'net.corda'
|
||||||
|
corda_release_version = '3.0-SNAPSHOT' // TODO: Set to 3.0.0 when Corda 3 is released
|
||||||
|
jolokia_version = '1.3.7'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'net.corda.plugins.cordformation'
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-dev' }
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
runtime "$corda_group:corda:$corda_release_version"
|
||||||
|
runtime "$corda_group:corda-node-api:$corda_release_version"
|
||||||
|
cordapp "$corda_group:corda-finance:$corda_release_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
task deployNodes(type: net.corda.plugins.Cordform) {
|
||||||
|
node {
|
||||||
|
name 'O=Notary Service,L=Zurich,C=CH'
|
||||||
|
notary = [validating : true]
|
||||||
|
p2pPort 10002
|
||||||
|
rpcPort 10003
|
||||||
|
cordapp "$corda_group:corda-finance:$corda_release_version", {
|
||||||
|
config "a=b"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
buildscript {
|
||||||
|
ext {
|
||||||
|
corda_group = 'net.corda'
|
||||||
|
corda_release_version = '3.0-SNAPSHOT' // TODO: Set to 3.0.0 when Corda 3 is released
|
||||||
|
jolokia_version = '1.3.7'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'net.corda.plugins.cordformation'
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven { url 'https://ci-artifactory.corda.r3cev.com/artifactory/corda-dev' }
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
runtime "$corda_group:corda:$corda_release_version"
|
||||||
|
runtime "$corda_group:corda-node-api:$corda_release_version"
|
||||||
|
cordapp "$corda_group:corda-finance:$corda_release_version"
|
||||||
|
}
|
||||||
|
|
||||||
|
jar {
|
||||||
|
baseName 'locally-built-cordapp'
|
||||||
|
}
|
||||||
|
|
||||||
|
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: [jar]) {
|
||||||
|
node {
|
||||||
|
name 'O=Notary Service,L=Zurich,C=CH'
|
||||||
|
notary = [validating : true]
|
||||||
|
p2pPort 10002
|
||||||
|
rpcPort 10003
|
||||||
|
cordapp {
|
||||||
|
config "a=b"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@ import net.corda.testing.core.DUMMY_NOTARY_NAME
|
|||||||
import net.corda.testing.core.SerializationEnvironmentRule
|
import net.corda.testing.core.SerializationEnvironmentRule
|
||||||
import net.corda.testing.core.TestIdentity
|
import net.corda.testing.core.TestIdentity
|
||||||
import net.corda.testing.internal.rigorousMock
|
import net.corda.testing.internal.rigorousMock
|
||||||
|
import net.corda.testing.node.MockCordappConfigProvider
|
||||||
import net.corda.testing.services.MockAttachmentStorage
|
import net.corda.testing.services.MockAttachmentStorage
|
||||||
import org.junit.Assert.*
|
import org.junit.Assert.*
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
@ -58,7 +59,7 @@ class AttachmentsClassLoaderStaticContractTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val serviceHub = rigorousMock<ServicesForResolution>().also {
|
private val serviceHub = rigorousMock<ServicesForResolution>().also {
|
||||||
doReturn(CordappProviderImpl(CordappLoader.createWithTestPackages(listOf("net.corda.nodeapi.internal")), MockAttachmentStorage())).whenever(it).cordappProvider
|
doReturn(CordappProviderImpl(CordappLoader.createWithTestPackages(listOf("net.corda.nodeapi.internal")), MockCordappConfigProvider(), MockAttachmentStorage())).whenever(it).cordappProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -23,6 +23,7 @@ import net.corda.testing.core.SerializationEnvironmentRule
|
|||||||
import net.corda.testing.core.TestIdentity
|
import net.corda.testing.core.TestIdentity
|
||||||
import net.corda.testing.internal.kryoSpecific
|
import net.corda.testing.internal.kryoSpecific
|
||||||
import net.corda.testing.internal.rigorousMock
|
import net.corda.testing.internal.rigorousMock
|
||||||
|
import net.corda.testing.node.MockCordappConfigProvider
|
||||||
import net.corda.testing.services.MockAttachmentStorage
|
import net.corda.testing.services.MockAttachmentStorage
|
||||||
import org.apache.commons.io.IOUtils
|
import org.apache.commons.io.IOUtils
|
||||||
import org.junit.Assert.*
|
import org.junit.Assert.*
|
||||||
@ -57,7 +58,7 @@ class AttachmentsClassLoaderTests {
|
|||||||
@JvmField
|
@JvmField
|
||||||
val testSerialization = SerializationEnvironmentRule()
|
val testSerialization = SerializationEnvironmentRule()
|
||||||
private val attachments = MockAttachmentStorage()
|
private val attachments = MockAttachmentStorage()
|
||||||
private val cordappProvider = CordappProviderImpl(CordappLoader.createDevMode(listOf(ISOLATED_CONTRACTS_JAR_PATH)), attachments)
|
private val cordappProvider = CordappProviderImpl(CordappLoader.createDevMode(listOf(ISOLATED_CONTRACTS_JAR_PATH)), MockCordappConfigProvider(), attachments)
|
||||||
private val cordapp get() = cordappProvider.cordapps.first()
|
private val cordapp get() = cordappProvider.cordapps.first()
|
||||||
private val attachmentId get() = cordappProvider.getCordappAttachmentId(cordapp)!!
|
private val attachmentId get() = cordappProvider.getCordappAttachmentId(cordapp)!!
|
||||||
private val appContext get() = cordappProvider.getAppContext(cordapp)
|
private val appContext get() = cordappProvider.getAppContext(cordapp)
|
||||||
|
@ -59,11 +59,6 @@ task buildCordaJAR(type: FatCapsule, dependsOn: project(':node').compileJava) {
|
|||||||
// If you change these flags, please also update Driver.kt
|
// If you change these flags, please also update Driver.kt
|
||||||
jvmArgs = ['-Xmx200m', '-XX:+UseG1GC']
|
jvmArgs = ['-Xmx200m', '-XX:+UseG1GC']
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make the resulting JAR file directly executable on UNIX by prepending a shell script to it.
|
|
||||||
// This lets you run the file like so: ./corda.jar
|
|
||||||
// Other than being slightly less typing, this has one big advantage: Ctrl-C works properly in the terminal.
|
|
||||||
reallyExecutable { trampolining() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
artifacts {
|
artifacts {
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
package net.corda.node
|
||||||
|
|
||||||
|
import com.typesafe.config.Config
|
||||||
|
import com.typesafe.config.ConfigException
|
||||||
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import com.typesafe.config.ConfigRenderOptions
|
||||||
|
import net.corda.node.internal.cordapp.CordappConfigFileProvider
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.Test
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
class CordappConfigFileProviderTests {
|
||||||
|
private companion object {
|
||||||
|
val cordappConfDir = File("build/tmp/cordapps/config")
|
||||||
|
val cordappName = "test"
|
||||||
|
val cordappConfFile = File(cordappConfDir, cordappName + ".conf").toPath()
|
||||||
|
|
||||||
|
val validConfig = ConfigFactory.parseString("key=value")
|
||||||
|
val alternateValidConfig = ConfigFactory.parseString("key=alternateValue")
|
||||||
|
val invalidConfig = "Invalid"
|
||||||
|
}
|
||||||
|
|
||||||
|
val provider = CordappConfigFileProvider(cordappConfDir)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test that config can be loaded`() {
|
||||||
|
writeConfig(validConfig, cordappConfFile)
|
||||||
|
assertThat(provider.getConfigByName(cordappName)).isEqualTo(validConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `config is idempotent if the underlying file is not changed`() {
|
||||||
|
writeConfig(validConfig, cordappConfFile)
|
||||||
|
assertThat(provider.getConfigByName(cordappName)).isEqualTo(validConfig)
|
||||||
|
assertThat(provider.getConfigByName(cordappName)).isEqualTo(validConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `config is not idempotent if the underlying file is changed`() {
|
||||||
|
writeConfig(validConfig, cordappConfFile)
|
||||||
|
assertThat(provider.getConfigByName(cordappName)).isEqualTo(validConfig)
|
||||||
|
|
||||||
|
writeConfig(alternateValidConfig, cordappConfFile)
|
||||||
|
assertThat(provider.getConfigByName(cordappName)).isEqualTo(alternateValidConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ConfigException.Parse::class)
|
||||||
|
fun `an invalid config throws an exception`() {
|
||||||
|
Files.write(cordappConfFile, invalidConfig.toByteArray())
|
||||||
|
|
||||||
|
provider.getConfigByName(cordappName)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the config to the path provided - will (and must) overwrite any existing config
|
||||||
|
*/
|
||||||
|
private fun writeConfig(config: Config, to: Path) = Files.write(cordappConfFile, config.root().render(ConfigRenderOptions.concise()).toByteArray())
|
||||||
|
}
|
@ -29,6 +29,7 @@ import net.corda.testing.driver.NodeHandle
|
|||||||
import net.corda.testing.driver.driver
|
import net.corda.testing.driver.driver
|
||||||
import net.corda.testing.internal.rigorousMock
|
import net.corda.testing.internal.rigorousMock
|
||||||
import net.corda.testing.internal.withoutTestSerialization
|
import net.corda.testing.internal.withoutTestSerialization
|
||||||
|
import net.corda.testing.node.MockCordappConfigProvider
|
||||||
import net.corda.testing.services.MockAttachmentStorage
|
import net.corda.testing.services.MockAttachmentStorage
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
@ -42,7 +43,7 @@ class AttachmentLoadingTests {
|
|||||||
@JvmField
|
@JvmField
|
||||||
val testSerialization = SerializationEnvironmentRule()
|
val testSerialization = SerializationEnvironmentRule()
|
||||||
private val attachments = MockAttachmentStorage()
|
private val attachments = MockAttachmentStorage()
|
||||||
private val provider = CordappProviderImpl(CordappLoader.createDevMode(listOf(isolatedJAR)), attachments)
|
private val provider = CordappProviderImpl(CordappLoader.createDevMode(listOf(isolatedJAR)), MockCordappConfigProvider(), attachments)
|
||||||
private val cordapp get() = provider.cordapps.first()
|
private val cordapp get() = provider.cordapps.first()
|
||||||
private val attachmentId get() = provider.getCordappAttachmentId(cordapp)!!
|
private val attachmentId get() = provider.getCordappAttachmentId(cordapp)!!
|
||||||
private val appContext get() = provider.getAppContext(cordapp)
|
private val appContext get() = provider.getAppContext(cordapp)
|
||||||
|
@ -32,6 +32,7 @@ import net.corda.core.utilities.debug
|
|||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.node.VersionInfo
|
import net.corda.node.VersionInfo
|
||||||
import net.corda.node.internal.classloading.requireAnnotation
|
import net.corda.node.internal.classloading.requireAnnotation
|
||||||
|
import net.corda.node.internal.cordapp.CordappConfigFileProvider
|
||||||
import net.corda.node.internal.cordapp.CordappLoader
|
import net.corda.node.internal.cordapp.CordappLoader
|
||||||
import net.corda.node.internal.cordapp.CordappProviderImpl
|
import net.corda.node.internal.cordapp.CordappProviderImpl
|
||||||
import net.corda.node.internal.cordapp.CordappProviderInternal
|
import net.corda.node.internal.cordapp.CordappProviderInternal
|
||||||
@ -539,7 +540,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
checkpointStorage = DBCheckpointStorage()
|
checkpointStorage = DBCheckpointStorage()
|
||||||
val metrics = MetricRegistry()
|
val metrics = MetricRegistry()
|
||||||
attachments = NodeAttachmentService(metrics, configuration.attachmentContentCacheSizeBytes, configuration.attachmentCacheBound)
|
attachments = NodeAttachmentService(metrics, configuration.attachmentContentCacheSizeBytes, configuration.attachmentCacheBound)
|
||||||
val cordappProvider = CordappProviderImpl(cordappLoader, attachments)
|
val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(), attachments)
|
||||||
val keyManagementService = makeKeyManagementService(identityService, keyPairs)
|
val keyManagementService = makeKeyManagementService(identityService, keyPairs)
|
||||||
_services = ServiceHubInternalImpl(
|
_services = ServiceHubInternalImpl(
|
||||||
identityService,
|
identityService,
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
package net.corda.node.internal.cordapp
|
||||||
|
|
||||||
|
import com.typesafe.config.Config
|
||||||
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import net.corda.core.internal.cordapp.CordappConfigProvider
|
||||||
|
import net.corda.core.utilities.loggerFor
|
||||||
|
import sun.plugin.dom.exception.InvalidStateException
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class CordappConfigFileProvider(val configDir: File = DEFAULT_CORDAPP_CONFIG_DIR) : CordappConfigProvider {
|
||||||
|
companion object {
|
||||||
|
val DEFAULT_CORDAPP_CONFIG_DIR = File("cordapps/config")
|
||||||
|
val CONFIG_EXT = ".conf"
|
||||||
|
val logger = loggerFor<CordappConfigFileProvider>()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
configDir.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getConfigByName(name: String): Config {
|
||||||
|
val configFile = File(configDir, name + CONFIG_EXT)
|
||||||
|
return if (configFile.exists()) {
|
||||||
|
if (configFile.isDirectory) {
|
||||||
|
throw InvalidStateException("ile at ${configFile.absolutePath} is a directory, expected a config file")
|
||||||
|
} else {
|
||||||
|
logger.info("Found config for cordapp $name in ${configFile.absolutePath}")
|
||||||
|
ConfigFactory.parseFile(configFile)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.info("No config found for cordapp $name in ${configFile.absolutePath}")
|
||||||
|
ConfigFactory.empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package net.corda.core.internal.cordapp
|
||||||
|
|
||||||
|
import com.typesafe.config.Config
|
||||||
|
|
||||||
|
interface CordappConfigProvider {
|
||||||
|
fun getConfigByName(name: String): Config
|
||||||
|
}
|
@ -5,21 +5,27 @@ import net.corda.core.contracts.ContractClassName
|
|||||||
import net.corda.core.cordapp.Cordapp
|
import net.corda.core.cordapp.Cordapp
|
||||||
import net.corda.core.cordapp.CordappContext
|
import net.corda.core.cordapp.CordappContext
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.internal.cordapp.CordappConfigProvider
|
||||||
|
import net.corda.core.internal.createCordappContext
|
||||||
import net.corda.core.node.services.AttachmentId
|
import net.corda.core.node.services.AttachmentId
|
||||||
import net.corda.core.node.services.AttachmentStorage
|
import net.corda.core.node.services.AttachmentStorage
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.loggerFor
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cordapp provider and store. For querying CorDapps for their attachment and vice versa.
|
* Cordapp provider and store. For querying CorDapps for their attachment and vice versa.
|
||||||
*/
|
*/
|
||||||
open class CordappProviderImpl(private val cordappLoader: CordappLoader, attachmentStorage: AttachmentStorage) : SingletonSerializeAsToken(), CordappProviderInternal {
|
open class CordappProviderImpl(private val cordappLoader: CordappLoader, private val cordappConfigProvider: CordappConfigProvider, attachmentStorage: AttachmentStorage) : SingletonSerializeAsToken(), CordappProviderInternal {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val log = loggerFor<CordappProviderImpl>()
|
private val log = loggerFor<CordappProviderImpl>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val contextCache = ConcurrentHashMap<Cordapp, CordappContext>()
|
||||||
|
|
||||||
|
|
||||||
override fun getAppContext(): CordappContext {
|
override fun getAppContext(): CordappContext {
|
||||||
// TODO: Use better supported APIs in Java 9
|
// TODO: Use better supported APIs in Java 9
|
||||||
Exception().stackTrace.forEach { stackFrame ->
|
Exception().stackTrace.forEach { stackFrame ->
|
||||||
@ -51,7 +57,7 @@ open class CordappProviderImpl(private val cordappLoader: CordappLoader, attachm
|
|||||||
|
|
||||||
private fun loadContractsIntoAttachmentStore(attachmentStorage: AttachmentStorage): Map<SecureHash, URL> {
|
private fun loadContractsIntoAttachmentStore(attachmentStorage: AttachmentStorage): Map<SecureHash, URL> {
|
||||||
val cordappsWithAttachments = cordapps.filter { !it.contractClassNames.isEmpty() }.map { it.jarPath }
|
val cordappsWithAttachments = cordapps.filter { !it.contractClassNames.isEmpty() }.map { it.jarPath }
|
||||||
val attachmentIds = cordappsWithAttachments.map { it.openStream().use { attachmentStorage.importOrGetAttachment(it) }}
|
val attachmentIds = cordappsWithAttachments.map { it.openStream().use { attachmentStorage.importOrGetAttachment(it) } }
|
||||||
return attachmentIds.zip(cordappsWithAttachments).toMap()
|
return attachmentIds.zip(cordappsWithAttachments).toMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +68,14 @@ open class CordappProviderImpl(private val cordappLoader: CordappLoader, attachm
|
|||||||
* @return A cordapp context for the given CorDapp
|
* @return A cordapp context for the given CorDapp
|
||||||
*/
|
*/
|
||||||
fun getAppContext(cordapp: Cordapp): CordappContext {
|
fun getAppContext(cordapp: Cordapp): CordappContext {
|
||||||
return CordappContext(cordapp, getCordappAttachmentId(cordapp), cordappLoader.appClassLoader)
|
return contextCache.computeIfAbsent(cordapp, {
|
||||||
|
createCordappContext(
|
||||||
|
cordapp,
|
||||||
|
getCordappAttachmentId(cordapp),
|
||||||
|
cordappLoader.appClassLoader,
|
||||||
|
TypesafeCordappConfig(cordappConfigProvider.getConfigByName(cordapp.name))
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,79 @@
|
|||||||
|
package net.corda.node.internal.cordapp
|
||||||
|
|
||||||
|
import com.typesafe.config.Config
|
||||||
|
import com.typesafe.config.ConfigException
|
||||||
|
import net.corda.core.cordapp.CordappConfig
|
||||||
|
import net.corda.core.cordapp.CordappConfigException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides configuration from a typesafe config source
|
||||||
|
*/
|
||||||
|
class TypesafeCordappConfig(private val cordappConfig: Config) : CordappConfig {
|
||||||
|
override fun exists(path: String): Boolean {
|
||||||
|
return cordappConfig.hasPath(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(path: String): Any {
|
||||||
|
try {
|
||||||
|
return cordappConfig.getAnyRef(path)
|
||||||
|
} catch (e: ConfigException) {
|
||||||
|
throw CordappConfigException("Cordapp configuration is incorrect due to exception", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getInt(path: String): Int {
|
||||||
|
try {
|
||||||
|
return cordappConfig.getInt(path)
|
||||||
|
} catch (e: ConfigException) {
|
||||||
|
throw CordappConfigException("Cordapp configuration is incorrect due to exception", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLong(path: String): Long {
|
||||||
|
try {
|
||||||
|
return cordappConfig.getLong(path)
|
||||||
|
} catch (e: ConfigException) {
|
||||||
|
throw CordappConfigException("Cordapp configuration is incorrect due to exception", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFloat(path: String): Float {
|
||||||
|
try {
|
||||||
|
return cordappConfig.getDouble(path).toFloat()
|
||||||
|
} catch (e: ConfigException) {
|
||||||
|
throw CordappConfigException("Cordapp configuration is incorrect due to exception", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDouble(path: String): Double {
|
||||||
|
try {
|
||||||
|
return cordappConfig.getDouble(path)
|
||||||
|
} catch (e: ConfigException) {
|
||||||
|
throw CordappConfigException("Cordapp configuration is incorrect due to exception", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getNumber(path: String): Number {
|
||||||
|
try {
|
||||||
|
return cordappConfig.getNumber(path)
|
||||||
|
} catch (e: ConfigException) {
|
||||||
|
throw CordappConfigException("Cordapp configuration is incorrect due to exception", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getString(path: String): String {
|
||||||
|
try {
|
||||||
|
return cordappConfig.getString(path)
|
||||||
|
} catch (e: ConfigException) {
|
||||||
|
throw CordappConfigException("Cordapp configuration is incorrect due to exception", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBoolean(path: String): Boolean {
|
||||||
|
try {
|
||||||
|
return cordappConfig.getBoolean(path)
|
||||||
|
} catch (e: ConfigException) {
|
||||||
|
throw CordappConfigException("Cordapp configuration is incorrect due to exception", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,27 @@
|
|||||||
package net.corda.node.internal.cordapp
|
package net.corda.node.internal.cordapp
|
||||||
|
|
||||||
|
import com.typesafe.config.Config
|
||||||
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import net.corda.core.internal.cordapp.CordappConfigProvider
|
||||||
import net.corda.core.node.services.AttachmentStorage
|
import net.corda.core.node.services.AttachmentStorage
|
||||||
|
import net.corda.testing.node.MockCordappConfigProvider
|
||||||
import net.corda.testing.services.MockAttachmentStorage
|
import net.corda.testing.services.MockAttachmentStorage
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class CordappProviderImplTests {
|
class CordappProviderImplTests {
|
||||||
companion object {
|
private companion object {
|
||||||
private val isolatedJAR = this::class.java.getResource("isolated.jar")!!
|
val isolatedJAR = this::class.java.getResource("isolated.jar")!!
|
||||||
private val emptyJAR = this::class.java.getResource("empty.jar")!!
|
// TODO: Cordapp name should differ from the JAR name
|
||||||
|
val isolatedCordappName = "isolated"
|
||||||
|
val emptyJAR = this::class.java.getResource("empty.jar")!!
|
||||||
|
val validConfig = ConfigFactory.parseString("key=value")
|
||||||
|
|
||||||
|
val stubConfigProvider = object : CordappConfigProvider {
|
||||||
|
override fun getConfigByName(name: String): Config = ConfigFactory.empty()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var attachmentStore: AttachmentStorage
|
private lateinit var attachmentStore: AttachmentStorage
|
||||||
@ -22,7 +34,7 @@ class CordappProviderImplTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `isolated jar is loaded into the attachment store`() {
|
fun `isolated jar is loaded into the attachment store`() {
|
||||||
val loader = CordappLoader.createDevMode(listOf(isolatedJAR))
|
val loader = CordappLoader.createDevMode(listOf(isolatedJAR))
|
||||||
val provider = CordappProviderImpl(loader, attachmentStore)
|
val provider = CordappProviderImpl(loader, stubConfigProvider, attachmentStore)
|
||||||
val maybeAttachmentId = provider.getCordappAttachmentId(provider.cordapps.first())
|
val maybeAttachmentId = provider.getCordappAttachmentId(provider.cordapps.first())
|
||||||
|
|
||||||
Assert.assertNotNull(maybeAttachmentId)
|
Assert.assertNotNull(maybeAttachmentId)
|
||||||
@ -32,14 +44,14 @@ class CordappProviderImplTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `empty jar is not loaded into the attachment store`() {
|
fun `empty jar is not loaded into the attachment store`() {
|
||||||
val loader = CordappLoader.createDevMode(listOf(emptyJAR))
|
val loader = CordappLoader.createDevMode(listOf(emptyJAR))
|
||||||
val provider = CordappProviderImpl(loader, attachmentStore)
|
val provider = CordappProviderImpl(loader, stubConfigProvider, attachmentStore)
|
||||||
Assert.assertNull(provider.getCordappAttachmentId(provider.cordapps.first()))
|
Assert.assertNull(provider.getCordappAttachmentId(provider.cordapps.first()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test that we find a cordapp class that is loaded into the store`() {
|
fun `test that we find a cordapp class that is loaded into the store`() {
|
||||||
val loader = CordappLoader.createDevMode(listOf(isolatedJAR))
|
val loader = CordappLoader.createDevMode(listOf(isolatedJAR))
|
||||||
val provider = CordappProviderImpl(loader, attachmentStore)
|
val provider = CordappProviderImpl(loader, stubConfigProvider, attachmentStore)
|
||||||
val className = "net.corda.finance.contracts.isolated.AnotherDummyContract"
|
val className = "net.corda.finance.contracts.isolated.AnotherDummyContract"
|
||||||
|
|
||||||
val expected = provider.cordapps.first()
|
val expected = provider.cordapps.first()
|
||||||
@ -50,9 +62,9 @@ class CordappProviderImplTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test that we find an attachment for a cordapp contrat class`() {
|
fun `test that we find an attachment for a cordapp contract class`() {
|
||||||
val loader = CordappLoader.createDevMode(listOf(isolatedJAR))
|
val loader = CordappLoader.createDevMode(listOf(isolatedJAR))
|
||||||
val provider = CordappProviderImpl(loader, attachmentStore)
|
val provider = CordappProviderImpl(loader, stubConfigProvider, attachmentStore)
|
||||||
val className = "net.corda.finance.contracts.isolated.AnotherDummyContract"
|
val className = "net.corda.finance.contracts.isolated.AnotherDummyContract"
|
||||||
val expected = provider.getAppContext(provider.cordapps.first()).attachmentId
|
val expected = provider.getAppContext(provider.cordapps.first()).attachmentId
|
||||||
val actual = provider.getContractAttachmentID(className)
|
val actual = provider.getContractAttachmentID(className)
|
||||||
@ -60,4 +72,16 @@ class CordappProviderImplTests {
|
|||||||
Assert.assertNotNull(actual)
|
Assert.assertNotNull(actual)
|
||||||
Assert.assertEquals(actual!!, expected)
|
Assert.assertEquals(actual!!, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test cordapp configuration`() {
|
||||||
|
val configProvider = MockCordappConfigProvider()
|
||||||
|
configProvider.cordappConfigs.put(isolatedCordappName, validConfig)
|
||||||
|
val loader = CordappLoader.createDevMode(listOf(isolatedJAR))
|
||||||
|
val provider = CordappProviderImpl(loader, configProvider, attachmentStore)
|
||||||
|
|
||||||
|
val expected = provider.getAppContext(provider.cordapps.first()).config
|
||||||
|
|
||||||
|
assertThat(expected.getString("key")).isEqualTo("value")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
package net.corda.node.internal.cordapp
|
||||||
|
|
||||||
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import net.corda.core.cordapp.CordappConfigException
|
||||||
|
import org.junit.Test
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
|
||||||
|
class TypesafeCordappConfigTests {
|
||||||
|
@Test
|
||||||
|
fun `test that all value types can be retrieved`() {
|
||||||
|
val config = ConfigFactory.parseString("string=string\nint=1\nfloat=1.0\ndouble=1.0\nnumber=2\ndouble=1.01\nbool=false")
|
||||||
|
val cordappConf = TypesafeCordappConfig(config)
|
||||||
|
|
||||||
|
assertThat(cordappConf.get("string")).isEqualTo("string")
|
||||||
|
assertThat(cordappConf.getString("string")).isEqualTo("string")
|
||||||
|
assertThat(cordappConf.getInt("int")).isEqualTo(1)
|
||||||
|
assertThat(cordappConf.getFloat("float")).isEqualTo(1.0F)
|
||||||
|
assertThat(cordappConf.getDouble("double")).isEqualTo(1.01)
|
||||||
|
assertThat(cordappConf.getNumber("number")).isEqualTo(2)
|
||||||
|
assertThat(cordappConf.getBoolean("bool")).isEqualTo(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test a nested path`() {
|
||||||
|
val config = ConfigFactory.parseString("outer: { inner: string }")
|
||||||
|
val cordappConf = TypesafeCordappConfig(config)
|
||||||
|
|
||||||
|
assertThat(cordappConf.getString("outer.inner")).isEqualTo("string")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test exists determines existence and lack of existence correctly`() {
|
||||||
|
val config = ConfigFactory.parseString("exists=exists")
|
||||||
|
val cordappConf = TypesafeCordappConfig(config)
|
||||||
|
|
||||||
|
assertThat(cordappConf.exists("exists")).isTrue()
|
||||||
|
assertThat(cordappConf.exists("notexists")).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = CordappConfigException::class)
|
||||||
|
fun `test that an exception is thrown when trying to access a non-extant field`() {
|
||||||
|
val config = ConfigFactory.empty()
|
||||||
|
val cordappConf = TypesafeCordappConfig(config)
|
||||||
|
|
||||||
|
cordappConf.get("anything")
|
||||||
|
}
|
||||||
|
}
|
@ -2,13 +2,13 @@ package net.corda.bank
|
|||||||
|
|
||||||
import net.corda.finance.DOLLARS
|
import net.corda.finance.DOLLARS
|
||||||
import net.corda.finance.POUNDS
|
import net.corda.finance.POUNDS
|
||||||
import net.corda.testing.node.internal.demorun.deployNodesThen
|
import net.corda.testing.node.internal.demorun.nodeRunner
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class BankOfCordaCordformTest {
|
class BankOfCordaCordformTest {
|
||||||
@Test
|
@Test
|
||||||
fun `run demo`() {
|
fun `run demo`() {
|
||||||
BankOfCordaCordform().deployNodesThen {
|
BankOfCordaCordform().nodeRunner().scanPackages(listOf("net.corda.finance")).deployAndRunNodesThen {
|
||||||
IssueCash.requestWebIssue(30000.POUNDS)
|
IssueCash.requestWebIssue(30000.POUNDS)
|
||||||
IssueCash.requestRpcIssue(20000.DOLLARS)
|
IssueCash.requestRpcIssue(20000.DOLLARS)
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,8 @@ private const val BOC_RPC_ADMIN_PORT = 10015
|
|||||||
private const val BOC_WEB_PORT = 10007
|
private const val BOC_WEB_PORT = 10007
|
||||||
|
|
||||||
class BankOfCordaCordform : CordformDefinition() {
|
class BankOfCordaCordform : CordformDefinition() {
|
||||||
|
// TODO: Readd finance dependency - will fail without it
|
||||||
init {
|
init {
|
||||||
cordappPackages += "net.corda.finance"
|
|
||||||
node {
|
node {
|
||||||
name(NOTARY_NAME)
|
name(NOTARY_NAME)
|
||||||
notary(NotaryConfig(validating = true))
|
notary(NotaryConfig(validating = true))
|
||||||
@ -65,7 +65,7 @@ class BankOfCordaCordform : CordformDefinition() {
|
|||||||
object DeployNodes {
|
object DeployNodes {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
BankOfCordaCordform().deployNodes()
|
BankOfCordaCordform().nodeRunner().scanPackages(listOf("net.corda.finance")).deployAndRunNodes()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
23
samples/cordapp-configuration/README.md
Normal file
23
samples/cordapp-configuration/README.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Cordapp Configuration Sample
|
||||||
|
|
||||||
|
This sample shows a simple example of how to use per-cordapp configuration. It includes;
|
||||||
|
|
||||||
|
* A configuration file
|
||||||
|
* Gradle build file to show how to install your Cordapp configuration
|
||||||
|
* A flow that consumes the Cordapp configuration
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
To run the sample you must first build it from the project root with;
|
||||||
|
|
||||||
|
./gradlew deployNodes
|
||||||
|
|
||||||
|
This will deploy the node with the configuration installed.
|
||||||
|
The relevant section is the ``deployNodes`` task.
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
* Windows: `build\nodes\runnodes`
|
||||||
|
* Mac/Linux: `./build/nodes/runnodes`
|
||||||
|
|
||||||
|
Once the nodes have started up and show a prompt you can now run your flow.
|
54
samples/cordapp-configuration/build.gradle
Normal file
54
samples/cordapp-configuration/build.gradle
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
apply plugin: 'kotlin'
|
||||||
|
apply plugin: 'java'
|
||||||
|
apply plugin: 'net.corda.plugins.cordapp'
|
||||||
|
apply plugin: 'net.corda.plugins.cordformation'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
cordaCompile project(":core")
|
||||||
|
cordaCompile project(":node-api")
|
||||||
|
cordaCompile project(path: ":node:capsule", configuration: 'runtimeArtifacts')
|
||||||
|
cordaCompile project(path: ":webserver:webcapsule", configuration: 'runtimeArtifacts')
|
||||||
|
}
|
||||||
|
|
||||||
|
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||||
|
ext.rpcUsers = [['username': "default", 'password': "default", 'permissions': [ 'ALL' ]]]
|
||||||
|
|
||||||
|
directory "./build/nodes"
|
||||||
|
node {
|
||||||
|
name "O=Notary Service,L=Zurich,C=CH"
|
||||||
|
notary = [validating : true]
|
||||||
|
p2pPort 10002
|
||||||
|
rpcSettings {
|
||||||
|
port 10003
|
||||||
|
adminPort 10004
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node {
|
||||||
|
name "O=Bank A,L=London,C=GB"
|
||||||
|
p2pPort 10005
|
||||||
|
cordapps = []
|
||||||
|
rpcUsers = ext.rpcUsers
|
||||||
|
// This configures the default cordapp for this node
|
||||||
|
projectCordapp {
|
||||||
|
config "someStringValue=test"
|
||||||
|
}
|
||||||
|
rpcSettings {
|
||||||
|
port 10007
|
||||||
|
adminPort 10008
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node {
|
||||||
|
name "O=Bank B,L=New York,C=US"
|
||||||
|
p2pPort 10009
|
||||||
|
cordapps = []
|
||||||
|
rpcUsers = ext.rpcUsers
|
||||||
|
// This configures the default cordapp for this node
|
||||||
|
projectCordapp {
|
||||||
|
config project.file("src/config.conf")
|
||||||
|
}
|
||||||
|
rpcSettings {
|
||||||
|
port 10011
|
||||||
|
adminPort 10012
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
samples/cordapp-configuration/src/config.conf
Normal file
5
samples/cordapp-configuration/src/config.conf
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
someStringValue=hello world
|
||||||
|
someIntValue=1
|
||||||
|
nested: {
|
||||||
|
value: a string
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package net.corda.configsample
|
||||||
|
|
||||||
|
import net.corda.core.flows.FlowLogic
|
||||||
|
|
||||||
|
class ConfigSampleFlow : FlowLogic<String>() {
|
||||||
|
override fun call(): String {
|
||||||
|
val config = serviceHub.getAppContext().config
|
||||||
|
return config.getString("someStringValue")
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@ import net.corda.testing.core.ALICE_NAME
|
|||||||
import net.corda.testing.core.BOB_NAME
|
import net.corda.testing.core.BOB_NAME
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
|
||||||
fun main(args: Array<String>) = BFTNotaryCordform().deployNodes()
|
fun main(args: Array<String>) = BFTNotaryCordform().nodeRunner().deployAndRunNodes()
|
||||||
|
|
||||||
private val clusterSize = 4 // Minimum size that tolerates a faulty replica.
|
private val clusterSize = 4 // Minimum size that tolerates a faulty replica.
|
||||||
private val notaryNames = createNotaryNames(clusterSize)
|
private val notaryNames = createNotaryNames(clusterSize)
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package net.corda.notarydemo
|
package net.corda.notarydemo
|
||||||
|
|
||||||
import net.corda.testing.node.internal.demorun.clean
|
import net.corda.testing.node.internal.demorun.nodeRunner
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
listOf(SingleNotaryCordform(), RaftNotaryCordform(), BFTNotaryCordform()).forEach {
|
listOf(SingleNotaryCordform(), RaftNotaryCordform(), BFTNotaryCordform()).map { it.nodeRunner() }.forEach {
|
||||||
it.clean()
|
it.clean()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import net.corda.testing.core.BOB_NAME
|
|||||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
|
||||||
fun main(args: Array<String>) = CustomNotaryCordform().deployNodes()
|
fun main(args: Array<String>) = CustomNotaryCordform().nodeRunner().deployAndRunNodes()
|
||||||
|
|
||||||
class CustomNotaryCordform : CordformDefinition() {
|
class CustomNotaryCordform : CordformDefinition() {
|
||||||
init {
|
init {
|
||||||
|
@ -13,7 +13,7 @@ import net.corda.testing.core.ALICE_NAME
|
|||||||
import net.corda.testing.core.BOB_NAME
|
import net.corda.testing.core.BOB_NAME
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
|
||||||
fun main(args: Array<String>) = RaftNotaryCordform().deployNodes()
|
fun main(args: Array<String>) = RaftNotaryCordform().nodeRunner().deployAndRunNodes()
|
||||||
|
|
||||||
internal fun createNotaryNames(clusterSize: Int) = (0 until clusterSize).map { CordaX500Name("Notary Service $it", "Zurich", "CH") }
|
internal fun createNotaryNames(clusterSize: Int) = (0 until clusterSize).map { CordaX500Name("Notary Service $it", "Zurich", "CH") }
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import net.corda.testing.node.User
|
|||||||
import net.corda.testing.node.internal.demorun.*
|
import net.corda.testing.node.internal.demorun.*
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
|
||||||
fun main(args: Array<String>) = SingleNotaryCordform().deployNodes()
|
fun main(args: Array<String>) = SingleNotaryCordform().nodeRunner().deployAndRunNodes()
|
||||||
|
|
||||||
val notaryDemoUser = User("demou", "demop", setOf(all()))
|
val notaryDemoUser = User("demou", "demop", setOf(all()))
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
|||||||
name "O=Notary Service,L=Zurich,C=CH"
|
name "O=Notary Service,L=Zurich,C=CH"
|
||||||
notary = [validating : true]
|
notary = [validating : true]
|
||||||
p2pPort 10002
|
p2pPort 10002
|
||||||
cordapps = ["$project.group:finance:$corda_release_version"]
|
cordapp project(':finance')
|
||||||
extraConfig = [
|
extraConfig = [
|
||||||
jvmArgs : [ "-Xmx1g"]
|
jvmArgs : [ "-Xmx1g"]
|
||||||
]
|
]
|
||||||
@ -81,7 +81,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
|||||||
address("localhost:10016")
|
address("localhost:10016")
|
||||||
adminAddress("localhost:10017")
|
adminAddress("localhost:10017")
|
||||||
}
|
}
|
||||||
cordapps = ["$project.group:finance:$corda_release_version"]
|
cordapp project(':finance')
|
||||||
rpcUsers = ext.rpcUsers
|
rpcUsers = ext.rpcUsers
|
||||||
extraConfig = [
|
extraConfig = [
|
||||||
jvmArgs : [ "-Xmx1g"]
|
jvmArgs : [ "-Xmx1g"]
|
||||||
@ -95,7 +95,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
|||||||
address("localhost:10026")
|
address("localhost:10026")
|
||||||
adminAddress("localhost:10027")
|
adminAddress("localhost:10027")
|
||||||
}
|
}
|
||||||
cordapps = ["$project.group:finance:$corda_release_version"]
|
cordapp project(':finance')
|
||||||
rpcUsers = ext.rpcUsers
|
rpcUsers = ext.rpcUsers
|
||||||
extraConfig = [
|
extraConfig = [
|
||||||
jvmArgs : [ "-Xmx1g"]
|
jvmArgs : [ "-Xmx1g"]
|
||||||
@ -109,7 +109,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
|||||||
address("localhost:10036")
|
address("localhost:10036")
|
||||||
adminAddress("localhost:10037")
|
adminAddress("localhost:10037")
|
||||||
}
|
}
|
||||||
cordapps = ["$project.group:finance:$corda_release_version"]
|
cordapp project(':finance')
|
||||||
rpcUsers = ext.rpcUsers
|
rpcUsers = ext.rpcUsers
|
||||||
extraConfig = [
|
extraConfig = [
|
||||||
jvmArgs : [ "-Xmx1g"]
|
jvmArgs : [ "-Xmx1g"]
|
||||||
|
@ -46,3 +46,4 @@ include 'samples:network-visualiser'
|
|||||||
include 'samples:simm-valuation-demo'
|
include 'samples:simm-valuation-demo'
|
||||||
include 'samples:notary-demo'
|
include 'samples:notary-demo'
|
||||||
include 'samples:bank-of-corda-demo'
|
include 'samples:bank-of-corda-demo'
|
||||||
|
include 'samples:cordapp-configuration'
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
package net.corda.testing.node.internal.demorun
|
||||||
|
|
||||||
|
import net.corda.cordform.CordformDefinition
|
||||||
|
import net.corda.cordform.CordformNode
|
||||||
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
|
import net.corda.core.utilities.getOrThrow
|
||||||
|
import net.corda.testing.driver.JmxPolicy
|
||||||
|
import net.corda.testing.driver.PortAllocation
|
||||||
|
import net.corda.testing.node.internal.internalDriver
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a demo runner for this cordform definition
|
||||||
|
*/
|
||||||
|
fun CordformDefinition.nodeRunner() = CordformNodeRunner(this)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A node runner creates and runs nodes for a given [[CordformDefinition]].
|
||||||
|
*/
|
||||||
|
class CordformNodeRunner(val cordformDefinition: CordformDefinition) {
|
||||||
|
private var extraPackagesToScan = emptyList<String>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder method to sets the extra cordapp scan packages
|
||||||
|
*/
|
||||||
|
fun scanPackages(packages: List<String>): CordformNodeRunner {
|
||||||
|
extraPackagesToScan = packages
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clean() {
|
||||||
|
System.err.println("Deleting: ${cordformDefinition.nodesDirectory}")
|
||||||
|
cordformDefinition.nodesDirectory.toFile().deleteRecursively()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deploy the nodes specified in the given [CordformDefinition]. This will block until all the nodes and webservers
|
||||||
|
* have terminated.
|
||||||
|
*/
|
||||||
|
fun deployAndRunNodes() {
|
||||||
|
runNodes(waitForAllNodesToFinish = true) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deploy the nodes specified in the given [CordformDefinition] and then execute the given [block] once all the nodes
|
||||||
|
* and webservers are up. After execution all these processes will be terminated.
|
||||||
|
*/
|
||||||
|
fun deployAndRunNodesThen(block: () -> Unit) {
|
||||||
|
runNodes(waitForAllNodesToFinish = false, block = block)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun runNodes(waitForAllNodesToFinish: Boolean, block: () -> Unit) {
|
||||||
|
clean()
|
||||||
|
val nodes = cordformDefinition.nodeConfigurers.map { configurer -> CordformNode().also { configurer.accept(it) } }
|
||||||
|
val maxPort = nodes
|
||||||
|
.flatMap { listOf(it.p2pAddress, it.rpcAddress, it.webAddress) }
|
||||||
|
.mapNotNull { address -> address?.let { NetworkHostAndPort.parse(it).port } }
|
||||||
|
.max()!!
|
||||||
|
internalDriver(
|
||||||
|
isDebug = true,
|
||||||
|
jmxPolicy = JmxPolicy(true),
|
||||||
|
driverDirectory = cordformDefinition.nodesDirectory,
|
||||||
|
extraCordappPackagesToScan = extraPackagesToScan,
|
||||||
|
// Notaries are manually specified in Cordform so we don't want the driver automatically starting any
|
||||||
|
notarySpecs = emptyList(),
|
||||||
|
// Start from after the largest port used to prevent port clash
|
||||||
|
portAllocation = PortAllocation.Incremental(maxPort + 1),
|
||||||
|
waitForAllNodesToFinish = waitForAllNodesToFinish
|
||||||
|
) {
|
||||||
|
cordformDefinition.setup(this)
|
||||||
|
startCordformNodes(nodes).getOrThrow() // Only proceed once everything is up and running
|
||||||
|
println("All nodes and webservers are ready...")
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
|||||||
@file:JvmName("DemoRunner")
|
|
||||||
|
|
||||||
package net.corda.testing.node.internal.demorun
|
|
||||||
|
|
||||||
import net.corda.cordform.CordformDefinition
|
|
||||||
import net.corda.cordform.CordformNode
|
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
|
||||||
import net.corda.core.utilities.getOrThrow
|
|
||||||
import net.corda.testing.driver.JmxPolicy
|
|
||||||
import net.corda.testing.driver.PortAllocation
|
|
||||||
import net.corda.testing.node.internal.internalDriver
|
|
||||||
|
|
||||||
fun CordformDefinition.clean() {
|
|
||||||
System.err.println("Deleting: $nodesDirectory")
|
|
||||||
nodesDirectory.toFile().deleteRecursively()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deploy the nodes specified in the given [CordformDefinition]. This will block until all the nodes and webservers
|
|
||||||
* have terminated.
|
|
||||||
*/
|
|
||||||
fun CordformDefinition.deployNodes() {
|
|
||||||
runNodes(waitForAllNodesToFinish = true) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deploy the nodes specified in the given [CordformDefinition] and then execute the given [block] once all the nodes
|
|
||||||
* and webservers are up. After execution all these processes will be terminated.
|
|
||||||
*/
|
|
||||||
fun CordformDefinition.deployNodesThen(block: () -> Unit) {
|
|
||||||
runNodes(waitForAllNodesToFinish = false, block = block)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun CordformDefinition.runNodes(waitForAllNodesToFinish: Boolean, block: () -> Unit) {
|
|
||||||
clean()
|
|
||||||
val nodes = nodeConfigurers.map { configurer -> CordformNode().also { configurer.accept(it) } }
|
|
||||||
val maxPort = nodes
|
|
||||||
.flatMap { listOf(it.p2pAddress, it.rpcAddress, it.webAddress) }
|
|
||||||
.mapNotNull { address -> address?.let { NetworkHostAndPort.parse(it).port } }
|
|
||||||
.max()!!
|
|
||||||
internalDriver(
|
|
||||||
isDebug = true,
|
|
||||||
jmxPolicy = JmxPolicy(true),
|
|
||||||
driverDirectory = nodesDirectory,
|
|
||||||
extraCordappPackagesToScan = cordappPackages,
|
|
||||||
// Notaries are manually specified in Cordform so we don't want the driver automatically starting any
|
|
||||||
notarySpecs = emptyList(),
|
|
||||||
// Start from after the largest port used to prevent port clash
|
|
||||||
portAllocation = PortAllocation.Incremental(maxPort + 1),
|
|
||||||
waitForAllNodesToFinish = waitForAllNodesToFinish
|
|
||||||
) {
|
|
||||||
setup(this)
|
|
||||||
startCordformNodes(nodes).getOrThrow() // Only proceed once everything is up and running
|
|
||||||
println("All nodes and webservers are ready...")
|
|
||||||
block()
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,17 @@
|
|||||||
|
package net.corda.testing.node
|
||||||
|
|
||||||
|
import com.typesafe.config.Config
|
||||||
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import net.corda.core.internal.cordapp.CordappConfigProvider
|
||||||
|
|
||||||
|
class MockCordappConfigProvider : CordappConfigProvider {
|
||||||
|
val cordappConfigs = mutableMapOf<String, Config> ()
|
||||||
|
|
||||||
|
override fun getConfigByName(name: String): Config {
|
||||||
|
return if(cordappConfigs.containsKey(name)) {
|
||||||
|
cordappConfigs[name]!!
|
||||||
|
} else {
|
||||||
|
ConfigFactory.empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,10 +7,17 @@ import net.corda.core.node.services.AttachmentId
|
|||||||
import net.corda.core.node.services.AttachmentStorage
|
import net.corda.core.node.services.AttachmentStorage
|
||||||
import net.corda.node.internal.cordapp.CordappLoader
|
import net.corda.node.internal.cordapp.CordappLoader
|
||||||
import net.corda.node.internal.cordapp.CordappProviderImpl
|
import net.corda.node.internal.cordapp.CordappProviderImpl
|
||||||
|
import net.corda.testing.node.MockCordappConfigProvider
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class MockCordappProvider(cordappLoader: CordappLoader, attachmentStorage: AttachmentStorage) : CordappProviderImpl(cordappLoader, attachmentStorage) {
|
class MockCordappProvider(
|
||||||
|
cordappLoader: CordappLoader,
|
||||||
|
attachmentStorage: AttachmentStorage,
|
||||||
|
val cordappConfigProvider: MockCordappConfigProvider = MockCordappConfigProvider()
|
||||||
|
) : CordappProviderImpl(cordappLoader, cordappConfigProvider, attachmentStorage) {
|
||||||
|
constructor(cordappLoader: CordappLoader, attachmentStorage: AttachmentStorage) : this(cordappLoader, attachmentStorage, MockCordappConfigProvider())
|
||||||
|
|
||||||
val cordappRegistry = mutableListOf<Pair<Cordapp, AttachmentId>>()
|
val cordappRegistry = mutableListOf<Pair<Cordapp, AttachmentId>>()
|
||||||
|
|
||||||
fun addMockCordapp(contractClassName: ContractClassName, attachments: MockAttachmentStorage) {
|
fun addMockCordapp(contractClassName: ContractClassName, attachments: MockAttachmentStorage) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user