Initial commit of the java template - includes only concverted code
This commit is contained in:
commit
a15efd43a1
15
.run/runConfigurations/DebugCorDapp.run.xml
Normal file
15
.run/runConfigurations/DebugCorDapp.run.xml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="DebugCorDapp" type="Remote">
|
||||||
|
<option name="USE_SOCKET_TRANSPORT" value="true" />
|
||||||
|
<option name="SERVER_MODE" value="false" />
|
||||||
|
<option name="SHMEM_ADDRESS" />
|
||||||
|
<option name="HOST" value="localhost" />
|
||||||
|
<option name="PORT" value="5005" />
|
||||||
|
<option name="AUTO_RESTART" value="false" />
|
||||||
|
<RunnerSettings RunnerId="Debug">
|
||||||
|
<option name="DEBUG_PORT" value="5005" />
|
||||||
|
<option name="LOCAL" value="false" />
|
||||||
|
</RunnerSettings>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
142
build.gradle
Normal file
142
build.gradle
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import static org.gradle.api.JavaVersion.VERSION_11
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'org.jetbrains.kotlin.jvm'
|
||||||
|
|
||||||
|
// Include the cordapp-cpb plugin. This automatically includes the cordapp-cpk plugin as well.
|
||||||
|
// These extend existing build environment so that CPB and CPK files can be built.
|
||||||
|
// This includes a CorDapp DSL that allows the developer to supply metadata for the CorDapp
|
||||||
|
// required by Corda.
|
||||||
|
id 'net.corda.plugins.cordapp-cpb2'
|
||||||
|
id 'net.corda.cordapp.cordapp-configuration'
|
||||||
|
|
||||||
|
id 'org.jetbrains.kotlin.plugin.jpa'
|
||||||
|
|
||||||
|
id 'java'
|
||||||
|
id 'maven-publish'
|
||||||
|
|
||||||
|
id 'csde'
|
||||||
|
}
|
||||||
|
|
||||||
|
group 'com.r3.hellocorda'
|
||||||
|
version '1.0-SNAPSHOT'
|
||||||
|
|
||||||
|
def javaVersion = VERSION_11
|
||||||
|
|
||||||
|
// The CordApp section.
|
||||||
|
// This is part of the DSL provided by the corda plugins to define metadata for our CorDapp.
|
||||||
|
// Each component of the CorDapp would get its own CorDapp section in the build.gradle file for the component’s
|
||||||
|
// subproject.
|
||||||
|
// This is required by the corda plugins to build the CorDapp.
|
||||||
|
cordapp {
|
||||||
|
// "targetPlatformVersion" and "minimumPlatformVersion" are intended to specify the preferred
|
||||||
|
// and earliest versions of the Corda platform that the CorDapp will run on respectively.
|
||||||
|
// Enforced versioning has not implemented yet so we need to pass in a dummy value for now.
|
||||||
|
// The platform version will correspond to and be roughly equivalent to the Corda API version.
|
||||||
|
targetPlatformVersion platformVersion.toInteger()
|
||||||
|
minimumPlatformVersion platformVersion.toInteger()
|
||||||
|
|
||||||
|
// The cordapp section contains either a workflow or contract subsection depending on the type of component.
|
||||||
|
// Declares the type and metadata of the CPK (this CPB has one CPK).
|
||||||
|
workflow {
|
||||||
|
name "ModuleNameHere"
|
||||||
|
versionId 1
|
||||||
|
vendor "VendorNameHere"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declare the set of Kotlin compiler options we need to build a CorDapp.
|
||||||
|
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
|
||||||
|
kotlinOptions {
|
||||||
|
allWarningsAsErrors = false
|
||||||
|
|
||||||
|
// Specify the version of Kotlin that we are that we will be developing.
|
||||||
|
languageVersion = '1.7'
|
||||||
|
// Specify the Kotlin libraries that code is compatible with
|
||||||
|
apiVersion = '1.7'
|
||||||
|
// Note that we Need to use a version of Kotlin that will be compatible with the Corda API.
|
||||||
|
// Currently that is developed in Kotlin 1.7 so picking the same version ensures compatibility with that.
|
||||||
|
|
||||||
|
// Specify the version of Java to target.
|
||||||
|
jvmTarget = javaVersion
|
||||||
|
|
||||||
|
// Needed for reflection to work correctly.
|
||||||
|
javaParameters = true
|
||||||
|
|
||||||
|
// -Xjvm-default determines how Kotlin supports default methods.
|
||||||
|
// JetBrains currently recommends developers use -Xjvm-default=all
|
||||||
|
// https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/-jvm-default/
|
||||||
|
freeCompilerArgs += [
|
||||||
|
"-Xjvm-default=all"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
// All dependencies are held in Maven Central
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declare dependencies for the modules we will use.
|
||||||
|
// A cordaProvided declaration is required for anything that we use that the Corda API provides.
|
||||||
|
// This is required to allow us to build CorDapp modules as OSGi bundles that CPI and CPB files are built on.
|
||||||
|
dependencies {
|
||||||
|
// We need a version of kotlin-stdlib-jdk8 built as an OSGi bundle, this is "kotlin-stdlib-jdk8-osgi".
|
||||||
|
// R3 builds kotlin-stdlib-jdk8-osgi from Kotlin's kotlin-stdlib-jdk8.
|
||||||
|
// NB:
|
||||||
|
// Kotlin's kotlin-osgi-bundle does not provide all of the Kotlin API that is required,
|
||||||
|
// There is no kotlin-stdlib-jdk11, but one is not needed even though we are targetting Java 11.
|
||||||
|
cordaProvided 'net.corda.kotlin:kotlin-stdlib-jdk8-osgi'
|
||||||
|
|
||||||
|
// Declare a "platform" so that we use the correct set of dependency versions for the version of the
|
||||||
|
// Corda API specified.
|
||||||
|
cordaProvided platform("net.corda:corda-api:$cordaApiVersion")
|
||||||
|
|
||||||
|
// If using transistive dependencies this will provide most of Corda-API:
|
||||||
|
// cordaProvided 'net.corda:corda-application'
|
||||||
|
|
||||||
|
// Alternatively we can explicitly specify all our Corda-API dependencies:
|
||||||
|
cordaProvided 'net.corda:corda-base'
|
||||||
|
cordaProvided 'net.corda:corda-application'
|
||||||
|
cordaProvided 'net.corda:corda-crypto'
|
||||||
|
cordaProvided 'net.corda:corda-membership'
|
||||||
|
// cordaProvided 'net.corda:corda-persistence'
|
||||||
|
cordaProvided 'net.corda:corda-serialization'
|
||||||
|
|
||||||
|
// Not yet fully implemented:
|
||||||
|
// cordaProvided 'net.corda:corda-ledger'
|
||||||
|
|
||||||
|
// The CorDapp uses the slf4j logging framework. Corda-API provides this so we need a 'cordaProvided' declaration.
|
||||||
|
cordaProvided 'org.slf4j:slf4j-api'
|
||||||
|
|
||||||
|
// Dependencies Required By Test Tooling
|
||||||
|
testImplementation "net.corda:corda-simulator-api:$combinedWorkerVersion"
|
||||||
|
testRuntimeOnly "net.corda:corda-simulator-runtime:$combinedWorkerVersion"
|
||||||
|
|
||||||
|
// 3rd party libraries
|
||||||
|
// Required
|
||||||
|
testImplementation "org.slf4j:slf4j-simple:2.0.0"
|
||||||
|
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
|
||||||
|
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
|
||||||
|
|
||||||
|
// Optional but used by exmaple tests.
|
||||||
|
testImplementation "org.mockito:mockito-core:$mockitoVersion"
|
||||||
|
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
|
||||||
|
testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
maven(MavenPublication) {
|
||||||
|
artifactId "corda-CSDE-kotlin-sample"
|
||||||
|
groupId project.group
|
||||||
|
artifact jar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
buildSrc/build.gradle
Normal file
21
buildSrc/build.gradle
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'groovy-gradle-plugin'
|
||||||
|
id 'java'
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
|
||||||
|
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
|
||||||
|
|
||||||
|
implementation "com.konghq:unirest-java:$unirestVersion"
|
||||||
|
implementation "com.konghq:unirest-objectmapper-jackson:$unirestVersion"
|
||||||
|
implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
|
||||||
|
implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:$jacksonVersion"
|
||||||
|
|
||||||
|
implementation "net.corda:corda-base:$cordaApiVersion"
|
||||||
|
}
|
3
buildSrc/gradle.properties
Normal file
3
buildSrc/gradle.properties
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
jacksonVersion = 2.13.3
|
||||||
|
unirestVersion=3.13.10
|
||||||
|
cordaApiVersion=5.0.0.190-DevPreview-2
|
1
buildSrc/settings.gradle
Normal file
1
buildSrc/settings.gradle
Normal file
@ -0,0 +1 @@
|
|||||||
|
// File intentionally left blank
|
252
buildSrc/src/main/groovy/csde.gradle
Normal file
252
buildSrc/src/main/groovy/csde.gradle
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
import com.r3.csde.CsdeRpcInterface
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'java-library'
|
||||||
|
id 'checkstyle'
|
||||||
|
id 'groovy'
|
||||||
|
id 'java'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
combinedWorker{
|
||||||
|
canBeConsumed = false
|
||||||
|
canBeResolved= true
|
||||||
|
}
|
||||||
|
|
||||||
|
myPostgresJDBC {
|
||||||
|
canBeConsumed = false
|
||||||
|
canBeResolved = true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dependencies for supporting tools
|
||||||
|
dependencies {
|
||||||
|
combinedWorker "net.corda:corda-combined-worker:$combinedWorkerVersion"
|
||||||
|
myPostgresJDBC 'org.postgresql:postgresql:42.4.1'
|
||||||
|
implementation "org.codehaus.groovy:groovy-json:3.0.9"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def pluginGroupName = "CSDE"
|
||||||
|
def pluginImplGroupName = "other"
|
||||||
|
def cordaBinDir= System.getProperty('user.home') + "/.corda/corda5"
|
||||||
|
def cordaCliBinDir = System.getProperty('user.home') + "/.corda/cli"
|
||||||
|
def cordaJDBCDir = cordaBinDir + "/jdbcDrivers"
|
||||||
|
def signingCertAlias="gradle-plugin-default-key"
|
||||||
|
// Get error if this is not a autotyped object
|
||||||
|
// def signingCertFName = "$rootDir/config/gradle-plugin-default-key.pem"
|
||||||
|
def signingCertFName = rootDir.toString() + "/csde-config/gradle-plugin-default-key.pem"
|
||||||
|
def keystoreAlias = "my-signing-key"
|
||||||
|
def keystoreFName = devEnvWorkspace + "/signingkeys.pfx"
|
||||||
|
def keystoreCertFName = devEnvWorkspace + "/signingkey1.pem"
|
||||||
|
def combiWorkerPidCacheFile = devEnvWorkspace + "/CordaPID.dat"
|
||||||
|
|
||||||
|
|
||||||
|
// Need to read things from cordapp plugin
|
||||||
|
def cpiName = 'cpi name'
|
||||||
|
|
||||||
|
def csdeHelper = new CsdeRpcInterface(project,
|
||||||
|
cordaClusterURL.toString(),
|
||||||
|
cordaRpcUser,
|
||||||
|
cordaRpcPasswd,
|
||||||
|
devEnvWorkspace,
|
||||||
|
new String("${System.getProperty("java.home")}/bin"),
|
||||||
|
dbContainerName,
|
||||||
|
cordaJDBCDir,
|
||||||
|
combiWorkerPidCacheFile
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
tasks.register("getPostgresJDBC") {
|
||||||
|
group = pluginImplGroupName
|
||||||
|
doLast {
|
||||||
|
copy {
|
||||||
|
from configurations.myPostgresJDBC
|
||||||
|
into "$cordaJDBCDir"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('projInit') {
|
||||||
|
group = pluginImplGroupName
|
||||||
|
doLast {
|
||||||
|
mkdir devEnvWorkspace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("createGroupPolicy") {
|
||||||
|
group = pluginImplGroupName
|
||||||
|
dependsOn('projInit')
|
||||||
|
doLast {
|
||||||
|
|
||||||
|
def groupPolicyFName = new String("${devEnvWorkspace}/GroupPolicy.json")
|
||||||
|
def devnetFName = new String("$rootDir/csde-config/dev-net.json")
|
||||||
|
File groupPolicyFile = new File(groupPolicyFName)
|
||||||
|
File devnetFile = new File(devnetFName)
|
||||||
|
if (!groupPolicyFile.exists() || groupPolicyFile.lastModified() < devnetFile.lastModified()) {
|
||||||
|
def configX500Ids = csdeHelper.getConfigX500Ids()
|
||||||
|
|
||||||
|
println("createGroupPolicy: Creating a GroupPolicy")
|
||||||
|
|
||||||
|
javaexec {
|
||||||
|
classpath = files("$cordaCliBinDir/corda-cli.jar")
|
||||||
|
jvmArgs = ["-Dpf4j.pluginsDir=$cordaCliBinDir/plugins/"]
|
||||||
|
standardOutput = new FileOutputStream(groupPolicyFName)
|
||||||
|
LinkedList<String> myArgs = new LinkedList<String>()
|
||||||
|
myArgs.add("mgm")
|
||||||
|
myArgs.add("groupPolicy")
|
||||||
|
configX500Ids.forEach {
|
||||||
|
myArgs.add("--name")
|
||||||
|
myArgs.add("$it")
|
||||||
|
}
|
||||||
|
|
||||||
|
myArgs.add("--endpoint-protocol=1")
|
||||||
|
myArgs.add("--endpoint=http://localhost:1080")
|
||||||
|
args = myArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
println("createPolicyTask: everything up to date; nothing to do.")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("getDevCordaLite", Copy) {
|
||||||
|
group = pluginImplGroupName
|
||||||
|
from configurations.combinedWorker
|
||||||
|
into cordaBinDir
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('createKeystore') {
|
||||||
|
group = pluginImplGroupName
|
||||||
|
dependsOn('projInit')
|
||||||
|
doLast {
|
||||||
|
File keystoreFile = new File(keystoreFName)
|
||||||
|
if(!keystoreFile.exists()) {
|
||||||
|
println('createKeystore: Create a keystore')
|
||||||
|
exec {
|
||||||
|
commandLine "keytool", "-genkeypair",
|
||||||
|
"-alias", keystoreAlias,
|
||||||
|
"-keystore", keystoreFName,
|
||||||
|
"-storepass", "keystore password",
|
||||||
|
"-dname", "CN=CPI Example - My Signing Key, O=CorpOrgCorp, L=London, C=GB",
|
||||||
|
"-keyalg", "RSA",
|
||||||
|
"-storetype", "pkcs12",
|
||||||
|
"-validity", "4000"
|
||||||
|
}
|
||||||
|
// Need to add the default signing key to the keystore
|
||||||
|
exec {
|
||||||
|
commandLine "keytool", "-importcert",
|
||||||
|
"-keystore", keystoreFName,
|
||||||
|
"-storepass", "keystore password",
|
||||||
|
"-noprompt",
|
||||||
|
"-alias", signingCertAlias,
|
||||||
|
"-file", signingCertFName
|
||||||
|
}
|
||||||
|
// keytool -exportcert -rfc -alias "signing key 1" -keystore signingkeys.pfx -storepass "keystore password" -file signingkey1.pem
|
||||||
|
exec {
|
||||||
|
commandLine "keytool",
|
||||||
|
"-exportcert", "-rfc", "-alias", keystoreAlias,
|
||||||
|
"-keystore", keystoreFName,
|
||||||
|
"-storepass", "keystore password",
|
||||||
|
"-file", keystoreCertFName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
println('createKeystore: keystore already created; nothing to do.')
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
tasks.register('buildCPI') {
|
||||||
|
group = pluginGroupName
|
||||||
|
dependsOn('build', 'createGroupPolicy', 'createKeystore')
|
||||||
|
|
||||||
|
doLast{
|
||||||
|
def cpiFile= buildDir.toString() + "/" + project.archivesBaseName + "-" + project.version + ".cpi"
|
||||||
|
delete { delete cpiFile }
|
||||||
|
File srcDir
|
||||||
|
srcDir = file('build/libs')
|
||||||
|
|
||||||
|
// Create a file collection using a closure
|
||||||
|
def collection = layout.files { srcDir.listFiles() }
|
||||||
|
def cpbs = collection.filter { it.getName().endsWith(".cpb") }
|
||||||
|
|
||||||
|
javaexec {
|
||||||
|
classpath = files("$cordaCliBinDir/corda-cli.jar")
|
||||||
|
jvmArgs = ["-Dpf4j.pluginsDir=$cordaCliBinDir/plugins/"]
|
||||||
|
args = ['package', 'create-cpi',
|
||||||
|
'--cpb', cpbs.singleFile.absolutePath,
|
||||||
|
'--group-policy', "${devEnvWorkspace}/GroupPolicy.json",
|
||||||
|
'--cpi-name', cpiName,
|
||||||
|
'--cpi-version', project.version,
|
||||||
|
'--file', cpiFile,
|
||||||
|
'--keystore', "${devEnvWorkspace}/signingkeys.pfx",
|
||||||
|
'--storepass', 'keystore password',
|
||||||
|
'--key', 'my-signing-key' ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("deployCPI") {
|
||||||
|
group = pluginImplGroupName
|
||||||
|
dependsOn('buildCPI')
|
||||||
|
doLast {
|
||||||
|
csdeHelper.uploadCertificate(signingCertAlias, signingCertFName)
|
||||||
|
csdeHelper.uploadCertificate(keystoreAlias, keystoreCertFName)
|
||||||
|
csdeHelper.deployCPI("${buildDir}/${project.archivesBaseName}-${project.version}.cpi", cpiName, project.version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("createAndRegVNodes") {
|
||||||
|
group = pluginImplGroupName
|
||||||
|
dependsOn('deployCPI')
|
||||||
|
doLast {
|
||||||
|
csdeHelper.createAndRegVNodes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('listVNodes') {
|
||||||
|
group = pluginGroupName
|
||||||
|
doLast {
|
||||||
|
csdeHelper.listVNodes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('listCPIs') {
|
||||||
|
group = pluginImplGroupName
|
||||||
|
doLast {
|
||||||
|
csdeHelper.listCPIs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty task, just acts as the Task user entry point task.
|
||||||
|
tasks.register('deployCordapp') {
|
||||||
|
group = pluginGroupName
|
||||||
|
dependsOn("createAndRegVNodes")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("startCorda") {
|
||||||
|
group = pluginGroupName
|
||||||
|
dependsOn('getDevCordaLite', 'getPostgresJDBC')
|
||||||
|
doLast {
|
||||||
|
mkdir devEnvWorkspace
|
||||||
|
csdeHelper.startCorda()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("stopCorda") {
|
||||||
|
group = pluginGroupName
|
||||||
|
doLast {
|
||||||
|
csdeHelper.stopCorda()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
7
buildSrc/src/main/java/com/r3/csde/CsdeException.java
Normal file
7
buildSrc/src/main/java/com/r3/csde/CsdeException.java
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package com.r3.csde;
|
||||||
|
|
||||||
|
public class CsdeException extends Exception {
|
||||||
|
public CsdeException(String message){
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
415
buildSrc/src/main/java/com/r3/csde/CsdeRpcInterface.java
Normal file
415
buildSrc/src/main/java/com/r3/csde/CsdeRpcInterface.java
Normal file
@ -0,0 +1,415 @@
|
|||||||
|
package com.r3.csde;
|
||||||
|
|
||||||
|
import kong.unirest.json.JSONArray;
|
||||||
|
import org.gradle.api.Project;
|
||||||
|
import net.corda.v5.base.types.MemberX500Name;
|
||||||
|
import kong.unirest.Unirest;
|
||||||
|
import kong.unirest.json.JSONObject;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.util.Set;
|
||||||
|
import static java.lang.Thread.sleep;
|
||||||
|
|
||||||
|
public class CsdeRpcInterface {
|
||||||
|
private Project project;
|
||||||
|
private String baseURL = "https://localhost:8888";
|
||||||
|
private String rpcUser = "admin";
|
||||||
|
private String rpcPasswd = "admin";
|
||||||
|
private String workspaceDir = "workspace";
|
||||||
|
static private int retryWaitMs = 1000;
|
||||||
|
static PrintStream out = System.out;
|
||||||
|
static private String CPIUploadStatusBaseName = "CPIFileStatus.json";
|
||||||
|
static private String CPIUploadStatusFName;
|
||||||
|
static private String X500ConfigFile = "config/dev-net.json";
|
||||||
|
static private String javaBinDir;
|
||||||
|
static private String cordaPidCache = "CordaPIDCache.dat";
|
||||||
|
static private String dbContainerName;
|
||||||
|
private String JDBCDir;
|
||||||
|
private String combinedWorkerBinRe;
|
||||||
|
|
||||||
|
public CsdeRpcInterface() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public CsdeRpcInterface (Project inProject,
|
||||||
|
String inBaseUrl,
|
||||||
|
String inRpcUser,
|
||||||
|
String inRpcPasswd,
|
||||||
|
String inWorkspaceDir,
|
||||||
|
String inJavaBinDir,
|
||||||
|
String inDbContainerName,
|
||||||
|
String inJDBCDir,
|
||||||
|
String inCordaPidCache
|
||||||
|
) {
|
||||||
|
project = inProject;
|
||||||
|
baseURL = inBaseUrl;
|
||||||
|
rpcUser = inRpcUser;
|
||||||
|
rpcPasswd = inRpcPasswd;
|
||||||
|
workspaceDir = inWorkspaceDir;
|
||||||
|
javaBinDir = inJavaBinDir;
|
||||||
|
cordaPidCache = inCordaPidCache;
|
||||||
|
dbContainerName = inDbContainerName;
|
||||||
|
JDBCDir = inJDBCDir;
|
||||||
|
CPIUploadStatusFName = workspaceDir +"/"+ CPIUploadStatusBaseName;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static private void rpcWait(int millis) {
|
||||||
|
try {
|
||||||
|
sleep(millis);
|
||||||
|
}
|
||||||
|
catch(InterruptedException e) {
|
||||||
|
throw new UnsupportedOperationException("Interrupts not supported.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static private void rpcWait() {
|
||||||
|
rpcWait(retryWaitMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinkedList<String> getConfigX500Ids() throws IOException {
|
||||||
|
LinkedList<String> x500Ids = new LinkedList<>();
|
||||||
|
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
|
||||||
|
|
||||||
|
FileInputStream in = new FileInputStream(X500ConfigFile);
|
||||||
|
com.fasterxml.jackson.databind.JsonNode jsonNode = mapper.readTree(in);
|
||||||
|
for( com.fasterxml.jackson.databind.JsonNode identity: jsonNode.get("identities")) {
|
||||||
|
String idAsString = identity.toString();
|
||||||
|
x500Ids.add(idAsString.substring(1,idAsString.length()-1));
|
||||||
|
}
|
||||||
|
return x500Ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public String getLastCPIUploadChkSum(@NotNull String CPIUploadStatusFName) throws IOException, NullPointerException {
|
||||||
|
|
||||||
|
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
|
||||||
|
FileInputStream in = new FileInputStream(CPIUploadStatusFName);
|
||||||
|
com.fasterxml.jackson.databind.JsonNode jsonNode = mapper.readTree(in);
|
||||||
|
|
||||||
|
|
||||||
|
String checksum = jsonNode.get("cpiFileChecksum").toString();
|
||||||
|
if(checksum == null || checksum.equals("null")) {
|
||||||
|
throw new NullPointerException("Missing cpiFileChecksum in file " + CPIUploadStatusFName+ " with contents:" + jsonNode);
|
||||||
|
}
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void reportError(@NotNull kong.unirest.HttpResponse<kong.unirest.JsonNode> response) throws CsdeException {
|
||||||
|
|
||||||
|
out.println("*** *** ***");
|
||||||
|
out.println("Unexpected response from Corda");
|
||||||
|
out.println("Status="+ response.getStatus());
|
||||||
|
out.println("*** Headers ***\n"+ response.getHeaders());
|
||||||
|
out.println("*** Body ***\n"+ response.getBody());
|
||||||
|
out.println("*** *** ***");
|
||||||
|
throw new CsdeException("Error: unexpected response from Corda.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void downloadFile(String url, String targetPath) {
|
||||||
|
Unirest.get(url)
|
||||||
|
.asFile(targetPath)
|
||||||
|
.getBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
public kong.unirest.HttpResponse<kong.unirest.JsonNode> getVNodeInfo() {
|
||||||
|
Unirest.config().verifySsl(false);
|
||||||
|
return Unirest.get(baseURL + "/api/v1/virtualnode/")
|
||||||
|
.basicAuth(rpcUser, rpcPasswd)
|
||||||
|
.asJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void listVNodesVerbose() {
|
||||||
|
kong.unirest.HttpResponse<kong.unirest.JsonNode> vnodeResponse = getVNodeInfo();
|
||||||
|
out.println("VNodes:\n" + vnodeResponse.getBody().toPrettyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// X500Name, cpiname, shorthash,
|
||||||
|
public void listVNodes() {
|
||||||
|
kong.unirest.HttpResponse<kong.unirest.JsonNode> vnodeResponse = getVNodeInfo();
|
||||||
|
|
||||||
|
kong.unirest.json.JSONArray virtualNodesJson = (JSONArray) vnodeResponse.getBody().getObject().get("virtualNodes");
|
||||||
|
out.println("X500 Name\tHolding identity short hash");
|
||||||
|
for(Object o: virtualNodesJson){
|
||||||
|
if(o instanceof kong.unirest.json.JSONObject) {
|
||||||
|
kong.unirest.json.JSONObject idObj = ((kong.unirest.json.JSONObject) o).getJSONObject("holdingIdentity");
|
||||||
|
out.print("\"" + idObj.get("x500Name") + "\"");
|
||||||
|
out.println("\t\"" + idObj.get("shortHash") + "\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public kong.unirest.HttpResponse<kong.unirest.JsonNode> getCpiInfo() {
|
||||||
|
Unirest.config().verifySsl(false);
|
||||||
|
return Unirest.get(baseURL + "/api/v1/cpi/")
|
||||||
|
.basicAuth(rpcUser, rpcPasswd)
|
||||||
|
.asJson();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void listCPIs() {
|
||||||
|
kong.unirest.HttpResponse<kong.unirest.JsonNode> cpiResponse = getCpiInfo();
|
||||||
|
kong.unirest.json.JSONArray jArray = (JSONArray) cpiResponse.getBody().getObject().get("cpis");
|
||||||
|
|
||||||
|
for(Object o: jArray){
|
||||||
|
if(o instanceof kong.unirest.json.JSONObject) {
|
||||||
|
kong.unirest.json.JSONObject idObj = ((kong.unirest.json.JSONObject) o).getJSONObject("id");
|
||||||
|
out.print("cpiName=" + idObj.get("cpiName"));
|
||||||
|
out.println(", cpiVersion=" + idObj.get("cpiVersion"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void uploadCertificate(String certAlias, String certFName) {
|
||||||
|
Unirest.config().verifySsl(false);
|
||||||
|
kong.unirest.HttpResponse<kong.unirest.JsonNode> uploadResponse = Unirest.put(baseURL + "/api/v1/certificates/codesigner/")
|
||||||
|
.field("alias", certAlias)
|
||||||
|
.field("certificate", new File(certFName))
|
||||||
|
.basicAuth(rpcUser, rpcPasswd)
|
||||||
|
.asJson();
|
||||||
|
out.println("Certificate/key upload, alias "+certAlias+" certificate/key file "+certFName);
|
||||||
|
out.println(uploadResponse.getBody().toPrettyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void forceuploadCPI(String cpiFName) throws FileNotFoundException, CsdeException {
|
||||||
|
Unirest.config().verifySsl(false);
|
||||||
|
kong.unirest.HttpResponse<kong.unirest.JsonNode> jsonResponse = Unirest.post(baseURL + "/api/v1/maintenance/virtualnode/forcecpiupload/")
|
||||||
|
.field("upload", new File(cpiFName))
|
||||||
|
.basicAuth(rpcUser, rpcPasswd)
|
||||||
|
.asJson();
|
||||||
|
|
||||||
|
if(jsonResponse.getStatus() == 200) {
|
||||||
|
String id = (String) jsonResponse.getBody().getObject().get("id");
|
||||||
|
out.println("get id:\n" +id);
|
||||||
|
kong.unirest.HttpResponse<kong.unirest.JsonNode> statusResponse = uploadStatus(id);
|
||||||
|
|
||||||
|
if (statusResponse.getStatus() == 200) {
|
||||||
|
PrintStream cpiUploadStatus = new PrintStream(new FileOutputStream(CPIUploadStatusFName));
|
||||||
|
cpiUploadStatus.print(statusResponse.getBody());
|
||||||
|
out.println("Caching CPI file upload status:\n" + statusResponse.getBody());
|
||||||
|
} else {
|
||||||
|
reportError(statusResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reportError(jsonResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean uploadStatusRetry(kong.unirest.HttpResponse<kong.unirest.JsonNode> response) {
|
||||||
|
int status = response.getStatus();
|
||||||
|
kong.unirest.JsonNode body = response.getBody();
|
||||||
|
// Do not retry on success
|
||||||
|
if(status == 200) {
|
||||||
|
// Keep retrying until we get "OK" may move through "Validateing upload", "Persisting CPI"
|
||||||
|
return !(body.getObject().get("status").equals("OK"));
|
||||||
|
}
|
||||||
|
else if (status == 400){
|
||||||
|
JSONObject details = response.getBody().getObject().getJSONObject("details");
|
||||||
|
if( details != null ){
|
||||||
|
String code = (String) details.getString("code");
|
||||||
|
return !code.equals("BAD_REQUEST");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 400 otherwise means some transient problem
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public kong.unirest.HttpResponse<kong.unirest.JsonNode> uploadStatus(String requestId) {
|
||||||
|
kong.unirest.HttpResponse<kong.unirest.JsonNode> statusResponse = null;
|
||||||
|
do {
|
||||||
|
rpcWait(1000);
|
||||||
|
statusResponse = Unirest
|
||||||
|
.get(baseURL + "/api/v1/cpi/status/" + requestId + "/")
|
||||||
|
.basicAuth(rpcUser, rpcPasswd)
|
||||||
|
.asJson();
|
||||||
|
out.println("Upload status="+statusResponse.getStatus()+", status query response:\n"+statusResponse.getBody().toPrettyString());
|
||||||
|
}
|
||||||
|
while(uploadStatusRetry(statusResponse));
|
||||||
|
|
||||||
|
return statusResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deployCPI(String cpiFName, String cpiName, String cpiVersion) throws FileNotFoundException, CsdeException {
|
||||||
|
Unirest.config().verifySsl(false);
|
||||||
|
|
||||||
|
kong.unirest.HttpResponse<kong.unirest.JsonNode> cpiResponse = getCpiInfo();
|
||||||
|
kong.unirest.json.JSONArray jArray = (JSONArray) cpiResponse.getBody().getObject().get("cpis");
|
||||||
|
|
||||||
|
|
||||||
|
int matches = 0;
|
||||||
|
for(Object o: jArray.toList() ) {
|
||||||
|
if(o instanceof JSONObject) {
|
||||||
|
JSONObject idObj = ((JSONObject) o).getJSONObject("id");
|
||||||
|
if((idObj.get("cpiName").toString().equals(cpiName)
|
||||||
|
&& idObj.get("cpiVersion").toString().equals(cpiVersion))) {
|
||||||
|
matches++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.println("Matching CPIS="+matches);
|
||||||
|
|
||||||
|
|
||||||
|
if(matches == 0) {
|
||||||
|
kong.unirest.HttpResponse<kong.unirest.JsonNode> uploadResponse = Unirest.post(baseURL + "/api/v1/cpi/")
|
||||||
|
.field("upload", new File(cpiFName))
|
||||||
|
.basicAuth(rpcUser, rpcPasswd)
|
||||||
|
.asJson();
|
||||||
|
|
||||||
|
kong.unirest.JsonNode body = uploadResponse.getBody();
|
||||||
|
|
||||||
|
int status = uploadResponse.getStatus();
|
||||||
|
|
||||||
|
out.println("Upload Status:" + status);
|
||||||
|
out.println("Pretty print the body\n" + body.toPrettyString());
|
||||||
|
|
||||||
|
// We expect the id field to be a string.
|
||||||
|
if (status == 200) {
|
||||||
|
String id = (String) body.getObject().get("id");
|
||||||
|
out.println("get id:\n" + id);
|
||||||
|
|
||||||
|
kong.unirest.HttpResponse<kong.unirest.JsonNode> statusResponse = uploadStatus(id);
|
||||||
|
if (statusResponse.getStatus() == 200) {
|
||||||
|
PrintStream cpiUploadStatus = new PrintStream(new FileOutputStream(CPIUploadStatusFName));
|
||||||
|
cpiUploadStatus.print(statusResponse.getBody());
|
||||||
|
out.println("Caching CPI file upload status:\n" + statusResponse.getBody());
|
||||||
|
} else {
|
||||||
|
reportError(statusResponse);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reportError(uploadResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
out.println("CPI already uploaded doing a 'force' upload.");
|
||||||
|
forceuploadCPI(cpiFName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createAndRegVNodes() throws IOException, CsdeException{
|
||||||
|
Unirest.config().verifySsl(false);
|
||||||
|
String cpiCheckSum = getLastCPIUploadChkSum( CPIUploadStatusFName );
|
||||||
|
|
||||||
|
LinkedList<String> x500Ids = getConfigX500Ids();
|
||||||
|
LinkedList<String> OKHoldingShortIds = new LinkedList<>();
|
||||||
|
|
||||||
|
// For each identity check that it already exists.
|
||||||
|
Set<MemberX500Name> existingX500 = new HashSet<>();
|
||||||
|
kong.unirest.HttpResponse<kong.unirest.JsonNode> vnodeListResponse = getVNodeInfo();
|
||||||
|
|
||||||
|
kong.unirest.json.JSONArray virtualNodesJson = (JSONArray) vnodeListResponse.getBody().getObject().get("virtualNodes");
|
||||||
|
for(Object o: virtualNodesJson){
|
||||||
|
if(o instanceof kong.unirest.json.JSONObject) {
|
||||||
|
kong.unirest.json.JSONObject idObj = ((kong.unirest.json.JSONObject) o).getJSONObject("holdingIdentity");
|
||||||
|
String x500id = (String) idObj.get("x500Name");
|
||||||
|
existingX500.add(MemberX500Name.parse( x500id) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the VNodes
|
||||||
|
for(String x500id: x500Ids) {
|
||||||
|
if(!existingX500.contains(MemberX500Name.parse(x500id) )) {
|
||||||
|
out.println("Creating VNode for x500id=\"" + x500id + "\" cpi checksum=" + cpiCheckSum);
|
||||||
|
kong.unirest.HttpResponse<kong.unirest.JsonNode> jsonNode = Unirest.post(baseURL + "/api/v1/virtualnode")
|
||||||
|
.body("{ \"request\" : { \"cpiFileChecksum\": " + cpiCheckSum + ", \"x500Name\": \"" + x500id + "\" } }")
|
||||||
|
.basicAuth(rpcUser, rpcPasswd)
|
||||||
|
.asJson();
|
||||||
|
// Logging.
|
||||||
|
|
||||||
|
// need to check this and report errors.
|
||||||
|
// 200 - OK
|
||||||
|
// 409 - Vnode already exists
|
||||||
|
if (jsonNode.getStatus() != 409) {
|
||||||
|
if (jsonNode.getStatus() != 200) {
|
||||||
|
reportError(jsonNode);
|
||||||
|
} else {
|
||||||
|
JSONObject thing = jsonNode.getBody().getObject().getJSONObject("holdingIdentity");
|
||||||
|
String shortHash = (String) thing.get("shortHash");
|
||||||
|
OKHoldingShortIds.add(shortHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
out.println("Not creating a vnode for \"" + x500id + "\", vnode already exists.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the VNodes
|
||||||
|
for(String shortHoldingIdHash: OKHoldingShortIds) {
|
||||||
|
kong.unirest.HttpResponse<kong.unirest.JsonNode> vnodeResponse = Unirest.post(baseURL + "/api/v1/membership/" + shortHoldingIdHash)
|
||||||
|
.body("{ \"memberRegistrationRequest\": { \"action\": \"requestJoin\", \"context\": { \"corda.key.scheme\" : \"CORDA.ECDSA.SECP256R1\" } } }")
|
||||||
|
.basicAuth(rpcUser, rpcPasswd)
|
||||||
|
.asJson();
|
||||||
|
|
||||||
|
out.println("Vnode membership submission:\n" + vnodeResponse.getBody().toPrettyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startCorda() throws IOException {
|
||||||
|
PrintStream pidStore = new PrintStream(new FileOutputStream(cordaPidCache));
|
||||||
|
File combinedWorkerJar = project.getConfigurations().getByName("combinedWorker").getSingleFile();
|
||||||
|
|
||||||
|
new ProcessBuilder(
|
||||||
|
"docker",
|
||||||
|
"run", "-d", "--rm",
|
||||||
|
"-p", "5432:5432",
|
||||||
|
"--name", dbContainerName,
|
||||||
|
"-e", "POSTGRES_DB=cordacluster",
|
||||||
|
"-e", "POSTGRES_USER=postgres",
|
||||||
|
"-e", "POSTGRES_PASSWORD=password",
|
||||||
|
"postgres:latest").start();
|
||||||
|
rpcWait(10000);
|
||||||
|
|
||||||
|
ProcessBuilder procBuild = new ProcessBuilder(javaBinDir + "/java",
|
||||||
|
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005",
|
||||||
|
"-Dco.paralleluniverse.fibers.verifyInstrumentation=true",
|
||||||
|
"-jar",
|
||||||
|
combinedWorkerJar.toString(),
|
||||||
|
"--instanceId=0",
|
||||||
|
"-mbus.busType=DATABASE",
|
||||||
|
"-spassphrase=password",
|
||||||
|
"-ssalt=salt",
|
||||||
|
"-spassphrase=password",
|
||||||
|
"-ssalt=salt",
|
||||||
|
"-ddatabase.user=user",
|
||||||
|
"-ddatabase.pass=password",
|
||||||
|
"-ddatabase.jdbc.url=jdbc:postgresql://localhost:5432/cordacluster",
|
||||||
|
"-ddatabase.jdbc.directory="+JDBCDir);
|
||||||
|
|
||||||
|
|
||||||
|
procBuild.redirectErrorStream(true);
|
||||||
|
Process proc = procBuild.start();
|
||||||
|
pidStore.print(proc.pid());
|
||||||
|
out.println("Corda Process-id="+proc.pid());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopCorda() throws IOException, NoPidFile {
|
||||||
|
File cordaPIDFile = new File(cordaPidCache);
|
||||||
|
if(cordaPIDFile.exists()) {
|
||||||
|
Scanner sc = new Scanner(cordaPIDFile);
|
||||||
|
long pid = sc.nextLong();
|
||||||
|
out.println("pid to kill=" + pid);
|
||||||
|
|
||||||
|
if (System.getProperty("os.name").toLowerCase().contains("windows")) {
|
||||||
|
new ProcessBuilder("Powershell", "-Command", "Stop-Process", "-Id", Long.toString(pid), "-PassThru").start();
|
||||||
|
} else {
|
||||||
|
new ProcessBuilder("kill", "-9", Long.toString(pid)).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
Process proc = new ProcessBuilder("docker", "stop", dbContainerName).start();
|
||||||
|
|
||||||
|
cordaPIDFile.delete();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new NoPidFile("Cannot stop the Combined worker\nCached process ID file " + cordaPidCache + " missing.\nWas the combined worker not started?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
7
buildSrc/src/main/java/com/r3/csde/NoPidFile.java
Normal file
7
buildSrc/src/main/java/com/r3/csde/NoPidFile.java
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package com.r3.csde;
|
||||||
|
|
||||||
|
public class NoPidFile extends Exception {
|
||||||
|
public NoPidFile(String message){
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
4
buildSrc/src/test/java/CsdeRpcInterfaceTests.java
Normal file
4
buildSrc/src/test/java/CsdeRpcInterfaceTests.java
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import com.r3.csde.CsdeRpcInterface;
|
||||||
|
|
||||||
|
public class CsdeRpcInterfaceTests {
|
||||||
|
}
|
7
config/dev-net.json
Normal file
7
config/dev-net.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"identities" : [
|
||||||
|
"CN=Alice, OU=Test Dept, O=R3, L=London, C=GB",
|
||||||
|
"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB",
|
||||||
|
"CN=Charlie, OU=Test Dept, O=R3, L=London, C=GB"
|
||||||
|
]
|
||||||
|
}
|
13
config/gradle-plugin-default-key.pem
Normal file
13
config/gradle-plugin-default-key.pem
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIB7zCCAZOgAwIBAgIEFyV7dzAMBggqhkjOPQQDAgUAMFsxCzAJBgNVBAYTAkdC
|
||||||
|
MQ8wDQYDVQQHDAZMb25kb24xDjAMBgNVBAoMBUNvcmRhMQswCQYDVQQLDAJSMzEe
|
||||||
|
MBwGA1UEAwwVQ29yZGEgRGV2IENvZGUgU2lnbmVyMB4XDTIwMDYyNTE4NTI1NFoX
|
||||||
|
DTMwMDYyMzE4NTI1NFowWzELMAkGA1UEBhMCR0IxDzANBgNVBAcTBkxvbmRvbjEO
|
||||||
|
MAwGA1UEChMFQ29yZGExCzAJBgNVBAsTAlIzMR4wHAYDVQQDExVDb3JkYSBEZXYg
|
||||||
|
Q29kZSBTaWduZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQDjSJtzQ+ldDFt
|
||||||
|
pHiqdSJebOGPZcvZbmC/PIJRsZZUF1bl3PfMqyG3EmAe0CeFAfLzPQtf2qTAnmJj
|
||||||
|
lGTkkQhxo0MwQTATBgNVHSUEDDAKBggrBgEFBQcDAzALBgNVHQ8EBAMCB4AwHQYD
|
||||||
|
VR0OBBYEFLMkL2nlYRLvgZZq7GIIqbe4df4pMAwGCCqGSM49BAMCBQADSAAwRQIh
|
||||||
|
ALB0ipx6EplT1fbUKqgc7rjH+pV1RQ4oKF+TkfjPdxnAAiArBdAI15uI70wf+xlL
|
||||||
|
zU+Rc5yMtcOY4/moZUq36r0Ilg==
|
||||||
|
-----END CERTIFICATE-----
|
35
gradle.properties
Normal file
35
gradle.properties
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
kotlin.code.style=official
|
||||||
|
|
||||||
|
# Specify the version of the Corda-API to use.
|
||||||
|
# This needs to match the version supported by the Corda Cluster the CorDapp will run on.
|
||||||
|
cordaApiVersion=5.0.0.190-DevPreview-2
|
||||||
|
|
||||||
|
# Specify the version of the cordapp-cpb and cordapp-cpk plugins
|
||||||
|
cordaPluginsVersion=7.0.0-DevPreview-2
|
||||||
|
|
||||||
|
# For the time being this just needs to be set to a dummy value.
|
||||||
|
platformVersion = 999
|
||||||
|
|
||||||
|
# Version of Kotlin to use.
|
||||||
|
# We recommend using a version close to that used by Corda-API.
|
||||||
|
kotlinVersion = 1.7.10
|
||||||
|
|
||||||
|
# Do not use default dependencies.
|
||||||
|
kotlin.stdlib.default.dependency=false
|
||||||
|
|
||||||
|
# Test Tooling Dependency Versions
|
||||||
|
junitVersion = 5.8.2
|
||||||
|
mockitoKotlinVersion=4.0.0
|
||||||
|
mockitoVersion=4.6.1
|
||||||
|
hamcrestVersion=2.2
|
||||||
|
|
||||||
|
# Settings For Development Utilities
|
||||||
|
testUtilsVersion=5.0.0.0-DevPreview-2
|
||||||
|
combinedWorkerVersion=5.0.0.0-DevPreview-2
|
||||||
|
|
||||||
|
cordaClusterURL=https://localhost:8888
|
||||||
|
cordaRpcUser=admin
|
||||||
|
cordaRpcPasswd=admin
|
||||||
|
devEnvWorkspace=workspace
|
||||||
|
dbContainerName=CSDEpostgresql
|
||||||
|
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
185
gradlew
vendored
Executable file
185
gradlew
vendored
Executable file
@ -0,0 +1,185 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright 2015 the original author or authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MSYS* | MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=`expr $i + 1`
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
0) set -- ;;
|
||||||
|
1) set -- "$args0" ;;
|
||||||
|
2) set -- "$args0" "$args1" ;;
|
||||||
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
89
gradlew.bat
vendored
Normal file
89
gradlew.bat
vendored
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
27
settings.gradle
Normal file
27
settings.gradle
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
pluginManagement {
|
||||||
|
// Declare the repositories where plugins are stored.
|
||||||
|
repositories {
|
||||||
|
gradlePluginPortal()
|
||||||
|
mavenCentral {
|
||||||
|
content {
|
||||||
|
includeGroupByRegex 'net\\.corda(\\..*)?'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The plugin dependencies with versions of the plugins congruent with the specified CorDapp plugin version,
|
||||||
|
// Corda API version, and Kotlin version.
|
||||||
|
plugins {
|
||||||
|
id 'net.corda.plugins.cordapp-cpk2' version cordaPluginsVersion
|
||||||
|
id 'net.corda.plugins.cordapp-cpb2' version cordaPluginsVersion
|
||||||
|
id 'net.corda.cordapp.cordapp-configuration' version cordaApiVersion
|
||||||
|
id 'org.jetbrains.kotlin.jvm' version kotlinVersion
|
||||||
|
id 'org.jetbrains.kotlin.plugin.jpa' version kotlinVersion
|
||||||
|
id 'org.jetbrains.kotlin.plugin.allopen' version kotlinVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root project name, used in naming the project as a whole and used in naming objects built by the project.
|
||||||
|
rootProject.name = 'csde-cordapp-template-java'
|
||||||
|
|
||||||
|
|
16
src/main/java/com/r3/developers/csdetemplate/Message.java
Normal file
16
src/main/java/com/r3/developers/csdetemplate/Message.java
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package com.r3.developers.csdetemplate;
|
||||||
|
|
||||||
|
import net.corda.v5.base.annotations.CordaSerializable;
|
||||||
|
import net.corda.v5.base.types.MemberX500Name;
|
||||||
|
|
||||||
|
// // A class which will contain a message, It must be marked with @CordaSerializable for Corda
|
||||||
|
//// to be able to send from one virtual node to another.
|
||||||
|
@CordaSerializable
|
||||||
|
public class Message {
|
||||||
|
Message(MemberX500Name _sender, String _message) {
|
||||||
|
sender = _sender;
|
||||||
|
message = _message;
|
||||||
|
}
|
||||||
|
public MemberX500Name sender;
|
||||||
|
public String message;
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
package com.r3.developers.csdetemplate;
|
||||||
|
|
||||||
|
import net.corda.v5.application.flows.*;
|
||||||
|
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||||
|
import net.corda.v5.application.membership.MemberLookup;
|
||||||
|
import net.corda.v5.application.messaging.FlowMessaging;
|
||||||
|
import net.corda.v5.application.messaging.FlowSession;
|
||||||
|
import net.corda.v5.base.annotations.Suspendable;
|
||||||
|
import net.corda.v5.base.types.MemberX500Name;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
|
||||||
|
// MyFirstFlow is an initiating flow, it's corresponding responder flow is called MyFirstFlowResponder (defined below)
|
||||||
|
// to link the two sides of the flow together they need to have the same protocol.
|
||||||
|
@InitiatingFlow(protocol = "another-flow")
|
||||||
|
// MyFirstFlow should inherit from RPCStartableFlow, which tells Corda it can be started via an RPC call
|
||||||
|
class MyFirstFlow implements RPCStartableFlow {
|
||||||
|
|
||||||
|
// It is useful to be able to log messages from the flows for debugging.
|
||||||
|
private final Logger log = LoggerFactory.getLogger(MyFirstFlow.class);
|
||||||
|
|
||||||
|
// Corda has a set of injectable services which are injected into the flow at runtime.
|
||||||
|
// Flows declare them with @CordaInjectable, then the flows have access to their services.
|
||||||
|
|
||||||
|
// JsonMarshallingService provides a Service for manipulating json
|
||||||
|
@CordaInject
|
||||||
|
JsonMarshallingService jsonMarshallingService;
|
||||||
|
|
||||||
|
// FlowMessaging provides a service for establishing flow sessions between Virtual Nodes and
|
||||||
|
// sending and receiving payloads between them
|
||||||
|
@CordaInject
|
||||||
|
FlowMessaging flowMessaging;
|
||||||
|
|
||||||
|
// MemberLookup provides a service for looking up information about members of the Virtual Network which
|
||||||
|
// this CorDapp is operating in.
|
||||||
|
@CordaInject
|
||||||
|
MemberLookup memberLookup;
|
||||||
|
|
||||||
|
public MyFirstFlow() {}
|
||||||
|
|
||||||
|
// When a flow is invoked it's call() method is called.
|
||||||
|
// call() methods must be marked as @Suspendable, this allows Corda to pause mid-execution to wait
|
||||||
|
// for a response from the other flows and services
|
||||||
|
@Suspendable
|
||||||
|
@Override
|
||||||
|
public String call(@NotNull RPCRequestData requestBody) {
|
||||||
|
|
||||||
|
// Useful logging to follow what's happening in the console or logs
|
||||||
|
log.info("MFF: MyFirstFlow.call() called");
|
||||||
|
|
||||||
|
// Show the requestBody in the logs - this can be used to help establish the format for starting a flow on corda
|
||||||
|
log.info("MFF: requestBody: " + requestBody.getRequestBody());
|
||||||
|
|
||||||
|
// Deserialize the Json requestBody into the MyfirstFlowStartArgs class using the JsonSerialisation Service
|
||||||
|
MyFirstFlowStartArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, MyFirstFlowStartArgs.class);
|
||||||
|
|
||||||
|
// Obtain the MemberX500Name of counterparty
|
||||||
|
MemberX500Name otherMember = flowArgs.othermember;
|
||||||
|
|
||||||
|
// Get our identity from the MemberLookup service.
|
||||||
|
MemberX500Name ourIdentity = memberLookup.myInfo().getName();
|
||||||
|
|
||||||
|
// Create the message payload using the MessageClass we defined.
|
||||||
|
Message message = new Message(otherMember, "Hello from " + ourIdentity + ".");
|
||||||
|
|
||||||
|
// Log the message to be sent.
|
||||||
|
log.info("MFF: message.message: " + message.message);
|
||||||
|
|
||||||
|
// Start a flow session with the otherMember using the FlowMessaging service
|
||||||
|
// The otherMember's Virtual Node will run the corresponding MyFirstFlowResponder responder flow
|
||||||
|
FlowSession session = flowMessaging.initiateFlow(otherMember);
|
||||||
|
|
||||||
|
// Send the Payload using the send method on the session to the MyFirstFlowResponder Responder flow
|
||||||
|
session.send(message);
|
||||||
|
|
||||||
|
// Receive a response from the Responder flow
|
||||||
|
Message response = session.receive(Message.class);
|
||||||
|
|
||||||
|
// The return value of a RPCStartableFlow must always be a String, this string will be passed
|
||||||
|
// back as the REST RPC response when the status of the flow is queried on Corda, or as the return
|
||||||
|
// value from the flow when testing using the Simulator
|
||||||
|
return response.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
RequestBody for triggering the flow via http-rpc:
|
||||||
|
{
|
||||||
|
"clientRequestId": "r1",
|
||||||
|
"flowClassName": "com.r3.developers.csdetemplate.MyFirstFlow",
|
||||||
|
"requestData": {
|
||||||
|
"otherMember":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
@ -0,0 +1,72 @@
|
|||||||
|
package com.r3.developers.csdetemplate;
|
||||||
|
|
||||||
|
import net.corda.v5.application.flows.CordaInject;
|
||||||
|
import net.corda.v5.application.flows.InitiatedBy;
|
||||||
|
import net.corda.v5.application.flows.ResponderFlow;
|
||||||
|
import net.corda.v5.application.membership.MemberLookup;
|
||||||
|
import net.corda.v5.application.messaging.FlowSession;
|
||||||
|
import net.corda.v5.base.annotations.Suspendable;
|
||||||
|
import net.corda.v5.base.types.MemberX500Name;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
|
||||||
|
// MyFirstFlowResponder is a responder flow, it's corresponding initiating flow is called MyFirstFlow (defined above)
|
||||||
|
// to link the two sides of the flow together they need to have the same protocol.
|
||||||
|
@InitiatedBy(protocol = "another-flow")
|
||||||
|
// Responder flows must inherit from ResponderFlow
|
||||||
|
class MyFirstFlowResponder implements ResponderFlow {
|
||||||
|
|
||||||
|
// It is useful to be able to log messages from the flows for debugging.
|
||||||
|
private final Logger log = LoggerFactory.getLogger(MyFirstFlowResponder.class);
|
||||||
|
|
||||||
|
// MemberLookup provides a service for looking up information about members of the Virtual Network which
|
||||||
|
// this CorDapp is operating in.
|
||||||
|
@CordaInject
|
||||||
|
MemberLookup memberLookup;
|
||||||
|
|
||||||
|
public MyFirstFlowResponder() {}
|
||||||
|
|
||||||
|
// Responder flows are invoked when an initiating flow makes a call via a session set up with the Virtual
|
||||||
|
// node hosting the Responder flow. When a responder flow is invoked it's call() method is called.
|
||||||
|
// call() methods must be marked as @Suspendable, this allows Corda to pause mid-execution to wait
|
||||||
|
// for a response from the other flows and services/
|
||||||
|
// The Call method has the flow session passed in as a parameter by Corda so the session is available to
|
||||||
|
// responder flow code, you don't need to inject the FlowMessaging service.
|
||||||
|
@Suspendable
|
||||||
|
@Override
|
||||||
|
public void call(FlowSession session) {
|
||||||
|
|
||||||
|
// Useful logging to follow what's happening in the console or logs
|
||||||
|
log.info("MFF: MyFirstResponderFlow.call() called");
|
||||||
|
|
||||||
|
// Receive the payload and deserialize it into a Message class
|
||||||
|
Message receivedMessage = session.receive(Message.class);
|
||||||
|
|
||||||
|
// Log the message as a proxy for performing some useful operation on it.
|
||||||
|
log.info("MFF: Message received from " + receivedMessage.sender + ":" + receivedMessage.message);
|
||||||
|
|
||||||
|
// Get our identity from the MemberLookup service.
|
||||||
|
MemberX500Name ourIdentity = memberLookup.myInfo().getName();
|
||||||
|
|
||||||
|
// Create a response to greet the sender
|
||||||
|
Message response = new Message(ourIdentity,
|
||||||
|
"Hello " + session.getCounterparty().getCommonName() + ", best wishes from " + ourIdentity.getCommonName());
|
||||||
|
|
||||||
|
// Log the response to be sent.
|
||||||
|
log.info("MFF: response.message: " + response.message);
|
||||||
|
|
||||||
|
// Send the response via the send method on the flow session
|
||||||
|
session.send(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
RequestBody for triggering the flow via http-rpc:
|
||||||
|
{
|
||||||
|
"clientRequestId": "r1",
|
||||||
|
"flowClassName": "com.r3.developers.csdetemplate.MyFirstFlow",
|
||||||
|
"requestData": {
|
||||||
|
"otherMember":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.r3.developers.csdetemplate;
|
||||||
|
|
||||||
|
import net.corda.v5.base.types.MemberX500Name;
|
||||||
|
|
||||||
|
// // A class to hold the arguments required to start the flow
|
||||||
|
//class MyFirstFlowStartArgs(val otherMember: MemberX500Name)
|
||||||
|
public class MyFirstFlowStartArgs {
|
||||||
|
public MemberX500Name othermember;
|
||||||
|
|
||||||
|
public MyFirstFlowStartArgs(MemberX500Name othermember) {
|
||||||
|
this.othermember = othermember;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package com.r3.developers.csdetemplate;
|
||||||
|
|
||||||
|
import net.corda.simulator.HoldingIdentity;
|
||||||
|
import net.corda.simulator.RequestData;
|
||||||
|
import net.corda.simulator.SimulatedVirtualNode;
|
||||||
|
import net.corda.simulator.Simulator;
|
||||||
|
import net.corda.v5.base.types.MemberX500Name;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class MyFirstFlowTest {
|
||||||
|
|
||||||
|
// Names picked to match the corda network in config/dev-net.json
|
||||||
|
private MemberX500Name aliceX500 = MemberX500Name.parse("CN=Alice, OU=Test Dept, O=R3, L=London, C=GB");
|
||||||
|
private MemberX500Name bobX500 = MemberX500Name.parse("CN=Bob, OU=Test Dept, O=R3, L=London, C=GB");
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_that_MyFirstFLow_returns_correct_message() {
|
||||||
|
|
||||||
|
// Instantiate an instance of the Simulator
|
||||||
|
Simulator simulator = new Simulator();
|
||||||
|
|
||||||
|
// Create Alice's and Bob HoldingIDs
|
||||||
|
HoldingIdentity aliceHoldingID = HoldingIdentity.Companion.create(aliceX500);
|
||||||
|
HoldingIdentity bobHoldingID = HoldingIdentity.Companion.create(bobX500);
|
||||||
|
|
||||||
|
// Create Alice and Bob's virtual nodes, including the Class's of the flows which will be registered on each node.
|
||||||
|
// We don't assign Bob's virtual node to a val because we don't need it for this particular test.
|
||||||
|
SimulatedVirtualNode aliceVN = simulator.createVirtualNode(aliceHoldingID, MyFirstFlow.class);
|
||||||
|
simulator.createVirtualNode(bobHoldingID, MyFirstFlowResponder.class);
|
||||||
|
|
||||||
|
// Create an instance of the MyFirstFlowStartArgs which contains the request arguments for starting the flow
|
||||||
|
MyFirstFlowStartArgs myFirstFlowStartArgs = new MyFirstFlowStartArgs(bobX500);
|
||||||
|
|
||||||
|
// Create a requestData object
|
||||||
|
RequestData requestData = RequestData.Companion.create(
|
||||||
|
"request no 1", // A unique reference for the instance of the flow request
|
||||||
|
MyFirstFlow.class, // The name of the flow class which is to be started
|
||||||
|
myFirstFlowStartArgs // The object which contains the start arguments of the flow
|
||||||
|
);
|
||||||
|
|
||||||
|
// Call the Flow on Alice's virtual node and capture the response from the flow
|
||||||
|
String flowResponse = aliceVN.callFlow(requestData);
|
||||||
|
|
||||||
|
// Check that the flow has returned the expected string
|
||||||
|
assert(flowResponse.equals("Hello Alice, best wishes from Bob"));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user