diff --git a/.ci/dev/compatibility/JenkinsfileJDK11Azul b/.ci/dev/compatibility/JenkinsfileJDK11Azul
new file mode 100644
index 0000000000..dd7b0001ba
--- /dev/null
+++ b/.ci/dev/compatibility/JenkinsfileJDK11Azul
@@ -0,0 +1,83 @@
+import static com.r3.build.BuildControl.killAllExistingBuildsForJob
+@Library('corda-shared-build-pipeline-steps')
+import static com.r3.build.BuildControl.killAllExistingBuildsForJob
+
+killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger())
+
+pipeline {
+ agent { label 'k8s' }
+ options {
+ timestamps()
+ timeout(time: 3, unit: 'HOURS')
+ }
+
+ environment {
+ DOCKER_TAG_TO_USE = "${env.GIT_COMMIT.subSequence(0, 8)}"
+ EXECUTOR_NUMBER = "${env.EXECUTOR_NUMBER}"
+ BUILD_ID = "${env.BUILD_ID}-${env.JOB_NAME}"
+ ARTIFACTORY_CREDENTIALS = credentials('artifactory-credentials')
+ }
+
+ stages {
+ stage('Corda Pull Request - Generate Build Image') {
+ steps {
+ withCredentials([string(credentialsId: 'container_reg_passwd', variable: 'DOCKER_PUSH_PWD')]) {
+ sh "./gradlew --no-daemon " +
+ "-Dkubenetize=true " +
+ "-Ddocker.push.password=\"\${DOCKER_PUSH_PWD}\" " +
+ "-Ddocker.work.dir=\"/tmp/\${EXECUTOR_NUMBER}\" " +
+ "-Ddocker.build.tag=\"\${DOCKER_TAG_TO_USE}\" " +
+ "-Ddocker.buildbase.tag=11latest " +
+ "-Ddocker.dockerfile=DockerfileJDK11Azul" +
+ " clean pushBuildImage --stacktrace"
+ }
+ sh "kubectl auth can-i get pods"
+ }
+ }
+
+ stage('Corda Pull Request - Run Tests') {
+ parallel {
+ stage('Integration Tests') {
+ steps {
+ sh "./gradlew --no-daemon " +
+ "-DbuildId=\"\${BUILD_ID}\" " +
+ "-Dkubenetize=true " +
+ "-Ddocker.run.tag=\"\${DOCKER_TAG_TO_USE}\" " +
+ "-Dartifactory.username=\"\${ARTIFACTORY_CREDENTIALS_USR}\" " +
+ "-Dartifactory.password=\"\${ARTIFACTORY_CREDENTIALS_PSW}\" " +
+ "-Dgit.branch=\"\${GIT_BRANCH}\" " +
+ "-Dgit.target.branch=\"\${CHANGE_TARGET}\" " +
+ "-Ddependx.branch.origin=${env.GIT_COMMIT} " +
+ "-Ddependx.branch.target=${CHANGE_TARGET} " +
+ " allParallelIntegrationTest --stacktrace"
+ }
+ }
+ stage('Unit Tests') {
+ steps {
+ sh "./gradlew --no-daemon " +
+ "-DbuildId=\"\${BUILD_ID}\" " +
+ "-Dkubenetize=true " +
+ "-Ddocker.run.tag=\"\${DOCKER_TAG_TO_USE}\" " +
+ "-Dartifactory.username=\"\${ARTIFACTORY_CREDENTIALS_USR}\" " +
+ "-Dartifactory.password=\"\${ARTIFACTORY_CREDENTIALS_PSW}\" " +
+ "-Dgit.branch=\"\${GIT_BRANCH}\" " +
+ "-Dgit.target.branch=\"\${CHANGE_TARGET}\" " +
+ "-Ddependx.branch.origin=${env.GIT_COMMIT} " +
+ "-Ddependx.branch.target=${CHANGE_TARGET} " +
+ " allParallelUnitTest --stacktrace"
+ }
+ }
+ }
+ }
+ }
+
+ post {
+ always {
+ archiveArtifacts artifacts: '**/pod-logs/**/*.log', fingerprint: false
+ junit '**/build/test-results-xml/**/*.xml'
+ }
+ cleanup {
+ deleteDir() /* clean up our workspace */
+ }
+ }
+}
diff --git a/.ci/dev/integration/Jenkinsfile b/.ci/dev/integration/Jenkinsfile
index 420ad78d2a..eba467e5a7 100644
--- a/.ci/dev/integration/Jenkinsfile
+++ b/.ci/dev/integration/Jenkinsfile
@@ -5,7 +5,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob
killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger())
pipeline {
- agent { label 'local-k8s' }
+ agent { label 'k8s' }
options {
timestamps()
timeout(time: 3, unit: 'HOURS')
diff --git a/.ci/dev/nightly-regression/Jenkinsfile b/.ci/dev/nightly-regression/Jenkinsfile
index 1b8739fe7b..2f4389a801 100644
--- a/.ci/dev/nightly-regression/Jenkinsfile
+++ b/.ci/dev/nightly-regression/Jenkinsfile
@@ -4,7 +4,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob
killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger())
pipeline {
- agent { label 'local-k8s' }
+ agent { label 'k8s' }
options {
timestamps()
overrideIndexTriggers(false)
diff --git a/.ci/dev/on-demand-tests/Jenkinsfile b/.ci/dev/on-demand-tests/Jenkinsfile
index f59d3d67d0..25127ef133 100644
--- a/.ci/dev/on-demand-tests/Jenkinsfile
+++ b/.ci/dev/on-demand-tests/Jenkinsfile
@@ -3,4 +3,4 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob
killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger())
-onDemandTestPipeline('local-k8s', '.ci/dev/on-demand-tests/commentMappings.yml')
+onDemandTestPipeline('k8s', '.ci/dev/on-demand-tests/commentMappings.yml')
diff --git a/.ci/dev/regression/Jenkinsfile b/.ci/dev/regression/Jenkinsfile
index 0f785396bb..3bd9886e4e 100644
--- a/.ci/dev/regression/Jenkinsfile
+++ b/.ci/dev/regression/Jenkinsfile
@@ -4,7 +4,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob
killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger())
pipeline {
- agent { label 'local-k8s' }
+ agent { label 'k8s' }
options {
timestamps()
buildDiscarder(logRotator(daysToKeepStr: '7', artifactDaysToKeepStr: '7'))
@@ -61,6 +61,36 @@ pipeline {
" allParallelSlowIntegrationTest --stacktrace"
}
}
+
+ stage('Generate sonarqube report') {
+ steps {
+ script {
+ try {
+ // running this step here is the only way to not majorly affect the distributed test plugin,
+ // as now that neither returns build files nor runs jacoco reports
+ sh "./gradlew --no-daemon build jacocoRootReport --stacktrace"
+ withSonarQubeEnv('sq01') {
+ sh "./gradlew --no-daemon sonarqube -x test --stacktrace"
+ }
+ timeout(time: 3, unit: 'MINUTES') {
+ script {
+ try {
+ def qg = waitForQualityGate();
+ if (qg.status != 'OK') {
+ error "Pipeline aborted due to quality gate failure: ${qg.status}"
+ }
+ } catch (org.jenkinsci.plugins.workflow.steps.FlowInterruptedException e) {
+ println('No sonarqube webhook response within timeout. Please check the webhook configuration in sonarqube.')
+ // continue the pipeline
+ }
+ }
+ }
+ } catch (err) {
+ println('Error while trying to execute sonarqube analysis, will be skipped.')
+ }
+ }
+ }
+ }
}
}
}
diff --git a/.ci/dev/smoke/Jenkinsfile b/.ci/dev/smoke/Jenkinsfile
index 05aec41e59..3ddc3cdce8 100644
--- a/.ci/dev/smoke/Jenkinsfile
+++ b/.ci/dev/smoke/Jenkinsfile
@@ -4,7 +4,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob
killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger())
pipeline {
- agent { label 'local-k8s' }
+ agent { label 'k8s' }
options {
timestamps()
overrideIndexTriggers(false)
diff --git a/.ci/dev/unit/Jenkinsfile b/.ci/dev/unit/Jenkinsfile
index 0b7facd77c..b2d2d54393 100644
--- a/.ci/dev/unit/Jenkinsfile
+++ b/.ci/dev/unit/Jenkinsfile
@@ -5,7 +5,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob
killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger())
pipeline {
- agent { label 'local-k8s' }
+ agent { label 'k8s' }
options {
timestamps()
timeout(time: 3, unit: 'HOURS')
diff --git a/.github/workflows/jira_assign_issue.yml b/.github/workflows/jira_assign_issue.yml
new file mode 100644
index 0000000000..7d8b4fba96
--- /dev/null
+++ b/.github/workflows/jira_assign_issue.yml
@@ -0,0 +1,19 @@
+name: Sync assigned jira issues
+
+on:
+ schedule:
+ - cron: '15 * * * *'
+
+jobs:
+ sync_assigned:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Assign
+ uses: corda/jira-sync-assigned-action@master
+ with:
+ jiraBaseUrl: ${{ secrets.JIRA_BASE_URL }}
+ jiraEmail: ${{ secrets.JIRA_USER_EMAIL }}
+ jiraToken: ${{ secrets.JIRA_API_TOKEN }}
+ token: ${{ secrets.GH_TOKEN }}
+ owner: corda
+ repository: corda
diff --git a/.github/workflows/jira_close_issue.yml b/.github/workflows/jira_close_issue.yml
new file mode 100644
index 0000000000..1af7f841e5
--- /dev/null
+++ b/.github/workflows/jira_close_issue.yml
@@ -0,0 +1,20 @@
+name: Sync closed jira issues
+
+on:
+ schedule:
+ - cron: '30 * * * *'
+
+jobs:
+ sync_closed:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Close
+ uses: corda/jira-sync-closed-action@master
+ with:
+ project: CORDA
+ jiraBaseUrl: https://r3-cev.atlassian.net
+ jiraEmail: ${{ secrets.JIRA_USER_EMAIL }}
+ jiraToken: ${{ secrets.JIRA_API_TOKEN }}
+ token: ${{ secrets.GH_TOKEN }}
+ owner: corda
+ repository: corda
diff --git a/.github/workflows/jira_create_issue.yml b/.github/workflows/jira_create_issue.yml
new file mode 100644
index 0000000000..fe9f5eb8de
--- /dev/null
+++ b/.github/workflows/jira_create_issue.yml
@@ -0,0 +1,36 @@
+name: Create jira issue from github issue
+
+on:
+ issues:
+ types: [opened]
+
+jobs:
+ update_jira:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Jira Create issue
+ id: create
+ uses: corda/jira-create-issue-action@master
+ with:
+ jiraBaseUrl: https://r3-cev.atlassian.net
+ project: CORDA
+ issuetype: Bug
+ summary: ${{ github.event.issue.title }}
+ labels: community
+ jiraEmail: ${{ secrets.JIRA_USER_EMAIL }}
+ jiraToken: ${{ secrets.JIRA_API_TOKEN }}
+ description: |
+ ${{ github.event.issue.body }}
+
+ Created by github action.
+
+ - name: Create comment
+ uses: peter-evans/create-or-update-comment@v1
+ with:
+ token: ${{ secrets.GH_TOKEN }}
+ issue-number: ${{ github.event.issue.number }}
+ body: |
+ Automatically created Jira issue: ${{ steps.create.outputs.issue }}
+ reaction-type: '+1'
\ No newline at end of file
diff --git a/Jenkinsfile b/Jenkinsfile
index ee46820d74..cd3418f6b3 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -5,7 +5,7 @@ import static com.r3.build.BuildControl.killAllExistingBuildsForJob
killAllExistingBuildsForJob(env.JOB_NAME, env.BUILD_NUMBER.toInteger())
pipeline {
- agent { label 'local-k8s' }
+ agent { label 'k8s' }
options {
timestamps()
timeout(time: 3, unit: 'HOURS')
diff --git a/README.md b/README.md
index d9fd349b3d..78915358e4 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
- [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
# Corda
diff --git a/build.gradle b/build.gradle
index e7c3f3cd24..833766c97f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -79,8 +79,8 @@ buildscript {
ext.djvm_version = constants.getProperty("djvmVersion")
ext.deterministic_rt_version = constants.getProperty('deterministicRtVersion')
ext.okhttp_version = '3.14.2'
- ext.netty_version = '4.1.29.Final'
- ext.tcnative_version = '2.0.14.Final'
+ ext.netty_version = '4.1.46.Final'
+ ext.tcnative_version = '2.0.29.Final'
ext.typesafe_config_version = constants.getProperty("typesafeConfigVersion")
ext.fileupload_version = '1.4'
ext.kryo_version = '4.0.2'
@@ -187,8 +187,9 @@ buildscript {
// See https://github.com/corda/gradle-capsule-plugin
classpath "us.kirchmeier:gradle-capsule-plugin:1.0.4_r3"
classpath group: "com.r3.testing", name: "gradle-distributed-testing-plugin", version: "1.2-LOCAL-K8S-SHARED-CACHE-SNAPSHOT", changing: true
- classpath group: "com.r3.dependx", name: "gradle-dependx", version: "0.1.12", changing: true
+ classpath group: "com.r3.dependx", name: "gradle-dependx", version: "0.1.13", changing: true
classpath "com.bmuschko:gradle-docker-plugin:5.0.0"
+ classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.8"
}
}
@@ -241,6 +242,7 @@ allprojects {
apply plugin: 'jacoco'
apply plugin: 'org.owasp.dependencycheck'
apply plugin: 'kotlin-allopen'
+ apply plugin: 'org.sonarqube'
allOpen {
annotations(
@@ -400,6 +402,16 @@ allprojects {
}
}
}
+sonarqube {
+ properties {
+ property "sonar.projectName", "Corda"
+ property "sonar.projectKey", "corda"
+ property 'sonar.tests', '**/src/test/**,**/src/smoke-test/**,**/src/integration-test/**,**/src/integration-test-slow/**'
+ property 'sonar.coverage.jacoco.xmlReportPaths', "${rootDir.path}/build/reports/jacoco/jacocoRootReport/jacocoRootReport.xml"
+ property 'detekt.sonar.kotlin.baseline.path', "${rootDir.path}/detekt-baseline.xml"
+ property 'detekt.sonar.kotlin.config.path', "${rootDir.path}/detekt-config.yml"
+ }
+}
// Check that we are running on a Java 8 JDK. The source/targetCompatibility values above aren't sufficient to
// guarantee this because those are properties checked by the Java plugin, but we're using Kotlin.
@@ -459,6 +471,28 @@ task jacocoRootReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) {
it.exists()
})
}
+ afterEvaluate {
+ classDirectories = files(classDirectories.files.collect {
+ fileTree(dir: it,
+ // these exclusions are necessary because jacoco gets confused by same class names
+ // which occur due to deterministic versions of non deterministic classes
+ exclude: ['**/net/corda/core/crypto/SHA256DigestSupplier**',
+ '**/net/corda/core/crypto/DelegatingSecureRandomService',
+ '**/net/corda/core/internal/ThreadLocalToggleField**',
+ '**/net/corda/core/internal/InheritableThreadLocalToggleField**',
+ '**/net/corda/core/internal/ToggleField**',
+ 'net/corda/core/internal/rules/StateContractValidationEnforcementRule**',
+ 'net/corda/core/internal/SimpleToggleField**',
+ 'net/corda/core/serialization/SerializationFactory**',
+ 'net/corda/serialization/internal/amqp/AMQPStreams**',
+ 'net/corda/serialization/internal/amqp/AMQPSerializerFactories**',
+ 'net/corda/serialization/internal/amqp/AMQPSerializationThreadContext**',
+ 'net/corda/serialization/internal/ByteBufferStreams**',
+ 'net/corda/serialization/internal/model/DefaultCacheProvider**',
+ 'net/corda/serialization/internal/DefaultWhitelist**'
+ ])
+ })
+ }
}
tasks.register('detekt', JavaExec) {
@@ -635,67 +669,83 @@ buildScan {
termsOfServiceAgree = 'yes'
}
+ext.generalPurpose = [
+ numberOfShards: 10,
+ streamOutput: false,
+ coresPerFork: 2,
+ memoryInGbPerFork: 12,
+ nodeTaints: "small"
+]
+
+ext.largeScaleSet = [
+ numberOfShards: 15,
+ streamOutput: false,
+ coresPerFork: 6,
+ memoryInGbPerFork: 10,
+ nodeTaints: "big"
+]
+
task allParallelIntegrationTest(type: ParallelTestGroup) {
dependsOn dependxiesModule
podLogLevel PodLogLevel.INFO
testGroups "integrationTest"
- numberOfShards 10
- streamOutput false
- coresPerFork 2
- memoryInGbPerFork 12
+ numberOfShards generalPurpose.numberOfShards
+ streamOutput generalPurpose.streamOutput
+ coresPerFork generalPurpose.coresPerFork
+ memoryInGbPerFork generalPurpose.memoryInGbPerFork
+ nodeTaints generalPurpose.nodeTaints
distribute DistributeTestsBy.METHOD
- nodeTaints "big"
}
task allParallelUnitTest(type: ParallelTestGroup) {
dependsOn dependxiesModule
podLogLevel PodLogLevel.INFO
testGroups "test"
- numberOfShards 10
- streamOutput false
- coresPerFork 2
- memoryInGbPerFork 12
+ numberOfShards generalPurpose.numberOfShards
+ streamOutput generalPurpose.streamOutput
+ coresPerFork generalPurpose.coresPerFork
+ memoryInGbPerFork generalPurpose.memoryInGbPerFork
+ nodeTaints generalPurpose.nodeTaints
distribute DistributeTestsBy.CLASS
- nodeTaints "small"
}
task allParallelUnitAndIntegrationTest(type: ParallelTestGroup) {
dependsOn dependxiesModule
testGroups "test", "integrationTest"
- numberOfShards 15
- streamOutput false
- coresPerFork 6
- memoryInGbPerFork 10
+ numberOfShards generalPurpose.numberOfShards
+ streamOutput generalPurpose.streamOutput
+ coresPerFork generalPurpose.coresPerFork
+ memoryInGbPerFork generalPurpose.memoryInGbPerFork
+ nodeTaints generalPurpose.nodeTaints
distribute DistributeTestsBy.METHOD
- nodeTaints "big"
}
task parallelRegressionTest(type: ParallelTestGroup) {
testGroups "test", "integrationTest", "smokeTest"
dependsOn dependxiesModule
- numberOfShards 15
- streamOutput false
- coresPerFork 2
- memoryInGbPerFork 10
+ numberOfShards generalPurpose.numberOfShards
+ streamOutput generalPurpose.streamOutput
+ coresPerFork generalPurpose.coresPerFork
+ memoryInGbPerFork generalPurpose.memoryInGbPerFork
+ nodeTaints generalPurpose.nodeTaints
distribute DistributeTestsBy.METHOD
- nodeTaints "big"
}
task allParallelSmokeTest(type: ParallelTestGroup) {
testGroups "smokeTest"
dependsOn dependxiesModule
- numberOfShards 4
- streamOutput false
- coresPerFork 6
- memoryInGbPerFork 10
- distribute DistributeTestsBy.CLASS
- nodeTaints "big"
+ numberOfShards generalPurpose.numberOfShards
+ streamOutput generalPurpose.streamOutput
+ coresPerFork generalPurpose.coresPerFork
+ memoryInGbPerFork generalPurpose.memoryInGbPerFork
+ nodeTaints generalPurpose.nodeTaints
+ distribute DistributeTestsBy.METHOD
}
task allParallelSlowIntegrationTest(type: ParallelTestGroup) {
testGroups "slowIntegrationTest"
dependsOn dependxiesModule
- numberOfShards 4
- streamOutput false
- coresPerFork 6
- memoryInGbPerFork 10
- distribute DistributeTestsBy.CLASS
- nodeTaints "big"
+ numberOfShards generalPurpose.numberOfShards
+ streamOutput generalPurpose.streamOutput
+ coresPerFork generalPurpose.coresPerFork
+ memoryInGbPerFork generalPurpose.memoryInGbPerFork
+ nodeTaints generalPurpose.nodeTaints
+ distribute DistributeTestsBy.METHOD
}
apply plugin: 'com.r3.testing.distributed-testing'
apply plugin: 'com.r3.testing.image-building'
diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/BlacklistKotlinClosureTest.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/BlacklistKotlinClosureTest.kt
index 8e3b4f9a5a..51b7cafdf3 100644
--- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/BlacklistKotlinClosureTest.kt
+++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/BlacklistKotlinClosureTest.kt
@@ -1,7 +1,6 @@
package net.corda.client.rpc
import co.paralleluniverse.fibers.Suspendable
-import com.esotericsoftware.kryo.KryoException
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StartableByRPC
import net.corda.core.messaging.startFlow
diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt
index 96d83d1d81..6875840d04 100644
--- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt
+++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/RPCStabilityTests.kt
@@ -14,13 +14,13 @@ import net.corda.core.utilities.Try
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.millis
import net.corda.core.utilities.seconds
+import net.corda.coretesting.internal.testThreadFactory
import net.corda.node.services.rpc.RPCServerConfiguration
import net.corda.nodeapi.RPCApi
import net.corda.testing.common.internal.eventually
import net.corda.testing.common.internal.succeeds
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.driver.internal.incrementalPortAllocation
-import net.corda.coretesting.internal.testThreadFactory
import net.corda.testing.node.internal.RPCDriverDSL
import net.corda.testing.node.internal.RpcBrokerHandle
import net.corda.testing.node.internal.RpcServerHandle
@@ -75,7 +75,7 @@ class RPCStabilityTests {
values.poll()
}
val first = values.peek()
- if (values.size == 5 && values.all { it.keys.size == first.keys.size }) {
+ if (values.size == 5 && values.all { it.keys == first.keys }) {
first
} else {
null
diff --git a/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Configuration.kt b/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Configuration.kt
index 7883f82586..ba623b2b56 100644
--- a/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Configuration.kt
+++ b/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Configuration.kt
@@ -21,7 +21,7 @@ object Configuration {
/**
* Describes a [Config] hiding sensitive data.
*/
- fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue = { value -> ConfigValueFactory.fromAnyRef(value.toString()) }): ConfigValue?
+ fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue = { value -> ConfigValueFactory.fromAnyRef(value.toString()) }, options: Options): ConfigValue?
}
object Value {
@@ -36,10 +36,11 @@ object Configuration {
*
* @throws ConfigException.Missing if the [Config] does not specify the value.
* @throws ConfigException.WrongType if the [Config] specifies a value of the wrong type.
- * @throws ConfigException.BadValue if the [Config] specifies a value of the correct type, but this in unacceptable according to application-level validation rules..
+ * @throws ConfigException.BadValue if the [Config] specifies a value of the correct type, but this in unacceptable according to
+ * application-level validation rules.
*/
@Throws(ConfigException.Missing::class, ConfigException.WrongType::class, ConfigException.BadValue::class)
- fun valueIn(configuration: Config): TYPE
+ fun valueIn(configuration: Config, options: Options): TYPE
/**
* Returns whether the value is specified by the [Config].
@@ -50,27 +51,28 @@ object Configuration {
* Returns a value out of a [Config] if all is good, or null if no value is present. Otherwise, it throws an exception.
*
* @throws ConfigException.WrongType if the [Config] specifies a value of the wrong type.
- * @throws ConfigException.BadValue if the [Config] specifies a value of the correct type, but this in unacceptable according to application-level validation rules..
+ * @throws ConfigException.BadValue if the [Config] specifies a value of the correct type, but this in unacceptable according to
+ * application-level validation rules.
*/
@Throws(ConfigException.WrongType::class, ConfigException.BadValue::class)
- fun valueInOrNull(configuration: Config): TYPE? {
+ fun valueInOrNull(configuration: Config, options: Options): TYPE? {
return when {
- isSpecifiedBy(configuration) -> valueIn(configuration)
+ isSpecifiedBy(configuration) -> valueIn(configuration, options)
else -> null
}
}
}
/**
- * Able to parse a value from a [Config] and [Configuration.Validation.Options], returning a [Valid] result containing either the value itself, or some [Configuration.Validation.Error]s.
+ * Able to parse a value from a [Config] and [Configuration.Options], returning a [Valid] result containing either the value itself, or some [Configuration.Validation.Error]s.
*/
interface Parser {
/**
* Returns a [Valid] wrapper either around a valid value extracted from the [Config], or around a set of [Configuration.Validation.Error] with details about what went wrong.
*/
- fun parse(configuration: Config, options: Configuration.Validation.Options = Configuration.Validation.Options.defaults): Valid
+ fun parse(configuration: Config, options: Options = Options.defaults): Valid
}
}
@@ -109,11 +111,6 @@ object Configuration {
*/
interface Definition : Configuration.Property.Metadata, Configuration.Validator, Configuration.Value.Extractor, Configuration.Describer, Configuration.Value.Parser {
- /**
- * Validates target [Config] with default [Configuration.Validation.Options].
- */
- fun validate(target: Config): Valid = validate(target, Configuration.Validation.Options.defaults)
-
override fun isSpecifiedBy(configuration: Config): Boolean = configuration.hasPath(key)
/**
@@ -181,9 +178,8 @@ object Configuration {
fun map(mappedTypeName: String, convert: (TYPE) -> MAPPED): Standard = mapValid(mappedTypeName) { value -> valid(convert.invoke(value)) }
}
- override fun parse(configuration: Config, options: Configuration.Validation.Options): Validated {
-
- return validate(configuration, options).mapValid { config -> valid(valueIn(config)) }
+ override fun parse(configuration: Config, options: Configuration.Options): Validated {
+ return validate(configuration, options).mapValid { config -> valid(valueIn(config, options)) }
}
companion object {
@@ -199,7 +195,6 @@ object Configuration {
* Returns a [Configuration.Property.Definition.Standard] with value of type [Int].
*/
fun int(key: String, sensitive: Boolean = false): Standard = long(key, sensitive).mapValid { value ->
-
try {
valid(Math.toIntExact(value))
} catch (e: ArithmeticException) {
@@ -210,18 +205,17 @@ object Configuration {
/**
* Returns a [Configuration.Property.Definition.Standard] with value of type [Boolean].
*/
- fun boolean(key: String, sensitive: Boolean = false): Standard = StandardProperty(key, Boolean::class.javaObjectType.simpleName, Config::getBoolean, Config::getBooleanList, sensitive)
+ fun boolean(key: String, sensitive: Boolean = false): Standard = StandardProperty(key, Boolean::class.javaObjectType.simpleName, { config, path, _ -> config.getBoolean(path) }, { config, path, _ -> config.getBooleanList(path) }, sensitive)
/**
* Returns a [Configuration.Property.Definition.Standard] with value of type [Double].
*/
- fun double(key: String, sensitive: Boolean = false): Standard = StandardProperty(key, Double::class.javaObjectType.simpleName, Config::getDouble, Config::getDoubleList, sensitive)
+ fun double(key: String, sensitive: Boolean = false): Standard = StandardProperty(key, Double::class.javaObjectType.simpleName, { config, path, _ -> config.getDouble(path) }, { config, path, _ -> config.getDoubleList(path) }, sensitive)
/**
* Returns a [Configuration.Property.Definition.Standard] with value of type [Float].
*/
fun float(key: String, sensitive: Boolean = false): Standard = double(key, sensitive).mapValid { value ->
-
val floatValue = value.toFloat()
if (floatValue.isInfinite() || floatValue.isNaN()) {
invalid(Configuration.Validation.Error.BadValue.of(key, Float::class.javaObjectType.simpleName, "Provided value exceeds Float range."))
@@ -233,24 +227,43 @@ object Configuration {
/**
* Returns a [Configuration.Property.Definition.Standard] with value of type [String].
*/
- fun string(key: String, sensitive: Boolean = false): Standard = StandardProperty(key, String::class.java.simpleName, Config::getString, Config::getStringList, sensitive)
+ fun string(key: String, sensitive: Boolean = false): Standard = StandardProperty(
+ key,
+ String::class.java.simpleName,
+ { config, path, _ -> config.getString(path) },
+ { config, path, _ -> config.getStringList(path) },
+ sensitive
+ )
/**
* Returns a [Configuration.Property.Definition.Standard] with value of type [Duration].
*/
- fun duration(key: String, sensitive: Boolean = false): Standard = StandardProperty(key, Duration::class.java.simpleName, Config::getDuration, Config::getDurationList, sensitive)
+ fun duration(key: String, sensitive: Boolean = false): Standard = StandardProperty(key, Duration::class.java.simpleName, { config, path, _ -> config.getDuration(path) }, { config, path, _ -> config.getDurationList(path) }, sensitive)
/**
* Returns a [Configuration.Property.Definition.Standard] with value of type [ConfigObject].
* It supports an optional [Configuration.Schema], which is used for validation and more when provided.
*/
- fun nestedObject(key: String, schema: Schema? = null, sensitive: Boolean = false): Standard = StandardProperty(key, ConfigObject::class.java.simpleName, Config::getObject, Config::getObjectList, sensitive, schema)
+ fun nestedObject(key: String, schema: Schema? = null, sensitive: Boolean = false): Standard = StandardProperty(
+ key,
+ ConfigObject::class.java.simpleName,
+ { config, path, _ -> config.getObject(path) },
+ { config, path, _ -> config.getObjectList(path) },
+ sensitive,
+ schema
+ )
/**
* Returns a [Configuration.Property.Definition.Standard] with value of type [ENUM].
* This property expects a value in the configuration matching one of the cases of [ENUM], as text, in uppercase.
*/
- fun > enum(key: String, enumClass: KClass, sensitive: Boolean = false): Standard = StandardProperty(key, enumClass.java.simpleName, { conf: Config, propertyKey: String -> conf.getEnum(enumClass.java, propertyKey) }, { conf: Config, propertyKey: String -> conf.getEnumList(enumClass.java, propertyKey) }, sensitive)
+ fun > enum(key: String, enumClass: KClass, sensitive: Boolean = false): Standard = StandardProperty(
+ key,
+ enumClass.java.simpleName,
+ { conf: Config, propertyKey: String, _ -> conf.getEnum(enumClass.java, propertyKey) },
+ { conf: Config, propertyKey: String, _ -> conf.getEnumList(enumClass.java, propertyKey) },
+ sensitive
+ )
}
}
}
@@ -275,12 +288,7 @@ object Configuration {
*/
val properties: Set>
- /**
- * Validates target [Config] with default [Configuration.Validation.Options].
- */
- fun validate(target: Config): Valid = validate(target, Configuration.Validation.Options.defaults)
-
- override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue): ConfigValue
+ override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue, options: Configuration.Options): ConfigValue
companion object {
@@ -368,35 +376,35 @@ object Configuration {
override fun description() = schema.description()
- override fun validate(target: Config, options: Validation.Options) = schema.validate(target, options)
+ override fun validate(target: Config, options: Options) = schema.validate(target, options)
- override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue) = schema.describe(configuration, serialiseValue)
+ override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue, options: Configuration.Options) = schema.describe(configuration, serialiseValue, options)
- final override fun parse(configuration: Config, options: Configuration.Validation.Options): Valid = validate(configuration, options).mapValid(::parseValid)
+ final override fun parse(configuration: Config, options: Options): Valid = validate(configuration, options).mapValid { parseValid(it, options) }
/**
* Implement to define further mapping and validation logic, assuming the underlying raw [Config] is correct in terms of this [Configuration.Specification].
*/
- protected abstract fun parseValid(configuration: Config): Valid
+ protected abstract fun parseValid(configuration: Config, options: Options): Valid
+ }
+
+ /**
+ * Validation and processing options.
+ * @property strict whether to raise unknown property keys as errors.
+ */
+ class Options(val strict: Boolean = false) {
+
+ companion object {
+
+ /**
+ * Default [Config] options, without [strict] parsing enabled.
+ */
+ val defaults: Configuration.Options = Options()
+ }
}
object Validation {
- /**
- * [Config] validation options.
- * @property strict whether to raise unknown property keys as errors.
- */
- data class Options(val strict: Boolean) {
-
- companion object {
-
- /**
- * Default [Config] validation options, without [strict] parsing enabled.
- */
- val defaults: Configuration.Validation.Options = Options(strict = false)
- }
- }
-
/**
* Super-type for the errors raised by the parsing and validation of a [Config] object.
*
@@ -531,7 +539,7 @@ object Configuration {
}
/**
- * Raised when a key-value pair appeared in the [Config] object without a matching property in the [Configuration.Schema], and [Configuration.Validation.Options.strict] was enabled.
+ * Raised when a key-value pair appeared in the [Config] object without a matching property in the [Configuration.Schema], and [Configuration.Options.strict] was enabled.
*/
class Unknown private constructor(override val keyName: String, containingPath: List = emptyList()) : Configuration.Validation.Error(keyName, null, message(keyName), containingPath) {
@@ -586,5 +594,5 @@ object Configuration {
/**
* Defines the ability to validate a [Config] object, producing a valid [Config] or a set of [Configuration.Validation.Error].
*/
- interface Validator : net.corda.common.validation.internal.Validator
+ interface Validator : net.corda.common.validation.internal.Validator
}
\ No newline at end of file
diff --git a/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Properties.kt b/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Properties.kt
index 4ce49fc5e6..048492240a 100644
--- a/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Properties.kt
+++ b/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Properties.kt
@@ -5,10 +5,9 @@ import net.corda.common.validation.internal.Validated
import net.corda.common.validation.internal.Validated.Companion.invalid
import net.corda.common.validation.internal.Validated.Companion.valid
-internal class LongProperty(key: String, sensitive: Boolean = false) : StandardProperty(key, Long::class.javaObjectType.simpleName, Config::getLong, Config::getLongList, sensitive) {
-
- override fun validate(target: Config, options: Configuration.Validation.Options): Valid {
+internal class LongProperty(key: String, sensitive: Boolean = false) : StandardProperty(key, Long::class.javaObjectType.simpleName, { config, path, _ -> config.getLong(path) }, { config, path, _ -> config.getLongList(path) }, sensitive) {
+ override fun validate(target: Config, options: Configuration.Options): Valid {
val validated = super.validate(target, options)
if (validated.isValid && target.getValue(key).unwrapped().toString().contains(".")) {
return invalid(ConfigException.WrongType(target.origin(), key, Long::class.javaObjectType.simpleName, Double::class.javaObjectType.simpleName).toValidationError(key, typeName))
@@ -17,9 +16,11 @@ internal class LongProperty(key: String, sensitive: Boolean = false) : StandardP
}
}
-internal open class StandardProperty(override val key: String, typeNameArg: String, private val extractSingleValue: (Config, String) -> TYPE, internal val extractListValue: (Config, String) -> List, override val isSensitive: Boolean = false, final override val schema: Configuration.Schema? = null) : Configuration.Property.Definition.Standard {
+typealias ValueSelector = (Config, String, Configuration.Options) -> T
- override fun valueIn(configuration: Config) = extractSingleValue.invoke(configuration, key)
+internal open class StandardProperty(override val key: String, typeNameArg: String, private val extractSingleValue: ValueSelector, internal val extractListValue: ValueSelector>, override val isSensitive: Boolean = false, final override val schema: Configuration.Schema? = null) : Configuration.Property.Definition.Standard {
+
+ override fun valueIn(configuration: Config, options: Configuration.Options) = extractSingleValue.invoke(configuration, key, options)
override val typeName: String = schema?.let { "#${it.name ?: "Object@$key"}" } ?: typeNameArg
@@ -29,20 +30,18 @@ internal open class StandardProperty(override val key: String, typeN
override fun list(): Configuration.Property.Definition.RequiredList = ListProperty(this)
- override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue): ConfigValue {
-
+ override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue, options: Configuration.Options): ConfigValue {
if (isSensitive) {
return valueDescription(Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER, serialiseValue)
}
- return schema?.describe(configuration.getConfig(key), serialiseValue) ?: valueDescription(valueIn(configuration), serialiseValue)
+ return schema?.describe(configuration.getConfig(key), serialiseValue, options) ?: valueDescription(valueIn(configuration, options), serialiseValue)
}
override val isMandatory = true
- override fun validate(target: Config, options: Configuration.Validation.Options): Valid {
-
+ override fun validate(target: Config, options: Configuration.Options): Valid {
val errors = mutableSetOf()
- errors += errorsWhenExtractingValue(target)
+ errors += errorsWhenExtractingValue(target, options)
if (errors.isEmpty()) {
schema?.let { nestedSchema ->
val nestedConfig: Config? = target.getConfig(key)
@@ -61,15 +60,19 @@ private class ListProperty(delegate: StandardProperty) : Requi
override val typeName: String = "List<${delegate.typeName}>"
- override fun valueIn(configuration: Config): List = delegate.extractListValue.invoke(configuration, key)
-
- override fun validate(target: Config, options: Configuration.Validation.Options): Valid {
+ override fun valueIn(configuration: Config, options: Configuration.Options): List = delegate.extractListValue.invoke(configuration, key, options)
+ override fun validate(target: Config, options: Configuration.Options): Valid {
val errors = mutableSetOf()
- errors += errorsWhenExtractingValue(target)
+ errors += errorsWhenExtractingValue(target, options)
if (errors.isEmpty()) {
delegate.schema?.let { schema ->
- errors += valueIn(target).asSequence().map { element -> element as ConfigObject }.map(ConfigObject::toConfig).mapIndexed { index, targetConfig -> schema.validate(targetConfig, options).errors.map { error -> error.withContainingPath(*error.containingPath(index).toTypedArray()) } }.fold(emptyList()) { one, other -> one + other }.toSet()
+ errors += valueIn(target, options).asSequence()
+ .map { element -> element as ConfigObject }
+ .map(ConfigObject::toConfig)
+ .mapIndexed { index, targetConfig -> schema.validate(targetConfig, options).errors.map { error -> error.withContainingPath(*error.containingPath(index).toTypedArray()) } }
+ .fold(emptyList()) { one, other -> one + other }
+ .toSet()
}
}
return Validated.withResult(target, errors)
@@ -77,17 +80,16 @@ private class ListProperty(delegate: StandardProperty) : Requi
override fun mapValid(mappedTypeName: String, convert: (List) -> Validated): Configuration.Property.Definition.Required = ListMappingProperty(this, mappedTypeName, convert)
- override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue): ConfigValue {
-
+ override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue, options: Configuration.Options): ConfigValue {
if (isSensitive) {
return valueDescription(Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER, serialiseValue)
}
return when {
delegate.schema != null -> {
- val elementsDescription = valueIn(configuration).asSequence().map { it as ConfigObject }.map(ConfigObject::toConfig).map { delegate.schema.describe(it, serialiseValue) }.toList()
+ val elementsDescription = valueIn(configuration, options).asSequence().map { it as ConfigObject }.map(ConfigObject::toConfig).map { delegate.schema.describe(it, serialiseValue, options) }.toList()
ConfigValueFactory.fromIterable(elementsDescription)
}
- else -> valueDescription(valueIn(configuration), serialiseValue)
+ else -> valueDescription(valueIn(configuration, options), serialiseValue)
}
}
@@ -106,16 +108,17 @@ private class OptionalPropertyWithDefault(delegate: Configuration.Property
override val typeName: String = delegate.typeName.removeSuffix("?")
- override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue): ConfigValue? = delegate.describe(configuration, serialiseValue) ?: valueDescription(if (isSensitive) Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER else defaultValue, serialiseValue)
+ override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue, options: Configuration.Options): ConfigValue? = delegate.describe(configuration, serialiseValue, options) ?: valueDescription(if (isSensitive) Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER else defaultValue, serialiseValue)
- override fun valueIn(configuration: Config): TYPE = delegate.valueIn(configuration) ?: defaultValue
+ override fun valueIn(configuration: Config, options: Configuration.Options): TYPE = delegate.valueIn(configuration, options) ?: defaultValue
- override fun validate(target: Config, options: Configuration.Validation.Options): Valid = delegate.validate(target, options)
+ override fun validate(target: Config, options: Configuration.Options): Valid = delegate.validate(target, options)
}
-private class FunctionalProperty(delegate: Configuration.Property.Definition.Standard, private val mappedTypeName: String, internal val extractListValue: (Config, String) -> List, private val convert: (TYPE) -> Valid) : RequiredDelegatedProperty>(delegate), Configuration.Property.Definition.Standard {
+private class FunctionalProperty(delegate: Configuration.Property.Definition.Standard, private val mappedTypeName: String, internal val extractListValue: ValueSelector>, private val convert: (TYPE) -> Valid)
+ : RequiredDelegatedProperty>(delegate), Configuration.Property.Definition.Standard {
- override fun valueIn(configuration: Config) = convert.invoke(delegate.valueIn(configuration)).value()
+ override fun valueIn(configuration: Config, options: Configuration.Options) = convert.invoke(delegate.valueIn(configuration, options)).value()
override val typeName: String = if (super.typeName == "#$mappedTypeName") super.typeName else "$mappedTypeName(${super.typeName})"
@@ -123,29 +126,31 @@ private class FunctionalProperty(delegate: Configuration.Property.
override fun list(): Configuration.Property.Definition.RequiredList = FunctionalListProperty(this)
- override fun validate(target: Config, options: Configuration.Validation.Options): Valid {
-
+ override fun validate(target: Config, options: Configuration.Options): Valid {
val errors = mutableSetOf()
errors += delegate.validate(target, options).errors
if (errors.isEmpty()) {
- errors += convert.invoke(delegate.valueIn(target)).mapErrors { error -> error.with(delegate.key, mappedTypeName) }.errors
+ errors += convert.invoke(delegate.valueIn(target, options)).mapErrors { error -> error.with(delegate.key, mappedTypeName) }.errors
}
return Validated.withResult(target, errors)
}
- override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue) = delegate.describe(configuration, serialiseValue)
+ override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue, options: Configuration.Options) = delegate.describe(configuration, serialiseValue, options)
}
private class FunctionalListProperty(delegate: FunctionalProperty) : RequiredDelegatedProperty, FunctionalProperty>(delegate), Configuration.Property.Definition.RequiredList {
override val typeName: String = "List<${super.typeName}>"
- override fun valueIn(configuration: Config): List = delegate.extractListValue.invoke(configuration, key).asSequence().map { configObject(key to ConfigValueFactory.fromAnyRef(it)) }.map(ConfigObject::toConfig).map(delegate::valueIn).toList()
-
- override fun validate(target: Config, options: Configuration.Validation.Options): Valid {
+ override fun valueIn(configuration: Config, options: Configuration.Options): List = delegate.extractListValue.invoke(configuration, key, options).asSequence()
+ .map { configObject(key to ConfigValueFactory.fromAnyRef(it)) }
+ .map(ConfigObject::toConfig)
+ .map { delegate.valueIn(it, options) }
+ .toList()
+ override fun validate(target: Config, options: Configuration.Options): Valid {
val list = try {
- delegate.extractListValue.invoke(target, key)
+ delegate.extractListValue.invoke(target, key, options)
} catch (e: ConfigException) {
if (isErrorExpected(e)) {
return invalid(e.toValidationError(key, typeName))
@@ -153,7 +158,11 @@ private class FunctionalListProperty(delegate: FunctionalProperty delegate.validate(value.toConfig(), options).errors.map { error -> error.withContainingPath(*error.containingPath(index).toTypedArray()) } }.fold(emptyList()) { one, other -> one + other }.toSet()
+ val errors = list.asSequence()
+ .map { configObject(key to ConfigValueFactory.fromAnyRef(it)) }
+ .mapIndexed { index, value -> delegate.validate(value.toConfig(), options).errors.map { error -> error.withContainingPath(*error.containingPath(index).toTypedArray()) } }
+ .fold(emptyList()) { one, other -> one + other }
+ .toSet()
return Validated.withResult(target, errors)
}
@@ -165,12 +174,11 @@ private class FunctionalListProperty(delegate: FunctionalProperty ConfigValue): ConfigValue {
-
+ override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue, options: Configuration.Options): ConfigValue {
if (isSensitive) {
return valueDescription(Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER, serialiseValue)
}
- return delegate.schema?.let { schema -> valueDescription(valueIn(configuration).asSequence().map { element -> valueDescription(element, serialiseValue) }.map { it as ConfigObject }.map(ConfigObject::toConfig).map { schema.describe(it, serialiseValue) }.toList(), serialiseValue) } ?: valueDescription(valueIn(configuration), serialiseValue)
+ return delegate.schema?.let { schema -> valueDescription(valueIn(configuration, options).asSequence() .map { element -> valueDescription(element, serialiseValue) } .map { it as ConfigObject } .map(ConfigObject::toConfig) .map { schema.describe(it, serialiseValue, options) } .toList(), serialiseValue) } ?: valueDescription(valueIn(configuration, options), serialiseValue)
}
override fun mapValid(mappedTypeName: String, convert: (List) -> Validated): Configuration.Property.Definition.Required = ListMappingProperty(this, mappedTypeName, convert)
@@ -187,18 +195,16 @@ private class OptionalDelegatedProperty(private val delegate: Configuratio
override val typeName: String = "${delegate.typeName}?"
- override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue) = if (isSpecifiedBy(configuration)) delegate.describe(configuration, serialiseValue) else null
-
- override fun valueIn(configuration: Config): TYPE? {
+ override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue, options: Configuration.Options) = if (isSpecifiedBy(configuration)) delegate.describe(configuration, serialiseValue, options) else null
+ override fun valueIn(configuration: Config, options: Configuration.Options): TYPE? {
return when {
- isSpecifiedBy(configuration) -> delegate.valueIn(configuration)
+ isSpecifiedBy(configuration) -> delegate.valueIn(configuration, options)
else -> null
}
}
- override fun validate(target: Config, options: Configuration.Validation.Options): Valid {
-
+ override fun validate(target: Config, options: Configuration.Options): Valid {
val result = delegate.validate(target, options)
val errors = result.errors
val missingValueError = errors.asSequence().filterIsInstance().filter { it.pathAsString == key }.singleOrNull()
@@ -221,18 +227,17 @@ private abstract class RequiredDelegatedProperty(private val delegate: Configuration.Property.Definition.RequiredList, private val mappedTypeName: String, private val convert: (List) -> Validated) : Configuration.Property.Definition.Required {
- override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue): ConfigValue? = delegate.describe(configuration, serialiseValue)
+ override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue, options: Configuration.Options): ConfigValue? = delegate.describe(configuration, serialiseValue, options)
- override fun valueIn(configuration: Config) = convert.invoke(delegate.valueIn(configuration)).value()
+ override fun valueIn(configuration: Config, options: Configuration.Options) = convert.invoke(delegate.valueIn(configuration, options)).value()
override fun optional(): Configuration.Property.Definition.Optional = OptionalDelegatedProperty(this)
- override fun validate(target: Config, options: Configuration.Validation.Options): Validated {
-
+ override fun validate(target: Config, options: Configuration.Options): Validated {
val errors = mutableSetOf()
errors += delegate.validate(target, options).errors
if (errors.isEmpty()) {
- errors += convert.invoke(delegate.valueIn(target)).mapErrors { error -> error.with(delegate.key, mappedTypeName) }.errors
+ errors += convert.invoke(delegate.valueIn(target, options)).mapErrors { error -> error.with(delegate.key, mappedTypeName) }.errors
}
return Validated.withResult(target, errors)
}
@@ -248,7 +253,6 @@ private class ListMappingProperty(private val delegate: Configurat
}
fun ConfigException.toValidationError(keyName: String? = null, typeName: String): Configuration.Validation.Error {
-
val toError = when (this) {
is ConfigException.Missing -> Configuration.Validation.Error.MissingValue.Companion::of
is ConfigException.WrongType -> Configuration.Validation.Error.WrongType.Companion::of
@@ -260,10 +264,9 @@ fun ConfigException.toValidationError(keyName: String? = null, typeName: String)
return toError.invoke(message!!, keyName, typeName, emptyList())
}
-private fun Configuration.Property.Definition<*>.errorsWhenExtractingValue(target: Config): Set {
-
+private fun Configuration.Property.Definition<*>.errorsWhenExtractingValue(target: Config, options: Configuration.Options): Set {
try {
- valueIn(target)
+ valueIn(target, options)
return emptySet()
} catch (exception: ConfigException) {
if (isErrorExpected(exception)) {
diff --git a/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Schema.kt b/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Schema.kt
index 348714fef5..48b3fa78d0 100644
--- a/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Schema.kt
+++ b/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Schema.kt
@@ -16,7 +16,7 @@ internal class Schema(override val name: String?, unorderedProperties: Iterable<
}
}
- override fun validate(target: Config, options: Configuration.Validation.Options): Valid {
+ override fun validate(target: Config, options: Configuration.Options): Valid {
val propertyErrors = properties.flatMap { property ->
property.validate(target, options).errors
@@ -47,9 +47,9 @@ internal class Schema(override val name: String?, unorderedProperties: Iterable<
return description.toString()
}
- override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue): ConfigValue {
+ override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue, options: Configuration.Options): ConfigValue {
- return properties.asSequence().map { it.key to it.describe(configuration, serialiseValue) }.filter { it.second != null }.fold(configObject()) { config, (key, value) -> config.withValue(key, value) }
+ return properties.asSequence().map { it.key to it.describe(configuration, serialiseValue, options) }.filter { it.second != null }.fold(configObject()) { config, (key, value) -> config.withValue(key, value) }
}
override fun equals(other: Any?): Boolean {
diff --git a/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Utils.kt b/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Utils.kt
index e7455a38a7..e2ba999108 100644
--- a/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Utils.kt
+++ b/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Utils.kt
@@ -21,7 +21,11 @@ inline fun Configuration.Property.Definition.RequiredList
inline fun Configuration.Property.Definition.RequiredList.map(noinline convert: (List) -> MAPPED): Configuration.Property.Definition.Required = map(MAPPED::class.java.simpleName, convert)
-operator fun Config.get(property: Configuration.Property.Definition): TYPE = property.valueIn(this)
+fun Config.withOptions(options: Configuration.Options) = ConfigurationWithOptions(this, options)
+
+data class ConfigurationWithOptions(private val config: Config, private val options: Configuration.Options) {
+ operator fun get(property: Configuration.Value.Extractor): TYPE = property.valueIn(config, options)
+}
inline fun Configuration.Specification<*>.nested(specification: Configuration.Specification, key: String? = null, sensitive: Boolean = false): PropertyDelegate.Standard = nestedObject(schema = specification, key = key, sensitive = sensitive).map(ConfigObject::toConfig).mapValid { value -> specification.parse(value) }
@@ -66,15 +70,6 @@ internal typealias Valid = Validated valid(target: TYPE) = Validated.valid(target)
-/**
- * Value extracted from a configuration file is a function of the actual value specified and configuration options.
- * E.g. password value may be stored in the encrypted form rather than in a clear text.
- */
-data class ConfigurationWithOptions(private val config: Config, private val options: Configuration.Validation.Options) {
- operator fun get(property: Configuration.Property.Definition): TYPE = property.valueIn(config)
- operator fun get(property: Configuration.Value.Extractor): TYPE = property.valueIn(config)
-}
-
/**
* Helper interface to mark objects that will have [ConfigurationWithOptions] in them.
*/
diff --git a/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/versioned/VersionExtractor.kt b/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/versioned/VersionExtractor.kt
index 55e34d6467..aefe57d132 100644
--- a/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/versioned/VersionExtractor.kt
+++ b/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/versioned/VersionExtractor.kt
@@ -6,21 +6,17 @@ import net.corda.common.configuration.parsing.internal.Valid
import net.corda.common.configuration.parsing.internal.valid
internal class VersionExtractor(versionPath: String, versionDefaultValue: Int) : Configuration.Version.Extractor {
-
private val containingPath = versionPath.split(".").let { if (it.size > 1) it.subList(0, it.size - 1) else null }
private val key = versionPath.split(".").last()
private val spec = Spec(key, versionDefaultValue, containingPath?.joinToString("."))
- override fun parse(configuration: Config, options: Configuration.Validation.Options): Valid {
-
+ override fun parse(configuration: Config, options: Configuration.Options): Valid {
return spec.parse(configuration)
}
private class Spec(key: String, versionDefaultValue: Int, prefix: String?) : Configuration.Specification("Version", prefix) {
-
private val version by int(key = key).optional().withDefaultValue(versionDefaultValue)
-
- override fun parseValid(configuration: Config) = valid(version.valueIn(configuration))
+ override fun parseValid(configuration: Config, options: Configuration.Options) = valid(version.valueIn(configuration, options))
}
}
\ No newline at end of file
diff --git a/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/PropertyTest.kt b/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/PropertyTest.kt
index 61dbd39551..75a08a2f59 100644
--- a/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/PropertyTest.kt
+++ b/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/PropertyTest.kt
@@ -21,8 +21,8 @@ class PropertyTest {
assertThat(property.key).isEqualTo(key)
assertThat(property.isMandatory).isTrue()
assertThat(property.isSpecifiedBy(configuration)).isTrue()
- assertThat(property.valueIn(configuration)).isEqualTo(value)
- assertThat(configuration[property]).isEqualTo(value)
+ assertThat(property.valueIn(configuration, Configuration.Options.defaults)).isEqualTo(value)
+ assertThat(configuration.withOptions(Configuration.Options.defaults)[property]).isEqualTo(value)
}
@Test(timeout=300_000)
@@ -38,7 +38,7 @@ class PropertyTest {
assertThat(property.key).isEqualTo(key)
assertThat(property.isMandatory).isTrue()
assertThat(property.isSpecifiedBy(configuration)).isTrue()
- assertThatThrownBy { property.valueIn(configuration) }.isInstanceOf(ConfigException.WrongType::class.java)
+ assertThatThrownBy { property.valueIn(configuration, Configuration.Options.defaults) }.isInstanceOf(ConfigException.WrongType::class.java)
}
@Test(timeout=300_000)
@@ -54,7 +54,7 @@ class PropertyTest {
assertThat(property.key).isEqualTo(key)
assertThat(property.isMandatory).isTrue()
assertThat(property.isSpecifiedBy(configuration)).isTrue()
- assertThat(property.valueIn(configuration)).isEqualTo(value)
+ assertThat(property.valueIn(configuration, Configuration.Options.defaults)).isEqualTo(value)
}
@Test(timeout=300_000)
@@ -70,7 +70,7 @@ class PropertyTest {
assertThat(property.key).isEqualTo(key)
assertThat(property.isMandatory).isTrue()
assertThat(property.isSpecifiedBy(configuration)).isTrue()
- assertThat(property.valueIn(configuration)).isEqualTo(value.max())
+ assertThat(property.valueIn(configuration, Configuration.Options.defaults)).isEqualTo(value.max())
}
@Test(timeout=300_000)
@@ -85,7 +85,7 @@ class PropertyTest {
assertThat(property.key).isEqualTo(key)
assertThat(property.isMandatory).isFalse()
assertThat(property.isSpecifiedBy(configuration)).isFalse()
- assertThat(property.valueIn(configuration)).isEqualTo(null)
+ assertThat(property.valueIn(configuration, Configuration.Options.defaults)).isEqualTo(null)
}
@Test(timeout=300_000)
@@ -101,7 +101,7 @@ class PropertyTest {
assertThat(property.key).isEqualTo(key)
assertThat(property.isMandatory).isTrue()
assertThat(property.isSpecifiedBy(configuration)).isTrue()
- assertThat(property.valueIn(configuration)).isEqualTo(value.max())
+ assertThat(property.valueIn(configuration, Configuration.Options.defaults)).isEqualTo(value.max())
}
@Test(timeout=300_000)
@@ -116,7 +116,7 @@ class PropertyTest {
assertThat(property.key).isEqualTo(key)
assertThat(property.isMandatory).isFalse()
assertThat(property.isSpecifiedBy(configuration)).isFalse()
- assertThat(property.valueIn(configuration)).isEqualTo(null)
+ assertThat(property.valueIn(configuration, Configuration.Options.defaults)).isEqualTo(null)
}
@Test(timeout=300_000)
@@ -132,7 +132,7 @@ class PropertyTest {
assertThat(property.key).isEqualTo(key)
assertThat(property.isMandatory).isFalse()
assertThat(property.isSpecifiedBy(configuration)).isTrue()
- assertThat(property.valueIn(configuration)).isEqualTo(value)
+ assertThat(property.valueIn(configuration, Configuration.Options.defaults)).isEqualTo(value)
}
@Test(timeout=300_000)
@@ -147,7 +147,7 @@ class PropertyTest {
assertThat(property.key).isEqualTo(key)
assertThat(property.isMandatory).isFalse()
assertThat(property.isSpecifiedBy(configuration)).isFalse()
- assertThat(property.valueIn(configuration)).isNull()
+ assertThat(property.valueIn(configuration, Configuration.Options.defaults)).isNull()
}
@@ -164,7 +164,7 @@ class PropertyTest {
assertThat(property.key).isEqualTo(key)
assertThat(property.isMandatory).isFalse()
assertThat(property.isSpecifiedBy(configuration)).isFalse()
- assertThat(property.valueIn(configuration)).isEqualTo(defaultValue)
+ assertThat(property.valueIn(configuration, Configuration.Options.defaults)).isEqualTo(defaultValue)
}
@Test(timeout=300_000)
@@ -179,7 +179,7 @@ class PropertyTest {
assertThat(property.key).isEqualTo(key)
assertThat(property.isMandatory).isTrue()
assertThat(property.isSpecifiedBy(configuration)).isFalse()
- assertThatThrownBy { property.valueIn(configuration) }.isInstanceOf(ConfigException.Missing::class.java)
+ assertThatThrownBy { property.valueIn(configuration, Configuration.Options.defaults) }.isInstanceOf(ConfigException.Missing::class.java)
}
@Test(timeout=300_000)
@@ -195,7 +195,7 @@ class PropertyTest {
assertThat(property.key).isEqualTo(key)
assertThat(property.isMandatory).isFalse()
assertThat(property.isSpecifiedBy(configuration)).isTrue()
- assertThat(property.valueIn(configuration)).isEqualTo(value)
+ assertThat(property.valueIn(configuration, Configuration.Options.defaults)).isEqualTo(value)
}
@Test(timeout=300_000)
@@ -211,7 +211,7 @@ class PropertyTest {
assertThat(property.key).isEqualTo(key)
assertThat(property.isMandatory).isFalse()
assertThat(property.isSpecifiedBy(configuration)).isTrue()
- assertThatThrownBy { property.valueIn(configuration) }.isInstanceOf(ConfigException.WrongType::class.java)
+ assertThatThrownBy { property.valueIn(configuration, Configuration.Options.defaults) }.isInstanceOf(ConfigException.WrongType::class.java)
}
@Test(timeout=300_000)
@@ -226,7 +226,7 @@ class PropertyTest {
assertThat(property.key).isEqualTo(key)
assertThat(property.isMandatory).isFalse()
assertThat(property.isSpecifiedBy(configuration)).isFalse()
- assertThat(property.valueIn(configuration)).isNull()
+ assertThat(property.valueIn(configuration, Configuration.Options.defaults)).isNull()
}
@Test(timeout=300_000)
@@ -242,6 +242,6 @@ class PropertyTest {
assertThat(property.key).isEqualTo(key)
assertThat(property.isMandatory).isFalse()
assertThat(property.isSpecifiedBy(configuration)).isFalse()
- assertThat(property.valueIn(configuration)).isEqualTo(defaultValue)
+ assertThat(property.valueIn(configuration, Configuration.Options.defaults)).isEqualTo(defaultValue)
}
}
\ No newline at end of file
diff --git a/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/PropertyValidationTest.kt b/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/PropertyValidationTest.kt
index 3c03a74c33..0c748f2e70 100644
--- a/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/PropertyValidationTest.kt
+++ b/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/PropertyValidationTest.kt
@@ -15,7 +15,7 @@ class PropertyValidationTest {
val property = Configuration.Property.Definition.long(key)
- assertThat(property.validate(configuration).errors).satisfies { errors ->
+ assertThat(property.validate(configuration, Configuration.Options.defaults).errors).satisfies { errors ->
assertThat(errors).hasSize(1)
assertThat(errors.first()).isInstanceOfSatisfying(Configuration.Validation.Error.MissingValue::class.java) { error ->
@@ -34,7 +34,7 @@ class PropertyValidationTest {
val property = Configuration.Property.Definition.long(key)
- assertThat(property.validate(configuration).errors).satisfies { errors ->
+ assertThat(property.validate(configuration, Configuration.Options.defaults).errors).satisfies { errors ->
assertThat(errors).hasSize(1)
assertThat(errors.first()).isInstanceOfSatisfying(Configuration.Validation.Error.MissingValue::class.java) { error ->
@@ -53,7 +53,7 @@ class PropertyValidationTest {
val property = Configuration.Property.Definition.long(key).list()
- assertThat(property.validate(configuration).errors).satisfies { errors ->
+ assertThat(property.validate(configuration, Configuration.Options.defaults).errors).satisfies { errors ->
assertThat(errors).hasSize(1)
assertThat(errors.first()).isInstanceOfSatisfying(Configuration.Validation.Error.MissingValue::class.java) { error ->
@@ -72,7 +72,7 @@ class PropertyValidationTest {
val property = Configuration.Property.Definition.long(key).list()
- assertThat(property.validate(configuration).errors).satisfies { errors ->
+ assertThat(property.validate(configuration, Configuration.Options.defaults).errors).satisfies { errors ->
assertThat(errors).hasSize(1)
assertThat(errors.first()).isInstanceOfSatisfying(Configuration.Validation.Error.MissingValue::class.java) { error ->
@@ -94,7 +94,7 @@ class PropertyValidationTest {
val property = Configuration.Property.Definition.long(key).list().mapValid(::parseMax)
- assertThat(property.validate(configuration).errors).isEmpty()
+ assertThat(property.validate(configuration, Configuration.Options.defaults).errors).isEmpty()
}
@Test(timeout=300_000)
@@ -114,7 +114,7 @@ class PropertyValidationTest {
val property = Configuration.Property.Definition.long(key).list().mapValid(::parseMax)
- assertThat(property.validate(configuration).errors).satisfies { errors ->
+ assertThat(property.validate(configuration, Configuration.Options.defaults).errors).satisfies { errors ->
assertThat(errors).hasSize(1)
assertThat(errors.first()).isInstanceOfSatisfying(Configuration.Validation.Error.BadValue::class.java) { error ->
@@ -134,7 +134,7 @@ class PropertyValidationTest {
val configuration = configObject(key to false).toConfig()
- assertThat(property.validate(configuration).errors).satisfies { errors ->
+ assertThat(property.validate(configuration, Configuration.Options.defaults).errors).satisfies { errors ->
assertThat(errors).hasSize(1)
assertThat(errors.first()).isInstanceOfSatisfying(Configuration.Validation.Error.WrongType::class.java) { error ->
@@ -154,7 +154,7 @@ class PropertyValidationTest {
val configuration = configObject(key to 1.2).toConfig()
- assertThat(property.validate(configuration).errors).satisfies { errors ->
+ assertThat(property.validate(configuration, Configuration.Options.defaults).errors).satisfies { errors ->
assertThat(errors).hasSize(1)
assertThat(errors.first()).isInstanceOfSatisfying(Configuration.Validation.Error.WrongType::class.java) { error ->
@@ -174,7 +174,7 @@ class PropertyValidationTest {
val configuration = configObject(key to 1).toConfig()
- assertThat(property.validate(configuration).isValid).isTrue()
+ assertThat(property.validate(configuration, Configuration.Options.defaults).isValid).isTrue()
}
@Test(timeout=300_000)
@@ -186,7 +186,7 @@ class PropertyValidationTest {
val configuration = configObject(key to listOf(false, true)).toConfig()
- assertThat(property.validate(configuration).errors).satisfies { errors ->
+ assertThat(property.validate(configuration, Configuration.Options.defaults).errors).satisfies { errors ->
assertThat(errors).hasSize(1)
assertThat(errors.first()).isInstanceOfSatisfying(Configuration.Validation.Error.WrongType::class.java) { error ->
@@ -206,7 +206,7 @@ class PropertyValidationTest {
val configuration = configObject(key to listOf(1, 2, 3)).toConfig()
- assertThat(property.validate(configuration).errors).satisfies { errors ->
+ assertThat(property.validate(configuration, Configuration.Options.defaults).errors).satisfies { errors ->
assertThat(errors).hasSize(1)
assertThat(errors.first()).isInstanceOfSatisfying(Configuration.Validation.Error.WrongType::class.java) { error ->
@@ -226,7 +226,7 @@ class PropertyValidationTest {
val configuration = configObject(key to 1).toConfig()
- assertThat(property.validate(configuration).errors).satisfies { errors ->
+ assertThat(property.validate(configuration, Configuration.Options.defaults).errors).satisfies { errors ->
assertThat(errors).hasSize(1)
assertThat(errors.first()).isInstanceOfSatisfying(Configuration.Validation.Error.WrongType::class.java) { error ->
@@ -249,7 +249,7 @@ class PropertyValidationTest {
val configuration = configObject(key to configObject(nestedKey to false)).toConfig()
- assertThat(property.validate(configuration).errors).satisfies { errors ->
+ assertThat(property.validate(configuration, Configuration.Options.defaults).errors).satisfies { errors ->
assertThat(errors).hasSize(1)
assertThat(errors.first()).isInstanceOfSatisfying(Configuration.Validation.Error.WrongType::class.java) { error ->
@@ -272,7 +272,7 @@ class PropertyValidationTest {
val configuration = configObject(key to configObject()).toConfig()
- assertThat(property.validate(configuration).errors).satisfies { errors ->
+ assertThat(property.validate(configuration, Configuration.Options.defaults).errors).satisfies { errors ->
assertThat(errors).hasSize(1)
assertThat(errors.first()).isInstanceOfSatisfying(Configuration.Validation.Error.MissingValue::class.java) { error ->
@@ -295,7 +295,7 @@ class PropertyValidationTest {
val configuration = configObject(key to configObject(nestedKey to null)).toConfig()
- assertThat(property.validate(configuration).errors).satisfies { errors ->
+ assertThat(property.validate(configuration, Configuration.Options.defaults).errors).satisfies { errors ->
assertThat(errors).hasSize(1)
assertThat(errors.first()).isInstanceOfSatisfying(Configuration.Validation.Error.MissingValue::class.java) { error ->
@@ -317,7 +317,7 @@ class PropertyValidationTest {
val configuration = configObject(key to configObject(nestedKey to false)).toConfig()
- assertThat(property.validate(configuration).isValid).isTrue()
+ assertThat(property.validate(configuration, Configuration.Options.defaults).isValid).isTrue()
}
@Test(timeout=300_000)
@@ -333,7 +333,7 @@ class PropertyValidationTest {
val configuration = configObject(key to value).toConfig()
- assertThat(property.validate(configuration).isValid).isTrue()
+ assertThat(property.validate(configuration, Configuration.Options.defaults).isValid).isTrue()
}
@Test(timeout=300_000)
@@ -350,7 +350,7 @@ class PropertyValidationTest {
val configuration = configObject(key to value).toConfig()
- val result = property.validate(configuration)
+ val result = property.validate(configuration, Configuration.Options.defaults)
assertThat(result.errors).satisfies { errors ->
diff --git a/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/SchemaTest.kt b/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/SchemaTest.kt
index f6393bd39b..2ad58808f2 100644
--- a/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/SchemaTest.kt
+++ b/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/SchemaTest.kt
@@ -29,7 +29,7 @@ class SchemaTest {
val fooConfigSchema = Configuration.Schema.withProperties(name = "Foo") { setOf(boolean("prop4"), double("prop5")) }
val barConfigSchema = Configuration.Schema.withProperties(name = "Bar") { setOf(string(prop1), long(prop2), nestedObject("prop3", fooConfigSchema)) }
- val result = barConfigSchema.validate(configuration)
+ val result = barConfigSchema.validate(configuration, Configuration.Options.defaults)
println(barConfigSchema.description())
assertThat(result.isValid).isTrue()
@@ -59,17 +59,17 @@ class SchemaTest {
val fooConfigSchema = Configuration.Schema.withProperties { setOf(boolean("prop4"), double("prop5")) }
val barConfigSchema = Configuration.Schema.withProperties { setOf(string(prop1), long(prop2), nestedObject("prop3", fooConfigSchema)) }
- val strictErrors = barConfigSchema.validate(configuration, Configuration.Validation.Options(strict = true)).errors
+ val strictErrors = barConfigSchema.validate(configuration, Configuration.Options(strict = true)).errors
assertThat(strictErrors).hasSize(2)
assertThat(strictErrors.filter { error -> error.keyName == "prop4" }).hasSize(1)
assertThat(strictErrors.filter { error -> error.keyName == "prop6" }).hasSize(1)
- val errors = barConfigSchema.validate(configuration, Configuration.Validation.Options(strict = false)).errors
+ val errors = barConfigSchema.validate(configuration, Configuration.Options(strict = false)).errors
assertThat(errors).isEmpty()
- val errorsWithDefaultOptions = barConfigSchema.validate(configuration).errors
+ val errorsWithDefaultOptions = barConfigSchema.validate(configuration, Configuration.Options.defaults).errors
assertThat(errorsWithDefaultOptions).isEmpty()
}
@@ -98,7 +98,7 @@ class SchemaTest {
val fooConfigSchema = Configuration.Schema.withProperties { setOf(boolean("prop4"), double("prop5")) }
val barConfigSchema = Configuration.Schema.withProperties { setOf(string(prop1), long(prop2), nestedObject("prop3", fooConfigSchema)) }
- val result = barConfigSchema.validate(configuration)
+ val result = barConfigSchema.validate(configuration, Configuration.Options.defaults)
assertThat(result.isValid).isTrue()
}
@@ -127,7 +127,7 @@ class SchemaTest {
val fooConfigSchema = Configuration.Schema.withProperties { setOf(boolean("prop4"), double("prop5")) }
val barConfigSchema = Configuration.Schema.withProperties { setOf(string(prop1), long(prop2), nestedObject("prop3", fooConfigSchema)) }
- val errors = barConfigSchema.validate(configuration).errors
+ val errors = barConfigSchema.validate(configuration, Configuration.Options.defaults).errors
errors.forEach(::println)
assertThat(errors).hasSize(2)
@@ -154,7 +154,7 @@ class SchemaTest {
val fooConfigSchema = Configuration.Schema.withProperties(name = "Foo") { setOf(boolean("prop4"), string("prop5", sensitive = true)) }
val barConfigSchema = Configuration.Schema.withProperties(name = "Bar") { setOf(string(prop1), long(prop2), nestedObject("prop3", fooConfigSchema)) }
- val printedConfiguration = barConfigSchema.describe(configuration)
+ val printedConfiguration = barConfigSchema.describe(configuration, options = Configuration.Options.defaults)
val description = printedConfiguration.serialize().also { println(it) }
@@ -185,7 +185,7 @@ class SchemaTest {
val fooConfigSchema = Configuration.Schema.withProperties(name = "Foo") { setOf(boolean("prop4"), string("prop5", sensitive = true)) }
val barConfigSchema = Configuration.Schema.withProperties(name = "Bar") { setOf(string(prop1), long(prop2), nestedObject("prop3", fooConfigSchema).list()) }
- val printedConfiguration = barConfigSchema.describe(configuration)
+ val printedConfiguration = barConfigSchema.describe(configuration, options = Configuration.Options.defaults)
val description = printedConfiguration.serialize().also { println(it) }
diff --git a/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/SpecificationTest.kt b/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/SpecificationTest.kt
index a78ef5ee59..96a9f181ef 100644
--- a/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/SpecificationTest.kt
+++ b/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/SpecificationTest.kt
@@ -1,7 +1,6 @@
package net.corda.common.configuration.parsing.internal
import com.typesafe.config.Config
-import net.corda.common.validation.internal.Validated
import net.corda.common.validation.internal.Validated.Companion.invalid
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
@@ -16,7 +15,7 @@ class SpecificationTest {
val principal by string().mapValid(::parseAddress)
val admin by string().mapValid(::parseAddress)
- override fun parseValid(configuration: Config) = valid(Addresses(configuration[principal], configuration[admin]))
+ override fun parseValid(configuration: Config, options: Configuration.Options) = configuration.withOptions(options).let { valid(Addresses(it[principal], it[admin])) }
private fun parseAddress(rawValue: String): Valid {
@@ -27,7 +26,7 @@ class SpecificationTest {
val useSsl by boolean()
val addresses by nested(AddressesSpec)
- override fun parseValid(configuration: Config) = valid(RpcSettingsImpl(configuration[addresses], configuration[useSsl]))
+ override fun parseValid(configuration: Config, options: Configuration.Options) = configuration.withOptions(options).let { valid(RpcSettingsImpl(it[addresses], it[useSsl])) }
}
@Test(timeout=300_000)
@@ -60,9 +59,9 @@ class SpecificationTest {
private val maxElement by long("elements").list().map { elements -> elements.max() }
- override fun parseValid(configuration: Config): Valid {
-
- return valid(AtomicLong(configuration[maxElement]!!))
+ override fun parseValid(configuration: Config, options: Configuration.Options): Valid {
+ val config = configuration.withOptions(options)
+ return valid(AtomicLong(config[maxElement]!!))
}
}
@@ -111,9 +110,9 @@ class SpecificationTest {
private val maxElement by long("elements").list().mapValid(::parseMax)
- override fun parseValid(configuration: Config): Valid {
-
- return valid(AtomicLong(configuration[maxElement]))
+ override fun parseValid(configuration: Config, options: Configuration.Options): Valid {
+ val config = configuration.withOptions(options)
+ return valid(AtomicLong(config[maxElement]))
}
}
@@ -159,7 +158,7 @@ class SpecificationTest {
@Suppress("unused")
val myProp by string().list().optional()
- override fun parseValid(configuration: Config) = valid(configuration[myProp])
+ override fun parseValid(configuration: Config, options: Configuration.Options) = configuration.withOptions(options).let { valid(it[myProp]) }
}
assertThat(spec.properties).hasSize(1)
diff --git a/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/TestUtils.kt b/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/TestUtils.kt
index bf2c2f92bf..5e604fd765 100644
--- a/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/TestUtils.kt
+++ b/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/TestUtils.kt
@@ -15,5 +15,5 @@ internal fun extractValueWithErrors(errors: Set extractValue(value: Valid) = object : Configuration.Value.Parser {
- override fun parse(configuration: Config, options: Configuration.Validation.Options): Valid = value
+ override fun parse(configuration: Config, options: Configuration.Options): Valid = value
}
\ No newline at end of file
diff --git a/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReporting.kt b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReporting.kt
index 20cdb77cf6..6ee60f8c49 100644
--- a/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReporting.kt
+++ b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReporting.kt
@@ -1,6 +1,5 @@
package net.corda.common.logging.errorReporting
-import java.lang.UnsupportedOperationException
import java.util.*
/**
diff --git a/common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/ErrorReporterImplTest.kt b/common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/ErrorReporterImplTest.kt
index 9971f8c60d..38192f81ad 100644
--- a/common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/ErrorReporterImplTest.kt
+++ b/common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/ErrorReporterImplTest.kt
@@ -7,7 +7,6 @@ import net.corda.common.logging.errorReporting.ErrorContextProvider
import net.corda.common.logging.errorReporting.ErrorReporterImpl
import org.junit.After
import org.junit.Test
-import org.junit.rules.TestName
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mockito
import org.slf4j.Logger
diff --git a/constants.properties b/constants.properties
index 6021fb6a5b..0ed8cd5e3b 100644
--- a/constants.properties
+++ b/constants.properties
@@ -11,7 +11,7 @@ java8MinUpdateVersion=171
# When incrementing platformVersion make sure to update #
# net.corda.core.internal.CordaUtilsKt.PLATFORM_VERSION as well. #
# ***************************************************************#
-platformVersion=6
+platformVersion=7
guavaVersion=28.0-jre
# Quasar version to use with Java 8:
quasarVersion=0.7.12_r3
@@ -21,7 +21,7 @@ quasarVersion11=0.8.0_r3
jdkClassifier11=jdk11
proguardVersion=6.1.1
bouncycastleVersion=1.60
-classgraphVersion=4.8.58
+classgraphVersion=4.8.68
disruptorVersion=3.4.2
typesafeConfigVersion=1.3.4
jsr305Version=3.0.2
@@ -30,7 +30,7 @@ snakeYamlVersion=1.19
caffeineVersion=2.7.0
metricsVersion=4.1.0
metricsNewRelicVersion=1.1.1
-djvmVersion=1.0-RC10
+djvmVersion=1.1-RC01
deterministicRtVersion=1.0-RC02
openSourceBranch=https://github.com/corda/corda/blob/release/os/4.4
openSourceSamplesBranch=https://github.com/corda/samples/blob/release-V4
diff --git a/core-tests/src/test/java/net/corda/coretests/flows/FlowExternalOperationInJavaTest.java b/core-tests/src/test/java/net/corda/coretests/flows/FlowExternalOperationInJavaTest.java
index 74613e5d96..ec41941360 100644
--- a/core-tests/src/test/java/net/corda/coretests/flows/FlowExternalOperationInJavaTest.java
+++ b/core-tests/src/test/java/net/corda/coretests/flows/FlowExternalOperationInJavaTest.java
@@ -23,7 +23,6 @@ import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import static net.corda.testing.driver.Driver.driver;
-import static org.junit.Assert.assertEquals;
public class FlowExternalOperationInJavaTest extends AbstractFlowExternalOperationTest {
@@ -32,16 +31,16 @@ public class FlowExternalOperationInJavaTest extends AbstractFlowExternalOperati
driver(new DriverParameters().withStartNodesInProcess(true), driver -> {
NodeHandle alice = KotlinUtilsKt.getOrThrow(
driver.startNode(new NodeParameters().withProvidedName(TestConstants.ALICE_NAME)),
- Duration.of(20, ChronoUnit.SECONDS)
+ Duration.of(1, ChronoUnit.MINUTES)
);
NodeHandle bob = KotlinUtilsKt.getOrThrow(
driver.startNode(new NodeParameters().withProvidedName(TestConstants.BOB_NAME)),
- Duration.of(20, ChronoUnit.SECONDS)
+ Duration.of(1, ChronoUnit.MINUTES)
);
return KotlinUtilsKt.getOrThrow(alice.getRpc().startFlowDynamic(
FlowWithExternalOperationInJava.class,
TestUtils.singleIdentity(bob.getNodeInfo())
- ).getReturnValue(), Duration.of(20, ChronoUnit.SECONDS));
+ ).getReturnValue(), Duration.of(1, ChronoUnit.MINUTES));
});
}
@@ -50,16 +49,16 @@ public class FlowExternalOperationInJavaTest extends AbstractFlowExternalOperati
driver(new DriverParameters().withStartNodesInProcess(true), driver -> {
NodeHandle alice = KotlinUtilsKt.getOrThrow(
driver.startNode(new NodeParameters().withProvidedName(TestConstants.ALICE_NAME)),
- Duration.of(20, ChronoUnit.SECONDS)
+ Duration.of(1, ChronoUnit.MINUTES)
);
NodeHandle bob = KotlinUtilsKt.getOrThrow(
driver.startNode(new NodeParameters().withProvidedName(TestConstants.BOB_NAME)),
- Duration.of(20, ChronoUnit.SECONDS)
+ Duration.of(1, ChronoUnit.MINUTES)
);
return KotlinUtilsKt.getOrThrow(alice.getRpc().startFlowDynamic(
FlowWithExternalAsyncOperationInJava.class,
TestUtils.singleIdentity(bob.getNodeInfo())
- ).getReturnValue(), Duration.of(20, ChronoUnit.SECONDS));
+ ).getReturnValue(), Duration.of(1, ChronoUnit.MINUTES));
});
}
@@ -68,22 +67,18 @@ public class FlowExternalOperationInJavaTest extends AbstractFlowExternalOperati
driver(new DriverParameters().withStartNodesInProcess(true), driver -> {
NodeHandle alice = KotlinUtilsKt.getOrThrow(
driver.startNode(new NodeParameters().withProvidedName(TestConstants.ALICE_NAME)),
- Duration.of(20, ChronoUnit.SECONDS)
+ Duration.of(1, ChronoUnit.MINUTES)
);
NodeHandle bob = KotlinUtilsKt.getOrThrow(
driver.startNode(new NodeParameters().withProvidedName(TestConstants.BOB_NAME)),
- Duration.of(20, ChronoUnit.SECONDS)
+ Duration.of(1, ChronoUnit.MINUTES)
);
KotlinUtilsKt.getOrThrow(alice.getRpc().startFlowDynamic(
FlowWithExternalOperationThatGetsRetriedInJava.class,
TestUtils.singleIdentity(bob.getNodeInfo())
- ).getReturnValue(), Duration.of(20, ChronoUnit.SECONDS));
+ ).getReturnValue(), Duration.of(1, ChronoUnit.MINUTES));
- HospitalCounts counts = KotlinUtilsKt.getOrThrow(alice.getRpc().startFlowDynamic(
- GetHospitalCountersFlow.class
- ).getReturnValue(), Duration.of(20, ChronoUnit.SECONDS));
- assertEquals(1, counts.getDischarge());
- assertEquals(0, counts.getObservation());
+ assertHospitalCounters(1, 0);
return null;
});
diff --git a/core-tests/src/test/kotlin/net/corda/coretests/flows/AbstractFlowExternalOperationTest.kt b/core-tests/src/test/kotlin/net/corda/coretests/flows/AbstractFlowExternalOperationTest.kt
index 52d963678d..559369c442 100644
--- a/core-tests/src/test/kotlin/net/corda/coretests/flows/AbstractFlowExternalOperationTest.kt
+++ b/core-tests/src/test/kotlin/net/corda/coretests/flows/AbstractFlowExternalOperationTest.kt
@@ -12,26 +12,55 @@ import net.corda.core.flows.StartableByRPC
import net.corda.core.flows.StartableByService
import net.corda.core.identity.Party
import net.corda.core.internal.concurrent.doOnComplete
+import net.corda.core.messaging.FlowHandle
import net.corda.core.node.AppServiceHub
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.CordaService
import net.corda.core.schemas.MappedSchema
-import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.getOrThrow
+import net.corda.core.utilities.unwrap
import net.corda.node.services.statemachine.StaffedFlowHospital
+import org.junit.Before
import java.sql.SQLTransientConnectionException
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executors
+import java.util.concurrent.Semaphore
import java.util.function.Supplier
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.Id
import javax.persistence.Table
+import kotlin.test.assertEquals
abstract class AbstractFlowExternalOperationTest {
+ var dischargeCounter = 0
+ var observationCounter = 0
+
+ @Before
+ fun before() {
+ StaffedFlowHospital.onFlowDischarged.clear()
+ StaffedFlowHospital.onFlowDischarged.add { _, _ -> ++dischargeCounter }
+ StaffedFlowHospital.onFlowKeptForOvernightObservation.clear()
+ StaffedFlowHospital.onFlowKeptForOvernightObservation.add { _, _ -> ++observationCounter }
+ dischargeCounter = 0
+ observationCounter = 0
+ }
+
+ fun blockUntilFlowKeptInForObservation(flow: () -> FlowHandle<*>) {
+ val lock = Semaphore(0)
+ StaffedFlowHospital.onFlowKeptForOvernightObservation.add { _, _ -> lock.release() }
+ flow()
+ lock.acquire()
+ }
+
+ fun assertHospitalCounters(discharge: Int, observation: Int) {
+ assertEquals(discharge, dischargeCounter)
+ assertEquals(observation, observationCounter)
+ }
+
@StartableByRPC
@InitiatingFlow
@StartableByService
@@ -44,11 +73,12 @@ abstract class AbstractFlowExternalOperationTest {
@Suspendable
override fun call(): Any {
log.info("Started my flow")
+ subFlow(PingPongFlow(party))
val result = testCode()
val session = initiateFlow(party)
- session.send("hi there")
- log.info("ServiceHub value = $serviceHub")
- session.receive()
+ session.sendAndReceive("hi there").unwrap { it }
+ session.sendAndReceive("hi there").unwrap { it }
+ subFlow(PingPongFlow(party))
log.info("Finished my flow")
return result
}
@@ -64,8 +94,28 @@ abstract class AbstractFlowExternalOperationTest {
class FlowWithExternalOperationResponder(val session: FlowSession) : FlowLogic() {
@Suspendable
override fun call() {
- session.receive()
+ session.receive().unwrap { it }
session.send("go away")
+ session.receive().unwrap { it }
+ session.send("go away")
+ }
+ }
+
+ @InitiatingFlow
+ class PingPongFlow(val party: Party): FlowLogic() {
+ @Suspendable
+ override fun call() {
+ val session = initiateFlow(party)
+ session.sendAndReceive("ping pong").unwrap { it }
+ }
+ }
+
+ @InitiatedBy(PingPongFlow::class)
+ class PingPongResponder(val session: FlowSession) : FlowLogic() {
+ @Suspendable
+ override fun call() {
+ session.receive().unwrap { it }
+ session.send("I got you bro")
}
}
@@ -83,7 +133,7 @@ abstract class AbstractFlowExternalOperationTest {
fun createFuture(): CompletableFuture {
return CompletableFuture.supplyAsync(Supplier {
log.info("Starting sleep inside of future")
- Thread.sleep(2000)
+ Thread.sleep(1000)
log.info("Finished sleep inside of future")
"Here is your return value"
}, executorService)
@@ -182,31 +232,6 @@ abstract class AbstractFlowExternalOperationTest {
object CustomMappedSchema : MappedSchema(CustomSchema::class.java, 1, listOf(CustomTableEntity::class.java))
- // Internal use for testing only!!
- @StartableByRPC
- class GetHospitalCountersFlow : FlowLogic() {
- override fun call(): HospitalCounts =
- HospitalCounts(
- serviceHub.cordaService(HospitalCounter::class.java).dischargeCounter,
- serviceHub.cordaService(HospitalCounter::class.java).observationCounter
- )
- }
-
- @CordaSerializable
- data class HospitalCounts(val discharge: Int, val observation: Int)
-
- @Suppress("UNUSED_PARAMETER")
- @CordaService
- class HospitalCounter(services: AppServiceHub) : SingletonSerializeAsToken() {
- var observationCounter: Int = 0
- var dischargeCounter: Int = 0
-
- init {
- StaffedFlowHospital.onFlowDischarged.add { _, _ -> ++dischargeCounter }
- StaffedFlowHospital.onFlowKeptForOvernightObservation.add { _, _ -> ++observationCounter }
- }
- }
-
class MyCordaException(message: String) : CordaException(message)
class DirectlyAccessedServiceHubException : CordaException("Null pointer from accessing flow's serviceHub")
diff --git a/core-tests/src/test/kotlin/net/corda/coretests/flows/FlowExternalAsyncOperationTest.kt b/core-tests/src/test/kotlin/net/corda/coretests/flows/FlowExternalAsyncOperationTest.kt
index a9d7438347..cbf1892e51 100644
--- a/core-tests/src/test/kotlin/net/corda/coretests/flows/FlowExternalAsyncOperationTest.kt
+++ b/core-tests/src/test/kotlin/net/corda/coretests/flows/FlowExternalAsyncOperationTest.kt
@@ -6,12 +6,7 @@ import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.Party
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
-import net.corda.core.utilities.seconds
-import net.corda.coretests.flows.AbstractFlowExternalOperationTest.DirectlyAccessedServiceHubException
-import net.corda.coretests.flows.AbstractFlowExternalOperationTest.ExternalAsyncOperation
-import net.corda.coretests.flows.AbstractFlowExternalOperationTest.FlowWithExternalProcess
-import net.corda.coretests.flows.AbstractFlowExternalOperationTest.FutureService
-import net.corda.coretests.flows.AbstractFlowExternalOperationTest.MyCordaException
+import net.corda.core.utilities.minutes
import net.corda.node.services.statemachine.StateTransitionException
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
@@ -21,28 +16,24 @@ import net.corda.testing.driver.driver
import org.junit.Test
import java.sql.SQLTransientConnectionException
import java.util.concurrent.CompletableFuture
-import java.util.concurrent.TimeoutException
-import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class FlowExternalAsyncOperationTest : AbstractFlowExternalOperationTest() {
- @Test(timeout=300_000)
- fun `external async operation`() {
+ @Test(timeout = 300_000)
+ fun `external async operation`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val bob = startNode(providedName = BOB_NAME).getOrThrow()
alice.rpc.startFlow(::FlowWithExternalAsyncOperation, bob.nodeInfo.singleIdentity())
- .returnValue.getOrThrow(20.seconds)
- val (discharged, observation) = alice.rpc.startFlow(::GetHospitalCountersFlow).returnValue.getOrThrow()
- assertEquals(0, discharged)
- assertEquals(0, observation)
+ .returnValue.getOrThrow(1.minutes)
+ assertHospitalCounters(0, 0)
}
}
- @Test(timeout=300_000)
- fun `external async operation that checks deduplicationId is not rerun when flow is retried`() {
+ @Test(timeout = 300_000)
+ fun `external async operation that checks deduplicationId is not rerun when flow is retried`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val bob = startNode(providedName = BOB_NAME).getOrThrow()
@@ -50,16 +41,14 @@ class FlowExternalAsyncOperationTest : AbstractFlowExternalOperationTest() {
alice.rpc.startFlow(
::FlowWithExternalAsyncOperationWithDeduplication,
bob.nodeInfo.singleIdentity()
- ).returnValue.getOrThrow(20.seconds)
+ ).returnValue.getOrThrow(1.minutes)
}
- val (discharged, observation) = alice.rpc.startFlow(::GetHospitalCountersFlow).returnValue.getOrThrow()
- assertEquals(1, discharged)
- assertEquals(0, observation)
+ assertHospitalCounters(1, 0)
}
}
- @Test(timeout=300_000)
- fun `external async operation propagates exception to calling flow`() {
+ @Test(timeout = 300_000)
+ fun `external async operation propagates exception to calling flow`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val bob = startNode(providedName = BOB_NAME).getOrThrow()
@@ -68,100 +57,88 @@ class FlowExternalAsyncOperationTest : AbstractFlowExternalOperationTest() {
::FlowWithExternalAsyncOperationPropagatesException,
bob.nodeInfo.singleIdentity(),
MyCordaException::class.java
- ).returnValue.getOrThrow(20.seconds)
+ ).returnValue.getOrThrow(1.minutes)
}
- val (discharged, observation) = alice.rpc.startFlow(::GetHospitalCountersFlow).returnValue.getOrThrow()
- assertEquals(0, discharged)
- assertEquals(0, observation)
+ assertHospitalCounters(0, 0)
}
}
- @Test(timeout=300_000)
- fun `external async operation exception can be caught in flow`() {
+ @Test(timeout = 300_000)
+ fun `external async operation exception can be caught in flow`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val bob = startNode(providedName = BOB_NAME).getOrThrow()
val result = alice.rpc.startFlow(
::FlowWithExternalAsyncOperationThatThrowsExceptionAndCaughtInFlow,
bob.nodeInfo.singleIdentity()
- ).returnValue.getOrThrow(20.seconds)
+ ).returnValue.getOrThrow(1.minutes)
assertTrue(result as Boolean)
- val (discharged, observation) = alice.rpc.startFlow(::GetHospitalCountersFlow).returnValue.getOrThrow()
- assertEquals(0, discharged)
- assertEquals(0, observation)
+ assertHospitalCounters(0, 0)
}
}
- @Test(timeout=300_000)
- fun `external async operation with exception that hospital keeps for observation does not fail`() {
+ @Test(timeout = 300_000)
+ fun `external async operation with exception that hospital keeps for observation does not fail`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val bob = startNode(providedName = BOB_NAME).getOrThrow()
- assertFailsWith {
+ blockUntilFlowKeptInForObservation {
alice.rpc.startFlow(
::FlowWithExternalAsyncOperationPropagatesException,
bob.nodeInfo.singleIdentity(),
HospitalizeFlowException::class.java
- ).returnValue.getOrThrow(20.seconds)
+ )
}
- val (discharged, observation) = alice.rpc.startFlow(::GetHospitalCountersFlow).returnValue.getOrThrow()
- assertEquals(0, discharged)
- assertEquals(1, observation)
+ assertHospitalCounters(0, 1)
}
}
- @Test(timeout=300_000)
- fun `external async operation with exception that hospital discharges is retried and runs the future again`() {
+ @Test(timeout = 300_000)
+ fun `external async operation with exception that hospital discharges is retried and runs the future again`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val bob = startNode(providedName = BOB_NAME).getOrThrow()
- assertFailsWith {
+ blockUntilFlowKeptInForObservation {
alice.rpc.startFlow(
::FlowWithExternalAsyncOperationPropagatesException,
bob.nodeInfo.singleIdentity(),
SQLTransientConnectionException::class.java
- ).returnValue.getOrThrow(20.seconds)
+ )
}
- val (discharged, observation) = alice.rpc.startFlow(::GetHospitalCountersFlow).returnValue.getOrThrow()
- assertEquals(3, discharged)
- assertEquals(1, observation)
+ assertHospitalCounters(3, 1)
}
}
- @Test(timeout=300_000)
- fun `external async operation that throws exception rather than completing future exceptionally fails with internal exception`() {
+ @Test(timeout = 300_000)
+ fun `external async operation that throws exception rather than completing future exceptionally fails with internal exception`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val bob = startNode(providedName = BOB_NAME).getOrThrow()
assertFailsWith {
alice.rpc.startFlow(::FlowWithExternalAsyncOperationUnhandledException, bob.nodeInfo.singleIdentity())
- .returnValue.getOrThrow(20.seconds)
+ .returnValue.getOrThrow(1.minutes)
}
- val (discharged, observation) = alice.rpc.startFlow(::GetHospitalCountersFlow).returnValue.getOrThrow()
- assertEquals(0, discharged)
- assertEquals(0, observation)
+ assertHospitalCounters(0, 0)
}
}
- @Test(timeout=300_000)
- fun `external async operation that passes serviceHub into process can be retried`() {
+ @Test(timeout = 300_000)
+ fun `external async operation that passes serviceHub into process can be retried`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val bob = startNode(providedName = BOB_NAME).getOrThrow()
- assertFailsWith {
+ blockUntilFlowKeptInForObservation {
alice.rpc.startFlow(
::FlowWithExternalAsyncOperationThatPassesInServiceHubCanRetry,
bob.nodeInfo.singleIdentity()
- ).returnValue.getOrThrow(20.seconds)
+ )
}
- val (discharged, observation) = alice.rpc.startFlow(::GetHospitalCountersFlow).returnValue.getOrThrow()
- assertEquals(3, discharged)
- assertEquals(1, observation)
+ assertHospitalCounters(3, 1)
}
}
- @Test(timeout=300_000)
- fun `external async operation that accesses serviceHub from flow directly will fail when retried`() {
+ @Test(timeout = 300_000)
+ fun `external async operation that accesses serviceHub from flow directly will fail when retried`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val bob = startNode(providedName = BOB_NAME).getOrThrow()
@@ -169,23 +146,19 @@ class FlowExternalAsyncOperationTest : AbstractFlowExternalOperationTest() {
alice.rpc.startFlow(
::FlowWithExternalAsyncOperationThatDirectlyAccessesServiceHubFailsRetry,
bob.nodeInfo.singleIdentity()
- ).returnValue.getOrThrow(20.seconds)
+ ).returnValue.getOrThrow(1.minutes)
}
- val (discharged, observation) = alice.rpc.startFlow(::GetHospitalCountersFlow).returnValue.getOrThrow()
- assertEquals(1, discharged)
- assertEquals(0, observation)
+ assertHospitalCounters(1, 0)
}
}
- @Test(timeout=300_000)
- fun `starting multiple futures and joining on their results`() {
+ @Test(timeout = 300_000)
+ fun `starting multiple futures and joining on their results`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val bob = startNode(providedName = BOB_NAME).getOrThrow()
- alice.rpc.startFlow(::FlowThatStartsMultipleFuturesAndJoins, bob.nodeInfo.singleIdentity()).returnValue.getOrThrow(20.seconds)
- val (discharged, observation) = alice.rpc.startFlow(::GetHospitalCountersFlow).returnValue.getOrThrow()
- assertEquals(0, discharged)
- assertEquals(0, observation)
+ alice.rpc.startFlow(::FlowThatStartsMultipleFuturesAndJoins, bob.nodeInfo.singleIdentity()).returnValue.getOrThrow(1.minutes)
+ assertHospitalCounters(0, 0)
}
}
@@ -204,12 +177,14 @@ class FlowExternalAsyncOperationTest : AbstractFlowExternalOperationTest() {
FlowWithExternalProcess(party) {
@Suspendable
- override fun testCode(): Any =
- await(ExternalAsyncOperation(serviceHub) { _, _ ->
+ override fun testCode(): Any {
+ val e = createException()
+ return await(ExternalAsyncOperation(serviceHub) { _, _ ->
CompletableFuture().apply {
- completeExceptionally(createException())
+ completeExceptionally(e)
}
})
+ }
private fun createException() = when (exceptionType) {
HospitalizeFlowException::class.java -> HospitalizeFlowException("keep it around")
diff --git a/core-tests/src/test/kotlin/net/corda/coretests/flows/FlowExternalOperationStartFlowTest.kt b/core-tests/src/test/kotlin/net/corda/coretests/flows/FlowExternalOperationStartFlowTest.kt
index a1385d5563..bb80313c86 100644
--- a/core-tests/src/test/kotlin/net/corda/coretests/flows/FlowExternalOperationStartFlowTest.kt
+++ b/core-tests/src/test/kotlin/net/corda/coretests/flows/FlowExternalOperationStartFlowTest.kt
@@ -5,40 +5,35 @@ import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.Party
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
-import net.corda.core.utilities.seconds
+import net.corda.core.utilities.minutes
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.singleIdentity
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import org.junit.Test
-import kotlin.test.assertEquals
class FlowExternalOperationStartFlowTest : AbstractFlowExternalOperationTest() {
- @Test(timeout=300_000)
- fun `starting a flow inside of a flow that starts a future will succeed`() {
+ @Test(timeout = 300_000)
+ fun `starting a flow inside of a flow that starts a future will succeed`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val bob = startNode(providedName = BOB_NAME).getOrThrow()
alice.rpc.startFlow(::FlowThatStartsAnotherFlowInAnExternalOperation, bob.nodeInfo.singleIdentity())
- .returnValue.getOrThrow(40.seconds)
- val (discharged, observation) = alice.rpc.startFlow(::GetHospitalCountersFlow).returnValue.getOrThrow()
- assertEquals(0, discharged)
- assertEquals(0, observation)
+ .returnValue.getOrThrow(1.minutes)
+ assertHospitalCounters(0, 0)
}
}
- @Test(timeout=300_000)
- fun `multiple flows can be started and their futures joined from inside a flow`() {
+ @Test(timeout = 300_000)
+ fun `multiple flows can be started and their futures joined from inside a flow`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val bob = startNode(providedName = BOB_NAME).getOrThrow()
alice.rpc.startFlow(::ForkJoinFlows, bob.nodeInfo.singleIdentity())
- .returnValue.getOrThrow(40.seconds)
- val (discharged, observation) = alice.rpc.startFlow(::GetHospitalCountersFlow).returnValue.getOrThrow()
- assertEquals(0, discharged)
- assertEquals(0, observation)
+ .returnValue.getOrThrow(1.minutes)
+ assertHospitalCounters(0, 0)
}
}
diff --git a/core-tests/src/test/kotlin/net/corda/coretests/flows/FlowExternalOperationTest.kt b/core-tests/src/test/kotlin/net/corda/coretests/flows/FlowExternalOperationTest.kt
index 3d6fdea190..8e1a96aecd 100644
--- a/core-tests/src/test/kotlin/net/corda/coretests/flows/FlowExternalOperationTest.kt
+++ b/core-tests/src/test/kotlin/net/corda/coretests/flows/FlowExternalOperationTest.kt
@@ -10,13 +10,7 @@ import net.corda.core.messaging.startFlow
import net.corda.core.node.services.queryBy
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow
-import net.corda.core.utilities.seconds
-import net.corda.coretests.flows.AbstractFlowExternalOperationTest.CustomTableEntity
-import net.corda.coretests.flows.AbstractFlowExternalOperationTest.DirectlyAccessedServiceHubException
-import net.corda.coretests.flows.AbstractFlowExternalOperationTest.ExternalOperation
-import net.corda.coretests.flows.AbstractFlowExternalOperationTest.FlowWithExternalProcess
-import net.corda.coretests.flows.AbstractFlowExternalOperationTest.FutureService
-import net.corda.coretests.flows.AbstractFlowExternalOperationTest.MyCordaException
+import net.corda.core.utilities.minutes
import net.corda.testing.contracts.DummyContract
import net.corda.testing.contracts.DummyState
import net.corda.testing.core.ALICE_NAME
@@ -26,30 +20,25 @@ import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.node.internal.cordappsForPackages
import org.junit.Test
-import java.lang.IllegalStateException
import java.sql.SQLTransientConnectionException
-import java.util.concurrent.TimeoutException
-import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class FlowExternalOperationTest : AbstractFlowExternalOperationTest() {
- @Test(timeout=300_000)
- fun `external operation`() {
+ @Test(timeout = 300_000)
+ fun `external operation`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val bob = startNode(providedName = BOB_NAME).getOrThrow()
alice.rpc.startFlow(::FlowWithExternalOperation, bob.nodeInfo.singleIdentity())
- .returnValue.getOrThrow(20.seconds)
- val (discharged, observation) = alice.rpc.startFlow(::GetHospitalCountersFlow).returnValue.getOrThrow()
- assertEquals(0, discharged)
- assertEquals(0, observation)
+ .returnValue.getOrThrow(1.minutes)
+ assertHospitalCounters(0, 0)
}
}
- @Test(timeout=300_000)
- fun `external operation that checks deduplicationId is not rerun when flow is retried`() {
+ @Test(timeout = 300_000)
+ fun `external operation that checks deduplicationId is not rerun when flow is retried`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val bob = startNode(providedName = BOB_NAME).getOrThrow()
@@ -57,16 +46,14 @@ class FlowExternalOperationTest : AbstractFlowExternalOperationTest() {
alice.rpc.startFlow(
::FlowWithExternalOperationWithDeduplication,
bob.nodeInfo.singleIdentity()
- ).returnValue.getOrThrow(20.seconds)
+ ).returnValue.getOrThrow(1.minutes)
}
- val (discharged, observation) = alice.rpc.startFlow(::GetHospitalCountersFlow).returnValue.getOrThrow()
- assertEquals(1, discharged)
- assertEquals(0, observation)
+ assertHospitalCounters(1, 0)
}
}
- @Test(timeout=300_000)
- fun `external operation propagates exception to calling flow`() {
+ @Test(timeout = 300_000)
+ fun `external operation propagates exception to calling flow`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val bob = startNode(providedName = BOB_NAME).getOrThrow()
@@ -75,82 +62,72 @@ class FlowExternalOperationTest : AbstractFlowExternalOperationTest() {
::FlowWithExternalOperationPropagatesException,
bob.nodeInfo.singleIdentity(),
MyCordaException::class.java
- ).returnValue.getOrThrow(20.seconds)
+ ).returnValue.getOrThrow(1.minutes)
}
- val (discharged, observation) = alice.rpc.startFlow(::GetHospitalCountersFlow).returnValue.getOrThrow()
- assertEquals(0, discharged)
- assertEquals(0, observation)
+ assertHospitalCounters(0, 0)
}
}
- @Test(timeout=300_000)
- fun `external operation exception can be caught in flow`() {
+ @Test(timeout = 300_000)
+ fun `external operation exception can be caught in flow`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val bob = startNode(providedName = BOB_NAME).getOrThrow()
alice.rpc.startFlow(::FlowWithExternalOperationThatThrowsExceptionAndCaughtInFlow, bob.nodeInfo.singleIdentity())
- .returnValue.getOrThrow(20.seconds)
- val (discharged, observation) = alice.rpc.startFlow(::GetHospitalCountersFlow).returnValue.getOrThrow()
- assertEquals(0, discharged)
- assertEquals(0, observation)
+ .returnValue.getOrThrow(1.minutes)
+ assertHospitalCounters(0, 0)
}
}
- @Test(timeout=300_000)
- fun `external operation with exception that hospital keeps for observation does not fail`() {
+ @Test(timeout = 300_000)
+ fun `external operation with exception that hospital keeps for observation does not fail`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val bob = startNode(providedName = BOB_NAME).getOrThrow()
- assertFailsWith {
+ blockUntilFlowKeptInForObservation {
alice.rpc.startFlow(
::FlowWithExternalOperationPropagatesException,
bob.nodeInfo.singleIdentity(),
HospitalizeFlowException::class.java
- ).returnValue.getOrThrow(20.seconds)
+ )
}
- val (discharged, observation) = alice.rpc.startFlow(::GetHospitalCountersFlow).returnValue.getOrThrow()
- assertEquals(0, discharged)
- assertEquals(1, observation)
+ assertHospitalCounters(0, 1)
}
}
- @Test(timeout=300_000)
- fun `external operation with exception that hospital discharges is retried and runs the external operation again`() {
+ @Test(timeout = 300_000)
+ fun `external operation with exception that hospital discharges is retried and runs the external operation again`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val bob = startNode(providedName = BOB_NAME).getOrThrow()
- assertFailsWith {
+ blockUntilFlowKeptInForObservation {
alice.rpc.startFlow(
::FlowWithExternalOperationPropagatesException,
bob.nodeInfo.singleIdentity(),
SQLTransientConnectionException::class.java
- ).returnValue.getOrThrow(20.seconds)
+ )
}
- val (discharged, observation) = alice.rpc.startFlow(::GetHospitalCountersFlow).returnValue.getOrThrow()
- assertEquals(3, discharged)
- assertEquals(1, observation)
+ assertHospitalCounters(3, 1)
}
}
- @Test(timeout=300_000)
- fun `external async operation that passes serviceHub into process can be retried`() {
+ @Test(timeout = 300_000)
+ fun `external async operation that passes serviceHub into process can be retried`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val bob = startNode(providedName = BOB_NAME).getOrThrow()
- assertFailsWith {
+ blockUntilFlowKeptInForObservation {
alice.rpc.startFlow(
::FlowWithExternalOperationThatPassesInServiceHubCanRetry,
bob.nodeInfo.singleIdentity()
- ).returnValue.getOrThrow(20.seconds)
+ )
}
- val (discharged, observation) = alice.rpc.startFlow(::GetHospitalCountersFlow).returnValue.getOrThrow()
- assertEquals(3, discharged)
- assertEquals(1, observation)
+ assertHospitalCounters(3, 1)
}
}
- @Test(timeout=300_000)
- fun `external async operation that accesses serviceHub from flow directly will fail when retried`() {
+ @Test(timeout = 300_000)
+ fun `external async operation that accesses serviceHub from flow directly will fail when retried`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val bob = startNode(providedName = BOB_NAME).getOrThrow()
@@ -158,16 +135,14 @@ class FlowExternalOperationTest : AbstractFlowExternalOperationTest() {
alice.rpc.startFlow(
::FlowWithExternalOperationThatDirectlyAccessesServiceHubFailsRetry,
bob.nodeInfo.singleIdentity()
- ).returnValue.getOrThrow(20.seconds)
+ ).returnValue.getOrThrow(1.minutes)
}
- val (discharged, observation) = alice.rpc.startFlow(::GetHospitalCountersFlow).returnValue.getOrThrow()
- assertEquals(1, discharged)
- assertEquals(0, observation)
+ assertHospitalCounters(1, 0)
}
}
- @Test(timeout=300_000)
- fun `vault can be queried`() {
+ @Test(timeout = 300_000)
+ fun `vault can be queried`() {
driver(
DriverParameters(
cordappsForAllNodes = cordappsForPackages(DummyState::class.packageName),
@@ -176,64 +151,62 @@ class FlowExternalOperationTest : AbstractFlowExternalOperationTest() {
) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val success = alice.rpc.startFlow(::FlowWithWithExternalOperationThatQueriesVault)
- .returnValue.getOrThrow(20.seconds)
+ .returnValue.getOrThrow(1.minutes)
assertTrue(success)
}
}
- @Test(timeout=300_000)
- fun `data can be persisted to node database via entity manager`() {
+ @Test(timeout = 300_000)
+ fun `data can be persisted to node database via entity manager`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val success = alice.rpc.startFlow(::FlowWithExternalOperationThatPersistsViaEntityManager)
- .returnValue.getOrThrow(20.seconds)
+ .returnValue.getOrThrow(1.minutes)
assertTrue(success)
}
}
- @Test(timeout=300_000)
- fun `data can be persisted to node database via jdbc session`() {
+ @Test(timeout = 300_000)
+ fun `data can be persisted to node database via jdbc session`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val success = alice.rpc.startFlow(::FlowWithExternalOperationThatPersistsViaJdbcSession)
- .returnValue.getOrThrow(20.seconds)
+ .returnValue.getOrThrow(1.minutes)
assertTrue(success)
}
}
- @Test(timeout=300_000)
- fun `data can be persisted to node database via servicehub database transaction`() {
+ @Test(timeout = 300_000)
+ fun `data can be persisted to node database via servicehub database transaction`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val success = alice.rpc.startFlow(::FlowWithExternalOperationThatPersistsViaDatabaseTransaction)
- .returnValue.getOrThrow(20.seconds)
+ .returnValue.getOrThrow(1.minutes)
assertTrue(success)
}
}
- @Test(timeout=300_000)
- fun `data can be persisted to node database in external operation and read from another process once finished`() {
+ @Test(timeout = 300_000)
+ fun `data can be persisted to node database in external operation and read from another process once finished`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val success = alice.rpc.startFlow(::FlowWithExternalOperationThatPersistsToDatabaseAndReadsFromExternalOperation)
- .returnValue.getOrThrow(20.seconds)
+ .returnValue.getOrThrow(1.minutes)
assertTrue(success)
}
}
- @Test(timeout=300_000)
- fun `external operation can be retried when an error occurs inside of database transaction`() {
+ @Test(timeout = 300_000)
+ fun `external operation can be retried when an error occurs inside of database transaction`() {
driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
val bob = startNode(providedName = BOB_NAME).getOrThrow()
val success = alice.rpc.startFlow(
::FlowWithExternalOperationThatErrorsInsideOfDatabaseTransaction,
bob.nodeInfo.singleIdentity()
- ).returnValue.getOrThrow(20.seconds)
+ ).returnValue.getOrThrow(1.minutes)
assertTrue(success as Boolean)
- val (discharged, observation) = alice.rpc.startFlow(::GetHospitalCountersFlow).returnValue.getOrThrow()
- assertEquals(1, discharged)
- assertEquals(0, observation)
+ assertHospitalCounters(1, 0)
}
}
@@ -260,7 +233,10 @@ class FlowExternalOperationTest : AbstractFlowExternalOperationTest() {
FlowWithExternalProcess(party) {
@Suspendable
- override fun testCode(): Any = await(ExternalOperation(serviceHub) { _, _ -> throw createException() })
+ override fun testCode() {
+ val e = createException()
+ await(ExternalOperation(serviceHub) { _, _ -> throw e })
+ }
private fun createException() = when (exceptionType) {
HospitalizeFlowException::class.java -> HospitalizeFlowException("keep it around")
diff --git a/core-tests/src/test/kotlin/net/corda/coretests/flows/FlowIsKilledTest.kt b/core-tests/src/test/kotlin/net/corda/coretests/flows/FlowIsKilledTest.kt
new file mode 100644
index 0000000000..211c597c29
--- /dev/null
+++ b/core-tests/src/test/kotlin/net/corda/coretests/flows/FlowIsKilledTest.kt
@@ -0,0 +1,364 @@
+package net.corda.coretests.flows
+
+import co.paralleluniverse.fibers.Suspendable
+import net.corda.core.flows.FlowLogic
+import net.corda.core.flows.FlowSession
+import net.corda.core.flows.InitiatedBy
+import net.corda.core.flows.InitiatingFlow
+import net.corda.core.flows.KilledFlowException
+import net.corda.core.flows.StartableByRPC
+import net.corda.core.flows.StateMachineRunId
+import net.corda.core.flows.UnexpectedFlowEndException
+import net.corda.core.identity.Party
+import net.corda.core.messaging.startFlow
+import net.corda.core.utilities.getOrThrow
+import net.corda.core.utilities.minutes
+import net.corda.core.utilities.seconds
+import net.corda.testing.core.ALICE_NAME
+import net.corda.testing.core.BOB_NAME
+import net.corda.testing.core.CHARLIE_NAME
+import net.corda.testing.core.singleIdentity
+import net.corda.testing.driver.DriverParameters
+import net.corda.testing.driver.driver
+import org.apache.logging.log4j.Level
+import org.apache.logging.log4j.core.config.Configurator
+import org.assertj.core.api.Assertions.assertThatExceptionOfType
+import org.junit.Before
+import org.junit.Test
+import java.util.concurrent.Semaphore
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertTrue
+
+class FlowIsKilledTest {
+
+ private companion object {
+ const val EXCEPTION_MESSAGE = "Goodbye, cruel world!"
+ }
+
+ @Before
+ fun setup() {
+ Configurator.setLevel("net.corda.node.services.statemachine", Level.DEBUG)
+ }
+
+ @Test(timeout = 300_000)
+ fun `manually handle the isKilled check`() {
+ driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
+ val alice = startNode(providedName = ALICE_NAME).getOrThrow()
+ alice.rpc.let { rpc ->
+ val handle = rpc.startFlow(::AFlowThatWantsToDie)
+ AFlowThatWantsToDie.lockA.acquire()
+ rpc.killFlow(handle.id)
+ AFlowThatWantsToDie.lockB.release()
+ assertThatExceptionOfType(KilledFlowException::class.java)
+ .isThrownBy { handle.returnValue.getOrThrow(1.minutes) }
+ .withMessage(EXCEPTION_MESSAGE)
+ assertEquals(11, AFlowThatWantsToDie.position)
+ val checkpoints = rpc.startFlow(::GetNumberOfCheckpointsFlow).returnValue.getOrThrow(20.seconds)
+ assertEquals(1, checkpoints)
+ }
+ }
+ }
+
+ @Test(timeout = 300_000)
+ fun `manually handled killed flows propagate error to counter parties`() {
+ driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
+ val alice = startNode(providedName = ALICE_NAME).getOrThrow()
+ val bob = startNode(providedName = BOB_NAME).getOrThrow()
+ val charlie = startNode(providedName = CHARLIE_NAME).getOrThrow()
+ alice.rpc.let { rpc ->
+ val handle = rpc.startFlow(
+ ::AFlowThatWantsToDieAndKillsItsFriends,
+ listOf(bob.nodeInfo.singleIdentity(), charlie.nodeInfo.singleIdentity())
+ )
+ AFlowThatWantsToDieAndKillsItsFriends.lockA.acquire()
+ AFlowThatWantsToDieAndKillsItsFriendsResponder.locks.forEach { it.value.acquire() }
+ rpc.killFlow(handle.id)
+ AFlowThatWantsToDieAndKillsItsFriends.lockB.release()
+ assertThatExceptionOfType(KilledFlowException::class.java)
+ .isThrownBy { handle.returnValue.getOrThrow(1.minutes) }
+ .withMessage(EXCEPTION_MESSAGE)
+ AFlowThatWantsToDieAndKillsItsFriendsResponder.locks.forEach { it.value.acquire() }
+ assertEquals(11, AFlowThatWantsToDieAndKillsItsFriends.position)
+ assertTrue(AFlowThatWantsToDieAndKillsItsFriendsResponder.receivedKilledExceptions[BOB_NAME]!!)
+ assertTrue(AFlowThatWantsToDieAndKillsItsFriendsResponder.receivedKilledExceptions[CHARLIE_NAME]!!)
+ val aliceCheckpoints = alice.rpc.startFlow(::GetNumberOfCheckpointsFlow).returnValue.getOrThrow(20.seconds)
+ assertEquals(1, aliceCheckpoints)
+ val bobCheckpoints = bob.rpc.startFlow(::GetNumberOfCheckpointsFlow).returnValue.getOrThrow(20.seconds)
+ assertEquals(1, bobCheckpoints)
+ }
+ }
+ }
+
+ @Test(timeout = 300_000)
+ fun `a manually killed initiated flow will propagate the killed error to the initiator and its counter parties`() {
+ driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
+ val alice = startNode(providedName = ALICE_NAME).getOrThrow()
+ val bob = startNode(providedName = BOB_NAME).getOrThrow()
+ val handle = alice.rpc.startFlow(
+ ::AFlowThatGetsMurderedByItsFriend,
+ bob.nodeInfo.singleIdentity()
+ )
+
+ AFlowThatGetsMurderedByItsFriendResponder.lockA.acquire()
+
+ val initiatedFlowId = AFlowThatGetsMurderedByItsFriendResponder.flowId!!
+
+ bob.rpc.killFlow(initiatedFlowId)
+
+ AFlowThatGetsMurderedByItsFriendResponder.lockB.release()
+
+ assertFailsWith {
+ handle.returnValue.getOrThrow(1.minutes)
+ }
+ assertTrue(AFlowThatGetsMurderedByItsFriend.receivedKilledException)
+ assertEquals(11, AFlowThatGetsMurderedByItsFriendResponder.position)
+ val aliceCheckpoints = alice.rpc.startFlow(::GetNumberOfCheckpointsFlow).returnValue.getOrThrow(20.seconds)
+ assertEquals(1, aliceCheckpoints)
+ val bobCheckpoints = bob.rpc.startFlow(::GetNumberOfCheckpointsFlow).returnValue.getOrThrow(20.seconds)
+ assertEquals(1, bobCheckpoints)
+ }
+ }
+
+ @Test(timeout = 300_000)
+ fun `manually handle killed flows using checkForIsNotKilled`() {
+ driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
+ val alice = startNode(providedName = ALICE_NAME).getOrThrow()
+ alice.rpc.let { rpc ->
+ val handle = rpc.startFlow(::AFlowThatChecksIfItWantsToDie)
+ AFlowThatChecksIfItWantsToDie.lockA.acquire()
+ rpc.killFlow(handle.id)
+ AFlowThatChecksIfItWantsToDie.lockB.release()
+ assertThatExceptionOfType(KilledFlowException::class.java)
+ .isThrownBy { handle.returnValue.getOrThrow(1.minutes) }
+ .withMessageNotContaining(EXCEPTION_MESSAGE)
+ assertEquals(11, AFlowThatChecksIfItWantsToDie.position)
+ val checkpoints = rpc.startFlow(::GetNumberOfCheckpointsFlow).returnValue.getOrThrow(20.seconds)
+ assertEquals(1, checkpoints)
+ }
+ }
+ }
+
+ @Test(timeout = 300_000)
+ fun `manually handle killed flows using checkForIsNotKilled with lazy message`() {
+ driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
+ val alice = startNode(providedName = ALICE_NAME).getOrThrow()
+ alice.rpc.let { rpc ->
+ val handle = rpc.startFlow(::AFlowThatChecksIfItWantsToDieAndLeavesANote)
+ AFlowThatChecksIfItWantsToDieAndLeavesANote.lockA.acquire()
+ rpc.killFlow(handle.id)
+ AFlowThatChecksIfItWantsToDieAndLeavesANote.lockB.release()
+ assertThatExceptionOfType(KilledFlowException::class.java)
+ .isThrownBy { handle.returnValue.getOrThrow(1.minutes) }
+ .withMessage(EXCEPTION_MESSAGE)
+ assertEquals(11, AFlowThatChecksIfItWantsToDie.position)
+ val checkpoints = rpc.startFlow(::GetNumberOfCheckpointsFlow).returnValue.getOrThrow(20.seconds)
+ assertEquals(1, checkpoints)
+ }
+ }
+ }
+
+ @StartableByRPC
+ class AFlowThatWantsToDie : FlowLogic() {
+
+ companion object {
+ val lockA = Semaphore(0)
+ val lockB = Semaphore(0)
+
+ var position = 0
+ }
+
+ @Suspendable
+ override fun call() {
+ for (i in 0..100) {
+ position = i
+ logger.info("i = $i")
+ if (isKilled) {
+ throw KilledFlowException(runId, EXCEPTION_MESSAGE)
+ }
+
+ if (i == 10) {
+ lockA.release()
+ lockB.acquire()
+ }
+ }
+ }
+ }
+
+ @StartableByRPC
+ @InitiatingFlow
+ class AFlowThatWantsToDieAndKillsItsFriends(private val parties: List) : FlowLogic() {
+
+ companion object {
+ val lockA = Semaphore(0)
+ val lockB = Semaphore(0)
+ var position = 0
+ }
+
+ @Suspendable
+ override fun call() {
+ val sessionOne = initiateFlow(parties[0])
+ val sessionTwo = initiateFlow(parties[1])
+ // trigger sessions with 2 counter parties
+ sessionOne.sendAndReceive("what is up")
+ sessionOne.send("what is up 2")
+ sessionTwo.sendAndReceive("what is up")
+ sessionTwo.send("what is up 2")
+ for (i in 0..100) {
+ position = i
+ logger.info("i = $i")
+ if (isKilled) {
+ throw KilledFlowException(runId, EXCEPTION_MESSAGE)
+ }
+
+ if (i == 10) {
+ lockA.release()
+ lockB.acquire()
+ }
+ }
+ }
+ }
+
+ @InitiatedBy(AFlowThatWantsToDieAndKillsItsFriends::class)
+ class AFlowThatWantsToDieAndKillsItsFriendsResponder(private val session: FlowSession) : FlowLogic() {
+
+ companion object {
+ val locks = mapOf(
+ BOB_NAME to Semaphore(0),
+ CHARLIE_NAME to Semaphore(0)
+ )
+ var receivedKilledExceptions = mutableMapOf(
+ BOB_NAME to false,
+ CHARLIE_NAME to false
+ )
+ }
+
+ @Suspendable
+ override fun call() {
+ session.receive()
+ session.send("hi")
+ session.receive()
+ locks[ourIdentity.name]!!.release()
+ try {
+ session.receive()
+ } catch (e: UnexpectedFlowEndException) {
+ receivedKilledExceptions[ourIdentity.name] = true
+ locks[ourIdentity.name]!!.release()
+ throw e
+ }
+ }
+ }
+
+ @StartableByRPC
+ @InitiatingFlow
+ class AFlowThatGetsMurderedByItsFriend(private val party: Party) : FlowLogic() {
+
+ companion object {
+ var receivedKilledException = false
+ }
+
+ @Suspendable
+ override fun call() {
+ val sessionOne = initiateFlow(party)
+ // trigger sessions with 2 counter parties
+ sessionOne.sendAndReceive("what is up")
+ try {
+ sessionOne.receive()
+ } catch (e: UnexpectedFlowEndException) {
+ receivedKilledException = true
+ throw e
+ }
+ }
+ }
+
+ @InitiatedBy(AFlowThatGetsMurderedByItsFriend::class)
+ class AFlowThatGetsMurderedByItsFriendResponder(private val session: FlowSession) : FlowLogic() {
+
+ companion object {
+ val lockA = Semaphore(0)
+ val lockB = Semaphore(0)
+ var flowId: StateMachineRunId? = null
+ var position = 0
+ }
+
+ @Suspendable
+ override fun call() {
+ flowId = runId
+ session.receive()
+ session.send("hi")
+ for (i in 0..100) {
+ position = i
+ if (isKilled) {
+ throw KilledFlowException(runId, EXCEPTION_MESSAGE)
+ }
+
+ if (i == 10) {
+ lockA.release()
+ lockB.acquire()
+ }
+ }
+ }
+ }
+
+ @StartableByRPC
+ class AFlowThatChecksIfItWantsToDie : FlowLogic() {
+
+ companion object {
+ val lockA = Semaphore(0)
+ val lockB = Semaphore(0)
+
+ var position = 0
+ }
+
+ @Suspendable
+ override fun call() {
+ for (i in 0..100) {
+ position = i
+ logger.info("i = $i")
+ checkFlowIsNotKilled()
+
+ if (i == 10) {
+ lockA.release()
+ lockB.acquire()
+ }
+ }
+ }
+ }
+
+ @StartableByRPC
+ class AFlowThatChecksIfItWantsToDieAndLeavesANote : FlowLogic() {
+
+ companion object {
+ val lockA = Semaphore(0)
+ val lockB = Semaphore(0)
+
+ var position = 0
+ }
+
+ @Suspendable
+ override fun call() {
+ for (i in 0..100) {
+ position = i
+ logger.info("i = $i")
+ checkFlowIsNotKilled { EXCEPTION_MESSAGE }
+
+ if (i == 10) {
+ lockA.release()
+ lockB.acquire()
+ }
+ }
+ }
+ }
+
+ @StartableByRPC
+ class GetNumberOfCheckpointsFlow : FlowLogic() {
+ override fun call(): Long {
+ return serviceHub.jdbcSession().prepareStatement("select count(*) from node_checkpoints").use { ps ->
+ ps.executeQuery().use { rs ->
+ rs.next()
+ rs.getLong(1)
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/core-tests/src/test/kotlin/net/corda/coretests/flows/FlowSleepTest.kt b/core-tests/src/test/kotlin/net/corda/coretests/flows/FlowSleepTest.kt
new file mode 100644
index 0000000000..c2ce0b66d9
--- /dev/null
+++ b/core-tests/src/test/kotlin/net/corda/coretests/flows/FlowSleepTest.kt
@@ -0,0 +1,146 @@
+package net.corda.coretests.flows
+
+import co.paralleluniverse.fibers.Suspendable
+import net.corda.core.flows.FlowLogic
+import net.corda.core.flows.FlowSession
+import net.corda.core.flows.InitiatedBy
+import net.corda.core.flows.InitiatingFlow
+import net.corda.core.flows.StartableByRPC
+import net.corda.core.identity.Party
+import net.corda.core.messaging.startFlow
+import net.corda.core.utilities.getOrThrow
+import net.corda.core.utilities.minutes
+import net.corda.core.utilities.seconds
+import net.corda.core.utilities.unwrap
+import net.corda.testing.core.ALICE_NAME
+import net.corda.testing.core.BOB_NAME
+import net.corda.testing.core.singleIdentity
+import net.corda.testing.driver.DriverParameters
+import net.corda.testing.driver.driver
+import org.apache.logging.log4j.Level
+import org.apache.logging.log4j.core.config.Configurator
+import org.junit.Before
+import org.junit.Test
+import java.time.Duration
+import java.time.Instant
+import kotlin.test.assertTrue
+
+class FlowSleepTest {
+
+ @Before
+ fun setup() {
+ Configurator.setLevel("net.corda.node.services.statemachine", Level.DEBUG)
+ }
+
+ @Test(timeout = 300_000)
+ fun `flow can sleep`() {
+ driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
+ val alice = startNode(providedName = ALICE_NAME).getOrThrow()
+ val (start, finish) = alice.rpc.startFlow(::SleepyFlow).returnValue.getOrThrow(1.minutes)
+ val difference = Duration.between(start, finish)
+ assertTrue(difference >= 5.seconds)
+ assertTrue(difference < 7.seconds)
+ }
+ }
+
+ @Test(timeout = 300_000)
+ fun `flow can sleep multiple times`() {
+ driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
+ val alice = startNode(providedName = ALICE_NAME).getOrThrow()
+ val (start, middle, finish) = alice.rpc.startFlow(::AnotherSleepyFlow).returnValue.getOrThrow(1.minutes)
+ val differenceBetweenStartAndMiddle = Duration.between(start, middle)
+ val differenceBetweenMiddleAndFinish = Duration.between(middle, finish)
+ assertTrue(differenceBetweenStartAndMiddle >= 5.seconds)
+ assertTrue(differenceBetweenStartAndMiddle < 7.seconds)
+ assertTrue(differenceBetweenMiddleAndFinish >= 10.seconds)
+ assertTrue(differenceBetweenMiddleAndFinish < 12.seconds)
+ }
+ }
+
+ @Test(timeout = 300_000)
+ fun `flow can sleep and perform other suspending functions`() {
+ // ensures that events received while the flow is sleeping are not processed
+ driver(DriverParameters(notarySpecs = emptyList(), startNodesInProcess = true)) {
+ val alice = startNode(providedName = ALICE_NAME).getOrThrow()
+ val bob = startNode(providedName = BOB_NAME).getOrThrow()
+ val (start, finish) = alice.rpc.startFlow(
+ ::SleepAndInteractWithPartyFlow,
+ bob.nodeInfo.singleIdentity()
+ ).returnValue.getOrThrow(1.minutes)
+ val difference = Duration.between(start, finish)
+ assertTrue(difference >= 5.seconds)
+ assertTrue(difference < 7.seconds)
+ }
+ }
+
+ @StartableByRPC
+ class SleepyFlow : FlowLogic>() {
+
+ @Suspendable
+ override fun call(): Pair {
+ val start = Instant.now()
+ sleep(5.seconds)
+ return start to Instant.now()
+ }
+ }
+
+ @StartableByRPC
+ class AnotherSleepyFlow : FlowLogic>() {
+
+ @Suspendable
+ override fun call(): Triple {
+ val start = Instant.now()
+ sleep(5.seconds)
+ val middle = Instant.now()
+ sleep(10.seconds)
+ return Triple(start, middle, Instant.now())
+ }
+ }
+
+ @StartableByRPC
+ @InitiatingFlow
+ class SleepAndInteractWithPartyFlow(private val party: Party) : FlowLogic>() {
+
+ @Suspendable
+ override fun call(): Pair {
+ subFlow(PingPongFlow(party))
+ val start = Instant.now()
+ sleep(5.seconds)
+ val finish = Instant.now()
+ val session = initiateFlow(party)
+ session.sendAndReceive("hi")
+ session.sendAndReceive("hi")
+ subFlow(PingPongFlow(party))
+ return start to finish
+ }
+ }
+
+ @InitiatedBy(SleepAndInteractWithPartyFlow::class)
+ class SleepAndInteractWithPartyResponder(val session: FlowSession) : FlowLogic() {
+ @Suspendable
+ override fun call() {
+ session.receive().unwrap { it }
+ session.send("go away")
+ session.receive().unwrap { it }
+ session.send("go away")
+ }
+ }
+
+ @InitiatingFlow
+ class PingPongFlow(val party: Party) : FlowLogic() {
+ @Suspendable
+ override fun call() {
+ val session = initiateFlow(party)
+ session.sendAndReceive("ping pong").unwrap { it }
+ }
+ }
+
+ @InitiatedBy(PingPongFlow::class)
+ class PingPongResponder(val session: FlowSession) : FlowLogic() {
+ @Suspendable
+ override fun call() {
+ session.receive().unwrap { it }
+ session.send("I got you bro")
+ }
+ }
+}
\ No newline at end of file
diff --git a/core-tests/src/test/kotlin/net/corda/coretests/transactions/AttachmentsClassLoaderTests.kt b/core-tests/src/test/kotlin/net/corda/coretests/transactions/AttachmentsClassLoaderTests.kt
index 29edf8938e..6a98b20172 100644
--- a/core-tests/src/test/kotlin/net/corda/coretests/transactions/AttachmentsClassLoaderTests.kt
+++ b/core-tests/src/test/kotlin/net/corda/coretests/transactions/AttachmentsClassLoaderTests.kt
@@ -21,14 +21,18 @@ import net.corda.testing.internal.services.InternalMockAttachmentStorage
import net.corda.testing.node.internal.FINANCE_CONTRACTS_CORDAPP
import net.corda.testing.services.MockAttachmentStorage
import org.apache.commons.io.IOUtils
+import org.assertj.core.api.Assertions.assertThat
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.net.URL
import kotlin.test.assertFailsWith
+import kotlin.test.fail
class AttachmentsClassLoaderTests {
companion object {
@@ -84,14 +88,29 @@ class AttachmentsClassLoaderTests {
}
}
+ @Test(timeout=300_000)
+ fun `test contracts have no permissions for protection domain`() {
+ val isolatedId = importAttachment(ISOLATED_CONTRACTS_JAR_PATH.openStream(), "app", "isolated.jar")
+ assertNull(System.getSecurityManager())
+
+ createClassloader(isolatedId).use { classLoader ->
+ val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, classLoader)
+ val protectionDomain = contractClass.protectionDomain ?: fail("Protection Domain missing")
+ val permissions = protectionDomain.permissions ?: fail("Protection domain has no permissions")
+ assertThat(permissions.elements().toList()).isEmpty()
+ assertTrue(permissions.isReadOnly)
+ }
+ }
+
@Test(timeout=300_000)
fun `Dynamically load AnotherDummyContract from isolated contracts jar using the AttachmentsClassLoader`() {
val isolatedId = importAttachment(ISOLATED_CONTRACTS_JAR_PATH.openStream(), "app", "isolated.jar")
- val classloader = createClassloader(isolatedId)
- val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, classloader)
- val contract = contractClass.getDeclaredConstructor().newInstance() as Contract
- assertEquals("helloworld", contract.declaredField("magicString").value)
+ createClassloader(isolatedId).use { classloader ->
+ val contractClass = Class.forName(ISOLATED_CONTRACT_CLASS_NAME, true, classloader)
+ val contract = contractClass.getDeclaredConstructor().newInstance() as Contract
+ assertEquals("helloworld", contract.declaredField("magicString").value)
+ }
}
@Test(timeout=300_000)
@@ -100,7 +119,7 @@ class AttachmentsClassLoaderTests {
val att2 = importAttachment(ISOLATED_CONTRACTS_JAR_PATH_V4.openStream(), "app", "isolated-4.0.jar")
assertFailsWith(TransactionVerificationException.OverlappingAttachmentsException::class) {
- createClassloader(listOf(att1, att2))
+ createClassloader(listOf(att1, att2)).use {}
}
}
@@ -111,7 +130,7 @@ class AttachmentsClassLoaderTests {
val isolatedSignedId = importAttachment(signedJar.first.toUri().toURL().openStream(), "app", "isolated-signed.jar")
// does not throw OverlappingAttachments exception
- createClassloader(listOf(isolatedId, isolatedSignedId))
+ createClassloader(listOf(isolatedId, isolatedSignedId)).use {}
}
@Test(timeout=300_000)
@@ -120,7 +139,7 @@ class AttachmentsClassLoaderTests {
val att2 = importAttachment(FINANCE_CONTRACTS_CORDAPP.jarFile.inputStream(), "app", "finance.jar")
// does not throw OverlappingAttachments exception
- createClassloader(listOf(att1, att2))
+ createClassloader(listOf(att1, att2)).use {}
}
@Test(timeout=300_000)
@@ -128,12 +147,13 @@ class AttachmentsClassLoaderTests {
val att1 = importAttachment(fakeAttachment("file1.txt", "some data").inputStream(), "app", "file1.jar")
val att2 = importAttachment(fakeAttachment("file2.txt", "some other data").inputStream(), "app", "file2.jar")
- val cl = createClassloader(listOf(att1, att2))
- val txt = IOUtils.toString(cl.getResourceAsStream("file1.txt"), Charsets.UTF_8.name())
- assertEquals("some data", txt)
+ createClassloader(listOf(att1, att2)).use { cl ->
+ val txt = IOUtils.toString(cl.getResourceAsStream("file1.txt"), Charsets.UTF_8.name())
+ assertEquals("some data", txt)
- val txt1 = IOUtils.toString(cl.getResourceAsStream("file2.txt"), Charsets.UTF_8.name())
- assertEquals("some other data", txt1)
+ val txt1 = IOUtils.toString(cl.getResourceAsStream("file2.txt"), Charsets.UTF_8.name())
+ assertEquals("some other data", txt1)
+ }
}
@Test(timeout=300_000)
@@ -141,9 +161,10 @@ class AttachmentsClassLoaderTests {
val att1 = importAttachment(fakeAttachment("file1.txt", "same data", "file2.txt", "same other data").inputStream(), "app", "file1.jar")
val att2 = importAttachment(fakeAttachment("file1.txt", "same data", "file3.txt", "same totally different").inputStream(), "app", "file2.jar")
- val cl = createClassloader(listOf(att1, att2))
- val txt = IOUtils.toString(cl.getResourceAsStream("file1.txt"), Charsets.UTF_8.name())
- assertEquals("same data", txt)
+ createClassloader(listOf(att1, att2)).use { cl ->
+ val txt = IOUtils.toString(cl.getResourceAsStream("file1.txt"), Charsets.UTF_8.name())
+ assertEquals("same data", txt)
+ }
}
@Test(timeout=300_000)
@@ -152,7 +173,7 @@ class AttachmentsClassLoaderTests {
val att1 = importAttachment(fakeAttachment(path, "some data").inputStream(), "app", "file1.jar")
val att2 = importAttachment(fakeAttachment(path, "some other data").inputStream(), "app", "file2.jar")
- createClassloader(listOf(att1, att2))
+ createClassloader(listOf(att1, att2)).use {}
}
}
@@ -161,7 +182,7 @@ class AttachmentsClassLoaderTests {
val att1 = importAttachment(fakeAttachment("meta-inf/services/net.corda.core.serialization.SerializationWhitelist", "some data").inputStream(), "app", "file1.jar")
val att2 = importAttachment(fakeAttachment("meta-inf/services/net.corda.core.serialization.SerializationWhitelist", "some other data").inputStream(), "app", "file2.jar")
- createClassloader(listOf(att1, att2))
+ createClassloader(listOf(att1, att2)).use {}
}
@Test(timeout=300_000)
@@ -170,7 +191,7 @@ class AttachmentsClassLoaderTests {
val att2 = importAttachment(fakeAttachment("meta-inf/services/com.example.something", "some other data").inputStream(), "app", "file2.jar")
assertFailsWith(TransactionVerificationException.OverlappingAttachmentsException::class) {
- createClassloader(listOf(att1, att2))
+ createClassloader(listOf(att1, att2)).use {}
}
}
@@ -180,7 +201,7 @@ class AttachmentsClassLoaderTests {
val att2 = storage.importAttachment(fakeAttachment("file1.txt", "some other data").inputStream(), "app", "file2.jar")
assertFailsWith(TransactionVerificationException.OverlappingAttachmentsException::class) {
- createClassloader(listOf(att1, att2))
+ createClassloader(listOf(att1, att2)).use {}
}
}
@@ -191,7 +212,7 @@ class AttachmentsClassLoaderTests {
val att1 = importAttachment(ISOLATED_CONTRACTS_JAR_PATH.openStream(), "app", ISOLATED_CONTRACTS_JAR_PATH.file)
val att2 = importAttachment(fakeAttachment("net/corda/finance/contracts/isolated/AnotherDummyContract\$State.class", "some attackdata").inputStream(), "app", "file2.jar")
assertFailsWith(TransactionVerificationException.OverlappingAttachmentsException::class) {
- createClassloader(listOf(att1, att2))
+ createClassloader(listOf(att1, att2)).use {}
}
}
@@ -220,10 +241,10 @@ class AttachmentsClassLoaderTests {
val untrustedClassJar = importAttachment(fakeAttachment("/com/example/something/MaliciousClass.class", "some malicious data").inputStream(), "untrusted", "file2.jar")
val trustedClassJar = importAttachment(fakeAttachment("/com/example/something/VirtuousClass.class", "some other data").inputStream(), "app", "file3.jar")
- createClassloader(listOf(trustedResourceJar, untrustedResourceJar, trustedClassJar))
-
- assertFailsWith(TransactionVerificationException.UntrustedAttachmentsException::class) {
- createClassloader(listOf(trustedResourceJar, untrustedResourceJar, trustedClassJar, untrustedClassJar))
+ createClassloader(listOf(trustedResourceJar, untrustedResourceJar, trustedClassJar)).use {
+ assertFailsWith(TransactionVerificationException.UntrustedAttachmentsException::class) {
+ createClassloader(listOf(trustedResourceJar, untrustedResourceJar, trustedClassJar, untrustedClassJar)).use {}
+ }
}
}
@@ -257,7 +278,7 @@ class AttachmentsClassLoaderTests {
signers = listOf(keyPairA.public, keyPairB.public)
)
- createClassloader(untrustedAttachment)
+ createClassloader(untrustedAttachment).use {}
}
@Test(timeout=300_000)
@@ -287,7 +308,7 @@ class AttachmentsClassLoaderTests {
signers = listOf(keyPairA.public, keyPairB.public)
)
- createClassloader(untrustedAttachment)
+ createClassloader(untrustedAttachment).use {}
}
@Test(timeout=300_000)
@@ -306,7 +327,7 @@ class AttachmentsClassLoaderTests {
)
assertFailsWith(TransactionVerificationException.UntrustedAttachmentsException::class) {
- createClassloader(untrustedAttachment)
+ createClassloader(untrustedAttachment).use {}
}
}
@@ -337,7 +358,7 @@ class AttachmentsClassLoaderTests {
)
assertFailsWith(TransactionVerificationException.UntrustedAttachmentsException::class) {
- createClassloader(untrustedAttachment)
+ createClassloader(untrustedAttachment).use {}
}
}
@@ -380,10 +401,10 @@ class AttachmentsClassLoaderTests {
)
// pass the inherited trust attachment through the classloader first to ensure it does not affect the next loaded attachment
- createClassloader(inheritedTrustAttachment)
-
- assertFailsWith(TransactionVerificationException.UntrustedAttachmentsException::class) {
- createClassloader(untrustedAttachment)
+ createClassloader(inheritedTrustAttachment).use {
+ assertFailsWith(TransactionVerificationException.UntrustedAttachmentsException::class) {
+ createClassloader(untrustedAttachment).use {}
+ }
}
}
@@ -421,7 +442,7 @@ class AttachmentsClassLoaderTests {
)
assertFailsWith(TransactionVerificationException.UntrustedAttachmentsException::class) {
- createClassloader(untrustedAttachment)
+ createClassloader(untrustedAttachment).use {}
}
}
@@ -446,6 +467,6 @@ class AttachmentsClassLoaderTests {
signers = listOf(keyPairA.public)
)
- createClassloader(trustedAttachment)
+ createClassloader(trustedAttachment).use {}
}
}
diff --git a/core/build.gradle b/core/build.gradle
index 37b542c0e9..d024d164c0 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -101,7 +101,8 @@ task copyQuasarJar(type: Copy) {
jar {
finalizedBy(copyQuasarJar)
- baseName 'corda-core'
+ archiveBaseName = 'corda-core'
+ archiveClassifier = ''
}
configurations {
diff --git a/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt b/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt
index 51fad89acf..9d95250b56 100644
--- a/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt
+++ b/core/src/main/kotlin/net/corda/core/contracts/Attachment.kt
@@ -49,7 +49,7 @@ interface Attachment : NamedByHash {
/**
* Finds the named file case insensitively and copies it to the output stream.
- * @throws FileNotFoundException if the given path doesn't exist in the attachment.
+ * @throws [FileNotFoundException] if the given path doesn't exist in the attachment.
*/
@JvmDefault
fun extractFile(path: String, outputTo: OutputStream) = openAsJAR().use { it.extractFile(path, outputTo) }
diff --git a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt
index 1598e3c3a0..e750aa2f77 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt
@@ -108,7 +108,7 @@ object Crypto {
AlgorithmIdentifier(X9ObjectIdentifiers.ecdsa_with_SHA256, SECObjectIdentifiers.secp256k1),
listOf(AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, SECObjectIdentifiers.secp256k1)),
cordaBouncyCastleProvider.name,
- "ECDSA",
+ "EC",
"SHA256withECDSA",
ECNamedCurveTable.getParameterSpec("secp256k1"),
256,
@@ -123,7 +123,7 @@ object Crypto {
AlgorithmIdentifier(X9ObjectIdentifiers.ecdsa_with_SHA256, SECObjectIdentifiers.secp256r1),
listOf(AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, SECObjectIdentifiers.secp256r1)),
cordaBouncyCastleProvider.name,
- "ECDSA",
+ "EC",
"SHA256withECDSA",
ECNamedCurveTable.getParameterSpec("secp256r1"),
256,
diff --git a/core/src/main/kotlin/net/corda/core/crypto/NullKeys.kt b/core/src/main/kotlin/net/corda/core/crypto/NullKeys.kt
index 47b7c823eb..082163c5ef 100644
--- a/core/src/main/kotlin/net/corda/core/crypto/NullKeys.kt
+++ b/core/src/main/kotlin/net/corda/core/crypto/NullKeys.kt
@@ -2,7 +2,6 @@ package net.corda.core.crypto
import net.corda.core.KeepForDJVM
import net.corda.core.identity.AnonymousParty
-import net.corda.core.serialization.CordaSerializable
import java.security.PublicKey
@KeepForDJVM
diff --git a/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt b/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt
index 80a663a123..810b143dac 100644
--- a/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt
+++ b/core/src/main/kotlin/net/corda/core/flows/FinalityFlow.kt
@@ -1,6 +1,7 @@
package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
+import net.corda.core.CordaInternal
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.isFulfilledBy
import net.corda.core.identity.Party
@@ -45,6 +46,13 @@ class FinalityFlow private constructor(val transaction: SignedTransaction,
private val sessions: Collection,
private val newApi: Boolean,
private val statesToRecord: StatesToRecord = ONLY_RELEVANT) : FlowLogic() {
+
+ @CordaInternal
+ data class ExtraConstructorArgs(val oldParticipants: Collection, val sessions: Collection, val newApi: Boolean, val statesToRecord: StatesToRecord)
+
+ @CordaInternal
+ fun getExtraConstructorArgs() = ExtraConstructorArgs(oldParticipants, sessions, newApi, statesToRecord)
+
@Deprecated(DEPRECATION_MSG)
constructor(transaction: SignedTransaction, extraRecipients: Set, progressTracker: ProgressTracker) : this(
transaction, extraRecipients, progressTracker, emptyList(), false
diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowExternalOperations.kt b/core/src/main/kotlin/net/corda/core/flows/FlowExternalOperations.kt
index 306d6af9ed..ccc0181fd1 100644
--- a/core/src/main/kotlin/net/corda/core/flows/FlowExternalOperations.kt
+++ b/core/src/main/kotlin/net/corda/core/flows/FlowExternalOperations.kt
@@ -1,7 +1,5 @@
package net.corda.core.flows
-import net.corda.core.internal.ServiceHubCoreInternal
-import net.corda.core.node.ServiceHub
import java.util.concurrent.CompletableFuture
/**
diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt
index 6a45d1b0b6..f6f502cf98 100644
--- a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt
+++ b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt
@@ -24,9 +24,6 @@ import net.corda.core.messaging.DataFeed
import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.CordaSerializable
-import net.corda.core.serialization.SerializationDefaults
-import net.corda.core.serialization.SerializedBytes
-import net.corda.core.serialization.serialize
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.UntrustworthyData
@@ -130,6 +127,32 @@ abstract class FlowLogic {
*/
val serviceHub: ServiceHub get() = stateMachine.serviceHub
+ /**
+ * Returns `true` when the current [FlowLogic] has been killed (has received a command to halt its progress and terminate).
+ *
+ * Check this property in long-running computation loops to exit a flow that has been killed:
+ * ```
+ * while (!isKilled) {
+ * // do some computation
+ * }
+ * ```
+ *
+ * Ideal usage would include throwing a [KilledFlowException] which will lead to the termination of the flow:
+ * ```
+ * for (item in list) {
+ * if (isKilled) {
+ * throw KilledFlowException(runId)
+ * }
+ * // do some computation
+ * }
+ * ```
+ *
+ * Note, once the [isKilled] flag is set to `true` the flow may terminate once it reaches the next API function marked with the
+ * @[Suspendable] annotation. Therefore, it is possible to write a flow that does not interact with the [isKilled] flag while still
+ * terminating correctly.
+ */
+ val isKilled: Boolean get() = stateMachine.isKilled
+
/**
* Creates a communication session with [destination]. Subsequently you may send/receive using this session object. How the messaging
* is routed depends on the [Destination] type, including whether this call does any initial communication.
@@ -267,7 +290,7 @@ abstract class FlowLogic {
@Suspendable
internal fun FlowSession.sendAndReceiveWithRetry(receiveType: Class, payload: Any): UntrustworthyData {
val request = FlowIORequest.SendAndReceive(
- sessionToMessage = mapOf(this to payload.serialize(context = SerializationDefaults.P2P_CONTEXT)),
+ sessionToMessage = stateMachine.serialize(mapOf(this to payload)),
shouldRetrySend = true
)
return stateMachine.suspend(request, maySkipCheckpoint = false)[this]!!.checkPayloadIs(receiveType)
@@ -333,7 +356,7 @@ abstract class FlowLogic {
@JvmOverloads
fun sendAll(payload: Any, sessions: Set, maySkipCheckpoint: Boolean = false) {
val sessionToPayload = sessions.map { it to payload }.toMap()
- return sendAll(sessionToPayload, maySkipCheckpoint)
+ return sendAllMap(sessionToPayload, maySkipCheckpoint)
}
/**
@@ -348,23 +371,13 @@ abstract class FlowLogic {
*/
@Suspendable
@JvmOverloads
- fun sendAll(payloadsPerSession: Map, maySkipCheckpoint: Boolean = false) {
+ fun sendAllMap(payloadsPerSession: Map, maySkipCheckpoint: Boolean = false) {
val request = FlowIORequest.Send(
- sessionToMessage = serializePayloads(payloadsPerSession)
+ sessionToMessage = stateMachine.serialize(payloadsPerSession)
)
stateMachine.suspend(request, maySkipCheckpoint)
}
- @Suspendable
- private fun serializePayloads(payloadsPerSession: Map): Map> {
- val cachedSerializedPayloads = mutableMapOf>()
-
- return payloadsPerSession.mapValues { (_, payload) ->
- cachedSerializedPayloads[payload] ?: payload.serialize(context = SerializationDefaults.P2P_CONTEXT).also { cachedSerializedPayloads[payload] = it }
- }
- }
-
-
/**
* Invokes the given subflow. This function returns once the subflow completes successfully with the result
* returned by that subflow's [call] method. If the subflow has a progress tracker, it is attached to the
@@ -380,10 +393,8 @@ abstract class FlowLogic {
@Suspendable
@Throws(FlowException::class)
open fun subFlow(subLogic: FlowLogic): R {
- subLogic.stateMachine = stateMachine
- maybeWireUpProgressTracking(subLogic)
logger.debug { "Calling subflow: $subLogic" }
- val result = stateMachine.subFlow(subLogic)
+ val result = stateMachine.subFlow(this, subLogic)
logger.debug { "Subflow finished with result ${result.toString().abbreviate(300)}" }
return result
}
@@ -540,18 +551,6 @@ abstract class FlowLogic {
_stateMachine = value
}
- private fun maybeWireUpProgressTracking(subLogic: FlowLogic<*>) {
- val ours = progressTracker
- val theirs = subLogic.progressTracker
- if (ours != null && theirs != null && ours != theirs) {
- if (ours.currentStep == ProgressTracker.UNSTARTED) {
- logger.debug { "Initializing the progress tracker for flow: ${this::class.java.name}." }
- ours.nextStep()
- }
- ours.setChildProgressTracker(ours.currentStep, theirs)
- }
- }
-
private fun enforceNoDuplicates(sessions: List) {
require(sessions.size == sessions.toSet().size) { "A flow session can only appear once as argument." }
}
@@ -579,12 +578,7 @@ abstract class FlowLogic {
@Suspendable
fun await(operation: FlowExternalAsyncOperation): R {
// Wraps the passed in [FlowExternalAsyncOperation] so its [CompletableFuture] can be converted into a [CordaFuture]
- val flowAsyncOperation = object : FlowAsyncOperation, WrappedFlowExternalAsyncOperation {
- override val operation = operation
- override fun execute(deduplicationId: String): CordaFuture {
- return this.operation.execute(deduplicationId).asCordaFuture()
- }
- }
+ val flowAsyncOperation = WrappedFlowExternalAsyncOperation(operation)
val request = FlowIORequest.ExecuteAsyncOperation(flowAsyncOperation)
return stateMachine.suspend(request, false)
}
@@ -598,42 +592,82 @@ abstract class FlowLogic {
*/
@Suspendable
fun await(operation: FlowExternalOperation): R {
- val flowAsyncOperation = object : FlowAsyncOperation, WrappedFlowExternalOperation