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