Merge branch 'release/os/4.5' into jamesh/error-reporting-sync-29-04-20

# Conflicts:
#	node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt
This commit is contained in:
James Higgs 2020-04-29 11:47:14 +01:00
commit adbe030a2c
720 changed files with 10144 additions and 38638 deletions

View File

@ -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 */
}
}
}

View File

@ -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')

View File

@ -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)

View File

@ -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')

View File

@ -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.')
}
}
}
}
}
}
}

View File

@ -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)

View File

@ -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')

19
.github/workflows/jira_assign_issue.yml vendored Normal file
View File

@ -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

20
.github/workflows/jira_close_issue.yml vendored Normal file
View File

@ -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

36
.github/workflows/jira_create_issue.yml vendored Normal file
View File

@ -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'

2
Jenkinsfile vendored
View File

@ -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')

View File

@ -2,7 +2,7 @@
<img src="https://www.corda.net/wp-content/themes/corda/assets/images/crda-logo-big.svg" alt="Corda" width="500">
</p>
<a href="https://ci-master.corda.r3cev.com/viewType.html?buildTypeId=Corda_CordaBuild&tab=buildTypeStatusDiv&guest=1"><img src="https://ci.corda.r3cev.com/app/rest/builds/buildType:Corda_CordaBuild/statusIcon"/></a> [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
<a href="https://ci-master.corda.r3cev.com/viewType.html?buildTypeId=Corda_Build_ActiveReleaseBranches_BuildOsRelease45&tab=buildTypeStatusDiv&guest=1"><img src="https://ci.corda.r3cev.com/app/rest/builds/buildType:Corda_Build_ActiveReleaseBranches_BuildOsRelease45/statusIcon"/></a> [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
# Corda

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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<VALUE> {
/**
* 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<VALUE>
fun parse(configuration: Config, options: Options = Options.defaults): Valid<VALUE>
}
}
@ -109,11 +111,6 @@ object Configuration {
*/
interface Definition<TYPE> : Configuration.Property.Metadata, Configuration.Validator, Configuration.Value.Extractor<TYPE>, Configuration.Describer, Configuration.Value.Parser<TYPE> {
/**
* Validates target [Config] with default [Configuration.Validation.Options].
*/
fun validate(target: Config): Valid<Config> = validate(target, Configuration.Validation.Options.defaults)
override fun isSpecifiedBy(configuration: Config): Boolean = configuration.hasPath(key)
/**
@ -181,9 +178,8 @@ object Configuration {
fun <MAPPED> map(mappedTypeName: String, convert: (TYPE) -> MAPPED): Standard<MAPPED> = mapValid(mappedTypeName) { value -> valid(convert.invoke(value)) }
}
override fun parse(configuration: Config, options: Configuration.Validation.Options): Validated<TYPE, Validation.Error> {
return validate(configuration, options).mapValid { config -> valid(valueIn(config)) }
override fun parse(configuration: Config, options: Configuration.Options): Validated<TYPE, Validation.Error> {
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<Int> = 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<Boolean> = StandardProperty(key, Boolean::class.javaObjectType.simpleName, Config::getBoolean, Config::getBooleanList, sensitive)
fun boolean(key: String, sensitive: Boolean = false): Standard<Boolean> = 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<Double> = StandardProperty(key, Double::class.javaObjectType.simpleName, Config::getDouble, Config::getDoubleList, sensitive)
fun double(key: String, sensitive: Boolean = false): Standard<Double> = 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<Float> = double(key, sensitive).mapValid { value ->
val floatValue = value.toFloat()
if (floatValue.isInfinite() || floatValue.isNaN()) {
invalid<Float, Configuration.Validation.Error>(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<String> = StandardProperty(key, String::class.java.simpleName, Config::getString, Config::getStringList, sensitive)
fun string(key: String, sensitive: Boolean = false): Standard<String> = 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<Duration> = StandardProperty(key, Duration::class.java.simpleName, Config::getDuration, Config::getDurationList, sensitive)
fun duration(key: String, sensitive: Boolean = false): Standard<Duration> = 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<ConfigObject> = StandardProperty(key, ConfigObject::class.java.simpleName, Config::getObject, Config::getObjectList, sensitive, schema)
fun nestedObject(key: String, schema: Schema? = null, sensitive: Boolean = false): Standard<ConfigObject> = 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 : Enum<ENUM>> enum(key: String, enumClass: KClass<ENUM>, sensitive: Boolean = false): Standard<ENUM> = 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 : Enum<ENUM>> enum(key: String, enumClass: KClass<ENUM>, sensitive: Boolean = false): Standard<ENUM> = 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<Property.Definition<*>>
/**
* Validates target [Config] with default [Configuration.Validation.Options].
*/
fun validate(target: Config): Valid<Config> = 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<VALUE> = validate(configuration, options).mapValid(::parseValid)
final override fun parse(configuration: Config, options: Options): Valid<VALUE> = 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<VALUE>
protected abstract fun parseValid(configuration: Config, options: Options): Valid<VALUE>
}
/**
* 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<String> = 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<Config, Configuration.Validation.Error, Configuration.Validation.Options>
interface Validator : net.corda.common.validation.internal.Validator<Config, Configuration.Validation.Error, Configuration.Options>
}

View File

@ -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<Long>(key, Long::class.javaObjectType.simpleName, Config::getLong, Config::getLongList, sensitive) {
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> {
internal class LongProperty(key: String, sensitive: Boolean = false) : StandardProperty<Long>(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<Config> {
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<TYPE : Any>(override val key: String, typeNameArg: String, private val extractSingleValue: (Config, String) -> TYPE, internal val extractListValue: (Config, String) -> List<TYPE>, override val isSensitive: Boolean = false, final override val schema: Configuration.Schema? = null) : Configuration.Property.Definition.Standard<TYPE> {
typealias ValueSelector<T> = (Config, String, Configuration.Options) -> T
override fun valueIn(configuration: Config) = extractSingleValue.invoke(configuration, key)
internal open class StandardProperty<TYPE : Any>(override val key: String, typeNameArg: String, private val extractSingleValue: ValueSelector<TYPE>, internal val extractListValue: ValueSelector<List<TYPE>>, override val isSensitive: Boolean = false, final override val schema: Configuration.Schema? = null) : Configuration.Property.Definition.Standard<TYPE> {
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<TYPE : Any>(override val key: String, typeN
override fun list(): Configuration.Property.Definition.RequiredList<TYPE> = 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<Config> {
override fun validate(target: Config, options: Configuration.Options): Valid<Config> {
val errors = mutableSetOf<Configuration.Validation.Error>()
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<TYPE : Any>(delegate: StandardProperty<TYPE>) : Requi
override val typeName: String = "List<${delegate.typeName}>"
override fun valueIn(configuration: Config): List<TYPE> = delegate.extractListValue.invoke(configuration, key)
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> {
override fun valueIn(configuration: Config, options: Configuration.Options): List<TYPE> = delegate.extractListValue.invoke(configuration, key, options)
override fun validate(target: Config, options: Configuration.Options): Valid<Config> {
val errors = mutableSetOf<Configuration.Validation.Error>()
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<Configuration.Validation.Error>()) { 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<Configuration.Validation.Error>()) { one, other -> one + other }
.toSet()
}
}
return Validated.withResult(target, errors)
@ -77,17 +80,16 @@ private class ListProperty<TYPE : Any>(delegate: StandardProperty<TYPE>) : Requi
override fun <MAPPED> mapValid(mappedTypeName: String, convert: (List<TYPE>) -> Validated<MAPPED, Configuration.Validation.Error>): Configuration.Property.Definition.Required<MAPPED> = 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<TYPE>(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<Config> = delegate.validate(target, options)
override fun validate(target: Config, options: Configuration.Options): Valid<Config> = delegate.validate(target, options)
}
private class FunctionalProperty<TYPE, MAPPED>(delegate: Configuration.Property.Definition.Standard<TYPE>, private val mappedTypeName: String, internal val extractListValue: (Config, String) -> List<TYPE>, private val convert: (TYPE) -> Valid<MAPPED>) : RequiredDelegatedProperty<MAPPED, Configuration.Property.Definition.Standard<TYPE>>(delegate), Configuration.Property.Definition.Standard<MAPPED> {
private class FunctionalProperty<TYPE, MAPPED>(delegate: Configuration.Property.Definition.Standard<TYPE>, private val mappedTypeName: String, internal val extractListValue: ValueSelector<List<TYPE>>, private val convert: (TYPE) -> Valid<MAPPED>)
: RequiredDelegatedProperty<MAPPED, Configuration.Property.Definition.Standard<TYPE>>(delegate), Configuration.Property.Definition.Standard<MAPPED> {
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<TYPE, MAPPED>(delegate: Configuration.Property.
override fun list(): Configuration.Property.Definition.RequiredList<MAPPED> = FunctionalListProperty(this)
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> {
override fun validate(target: Config, options: Configuration.Options): Valid<Config> {
val errors = mutableSetOf<Configuration.Validation.Error>()
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<RAW, TYPE>(delegate: FunctionalProperty<RAW, TYPE>) : RequiredDelegatedProperty<List<TYPE>, FunctionalProperty<RAW, TYPE>>(delegate), Configuration.Property.Definition.RequiredList<TYPE> {
override val typeName: String = "List<${super.typeName}>"
override fun valueIn(configuration: Config): List<TYPE> = 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<Config> {
override fun valueIn(configuration: Config, options: Configuration.Options): List<TYPE> = 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<Config> {
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<RAW, TYPE>(delegate: FunctionalProperty<RAW
throw e
}
}
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<Configuration.Validation.Error>()) { 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<Configuration.Validation.Error>()) { one, other -> one + other }
.toSet()
return Validated.withResult(target, errors)
}
@ -165,12 +174,11 @@ private class FunctionalListProperty<RAW, TYPE>(delegate: FunctionalProperty<RAW
}
}
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 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 <MAPPED> mapValid(mappedTypeName: String, convert: (List<TYPE>) -> Validated<MAPPED, Configuration.Validation.Error>): Configuration.Property.Definition.Required<MAPPED> = ListMappingProperty(this, mappedTypeName, convert)
@ -187,18 +195,16 @@ private class OptionalDelegatedProperty<TYPE>(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<Config> {
override fun validate(target: Config, options: Configuration.Options): Valid<Config> {
val result = delegate.validate(target, options)
val errors = result.errors
val missingValueError = errors.asSequence().filterIsInstance<Configuration.Validation.Error.MissingValue>().filter { it.pathAsString == key }.singleOrNull()
@ -221,18 +227,17 @@ private abstract class RequiredDelegatedProperty<TYPE, DELEGATE : Configuration.
private class ListMappingProperty<TYPE, MAPPED>(private val delegate: Configuration.Property.Definition.RequiredList<TYPE>, private val mappedTypeName: String, private val convert: (List<TYPE>) -> Validated<MAPPED, Configuration.Validation.Error>) : Configuration.Property.Definition.Required<MAPPED> {
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<MAPPED> = OptionalDelegatedProperty(this)
override fun validate(target: Config, options: Configuration.Validation.Options): Validated<Config, Configuration.Validation.Error> {
override fun validate(target: Config, options: Configuration.Options): Validated<Config, Configuration.Validation.Error> {
val errors = mutableSetOf<Configuration.Validation.Error>()
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<TYPE, MAPPED>(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<Configuration.Validation.Error> {
private fun Configuration.Property.Definition<*>.errorsWhenExtractingValue(target: Config, options: Configuration.Options): Set<Configuration.Validation.Error> {
try {
valueIn(target)
valueIn(target, options)
return emptySet()
} catch (exception: ConfigException) {
if (isErrorExpected(exception)) {

View File

@ -16,7 +16,7 @@ internal class Schema(override val name: String?, unorderedProperties: Iterable<
}
}
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> {
override fun validate(target: Config, options: Configuration.Options): Valid<Config> {
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 {

View File

@ -21,7 +21,11 @@ inline fun <TYPE, reified MAPPED> Configuration.Property.Definition.RequiredList
inline fun <TYPE, reified MAPPED> Configuration.Property.Definition.RequiredList<TYPE>.map(noinline convert: (List<TYPE>) -> MAPPED): Configuration.Property.Definition.Required<MAPPED> = map(MAPPED::class.java.simpleName, convert)
operator fun <TYPE> Config.get(property: Configuration.Property.Definition<TYPE>): 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 <TYPE> get(property: Configuration.Value.Extractor<TYPE>): TYPE = property.valueIn(config, options)
}
inline fun <reified NESTED : Any> Configuration.Specification<*>.nested(specification: Configuration.Specification<NESTED>, key: String? = null, sensitive: Boolean = false): PropertyDelegate.Standard<NESTED> = nestedObject(schema = specification, key = key, sensitive = sensitive).map(ConfigObject::toConfig).mapValid { value -> specification.parse(value) }
@ -66,15 +70,6 @@ internal typealias Valid<TARGET> = Validated<TARGET, Configuration.Validation.Er
internal fun <TYPE> valid(target: TYPE) = Validated.valid<TYPE, Configuration.Validation.Error>(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 <TYPE> get(property: Configuration.Property.Definition<TYPE>): TYPE = property.valueIn(config)
operator fun <TYPE> get(property: Configuration.Value.Extractor<TYPE>): TYPE = property.valueIn(config)
}
/**
* Helper interface to mark objects that will have [ConfigurationWithOptions] in them.
*/

View File

@ -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<Int> {
override fun parse(configuration: Config, options: Configuration.Options): Valid<Int> {
return spec.parse(configuration)
}
private class Spec(key: String, versionDefaultValue: Int, prefix: String?) : Configuration.Specification<Int>("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))
}
}

View File

@ -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)
}
}

View File

@ -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 ->

View File

@ -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) }

View File

@ -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<Address> {
@ -27,7 +26,7 @@ class SpecificationTest {
val useSsl by boolean()
val addresses by nested(AddressesSpec)
override fun parseValid(configuration: Config) = valid<RpcSettings>(RpcSettingsImpl(configuration[addresses], configuration[useSsl]))
override fun parseValid(configuration: Config, options: Configuration.Options) = configuration.withOptions(options).let { valid<RpcSettings>(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<AtomicLong> {
return valid(AtomicLong(configuration[maxElement]!!))
override fun parseValid(configuration: Config, options: Configuration.Options): Valid<AtomicLong> {
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<AtomicLong> {
return valid(AtomicLong(configuration[maxElement]))
override fun parseValid(configuration: Config, options: Configuration.Options): Valid<AtomicLong> {
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)

View File

@ -15,5 +15,5 @@ internal fun <VALUE> extractValueWithErrors(errors: Set<Configuration.Validation
internal fun <VALUE> extractValue(value: Valid<VALUE>) = object : Configuration.Value.Parser<VALUE> {
override fun parse(configuration: Config, options: Configuration.Validation.Options): Valid<VALUE> = value
override fun parse(configuration: Config, options: Configuration.Options): Valid<VALUE> = value
}

View File

@ -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

View File

@ -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;
});

View File

@ -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<String>()
session.sendAndReceive<String>("hi there").unwrap { it }
session.sendAndReceive<String>("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<Unit>() {
@Suspendable
override fun call() {
session.receive<String>()
session.receive<String>().unwrap { it }
session.send("go away")
session.receive<String>().unwrap { it }
session.send("go away")
}
}
@InitiatingFlow
class PingPongFlow(val party: Party): FlowLogic<Unit>() {
@Suspendable
override fun call() {
val session = initiateFlow(party)
session.sendAndReceive<String>("ping pong").unwrap { it }
}
}
@InitiatedBy(PingPongFlow::class)
class PingPongResponder(val session: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
session.receive<String>().unwrap { it }
session.send("I got you bro")
}
}
@ -83,7 +133,7 @@ abstract class AbstractFlowExternalOperationTest {
fun createFuture(): CompletableFuture<Any> {
return CompletableFuture.supplyAsync(Supplier<Any> {
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<HospitalCounts>() {
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")

View File

@ -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<TimeoutException> {
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<TimeoutException> {
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<StateTransitionException> {
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<TimeoutException> {
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<Any>().apply {
completeExceptionally(createException())
completeExceptionally(e)
}
})
}
private fun createException() = when (exceptionType) {
HospitalizeFlowException::class.java -> HospitalizeFlowException("keep it around")

View File

@ -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)
}
}

View File

@ -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<TimeoutException> {
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<TimeoutException> {
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<TimeoutException> {
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")

View File

@ -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<UnexpectedFlowEndException> {
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<Unit>() {
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<Party>) : FlowLogic<Unit>() {
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<String>("what is up")
sessionOne.send("what is up 2")
sessionTwo.sendAndReceive<String>("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<Unit>() {
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<String>()
session.send("hi")
session.receive<String>()
locks[ourIdentity.name]!!.release()
try {
session.receive<String>()
} catch (e: UnexpectedFlowEndException) {
receivedKilledExceptions[ourIdentity.name] = true
locks[ourIdentity.name]!!.release()
throw e
}
}
}
@StartableByRPC
@InitiatingFlow
class AFlowThatGetsMurderedByItsFriend(private val party: Party) : FlowLogic<Unit>() {
companion object {
var receivedKilledException = false
}
@Suspendable
override fun call() {
val sessionOne = initiateFlow(party)
// trigger sessions with 2 counter parties
sessionOne.sendAndReceive<String>("what is up")
try {
sessionOne.receive<String>()
} catch (e: UnexpectedFlowEndException) {
receivedKilledException = true
throw e
}
}
}
@InitiatedBy(AFlowThatGetsMurderedByItsFriend::class)
class AFlowThatGetsMurderedByItsFriendResponder(private val session: FlowSession) : FlowLogic<Unit>() {
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<String>()
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<Unit>() {
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<Unit>() {
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<Long>() {
override fun call(): Long {
return serviceHub.jdbcSession().prepareStatement("select count(*) from node_checkpoints").use { ps ->
ps.executeQuery().use { rs ->
rs.next()
rs.getLong(1)
}
}
}
}
}

View File

@ -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<Pair<Instant, Instant>>() {
@Suspendable
override fun call(): Pair<Instant, Instant> {
val start = Instant.now()
sleep(5.seconds)
return start to Instant.now()
}
}
@StartableByRPC
class AnotherSleepyFlow : FlowLogic<Triple<Instant, Instant, Instant>>() {
@Suspendable
override fun call(): Triple<Instant, Instant, Instant> {
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<Pair<Instant, Instant>>() {
@Suspendable
override fun call(): Pair<Instant, Instant> {
subFlow(PingPongFlow(party))
val start = Instant.now()
sleep(5.seconds)
val finish = Instant.now()
val session = initiateFlow(party)
session.sendAndReceive<String>("hi")
session.sendAndReceive<String>("hi")
subFlow(PingPongFlow(party))
return start to finish
}
}
@InitiatedBy(SleepAndInteractWithPartyFlow::class)
class SleepAndInteractWithPartyResponder(val session: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
session.receive<String>().unwrap { it }
session.send("go away")
session.receive<String>().unwrap { it }
session.send("go away")
}
}
@InitiatingFlow
class PingPongFlow(val party: Party) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val session = initiateFlow(party)
session.sendAndReceive<String>("ping pong").unwrap { it }
}
}
@InitiatedBy(PingPongFlow::class)
class PingPongResponder(val session: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
session.receive<String>().unwrap { it }
session.send("I got you bro")
}
}
}

View File

@ -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<Any?>("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<Any?>("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 {}
}
}

View File

@ -101,7 +101,8 @@ task copyQuasarJar(type: Copy) {
jar {
finalizedBy(copyQuasarJar)
baseName 'corda-core'
archiveBaseName = 'corda-core'
archiveClassifier = ''
}
configurations {

View File

@ -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) }

View File

@ -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,

View File

@ -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

View File

@ -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<FlowSession>,
private val newApi: Boolean,
private val statesToRecord: StatesToRecord = ONLY_RELEVANT) : FlowLogic<SignedTransaction>() {
@CordaInternal
data class ExtraConstructorArgs(val oldParticipants: Collection<Party>, val sessions: Collection<FlowSession>, val newApi: Boolean, val statesToRecord: StatesToRecord)
@CordaInternal
fun getExtraConstructorArgs() = ExtraConstructorArgs(oldParticipants, sessions, newApi, statesToRecord)
@Deprecated(DEPRECATION_MSG)
constructor(transaction: SignedTransaction, extraRecipients: Set<Party>, progressTracker: ProgressTracker) : this(
transaction, extraRecipients, progressTracker, emptyList(), false

View File

@ -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
/**

View File

@ -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<out T> {
*/
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<out T> {
@Suspendable
internal fun <R : Any> FlowSession.sendAndReceiveWithRetry(receiveType: Class<R>, payload: Any): UntrustworthyData<R> {
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<out T> {
@JvmOverloads
fun sendAll(payload: Any, sessions: Set<FlowSession>, 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<out T> {
*/
@Suspendable
@JvmOverloads
fun sendAll(payloadsPerSession: Map<FlowSession, Any>, maySkipCheckpoint: Boolean = false) {
fun sendAllMap(payloadsPerSession: Map<FlowSession, Any>, maySkipCheckpoint: Boolean = false) {
val request = FlowIORequest.Send(
sessionToMessage = serializePayloads(payloadsPerSession)
sessionToMessage = stateMachine.serialize(payloadsPerSession)
)
stateMachine.suspend(request, maySkipCheckpoint)
}
@Suspendable
private fun serializePayloads(payloadsPerSession: Map<FlowSession, Any>): Map<FlowSession, SerializedBytes<Any>> {
val cachedSerializedPayloads = mutableMapOf<Any, SerializedBytes<Any>>()
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<out T> {
@Suspendable
@Throws(FlowException::class)
open fun <R> subFlow(subLogic: FlowLogic<R>): 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<out T> {
_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<FlowSession>) {
require(sessions.size == sessions.toSet().size) { "A flow session can only appear once as argument." }
}
@ -579,12 +578,7 @@ abstract class FlowLogic<out T> {
@Suspendable
fun <R : Any> await(operation: FlowExternalAsyncOperation<R>): R {
// Wraps the passed in [FlowExternalAsyncOperation] so its [CompletableFuture] can be converted into a [CordaFuture]
val flowAsyncOperation = object : FlowAsyncOperation<R>, WrappedFlowExternalAsyncOperation<R> {
override val operation = operation
override fun execute(deduplicationId: String): CordaFuture<R> {
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<out T> {
*/
@Suspendable
fun <R : Any> await(operation: FlowExternalOperation<R>): R {
val flowAsyncOperation = object : FlowAsyncOperation<R>, WrappedFlowExternalOperation<R> {
override val serviceHub = this@FlowLogic.serviceHub as ServiceHubCoreInternal
override val operation = operation
override fun execute(deduplicationId: String): CordaFuture<R> {
// Using a [CompletableFuture] allows unhandled exceptions to be thrown inside the background operation
// the exceptions will be set on the future by [CompletableFuture.AsyncSupply.run]
return CompletableFuture.supplyAsync(
Supplier { this.operation.execute(deduplicationId) },
serviceHub.externalOperationExecutor
).asCordaFuture()
}
}
val flowAsyncOperation = WrappedFlowExternalOperation(serviceHub as ServiceHubCoreInternal, operation)
val request = FlowIORequest.ExecuteAsyncOperation(flowAsyncOperation)
return stateMachine.suspend(request, false)
}
/**
* Helper function that throws a [KilledFlowException] if the current [FlowLogic] has been killed.
*
* Call this function in long-running computation loops to exit a flow that has been killed:
* ```
* for (item in list) {
* checkFlowIsNotKilled()
* // do some computation
* }
* ```
*
* See the [isKilled] property for more information.
*/
fun checkFlowIsNotKilled() {
if (isKilled) {
throw KilledFlowException(runId)
}
}
/**
* Helper function that throws a [KilledFlowException] if the current [FlowLogic] has been killed. The provided message is added to the
* thrown [KilledFlowException].
*
* Call this function in long-running computation loops to exit a flow that has been killed:
* ```
* for (item in list) {
* checkFlowIsNotKilled { "The flow $runId was killed while iterating through the list of items" }
* // do some computation
* }
* ```
*
* See the [isKilled] property for more information.
*/
fun checkFlowIsNotKilled(lazyMessage: () -> Any) {
if (isKilled) {
val message = lazyMessage()
throw KilledFlowException(runId, message.toString())
}
}
}
/**
* [WrappedFlowExternalAsyncOperation] is added to allow jackson to properly reference the data stored within the wrapped
* [FlowExternalAsyncOperation].
*/
private interface WrappedFlowExternalAsyncOperation<R : Any> {
val operation: FlowExternalAsyncOperation<R>
private class WrappedFlowExternalAsyncOperation<R : Any>(val operation: FlowExternalAsyncOperation<R>) : FlowAsyncOperation<R> {
override fun execute(deduplicationId: String): CordaFuture<R> {
return operation.execute(deduplicationId).asCordaFuture()
}
}
/**
* [WrappedFlowExternalOperation] is added to allow jackson to properly reference the data stored within the wrapped
* [FlowExternalOperation].
*
* The reference to [ServiceHub] is is also needed by Kryo to properly keep a reference to [ServiceHub] so that
* The reference to [ServiceHub] is also needed by Kryo to properly keep a reference to [ServiceHub] so that
* [FlowExternalOperation] can be run from the [ServiceHubCoreInternal.externalOperationExecutor] without causing errors when retrying a
* flow. A [NullPointerException] is thrown if [FlowLogic.serviceHub] is accessed from [FlowLogic.await] when retrying a flow.
*/
private interface WrappedFlowExternalOperation<R : Any> {
val serviceHub: ServiceHub
private class WrappedFlowExternalOperation<R : Any>(
val serviceHub: ServiceHubCoreInternal,
val operation: FlowExternalOperation<R>
) : FlowAsyncOperation<R> {
override fun execute(deduplicationId: String): CordaFuture<R> {
// Using a [CompletableFuture] allows unhandled exceptions to be thrown inside the background operation
// the exceptions will be set on the future by [CompletableFuture.AsyncSupply.run]
return CompletableFuture.supplyAsync(
Supplier { this.operation.execute(deduplicationId) },
serviceHub.externalOperationExecutor
).asCordaFuture()
}
}
/**

View File

@ -0,0 +1,14 @@
package net.corda.core.flows
import net.corda.core.CordaRuntimeException
/**
* An exception that is thrown when a flow has been killed.
*
* This exception can be returned and thrown to RPC clients waiting for the result of a flow's future.
*
* It can also be used in conjunction with [FlowLogic.isKilled] to escape long-running computation loops when a flow has been killed.
*/
class KilledFlowException(val id: StateMachineRunId, message: String) : CordaRuntimeException(message) {
constructor(id: StateMachineRunId) : this(id, "The flow $id was killed")
}

View File

@ -28,7 +28,7 @@ import java.util.jar.JarInputStream
// *Internal* Corda-specific utilities.
const val PLATFORM_VERSION = 6
const val PLATFORM_VERSION = 7
fun ServicesForResolution.ensureMinimumPlatformVersion(requiredMinPlatformVersion: Int, feature: String) {
checkMinimumPlatformVersion(networkParameters.minimumPlatformVersion, requiredMinPlatformVersion, feature)
@ -48,6 +48,20 @@ fun checkMinimumPlatformVersion(minimumPlatformVersion: Int, requiredMinPlatform
@Throws(NumberFormatException::class)
fun getJavaUpdateVersion(javaVersion: String): Long = javaVersion.substringAfter("_").substringBefore("-").toLong()
enum class JavaVersion(val versionString: String) {
Java_1_8("1.8"),
Java_11("11");
companion object {
fun isVersionAtLeast(version: JavaVersion): Boolean {
return currentVersion.toFloat() >= version.versionString.toFloat()
}
private val currentVersion: String = System.getProperty("java.specification.version") ?:
throw IllegalStateException("Unable to retrieve system property java.specification.version")
}
}
/** Provide access to internal method for AttachmentClassLoaderTests. */
@DeleteForDJVM
fun TransactionBuilder.toWireTransaction(services: ServicesForResolution, serializationContext: SerializationContext): WireTransaction {

View File

@ -8,6 +8,7 @@ import net.corda.core.context.InvocationContext
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.SerializedBytes
import org.slf4j.Logger
/** This is an internal interface that is implemented by code in the node module. You should look at [FlowLogic]. */
@ -17,6 +18,8 @@ interface FlowStateMachine<FLOWRETURN> {
@Suspendable
fun <SUSPENDRETURN : Any> suspend(ioRequest: FlowIORequest<SUSPENDRETURN>, maySkipCheckpoint: Boolean): SUSPENDRETURN
fun serialize(payloads: Map<FlowSession, Any>): Map<FlowSession, SerializedBytes<Any>>
@Suspendable
fun initiateFlow(destination: Destination, wellKnownParty: Party): FlowSession
@ -25,7 +28,7 @@ interface FlowStateMachine<FLOWRETURN> {
fun recordAuditEvent(eventType: String, comment: String, extraAuditData: Map<String, String>)
@Suspendable
fun <SUBFLOWRETURN> subFlow(subFlow: FlowLogic<SUBFLOWRETURN>): SUBFLOWRETURN
fun <SUBFLOWRETURN> subFlow(currentFlow: FlowLogic<*>, subFlow: FlowLogic<SUBFLOWRETURN>): SUBFLOWRETURN
@Suspendable
fun flowStackSnapshot(flowClass: Class<out FlowLogic<*>>): FlowStackSnapshot?
@ -44,4 +47,5 @@ interface FlowStateMachine<FLOWRETURN> {
val ourIdentity: Party
val ourSenderUUID: String?
val creationTime: Long
val isKilled: Boolean
}

View File

@ -4,6 +4,7 @@ package net.corda.core.internal
import net.corda.core.DeleteForDJVM
import net.corda.core.KeepForDJVM
import net.corda.core.StubOutForDJVM
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
@ -417,6 +418,7 @@ fun <T, U : T> uncheckedCast(obj: T) = obj as U
fun <K, V> Iterable<Pair<K, V>>.toMultiMap(): Map<K, List<V>> = this.groupBy({ it.first }) { it.second }
/** Returns the location of this class. */
@get:StubOutForDJVM
val Class<*>.location: URL get() = protectionDomain.codeSource.location
/** Convenience method to get the package name of a class literal. */

View File

@ -0,0 +1,10 @@
package net.corda.core.internal
/*
Constants for new features that can only be switched on at specific platform versions can be specified in this file.
The text constant describes the feature and the numeric specifies the platform version the feature is enabled at.
*/
object PlatformVersionSwitches {
const val REMOVE_NO_OVERLAP_RULE_FOR_REFERENCE_DATA_ATTACHMENTS = 7
const val ENABLE_P2P_COMPRESSION = 7
}

View File

@ -2,6 +2,7 @@ package net.corda.core.internal
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.DeleteForDJVM
import net.corda.core.internal.notary.NotaryService
import net.corda.core.node.ServiceHub
import net.corda.core.node.StatesToRecord
import java.util.concurrent.ExecutorService
@ -14,6 +15,11 @@ interface ServiceHubCoreInternal : ServiceHub {
val attachmentTrustCalculator: AttachmentTrustCalculator
/**
* Optional `NotaryService` which will be `null` for all non-Notary nodes.
*/
val notaryService: NotaryService?
fun createTransactionsResolver(flow: ResolveTransactionsFlow): TransactionsResolver
}

View File

@ -77,7 +77,12 @@ fun <T : Any> deserialiseComponentGroup(componentGroups: List<ComponentGroup>,
try {
factory.deserialize(component, clazz.java, context)
} catch (e: MissingAttachmentsException) {
throw e
/**
* [ServiceHub.signInitialTransaction] forgets to declare that
* it may throw any checked exceptions. Wrap this one inside
* an unchecked version to avoid breaking Java CorDapps.
*/
throw MissingAttachmentsRuntimeException(e.ids, e.message, e)
} catch (e: Exception) {
throw TransactionDeserialisationException(groupEnum, internalIndex, e)
}
@ -88,7 +93,7 @@ fun <T : Any> deserialiseComponentGroup(componentGroups: List<ComponentGroup>,
* Exception raised if an error was encountered while attempting to deserialise a component group in a transaction.
*/
class TransactionDeserialisationException(groupEnum: ComponentGroupEnum, index: Int, cause: Exception):
Exception("Failed to deserialise group $groupEnum at index $index in transaction: ${cause.message}", cause)
RuntimeException("Failed to deserialise group $groupEnum at index $index in transaction: ${cause.message}", cause)
/**
* Method to deserialise Commands from its two groups:

View File

@ -4,7 +4,6 @@ package net.corda.core.internal
import net.corda.core.KeepForDJVM
import net.corda.core.identity.CordaX500Name
import org.bouncycastle.asn1.ASN1Encodable
import org.bouncycastle.asn1.ASN1ObjectIdentifier
import org.bouncycastle.asn1.x500.AttributeTypeAndValue
import org.bouncycastle.asn1.x500.X500Name

View File

@ -14,6 +14,19 @@ abstract class NotaryService : SingletonSerializeAsToken() {
abstract val services: ServiceHub
abstract val notaryIdentityKey: PublicKey
/**
* Interfaces for the request and result formats of queries supported by notary services. To
* implement a new query, you must:
*
* - Define data classes which implement the [Query.Request] and [Query.Result] interfaces
* - Add corresponding handling for the new classes within the notary service implementations
* that you want to support the query.
*/
interface Query {
interface Request
interface Result
}
abstract fun start()
abstract fun stop()
@ -22,4 +35,18 @@ abstract class NotaryService : SingletonSerializeAsToken() {
* @param otherPartySession client [Party] making the request
*/
abstract fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?>
/**
* Processes a [Query.Request] and returns a [Query.Result].
*
* Note that this always throws an [UnsupportedOperationException] to handle notary
* implementations that do not support this functionality. This must be overridden by
* notary implementations wishing to support query functionality.
*
* Overrides of this function may themselves still throw an [UnsupportedOperationException],
* if they do not support specific query implementations
*/
open fun processQuery(query: Query.Request): Query.Result {
throw UnsupportedOperationException("Notary has not implemented query support")
}
}

View File

@ -252,7 +252,7 @@ interface CordaRPCOps : RPCOps {
* Note: This operation may be restricted only to node administrators.
* @param parametersHash hash of network parameters to accept
* @throws IllegalArgumentException if network map advertises update with different parameters hash then the one accepted by node's operator.
* @throws IOException if failed to send the approval to network map
* @throws [IOException] if failed to send the approval to network map
*/
// TODO This operation should be restricted to just node admins.
fun acceptNewNetworkParameters(parametersHash: SecureHash)

View File

@ -380,6 +380,26 @@ interface ServiceHub : ServicesForResolution {
* When used within a flow, this session automatically forms part of the enclosing flow transaction boundary,
* and thus queryable data will include everything committed as of the last checkpoint.
*
* We want to make sure users have a restricted access to administrative functions, this function will return a [RestrictedConnection] instance.
* The following methods are blocked:
* - abort(executor: Executor?)
* - clearWarnings()
* - close()
* - commit()
* - setSavepoint()
* - setSavepoint(name : String?)
* - releaseSavepoint(savepoint: Savepoint?)
* - rollback()
* - rollback(savepoint: Savepoint?)
* - setCatalog(catalog : String?)
* - setTransactionIsolation(level: Int)
* - setTypeMap(map: MutableMap<String, Class<*>>?)
* - setHoldability(holdability: Int)
* - setSchema(schema: String?)
* - setNetworkTimeout(executor: Executor?, milliseconds: Int)
* - setAutoCommit(autoCommit: Boolean)
* - setReadOnly(readOnly: Boolean)
*
* @throws IllegalStateException if called outside of a transaction.
* @return A [Connection]
*/
@ -393,6 +413,24 @@ interface ServiceHub : ServicesForResolution {
* NOTE: Suspendable flow operations such as send, receive, subFlow and sleep, cannot be called within the lambda.
*
* @param block a lambda function with access to an [EntityManager].
*
* We want to make sure users have a restricted access to administrative functions.
* The following methods are blocked:
* - close()
* - unwrap(cls: Class<T>?)
* - getDelegate(): Any
* - getMetamodel()
* - joinTransaction()
* - lock(entity: Any?, lockMode: LockModeType?)
* - lock(entity: Any?, lockMode: LockModeType?, properties: MutableMap<String, Any>?)
* - setProperty(propertyName: String?, value: Any?)
*
* getTransaction returns a [RestrictedEntityTransaction] to prevent unsafe manipulation of a flow's underlying
* database transaction.
* The following methods are blocked:
* - begin()
* - commit()
* - rollback()
*/
fun <T : Any?> withEntityManager(block: EntityManager.() -> T): T
@ -404,6 +442,24 @@ interface ServiceHub : ServicesForResolution {
* NOTE: Suspendable flow operations such as send, receive, subFlow and sleep, cannot be called within the lambda.
*
* @param block a lambda function with access to an [EntityManager].
*
* We want to make sure users have a restricted access to administrative functions.
* The following methods are blocked:
* - close()
* - unwrap(cls: Class<T>?)
* - getDelegate(): Any
* - getMetamodel()
* - joinTransaction()
* - lock(entity: Any?, lockMode: LockModeType?)
* - lock(entity: Any?, lockMode: LockModeType?, properties: MutableMap<String, Any>?)
* - setProperty(propertyName: String?, value: Any?)
*
* getTransaction returns a [RestrictedEntityTransaction] to prevent unsafe manipulation of a flow's underlying
* database transaction.
* The following methods are blocked:
* - begin()
* - commit()
* - rollback()
*/
fun withEntityManager(block: Consumer<EntityManager>)

View File

@ -8,7 +8,6 @@ import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.node.services.Vault
import net.corda.core.schemas.StatePersistable
import net.corda.core.serialization.CordaSerializable

View File

@ -302,7 +302,7 @@ object Builder {
@JvmStatic
@JvmOverloads
fun <R> FieldInfo.notEqual(value: R, exactMatch: Boolean = true) = predicate(Builder.equal(value, exactMatch))
fun <R> FieldInfo.notEqual(value: R, exactMatch: Boolean = true) = predicate(Builder.notEqual(value, exactMatch))
@JvmStatic
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")

View File

@ -0,0 +1,17 @@
package net.corda.core.serialization
import net.corda.core.CordaRuntimeException
import net.corda.core.KeepForDJVM
import net.corda.core.node.services.AttachmentId
@KeepForDJVM
@CordaSerializable
class MissingAttachmentsRuntimeException(val ids: List<AttachmentId>, message: String?, cause: Throwable?)
: CordaRuntimeException(message, cause) {
@Suppress("unused")
constructor(ids: List<AttachmentId>, message: String?) : this(ids, message, null)
@Suppress("unused")
constructor(ids: List<AttachmentId>) : this(ids, null, null)
}

View File

@ -18,6 +18,7 @@ import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.InputStream
import java.net.*
import java.security.Permission
import java.util.*
/**
@ -378,6 +379,15 @@ object AttachmentURLStreamHandlerFactory : URLStreamHandlerFactory {
private class AttachmentURLConnection(url: URL, private val attachment: Attachment) : URLConnection(url) {
override fun getContentLengthLong(): Long = attachment.size.toLong()
override fun getInputStream(): InputStream = attachment.open()
/**
* Define the permissions that [AttachmentsClassLoader] will need to
* use this [URL]. The attachment is stored in memory, and so we
* don't need any extra permissions here. But if we don't override
* [getPermission] then [AttachmentsClassLoader] will assign the
* default permission of ALL_PERMISSION to these classes'
* [java.security.ProtectionDomain]. This would be a security hole!
*/
override fun getPermission(): Permission? = null
override fun connect() {
connected = true
}

View File

@ -3,7 +3,6 @@ package net.corda.core.serialization.internal
import net.corda.core.DeleteForDJVM
import net.corda.core.DoNotImplement
import net.corda.core.KeepForDJVM
import net.corda.core.crypto.SecureHash
import net.corda.core.serialization.*
import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.sequence

View File

@ -134,7 +134,7 @@ open class TransactionBuilder(
*
* @returns A new [WireTransaction] that will be unaffected by further changes to this [TransactionBuilder].
*
* @throws ZoneVersionTooLowException if there are reference states and the zone minimum platform version is less than 4.
* @throws [ZoneVersionTooLowException] if there are reference states and the zone minimum platform version is less than 4.
*/
@Throws(MissingContractAttachments::class)
fun toWireTransaction(services: ServicesForResolution): WireTransaction = toWireTransactionWithContext(services, null)

View File

@ -317,7 +317,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
/**
* Checks that the given signature matches one of the commands and that it is a correct signature over the tx.
*
* @throws SignatureException if the signature didn't match the transaction contents.
* @throws [SignatureException] if the signature didn't match the transaction contents.
* @throws IllegalArgumentException if the signature key doesn't appear in any command.
*/
fun checkSignature(sig: TransactionSignature) {

View File

@ -471,9 +471,9 @@ class CryptoUtilsTest {
val privKeyDecoded = Crypto.decodePrivateKey(privKey.encoded)
val pubKeyDecoded = Crypto.decodePublicKey(pubKey.encoded)
assertEquals(privKeyDecoded.algorithm, "ECDSA")
assertEquals(privKeyDecoded.algorithm, "EC")
assertEquals((privKeyDecoded as ECKey).parameters, ECNamedCurveTable.getParameterSpec("secp256k1"))
assertEquals(pubKeyDecoded.algorithm, "ECDSA")
assertEquals(pubKeyDecoded.algorithm, "EC")
assertEquals((pubKeyDecoded as ECKey).parameters, ECNamedCurveTable.getParameterSpec("secp256k1"))
}
@ -481,9 +481,9 @@ class CryptoUtilsTest {
fun `ECDSA secp256r1 scheme finder by key type`() {
val keyPairR1 = Crypto.generateKeyPair(ECDSA_SECP256R1_SHA256)
val (privR1, pubR1) = keyPairR1
assertEquals(privR1.algorithm, "ECDSA")
assertEquals(privR1.algorithm, "EC")
assertEquals((privR1 as ECKey).parameters, ECNamedCurveTable.getParameterSpec("secp256r1"))
assertEquals(pubR1.algorithm, "ECDSA")
assertEquals(pubR1.algorithm, "EC")
assertEquals((pubR1 as ECKey).parameters, ECNamedCurveTable.getParameterSpec("secp256r1"))
}
@ -530,11 +530,11 @@ class CryptoUtilsTest {
val encodedPubK1 = pubK1.encoded
val decodedPrivK1 = Crypto.decodePrivateKey(encodedPrivK1)
assertEquals(decodedPrivK1.algorithm, "ECDSA")
assertEquals(decodedPrivK1.algorithm, "EC")
assertEquals(decodedPrivK1, privK1)
val decodedPubK1 = Crypto.decodePublicKey(encodedPubK1)
assertEquals(decodedPubK1.algorithm, "ECDSA")
assertEquals(decodedPubK1.algorithm, "EC")
assertEquals(decodedPubK1, pubK1)
}
@ -546,11 +546,11 @@ class CryptoUtilsTest {
val encodedPubR1 = pubR1.encoded
val decodedPrivR1 = Crypto.decodePrivateKey(encodedPrivR1)
assertEquals(decodedPrivR1.algorithm, "ECDSA")
assertEquals(decodedPrivR1.algorithm, "EC")
assertEquals(decodedPrivR1, privR1)
val decodedPubR1 = Crypto.decodePublicKey(encodedPubR1)
assertEquals(decodedPubR1.algorithm, "ECDSA")
assertEquals(decodedPubR1.algorithm, "EC")
assertEquals(decodedPubR1, pubR1)
}

View File

@ -0,0 +1,156 @@
package net.corda.core.node.services.vault
import net.corda.core.node.services.vault.Builder.`in`
import net.corda.core.node.services.vault.Builder.equal
import net.corda.core.node.services.vault.Builder.greaterThan
import net.corda.core.node.services.vault.Builder.greaterThanOrEqual
import net.corda.core.node.services.vault.Builder.isNull
import net.corda.core.node.services.vault.Builder.lessThan
import net.corda.core.node.services.vault.Builder.lessThanOrEqual
import net.corda.core.node.services.vault.Builder.like
import net.corda.core.node.services.vault.Builder.notEqual
import net.corda.core.node.services.vault.Builder.notIn
import net.corda.core.node.services.vault.Builder.notLike
import net.corda.core.node.services.vault.Builder.notNull
import net.corda.core.node.services.vault.ColumnPredicate.AggregateFunction
import net.corda.core.node.services.vault.ColumnPredicate.Between
import net.corda.core.node.services.vault.ColumnPredicate.BinaryComparison
import net.corda.core.node.services.vault.ColumnPredicate.CollectionExpression
import net.corda.core.node.services.vault.ColumnPredicate.EqualityComparison
import net.corda.core.node.services.vault.ColumnPredicate.Likeness
import net.corda.core.node.services.vault.ColumnPredicate.NullExpression
import net.corda.core.node.services.vault.CriteriaExpression.ColumnPredicateExpression
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.ObjectAssert
import org.junit.Test
import javax.persistence.Entity
class QueryCriteriaUtilsBuilderTest {
/** JPA Entity class needed by `getField` */
@Entity
private class TestEntity(val field: String)
/** Returns a `FieldInfo` object to work on */
private val fieldInfo: FieldInfo get() = getField("field", TestEntity::class.java)
/** Thrown for the `ColumnPredicate` types that have no `operator` field */
private class ColumnPredicateHasNoOperatorFieldException : Exception("This ColumnPredicate has no operator field")
/** Returns the `operator` for the given `ColumnPredicate` */
private fun ColumnPredicate<out Any?>.getOperator(): Operator = when (this) {
is AggregateFunction -> throw ColumnPredicateHasNoOperatorFieldException()
is Between -> throw ColumnPredicateHasNoOperatorFieldException()
is BinaryComparison<*> -> operator
is CollectionExpression -> operator
is EqualityComparison<*> -> operator
is Likeness -> operator
is NullExpression -> operator
}
/** Returns the `operator` for the given `ColumnPredicateExpression` */
private fun ColumnPredicateExpression<Any, *>.getOperator(): Operator = this.predicate.getOperator()
/** Assert that the `ColumnPredicateExpression` uses the given `Operator`. */
private fun <T : ColumnPredicateExpression<Any, C>, C> ObjectAssert<T>.usesOperator(operator: Operator) {
extracting {
assertThat(it.getOperator()).isEqualTo(operator)
}
}
/** Sample `String` value to pass to the predicate expression */
private val stringValue = ""
/** Sample `List` value to pass to the predicate expression */
private val listValue = emptyList<String>()
@Test(timeout = 500)
fun `equal predicate uses EQUAL operator`() {
assertThat(fieldInfo.equal(stringValue)).usesOperator(EqualityComparisonOperator.EQUAL)
}
@Test(timeout = 500)
fun `equal predicate (exactMatch=false) uses EQUAL_IGNORE_CASE operator`() {
assertThat(fieldInfo.equal(stringValue, exactMatch = false)).usesOperator(EqualityComparisonOperator.EQUAL_IGNORE_CASE)
}
@Test(timeout = 500)
fun `notEqual predicate uses NOT_EQUAL operator`() {
assertThat(fieldInfo.notEqual(stringValue)).usesOperator(EqualityComparisonOperator.NOT_EQUAL)
}
@Test(timeout = 500)
fun `notEqual predicate (exactMatch=false) uses NOT_EQUAL_IGNORE_CASE operator`() {
assertThat(fieldInfo.notEqual(stringValue, exactMatch = false)).usesOperator(EqualityComparisonOperator.NOT_EQUAL_IGNORE_CASE)
}
@Test(timeout = 500)
fun `lessThan predicate uses LESS_THAN operator`() {
assertThat(fieldInfo.lessThan(stringValue)).usesOperator(BinaryComparisonOperator.LESS_THAN)
}
@Test(timeout = 500)
fun `lessThanOrEqual predicate uses LESS_THAN_OR_EQUAL operator`() {
assertThat(fieldInfo.lessThanOrEqual(stringValue)).usesOperator(BinaryComparisonOperator.LESS_THAN_OR_EQUAL)
}
@Test(timeout = 500)
fun `greaterThan predicate uses GREATER_THAN operator`() {
assertThat(fieldInfo.greaterThan(stringValue)).usesOperator(BinaryComparisonOperator.GREATER_THAN)
}
@Test(timeout = 500)
fun `greaterThanOrEqual predicate uses GREATER_THAN_OR_EQUAL operator`() {
assertThat(fieldInfo.greaterThanOrEqual(stringValue)).usesOperator(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL)
}
@Test(timeout = 500)
fun `in predicate uses IN operator`() {
assertThat(fieldInfo.`in`(listValue)).usesOperator(CollectionOperator.IN)
}
@Test(timeout = 500)
fun `in predicate (exactMatch=false) uses IN_IGNORE_CASE operator`() {
assertThat(fieldInfo.`in`(listValue, exactMatch = false)).usesOperator(CollectionOperator.IN_IGNORE_CASE)
}
@Test(timeout = 500)
fun `notIn predicate uses NOT_IN operator`() {
assertThat(fieldInfo.notIn(listValue)).usesOperator(CollectionOperator.NOT_IN)
}
@Test(timeout = 500)
fun `notIn predicate (exactMatch=false) uses NOT_IN_IGNORE_CASE operator`() {
assertThat(fieldInfo.notIn(listValue, exactMatch = false)).usesOperator(CollectionOperator.NOT_IN_IGNORE_CASE)
}
@Test(timeout = 500)
fun `like predicate uses LIKE operator`() {
assertThat(fieldInfo.like(stringValue)).usesOperator(LikenessOperator.LIKE)
}
@Test(timeout = 500)
fun `like predicate (exactMatch=false) uses LIKE_IGNORE_CASE operator`() {
assertThat(fieldInfo.like(stringValue, exactMatch = false)).usesOperator(LikenessOperator.LIKE_IGNORE_CASE)
}
@Test(timeout = 500)
fun `notLike predicate uses NOT_LIKE operator`() {
assertThat(fieldInfo.notLike(stringValue)).usesOperator(LikenessOperator.NOT_LIKE)
}
@Test(timeout = 500)
fun `notLike predicate (exactMatch=false) uses NOT_LIKE_IGNORE_CASE operator`() {
assertThat(fieldInfo.notLike(stringValue, exactMatch = false)).usesOperator(LikenessOperator.NOT_LIKE_IGNORE_CASE)
}
@Test(timeout = 500)
fun `isNull predicate uses IS_NULL operator`() {
assertThat(fieldInfo.isNull()).usesOperator(NullOperator.IS_NULL)
}
@Test(timeout = 500)
fun `notNull predicate uses NOT_NULL operator`() {
assertThat(fieldInfo.notNull()).usesOperator(NullOperator.NOT_NULL)
}
}

View File

@ -87,11 +87,9 @@
<ID>ComplexCondition:InternalUtils.kt$it.type == this &amp;&amp; it.isPublic &amp;&amp; it.isStatic &amp;&amp; it.isFinal</ID>
<ID>ComplexCondition:Main.kt$Main$(hostname != null) &amp;&amp; (port != null) &amp;&amp; (username != null) &amp;&amp; (password != null)</ID>
<ID>ComplexCondition:Schema.kt$obj == null || obj is DescribedType || obj is Binary || forGenericType(type).run { isPrimitive(this) || this == TopType }</ID>
<ID>ComplexCondition:TopLevelTransition.kt$TopLevelTransition$currentState.isTransactionTracked &amp;&amp; checkpoint.flowState is FlowState.Started &amp;&amp; checkpoint.flowState.flowIORequest is FlowIORequest.WaitForLedgerCommit &amp;&amp; checkpoint.flowState.flowIORequest.hash == event.transaction.id</ID>
<ID>ComplexCondition:WireTransaction.kt$WireTransaction$notary != null &amp;&amp; (inputs.isNotEmpty() || references.isNotEmpty() || timeWindow != null)</ID>
<ID>ComplexMethod:AMQPBridgeManager.kt$AMQPBridgeManager.AMQPBridge$private fun clientArtemisMessageHandler(artemisMessage: ClientMessage)</ID>
<ID>ComplexMethod:AMQPBridgeTest.kt$AMQPBridgeTest$@Test(timeout=300_000) fun `test acked and nacked messages`()</ID>
<ID>ComplexMethod:AMQPChannelHandler.kt$AMQPChannelHandler$override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any)</ID>
<ID>ComplexMethod:AMQPTypeIdentifierParser.kt$AMQPTypeIdentifierParser$// Make sure our inputs aren't designed to blow things up. private fun validate(typeString: String)</ID>
<ID>ComplexMethod:ANSIProgressRenderer.kt$ANSIProgressRenderer$// Returns number of lines rendered. private fun renderLevel(ansi: Ansi, error: Boolean): Int</ID>
<ID>ComplexMethod:ANSIProgressRenderer.kt$ANSIProgressRenderer$@Synchronized protected fun draw(moveUp: Boolean, error: Throwable? = null)</ID>
@ -124,13 +122,10 @@
<ID>ComplexMethod:ConfigUtilities.kt$// For Iterables figure out the type parameter and apply the same logic as above on the individual elements. private fun Iterable&lt;*&gt;.toConfigIterable(field: Field): Iterable&lt;Any?&gt;</ID>
<ID>ComplexMethod:ConfigUtilities.kt$// TODO Move this to KeyStoreConfigHelpers. fun MutualSslConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500Name, signingCertificateStore: FileBasedCertificateStoreSupplier, certificatesDirectory: Path, cryptoService: CryptoService? = null)</ID>
<ID>ComplexMethod:ConfigUtilities.kt$@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") // Reflect over the fields of the receiver and generate a value Map that can use to create Config object. private fun Any.toConfigMap(): Map&lt;String, Any&gt;</ID>
<ID>ComplexMethod:ConfigUtilities.kt$private fun Config.getCollectionValue(path: String, type: KType, onUnknownKeys: (Set&lt;String&gt;, logger: Logger) -&gt; Unit, nestedPath: String?, baseDirectory: Path?): Collection&lt;Any&gt;</ID>
<ID>ComplexMethod:ConfigUtilities.kt$private fun Config.getSingleValue(path: String, type: KType, onUnknownKeys: (Set&lt;String&gt;, logger: Logger) -&gt; Unit, nestedPath: String?, baseDirectory: Path?): Any?</ID>
<ID>ComplexMethod:ConfigUtilities.kt$private fun convertValue(value: Any): Any</ID>
<ID>ComplexMethod:ConnectionStateMachine.kt$ConnectionStateMachine$override fun onConnectionFinal(event: Event)</ID>
<ID>ComplexMethod:ConnectionStateMachine.kt$ConnectionStateMachine$override fun onDelivery(event: Event)</ID>
<ID>ComplexMethod:ConstraintsUtils.kt$ fun AttachmentConstraint.canBeTransitionedFrom(input: AttachmentConstraint, attachment: ContractAttachment): Boolean</ID>
<ID>ComplexMethod:CordaCliWrapper.kt$fun CordaCliWrapper.start(args: Array&lt;String&gt;)</ID>
<ID>ComplexMethod:CordaPersistence.kt$CordaPersistence$private fun &lt;T&gt; inTopLevelTransaction(isolationLevel: TransactionIsolationLevel, recoverableFailureTolerance: Int, recoverAnyNestedSQLException: Boolean, statement: DatabaseTransaction.() -&gt; T): T</ID>
<ID>ComplexMethod:CordaRPCClient.kt$CordaRPCClientConfiguration$override fun equals(other: Any?): Boolean</ID>
<ID>ComplexMethod:CordaRPCClientTest.kt$CordaRPCClientTest$@Test(timeout=300_000) fun `shutdown command stops the node`()</ID>
@ -164,7 +159,6 @@
<ID>ComplexMethod:MerkleTransaction.kt$FilteredTransaction.Companion$ private fun filterWithFun(wtx: WireTransaction, filtering: Predicate&lt;Any&gt;): List&lt;FilteredComponentGroup&gt;</ID>
<ID>ComplexMethod:NetworkBootstrapper.kt$NetworkBootstrapper$private fun bootstrap( directory: Path, cordappJars: List&lt;Path&gt;, copyCordapps: CopyCordapps, fromCordform: Boolean, networkParametersOverrides: NetworkParametersOverrides = NetworkParametersOverrides() )</ID>
<ID>ComplexMethod:NetworkBootstrapper.kt$NetworkBootstrapper$private fun createNodeDirectoriesIfNeeded(directory: Path, fromCordform: Boolean): Boolean</ID>
<ID>ComplexMethod:NetworkMapUpdater.kt$NetworkMapUpdater$fun updateNetworkMapCache(): Duration</ID>
<ID>ComplexMethod:NetworkParametersReader.kt$NetworkParametersReader$fun read(): NetworkParametersAndSigned</ID>
<ID>ComplexMethod:NetworkRegistrationHelper.kt$NetworkRegistrationHelper$ private fun pollServerForCertificates(requestId: String): List&lt;X509Certificate&gt;</ID>
<ID>ComplexMethod:NewTransaction.kt$NewTransaction$fun show(window: Window)</ID>
@ -182,7 +176,6 @@
<ID>ComplexMethod:RPCClientProxyHandler.kt$RPCClientProxyHandler$// This is the general function that transforms a client side RPC to internal Artemis messages. override fun invoke(proxy: Any, method: Method, arguments: Array&lt;out Any?&gt;?): Any?</ID>
<ID>ComplexMethod:RPCClientProxyHandler.kt$RPCClientProxyHandler$private fun attemptReconnect()</ID>
<ID>ComplexMethod:RPCServer.kt$RPCServer$private fun clientArtemisMessageHandler(artemisMessage: ClientMessage)</ID>
<ID>ComplexMethod:ReconnectingCordaRPCOps.kt$ReconnectingCordaRPCOps.ErrorInterceptingHandler$ private fun doInvoke(method: Method, args: Array&lt;out Any&gt;?, maxNumberOfAttempts: Int): Any?</ID>
<ID>ComplexMethod:ReconnectingCordaRPCOps.kt$ReconnectingCordaRPCOps.ReconnectingRPCConnection$ private tailrec fun establishConnectionWithRetry( retryInterval: Duration, roundRobinIndex: Int = 0, retries: Int = -1 ): CordaRPCConnection?</ID>
<ID>ComplexMethod:RemoteTypeCarpenter.kt$SchemaBuildingRemoteTypeCarpenter$override fun carpent(typeInformation: RemoteTypeInformation): Type</ID>
<ID>ComplexMethod:RpcReconnectTests.kt$RpcReconnectTests$ @Test(timeout=300_000) fun `test that the RPC client is able to reconnect and proceed after node failure, restart, or connection reset`()</ID>
@ -215,7 +208,6 @@
<ID>EmptyDefaultConstructor:FlowRetryTest.kt$AsyncRetryFlow$()</ID>
<ID>EmptyDefaultConstructor:FlowRetryTest.kt$RetryFlow$()</ID>
<ID>EmptyDefaultConstructor:FlowRetryTest.kt$ThrowingFlow$()</ID>
<ID>EmptyElseBlock:CordaCliWrapper.kt${ }</ID>
<ID>EmptyIfBlock:ContentSignerBuilder.kt$ContentSignerBuilder.SignatureOutputStream$if (alreadySigned) throw IllegalStateException("Cannot write to already signed object")</ID>
<ID>EmptyIfBlock:InMemoryIdentityService.kt$InMemoryIdentityService${ }</ID>
<ID>EmptyKtFile:KryoHook.kt$.KryoHook.kt</ID>
@ -327,11 +319,9 @@
<ID>ForbiddenComment:CustomSerializer.kt$CustomSerializer.SubClass$// TODO: should this be a custom serializer at all, or should it just be a plain AMQPSerializer?</ID>
<ID>ForbiddenComment:CustomSerializer.kt$CustomSerializer.SubClass$// TODO: should this be empty or contain the schema of the super?</ID>
<ID>ForbiddenComment:DbTransactionsResolver.kt$DbTransactionsResolver$// TODO: This approach has two problems. Analyze and resolve them:</ID>
<ID>ForbiddenComment:DefaultKryoCustomizer.kt$DefaultKryoCustomizer$// TODO: re-organise registrations into logical groups before v1.0</ID>
<ID>ForbiddenComment:DigitalSignatureWithCert.kt$// TODO: Rename this to DigitalSignature.WithCert once we're happy for it to be public API. The methods will need documentation</ID>
<ID>ForbiddenComment:DriverDSLImpl.kt$DriverDSLImpl$// TODO: Derive name from the full picked name, don't just wrap the common name</ID>
<ID>ForbiddenComment:DriverDSLImpl.kt$DriverDSLImpl$//TODO: remove this once we can bundle quasar properly.</ID>
<ID>ForbiddenComment:DriverDSLImpl.kt$DriverDSLImpl.Companion$// TODO: There is pending work to fix this issue without custom blacklisting. See: https://r3-cev.atlassian.net/browse/CORDA-2164.</ID>
<ID>ForbiddenComment:DriverDSLImpl.kt$DriverDSLImpl.LocalNetworkMap$// TODO: this object will copy NodeInfo files from started nodes to other nodes additional-node-infos/</ID>
<ID>ForbiddenComment:DummyFungibleContract.kt$DummyFungibleContract$// TODO: This doesn't work with the trader demo, so use the underlying key instead</ID>
<ID>ForbiddenComment:E2ETestKeyManagementService.kt$E2ETestKeyManagementService$// TODO: A full KeyManagementService implementation needs to record activity to the Audit Service and to limit</ID>
@ -350,7 +340,6 @@
<ID>ForbiddenComment:FlowLogicRefFactoryImpl.kt$FlowLogicRefFactoryImpl$// TODO: This is used via RPC but it's probably better if we pass in argument names and values explicitly</ID>
<ID>ForbiddenComment:GenerateRpcSslCertsCli.kt$GenerateRpcSslCerts$// TODO: consider adding a password strength policy.</ID>
<ID>ForbiddenComment:GuiUtilities.kt$// TODO: This is a temporary fix for the UI to show the correct issuer identity, this will break when we start randomizing keys. More work is needed here when the identity work is done.</ID>
<ID>ForbiddenComment:HibernateConfiguration.kt$HibernateConfiguration$// TODO: require mechanism to set schemaOptions (databaseSchema, tablePrefix) which are not global to session</ID>
<ID>ForbiddenComment:IRS.kt$FloatingRatePaymentEvent$// TODO: Should an uncalculated amount return a zero ? null ? etc.</ID>
<ID>ForbiddenComment:IRS.kt$InterestRateSwap$// TODO: Confirm: would someone really enter a swap with a negative fixed rate?</ID>
<ID>ForbiddenComment:IRS.kt$InterestRateSwap$// TODO: further tests</ID>
@ -388,6 +377,7 @@
<ID>ForbiddenComment:LegalNameValidator.kt$LegalNameValidator.Rule.Companion$// TODO: Implement confusable character detection if we add more scripts.</ID>
<ID>ForbiddenComment:LocalTypeInformationBuilder.kt$// TODO: Revisit this when Kotlin issue is fixed.</ID>
<ID>ForbiddenComment:LoggingBuyerFlow.kt$LoggingBuyerFlow$// TODO: This is potentially very expensive, and requires transaction details we may no longer have once</ID>
<ID>ForbiddenComment:LoopbackBridgeManager.kt$LoopbackBridgeManager.LoopbackBridge$// TODO: refactor MDC support, duplicated in AMQPBridgeManager.</ID>
<ID>ForbiddenComment:MockServices.kt$MockServices.Companion$// TODO: Can we use an X509 principal generator here?</ID>
<ID>ForbiddenComment:NetParams.kt$NetParamsSigner$// TODO: not supported</ID>
<ID>ForbiddenComment:NetworkBootstrapper.kt$NetworkBootstrapper$// TODO: pass a commandline parameter to the bootstrapper instead. Better yet, a notary config map</ID>
@ -630,8 +620,6 @@
<ID>FunctionNaming:VersionExtractorTest.kt$VersionExtractorTest$@Test(timeout=300_000) fun version_header_extraction_no_metadata()</ID>
<ID>FunctionNaming:VersionExtractorTest.kt$VersionExtractorTest$@Test(timeout=300_000) fun version_header_extraction_no_value()</ID>
<ID>FunctionNaming:VersionExtractorTest.kt$VersionExtractorTest$@Test(timeout=300_000) fun version_header_extraction_present()</ID>
<ID>FunctionNaming:VersionedParsingExampleTest.kt$VersionedParsingExampleTest$@Test(timeout=300_000) fun correct_parsing_function_is_used_for_present_version()</ID>
<ID>FunctionNaming:VersionedParsingExampleTest.kt$VersionedParsingExampleTest$@Test(timeout=300_000) fun default_value_is_used_for_absent_version()</ID>
<ID>LargeClass:AbstractNode.kt$AbstractNode&lt;S&gt; : SingletonSerializeAsToken</ID>
<ID>LargeClass:SingleThreadedStateMachineManager.kt$SingleThreadedStateMachineManager : StateMachineManagerStateMachineManagerInternal</ID>
<ID>LongMethod:FlowCookbook.kt$InitiatorFlow$@Suppress("RemoveExplicitTypeArguments") @Suspendable override fun call()</ID>
@ -643,8 +631,8 @@
<ID>LongParameterList:AbstractNode.kt$(databaseConfig: DatabaseConfig, wellKnownPartyFromX500Name: (CordaX500Name) -&gt; Party?, wellKnownPartyFromAnonymous: (AbstractParty) -&gt; Party?, schemaService: SchemaService, hikariProperties: Properties, cacheFactory: NamedCacheFactory, customClassLoader: ClassLoader?)</ID>
<ID>LongParameterList:AbstractNode.kt$(hikariProperties: Properties, databaseConfig: DatabaseConfig, schemas: Set&lt;MappedSchema&gt;, metricRegistry: MetricRegistry? = null, cordappLoader: CordappLoader? = null, currentDir: Path? = null, ourName: CordaX500Name)</ID>
<ID>LongParameterList:ArtemisMessagingServer.kt$ArtemisMessagingServer$(name: String, send: Boolean = false, consume: Boolean = false, createDurableQueue: Boolean = false, deleteDurableQueue: Boolean = false, createNonDurableQueue: Boolean = false, deleteNonDurableQueue: Boolean = false, manage: Boolean = false, browse: Boolean = false)</ID>
<ID>LongParameterList:ArtemisRpcBroker.kt$ArtemisRpcBroker.Companion$(configuration: MutualSslConfiguration, address: NetworkHostAndPort, adminAddress: NetworkHostAndPort, securityManager: RPCSecurityManager, maxMessageSize: Int, jmxEnabled: Boolean, baseDirectory: Path, shouldStartLocalShell: Boolean)</ID>
<ID>LongParameterList:ArtemisRpcBroker.kt$ArtemisRpcBroker.Companion$(configuration: MutualSslConfiguration, address: NetworkHostAndPort, adminAddress: NetworkHostAndPort, sslOptions: BrokerRpcSslOptions, securityManager: RPCSecurityManager, maxMessageSize: Int, jmxEnabled: Boolean, baseDirectory: Path, shouldStartLocalShell: Boolean)</ID>
<ID>LongParameterList:ArtemisRpcBroker.kt$ArtemisRpcBroker.Companion$(configuration: MutualSslConfiguration, address: NetworkHostAndPort, adminAddress: NetworkHostAndPort, securityManager: RPCSecurityManager, maxMessageSize: Int, journalBufferTimeout: Int?, jmxEnabled: Boolean, baseDirectory: Path, shouldStartLocalShell: Boolean)</ID>
<ID>LongParameterList:ArtemisRpcBroker.kt$ArtemisRpcBroker.Companion$(configuration: MutualSslConfiguration, address: NetworkHostAndPort, adminAddress: NetworkHostAndPort, sslOptions: BrokerRpcSslOptions, securityManager: RPCSecurityManager, maxMessageSize: Int, journalBufferTimeout: Int?, jmxEnabled: Boolean, baseDirectory: Path, shouldStartLocalShell: Boolean)</ID>
<ID>LongParameterList:ArtemisRpcTests.kt$ArtemisRpcTests$(nodeSSlconfig: MutualSslConfiguration, brokerSslOptions: BrokerRpcSslOptions?, useSslForBroker: Boolean, clientSslOptions: ClientRpcSslOptions?, address: NetworkHostAndPort = ports.nextHostAndPort(), adminAddress: NetworkHostAndPort = ports.nextHostAndPort(), baseDirectory: Path = tempFolder.root.toPath() )</ID>
<ID>LongParameterList:AttachmentsClassLoader.kt$AttachmentsClassLoaderBuilder$(attachments: List&lt;Attachment&gt;, params: NetworkParameters, txId: SecureHash, isAttachmentTrusted: (Attachment) -&gt; Boolean, parent: ClassLoader = ClassLoader.getSystemClassLoader(), block: (ClassLoader) -&gt; T)</ID>
<ID>LongParameterList:BFTSmart.kt$BFTSmart.Replica$( states: List&lt;StateRef&gt;, txId: SecureHash, callerName: CordaX500Name, requestSignature: NotarisationRequestSignature, timeWindow: TimeWindow?, references: List&lt;StateRef&gt; = emptyList() )</ID>
@ -655,7 +643,6 @@
<ID>LongParameterList:CertificateRevocationListNodeTests.kt$CertificateRevocationListNodeTests$(port: Int, name: CordaX500Name = ALICE_NAME, crlCheckSoftFail: Boolean, nodeCrlDistPoint: String = "http://${server.hostAndPort}/crl/node.crl", tlsCrlDistPoint: String? = "http://${server.hostAndPort}/crl/empty.crl", maxMessageSize: Int = MAX_MESSAGE_SIZE)</ID>
<ID>LongParameterList:CertificateRevocationListNodeTests.kt$CertificateRevocationListNodeTests.Companion$(clrServer: CrlServer, signatureAlgorithm: String, caCertificate: X509Certificate, caPrivateKey: PrivateKey, endpoint: String, indirect: Boolean, vararg serialNumbers: BigInteger)</ID>
<ID>LongParameterList:CertificateStoreStubs.kt$CertificateStoreStubs.P2P.Companion$(baseDirectory: Path, certificatesDirectoryName: String = DEFAULT_CERTIFICATES_DIRECTORY_NAME, keyStoreFileName: String = KeyStore.DEFAULT_STORE_FILE_NAME, keyStorePassword: String = KeyStore.DEFAULT_STORE_PASSWORD, keyPassword: String = keyStorePassword, trustStoreFileName: String = TrustStore.DEFAULT_STORE_FILE_NAME, trustStorePassword: String = TrustStore.DEFAULT_STORE_PASSWORD)</ID>
<ID>LongParameterList:CertificateStoreStubs.kt$CertificateStoreStubs.P2P.Companion$(certificatesDirectory: Path, keyStoreFileName: String = KeyStore.DEFAULT_STORE_FILE_NAME, keyStorePassword: String = KeyStore.DEFAULT_STORE_PASSWORD, keyPassword: String = keyStorePassword, trustStoreFileName: String = TrustStore.DEFAULT_STORE_FILE_NAME, trustStorePassword: String = TrustStore.DEFAULT_STORE_PASSWORD, trustStoreKeyPassword: String = TrustStore.DEFAULT_KEY_PASSWORD, @Suppress("UNUSED_PARAMETER") useOpenSsl: Boolean = false)</ID>
<ID>LongParameterList:ContractAttachment.kt$ContractAttachment.Companion$(attachment: Attachment, contract: ContractClassName, additionalContracts: Set&lt;ContractClassName&gt; = emptySet(), uploader: String? = null, signerKeys: List&lt;PublicKey&gt; = emptyList(), version: Int = DEFAULT_CORDAPP_VERSION)</ID>
<ID>LongParameterList:ContractFunctions.kt$(expiry: String, notional: BigDecimal, strike: BigDecimal, foreignCurrency: Currency, domesticCurrency: Currency, partyA: Party, partyB: Party)</ID>
<ID>LongParameterList:ContractFunctions.kt$(expiry: String, notional: Long, strike: Double, foreignCurrency: Currency, domesticCurrency: Currency, partyA: Party, partyB: Party)</ID>
@ -925,7 +912,6 @@
<ID>MagicNumber:IrsDemoWebApplication.kt$IrsDemoWebApplication$1000</ID>
<ID>MagicNumber:JarScanningCordappLoader.kt$CordappLoaderTemplate$36</ID>
<ID>MagicNumber:JarScanningCordappLoader.kt$CordappLoaderTemplate$64</ID>
<ID>MagicNumber:JarScanningCordappLoader.kt$JarScanningCordappLoader$1000</ID>
<ID>MagicNumber:JarSignatureCollector.kt$JarSignatureCollector$1024</ID>
<ID>MagicNumber:JarSignatureTestUtils.kt$JarSignatureTestUtils$14</ID>
<ID>MagicNumber:KMSUtils.kt$3650</ID>
@ -1207,7 +1193,6 @@
<ID>MatchingDeclarationName:ConfigException.kt$net.corda.core.cordapp.ConfigException.kt</ID>
<ID>MatchingDeclarationName:ConfigUtilities.kt$net.corda.node.services.config.ConfigUtilities.kt</ID>
<ID>MatchingDeclarationName:ContractsDSL.kt$net.corda.core.contracts.ContractsDSL.kt</ID>
<ID>MatchingDeclarationName:CordaUtils.kt$net.corda.core.internal.CordaUtils.kt</ID>
<ID>MatchingDeclarationName:CurrencyParameterSensitivitySerialiser.kt$net.corda.vega.plugin.customserializers.CurrencyParameterSensitivitySerialiser.kt</ID>
<ID>MatchingDeclarationName:FinanceWorkflowsUtils.kt$net.corda.finance.workflows.utils.FinanceWorkflowsUtils.kt</ID>
<ID>MatchingDeclarationName:FlowStackSnapshot.kt$net.corda.testing.internal.FlowStackSnapshot.kt</ID>
@ -1229,8 +1214,6 @@
<ID>MatchingDeclarationName:Query.kt$net.corda.webserver.api.Query.kt</ID>
<ID>MatchingDeclarationName:ReceiveAllFlowTests.kt$net.corda.coretests.flows.ReceiveAllFlowTests.kt</ID>
<ID>MatchingDeclarationName:ReferenceInputStateTests.kt$net.corda.coretests.transactions.ReferenceInputStateTests.kt</ID>
<ID>MatchingDeclarationName:RigorousMock.kt$net.corda.testing.internal.RigorousMock.kt</ID>
<ID>MatchingDeclarationName:RpcServerCordaFutureSerialiser.kt$net.corda.node.serialization.amqp.RpcServerCordaFutureSerialiser.kt</ID>
<ID>MatchingDeclarationName:SSLHelper.kt$net.corda.nodeapi.internal.protonwrapper.netty.SSLHelper.kt</ID>
<ID>MatchingDeclarationName:SampleData.kt$net.corda.deterministic.verifier.SampleData.kt</ID>
<ID>MatchingDeclarationName:SerializationHelper.kt$net.corda.networkbuilder.serialization.SerializationHelper.kt</ID>
@ -1285,7 +1268,6 @@
<ID>NestedBlockDepth:RPCClientProxyHandler.kt$RPCClientProxyHandler$// The handler for Artemis messages. private fun artemisMessageHandler(message: ClientMessage)</ID>
<ID>NestedBlockDepth:ShutdownManager.kt$ShutdownManager$fun shutdown()</ID>
<ID>NestedBlockDepth:SpringDriver.kt$SpringBootDriverDSL$private fun queryWebserver(handle: NodeHandle, process: Process, checkUrl: String): WebserverHandle</ID>
<ID>NestedBlockDepth:StartedFlowTransition.kt$StartedFlowTransition$private fun TransitionBuilder.sendToSessionsTransition(sourceSessionIdToMessage: Map&lt;SessionId, SerializedBytes&lt;Any&gt;&gt;)</ID>
<ID>NestedBlockDepth:StatusTransitions.kt$StatusTransitions$ fun verify(tx: LedgerTransaction)</ID>
<ID>NestedBlockDepth:ThrowableSerializer.kt$ThrowableSerializer$override fun fromProxy(proxy: ThrowableProxy): Throwable</ID>
<ID>NestedBlockDepth:TransactionVerifierServiceInternal.kt$Verifier$ private fun verifyConstraintsValidity(contractAttachmentsByContract: Map&lt;ContractClassName, ContractAttachment&gt;)</ID>
@ -1316,7 +1298,6 @@
<ID>SpreadOperator:ConfigUtilities.kt$(*pairs)</ID>
<ID>SpreadOperator:Configuration.kt$Configuration.Validation.Error$(*(containingPath.toList() + this.containingPath).toTypedArray())</ID>
<ID>SpreadOperator:ContractJarTestUtils.kt$ContractJarTestUtils$(jarName, *contractNames.map{ "${it.replace(".", "/")}.class" }.toTypedArray())</ID>
<ID>SpreadOperator:CordaCliWrapper.kt$(RunLast().useOut(System.out).useAnsi(defaultAnsiMode), DefaultExceptionHandler&lt;List&lt;Any&gt;&gt;().useErr(System.err).useAnsi(defaultAnsiMode).andExit(ExitCodes.FAILURE), *args)</ID>
<ID>SpreadOperator:CordaRPCOpsImpl.kt$CordaRPCOpsImpl$(logicType, context(), *args)</ID>
<ID>SpreadOperator:CordaX500Name.kt$CordaX500Name.Companion$(*Locale.getISOCountries(), unspecifiedCountry)</ID>
<ID>SpreadOperator:CustomCordapp.kt$CustomCordapp$(*classes.map { it.name }.toTypedArray())</ID>
@ -1326,7 +1307,6 @@
<ID>SpreadOperator:DockerInstantiator.kt$DockerInstantiator$(*it.toTypedArray())</ID>
<ID>SpreadOperator:DummyContract.kt$DummyContract.Companion$( /* INPUTS */ *priors.toTypedArray(), /* COMMAND */ Command(cmd, priorState.owner.owningKey), /* OUTPUT */ StateAndContract(state, PROGRAM_ID) )</ID>
<ID>SpreadOperator:DummyContract.kt$DummyContract.Companion$(*items)</ID>
<ID>SpreadOperator:DummyContractV2.kt$DummyContractV2.Companion$( /* INPUTS */ *priors.toTypedArray(), /* COMMAND */ Command(cmd, priorState.owners.map { it.owningKey }), /* OUTPUT */ StateAndContract(state, DummyContractV2.PROGRAM_ID) )</ID>
<ID>SpreadOperator:ExceptionsErrorCodeFunctions.kt$(*fields)</ID>
<ID>SpreadOperator:ExceptionsErrorCodeFunctions.kt$(*fields, cause.staticLocationBasedHash(hashedFields, visited + cause))</ID>
<ID>SpreadOperator:ExceptionsErrorCodeFunctions.kt$(*hashedFields.invoke(this))</ID>
@ -1483,6 +1463,7 @@
<ID>TooGenericExceptionCaught:AbstractNode.kt$ex: Exception</ID>
<ID>TooGenericExceptionCaught:AbstractNodeTests.kt$ColdJVM.Companion$t: Throwable</ID>
<ID>TooGenericExceptionCaught:Amount.kt$Amount.Companion$e: Exception</ID>
<ID>TooGenericExceptionCaught:ArtemisRpcBroker.kt$ArtemisRpcBroker$th: Throwable</ID>
<ID>TooGenericExceptionCaught:AttachmentDemo.kt$e: Exception</ID>
<ID>TooGenericExceptionCaught:AttachmentLoadingTests.kt$AttachmentLoadingTests.ConsumeAndBroadcastResponderFlow$e: Exception</ID>
<ID>TooGenericExceptionCaught:AttachmentVersionNumberMigration.kt$AttachmentVersionNumberMigration$e: Exception</ID>
@ -1491,7 +1472,6 @@
<ID>TooGenericExceptionCaught:BankOfCordaWebApi.kt$BankOfCordaWebApi$e: Exception</ID>
<ID>TooGenericExceptionCaught:BlobInspector.kt$BlobInspector$e: Exception</ID>
<ID>TooGenericExceptionCaught:BootstrapperView.kt$BootstrapperView$e: Exception</ID>
<ID>TooGenericExceptionCaught:BridgeControlListener.kt$BridgeControlListener$ex: Exception</ID>
<ID>TooGenericExceptionCaught:BrokerJaasLoginModule.kt$BrokerJaasLoginModule$e: Exception</ID>
<ID>TooGenericExceptionCaught:CertRole.kt$CertRole.Companion$ex: ArrayIndexOutOfBoundsException</ID>
<ID>TooGenericExceptionCaught:CheckpointAgent.kt$CheckpointAgent.Companion$e: Exception</ID>
@ -1506,9 +1486,6 @@
<ID>TooGenericExceptionCaught:ContractUpgradeTransactions.kt$ContractUpgradeWireTransaction$e: Exception</ID>
<ID>TooGenericExceptionCaught:CordaAuthenticationPlugin.kt$CordaAuthenticationPlugin$e: Exception</ID>
<ID>TooGenericExceptionCaught:CordaClassResolver.kt$LoggingWhitelist.Companion$ioEx: Exception</ID>
<ID>TooGenericExceptionCaught:CordaFutureImpl.kt$CordaFutureImpl$e: Exception</ID>
<ID>TooGenericExceptionCaught:CordaFutureImpl.kt$ValueOrException$e: Exception</ID>
<ID>TooGenericExceptionCaught:CordaFutureImpl.kt$e: Exception</ID>
<ID>TooGenericExceptionCaught:CordaPersistence.kt$CordaPersistence$e: Exception</ID>
<ID>TooGenericExceptionCaught:CordaRPCClientTest.kt$CordaRPCClientTest$e: Exception</ID>
<ID>TooGenericExceptionCaught:CordaRPCOpsImpl.kt$CordaRPCOpsImpl$e: Exception</ID>
@ -1522,6 +1499,7 @@
<ID>TooGenericExceptionCaught:DeserializeSimpleTypesTests.kt$DeserializeSimpleTypesTests$e: Exception</ID>
<ID>TooGenericExceptionCaught:DockerInstantiator.kt$DockerInstantiator$e: Exception</ID>
<ID>TooGenericExceptionCaught:DriverDSLImpl.kt$DriverDSLImpl$e: Exception</ID>
<ID>TooGenericExceptionCaught:DriverDSLImpl.kt$DriverDSLImpl.Companion$th: Throwable</ID>
<ID>TooGenericExceptionCaught:DriverDSLImpl.kt$exception: Throwable</ID>
<ID>TooGenericExceptionCaught:DriverTests.kt$DriverTests$e: Exception</ID>
<ID>TooGenericExceptionCaught:ErrorCodeLoggingTests.kt$e: Exception</ID>
@ -1616,7 +1594,7 @@
<ID>TooGenericExceptionCaught:ReconnectingCordaRPCOps.kt$ReconnectingCordaRPCOps.ReconnectingRPCConnection$ex: Exception</ID>
<ID>TooGenericExceptionCaught:ReconnectingObservable.kt$ReconnectingObservable.ReconnectingSubscriber$e: Exception</ID>
<ID>TooGenericExceptionCaught:RpcServerObservableSerializerTests.kt$RpcServerObservableSerializerTests$e: Exception</ID>
<ID>TooGenericExceptionCaught:SSLHelper.kt$LoggingTrustManagerWrapper$ex: Exception</ID>
<ID>TooGenericExceptionCaught:SSLHelper.kt$ex: Exception</ID>
<ID>TooGenericExceptionCaught:ScheduledFlowIntegrationTests.kt$ScheduledFlowIntegrationTests$ex: Exception</ID>
<ID>TooGenericExceptionCaught:SerializationOutputTests.kt$SerializationOutputTests$t: Throwable</ID>
<ID>TooGenericExceptionCaught:ShutdownManager.kt$ShutdownManager$t: Throwable</ID>
@ -1644,7 +1622,6 @@
<ID>TooGenericExceptionCaught:ValidatingNotaryFlow.kt$ValidatingNotaryFlow$e: Exception</ID>
<ID>TooGenericExceptionCaught:VaultStateMigration.kt$VaultStateIterator$e: Exception</ID>
<ID>TooGenericExceptionCaught:VaultStateMigration.kt$VaultStateMigration$e: Exception</ID>
<ID>TooGenericExceptionCaught:VersionedParsingExampleTest.kt$VersionedParsingExampleTest.RpcSettingsSpec$e: Exception</ID>
<ID>TooGenericExceptionCaught:WebServer.kt$WebServer$e: Exception</ID>
<ID>TooGenericExceptionCaught:WebServer.kt$e: Exception</ID>
<ID>TooGenericExceptionCaught:WebServer.kt$ex: Exception</ID>
@ -1699,7 +1676,6 @@
<ID>TooManyFunctions:CryptoUtils.kt$net.corda.core.crypto.CryptoUtils.kt</ID>
<ID>TooManyFunctions:Currencies.kt$net.corda.finance.Currencies.kt</ID>
<ID>TooManyFunctions:Driver.kt$DriverParameters</ID>
<ID>TooManyFunctions:DriverDSLImpl.kt$DriverDSLImpl : InternalDriverDSL</ID>
<ID>TooManyFunctions:EncodingUtils.kt$net.corda.core.utilities.EncodingUtils.kt</ID>
<ID>TooManyFunctions:FlowLogic.kt$FlowLogic&lt;out T&gt;</ID>
<ID>TooManyFunctions:FlowStateMachineImpl.kt$FlowStateMachineImpl&lt;R&gt; : FiberFlowStateMachineFlowFiber</ID>
@ -1761,6 +1737,18 @@
<ID>TopLevelPropertyNaming:SerializationEnvironment.kt$val _inheritableContextSerializationEnv = InheritableThreadLocalToggleField&lt;SerializationEnvironment&gt;("inheritableContextSerializationEnv") { stack -&gt; stack.fold(false) { isAGlobalThreadBeingCreated, e -&gt; isAGlobalThreadBeingCreated || (e.className == "io.netty.util.concurrent.GlobalEventExecutor" &amp;&amp; e.methodName == "startThread") || (e.className == "java.util.concurrent.ForkJoinPool\$DefaultForkJoinWorkerThreadFactory" &amp;&amp; e.methodName == "newThread") } }</ID>
<ID>TopLevelPropertyNaming:SerializationEnvironment.kt$val _rpcClientSerializationEnv = SimpleToggleField&lt;SerializationEnvironment&gt;("rpcClientSerializationEnv")</ID>
<ID>TopLevelPropertyNaming:SerializationFormat.kt$const val encodingNotPermittedFormat = "Encoding not permitted: %s"</ID>
<ID>UnusedImports:Amount.kt$import net.corda.core.crypto.CompositeKey</ID>
<ID>UnusedImports:Amount.kt$import net.corda.core.identity.Party</ID>
<ID>UnusedImports:DummyLinearStateSchemaV1.kt$import net.corda.core.contracts.ContractState</ID>
<ID>UnusedImports:FlowsExecutionModeRpcTest.kt$import net.corda.core.internal.packageName</ID>
<ID>UnusedImports:FlowsExecutionModeRpcTest.kt$import net.corda.finance.schemas.CashSchemaV1</ID>
<ID>UnusedImports:InternalTestUtils.kt$import java.nio.file.Files</ID>
<ID>UnusedImports:InternalTestUtils.kt$import net.corda.nodeapi.internal.loadDevCaTrustStore</ID>
<ID>UnusedImports:NetworkMap.kt$import net.corda.core.node.NodeInfo</ID>
<ID>UnusedImports:NodeParameters.kt$import net.corda.core.identity.Party</ID>
<ID>UnusedImports:SerializerFactory.kt$import java.io.NotSerializableException</ID>
<ID>UnusedImports:TransformTypes.kt$import net.corda.core.serialization.CordaSerializationTransformEnumDefaults</ID>
<ID>UnusedImports:VaultSchema.kt$import net.corda.core.contracts.ContractState</ID>
<ID>VariableNaming:AttachmentsClassLoaderSerializationTests.kt$AttachmentsClassLoaderSerializationTests$val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party</ID>
<ID>VariableNaming:AttachmentsClassLoaderSerializationTests.kt$AttachmentsClassLoaderSerializationTests$val MEGA_CORP = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")).party</ID>
<ID>VariableNaming:BootstrapperView.kt$BootstrapperView$val YAML_MAPPER = Constants.getContextMapper()</ID>
@ -1885,8 +1873,6 @@
<ID>VariableNaming:VaultQueryTests.kt$VaultQueryTestsBase$// Beware: do not use `MyContractClass::class.qualifiedName` as this returns a fully qualified name using "dot" notation for enclosed class val MYCONTRACT_ID = "net.corda.node.services.vault.VaultQueryTestsBase\$MyContractClass"</ID>
<ID>VariableNaming:ZeroCouponBond.kt$ZeroCouponBond$val TEST_TX_TIME_1: Instant get() = Instant.parse("2017-09-02T12:00:00.00Z")</ID>
<ID>WildcardImport:AMQPClient.kt$import io.netty.channel.*</ID>
<ID>WildcardImport:AMQPClientSerializationScheme.kt$import net.corda.serialization.internal.*</ID>
<ID>WildcardImport:AMQPClientSerializationScheme.kt$import net.corda.serialization.internal.amqp.*</ID>
<ID>WildcardImport:AMQPRemoteTypeModel.kt$import net.corda.serialization.internal.model.*</ID>
<ID>WildcardImport:AMQPSerializationScheme.kt$import net.corda.core.serialization.*</ID>
<ID>WildcardImport:AMQPServerSerializationScheme.kt$import net.corda.serialization.internal.amqp.*</ID>
@ -2023,9 +2009,6 @@
<ID>WildcardImport:CordaModule.kt$import net.corda.core.crypto.*</ID>
<ID>WildcardImport:CordaModule.kt$import net.corda.core.identity.*</ID>
<ID>WildcardImport:CordaModule.kt$import net.corda.core.transactions.*</ID>
<ID>WildcardImport:CordaRPCClientTest.kt$import net.corda.core.context.*</ID>
<ID>WildcardImport:CordaRPCClientTest.kt$import net.corda.core.messaging.*</ID>
<ID>WildcardImport:CordaRPCClientTest.kt$import net.corda.testing.core.*</ID>
<ID>WildcardImport:CordaRPCOps.kt$import net.corda.core.node.services.vault.*</ID>
<ID>WildcardImport:CordaRPCOpsImplTest.kt$import net.corda.core.messaging.*</ID>
<ID>WildcardImport:CordaRPCOpsImplTest.kt$import org.assertj.core.api.Assertions.*</ID>
@ -2056,8 +2039,6 @@
<ID>WildcardImport:DBTransactionStorage.kt$import net.corda.core.serialization.*</ID>
<ID>WildcardImport:DBTransactionStorage.kt$import net.corda.nodeapi.internal.persistence.*</ID>
<ID>WildcardImport:DBTransactionStorageTests.kt$import net.corda.testing.core.*</ID>
<ID>WildcardImport:DefaultKryoCustomizer.kt$import de.javakaffee.kryoserializers.guava.*</ID>
<ID>WildcardImport:DefaultKryoCustomizer.kt$import net.corda.core.transactions.*</ID>
<ID>WildcardImport:DeleteForDJVM.kt$import kotlin.annotation.AnnotationTarget.*</ID>
<ID>WildcardImport:DemoBench.kt$import tornadofx.*</ID>
<ID>WildcardImport:DemoBenchNodeInfoFilesCopier.kt$import tornadofx.*</ID>
@ -2121,8 +2102,6 @@
<ID>WildcardImport:FlowStateMachineImpl.kt$import net.corda.core.flows.*</ID>
<ID>WildcardImport:FlowStateMachineImpl.kt$import net.corda.core.internal.*</ID>
<ID>WildcardImport:FlowsDrainingModeContentionTest.kt$import net.corda.core.flows.*</ID>
<ID>WildcardImport:FxTransactionBuildTutorial.kt$import net.corda.core.contracts.*</ID>
<ID>WildcardImport:FxTransactionBuildTutorial.kt$import net.corda.core.flows.*</ID>
<ID>WildcardImport:FxTransactionBuildTutorialTest.kt$import net.corda.finance.*</ID>
<ID>WildcardImport:GenericsTests.kt$import net.corda.serialization.internal.amqp.testutils.*</ID>
<ID>WildcardImport:Gui.kt$import tornadofx.*</ID>
@ -2168,10 +2147,7 @@
<ID>WildcardImport:InternalMockNetwork.kt$import net.corda.core.internal.*</ID>
<ID>WildcardImport:InternalMockNetwork.kt$import net.corda.node.services.config.*</ID>
<ID>WildcardImport:InternalMockNetwork.kt$import net.corda.testing.node.*</ID>
<ID>WildcardImport:InternalSerializationTestHelpers.kt$import net.corda.serialization.internal.*</ID>
<ID>WildcardImport:InternalTestUtils.kt$import net.corda.core.contracts.*</ID>
<ID>WildcardImport:InternalUtils.kt$import java.security.cert.*</ID>
<ID>WildcardImport:InternalUtils.kt$import net.corda.core.crypto.*</ID>
<ID>WildcardImport:IssuerModel.kt$import tornadofx.*</ID>
<ID>WildcardImport:JVMConfig.kt$import tornadofx.*</ID>
<ID>WildcardImport:JacksonSupport.kt$import com.fasterxml.jackson.core.*</ID>
@ -2194,8 +2170,6 @@
<ID>WildcardImport:KotlinIntegrationTestingTutorial.kt$import net.corda.testing.core.*</ID>
<ID>WildcardImport:Kryo.kt$import com.esotericsoftware.kryo.*</ID>
<ID>WildcardImport:Kryo.kt$import net.corda.core.transactions.*</ID>
<ID>WildcardImport:KryoCheckpointSerializer.kt$import net.corda.core.serialization.*</ID>
<ID>WildcardImport:KryoCheckpointSerializer.kt$import net.corda.serialization.internal.*</ID>
<ID>WildcardImport:KryoStreamsTest.kt$import java.io.*</ID>
<ID>WildcardImport:KryoTests.kt$import kotlin.test.*</ID>
<ID>WildcardImport:KryoTests.kt$import net.corda.core.crypto.*</ID>
@ -2249,19 +2223,12 @@
<ID>WildcardImport:NetworkBootstrapper.kt$import net.corda.nodeapi.internal.*</ID>
<ID>WildcardImport:NetworkBootstrapperRunnerTests.kt$import org.junit.*</ID>
<ID>WildcardImport:NetworkBootstrapperTest.kt$import net.corda.core.internal.*</ID>
<ID>WildcardImport:NetworkBootstrapperTest.kt$import net.corda.testing.core.*</ID>
<ID>WildcardImport:NetworkBuilder.kt$import net.corda.networkbuilder.nodes.*</ID>
<ID>WildcardImport:NetworkIdentityModel.kt$import net.corda.client.jfx.utils.*</ID>
<ID>WildcardImport:NetworkMapServer.kt$import javax.ws.rs.*</ID>
<ID>WildcardImport:NetworkMapTest.kt$import net.corda.core.internal.*</ID>
<ID>WildcardImport:NetworkMapTest.kt$import net.corda.testing.core.*</ID>
<ID>WildcardImport:NetworkMapTest.kt$import net.corda.testing.node.internal.*</ID>
<ID>WildcardImport:NetworkMapUpdater.kt$import net.corda.core.internal.*</ID>
<ID>WildcardImport:NetworkMapUpdater.kt$import net.corda.nodeapi.internal.network.*</ID>
<ID>WildcardImport:NetworkMapUpdaterTest.kt$import com.nhaarman.mockito_kotlin.*</ID>
<ID>WildcardImport:NetworkMapUpdaterTest.kt$import net.corda.core.internal.*</ID>
<ID>WildcardImport:NetworkMapUpdaterTest.kt$import net.corda.testing.core.*</ID>
<ID>WildcardImport:NetworkMapUpdaterTest.kt$import org.junit.*</ID>
<ID>WildcardImport:NetworkParametersReader.kt$import net.corda.core.internal.*</ID>
<ID>WildcardImport:NetworkParametersReaderTest.kt$import net.corda.core.internal.*</ID>
<ID>WildcardImport:NetworkParametersReaderTest.kt$import net.corda.nodeapi.internal.network.*</ID>
@ -2362,7 +2329,6 @@
<ID>WildcardImport:PathUtils.kt$import java.io.*</ID>
<ID>WildcardImport:PathUtils.kt$import java.nio.file.*</ID>
<ID>WildcardImport:PersistentIdentityMigrationNewTableTest.kt$import net.corda.testing.core.*</ID>
<ID>WildcardImport:PersistentIdentityServiceTests.kt$import net.corda.testing.core.*</ID>
<ID>WildcardImport:PersistentNetworkMapCacheTest.kt$import net.corda.testing.core.*</ID>
<ID>WildcardImport:PersistentStateServiceTests.kt$import net.corda.core.contracts.*</ID>
<ID>WildcardImport:Portfolio.kt$import net.corda.core.contracts.*</ID>
@ -2387,8 +2353,6 @@
<ID>WildcardImport:QueryCriteriaUtils.kt$import net.corda.core.node.services.vault.LikenessOperator.*</ID>
<ID>WildcardImport:RPCMultipleInterfacesTests.kt$import org.junit.Assert.*</ID>
<ID>WildcardImport:RPCSecurityManagerImpl.kt$import org.apache.shiro.authc.*</ID>
<ID>WildcardImport:RPCServer.kt$import net.corda.core.utilities.*</ID>
<ID>WildcardImport:RPCServer.kt$import org.apache.activemq.artemis.api.core.client.*</ID>
<ID>WildcardImport:ReceiveFinalityFlowTest.kt$import net.corda.node.services.statemachine.StaffedFlowHospital.*</ID>
<ID>WildcardImport:ReceiveFinalityFlowTest.kt$import net.corda.testing.node.internal.*</ID>
<ID>WildcardImport:ReceiveTransactionFlow.kt$import net.corda.core.contracts.*</ID>
@ -2427,7 +2391,6 @@
<ID>WildcardImport:SearchField.kt$import tornadofx.*</ID>
<ID>WildcardImport:SecureHashTest.kt$import org.junit.Assert.*</ID>
<ID>WildcardImport:SendTransactionFlow.kt$import net.corda.core.internal.*</ID>
<ID>WildcardImport:SerializationEnvironmentRule.kt$import net.corda.testing.internal.*</ID>
<ID>WildcardImport:SerializationHelper.kt$import java.lang.reflect.*</ID>
<ID>WildcardImport:SerializationHelper.kt$import net.corda.core.serialization.*</ID>
<ID>WildcardImport:SerializationOutputTests.kt$import java.time.*</ID>
@ -2572,7 +2535,6 @@
<ID>WildcardImport:VaultWithCashTest.kt$import net.corda.testing.core.*</ID>
<ID>WildcardImport:VaultWithCashTest.kt$import net.corda.testing.internal.vault.*</ID>
<ID>WildcardImport:VerifyTransactionTest.kt$import net.corda.finance.contracts.asset.Cash.Commands.*</ID>
<ID>WildcardImport:VersionedParsingExampleTest.kt$import net.corda.common.configuration.parsing.internal.*</ID>
<ID>WildcardImport:WebServerController.kt$import tornadofx.*</ID>
<ID>WildcardImport:WhitelistBasedTypeModelConfiguration.kt$import org.apache.qpid.proton.amqp.*</ID>
<ID>WildcardImport:WhitelistGenerator.kt$import net.corda.core.internal.*</ID>
@ -2581,8 +2543,6 @@
<ID>WildcardImport:WireTransaction.kt$import net.corda.core.internal.*</ID>
<ID>WildcardImport:WithFinality.kt$import net.corda.core.flows.*</ID>
<ID>WildcardImport:WithMockNet.kt$import com.natpryce.hamkrest.*</ID>
<ID>WildcardImport:WorkflowTransactionBuildTutorial.kt$import net.corda.core.contracts.*</ID>
<ID>WildcardImport:WorkflowTransactionBuildTutorial.kt$import net.corda.core.flows.*</ID>
<ID>WildcardImport:X509CRLSerializer.kt$import net.corda.serialization.internal.amqp.*</ID>
<ID>WildcardImport:X509CertificateSerializer.kt$import net.corda.serialization.internal.amqp.*</ID>
<ID>WildcardImport:X509EdDSAEngine.kt$import java.security.*</ID>

View File

@ -193,4 +193,6 @@ style:
WildcardImport:
active: true
excludes: "**/buildSrc/**"
excludeImports: 'java.util.*,kotlinx.android.synthetic.*'
excludeImports: 'java.util.*,kotlinx.android.synthetic.*'
UnusedImports:
active: true

View File

@ -0,0 +1,62 @@
FROM azul/zulu-openjdk:8u192
## Add packages, clean cache, create dirs, create corda user and change ownership
RUN apt-get update && \
apt-get -y upgrade && \
apt-get -y install bash curl unzip netstat lsof telnet netcat && \
rm -rf /var/lib/apt/lists/* && \
mkdir -p /opt/corda/cordapps && \
mkdir -p /opt/corda/persistence && \
mkdir -p /opt/corda/certificates && \
mkdir -p /opt/corda/drivers && \
mkdir -p /opt/corda/logs && \
mkdir -p /opt/corda/bin && \
mkdir -p /opt/corda/additional-node-infos && \
mkdir -p /etc/corda && \
addgroup corda && \
useradd corda -g corda -m -d /opt/corda && \
chown -R corda:corda /opt/corda && \
chown -R corda:corda /etc/corda
ENV CORDAPPS_FOLDER="/opt/corda/cordapps" \
PERSISTENCE_FOLDER="/opt/corda/persistence" \
CERTIFICATES_FOLDER="/opt/corda/certificates" \
DRIVERS_FOLDER="/opt/corda/drivers" \
CONFIG_FOLDER="/etc/corda" \
MY_P2P_PORT=10200 \
MY_RPC_PORT=10201 \
MY_RPC_ADMIN_PORT=10202 \
PATH=$PATH:/opt/corda/bin \
JVM_ARGS="-XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap " \
CORDA_ARGS=""
##CORDAPPS FOLDER
VOLUME ["/opt/corda/cordapps"]
##PERSISTENCE FOLDER
VOLUME ["/opt/corda/persistence"]
##CERTS FOLDER
VOLUME ["/opt/corda/certificates"]
##OPTIONAL JDBC DRIVERS FOLDER
VOLUME ["/opt/corda/drivers"]
##LOG FOLDER
VOLUME ["/opt/corda/logs"]
##ADDITIONAL NODE INFOS FOLDER
VOLUME ["/opt/corda/additional-node-infos"]
##CONFIG LOCATION
VOLUME ["/etc/corda"]
##CORDA JAR
COPY --chown=corda:corda corda.jar /opt/corda/bin/corda.jar
##CONFIG MANIPULATOR JAR
COPY --chown=corda:corda config-exporter.jar /opt/corda/config-exporter.jar
##CONFIG GENERATOR SHELL SCRIPT
COPY --chown=corda:corda generate-config.sh /opt/corda/bin/config-generator
##CORDA RUN SCRIPT
COPY --chown=corda:corda run-corda.sh /opt/corda/bin/run-corda
##BASE CONFIG FOR GENERATOR
COPY --chown=corda:corda starting-node.conf /opt/corda/starting-node.conf
USER "corda"
EXPOSE ${MY_P2P_PORT} ${MY_RPC_PORT} ${MY_RPC_ADMIN_PORT}
WORKDIR /opt/corda
CMD ["run-corda"]

View File

@ -0,0 +1,66 @@
FROM amazonlinux:2
## Add packages, clean cache, create dirs, create corda user and change ownership
RUN amazon-linux-extras enable corretto8 && \
yum -y install java-1.8.0-amazon-corretto-devel && \
yum -y install bash && \
yum -y install curl && \
yum -y install unzip && \
yum -y install lsof telnet net-tools nmap-ncat && \
yum clean all && \
rm -rf /var/cache/yum && \
mkdir -p /opt/corda/cordapps && \
mkdir -p /opt/corda/persistence && \
mkdir -p /opt/corda/certificates && \
mkdir -p /opt/corda/drivers && \
mkdir -p /opt/corda/logs && \
mkdir -p /opt/corda/bin && \
mkdir -p /opt/corda/additional-node-infos && \
mkdir -p /etc/corda && \
groupadd corda && \
useradd corda -g corda -m -d /opt/corda && \
chown -R corda:corda /opt/corda && \
chown -R corda:corda /etc/corda
ENV CORDAPPS_FOLDER="/opt/corda/cordapps" \
PERSISTENCE_FOLDER="/opt/corda/persistence" \
CERTIFICATES_FOLDER="/opt/corda/certificates" \
DRIVERS_FOLDER="/opt/corda/drivers" \
CONFIG_FOLDER="/etc/corda" \
MY_P2P_PORT=10200 \
MY_RPC_PORT=10201 \
MY_RPC_ADMIN_PORT=10202 \
PATH=$PATH:/opt/corda/bin \
JVM_ARGS="-XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap " \
CORDA_ARGS=""
##CORDAPPS FOLDER
VOLUME ["/opt/corda/cordapps"]
##PERSISTENCE FOLDER
VOLUME ["/opt/corda/persistence"]
##CERTS FOLDER
VOLUME ["/opt/corda/certificates"]
##OPTIONAL JDBC DRIVERS FOLDER
VOLUME ["/opt/corda/drivers"]
##LOG FOLDER
VOLUME ["/opt/corda/logs"]
##ADDITIONAL NODE INFOS FOLDER
VOLUME ["/opt/corda/additional-node-infos"]
##CONFIG LOCATION
VOLUME ["/etc/corda"]
##CORDA JAR
COPY --chown=corda:corda corda.jar /opt/corda/bin/corda.jar
##CONFIG MANIPULATOR JAR
COPY --chown=corda:corda config-exporter.jar /opt/corda/config-exporter.jar
##CONFIG GENERATOR SHELL SCRIPT
COPY --chown=corda:corda generate-config.sh /opt/corda/bin/config-generator
##CORDA RUN SCRIPT
COPY --chown=corda:corda run-corda.sh /opt/corda/bin/run-corda
##BASE CONFIG FOR GENERATOR
COPY --chown=corda:corda starting-node.conf /opt/corda/starting-node.conf
USER "corda"
EXPOSE ${MY_P2P_PORT} ${MY_RPC_PORT} ${MY_RPC_ADMIN_PORT}
WORKDIR /opt/corda
CMD ["run-corda"]

View File

@ -11,7 +11,6 @@ import net.corda.common.validation.internal.Validated
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.parseAsNodeConfiguration
import net.corda.nodeapi.internal.config.toConfig
import net.corda.nodeapi.internal.config.toConfigValue
import java.io.File
class ConfigExporter {

View File

@ -1,197 +0,0 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
HTMLSPHINXOPTS = $(ALLSPHINXOPTS) -t htmlmode
PDFSPHINXOPTS = $(ALLSPHINXOPTS) -t pdfmode
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " applehelp to make an Apple Help Book"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " coverage to run coverage check of the documentation (if enabled)"
clean:
rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(HTMLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(HTMLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(HTMLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(HTMLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Playground.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Playground.qhc"
applehelp:
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
@echo
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
@echo "N.B. You won't be able to view it unless you put it in" \
"~/Library/Documentation/Help or install it in your application" \
"bundle."
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/Playground"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Playground"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(PDFSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(PDFSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
latexpdfja:
$(SPHINXBUILD) -b latex $(PDFSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
coverage:
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
@echo "Testing of coverage in the sources finished, look at the " \
"results in $(BUILDDIR)/coverage/python.txt."
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
pdf:
$(SPHINXBUILD) -b pdf $(PDFSPHINXOPTS) $(BUILDDIR)/pdf

View File

@ -1,41 +1,23 @@
# Corda Documentation Build
# Docs
This Readme describes how to build the Corda documentation for the current version. The output html files will be written to the `corda\docs\build\html` directory.
## Released documentation
## Prerequisites / First time build
All released Corda documentation has now been moved to a standalone public documentation repository where the doc source can be found:
Before you begin, you need to:
1. Install Docker.
1. Ensure that Docker is running.
1. Select **Expose daemon on tcp://localhost:2375 without TLS** in the Docker Settings (which you can open from the **System Tray** by right-clicking the **Docker symbol** and then selecting **Settings**)
[corda/corda-docs](https://github.com/corda/corda-docs)
## Build process
1. Open a cmd dialogue.
1. Navigate to the root location (this is the `\corda` directory)
1. Run the documentation build (`gradlew makeDocs` or `./gradlew makeDocs`)
See the [readme](https://github.com/corda/corda-docs/blob/master/README.md) and [usage docs](https://github.com/corda/corda-docs/tree/master/usage-docs) pages for instructions on how to use the new repo and build the docs locally.
**Windows users:** *If this task fails because Docker can't find make-docsite.sh, go to Settings > Shared Drives in the Docker system tray
agent, make sure the relevant drive is shared, and click 'Reset credentials'.*
You can contribute to the docs source as before via fork & PR. We now use `markdown` to write/edit (instead of `rst`) and `Hugo` to build (instead of `Sphinx`).
# RST style guide
The published documentation is available at https://docs.corda.net.
The Corda documentation is described using the ReStructured Text (RST) markup language. For details of the syntax, see [this](http://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html).
## Documentation for future releases
# Version placeholders
R3's technical writing team, R3 engineering, and other R3 teams use a separate, private docs repo for working on draft documentation content targeting future releases:
We currently support the following placeholders; they get substituted with the correct value at build time:
[corda/corda-docs-develop](https://github.com/corda/corda-docs-develop)
```groovy
"|corda_version|"
"|corda_version_lower|"
"|java_version|"
"|kotlin_version|"
"|gradle_plugins_version|"
"|quasar_version|"
```
These docs are published as part of each quarterly release of Corda. At that point their doc source becomes available and open for contributions in the [public docs repo](https://github.com/corda/corda-docs).
If you put one of these in an rst file anywhere (including in a code tag), it will be substituted with the value from `constants.properties`
(which is in the root of the project) at build time. `corda_version_lower` returns the current Corda version in lowercase which is useful
for case sensitive artifacts such as docker images.
The code for this can be found near the top of the conf.py file in the `docs/source` directory.
The new documentation process is described in the technical writing team's space on [R3's internal confluence wiki](https://r3-cev.atlassian.net/wiki/spaces/EN/pages/1701249087/Technical+Writing).

View File

@ -1,80 +0,0 @@
import org.apache.tools.ant.taskdefs.condition.Os
import java.nio.file.Files
apply plugin: 'org.jetbrains.dokka'
apply plugin: 'kotlin'
dependencies {
compile rootProject
}
def internalPackagePrefixes(sourceDirs) {
def prefixes = []
// Kotlin allows packages to deviate from the directory structure, but let's assume they don't:
sourceDirs.collect { sourceDir ->
sourceDir.traverse(type: groovy.io.FileType.DIRECTORIES) {
if (it.name == 'internal') {
prefixes.add sourceDir.toPath().relativize(it.toPath()).toString().replace(File.separator, '.')
}
}
}
prefixes
}
ext {
// TODO: Add '../client/jfx/src/main/kotlin' and '../client/mock/src/main/kotlin' if we decide to make them into public API
dokkaSourceDirs = files('../core/src/main/kotlin', '../client/rpc/src/main/kotlin', '../finance/workflows/src/main/kotlin', '../finance/contracts/src/main/kotlin', '../client/jackson/src/main/kotlin',
'../testing/test-utils/src/main/kotlin', '../testing/node-driver/src/main/kotlin')
internalPackagePrefixes = internalPackagePrefixes(dokkaSourceDirs)
}
dokka {
outputDirectory = file("${rootProject.rootDir}/docs/build/html/api/kotlin")
}
task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
outputFormat = "javadoc"
outputDirectory = file("${rootProject.rootDir}/docs/build/html/api/javadoc")
}
[dokka, dokkaJavadoc].collect {
it.configure {
moduleName = 'corda'
processConfigurations = ['compile']
sourceDirs = dokkaSourceDirs
includes = ['packages.md']
jdkVersion = 8
externalDocumentationLink {
url = new URL("http://fasterxml.github.io/jackson-core/javadoc/2.9/")
}
externalDocumentationLink {
url = new URL("https://docs.oracle.com/javafx/2/api/")
}
externalDocumentationLink {
url = new URL("http://www.bouncycastle.org/docs/docs1.5on/")
}
internalPackagePrefixes.collect { packagePrefix ->
packageOptions {
prefix = packagePrefix
suppress = true
}
}
}
}
task buildDocs(dependsOn: ['apidocs', 'makeDocs'])
task apidocs(dependsOn: ['dokka', 'dokkaJavadoc'])
task makeDocs(type: Exec) {
// 2 volumes are mounted:
// - the docs project to /opt/docs_builder, where docs building is executed
// - the rest of the projects in /opt, so that code references to other projects are valid
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
commandLine "docker", "run", "--rm", "-v", "${project.projectDir}:/opt/docs_builder", "-v", "${project.projectDir}/..:/opt", "corda/docs-builder:latest", "bash", "-c", "make-docsite.sh"
} else {
commandLine "bash", "-c", "docker run --rm --user \$(id -u):\$(id -g) -v ${project.projectDir}:/opt/docs_builder -v ${project.projectDir}/..:/opt corda/docs-builder:latest bash -c make-docsite.sh"
}
}
apidocs.shouldRunAfter makeDocs

View File

@ -1,11 +0,0 @@
FROM python:2-stretch
RUN apt-get update \
&& apt-get --no-install-recommends install -y texlive preview-latex-style texlive-generic-extra texlive-latex-extra latexmk dos2unix \
&& apt-get -y clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
ENV PATH="/opt/docs_builder:${PATH}"
WORKDIR /opt/docs_builder
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt

View File

@ -1 +0,0 @@
Pygments*.whl

View File

@ -1,13 +0,0 @@
FROM python:2-stretch
RUN apt-get update \
&& apt-get --no-install-recommends install -y texlive preview-latex-style texlive-generic-extra texlive-latex-extra latexmk dos2unix \
&& apt-get -y clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
ENV PATH="/opt/docs_builder:${PATH}"
WORKDIR /opt/docs_builder
COPY requirements.txt requirements.txt
COPY docs_builder/lexer-fix/Pygments*.whl .
RUN pip install -r requirements.txt
RUN pip install Pygments*.whl --force-reinstall

File diff suppressed because it is too large Load Diff

View File

@ -1,35 +0,0 @@
# Pygments lexer
We were getting a lot of warnings in the docs build, and people were unable to see the real warnings due to this. So we're on a mission
to sort all of these out.
A lot of the errors were because the kotlin lexer in Pygments (the syntax highlighter that sphinx uses) didn't cope with a lot of the
kotlin syntax that we use.
We have fixes for the kotlin lexer that we are trying to get checked into Pygments, but while this is taking place we need to maintain a
slightly hacked corda/docs-build docker image in which to build the docs.
## Some notes on building and testing
The sphinx/pygments brigade have delightfully decided that mercurial is a good idea. So broadly speaking, to build/test a fix:
* checkout pygments from [here](https://bitbucket.org/birkenfeld/pygments-main/overview)
* copy the two python files in (might be worth diffing - they're based on 2.3.1 - nb the kotlin test is entirely new)
* build pygments whl file
```
cd /path/to/pygments/
python setup.py install
pip install wheel
wheel convert dist/Pygments-2.3.1.dev20190 # obviously use your version
cp Pygments-2.3.1.dev20190401-py27-none-any.whl /path/to/corda/docs/source/docs_builder/lexer-fix
```
* build the latest docker build (see docs readme)
```
cd docs
docker build -t corda/docs-builder:latest -f docs_builder/lexer-fix/Dockerfile .
```
* push the new image up to docker hub (nb you can also test by going to /opt/docs and running `./make-docsite.sh`)

View File

@ -1,131 +0,0 @@
# -*- coding: utf-8 -*-
"""
Basic JavaLexer Test
~~~~~~~~~~~~~~~~~~~~
:copyright: Copyright 2006-2017 by the Pygments team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import unittest
from pygments.token import Text, Name, Operator, Keyword, Number, Punctuation, String
from pygments.lexers import KotlinLexer
class KotlinTest(unittest.TestCase):
def setUp(self):
self.lexer = KotlinLexer()
self.maxDiff = None
def testCanCopeWithBackTickNamesInFunctions(self):
fragment = u'fun `wo bble`'
tokens = [
(Keyword, u'fun'),
(Text, u' '),
(Name.Function, u'`wo bble`'),
(Text, u'\n')
]
self.assertEqual(tokens, list(self.lexer.get_tokens(fragment)))
def testCanCopeWithCommasAndDashesInBackTickNames(self):
fragment = u'fun `wo,-bble`'
tokens = [
(Keyword, u'fun'),
(Text, u' '),
(Name.Function, u'`wo,-bble`'),
(Text, u'\n')
]
self.assertEqual(tokens, list(self.lexer.get_tokens(fragment)))
def testCanCopeWithDestructuring(self):
fragment = u'val (a, b) = '
tokens = [
(Keyword, u'val'),
(Text, u' '),
(Punctuation, u'('),
(Name.Property, u'a'),
(Punctuation, u','),
(Text, u' '),
(Name.Property, u'b'),
(Punctuation, u')'),
(Text, u' '),
(Punctuation, u'='),
(Text, u' '),
(Text, u'\n')
]
self.assertEqual(tokens, list(self.lexer.get_tokens(fragment)))
def testCanCopeGenericsInDestructuring(self):
fragment = u'val (a: List<Something>, b: Set<Wobble>) ='
tokens = [
(Keyword, u'val'),
(Text, u' '),
(Punctuation, u'('),
(Name.Property, u'a'),
(Punctuation, u':'),
(Text, u' '),
(Name.Property, u'List'),
(Punctuation, u'<'),
(Name, u'Something'),
(Punctuation, u'>'),
(Punctuation, u','),
(Text, u' '),
(Name.Property, u'b'),
(Punctuation, u':'),
(Text, u' '),
(Name.Property, u'Set'),
(Punctuation, u'<'),
(Name, u'Wobble'),
(Punctuation, u'>'),
(Punctuation, u')'),
(Text, u' '),
(Punctuation, u'='),
(Text, u'\n')
]
self.assertEqual(tokens, list(self.lexer.get_tokens(fragment)))
def testCanCopeWithGenerics(self):
fragment = u'inline fun <reified T : ContractState> VaultService.queryBy(): Vault.Page<T> {'
tokens = [
(Keyword, u'inline fun'),
(Text, u' '),
(Punctuation, u'<'),
(Keyword, u'reified'),
(Text, u' '),
(Name, u'T'),
(Text, u' '),
(Punctuation, u':'),
(Text, u' '),
(Name, u'ContractState'),
(Punctuation, u'>'),
(Text, u' '),
(Name.Class, u'VaultService'),
(Punctuation, u'.'),
(Name.Function, u'queryBy'),
(Punctuation, u'('),
(Punctuation, u')'),
(Punctuation, u':'),
(Text, u' '),
(Name, u'Vault'),
(Punctuation, u'.'),
(Name, u'Page'),
(Punctuation, u'<'),
(Name, u'T'),
(Punctuation, u'>'),
(Text, u' '),
(Punctuation, u'{'),
(Text, u'\n')
]
self.assertEqual(tokens, list(self.lexer.get_tokens(fragment)))
def testShouldCopeWithMultilineComments(self):
fragment = u'"""\nthis\nis\na\ncomment"""'
tokens = [
(String, u'"""\nthis\nis\na\ncomment"""'),
(Text, u'\n')
]
self.assertEqual(tokens, list(self.lexer.get_tokens(fragment)))
if __name__ == '__main__':
unittest.main()

Some files were not shown because too many files have changed in this diff Show More