mirror of
https://github.com/corda/corda.git
synced 2025-02-04 02:01:13 +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()
|
||||
##
|
||||
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.NotNull public final ClassLoader getClassLoader()
|
||||
@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_main" 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_main" 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
|
||||
platformVersion=2
|
||||
guavaVersion=21.0
|
||||
@ -6,4 +6,4 @@ bouncycastleVersion=1.57
|
||||
typesafeConfigVersion=1.3.1
|
||||
jsr305Version=3.0.2
|
||||
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
|
||||
|
||||
// TODO: Add per app config
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@ -15,5 +13,11 @@ import net.corda.core.crypto.SecureHash
|
||||
* @property attachmentId For CorDapps containing [Contract] or [UpgradedContract] implementations this will be populated
|
||||
* with the attachment containing those class files
|
||||
* @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
|
||||
|
||||
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.crypto.*
|
||||
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 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.contracts.*
|
||||
import net.corda.core.cordapp.CordappContext
|
||||
import net.corda.core.cordapp.CordappProvider
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SignableData
|
||||
@ -369,4 +370,9 @@ interface ServiceHub : ServicesForResolution {
|
||||
* node starts.
|
||||
*/
|
||||
fun registerUnloadHandler(runOnStop: () -> Unit)
|
||||
|
||||
/**
|
||||
* See [CordappProvider.getAppContext]
|
||||
*/
|
||||
fun getAppContext(): CordappContext = cordappProvider.getAppContext()
|
||||
}
|
||||
|
@ -7,6 +7,10 @@ from the previous milestone release.
|
||||
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.
|
||||
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.
|
||||
@ -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
|
||||
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
|
||||
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:
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
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: 'kotlin'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'net.corda.plugins.publish-utils'
|
||||
apply plugin: 'com.jfrog.artifactory'
|
||||
@ -7,11 +8,9 @@ repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
// This tracks the gradle plugins version and not Corda
|
||||
version gradle_plugins_version
|
||||
group 'net.corda.plugins'
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||
|
||||
// JSR 305: Nullability annotations
|
||||
compile "com.google.code.findbugs:jsr305:$jsr305_version"
|
||||
|
||||
|
@ -10,7 +10,12 @@ import java.util.function.Consumer;
|
||||
public abstract class CordformDefinition {
|
||||
private Path nodesDirectory = Paths.get("build", "nodes");
|
||||
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() {
|
||||
return nodesDirectory;
|
||||
@ -28,8 +33,11 @@ public abstract class CordformDefinition {
|
||||
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: 'java-gradle-plugin'
|
||||
apply plugin: 'net.corda.plugins.publish-utils'
|
||||
apply plugin: 'com.jfrog.artifactory'
|
||||
|
||||
@ -33,18 +34,22 @@ sourceSets {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile gradleApi()
|
||||
gradleApi()
|
||||
|
||||
compile project(":cordapp")
|
||||
compile project(':cordform-common')
|
||||
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"
|
||||
|
||||
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
|
||||
compile "org.yaml:snakeyaml:$snake_yaml_version"
|
||||
}
|
||||
|
||||
task createNodeRunner(type: Jar, dependsOn: [classes]) {
|
||||
task createNodeRunner(type: Jar) {
|
||||
manifest {
|
||||
attributes('Main-Class': 'net.corda.plugins.NodeRunnerKt')
|
||||
}
|
||||
@ -53,12 +58,12 @@ task createNodeRunner(type: Jar, dependsOn: [classes]) {
|
||||
from sourceSets.runnodes.output
|
||||
}
|
||||
|
||||
jar {
|
||||
publish {
|
||||
name project.name
|
||||
}
|
||||
|
||||
processResources {
|
||||
from(createNodeRunner) {
|
||||
rename { 'net/corda/plugins/runnodes.jar' }
|
||||
}
|
||||
}
|
||||
|
||||
publish {
|
||||
name project.name
|
||||
}
|
||||
|
@ -2,11 +2,9 @@ package net.corda.plugins
|
||||
|
||||
import groovy.lang.Closure
|
||||
import net.corda.cordform.CordformDefinition
|
||||
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 java.io.File
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.net.URLClassLoader
|
||||
@ -111,20 +109,27 @@ open class Baseform : DefaultTask() {
|
||||
}
|
||||
}
|
||||
|
||||
protected fun initializeConfiguration() {
|
||||
internal fun initializeConfiguration() {
|
||||
if (definitionClass != null) {
|
||||
val cd = loadCordformDefinition()
|
||||
// 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
|
||||
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 = cd.nodesDirectory
|
||||
val cordapps = cd.getMatchingCordapps()
|
||||
val cordapps = cd.cordappDependencies
|
||||
cd.nodeConfigurers.forEach {
|
||||
val node = 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)
|
||||
}
|
||||
cd.setup { nodeName -> project.projectDir.toPath().resolve(getNodeByName(nodeName)!!.nodeDir.toPath()) }
|
||||
@ -134,7 +139,6 @@ open class Baseform : DefaultTask() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun bootstrapNetwork() {
|
||||
val networkBootstrapperClass = loadNetworkBootstrapperClass()
|
||||
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 {
|
||||
JarInputStream(inputStream()).use {
|
||||
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
|
||||
|
||||
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 java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
@ -15,18 +12,25 @@ import java.nio.file.Paths
|
||||
*/
|
||||
@Suppress("unused")
|
||||
open class Cordform : Baseform() {
|
||||
private companion object {
|
||||
internal companion object {
|
||||
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.
|
||||
*/
|
||||
private fun installRunScript() {
|
||||
project.copy {
|
||||
it.apply {
|
||||
from(Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.jar"))
|
||||
from(Cordformation.getPluginFile(project, "runnodes.jar"))
|
||||
fileMode = Cordformation.executableFileMode
|
||||
into("$directory/")
|
||||
}
|
||||
@ -34,7 +38,7 @@ open class Cordform : Baseform() {
|
||||
|
||||
project.copy {
|
||||
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.
|
||||
filter(mapOf("eol" to FixCrLfFilter.CrLf.newInstance("lf")), FixCrLfFilter::class.java)
|
||||
fileMode = Cordformation.executableFileMode
|
||||
@ -44,7 +48,7 @@ open class Cordform : Baseform() {
|
||||
|
||||
project.copy {
|
||||
it.apply {
|
||||
from(Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.bat"))
|
||||
from(Cordformation.getPluginFile(project, "runnodes.bat"))
|
||||
into("$directory/")
|
||||
}
|
||||
}
|
||||
@ -63,4 +67,5 @@ open class Cordform : Baseform() {
|
||||
bootstrapNetwork()
|
||||
nodes.forEach(Node::build)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package net.corda.plugins
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
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,
|
||||
@ -13,19 +14,19 @@ class Cordformation : Plugin<Project> {
|
||||
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.
|
||||
* @return A file handle to the file in the JAR.
|
||||
*/
|
||||
fun getPluginFile(project: Project, filePathInJar: String): File {
|
||||
val archive = project.rootProject.buildscript.configurations
|
||||
.single { it.name == "classpath" }
|
||||
.first { it.name.contains("cordformation") }
|
||||
return project.rootProject.resources.text
|
||||
.fromArchiveEntry(archive, filePathInJar)
|
||||
.asFile()
|
||||
val tmpDir = File(project.buildDir, "tmp")
|
||||
val outputFile = File(tmpDir, filePathInJar)
|
||||
tmpDir.mkdir()
|
||||
outputFile.outputStream().use {
|
||||
Cordformation::class.java.getResourceAsStream(filePathInJar).copyTo(it)
|
||||
}
|
||||
return outputFile
|
||||
}
|
||||
|
||||
/**
|
||||
@ -38,7 +39,7 @@ class Cordformation : Plugin<Project> {
|
||||
fun verifyAndGetRuntimeJar(project: Project, jarName: String): File {
|
||||
val releaseVersion = project.rootProject.ext<String>("corda_release_version")
|
||||
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) {
|
||||
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
|
||||
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import com.typesafe.config.ConfigObject
|
||||
import com.typesafe.config.ConfigRenderOptions
|
||||
import com.typesafe.config.ConfigValueFactory
|
||||
import com.typesafe.config.ConfigObject
|
||||
import groovy.lang.Closure
|
||||
import net.corda.cordform.CordformNode
|
||||
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.artifacts.ProjectDependency
|
||||
import java.io.File
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
@JvmStatic
|
||||
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 Type is any due to gradle's use of "GStrings" - each value will have "toString" called on it
|
||||
*/
|
||||
var cordapps = mutableListOf<Any>()
|
||||
internal var additionalCordapps = mutableListOf<File>()
|
||||
var cordapps: MutableList<Any>
|
||||
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
|
||||
private set
|
||||
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
|
||||
*/
|
||||
fun sshdPort(sshdPort: Int?) {
|
||||
config = config.withValue("sshd.port", ConfigValueFactory.fromAnyRef(sshdPort))
|
||||
fun sshdPort(sshdPort: Int) {
|
||||
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()
|
||||
}
|
||||
installAgentJar()
|
||||
installBuiltCordapp()
|
||||
installCordapps()
|
||||
installConfig()
|
||||
}
|
||||
|
||||
internal fun buildDocker() {
|
||||
@ -109,7 +199,6 @@ class Node(private val project: Project) : CordformNode() {
|
||||
}
|
||||
}
|
||||
installAgentJar()
|
||||
installBuiltCordapp()
|
||||
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
|
||||
*/
|
||||
@ -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 {
|
||||
val options = ConfigRenderOptions
|
||||
.defaults()
|
||||
@ -217,7 +301,7 @@ class Node(private val project: Project) : CordformNode() {
|
||||
/**
|
||||
* Installs the configuration file to the root directory and detokenises it.
|
||||
*/
|
||||
internal fun installConfig() {
|
||||
fun installConfig() {
|
||||
configureProperties()
|
||||
val tmpConfFile = createTempConfigFile(config.root())
|
||||
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() {
|
||||
additionalCordapps.addAll(getCordappList())
|
||||
private fun installCordapps() {
|
||||
val cordapps = getCordappList()
|
||||
val cordappsDir = File(nodeDir, "cordapps")
|
||||
project.copy {
|
||||
it.apply {
|
||||
from(additionalCordapps)
|
||||
into(cordappsDir)
|
||||
from(cordapps.map { it.jarFile })
|
||||
into(project.file(cordappsDir))
|
||||
}
|
||||
}
|
||||
|
||||
installCordappConfigs(cordapps)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets a list of cordapps based on what dependent cordapps were specified.
|
||||
*
|
||||
* @return List of this node's cordapps.
|
||||
*/
|
||||
private fun getCordappList(): Collection<File> {
|
||||
// Cordapps can sometimes contain a GString instance which fails the equality test with the Java string
|
||||
@Suppress("RemoveRedundantCallsOfConversionMethods")
|
||||
val cordapps: List<String> = cordapps.map { it.toString() }
|
||||
return project.configuration("cordapp").files {
|
||||
cordapps.contains(it.group + ":" + it.name + ":" + it.version)
|
||||
private fun getCordappList(): Collection<ResolvedCordapp> =
|
||||
internalCordapps.map { cordapp -> resolveCordapp(cordapp) } + resolveBuiltCordapp()
|
||||
|
||||
private fun resolveCordapp(cordapp: Cordapp): ResolvedCordapp {
|
||||
val cordappConfiguration = project.configuration("cordapp")
|
||||
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>)
|
||||
: 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
|
||||
}
|
||||
|
||||
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) {
|
||||
override fun processBuilder() = ProcessBuilder(when (os) {
|
||||
OS.MACOS -> {
|
||||
listOf("osascript", "-e", """tell app "Terminal"
|
||||
override fun processBuilder(): ProcessBuilder {
|
||||
val params = when (os) {
|
||||
OS.MACOS -> {
|
||||
listOf("osascript", "-e", """tell app "Terminal"
|
||||
activate
|
||||
delay 0.5
|
||||
tell app "System Events" to tell process "Terminal" to keystroke "t" using command down
|
||||
delay 0.5
|
||||
do script "bash -c 'cd \"$dir\" ; \"${command.joinToString("""\" \"""")}\" && exit'" in selected tab of the front window
|
||||
end tell""")
|
||||
}
|
||||
OS.WINDOWS -> {
|
||||
listOf("cmd", "/C", "start ${command.joinToString(" ") { windowsSpaceEscape(it) }}")
|
||||
}
|
||||
OS.LINUX -> {
|
||||
// Start shell to keep window open unless java terminated normally or due to SIGTERM:
|
||||
val command = "${unixCommand()}; [ $? -eq 0 -o $? -eq 143 ] || sh"
|
||||
if (isTmux()) {
|
||||
listOf("tmux", "new-window", "-n", nodeName, command)
|
||||
} else {
|
||||
listOf("xterm", "-T", nodeName, "-e", command)
|
||||
}
|
||||
OS.WINDOWS -> {
|
||||
listOf("cmd", "/C", "start ${command.joinToString(" ") { windowsSpaceEscape(it) }}")
|
||||
}
|
||||
OS.LINUX -> {
|
||||
// Start shell to keep window open unless java terminated normally or due to SIGTERM:
|
||||
val command = "${unixCommand()}; [ $? -eq 0 -o $? -eq 143 ] || sh"
|
||||
if (isTmux()) {
|
||||
listOf("tmux", "new-window", "-n", nodeName, command)
|
||||
} else {
|
||||
listOf("xterm", "-T", nodeName, "-e", command)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
println("Running command: ${params.joinToString(" ")}")
|
||||
return ProcessBuilder(params)
|
||||
}
|
||||
|
||||
private fun unixCommand() = command.map(::quotedFormOf).joinToString(" ")
|
||||
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.TestIdentity
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.node.MockCordappConfigProvider
|
||||
import net.corda.testing.services.MockAttachmentStorage
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Rule
|
||||
@ -58,7 +59,7 @@ class AttachmentsClassLoaderStaticContractTests {
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -23,6 +23,7 @@ import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.core.TestIdentity
|
||||
import net.corda.testing.internal.kryoSpecific
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import net.corda.testing.node.MockCordappConfigProvider
|
||||
import net.corda.testing.services.MockAttachmentStorage
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.junit.Assert.*
|
||||
@ -57,7 +58,7 @@ class AttachmentsClassLoaderTests {
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
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 attachmentId get() = cordappProvider.getCordappAttachmentId(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
|
||||
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 {
|
||||
|
@ -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.internal.rigorousMock
|
||||
import net.corda.testing.internal.withoutTestSerialization
|
||||
import net.corda.testing.node.MockCordappConfigProvider
|
||||
import net.corda.testing.services.MockAttachmentStorage
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Rule
|
||||
@ -42,7 +43,7 @@ class AttachmentLoadingTests {
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
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 attachmentId get() = provider.getCordappAttachmentId(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.node.VersionInfo
|
||||
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.CordappProviderImpl
|
||||
import net.corda.node.internal.cordapp.CordappProviderInternal
|
||||
@ -539,7 +540,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
||||
checkpointStorage = DBCheckpointStorage()
|
||||
val metrics = MetricRegistry()
|
||||
attachments = NodeAttachmentService(metrics, configuration.attachmentContentCacheSizeBytes, configuration.attachmentCacheBound)
|
||||
val cordappProvider = CordappProviderImpl(cordappLoader, attachments)
|
||||
val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(), attachments)
|
||||
val keyManagementService = makeKeyManagementService(identityService, keyPairs)
|
||||
_services = ServiceHubInternalImpl(
|
||||
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.CordappContext
|
||||
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.AttachmentStorage
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import java.net.URL
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
private val log = loggerFor<CordappProviderImpl>()
|
||||
}
|
||||
|
||||
private val contextCache = ConcurrentHashMap<Cordapp, CordappContext>()
|
||||
|
||||
|
||||
override fun getAppContext(): CordappContext {
|
||||
// TODO: Use better supported APIs in Java 9
|
||||
Exception().stackTrace.forEach { stackFrame ->
|
||||
@ -51,7 +57,7 @@ open class CordappProviderImpl(private val cordappLoader: CordappLoader, attachm
|
||||
|
||||
private fun loadContractsIntoAttachmentStore(attachmentStorage: AttachmentStorage): Map<SecureHash, URL> {
|
||||
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()
|
||||
}
|
||||
|
||||
@ -62,7 +68,14 @@ open class CordappProviderImpl(private val cordappLoader: CordappLoader, attachm
|
||||
* @return A cordapp context for the given CorDapp
|
||||
*/
|
||||
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
|
||||
|
||||
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.testing.node.MockCordappConfigProvider
|
||||
import net.corda.testing.services.MockAttachmentStorage
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class CordappProviderImplTests {
|
||||
companion object {
|
||||
private val isolatedJAR = this::class.java.getResource("isolated.jar")!!
|
||||
private val emptyJAR = this::class.java.getResource("empty.jar")!!
|
||||
private companion object {
|
||||
val isolatedJAR = this::class.java.getResource("isolated.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
|
||||
@ -22,7 +34,7 @@ class CordappProviderImplTests {
|
||||
@Test
|
||||
fun `isolated jar is loaded into the attachment store`() {
|
||||
val loader = CordappLoader.createDevMode(listOf(isolatedJAR))
|
||||
val provider = CordappProviderImpl(loader, attachmentStore)
|
||||
val provider = CordappProviderImpl(loader, stubConfigProvider, attachmentStore)
|
||||
val maybeAttachmentId = provider.getCordappAttachmentId(provider.cordapps.first())
|
||||
|
||||
Assert.assertNotNull(maybeAttachmentId)
|
||||
@ -32,14 +44,14 @@ class CordappProviderImplTests {
|
||||
@Test
|
||||
fun `empty jar is not loaded into the attachment store`() {
|
||||
val loader = CordappLoader.createDevMode(listOf(emptyJAR))
|
||||
val provider = CordappProviderImpl(loader, attachmentStore)
|
||||
val provider = CordappProviderImpl(loader, stubConfigProvider, attachmentStore)
|
||||
Assert.assertNull(provider.getCordappAttachmentId(provider.cordapps.first()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test that we find a cordapp class that is loaded into the store`() {
|
||||
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 expected = provider.cordapps.first()
|
||||
@ -50,9 +62,9 @@ class CordappProviderImplTests {
|
||||
}
|
||||
|
||||
@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 provider = CordappProviderImpl(loader, attachmentStore)
|
||||
val provider = CordappProviderImpl(loader, stubConfigProvider, attachmentStore)
|
||||
val className = "net.corda.finance.contracts.isolated.AnotherDummyContract"
|
||||
val expected = provider.getAppContext(provider.cordapps.first()).attachmentId
|
||||
val actual = provider.getContractAttachmentID(className)
|
||||
@ -60,4 +72,16 @@ class CordappProviderImplTests {
|
||||
Assert.assertNotNull(actual)
|
||||
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.POUNDS
|
||||
import net.corda.testing.node.internal.demorun.deployNodesThen
|
||||
import net.corda.testing.node.internal.demorun.nodeRunner
|
||||
import org.junit.Test
|
||||
|
||||
class BankOfCordaCordformTest {
|
||||
@Test
|
||||
fun `run demo`() {
|
||||
BankOfCordaCordform().deployNodesThen {
|
||||
BankOfCordaCordform().nodeRunner().scanPackages(listOf("net.corda.finance")).deployAndRunNodesThen {
|
||||
IssueCash.requestWebIssue(30000.POUNDS)
|
||||
IssueCash.requestRpcIssue(20000.DOLLARS)
|
||||
}
|
||||
|
@ -25,8 +25,8 @@ private const val BOC_RPC_ADMIN_PORT = 10015
|
||||
private const val BOC_WEB_PORT = 10007
|
||||
|
||||
class BankOfCordaCordform : CordformDefinition() {
|
||||
// TODO: Readd finance dependency - will fail without it
|
||||
init {
|
||||
cordappPackages += "net.corda.finance"
|
||||
node {
|
||||
name(NOTARY_NAME)
|
||||
notary(NotaryConfig(validating = true))
|
||||
@ -65,7 +65,7 @@ class BankOfCordaCordform : CordformDefinition() {
|
||||
object DeployNodes {
|
||||
@JvmStatic
|
||||
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 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 notaryNames = createNotaryNames(clusterSize)
|
||||
|
@ -1,9 +1,9 @@
|
||||
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>) {
|
||||
listOf(SingleNotaryCordform(), RaftNotaryCordform(), BFTNotaryCordform()).forEach {
|
||||
listOf(SingleNotaryCordform(), RaftNotaryCordform(), BFTNotaryCordform()).map { it.nodeRunner() }.forEach {
|
||||
it.clean()
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import java.nio.file.Paths
|
||||
|
||||
fun main(args: Array<String>) = CustomNotaryCordform().deployNodes()
|
||||
fun main(args: Array<String>) = CustomNotaryCordform().nodeRunner().deployAndRunNodes()
|
||||
|
||||
class CustomNotaryCordform : CordformDefinition() {
|
||||
init {
|
||||
|
@ -13,7 +13,7 @@ import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
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") }
|
||||
|
||||
|
@ -11,7 +11,7 @@ import net.corda.testing.node.User
|
||||
import net.corda.testing.node.internal.demorun.*
|
||||
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()))
|
||||
|
||||
|
@ -68,7 +68,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||
name "O=Notary Service,L=Zurich,C=CH"
|
||||
notary = [validating : true]
|
||||
p2pPort 10002
|
||||
cordapps = ["$project.group:finance:$corda_release_version"]
|
||||
cordapp project(':finance')
|
||||
extraConfig = [
|
||||
jvmArgs : [ "-Xmx1g"]
|
||||
]
|
||||
@ -81,7 +81,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||
address("localhost:10016")
|
||||
adminAddress("localhost:10017")
|
||||
}
|
||||
cordapps = ["$project.group:finance:$corda_release_version"]
|
||||
cordapp project(':finance')
|
||||
rpcUsers = ext.rpcUsers
|
||||
extraConfig = [
|
||||
jvmArgs : [ "-Xmx1g"]
|
||||
@ -95,7 +95,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||
address("localhost:10026")
|
||||
adminAddress("localhost:10027")
|
||||
}
|
||||
cordapps = ["$project.group:finance:$corda_release_version"]
|
||||
cordapp project(':finance')
|
||||
rpcUsers = ext.rpcUsers
|
||||
extraConfig = [
|
||||
jvmArgs : [ "-Xmx1g"]
|
||||
@ -109,7 +109,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||
address("localhost:10036")
|
||||
adminAddress("localhost:10037")
|
||||
}
|
||||
cordapps = ["$project.group:finance:$corda_release_version"]
|
||||
cordapp project(':finance')
|
||||
rpcUsers = ext.rpcUsers
|
||||
extraConfig = [
|
||||
jvmArgs : [ "-Xmx1g"]
|
||||
|
@ -46,3 +46,4 @@ include 'samples:network-visualiser'
|
||||
include 'samples:simm-valuation-demo'
|
||||
include 'samples:notary-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.node.internal.cordapp.CordappLoader
|
||||
import net.corda.node.internal.cordapp.CordappProviderImpl
|
||||
import net.corda.testing.node.MockCordappConfigProvider
|
||||
import java.nio.file.Paths
|
||||
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>>()
|
||||
|
||||
fun addMockCordapp(contractClassName: ContractClassName, attachments: MockAttachmentStorage) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user