Cordformation in Kotlin (#1873)

Cordformation rewritten in kotlin.
This commit is contained in:
Clinton 2017-10-19 15:41:25 +01:00 committed by GitHub
parent 12fa945077
commit 3dd09fd69b
17 changed files with 539 additions and 465 deletions

View File

@ -1,4 +1,4 @@
gradlePluginsVersion=2.0.4 gradlePluginsVersion=2.0.5
kotlinVersion=1.1.50 kotlinVersion=1.1.50
guavaVersion=21.0 guavaVersion=21.0
bouncycastleVersion=1.57 bouncycastleVersion=1.57

View File

@ -1,7 +1,6 @@
apply plugin: 'kotlin' apply plugin: 'kotlin'
apply plugin: 'application' apply plugin: 'application'
apply plugin: 'net.corda.plugins.cordformation' apply plugin: 'net.corda.plugins.cordformation'
apply plugin: 'net.corda.plugins.cordapp'
apply plugin: 'net.corda.plugins.quasar-utils' apply plugin: 'net.corda.plugins.quasar-utils'
repositories { repositories {

View File

@ -29,7 +29,7 @@ class CordappPlugin : Plugin<Project> {
private fun configureCordappJar(project: Project) { private fun configureCordappJar(project: Project) {
// Note: project.afterEvaluate did not have full dependency resolution completed, hence a task is used instead // Note: project.afterEvaluate did not have full dependency resolution completed, hence a task is used instead
val task = project.task("configureCordappFatJar") val task = project.task("configureCordappFatJar")
val jarTask = project.tasks.single { it.name == "jar" } as Jar val jarTask = project.tasks.getByName("jar") as Jar
task.doLast { task.doLast {
jarTask.from(getDirectNonCordaDependencies(project).map { project.zipTree(it)}).apply { jarTask.from(getDirectNonCordaDependencies(project).map { project.zipTree(it)}).apply {
exclude("META-INF/*.SF") exclude("META-INF/*.SF")
@ -71,6 +71,4 @@ class CordappPlugin : Plugin<Project> {
} }
return filteredDeps.map { runtimeConfiguration.files(it) }.flatten().toSet() return filteredDeps.map { runtimeConfiguration.files(it) }.flatten().toSet()
} }
private fun Project.configuration(name: String): Configuration = configurations.single { it.name == name }
} }

View File

@ -1,7 +1,17 @@
package net.corda.plugins package net.corda.plugins
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.Configuration
import org.gradle.api.plugins.ExtraPropertiesExtension
/**
* Mimics the "project.ext" functionality in groovy which provides a direct
* accessor to the "ext" extention (See: ExtraPropertiesExtension)
*/
@Suppress("UNCHECKED_CAST")
fun <T : Any> Project.ext(name: String): T = (extensions.findByName("ext") as ExtraPropertiesExtension).get(name) as T
fun Project.configuration(name: String): Configuration = configurations.single { it.name == name }
class Utils { class Utils {
companion object { companion object {
@ -14,4 +24,5 @@ class Utils {
} }
} }
} }
} }

View File

@ -8,7 +8,6 @@ buildscript {
} }
} }
apply plugin: 'groovy'
apply plugin: 'kotlin' apply plugin: 'kotlin'
apply plugin: 'net.corda.plugins.publish-utils' apply plugin: 'net.corda.plugins.publish-utils'
@ -34,8 +33,8 @@ sourceSets {
dependencies { dependencies {
compile gradleApi() compile gradleApi()
compile localGroovy()
compile project(":cordapp") compile project(":cordapp")
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
noderunner "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" noderunner "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"

View File

@ -1,153 +0,0 @@
package net.corda.plugins
import static org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
import net.corda.cordform.CordformContext
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.TaskAction
import java.nio.file.Path
import java.nio.file.Paths
/**
* Creates nodes based on the configuration of this task in the gradle configuration DSL.
*
* See documentation for examples.
*/
class Cordform extends DefaultTask {
/**
* Optionally the name of a CordformDefinition subclass to which all configuration will be delegated.
*/
String definitionClass
protected def directory = Paths.get("build", "nodes")
private def nodes = new ArrayList<Node>()
/**
* Set the directory to install nodes into.
*
* @param directory The directory the nodes will be installed into.
* @return
*/
void directory(String directory) {
this.directory = Paths.get(directory)
}
/**
* Add a node configuration.
*
* @param configureClosure A node configuration that will be deployed.
*/
void node(Closure configureClosure) {
nodes << (Node) project.configure(new Node(project), configureClosure)
}
/**
* Returns a node by name.
*
* @param name The name of the node as specified in the node configuration DSL.
* @return A node instance.
*/
private Node getNodeByName(String name) {
for (Node node : nodes) {
if (node.name == name) {
return node
}
}
return null
}
/**
* Installs the run script into the nodes directory.
*/
private void installRunScript() {
project.copy {
from Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.jar")
fileMode 0755
into "${directory}/"
}
project.copy {
from Cordformation.getPluginFile(project, "net/corda/plugins/runnodes")
// Replaces end of line with lf to avoid issues with the bash interpreter and Windows style line endings.
filter(FixCrLfFilter.class, eol: FixCrLfFilter.CrLf.newInstance("lf"))
fileMode 0755
into "${directory}/"
}
project.copy {
from Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.bat")
into "${directory}/"
}
}
/**
* The definitionClass needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath.
*/
private CordformDefinition loadCordformDefinition() {
def plugin = project.convention.getPlugin(JavaPluginConvention.class)
def classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath
URL[] urls = classpath.files.collect { it.toURI().toURL() }
(CordformDefinition) new URLClassLoader(urls, CordformDefinition.classLoader).loadClass(definitionClass).newInstance()
}
/**
* This task action will create and install the nodes based on the node configurations added.
*/
@TaskAction
void build() {
initializeConfiguration()
installRunScript()
nodes.each {
it.build()
}
generateNodeInfos()
}
private initializeConfiguration() {
if (null != definitionClass) {
def cd = loadCordformDefinition()
cd.nodeConfigurers.each { nc ->
node { Node it ->
nc.accept it
it.rootDir directory
}
}
cd.setup new CordformContext() {
Path baseDirectory(String nodeName) {
project.projectDir.toPath().resolve(getNodeByName(nodeName).nodeDir.toPath())
}
}
} else {
nodes.each {
it.rootDir directory
}
}
}
Path fullNodePath(Node node) {
return project.projectDir.toPath().resolve(node.nodeDir.toPath())
}
private generateNodeInfos() {
nodes.each { Node node ->
def process = new ProcessBuilder("java", "-jar", Node.NODEJAR_NAME, "--just-generate-node-info")
.directory(fullNodePath(node).toFile())
.redirectErrorStream(true)
.start()
.waitFor()
}
for (source in nodes) {
for (destination in nodes) {
if (source.nodeDir != destination.nodeDir) {
project.copy {
from fullNodePath(source).toString()
include 'nodeInfo-*'
into fullNodePath(destination).resolve(Node.NODE_INFO_DIRECTORY).toString()
}
}
}
}
}
}

View File

@ -1,28 +0,0 @@
package net.corda.plugins
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
/**
* The Cordformation plugin deploys nodes to a directory in a state ready to be used by a developer for experimentation,
* testing, and debugging. It will prepopulate several fields in the configuration and create a simple node runner.
*/
class Cordformation implements Plugin<Project> {
/**
* Gets a resource file from this plugin's JAR file.
*
* @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.
*/
protected static File getPluginFile(Project project, String filePathInJar) {
return project.rootProject.resources.text.fromArchiveEntry(project.rootProject.buildscript.configurations.classpath.find {
it.name.contains('cordformation')
}, filePathInJar).asFile()
}
void apply(Project project) {
Utils.createCompileConfiguration("cordapp", project)
}
}

View File

@ -1,268 +0,0 @@
package net.corda.plugins
import com.typesafe.config.*
import net.corda.cordform.CordformNode
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x500.style.BCStyle
import org.gradle.api.Project
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
/**
* Represents a node that will be installed.
*/
class Node extends CordformNode {
static final String NODEJAR_NAME = 'corda.jar'
static final String WEBJAR_NAME = 'corda-webserver.jar'
/**
* Set the list of CorDapps to install to the cordapps directory. Each cordapp is a fully qualified Maven
* dependency name, eg: com.example:product-name:0.1
*
* @note Your app will be installed by default and does not need to be included here.
*/
protected List<String> cordapps = []
protected File nodeDir
private Project project
/**
* Sets whether this node will use HTTPS communication.
*
* @param isHttps True if this node uses HTTPS communication.
*/
void https(Boolean isHttps) {
config = config.withValue("useHTTPS", ConfigValueFactory.fromAnyRef(isHttps))
}
/**
* Sets the H2 port for this node
*/
void h2Port(Integer h2Port) {
config = config.withValue("h2port", ConfigValueFactory.fromAnyRef(h2Port))
}
void useTestClock(Boolean useTestClock) {
config = config.withValue("useTestClock", ConfigValueFactory.fromAnyRef(useTestClock))
}
/**
* Set the HTTP web server port for this node.
*
* @param webPort The web port number for this node.
*/
void webPort(Integer webPort) {
config = config.withValue("webAddress",
ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$webPort".toString()))
}
/**
* Set the network map address for this node.
*
* @warning This should not be directly set unless you know what you are doing. Use the networkMapName in the
* Cordform task instead.
* @param networkMapAddress Network map node address.
* @param networkMapLegalName Network map node legal name.
*/
void networkMapAddress(String networkMapAddress, String networkMapLegalName) {
def networkMapService = new HashMap()
networkMapService.put("address", networkMapAddress)
networkMapService.put("legalName", networkMapLegalName)
config = config.withValue("networkMapService", ConfigValueFactory.fromMap(networkMapService))
}
/**
* Set the SSHD port for this node.
*
* @param sshdPort The SSHD port.
*/
void sshdPort(Integer sshdPort) {
config = config.withValue("sshdAddress",
ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$sshdPort".toString()))
}
Node(Project project) {
this.project = project
}
protected void rootDir(Path rootDir) {
def dirName
try {
X500Name x500Name = new X500Name(name)
dirName = x500Name.getRDNs(BCStyle.O).getAt(0).getFirst().getValue().toString()
} catch(IllegalArgumentException ignore) {
// Can't parse as an X500 name, use the full string
dirName = name
}
nodeDir = new File(rootDir.toFile(), dirName.replaceAll("\\s",""))
}
protected void build() {
configureProperties()
installCordaJar()
if (config.hasPath("webAddress")) {
installWebserverJar()
}
installBuiltCordapp()
installCordapps()
installConfig()
appendOptionalConfig()
}
/**
* Get the artemis address for this node.
*
* @return This node's P2P address.
*/
String getP2PAddress() {
return config.getString("p2pAddress")
}
private void configureProperties() {
config = config.withValue("rpcUsers", ConfigValueFactory.fromIterable(rpcUsers))
if (notary) {
config = config.withValue("notary", ConfigValueFactory.fromMap(notary))
}
if (extraConfig) {
config = config.withFallback(ConfigFactory.parseMap(extraConfig))
}
}
/**
* Installs the corda fat JAR to the node directory.
*/
private void installCordaJar() {
def cordaJar = verifyAndGetCordaJar()
project.copy {
from cordaJar
into nodeDir
rename cordaJar.name, NODEJAR_NAME
fileMode 0755
}
}
/**
* Installs the corda webserver JAR to the node directory
*/
private void installWebserverJar() {
def webJar = verifyAndGetWebserverJar()
project.copy {
from webJar
into nodeDir
rename webJar.name, WEBJAR_NAME
}
}
/**
* Installs this project's cordapp to this directory.
*/
private void installBuiltCordapp() {
def cordappsDir = new File(nodeDir, "cordapps")
project.copy {
from project.jar
into cordappsDir
}
}
/**
* Installs other cordapps to this node's cordapps directory.
*/
private void installCordapps() {
def cordappsDir = new File(nodeDir, "cordapps")
def cordapps = getCordappList()
project.copy {
from cordapps
into cordappsDir
}
}
/**
* Installs the configuration file to this node's directory and detokenises it.
*/
private void installConfig() {
def configFileText = config.root().render(new ConfigRenderOptions(false, false, true, false)).split("\n").toList()
// Need to write a temporary file first to use the project.copy, which resolves directories correctly.
def tmpDir = new File(project.buildDir, "tmp")
def tmpConfFile = new File(tmpDir, 'node.conf')
Files.write(tmpConfFile.toPath(), configFileText, StandardCharsets.UTF_8)
project.copy {
from tmpConfFile
into nodeDir
}
}
/**
* Appends installed config file with properties from an optional file.
*/
private void appendOptionalConfig() {
final configFileProperty = "configFile"
File optionalConfig
if (project.findProperty(configFileProperty)) { //provided by -PconfigFile command line property when running Gradle task
optionalConfig = new File(project.findProperty(configFileProperty))
} else if (config.hasPath(configFileProperty)) {
optionalConfig = new File(config.getString(configFileProperty))
}
if (optionalConfig) {
if (!optionalConfig.exists()) {
println "$configFileProperty '$optionalConfig' not found"
} else {
def confFile = new File(project.buildDir.getPath() + "/../" + nodeDir, 'node.conf')
optionalConfig.withInputStream {
input -> confFile << input
}
}
}
}
/**
* Find the corda JAR amongst the dependencies.
*
* @return A file representing the Corda JAR.
*/
private File verifyAndGetCordaJar() {
def maybeCordaJAR = project.configurations.runtime.filter {
it.toString().contains("corda-${project.corda_release_version}.jar") || it.toString().contains("corda-enterprise-${project.corda_release_version}.jar")
}
if (maybeCordaJAR.size() == 0) {
throw new RuntimeException("No Corda Capsule JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-${project.corda_release_version}.jar\"")
} else {
def cordaJar = maybeCordaJAR.getSingleFile()
assert(cordaJar.isFile())
return cordaJar
}
}
/**
* Find the corda JAR amongst the dependencies
*
* @return A file representing the Corda webserver JAR
*/
private File verifyAndGetWebserverJar() {
def maybeJar = project.configurations.runtime.filter {
it.toString().contains("corda-webserver-${project.corda_release_version}.jar")
}
if (maybeJar.size() == 0) {
throw new RuntimeException("No Corda Webserver JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-webserver-${project.corda_release_version}.jar\"")
} else {
def jar = maybeJar.getSingleFile()
assert(jar.isFile())
return jar
}
}
/**
* Gets a list of cordapps based on what dependent cordapps were specified.
*
* @return List of this node's cordapps.
*/
private Collection<File> getCordappList() {
// Cordapps can sometimes contain a GString instance which fails the equality test with the Java string
List<String> cordapps = this.cordapps.collect { it.toString() }
return project.configurations.cordapp.files {
cordapps.contains(it.group + ":" + it.name + ":" + it.version)
}
}
}

View File

@ -0,0 +1,198 @@
package net.corda.plugins
import groovy.lang.Closure
import net.corda.cordform.CordformDefinition
import net.corda.cordform.CordformNode
import org.apache.tools.ant.filters.FixCrLfFilter
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
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.net.URLClassLoader
import java.nio.file.Path
import java.nio.file.Paths
import java.util.concurrent.TimeUnit
/**
* Creates nodes based on the configuration of this task in the gradle configuration DSL.
*
* See documentation for examples.
*/
@Suppress("unused")
open class Cordform : DefaultTask() {
/**
* Optionally the name of a CordformDefinition subclass to which all configuration will be delegated.
*/
@Suppress("MemberVisibilityCanPrivate")
var definitionClass: String? = null
private var directory = Paths.get("build", "nodes")
private val nodes = mutableListOf<Node>()
/**
* Set the directory to install nodes into.
*
* @param directory The directory the nodes will be installed into.
*/
fun directory(directory: String) {
this.directory = Paths.get(directory)
}
/**
* Add a node configuration.
*
* @param configureClosure A node configuration that will be deployed.
*/
@Suppress("MemberVisibilityCanPrivate")
fun node(configureClosure: Closure<in Node>) {
nodes += project.configure(Node(project), configureClosure) as Node
}
/**
* Add a node configuration
*
* @param configureFunc A node configuration that will be deployed
*/
@Suppress("MemberVisibilityCanPrivate")
fun node(configureFunc: Node.() -> Any?): Node {
val node = Node(project).apply { configureFunc() }
nodes += node
return node
}
/**
* 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"))
fileMode = Cordformation.executableFileMode
into("$directory/")
}
}
project.copy {
it.apply {
from(Cordformation.getPluginFile(project, "net/corda/plugins/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
into("$directory/")
}
}
project.copy {
it.apply {
from(Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.bat"))
into("$directory/")
}
}
}
/**
* The definitionClass needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath.
*/
private fun loadCordformDefinition(): CordformDefinition {
val plugin = project.convention.getPlugin(JavaPluginConvention::class.java)
val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath
val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray()
return URLClassLoader(urls, CordformDefinition::class.java.classLoader)
.loadClass(definitionClass)
.asSubclass(CordformDefinition::class.java)
.newInstance()
}
/**
* This task action will create and install the nodes based on the node configurations added.
*/
@Suppress("unused")
@TaskAction
fun build() {
project.logger.info("Running Cordform task")
initializeConfiguration()
installRunScript()
nodes.forEach(Node::build)
generateAndInstallNodeInfos()
}
private fun initializeConfiguration() {
if (definitionClass != null) {
val cd = loadCordformDefinition()
cd.nodeConfigurers.forEach {
val node = node { }
it.accept(node)
node.rootDir(directory)
}
cd.setup { nodeName -> project.projectDir.toPath().resolve(getNodeByName(nodeName)!!.nodeDir.toPath()) }
} else {
nodes.forEach {
it.rootDir(directory)
}
}
}
private fun fullNodePath(node: Node): Path = project.projectDir.toPath().resolve(node.nodeDir.toPath())
private fun generateAndInstallNodeInfos() {
generateNodeInfos()
installNodeInfos()
}
private fun generateNodeInfos() {
project.logger.info("Generating node infos")
val generateTimeoutSeconds = 60L
val processes = nodes.map { node ->
project.logger.info("Generating node info for ${fullNodePath(node)}")
val logDir = File(fullNodePath(node).toFile(), "logs")
logDir.mkdirs() // Directory may not exist at this point
Pair(node, ProcessBuilder("java", "-jar", Node.nodeJarName, "--just-generate-node-info")
.directory(fullNodePath(node).toFile())
.redirectErrorStream(true)
// InheritIO causes hangs on windows due the gradle buffer also not being flushed.
// Must redirect to output or logger (node log is still written, this is just startup banner)
.redirectOutput(File(logDir, "generate-info-log.txt"))
.start())
}
try {
processes.parallelStream().forEach { (node, process) ->
if (!process.waitFor(generateTimeoutSeconds, TimeUnit.SECONDS)) {
throw GradleException("Node took longer $generateTimeoutSeconds seconds than too to generate node info - see node log at ${fullNodePath(node)}/logs")
} else if (process.exitValue() != 0) {
throw GradleException("Node exited with ${process.exitValue()} when generating node infos - see node log at ${fullNodePath(node)}/logs")
}
}
} finally {
// This will be a no-op on success - abort remaining on failure
processes.forEach {
it.second.destroyForcibly()
}
}
}
private fun installNodeInfos() {
project.logger.info("Installing node infos")
for (source in nodes) {
for (destination in nodes) {
if (source.nodeDir != destination.nodeDir) {
project.copy {
it.apply {
from(fullNodePath(source).toString())
include("nodeInfo-*")
into(fullNodePath(destination).resolve(CordformNode.NODE_INFO_DIRECTORY).toString())
}
}
}
}
}
}
}

View File

@ -0,0 +1,33 @@
package net.corda.plugins
import org.gradle.api.Plugin
import org.gradle.api.Project
import java.io.File
/**
* The Cordformation plugin deploys nodes to a directory in a state ready to be used by a developer for experimentation,
* testing, and debugging. It will prepopulate several fields in the configuration and create a simple node runner.
*/
class Cordformation : Plugin<Project> {
internal companion object {
/**
* Gets a resource file from this plugin's JAR file.
*
* @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: File? = project.rootProject.buildscript.configurations.single { it.name == "classpath" }.find {
it.name.contains("cordformation")
}
return project.rootProject.resources.text.fromArchiveEntry(archive, filePathInJar).asFile()
}
val executableFileMode = "0755".toInt(8)
}
override fun apply(project: Project) {
Utils.createCompileConfiguration("cordapp", project)
}
}

View File

@ -0,0 +1,284 @@
package net.corda.plugins
import com.typesafe.config.*
import net.corda.cordform.CordformNode
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x500.style.BCStyle
import org.gradle.api.Project
import java.io.File
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
/**
* Represents a node that will be installed.
*/
class Node(private val project: Project) : CordformNode() {
companion object {
@JvmStatic
val nodeJarName = "corda.jar"
@JvmStatic
val webJarName = "corda-webserver.jar"
private val configFileProperty = "configFile"
}
/**
* Set the list of CorDapps to install to the plugins directory. Each cordapp is a fully qualified Maven
* dependency name, eg: com.example:product-name:0.1
*
* @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>()
private val releaseVersion = project.rootProject.ext<String>("corda_release_version")
internal lateinit var nodeDir: File
/**
* Sets whether this node will use HTTPS communication.
*
* @param isHttps True if this node uses HTTPS communication.
*/
fun https(isHttps: Boolean) {
config = config.withValue("useHTTPS", ConfigValueFactory.fromAnyRef(isHttps))
}
/**
* Sets the H2 port for this node
*/
fun h2Port(h2Port: Int) {
config = config.withValue("h2port", ConfigValueFactory.fromAnyRef(h2Port))
}
fun useTestClock(useTestClock: Boolean) {
config = config.withValue("useTestClock", ConfigValueFactory.fromAnyRef(useTestClock))
}
/**
* Set the HTTP web server port for this node.
*
* @param webPort The web port number for this node.
*/
fun webPort(webPort: Int) {
config = config.withValue("webAddress",
ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$webPort"))
}
/**
* Set the network map address for this node.
*
* @warning This should not be directly set unless you know what you are doing. Use the networkMapName in the
* Cordform task instead.
* @param networkMapAddress Network map node address.
* @param networkMapLegalName Network map node legal name.
*/
fun networkMapAddress(networkMapAddress: String, networkMapLegalName: String) {
val networkMapService = mutableMapOf<String, String>()
networkMapService.put("address", networkMapAddress)
networkMapService.put("legalName", networkMapLegalName)
config = config.withValue("networkMapService", ConfigValueFactory.fromMap(networkMapService))
}
/**
* Set the SSHD port for this node.
*
* @param sshdPort The SSHD port.
*/
fun sshdPort(sshdPort: Int) {
config = config.withValue("sshdAddress",
ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$sshdPort"))
}
internal fun build() {
configureProperties()
installCordaJar()
if (config.hasPath("webAddress")) {
installWebserverJar()
}
installBuiltCordapp()
installCordapps()
installConfig()
appendOptionalConfig()
}
/**
* Get the artemis address for this node.
*
* @return This node's P2P address.
*/
fun getP2PAddress(): String {
return config.getString("p2pAddress")
}
internal fun rootDir(rootDir: Path) {
if(name == null) {
project.logger.error("Node has a null name - cannot create node")
throw IllegalStateException("Node has a null name - cannot create node")
}
val dirName = try {
X500Name(name).getRDNs(BCStyle.O).first().first.value.toString()
} catch(_ : IllegalArgumentException) {
// Can't parse as an X500 name, use the full string
name
}
nodeDir = File(rootDir.toFile(), dirName.replace("\\s", ""))
}
private fun configureProperties() {
config = config.withValue("rpcUsers", ConfigValueFactory.fromIterable(rpcUsers))
if (notary != null) {
config = config.withValue("notary", ConfigValueFactory.fromMap(notary))
}
if (extraConfig != null) {
config = config.withFallback(ConfigFactory.parseMap(extraConfig))
}
}
/**
* Installs the corda fat JAR to the node directory.
*/
private fun installCordaJar() {
val cordaJar = verifyAndGetCordaJar()
project.copy {
it.apply {
from(cordaJar)
into(nodeDir)
rename(cordaJar.name, nodeJarName)
fileMode = Cordformation.executableFileMode
}
}
}
/**
* Installs the corda webserver JAR to the node directory
*/
private fun installWebserverJar() {
val webJar = verifyAndGetWebserverJar()
project.copy {
it.apply {
from(webJar)
into(nodeDir)
rename(webJar.name, webJarName)
}
}
}
/**
* 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 other cordapps to this node's cordapps directory.
*/
private fun installCordapps() {
val cordappsDir = File(nodeDir, "cordapps")
val cordapps = getCordappList()
project.copy {
it.apply {
from(cordapps)
into(cordappsDir)
}
}
}
/**
* Installs the configuration file to this node's directory and detokenises it.
*/
private fun installConfig() {
val options = ConfigRenderOptions.defaults().setOriginComments(false).setComments(false).setFormatted(false).setJson(false)
val configFileText = config.root().render(options).split("\n").toList()
// Need to write a temporary file first to use the project.copy, which resolves directories correctly.
val tmpDir = File(project.buildDir, "tmp")
val tmpConfFile = File(tmpDir, "node.conf")
Files.write(tmpConfFile.toPath(), configFileText, StandardCharsets.UTF_8)
project.copy {
it.apply {
from(tmpConfFile)
into(nodeDir)
}
}
}
/**
* Appends installed config file with properties from an optional file.
*/
private fun appendOptionalConfig() {
val optionalConfig: File? = when {
project.findProperty(configFileProperty) != null -> //provided by -PconfigFile command line property when running Gradle task
File(project.findProperty(configFileProperty) as String)
config.hasPath(configFileProperty) -> File(config.getString(configFileProperty))
else -> null
}
if (optionalConfig != null) {
if (!optionalConfig.exists()) {
project.logger.error("$configFileProperty '$optionalConfig' not found")
} else {
val confFile = File(project.buildDir.path + "/../" + nodeDir, "node.conf")
confFile.appendBytes(optionalConfig.readBytes())
}
}
}
/**
* Find the corda JAR amongst the dependencies.
*
* @return A file representing the Corda JAR.
*/
private fun verifyAndGetCordaJar(): File {
val maybeCordaJAR = project.configuration("runtime").filter {
it.toString().contains("corda-$releaseVersion.jar") || it.toString().contains("corda-enterprise-$releaseVersion.jar")
}
if (maybeCordaJAR.isEmpty) {
throw RuntimeException("No Corda Capsule JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-$releaseVersion.jar\"")
} else {
val cordaJar = maybeCordaJAR.singleFile
assert(cordaJar.isFile)
return cordaJar
}
}
/**
* Find the corda JAR amongst the dependencies
*
* @return A file representing the Corda webserver JAR
*/
private fun verifyAndGetWebserverJar(): File {
val maybeJar = project.configuration("runtime").filter {
it.toString().contains("corda-webserver-$releaseVersion.jar")
}
if (maybeJar.isEmpty) {
throw RuntimeException("No Corda Webserver JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-webserver-$releaseVersion.jar\"")
} else {
val jar = maybeJar.singleFile
assert(jar.isFile)
return jar
}
}
/**
* 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)
}
}
}

View File

@ -80,6 +80,7 @@ open class NodeStartup(val args: Array<String>) {
exitProcess(1) exitProcess(1)
} }
logger.info("Node exiting successfully")
exitProcess(0) exitProcess(0)
} }

View File

@ -19,14 +19,14 @@ import net.corda.testing.internal.demorun.notary
import net.corda.testing.internal.demorun.rpcUsers import net.corda.testing.internal.demorun.rpcUsers
import net.corda.testing.internal.demorun.runNodes import net.corda.testing.internal.demorun.runNodes
fun main(args: Array<String>) = BFTNotaryCordform.runNodes() fun main(args: Array<String>) = BFTNotaryCordform().runNodes()
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)
// This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO // This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO
// NOT use this as a design to copy. // NOT use this as a design to copy.
object BFTNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { class BFTNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") {
private val clusterName = CordaX500Name(BFTNonValidatingNotaryService.id, "BFT", "Zurich", "CH") private val clusterName = CordaX500Name(BFTNonValidatingNotaryService.id, "BFT", "Zurich", "CH")
init { init {

View File

@ -3,7 +3,7 @@ package net.corda.notarydemo
import net.corda.testing.internal.demorun.clean import net.corda.testing.internal.demorun.clean
fun main(args: Array<String>) { fun main(args: Array<String>) {
listOf(SingleNotaryCordform, RaftNotaryCordform, BFTNotaryCordform).forEach { listOf(SingleNotaryCordform(), RaftNotaryCordform(), BFTNotaryCordform()).forEach {
it.clean() it.clean()
} }
} }

View File

@ -9,9 +9,9 @@ import net.corda.testing.BOB
import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.internal.demorun.* import net.corda.testing.internal.demorun.*
fun main(args: Array<String>) = CustomNotaryCordform.runNodes() fun main(args: Array<String>) = CustomNotaryCordform().runNodes()
object CustomNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { class CustomNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") {
init { init {
node { node {
name(ALICE.name) name(ALICE.name)

View File

@ -14,7 +14,7 @@ import net.corda.testing.ALICE
import net.corda.testing.BOB import net.corda.testing.BOB
import net.corda.testing.internal.demorun.* import net.corda.testing.internal.demorun.*
fun main(args: Array<String>) = RaftNotaryCordform.runNodes() fun main(args: Array<String>) = RaftNotaryCordform().runNodes()
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") }
@ -22,7 +22,7 @@ private val notaryNames = createNotaryNames(3)
// This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO // This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO
// NOT use this as a design to copy. // NOT use this as a design to copy.
object RaftNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { class RaftNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") {
private val clusterName = CordaX500Name(RaftValidatingNotaryService.id, "Raft", "Zurich", "CH") private val clusterName = CordaX500Name(RaftValidatingNotaryService.id, "Raft", "Zurich", "CH")
init { init {

View File

@ -17,13 +17,13 @@ import net.corda.testing.internal.demorun.notary
import net.corda.testing.internal.demorun.rpcUsers import net.corda.testing.internal.demorun.rpcUsers
import net.corda.testing.internal.demorun.runNodes import net.corda.testing.internal.demorun.runNodes
fun main(args: Array<String>) = SingleNotaryCordform.runNodes() fun main(args: Array<String>) = SingleNotaryCordform().runNodes()
val notaryDemoUser = User("demou", "demop", setOf(startFlowPermission<DummyIssueAndMove>(), startFlowPermission<RPCStartableNotaryFlowClient>())) val notaryDemoUser = User("demou", "demop", setOf(startFlowPermission<DummyIssueAndMove>(), startFlowPermission<RPCStartableNotaryFlowClient>()))
// This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO // This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO
// NOT use this as a design to copy. // NOT use this as a design to copy.
object SingleNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") { class SingleNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") {
init { init {
node { node {
name(ALICE.name) name(ALICE.name)