From 9c508673cfd8095a48ff2e24ef1c23bd0d15a370 Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Thu, 8 Nov 2018 10:19:03 +0000 Subject: [PATCH 01/13] Updates tutorial to match template changes. (#4170) * Updates tutorial to match template changes. * Updates hello, world tutorials to match new template structures. --- docs/source/hello-world-flow.rst | 4 ++-- docs/source/hello-world-template.rst | 12 ++---------- docs/source/tut-two-party-flow.rst | 2 +- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/docs/source/hello-world-flow.rst b/docs/source/hello-world-flow.rst index 1454b96498..10c3ba66f6 100644 --- a/docs/source/hello-world-flow.rst +++ b/docs/source/hello-world-flow.rst @@ -40,7 +40,7 @@ FlowLogic --------- All flows must subclass ``FlowLogic``. You then define the steps taken by the flow by overriding ``FlowLogic.call``. -Let's define our ``IOUFlow`` in either ``TemplateFlow.java`` or ``App.kt``. Delete the two existing flows in the +Let's define our ``IOUFlow`` in either ``Initiator.java`` or ``Flows.kt``. Delete the two existing flows in the template (``Initiator`` and ``Responder``), and replace them with the following: .. container:: codeset @@ -55,7 +55,7 @@ template (``Initiator`` and ``Responder``), and replace them with the following: :start-after: DOCSTART 01 :end-before: DOCEND 01 -If you're following along in Java, you'll also need to rename ``TemplateFlow.java`` to ``IOUFlow.java``. Let's walk +If you're following along in Java, you'll also need to rename ``Initiator.java`` to ``IOUFlow.java``. Let's walk through this code step-by-step. We've defined our own ``FlowLogic`` subclass that overrides ``FlowLogic.call``. ``FlowLogic.call`` has a return type diff --git a/docs/source/hello-world-template.rst b/docs/source/hello-world-template.rst index 402f939fb1..5d421d0906 100644 --- a/docs/source/hello-world-template.rst +++ b/docs/source/hello-world-template.rst @@ -51,7 +51,7 @@ The template has a number of files, but we can ignore most of them. We will only cordapp-contracts-states/src/main/java/com/template/TemplateState.java // 2. The flow - cordapp/src/main/java/com/template/TemplateFlow.java + cordapp/src/main/java/com/template/Initiator.java .. code-block:: kotlin @@ -59,15 +59,7 @@ The template has a number of files, but we can ignore most of them. We will only cordapp-contracts-states/src/main/kotlin/com/template/StatesAndContracts.kt // 2. The flow - cordapp/src/main/kotlin/com/template/App.kt - -Clean up --------- -To prevent build errors later on, we should delete the following files before we begin: - -* Java: ``cordapp/src/main/java/com/template/TemplateClient.java`` - -* Kotlin: ``cordapp/src/main/kotlin/com/template/Client.kt`` + cordapp/src/main/kotlin/com/template/Flows.kt Progress so far --------------- diff --git a/docs/source/tut-two-party-flow.rst b/docs/source/tut-two-party-flow.rst index 5ed7e5716b..3a4cebb15d 100644 --- a/docs/source/tut-two-party-flow.rst +++ b/docs/source/tut-two-party-flow.rst @@ -17,7 +17,7 @@ We'll do this by modifying the flow we wrote in the previous tutorial. Verifying the transaction ------------------------- -In ``IOUFlow.java``/``App.kt``, change the imports block to the following: +In ``IOUFlow.java``/``Flows.kt``, change the imports block to the following: .. container:: codeset From 611ca53cf7669783e8e63c4f59c1fea7a909bb16 Mon Sep 17 00:00:00 2001 From: szymonsztuka Date: Thu, 8 Nov 2018 10:21:45 +0000 Subject: [PATCH 02/13] Added missing ALIAS_PRIVATE_KEY static field to API Stability check. (#4194) --- .ci/api-current.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.ci/api-current.txt b/.ci/api-current.txt index 46c29041ca..b42420c07c 100644 --- a/.ci/api-current.txt +++ b/.ci/api-current.txt @@ -20,6 +20,8 @@ public class net.corda.core.CordaException extends java.lang.Exception implement public void setOriginalExceptionClassName(String) ## public final class net.corda.core.CordaOID extends java.lang.Object + @NotNull + public static final String ALIAS_PRIVATE_KEY = "1.3.6.1.4.1.50530.1.2" @NotNull public static final String CORDA_PLATFORM = "1.3.6.1.4.1.50530.1" public static final net.corda.core.CordaOID INSTANCE From 99f6cc9e650be54575431fe044a1874057d4e0c5 Mon Sep 17 00:00:00 2001 From: Joel Dudley Date: Thu, 8 Nov 2018 11:30:04 +0000 Subject: [PATCH 03/13] Updates tutorial to match template changes. (#4201) --- .../src/main/java/com/template/TemplateContract.java | 2 +- .../net/corda/docs/java/tutorial/helloworld/IOUFlow.java | 4 +--- .../net/corda/docs/java/tutorial/twoparty/IOUContract.java | 2 +- .../net/corda/docs/java/tutorial/twoparty/IOUFlow.java | 2 +- .../net/corda/docs/kotlin/tutorial/helloworld/IOUFlow.kt | 4 +--- .../net/corda/docs/kotlin/tutorial/twoparty/IOUContract.kt | 7 ++++--- .../net/corda/docs/kotlin/tutorial/twoparty/IOUFlow.kt | 2 +- 7 files changed, 10 insertions(+), 13 deletions(-) diff --git a/docs/source/example-code/src/main/java/com/template/TemplateContract.java b/docs/source/example-code/src/main/java/com/template/TemplateContract.java index e77823a71a..3153d771fb 100644 --- a/docs/source/example-code/src/main/java/com/template/TemplateContract.java +++ b/docs/source/example-code/src/main/java/com/template/TemplateContract.java @@ -8,7 +8,7 @@ import org.jetbrains.annotations.NotNull; public class TemplateContract implements Contract { // This is used to identify our contract when building a transaction. - public static final String TEMPLATE_CONTRACT_ID = "com.template.TemplateContract"; + public static final String ID = "com.template.TemplateContract"; /** * A transaction is considered valid if the verify() function of the contract of each of the transaction's input diff --git a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/helloworld/IOUFlow.java b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/helloworld/IOUFlow.java index c752acd59d..5697aed05e 100644 --- a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/helloworld/IOUFlow.java +++ b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/helloworld/IOUFlow.java @@ -13,8 +13,6 @@ import net.corda.core.transactions.SignedTransaction; import net.corda.core.transactions.TransactionBuilder; import net.corda.core.utilities.ProgressTracker; -import static com.template.TemplateContract.TEMPLATE_CONTRACT_ID; - // Replace Initiator's definition with: @InitiatingFlow @StartableByRPC @@ -53,7 +51,7 @@ public class IOUFlow extends FlowLogic { // We create a transaction builder and add the components. TransactionBuilder txBuilder = new TransactionBuilder(notary) - .addOutputState(outputState, TEMPLATE_CONTRACT_ID) + .addOutputState(outputState, TemplateContract.ID) .addCommand(cmd); // Signing the transaction. diff --git a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUContract.java b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUContract.java index ed0031dc48..af2bb40daa 100644 --- a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUContract.java +++ b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUContract.java @@ -18,7 +18,7 @@ import static net.corda.core.contracts.ContractsDSL.requireThat; // Replace TemplateContract's definition with: public class IOUContract implements Contract { - public static final String IOU_CONTRACT_ID = "com.template.IOUContract"; + public static final String ID = "com.template.IOUContract"; // Our Create command. public static class Create implements CommandData { diff --git a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUFlow.java b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUFlow.java index afc88a12fb..eb5158d24b 100644 --- a/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUFlow.java +++ b/docs/source/example-code/src/main/java/net/corda/docs/java/tutorial/twoparty/IOUFlow.java @@ -52,7 +52,7 @@ public class IOUFlow extends FlowLogic { // We create the transaction components. IOUState outputState = new IOUState(iouValue, getOurIdentity(), otherParty); - StateAndContract outputContractAndState = new StateAndContract(outputState, IOUContract.IOU_CONTRACT_ID); + StateAndContract outputContractAndState = new StateAndContract(outputState, IOUContract.ID); List requiredSigners = ImmutableList.of(getOurIdentity().getOwningKey(), otherParty.getOwningKey()); Command cmd = new Command<>(new IOUContract.Create(), requiredSigners); diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/tutorial/helloworld/IOUFlow.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/tutorial/helloworld/IOUFlow.kt index d560b5553d..8d24d540f7 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/tutorial/helloworld/IOUFlow.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/tutorial/helloworld/IOUFlow.kt @@ -16,8 +16,6 @@ import net.corda.core.identity.Party import net.corda.core.transactions.TransactionBuilder import net.corda.core.utilities.ProgressTracker -import com.template.TemplateContract.TEMPLATE_CONTRACT_ID - // Replace Initiator's definition with: @InitiatingFlow @StartableByRPC @@ -39,7 +37,7 @@ class IOUFlow(val iouValue: Int, // We create a transaction builder and add the components. val txBuilder = TransactionBuilder(notary = notary) - .addOutputState(outputState, TEMPLATE_CONTRACT_ID) + .addOutputState(outputState, TemplateContract.ID) .addCommand(cmd) // We sign the transaction. diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/tutorial/twoparty/IOUContract.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/tutorial/twoparty/IOUContract.kt index 2f55ff6441..2cc42f6dd7 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/tutorial/twoparty/IOUContract.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/tutorial/twoparty/IOUContract.kt @@ -8,10 +8,11 @@ import net.corda.core.transactions.LedgerTransaction // Add these imports: import net.corda.core.contracts.* -// Replace IOUContract's contract ID and definition with: -const val IOU_CONTRACT_ID = "com.template.IOUContract" - class IOUContract : Contract { + companion object { + const val ID = "com.template.IOUContract" + } + // Our Create command. class Create : CommandData diff --git a/docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/tutorial/twoparty/IOUFlow.kt b/docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/tutorial/twoparty/IOUFlow.kt index f438107c88..ef1e9d9b03 100644 --- a/docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/tutorial/twoparty/IOUFlow.kt +++ b/docs/source/example-code/src/main/kotlin/net/corda/docs/kotlin/tutorial/twoparty/IOUFlow.kt @@ -36,7 +36,7 @@ class IOUFlow(val iouValue: Int, // We create the transaction components. val outputState = IOUState(iouValue, ourIdentity, otherParty) - val outputContractAndState = StateAndContract(outputState, IOU_CONTRACT_ID) + val outputContractAndState = StateAndContract(outputState, IOUContract.ID) val cmd = Command(IOUContract.Create(), listOf(ourIdentity.owningKey, otherParty.owningKey)) // We add the items to the builder. From 9277042db803c3391d8dbfeffc7213e86c0f407f Mon Sep 17 00:00:00 2001 From: Rick Parker Date: Thu, 8 Nov 2018 14:33:45 +0000 Subject: [PATCH 04/13] ENT-2695 Restore async logging (#4195) * Replace error code generation technique with custom event re-writer. Switch to RandomAccessFile appenders because they supposedly give higher throughput. * Review feedback --- config/dev/log4j2.xml | 39 +++++++++++------ .../resources/log4j2.component.properties | 2 +- .../utilities/logging/AsyncLoggingTest.kt | 12 ++++++ .../src/main/resources/log4j2-test.xml | 43 +++++++++++++------ .../cliutils/CordaLog4j2ConfigFactory.kt | 34 --------------- .../corda/cliutils/ErrorCodeRewritePolicy.kt | 28 ++++++++++++ .../resources/log4j2.component.properties | 1 - 7 files changed, 97 insertions(+), 62 deletions(-) create mode 100644 node/src/test/kotlin/net/corda/node/utilities/logging/AsyncLoggingTest.kt delete mode 100644 tools/cliutils/src/main/kotlin/net/corda/cliutils/CordaLog4j2ConfigFactory.kt create mode 100644 tools/cliutils/src/main/kotlin/net/corda/cliutils/ErrorCodeRewritePolicy.kt delete mode 100644 tools/cliutils/src/main/resources/log4j2.component.properties diff --git a/config/dev/log4j2.xml b/config/dev/log4j2.xml index 408d9574d5..17ae160b02 100644 --- a/config/dev/log4j2.xml +++ b/config/dev/log4j2.xml @@ -35,7 +35,7 @@ - @@ -71,29 +71,44 @@ - + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + diff --git a/node/src/main/resources/log4j2.component.properties b/node/src/main/resources/log4j2.component.properties index 40a6560d2f..1b55982139 100644 --- a/node/src/main/resources/log4j2.component.properties +++ b/node/src/main/resources/log4j2.component.properties @@ -1,2 +1,2 @@ -log4jContextSelector=net.corda.node.utilities.logging.AsyncLoggerContextSelectorNoThreadLocal +Log4jContextSelector=net.corda.node.utilities.logging.AsyncLoggerContextSelectorNoThreadLocal AsyncLogger.RingBufferSize=262144 \ No newline at end of file diff --git a/node/src/test/kotlin/net/corda/node/utilities/logging/AsyncLoggingTest.kt b/node/src/test/kotlin/net/corda/node/utilities/logging/AsyncLoggingTest.kt new file mode 100644 index 0000000000..351760d811 --- /dev/null +++ b/node/src/test/kotlin/net/corda/node/utilities/logging/AsyncLoggingTest.kt @@ -0,0 +1,12 @@ +package net.corda.node.utilities.logging + +import org.junit.Test +import kotlin.test.assertTrue + + +class AsyncLoggingTest { + @Test + fun `async logging is configured`() { + assertTrue(AsyncLoggerContextSelectorNoThreadLocal.isSelected()) + } +} \ No newline at end of file diff --git a/testing/test-common/src/main/resources/log4j2-test.xml b/testing/test-common/src/main/resources/log4j2-test.xml index 0d4feace63..221a56127b 100644 --- a/testing/test-common/src/main/resources/log4j2-test.xml +++ b/testing/test-common/src/main/resources/log4j2-test.xml @@ -1,5 +1,5 @@ - + ${sys:log-path:-logs} @@ -37,7 +37,7 @@ - @@ -59,32 +59,47 @@ - + + + + + + + + + + + + + + + + - + - - + + - - + + - - + + - - + + - - + + diff --git a/tools/cliutils/src/main/kotlin/net/corda/cliutils/CordaLog4j2ConfigFactory.kt b/tools/cliutils/src/main/kotlin/net/corda/cliutils/CordaLog4j2ConfigFactory.kt deleted file mode 100644 index 2f0931d9ba..0000000000 --- a/tools/cliutils/src/main/kotlin/net/corda/cliutils/CordaLog4j2ConfigFactory.kt +++ /dev/null @@ -1,34 +0,0 @@ -package net.corda.cliutils - -import org.apache.logging.log4j.core.LoggerContext -import org.apache.logging.log4j.core.config.Configuration -import org.apache.logging.log4j.core.config.ConfigurationFactory -import org.apache.logging.log4j.core.config.ConfigurationSource -import org.apache.logging.log4j.core.config.Order -import org.apache.logging.log4j.core.config.plugins.Plugin -import org.apache.logging.log4j.core.config.xml.XmlConfiguration -import org.apache.logging.log4j.core.impl.LogEventFactory - -@Plugin(name = "CordaLog4j2ConfigFactory", category = "ConfigurationFactory") -@Order(Integer.MAX_VALUE) -class CordaLog4j2ConfigFactory : ConfigurationFactory() { - private companion object { - private val SUPPORTED_TYPES = arrayOf(".xml", "*") - } - - override fun getConfiguration(loggerContext: LoggerContext, source: ConfigurationSource): Configuration = ErrorCodeAppendingConfiguration(loggerContext, source) - - override fun getSupportedTypes() = SUPPORTED_TYPES - - private class ErrorCodeAppendingConfiguration(loggerContext: LoggerContext, source: ConfigurationSource) : XmlConfiguration(loggerContext, source) { - override fun doConfigure() { - super.doConfigure() - loggers.values.forEach { - val existingFactory = it.logEventFactory - it.logEventFactory = LogEventFactory { loggerName, marker, fqcn, level, message, properties, error -> - existingFactory.createEvent(loggerName, marker, fqcn, level, message?.withErrorCodeFor(error, level), properties, error) - } - } - } - } -} \ No newline at end of file diff --git a/tools/cliutils/src/main/kotlin/net/corda/cliutils/ErrorCodeRewritePolicy.kt b/tools/cliutils/src/main/kotlin/net/corda/cliutils/ErrorCodeRewritePolicy.kt new file mode 100644 index 0000000000..dc35739ea7 --- /dev/null +++ b/tools/cliutils/src/main/kotlin/net/corda/cliutils/ErrorCodeRewritePolicy.kt @@ -0,0 +1,28 @@ +package net.corda.cliutils + +import org.apache.logging.log4j.core.Core +import org.apache.logging.log4j.core.LogEvent +import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy +import org.apache.logging.log4j.core.config.plugins.Plugin +import org.apache.logging.log4j.core.config.plugins.PluginFactory +import org.apache.logging.log4j.core.impl.Log4jLogEvent + +@Plugin(name = "ErrorCodeRewritePolicy", category = Core.CATEGORY_NAME, elementType = "rewritePolicy", printObject = false) +class ErrorCodeRewritePolicy : RewritePolicy { + override fun rewrite(source: LogEvent?): LogEvent? { + val newMessage = source?.message?.withErrorCodeFor(source.thrown, source.level) + return if (newMessage == source?.message) { + source + } else { + Log4jLogEvent.Builder(source).setMessage(newMessage).build() + } + } + + companion object { + @JvmStatic + @PluginFactory + fun createPolicy(): ErrorCodeRewritePolicy { + return ErrorCodeRewritePolicy() + } + } +} \ No newline at end of file diff --git a/tools/cliutils/src/main/resources/log4j2.component.properties b/tools/cliutils/src/main/resources/log4j2.component.properties deleted file mode 100644 index dab7dd2d32..0000000000 --- a/tools/cliutils/src/main/resources/log4j2.component.properties +++ /dev/null @@ -1 +0,0 @@ -log4j.configurationFactory=net.corda.cliutils.CordaLog4j2ConfigFactory \ No newline at end of file From 6c749889d07cbb06dc4096ed19747a06dc6faed8 Mon Sep 17 00:00:00 2001 From: Michele Sollecito Date: Thu, 8 Nov 2018 15:56:00 +0000 Subject: [PATCH 05/13] [CORDA-1993]: Replace reflection-based NodeConfiguration parsing with versioned property-based parsing mechanism. (#4132) --- .../parsing/internal/Configuration.kt | 107 ++++--- .../parsing/internal/Properties.kt | 139 +++++--- .../configuration/parsing/internal/Schema.kt | 12 +- .../parsing/internal/Specification.kt | 46 ++- .../configuration/parsing/internal/Utils.kt | 4 + .../internal/versioned/VersionExtractor.kt | 13 +- .../parsing/internal/PropertyTest.kt | 4 +- .../internal/PropertyValidationTest.kt | 34 +- .../parsing/internal/SpecificationTest.kt | 2 +- .../versioned/VersionExtractorTest.kt | 14 +- .../versioned/VersionedParsingExampleTest.kt | 6 +- .../common/validation/internal/Validated.kt | 6 +- .../common/validation/internal/Validator.kt | 2 +- docs/source/changelog.rst | 2 +- docs/source/corda-configuration-file.rst | 2 +- .../net/corda/docs/ExampleConfigTest.kt | 10 +- docs/source/running-a-node.rst | 2 +- .../internal/config/ConfigUtilities.kt | 27 +- .../internal/persistence/CordaPersistence.kt | 17 +- node/build.gradle | 1 + .../net/corda/node/NodeCmdLineOptions.kt | 61 +++- .../net/corda/node/internal/NodeStartup.kt | 5 +- .../subcommands/ValidateConfigurationCli.kt | 73 +---- .../node/services/config/NodeConfiguration.kt | 286 ++--------------- .../services/config/NodeConfigurationImpl.kt | 303 ++++++++++++++++++ .../parsers/StandardConfigValueParsers.kt | 51 +++ .../config/schema/v1/ConfigSections.kt | 236 ++++++++++++++ .../schema/v1/V1NodeConfigurationSpec.kt | 149 +++++++++ ...net.corda.node.internal.NodeStartupCli.yml | 1 - .../config/NodeConfigurationImplTest.kt | 34 +- .../testing/node/internal/DriverDSLImpl.kt | 9 +- .../testing/node/internal/NodeBasedTest.kt | 7 +- .../corda/demobench/model/NodeConfigTest.kt | 2 +- .../corda/bootstrapper/nodes/NodeBuilder.kt | 7 +- 34 files changed, 1161 insertions(+), 513 deletions(-) create mode 100644 node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt create mode 100644 node/src/main/kotlin/net/corda/node/services/config/schema/parsers/StandardConfigValueParsers.kt create mode 100644 node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt create mode 100644 node/src/main/kotlin/net/corda/node/services/config/schema/v1/V1NodeConfigurationSpec.kt diff --git a/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Configuration.kt b/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Configuration.kt index becab85429..71e8fbacc4 100644 --- a/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Configuration.kt +++ b/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Configuration.kt @@ -4,6 +4,7 @@ import com.typesafe.config.Config import com.typesafe.config.ConfigException import com.typesafe.config.ConfigObject import com.typesafe.config.ConfigValue +import com.typesafe.config.ConfigValueFactory import net.corda.common.configuration.parsing.internal.versioned.VersionExtractor import net.corda.common.validation.internal.Validated import net.corda.common.validation.internal.Validated.Companion.invalid @@ -24,7 +25,7 @@ object Configuration { /** * Describes a [Config] hiding sensitive data. */ - fun describe(configuration: Config): ConfigValue + fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue = { value -> ConfigValueFactory.fromAnyRef(value.toString()) }): ConfigValue? } object Value { @@ -112,6 +113,11 @@ object Configuration { */ interface Definition : Configuration.Property.Metadata, Configuration.Validator, Configuration.Value.Extractor, Configuration.Describer, Configuration.Value.Parser { + /** + * Validates target [Config] with default [Configuration.Validation.Options]. + */ + fun validate(target: Config): Valid = validate(target, Configuration.Validation.Options.defaults) + override fun isSpecifiedBy(configuration: Config): Boolean = configuration.hasPath(key) /** @@ -120,9 +126,9 @@ object Configuration { interface Required : Definition { /** - * Returns an optional property with given [defaultValue]. This property does not produce errors in case the value is unspecified, returning the [defaultValue] instead. + * Returns an optional property. This property does not produce errors in case the value is unspecified. */ - fun optional(defaultValue: TYPE? = null): Definition + fun optional(): Optional } /** @@ -136,6 +142,17 @@ object Configuration { fun list(): Required> } + /** + * Defines a property that might be missing, resulting in a null value. + */ + interface Optional : Definition { + + /** + * Allows to specify a [defaultValue], returning a required [Configuration.Property.Definition]. + */ + fun withDefaultValue(defaultValue: TYPE): Definition + } + /** * Default property definition, required and single-value. */ @@ -219,7 +236,7 @@ object Configuration { /** * Returns a [Configuration.Property.Definition.Standard] with value of type [ENUM]. - * This property expects the exact [ENUM] value specified as text for the relevant key. + * This property expects a value in the configuration matching one of the cases of [ENUM], as text, in uppercase. */ fun > enum(key: String, enumClass: KClass, sensitive: Boolean = false): Standard = StandardProperty(key, enumClass.java.simpleName, { conf: Config, propertyKey: String -> conf.getEnum(enumClass.java, propertyKey) }, { conf: Config, propertyKey: String -> conf.getEnumList(enumClass.java, propertyKey) }, sensitive) } @@ -246,6 +263,13 @@ object Configuration { */ val properties: Set> + /** + * Validates target [Config] with default [Configuration.Validation.Options]. + */ + fun validate(target: Config): Valid = validate(target, Configuration.Validation.Options.defaults) + + override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue): ConfigValue + companion object { /** @@ -270,7 +294,7 @@ object Configuration { * A [Configuration.Schema] that is also able to parse a raw [Config] object into a [VALUE]. * It is an abstract class to allow extension with delegated properties e.g., object Settings: Specification() { val address by string().optional("localhost:8080") }. */ - abstract class Specification(name: String?, private val prefix: String? = null) : Configuration.Schema, Configuration.Value.Parser { + abstract class Specification(override val name: String, private val prefix: String? = null) : Configuration.Schema, Configuration.Value.Parser { private val mutableProperties = mutableSetOf>() @@ -321,17 +345,20 @@ object Configuration { /** * Returns a delegate for a [Configuration.Property.Definition.Standard] of type [ENUM]. - * This property expects the exact [ENUM] value specified as text for the relevant key. + * This property expects a value in the configuration matching one of the cases of [ENUM], as text, in uppercase. */ fun > enum(key: String? = null, enumClass: KClass, sensitive: Boolean = false): PropertyDelegate.Standard = PropertyDelegate.enum(key, prefix, enumClass, sensitive) { mutableProperties.add(it) } - override val name: String? get() = schema.name + /** + * @see enum + */ + fun > enum(enumClass: KClass, sensitive: Boolean = false): PropertyDelegate.Standard = enum(key = null, enumClass = enumClass, sensitive = sensitive) override fun description() = schema.description() - override fun validate(target: Config, options: Validation.Options?) = schema.validate(target, options) + override fun validate(target: Config, options: Validation.Options) = schema.validate(target, options) - override fun describe(configuration: Config) = schema.describe(configuration) + override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue) = schema.describe(configuration, serialiseValue) final override fun parse(configuration: Config, options: Configuration.Validation.Options): Valid = validate(configuration, options).mapValid(::parseValid) @@ -397,9 +424,11 @@ object Configuration { val containingPathAsString: String = containingPath.joinToString(".") /** - * [pathstr] joined by "." characters. + * [path] joined by "." characters. */ - val pathAsString: String = path.joinToString(".") + val pathAsString: String get() = path.joinToString(".") + + internal fun withContainingPathPrefix(vararg containingPath: String): Error = withContainingPath(*(containingPath.toList() + this.containingPath).toTypedArray()) internal abstract fun withContainingPath(vararg containingPath: String): Error @@ -415,12 +444,14 @@ object Configuration { */ class WrongType private constructor(override val keyName: String, override val typeName: String, message: String, containingPath: List = emptyList()) : Configuration.Validation.Error(keyName, typeName, message, containingPath) { - internal companion object { + companion object { - internal fun of(message: String, keyName: String = UNKNOWN, typeName: String = UNKNOWN, containingPath: List = emptyList()): WrongType = contextualize(keyName, containingPath).let { (key, path) -> WrongType(key, typeName, message, path) } + fun of(message: String, keyName: String? = null, typeName: String = UNKNOWN, containingPath: List = emptyList()): WrongType = contextualize(keyName ?: UNKNOWN, containingPath).let { (key, path) -> WrongType(key, typeName, message, path) } + + fun forKey(keyName: String, expectedTypeName: String, actualTypeName: String): WrongType = of("$keyName has type ${actualTypeName.toUpperCase()} rather than ${expectedTypeName.toUpperCase()}") } - override fun withContainingPath(vararg containingPath: String) = WrongType(keyName, typeName, message, containingPath.toList() + this.containingPath) + override fun withContainingPath(vararg containingPath: String) = WrongType(keyName, typeName, message, containingPath.toList()) override fun with(keyName: String, typeName: String): WrongType = WrongType.of(message, keyName, typeName, containingPath) } @@ -430,12 +461,14 @@ object Configuration { */ class MissingValue private constructor(override val keyName: String, override val typeName: String, message: String, containingPath: List = emptyList()) : Configuration.Validation.Error(keyName, typeName, message, containingPath) { - internal companion object { + companion object { - internal fun of(message: String, keyName: String = UNKNOWN, typeName: String = UNKNOWN, containingPath: List = emptyList()): MissingValue = contextualize(keyName, containingPath).let { (key, path) -> MissingValue(key, typeName, message, path) } + fun of(message: String, keyName: String? = null, typeName: String = UNKNOWN, containingPath: List = emptyList()): MissingValue = contextualize(keyName ?: UNKNOWN, containingPath).let { (key, path) -> MissingValue(key, typeName, message, path) } + + fun forKey(keyName: String): MissingValue = of("No configuration setting found for key '$keyName'", keyName) } - override fun withContainingPath(vararg containingPath: String) = MissingValue(keyName, typeName, message, containingPath.toList() + this.containingPath) + override fun withContainingPath(vararg containingPath: String) = MissingValue(keyName, typeName, message, containingPath.toList()) override fun with(keyName: String, typeName: String): MissingValue = MissingValue.of(message, keyName, typeName, containingPath) } @@ -445,12 +478,12 @@ object Configuration { */ class BadValue private constructor(override val keyName: String, override val typeName: String, message: String, containingPath: List = emptyList()) : Configuration.Validation.Error(keyName, typeName, message, containingPath) { - internal companion object { + companion object { - internal fun of(message: String, keyName: String = UNKNOWN, typeName: String = UNKNOWN, containingPath: List = emptyList()): BadValue = contextualize(keyName, containingPath).let { (key, path) -> BadValue(key, typeName, message, path) } + fun of(message: String, keyName: String? = null, typeName: String = UNKNOWN, containingPath: List = emptyList()): BadValue = contextualize(keyName ?: UNKNOWN, containingPath).let { (key, path) -> BadValue(key, typeName, message, path) } } - override fun withContainingPath(vararg containingPath: String) = BadValue(keyName, typeName, message, containingPath.toList() + this.containingPath) + override fun withContainingPath(vararg containingPath: String) = BadValue(keyName, typeName, message, containingPath.toList()) override fun with(keyName: String, typeName: String): BadValue = BadValue.of(message, keyName, typeName, containingPath) } @@ -460,12 +493,12 @@ object Configuration { */ class BadPath private constructor(override val keyName: String, override val typeName: String, message: String, containingPath: List = emptyList()) : Configuration.Validation.Error(keyName, typeName, message, containingPath) { - internal companion object { + companion object { - internal fun of(message: String, keyName: String = UNKNOWN, typeName: String = UNKNOWN, containingPath: List = emptyList()): BadPath = contextualize(keyName, containingPath).let { (key, path) -> BadPath(key, typeName, message, path) } + fun of(message: String, keyName: String? = null, typeName: String = UNKNOWN, containingPath: List = emptyList()): BadPath = contextualize(keyName ?: UNKNOWN, containingPath).let { (key, path) -> BadPath(key, typeName, message, path) } } - override fun withContainingPath(vararg containingPath: String) = BadPath(keyName, typeName, message, containingPath.toList() + this.containingPath) + override fun withContainingPath(vararg containingPath: String) = BadPath(keyName, typeName, message, containingPath.toList()) override fun with(keyName: String, typeName: String): BadPath = BadPath.of(message, keyName, typeName, containingPath) } @@ -475,12 +508,12 @@ object Configuration { */ class MalformedStructure private constructor(override val keyName: String, override val typeName: String, message: String, containingPath: List = emptyList()) : Configuration.Validation.Error(keyName, typeName, message, containingPath) { - internal companion object { + companion object { - internal fun of(message: String, keyName: String = UNKNOWN, typeName: String = UNKNOWN, containingPath: List = emptyList()): MalformedStructure = contextualize(keyName, containingPath).let { (key, path) -> MalformedStructure(key, typeName, message, path) } + fun of(message: String, keyName: String? = null, typeName: String = UNKNOWN, containingPath: List = emptyList()): MalformedStructure = contextualize(keyName ?: UNKNOWN, containingPath).let { (key, path) -> MalformedStructure(key, typeName, message, path) } } - override fun withContainingPath(vararg containingPath: String) = MalformedStructure(keyName, typeName, message, containingPath.toList() + this.containingPath) + override fun withContainingPath(vararg containingPath: String) = MalformedStructure(keyName, typeName, message, containingPath.toList()) override fun with(keyName: String, typeName: String): MalformedStructure = MalformedStructure.of(message, keyName, typeName, containingPath) } @@ -490,16 +523,14 @@ object Configuration { */ class Unknown private constructor(override val keyName: String, containingPath: List = emptyList()) : Configuration.Validation.Error(keyName, null, message(keyName), containingPath) { - internal companion object { + companion object { - private fun message(keyName: String) = "Unknown property \"$keyName\"." + private fun message(keyName: String) = "Unknown property \'$keyName\'" - internal fun of(keyName: String = UNKNOWN, containingPath: List = emptyList()): Unknown = contextualize(keyName, containingPath).let { (key, path) -> Unknown(key, path) } + fun of(keyName: String = UNKNOWN, containingPath: List = emptyList()): Unknown = contextualize(keyName, containingPath).let { (key, path) -> Unknown(key, path) } } - override val message = message(pathAsString) - - override fun withContainingPath(vararg containingPath: String) = Unknown(keyName, containingPath.toList() + this.containingPath) + override fun withContainingPath(vararg containingPath: String) = Unknown(keyName, containingPath.toList()) override fun with(keyName: String, typeName: String): Unknown = Unknown.of(keyName, containingPath) } @@ -509,12 +540,12 @@ object Configuration { */ class UnsupportedVersion private constructor(val version: Int, containingPath: List = emptyList()) : Configuration.Validation.Error(null, null, "Unknown configuration version $version.", containingPath) { - internal companion object { + companion object { - internal fun of(version: Int): UnsupportedVersion = UnsupportedVersion(version) + fun of(version: Int): UnsupportedVersion = UnsupportedVersion(version) } - override fun withContainingPath(vararg containingPath: String) = UnsupportedVersion(version, containingPath.toList() + this.containingPath) + override fun withContainingPath(vararg containingPath: String) = UnsupportedVersion(version, containingPath.toList()) override fun with(keyName: String, typeName: String): UnsupportedVersion = this } @@ -526,16 +557,16 @@ object Configuration { /** * Defines the contract from extracting a specification version from a [Config] object. */ - interface Extractor : Configuration.Value.Parser { + interface Extractor : Configuration.Value.Parser { companion object { const val DEFAULT_VERSION_VALUE = 1 /** - * Returns a [Configuration.Version.Extractor] that reads the value from given [versionKey], defaulting to [versionDefaultValue] when [versionKey] is unspecified. + * Returns a [Configuration.Version.Extractor] that reads the value from given [versionPath], defaulting to [versionDefaultValue] when [versionPath] is unspecified. */ - fun fromKey(versionKey: String, versionDefaultValue: Int? = DEFAULT_VERSION_VALUE): Configuration.Version.Extractor = VersionExtractor(versionKey, versionDefaultValue) + fun fromPath(versionPath: String, versionDefaultValue: Int = DEFAULT_VERSION_VALUE): Configuration.Version.Extractor = VersionExtractor(versionPath, versionDefaultValue) } } } diff --git a/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Properties.kt b/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Properties.kt index 17b9fe73a1..5da5e35360 100644 --- a/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Properties.kt +++ b/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Properties.kt @@ -1,13 +1,17 @@ package net.corda.common.configuration.parsing.internal -import com.typesafe.config.* +import com.typesafe.config.Config +import com.typesafe.config.ConfigException +import com.typesafe.config.ConfigObject +import com.typesafe.config.ConfigValue +import com.typesafe.config.ConfigValueFactory import net.corda.common.validation.internal.Validated import net.corda.common.validation.internal.Validated.Companion.invalid import net.corda.common.validation.internal.Validated.Companion.valid internal class LongProperty(key: String, sensitive: Boolean = false) : StandardProperty(key, Long::class.javaObjectType.simpleName, Config::getLong, Config::getLongList, sensitive) { - override fun validate(target: Config, options: Configuration.Validation.Options?): Valid { + override fun validate(target: Config, options: Configuration.Validation.Options): Valid { val validated = super.validate(target, options) if (validated.isValid && target.getValue(key).unwrapped().toString().contains(".")) { @@ -17,7 +21,7 @@ internal class LongProperty(key: String, sensitive: Boolean = false) : StandardP } } -internal open class StandardProperty(override val key: String, typeNameArg: String, private val extractSingleValue: (Config, String) -> TYPE, internal val extractListValue: (Config, String) -> List, override val isSensitive: Boolean = false, final override val schema: Configuration.Schema? = null) : Configuration.Property.Definition.Standard { +internal open class StandardProperty(override val key: String, typeNameArg: String, private val extractSingleValue: (Config, String) -> TYPE, internal val extractListValue: (Config, String) -> List, override val isSensitive: Boolean = false, final override val schema: Configuration.Schema? = null) : Configuration.Property.Definition.Standard { override fun valueIn(configuration: Config) = extractSingleValue.invoke(configuration, key) @@ -25,21 +29,21 @@ internal open class StandardProperty(override val key: String, typeNameArg override fun mapValid(mappedTypeName: String, convert: (TYPE) -> Valid): Configuration.Property.Definition.Standard = FunctionalProperty(this, mappedTypeName, extractListValue, convert) - override fun optional(defaultValue: TYPE?): Configuration.Property.Definition = OptionalProperty(this, defaultValue) + override fun optional(): Configuration.Property.Definition.Optional = OptionalDelegatedProperty(this) override fun list(): Configuration.Property.Definition.Required> = ListProperty(this) - override fun describe(configuration: Config): ConfigValue { + override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue): ConfigValue { if (isSensitive) { - return ConfigValueFactory.fromAnyRef(Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER) + return valueDescription(Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER, serialiseValue) } - return schema?.describe(configuration.getConfig(key)) ?: ConfigValueFactory.fromAnyRef(valueIn(configuration)) + return schema?.describe(configuration.getConfig(key), serialiseValue) ?: valueDescription(valueIn(configuration), serialiseValue) } override val isMandatory = true - override fun validate(target: Config, options: Configuration.Validation.Options?): Valid { + override fun validate(target: Config, options: Configuration.Validation.Options): Valid { val errors = mutableSetOf() errors += errorsWhenExtractingValue(target) @@ -47,7 +51,7 @@ internal open class StandardProperty(override val key: String, typeNameArg schema?.let { nestedSchema -> val nestedConfig: Config? = target.getConfig(key) nestedConfig?.let { - errors += nestedSchema.validate(nestedConfig, options).errors.map { error -> error.withContainingPath(*key.split(".").toTypedArray()) } + errors += nestedSchema.validate(nestedConfig, options).errors.map { error -> error.withContainingPathPrefix(*key.split(".").toTypedArray()) } } } } @@ -57,63 +61,63 @@ internal open class StandardProperty(override val key: String, typeNameArg override fun toString() = "\"$key\": \"$typeName\"" } -private class ListProperty(delegate: StandardProperty) : RequiredDelegatedProperty, StandardProperty>(delegate) { +private class ListProperty(delegate: StandardProperty) : RequiredDelegatedProperty, StandardProperty>(delegate) { override val typeName: String = "List<${delegate.typeName}>" override fun valueIn(configuration: Config): List = delegate.extractListValue.invoke(configuration, key) - override fun validate(target: Config, options: Configuration.Validation.Options?): Valid { + override fun validate(target: Config, options: Configuration.Validation.Options): Valid { val errors = mutableSetOf() errors += errorsWhenExtractingValue(target) 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(key, "[$index]") } }.reduce { one, other -> one + other } + errors += valueIn(target).asSequence().map { element -> element as ConfigObject }.map(ConfigObject::toConfig).mapIndexed { index, targetConfig -> schema.validate(targetConfig, options).errors.map { error -> error.withContainingPath(*error.containingPath(index).toTypedArray()) } }.fold(emptyList()) { one, other -> one + other }.toSet() } } return Validated.withResult(target, errors) } - override fun describe(configuration: Config): ConfigValue { + override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue): ConfigValue { if (isSensitive) { - return ConfigValueFactory.fromAnyRef(Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER) + 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() + ConfigValueFactory.fromIterable(elementsDescription) + } + else -> valueDescription(valueIn(configuration), serialiseValue) + } + } + + private fun Configuration.Validation.Error.containingPath(index: Int): List { + val newContainingPath = listOf(key, "[$index]") + return when { + containingPath.size > 1 -> newContainingPath + containingPath.subList(1, containingPath.size) + else -> newContainingPath } - return delegate.schema?.let { schema -> ConfigValueFactory.fromAnyRef(valueIn(configuration).asSequence().map { element -> element as ConfigObject }.map(ConfigObject::toConfig).map { schema.describe(it) }.toList()) } ?: ConfigValueFactory.fromAnyRef(valueIn(configuration)) } } -private class OptionalProperty(delegate: Configuration.Property.Definition.Required, private val defaultValue: TYPE?) : DelegatedProperty>(delegate) { +private class OptionalPropertyWithDefault(delegate: Configuration.Property.Definition.Optional, private val defaultValue: TYPE) : DelegatedProperty>(delegate) { override val isMandatory: Boolean = false - override val typeName: String = "${super.typeName}?" + override val typeName: String = delegate.typeName.removeSuffix("?") - override fun describe(configuration: Config) = delegate.describe(configuration) + 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 valueIn(configuration: Config): TYPE? { + override fun valueIn(configuration: Config): TYPE = delegate.valueIn(configuration) ?: defaultValue - return when { - isSpecifiedBy(configuration) -> delegate.valueIn(configuration) - else -> defaultValue - } - } - - override fun validate(target: Config, options: Configuration.Validation.Options?): Valid { - - val result = delegate.validate(target, options) - val error = result.errors.asSequence().filterIsInstance().singleOrNull() - return when { - error != null -> if (result.errors.size > 1) result else valid(target) - else -> result - } - } + override fun validate(target: Config, options: Configuration.Validation.Options): Valid = delegate.validate(target, options) } private class FunctionalProperty(delegate: Configuration.Property.Definition.Standard, private val mappedTypeName: String, internal val extractListValue: (Config, String) -> List, private val convert: (TYPE) -> Valid) : RequiredDelegatedProperty>(delegate), Configuration.Property.Definition.Standard { - override fun valueIn(configuration: Config) = convert.invoke(delegate.valueIn(configuration)).valueOrThrow() + override fun valueIn(configuration: Config) = convert.invoke(delegate.valueIn(configuration)).orThrow() override val typeName: String = if (super.typeName == "#$mappedTypeName") super.typeName else "$mappedTypeName(${super.typeName})" @@ -121,7 +125,7 @@ private class FunctionalProperty(delegate: Configuration.Pro override fun list(): Configuration.Property.Definition.Required> = FunctionalListProperty(this) - override fun validate(target: Config, options: Configuration.Validation.Options?): Valid { + override fun validate(target: Config, options: Configuration.Validation.Options): Valid { val errors = mutableSetOf() errors += delegate.validate(target, options).errors @@ -131,7 +135,7 @@ private class FunctionalProperty(delegate: Configuration.Pro return Validated.withResult(target, errors) } - override fun describe(configuration: Config) = delegate.describe(configuration) + override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue) = delegate.describe(configuration, serialiseValue) } private class FunctionalListProperty(delegate: FunctionalProperty) : RequiredDelegatedProperty, FunctionalProperty>(delegate) { @@ -140,7 +144,7 @@ private class FunctionalListProperty(delegate: FunctionalProper override fun valueIn(configuration: Config): List = delegate.extractListValue.invoke(configuration, key).asSequence().map { configObject(key to ConfigValueFactory.fromAnyRef(it)) }.map(ConfigObject::toConfig).map(delegate::valueIn).toList() - override fun validate(target: Config, options: Configuration.Validation.Options?): Valid { + override fun validate(target: Config, options: Configuration.Validation.Options): Valid { val list = try { delegate.extractListValue.invoke(target, key) @@ -151,16 +155,24 @@ private class FunctionalListProperty(delegate: FunctionalProper 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(key, "[$index]") } }.reduce { one, other -> one + other }.toSet() + val errors = list.asSequence().map { configObject(key to ConfigValueFactory.fromAnyRef(it)) }.mapIndexed { index, value -> delegate.validate(value.toConfig(), options).errors.map { error -> error.withContainingPath(*error.containingPath(index).toTypedArray()) } }.fold(emptyList()) { one, other -> one + other }.toSet() return Validated.withResult(target, errors) } - override fun describe(configuration: Config): ConfigValue { + private fun Configuration.Validation.Error.containingPath(index: Int): List { + val newContainingPath = listOf(key, "[$index]") + return when { + containingPath.size > 1 -> newContainingPath + containingPath.subList(1, containingPath.size) + else -> newContainingPath + } + } + + override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue): ConfigValue { if (isSensitive) { - return ConfigValueFactory.fromAnyRef(Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER) + return valueDescription(Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER, serialiseValue) } - return delegate.schema?.let { schema -> ConfigValueFactory.fromAnyRef(valueIn(configuration).asSequence().map { element -> element as ConfigObject }.map(ConfigObject::toConfig).map { schema.describe(it) }.toList()) } ?: ConfigValueFactory.fromAnyRef(valueIn(configuration)) + 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) } } @@ -169,12 +181,45 @@ private abstract class DelegatedProperty>(delegate: DELEGATE) : DelegatedProperty(delegate), Configuration.Property.Definition.Required { +private class OptionalDelegatedProperty(private val delegate: Configuration.Property.Definition) : Configuration.Property.Metadata by delegate, Configuration.Property.Definition.Optional { - final override fun optional(defaultValue: TYPE?): Configuration.Property.Definition = OptionalProperty(this, defaultValue) + override val isMandatory: Boolean = false + + 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? { + + return when { + isSpecifiedBy(configuration) -> delegate.valueIn(configuration) + else -> null + } + } + + override fun validate(target: Config, options: Configuration.Validation.Options): Valid { + + val result = delegate.validate(target, options) + val errors = result.errors + val missingValueError = errors.asSequence().filterIsInstance().filter { it.pathAsString == key }.singleOrNull() + return when { + missingValueError != null -> if (errors.size > 1) result else valid(target) + else -> result + } + } + + override fun withDefaultValue(defaultValue: TYPE): Configuration.Property.Definition = OptionalPropertyWithDefault(this, defaultValue) + + override fun toString() = "\"$key\": \"$typeName\"" } -private fun ConfigException.toValidationError(keyName: String, typeName: String): Configuration.Validation.Error { + +private abstract class RequiredDelegatedProperty>(delegate: DELEGATE) : DelegatedProperty(delegate), Configuration.Property.Definition.Required { + + final override fun optional(): Configuration.Property.Definition.Optional = OptionalDelegatedProperty(this) +} + +fun ConfigException.toValidationError(keyName: String? = null, typeName: String): Configuration.Validation.Error { val toError = when (this) { is ConfigException.Missing -> Configuration.Validation.Error.MissingValue.Companion::of @@ -202,4 +247,6 @@ private fun Configuration.Property.Definition<*>.errorsWhenExtractingValue(targe private val expectedExceptionTypes = setOf(ConfigException.Missing::class, ConfigException.WrongType::class, ConfigException.BadValue::class, ConfigException.BadPath::class, ConfigException.Parse::class) -private fun isErrorExpected(error: ConfigException) = expectedExceptionTypes.any { expected -> expected.isInstance(error) } \ No newline at end of file +private fun isErrorExpected(error: ConfigException) = expectedExceptionTypes.any { expected -> expected.isInstance(error) } + +private fun valueDescription(value: Any, serialiseValue: (Any) -> ConfigValue) = serialiseValue.invoke(value) \ No newline at end of file diff --git a/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Schema.kt b/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Schema.kt index 06e90d34cd..e92214e5ca 100644 --- a/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Schema.kt +++ b/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Schema.kt @@ -16,10 +16,12 @@ internal class Schema(override val name: String?, unorderedProperties: Iterable< } } - override fun validate(target: Config, options: Configuration.Validation.Options?): Valid { + override fun validate(target: Config, options: Configuration.Validation.Options): Valid { - val propertyErrors = properties.flatMap { property -> property.validate(target, options).errors }.toMutableSet() - if (options?.strict == true) { + val propertyErrors = properties.flatMap { property -> + property.validate(target, options).errors + }.toMutableSet() + if (options.strict) { val unknownKeys = target.root().keys - properties.map(Configuration.Property.Definition<*>::key) propertyErrors += unknownKeys.map { Configuration.Validation.Error.Unknown.of(it) } } @@ -45,9 +47,9 @@ internal class Schema(override val name: String?, unorderedProperties: Iterable< return description.toString() } - override fun describe(configuration: Config): ConfigValue { + override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue): ConfigValue { - return properties.asSequence().map { it.key to it.describe(configuration) }.fold(configObject()) { config, (key, value) -> config.withValue(key, value) } + return properties.asSequence().map { it.key to it.describe(configuration, serialiseValue) }.filter { it.second != null }.fold(configObject()) { config, (key, value) -> config.withValue(key, value) } } override fun equals(other: Any?): Boolean { diff --git a/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Specification.kt b/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Specification.kt index 919263dc16..a7cfcedf23 100644 --- a/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Specification.kt +++ b/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Specification.kt @@ -14,7 +14,7 @@ interface PropertyDelegate { operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty> - fun optional(defaultValue: TYPE? = null): PropertyDelegate + fun optional(): PropertyDelegate.Optional } interface Single { @@ -24,6 +24,13 @@ interface PropertyDelegate { fun list(): Required> } + interface Optional { + + operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty> + + fun withDefaultValue(defaultValue: TYPE): PropertyDelegate + } + interface Standard : Required, Single { override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty> @@ -67,35 +74,52 @@ private class PropertyDelegateImpl(private val key: String?, private val p } } - override fun list(): PropertyDelegate.Required> = ListPropertyDelegateImpl(key, sensitive, addToProperties, { k, s -> construct.invoke(k, s).list() }) + override fun list(): PropertyDelegate.Required> = ListPropertyDelegateImpl(key, prefix, sensitive, addToProperties, { k, s -> construct.invoke(k, s).list() }) - override fun optional(defaultValue: TYPE?): PropertyDelegate = OptionalPropertyDelegateImpl(key, sensitive, addToProperties, { k, s -> construct.invoke(k, s).optional(defaultValue) }) + override fun optional(): PropertyDelegate.Optional = OptionalPropertyDelegateImpl(key, prefix, sensitive, addToProperties, { k, s -> construct.invoke(k, s).optional() }) override fun mapValid(mappedTypeName: String, convert: (TYPE) -> Valid): PropertyDelegate.Standard = PropertyDelegateImpl(key, prefix, sensitive, addToProperties, { k, s -> construct.invoke(k, s).mapValid(mappedTypeName) { value -> convert.invoke(value) } }) } -private class OptionalPropertyDelegateImpl(private val key: String?, private val sensitive: Boolean = false, private val addToProperties: (Configuration.Property.Definition<*>) -> Unit, private val construct: (String, Boolean) -> Configuration.Property.Definition) : PropertyDelegate { +private class OptionalPropertyDelegateImpl(private val key: String?, private val prefix: String?, private val sensitive: Boolean = false, private val addToProperties: (Configuration.Property.Definition<*>) -> Unit, private val construct: (String, Boolean) -> Configuration.Property.Definition.Optional) : PropertyDelegate.Optional { - override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty> { + override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty> { - val prop = construct.invoke(key ?: property.name, sensitive).also(addToProperties) - return object : ReadOnlyProperty> { + val shortName = key ?: property.name + val prop = construct.invoke(prefix?.let { "$prefix.$shortName" } ?: shortName, sensitive).also(addToProperties) + return object : ReadOnlyProperty> { - override fun getValue(thisRef: Any?, property: KProperty<*>): Configuration.Property.Definition = prop + override fun getValue(thisRef: Any?, property: KProperty<*>): Configuration.Property.Definition.Optional = prop + } + } + + override fun withDefaultValue(defaultValue: TYPE): PropertyDelegate = OptionalWithDefaultPropertyDelegateImpl(key, prefix, sensitive, addToProperties, { k, s -> construct.invoke(k, s).withDefaultValue(defaultValue) }) +} + +private class OptionalWithDefaultPropertyDelegateImpl(private val key: String?, private val prefix: String?, private val sensitive: Boolean = false, private val addToProperties: (Configuration.Property.Definition<*>) -> Unit, private val construct: (String, Boolean) -> Configuration.Property.Definition) : PropertyDelegate { + + override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty> { + + val shortName = key ?: property.name + val prop = construct.invoke(prefix?.let { "$prefix.$shortName" } ?: shortName, sensitive).also(addToProperties) + return object : ReadOnlyProperty> { + + override fun getValue(thisRef: Any?, property: KProperty<*>): Configuration.Property.Definition = prop } } } -private class ListPropertyDelegateImpl(private val key: String?, private val sensitive: Boolean = false, private val addToProperties: (Configuration.Property.Definition<*>) -> Unit, private val construct: (String, Boolean) -> Configuration.Property.Definition.Required) : PropertyDelegate.Required { +private class ListPropertyDelegateImpl(private val key: String?, private val prefix: String?, private val sensitive: Boolean = false, private val addToProperties: (Configuration.Property.Definition<*>) -> Unit, private val construct: (String, Boolean) -> Configuration.Property.Definition.Required) : PropertyDelegate.Required { override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty> { - val prop = construct.invoke(key ?: property.name, sensitive).also(addToProperties) + val shortName = key ?: property.name + val prop = construct.invoke(prefix?.let { "$prefix.$shortName" } ?: shortName, sensitive).also(addToProperties) return object : ReadOnlyProperty> { override fun getValue(thisRef: Any?, property: KProperty<*>): Configuration.Property.Definition.Required = prop } } - override fun optional(defaultValue: TYPE?): PropertyDelegate = OptionalPropertyDelegateImpl(key, sensitive, addToProperties, { k, s -> construct.invoke(k, s).optional(defaultValue) }) + override fun optional(): PropertyDelegate.Optional = OptionalPropertyDelegateImpl(key, prefix, sensitive, addToProperties, { k, s -> construct.invoke(k, s).optional() }) } \ No newline at end of file diff --git a/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Utils.kt b/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Utils.kt index d75a2b1d08..5475e23b84 100644 --- a/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Utils.kt +++ b/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/Utils.kt @@ -15,6 +15,10 @@ operator fun Config.get(property: Configuration.Property.Definition inline fun Configuration.Specification<*>.nested(specification: Configuration.Specification, key: String? = null, sensitive: Boolean = false): PropertyDelegate.Standard = nestedObject(schema = specification, key = key, sensitive = sensitive).map(ConfigObject::toConfig).mapValid { value -> specification.parse(value) } +fun Configuration.Property.Definition.Single.listOrEmpty(): Configuration.Property.Definition> = list().optional().withDefaultValue(emptyList()) + +fun PropertyDelegate.Single.listOrEmpty(): PropertyDelegate> = list().optional().withDefaultValue(emptyList()) + @Suppress("UNCHECKED_CAST") internal fun configObject(vararg entries: Pair): ConfigObject { diff --git a/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/versioned/VersionExtractor.kt b/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/versioned/VersionExtractor.kt index cdba00e7c7..55e34d6467 100644 --- a/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/versioned/VersionExtractor.kt +++ b/common/configuration-parsing/src/main/kotlin/net/corda/common/configuration/parsing/internal/versioned/VersionExtractor.kt @@ -5,18 +5,21 @@ import net.corda.common.configuration.parsing.internal.Configuration import net.corda.common.configuration.parsing.internal.Valid import net.corda.common.configuration.parsing.internal.valid -internal class VersionExtractor(versionKey: String, versionDefaultValue: Int?) : Configuration.Version.Extractor { +internal class VersionExtractor(versionPath: String, versionDefaultValue: Int) : Configuration.Version.Extractor { - private val spec = Spec(versionKey, versionDefaultValue) + private val containingPath = versionPath.split(".").let { if (it.size > 1) it.subList(0, it.size - 1) else null } + private val key = versionPath.split(".").last() - override fun parse(configuration: Config, options: Configuration.Validation.Options): Valid { + private val spec = Spec(key, versionDefaultValue, containingPath?.joinToString(".")) + + override fun parse(configuration: Config, options: Configuration.Validation.Options): Valid { return spec.parse(configuration) } - private class Spec(versionKey: String, versionDefaultValue: Int?) : Configuration.Specification("Version") { + private class Spec(key: String, versionDefaultValue: Int, prefix: String?) : Configuration.Specification("Version", prefix) { - private val version by int(key = versionKey).optional(versionDefaultValue) + private val version by int(key = key).optional().withDefaultValue(versionDefaultValue) override fun parseValid(configuration: Config) = valid(version.valueIn(configuration)) } diff --git a/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/PropertyTest.kt b/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/PropertyTest.kt index d2d35e5de1..cf01937b50 100644 --- a/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/PropertyTest.kt +++ b/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/PropertyTest.kt @@ -95,7 +95,7 @@ class PropertyTest { val configuration = configObject(key to null).toConfig() val defaultValue = listOf(1L, 2L, 3L) - val property = Configuration.Property.Definition.long(key).list().optional(defaultValue) + val property = Configuration.Property.Definition.long(key).list().optional().withDefaultValue(defaultValue) println(property) assertThat(property.key).isEqualTo(key) @@ -173,7 +173,7 @@ class PropertyTest { val configuration = configObject(key to null).toConfig() val defaultValue = 23L - val property = Configuration.Property.Definition.long(key).optional(defaultValue) + val property = Configuration.Property.Definition.long(key).optional().withDefaultValue(defaultValue) println(property) assertThat(property.key).isEqualTo(key) diff --git a/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/PropertyValidationTest.kt b/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/PropertyValidationTest.kt index b26aca0abb..e4fe7b85f7 100644 --- a/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/PropertyValidationTest.kt +++ b/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/PropertyValidationTest.kt @@ -1,9 +1,7 @@ package net.corda.common.configuration.parsing.internal -import com.typesafe.config.Config import net.corda.common.validation.internal.Validated.Companion.invalid import net.corda.common.validation.internal.Validated.Companion.valid -import net.corda.common.validation.internal.Validator import org.assertj.core.api.Assertions.assertThat import org.junit.Test @@ -15,7 +13,7 @@ class PropertyValidationTest { val key = "a.b.c" val configuration = configObject().toConfig() - val property: Validator = Configuration.Property.Definition.long(key) + val property = Configuration.Property.Definition.long(key) assertThat(property.validate(configuration).errors).satisfies { errors -> @@ -34,7 +32,7 @@ class PropertyValidationTest { val key = "a.b.c" val configuration = configObject(key to null).toConfig() - val property: Validator = Configuration.Property.Definition.long(key) + val property = Configuration.Property.Definition.long(key) assertThat(property.validate(configuration).errors).satisfies { errors -> @@ -53,7 +51,7 @@ class PropertyValidationTest { val key = "a.b.c" val configuration = configObject().toConfig() - val property: Validator = Configuration.Property.Definition.long(key).list() + val property = Configuration.Property.Definition.long(key).list() assertThat(property.validate(configuration).errors).satisfies { errors -> @@ -72,7 +70,7 @@ class PropertyValidationTest { val key = "a.b.c" val configuration = configObject(key to null).toConfig() - val property: Validator = Configuration.Property.Definition.long(key).list() + val property = Configuration.Property.Definition.long(key).list() assertThat(property.validate(configuration).errors).satisfies { errors -> @@ -90,7 +88,7 @@ class PropertyValidationTest { val key = "a.b.c" - val property: Validator = Configuration.Property.Definition.long(key) + val property = Configuration.Property.Definition.long(key) val configuration = configObject(key to false).toConfig() @@ -110,7 +108,7 @@ class PropertyValidationTest { val key = "a.b.c" - val property: Validator = Configuration.Property.Definition.long(key) + val property = Configuration.Property.Definition.long(key) val configuration = configObject(key to 1.2).toConfig() @@ -130,7 +128,7 @@ class PropertyValidationTest { val key = "a.b.c" - val property: Validator = Configuration.Property.Definition.double(key) + val property = Configuration.Property.Definition.double(key) val configuration = configObject(key to 1).toConfig() @@ -142,7 +140,7 @@ class PropertyValidationTest { val key = "a.b.c" - val property: Validator = Configuration.Property.Definition.long(key).list() + val property = Configuration.Property.Definition.long(key).list() val configuration = configObject(key to listOf(false, true)).toConfig() @@ -162,7 +160,7 @@ class PropertyValidationTest { val key = "a.b.c" - val property: Validator = Configuration.Property.Definition.long(key) + val property = Configuration.Property.Definition.long(key) val configuration = configObject(key to listOf(1, 2, 3)).toConfig() @@ -182,7 +180,7 @@ class PropertyValidationTest { val key = "a.b.c" - val property: Validator = Configuration.Property.Definition.long(key).list() + val property = Configuration.Property.Definition.long(key).list() val configuration = configObject(key to 1).toConfig() @@ -205,7 +203,7 @@ class PropertyValidationTest { val nestedKey = "d" val nestedPropertySchema = Configuration.Schema.withProperties(Configuration.Property.Definition.long(nestedKey)) - val property: Validator = Configuration.Property.Definition.nestedObject(key, nestedPropertySchema) + val property = Configuration.Property.Definition.nestedObject(key, nestedPropertySchema) val configuration = configObject(key to configObject(nestedKey to false)).toConfig() @@ -228,7 +226,7 @@ class PropertyValidationTest { val nestedKey = "d" val nestedPropertySchema = Configuration.Schema.withProperties(Configuration.Property.Definition.long(nestedKey)) - val property: Validator = Configuration.Property.Definition.nestedObject(key, nestedPropertySchema) + val property = Configuration.Property.Definition.nestedObject(key, nestedPropertySchema) val configuration = configObject(key to configObject()).toConfig() @@ -251,7 +249,7 @@ class PropertyValidationTest { val nestedKey = "d" val nestedPropertySchema = Configuration.Schema.withProperties(Configuration.Property.Definition.long(nestedKey)) - val property: Validator = Configuration.Property.Definition.nestedObject(key, nestedPropertySchema) + val property = Configuration.Property.Definition.nestedObject(key, nestedPropertySchema) val configuration = configObject(key to configObject(nestedKey to null)).toConfig() @@ -273,7 +271,7 @@ class PropertyValidationTest { val nestedKey = "d" - val property: Validator = Configuration.Property.Definition.nestedObject(key) + val property = Configuration.Property.Definition.nestedObject(key) val configuration = configObject(key to configObject(nestedKey to false)).toConfig() @@ -285,7 +283,7 @@ class PropertyValidationTest { val key = "a" - val property: Validator = Configuration.Property.Definition.string(key).mapValid(::parseAddress) + val property = Configuration.Property.Definition.string(key).mapValid(::parseAddress) val host = "localhost" val port = 8080 @@ -301,7 +299,7 @@ class PropertyValidationTest { val key = "a.b.c" - val property: Validator = Configuration.Property.Definition.string(key).mapValid(::parseAddress) + val property = Configuration.Property.Definition.string(key).mapValid(::parseAddress) val host = "localhost" val port = 8080 diff --git a/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/SpecificationTest.kt b/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/SpecificationTest.kt index a94489c99e..be859d764a 100644 --- a/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/SpecificationTest.kt +++ b/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/SpecificationTest.kt @@ -39,7 +39,7 @@ class SpecificationTest { val rpcSettings = RpcSettingsSpec.parse(configuration) assertThat(rpcSettings.isValid).isTrue() - assertThat(rpcSettings.valueOrThrow()).satisfies { value -> + assertThat(rpcSettings.orThrow()).satisfies { value -> assertThat(value.useSsl).isEqualTo(useSslValue) assertThat(value.addresses).satisfies { addresses -> diff --git a/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/versioned/VersionExtractorTest.kt b/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/versioned/VersionExtractorTest.kt index aa2e6e7139..1cae46f740 100644 --- a/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/versioned/VersionExtractorTest.kt +++ b/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/versioned/VersionExtractorTest.kt @@ -9,8 +9,8 @@ import org.junit.Test class VersionExtractorTest { - private val versionExtractor = Configuration.Version.Extractor.fromKey("configuration.metadata.version") - private val extractVersion: (Config) -> Valid = { config -> versionExtractor.parse(config) } + private val versionExtractor = Configuration.Version.Extractor.fromPath("configuration.metadata.version") + private val extractVersion: (Config) -> Valid = { config -> versionExtractor.parse(config) } @Test fun version_header_extraction_present() { @@ -18,7 +18,7 @@ class VersionExtractorTest { val versionValue = Configuration.Version.Extractor.DEFAULT_VERSION_VALUE + 1 val rawConfiguration = configObject("configuration" to configObject("metadata" to configObject("version" to versionValue), "node" to configObject("p2pAddress" to "localhost:8080"))).toConfig() - val version = extractVersion.invoke(rawConfiguration).valueOrThrow() + val version = extractVersion.invoke(rawConfiguration).orThrow() assertThat(version).isEqualTo(versionValue) } @@ -27,7 +27,7 @@ class VersionExtractorTest { val rawConfiguration = configObject("configuration" to configObject("node" to configObject("p2pAddress" to "localhost:8080"))).toConfig() - val version = extractVersion.invoke(rawConfiguration).valueOrThrow() + val version = extractVersion.invoke(rawConfiguration).orThrow() assertThat(version).isEqualTo(Configuration.Version.Extractor.DEFAULT_VERSION_VALUE) } @@ -36,7 +36,7 @@ class VersionExtractorTest { val rawConfiguration = configObject("configuration" to configObject("metadata" to configObject(), "node" to configObject("p2pAddress" to "localhost:8080"))).toConfig() - val version = extractVersion.invoke(rawConfiguration).valueOrThrow() + val version = extractVersion.invoke(rawConfiguration).orThrow() assertThat(version).isEqualTo(Configuration.Version.Extractor.DEFAULT_VERSION_VALUE) } @@ -46,7 +46,7 @@ class VersionExtractorTest { val rawConfiguration = configObject("configuration" to configObject("metadata" to configObject("version" to null), "node" to configObject("p2pAddress" to "localhost:8080"))).toConfig() - val version = extractVersion.invoke(rawConfiguration).valueOrThrow() + val version = extractVersion.invoke(rawConfiguration).orThrow() assertThat(version).isEqualTo(Configuration.Version.Extractor.DEFAULT_VERSION_VALUE) } @@ -56,7 +56,7 @@ class VersionExtractorTest { val rawConfiguration = configObject().toConfig() - val version = extractVersion.invoke(rawConfiguration).valueOrThrow() + val version = extractVersion.invoke(rawConfiguration).orThrow() assertThat(version).isEqualTo(Configuration.Version.Extractor.DEFAULT_VERSION_VALUE) } diff --git a/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/versioned/VersionedParsingExampleTest.kt b/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/versioned/VersionedParsingExampleTest.kt index 60b35480e5..7b0a8a9be0 100644 --- a/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/versioned/VersionedParsingExampleTest.kt +++ b/common/configuration-parsing/src/test/kotlin/net/corda/common/configuration/parsing/internal/versioned/VersionedParsingExampleTest.kt @@ -12,7 +12,7 @@ class VersionedParsingExampleTest { @Test fun correct_parsing_function_is_used_for_present_version() { - val versionParser = Configuration.Version.Extractor.fromKey("configuration.metadata.version", null) + val versionParser = Configuration.Version.Extractor.fromPath("configuration.metadata.version") val extractVersion: (Config) -> Valid = { config -> versionParser.parseRequired(config) } val parseConfiguration = VersionedSpecificationRegistry.mapping(extractVersion, 1 to RpcSettingsSpec.V1, 2 to RpcSettingsSpec.V2) @@ -35,7 +35,7 @@ class VersionedParsingExampleTest { fun default_value_is_used_for_absent_version() { val defaultVersion = 2 - val versionParser = Configuration.Version.Extractor.fromKey("configuration.metadata.version", defaultVersion) + val versionParser = Configuration.Version.Extractor.fromPath("configuration.metadata.version", defaultVersion) val extractVersion: (Config) -> Valid = { config -> versionParser.parseRequired(config) } val parseConfiguration = VersionedSpecificationRegistry.mapping(extractVersion, 1 to RpcSettingsSpec.V1, 2 to RpcSettingsSpec.V2) @@ -52,7 +52,7 @@ class VersionedParsingExampleTest { private fun assertResult(result: Valid, principalAddressValue: Address, adminAddressValue: Address) { assertThat(result.isValid).isTrue() - assertThat(result.valueOrThrow()).satisfies { value -> + assertThat(result.orThrow()).satisfies { value -> assertThat(value.principal).isEqualTo(principalAddressValue) assertThat(value.admin).isEqualTo(adminAddressValue) diff --git a/common/validation/src/main/kotlin/net/corda/common/validation/internal/Validated.kt b/common/validation/src/main/kotlin/net/corda/common/validation/internal/Validated.kt index ac7c6433ae..708af605b9 100644 --- a/common/validation/src/main/kotlin/net/corda/common/validation/internal/Validated.kt +++ b/common/validation/src/main/kotlin/net/corda/common/validation/internal/Validated.kt @@ -39,7 +39,7 @@ interface Validated { * * @throws IllegalStateException or the result of [exceptionOnErrors] if there are errors. */ - fun valueOrThrow(exceptionOnErrors: (Set) -> Exception = { errors -> IllegalStateException(errors.joinToString(System.lineSeparator())) }): TARGET + fun orThrow(exceptionOnErrors: (Set) -> Exception = { errors -> IllegalStateException(errors.joinToString(System.lineSeparator())) }): TARGET /** * Applies the [convert] function to the [TARGET] value, if valid. Otherwise, returns a [Validated] monad with a [MAPPED] generic type and the current errors set. @@ -113,7 +113,7 @@ interface Validated { class Successful(override val value: TARGET) : Result(), Validated { override val errors: Set = emptySet() - override fun valueOrThrow(exceptionOnErrors: (Set) -> Exception) = value + override fun orThrow(exceptionOnErrors: (Set) -> Exception) = value override fun map(convert: (TARGET) -> MAPPED): Validated { return valid(convert.invoke(value)) @@ -138,7 +138,7 @@ interface Validated { override val value: TARGET get() = throw IllegalStateException("Invalid state.") - override fun valueOrThrow(exceptionOnErrors: (Set) -> Exception) = throw exceptionOnErrors.invoke(errors) + override fun orThrow(exceptionOnErrors: (Set) -> Exception) = throw exceptionOnErrors.invoke(errors) override fun map(convert: (TARGET) -> MAPPED): Validated { return invalid(errors) diff --git a/common/validation/src/main/kotlin/net/corda/common/validation/internal/Validator.kt b/common/validation/src/main/kotlin/net/corda/common/validation/internal/Validator.kt index 2ba54acdcb..3a87f9aab6 100644 --- a/common/validation/src/main/kotlin/net/corda/common/validation/internal/Validator.kt +++ b/common/validation/src/main/kotlin/net/corda/common/validation/internal/Validator.kt @@ -8,5 +8,5 @@ interface Validator { /** * Validates [target] using given [options], producing a [Validated] monad wrapping either a valid [target] or a set of [ERROR]s. */ - fun validate(target: TARGET, options: OPTIONS? = null): Validated + fun validate(target: TARGET, options: OPTIONS): Validated } \ No newline at end of file diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index f220496eb1..978ae3af91 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -191,7 +191,7 @@ Unreleased * Configuration file changes: * Added program line argument ``on-unknown-config-keys`` to allow specifying behaviour on unknown node configuration property keys. - Values are: [FAIL, WARN, IGNORE], default to FAIL if unspecified. + Values are: [FAIL, IGNORE], default to FAIL if unspecified. * Introduced a placeholder for custom properties within ``node.conf``; the property key is "custom". * The deprecated web server now has its own ``web-server.conf`` file, separate from ``node.conf``. * Property keys with double quotes (e.g. "key") in ``node.conf`` are no longer allowed, for rationale refer to :doc:`corda-configuration-file`. diff --git a/docs/source/corda-configuration-file.rst b/docs/source/corda-configuration-file.rst index 9ba3067fcc..532ac838e0 100644 --- a/docs/source/corda-configuration-file.rst +++ b/docs/source/corda-configuration-file.rst @@ -32,7 +32,7 @@ The property `"dataSourceProperties.dataSourceClassName" = "val"` in ``reference would be not overwritten by the property `dataSourceProperties.dataSourceClassName = "val2"` in ``node.conf``. By default the node will fail to start in presence of unknown property keys. To alter this behaviour, program line argument -``on-unknown-config-keys`` can be set to ``WARN`` or ``IGNORE``. Default is ``FAIL`` if unspecified. +``on-unknown-config-keys`` can be set to ``IGNORE``. Default is ``FAIL`` if unspecified. Defaults -------- diff --git a/docs/source/example-code/src/test/kotlin/net/corda/docs/ExampleConfigTest.kt b/docs/source/example-code/src/test/kotlin/net/corda/docs/ExampleConfigTest.kt index 4ad6af7303..b66fbc3cb5 100644 --- a/docs/source/example-code/src/test/kotlin/net/corda/docs/ExampleConfigTest.kt +++ b/docs/source/example-code/src/test/kotlin/net/corda/docs/ExampleConfigTest.kt @@ -3,6 +3,7 @@ package net.corda.docs import net.corda.core.internal.toPath import net.corda.node.services.config.ConfigHelper import net.corda.node.services.config.parseAsNodeConfiguration +import org.assertj.core.api.Assertions.assertThat import org.junit.Test import java.nio.file.Path import java.nio.file.Paths @@ -27,14 +28,9 @@ class ExampleConfigTest { @Test fun `example node_confs parses fine`() { - readAndCheckConfigurations( - "example-node.conf" - ) { + readAndCheckConfigurations("example-node.conf") { val baseDirectory = Paths.get("some-example-base-dir") - ConfigHelper.loadConfig( - baseDirectory = baseDirectory, - configFile = it - ).parseAsNodeConfiguration() + assertThat(ConfigHelper.loadConfig(baseDirectory = baseDirectory, configFile = it).parseAsNodeConfiguration().isValid).isTrue() } } } \ No newline at end of file diff --git a/docs/source/running-a-node.rst b/docs/source/running-a-node.rst index 65941efd26..8366dd5ca6 100644 --- a/docs/source/running-a-node.rst +++ b/docs/source/running-a-node.rst @@ -51,7 +51,7 @@ The node can optionally be started with the following command-line options: * ``--config-file``, ``-f``: The path to the config file. Defaults to ``node.conf``. * ``--dev-mode``, ``-d``: Runs the node in development mode. Unsafe in production. Defaults to true on MacOS and desktop versions of Windows. False otherwise. * ``--no-local-shell``, ``-n``: Do not start the embedded shell locally. -* ``--on-unknown-config-keys <[FAIL,WARN,INFO]>``: How to behave on unknown node configuration. Defaults to FAIL. +* ``--on-unknown-config-keys <[FAIL,INFO]>``: How to behave on unknown node configuration. Defaults to FAIL. * ``--sshd``: Enables SSH server for node administration. * ``--sshd-port``: Sets the port for the SSH server. If not supplied and SSH server is enabled, the port defaults to 2222. * ``--verbose``, ``--log-to-console``, ``-v``: If set, prints logging to the console as well as to a file. diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt index 8ba2cc6b88..52484d6d75 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/config/ConfigUtilities.kt @@ -224,6 +224,8 @@ private fun > enumBridge(clazz: Class, name: String): T { */ fun Any.toConfig(): Config = ConfigValueFactory.fromMap(toConfigMap()).toConfig() +fun Any.toConfigValue(): ConfigValue = if (this is ConfigValue) this else ConfigValueFactory.fromAnyRef(convertValue(this)) + @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 { @@ -255,6 +257,28 @@ private fun Any.toConfigMap(): Map { return values } +private fun convertValue(value: Any): Any { + + return if (value is String || value is Boolean || value is Number) { + // These types are supported by Config as use as is + value + } else if (value is Temporal || value is NetworkHostAndPort || value is CordaX500Name || value is Path || value is URL || value is UUID || value is X500Principal) { + // These types make sense to be represented as Strings and the exact inverse parsing function for use in parseAs + value.toString() + } else if (value is Enum<*>) { + // Expicitly use the Enum's name in case the toString is overridden, which would make parsing problematic. + value.name + } else if (value is Properties) { + // For Properties we treat keys with . as nested configs + ConfigFactory.parseMap(uncheckedCast(value)).root() + } else if (value is Iterable<*>) { + value.toConfigIterable() + } else { + // Else this is a custom object recursed over + value.toConfigMap() + } +} + // For Iterables figure out the type parameter and apply the same logic as above on the individual elements. private fun Iterable<*>.toConfigIterable(field: Field): Iterable { val elementType = (field.genericType as ParameterizedType).actualTypeArguments[0] as Class<*> @@ -282,6 +306,8 @@ private fun Iterable<*>.toConfigIterable(field: Field): Iterable { } } +private fun Iterable<*>.toConfigIterable(): Iterable = map { element -> element?.let(::convertValue) } + // The typesafe .getBoolean function is case sensitive, this is a case insensitive version fun Config.getBooleanCaseInsensitive(path: String): Boolean { try { @@ -300,7 +326,6 @@ private val logger = LoggerFactory.getLogger("net.corda.nodeapi.internal.config" enum class UnknownConfigKeysPolicy(private val handle: (Set, logger: Logger) -> Unit) { FAIL({ unknownKeys, _ -> throw UnknownConfigurationKeysException.of(unknownKeys) }), - WARN({ unknownKeys, logger -> logger.warn("Unknown configuration keys found: ${unknownKeys.joinToString(", ", "[", "]")}.") }), IGNORE({ _, _ -> }); fun handle(unknownKeys: Set, logger: Logger) { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt index d3320d46ee..388438948b 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt @@ -22,11 +22,18 @@ const val NODE_DATABASE_PREFIX = "node_" // This class forms part of the node config and so any changes to it must be handled with care data class DatabaseConfig( - val initialiseSchema: Boolean = true, - val transactionIsolationLevel: TransactionIsolationLevel = TransactionIsolationLevel.REPEATABLE_READ, - val exportHibernateJMXStatistics: Boolean = false, - val mappedSchemaCacheSize: Long = 100 -) + val initialiseSchema: Boolean = Defaults.initialiseSchema, + val transactionIsolationLevel: TransactionIsolationLevel = Defaults.transactionIsolationLevel, + val exportHibernateJMXStatistics: Boolean = Defaults.exportHibernateJMXStatistics, + val mappedSchemaCacheSize: Long = Defaults.mappedSchemaCacheSize +) { + object Defaults { + val initialiseSchema = true + val transactionIsolationLevel = TransactionIsolationLevel.REPEATABLE_READ + val exportHibernateJMXStatistics = false + val mappedSchemaCacheSize = 100L + } +} // This class forms part of the node config and so any changes to it must be handled with care enum class TransactionIsolationLevel { diff --git a/node/build.gradle b/node/build.gradle index 506d71a0dc..8528de21a4 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -73,6 +73,7 @@ dependencies { compile project(':tools:shell') compile project(':tools:cliutils') compile project(':common-validation') + compile project(':common-configuration-parsing') // Log4J: logging framework (with SLF4J bindings) compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}" diff --git a/node/src/main/kotlin/net/corda/node/NodeCmdLineOptions.kt b/node/src/main/kotlin/net/corda/node/NodeCmdLineOptions.kt index 320885b123..1141c20297 100644 --- a/node/src/main/kotlin/net/corda/node/NodeCmdLineOptions.kt +++ b/node/src/main/kotlin/net/corda/node/NodeCmdLineOptions.kt @@ -1,10 +1,17 @@ package net.corda.node import com.typesafe.config.Config +import com.typesafe.config.ConfigException import com.typesafe.config.ConfigFactory +import net.corda.common.configuration.parsing.internal.Configuration +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 import net.corda.core.internal.div +import net.corda.core.utilities.loggerFor import net.corda.node.services.config.ConfigHelper import net.corda.node.services.config.NodeConfiguration +import net.corda.node.services.config.Valid import net.corda.node.services.config.parseAsNodeConfiguration import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy import picocli.CommandLine.Option @@ -12,6 +19,9 @@ import java.nio.file.Path import java.nio.file.Paths open class SharedNodeCmdLineOptions { + private companion object { + private val logger by lazy { loggerFor() } + } @Option( names = ["-b", "--base-directory"], description = ["The node working directory where all the files are kept."] @@ -37,9 +47,19 @@ open class SharedNodeCmdLineOptions { ) var devMode: Boolean? = null - open fun parseConfiguration(configuration: Config): NodeConfiguration = configuration.parseAsNodeConfiguration(unknownConfigKeysPolicy::handle) + open fun parseConfiguration(configuration: Config): Valid { - open fun rawConfiguration(): Config = ConfigHelper.loadConfig(baseDirectory, configFile) + val option = Configuration.Validation.Options(strict = unknownConfigKeysPolicy == UnknownConfigKeysPolicy.FAIL) + return configuration.parseAsNodeConfiguration(option) + } + + open fun rawConfiguration(): Validated { + return try { + valid(ConfigHelper.loadConfig(baseDirectory, configFile)) + } catch (e: ConfigException) { + return invalid(e) + } + } fun copyFrom(other: SharedNodeCmdLineOptions) { baseDirectory = other.baseDirectory @@ -47,11 +67,32 @@ open class SharedNodeCmdLineOptions { unknownConfigKeysPolicy= other.unknownConfigKeysPolicy devMode = other.devMode } + + fun logRawConfigurationErrors(errors: Set) { + if (errors.isNotEmpty()) { + logger.error("There were error(s) while attempting to load the node configuration:") + } + errors.forEach { error -> + when (error) { + is ConfigException.IO -> logger.error(configFileNotFoundMessage(configFile)) + else -> logger.error(error.message, error) + } + } + } + + private fun configFileNotFoundMessage(configFile: Path): String { + return """ + Unable to load the node config file from '$configFile'. + + Try setting the --base-directory flag to change which directory the node + is looking in, or use the --config-file flag to specify it explicitly. + """.trimIndent() + } } class InitialRegistrationCmdLineOptions : SharedNodeCmdLineOptions() { - override fun parseConfiguration(configuration: Config): NodeConfiguration { - return super.parseConfiguration(configuration).also { config -> + override fun parseConfiguration(configuration: Config): Valid { + return super.parseConfiguration(configuration).doIfValid { config -> require(!config.devMode) { "Registration cannot occur in development mode" } require(config.compatibilityZoneURL != null || config.networkServices != null) { "compatibilityZoneURL or networkServices must be present in the node configuration file in registration mode." @@ -121,8 +162,8 @@ open class NodeCmdLineOptions : SharedNodeCmdLineOptions() { ) var networkRootTrustStorePassword: String? = null - override fun parseConfiguration(configuration: Config): NodeConfiguration { - return super.parseConfiguration(configuration).also { config -> + override fun parseConfiguration(configuration: Config): Valid { + return super.parseConfiguration(configuration).doIfValid { config -> if (isRegistration) { require(!config.devMode) { "Registration cannot occur in development mode" } require(config.compatibilityZoneURL != null || config.networkServices != null) { @@ -132,7 +173,7 @@ open class NodeCmdLineOptions : SharedNodeCmdLineOptions() { } } - override fun rawConfiguration(): Config { + override fun rawConfiguration(): Validated { val configOverrides = mutableMapOf() configOverrides += "noLocalShell" to noLocalShell if (sshdServer) { @@ -141,7 +182,11 @@ open class NodeCmdLineOptions : SharedNodeCmdLineOptions() { devMode?.let { configOverrides += "devMode" to it } - return ConfigHelper.loadConfig(baseDirectory, configFile, configOverrides = ConfigFactory.parseMap(configOverrides)) + return try { + valid(ConfigHelper.loadConfig(baseDirectory, configFile, configOverrides = ConfigFactory.parseMap(configOverrides))) + } catch (e: ConfigException) { + return invalid(e) + } } } diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index 628a4e2508..79595ac719 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -15,6 +15,7 @@ import net.corda.node.internal.Node.Companion.isInvalidJavaVersion import net.corda.node.internal.cordapp.MultipleCordappsForFlowException import net.corda.node.internal.subcommands.* import net.corda.node.internal.subcommands.ValidateConfigurationCli.Companion.logConfigurationErrors +import net.corda.node.internal.subcommands.ValidateConfigurationCli.Companion.logRawConfig import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.shouldStartLocalShell import net.corda.node.services.config.shouldStartSSHDaemon @@ -34,7 +35,6 @@ import java.net.InetAddress import java.nio.file.Path import java.time.DayOfWeek import java.time.ZonedDateTime -import java.util.* /** An interface that can be implemented to tell the node what to do once it's intitiated. */ interface RunAfterNodeInitialisation { @@ -139,7 +139,8 @@ open class NodeStartup : NodeStartupLogging { Node.printBasicNodeInfo(LOGS_CAN_BE_FOUND_IN_STRING, System.getProperty("log-path")) // Step 5. Load and validate node configuration. - val configuration = cmdLineOptions.nodeConfiguration().doOnErrors { errors -> logConfigurationErrors(errors, cmdLineOptions.configFile) }.optional ?: return ExitCodes.FAILURE + val rawConfig = cmdLineOptions.rawConfiguration().doOnErrors(cmdLineOptions::logRawConfigurationErrors).optional ?: return ExitCodes.FAILURE + val configuration = cmdLineOptions.parseConfiguration(rawConfig).doIfValid { logRawConfig(rawConfig) }.doOnErrors(::logConfigurationErrors).optional ?: return ExitCodes.FAILURE // Step 6. Configuring special serialisation requirements, i.e., bft-smart relies on Java serialization. attempt { banJavaSerialisation(configuration) }.doOnException { error -> error.logAsUnexpected("Exception while configuring serialisation") } as? Try.Success diff --git a/node/src/main/kotlin/net/corda/node/internal/subcommands/ValidateConfigurationCli.kt b/node/src/main/kotlin/net/corda/node/internal/subcommands/ValidateConfigurationCli.kt index 2cf2544fa8..1f1b9371a9 100644 --- a/node/src/main/kotlin/net/corda/node/internal/subcommands/ValidateConfigurationCli.kt +++ b/node/src/main/kotlin/net/corda/node/internal/subcommands/ValidateConfigurationCli.kt @@ -1,41 +1,32 @@ package net.corda.node.internal.subcommands import com.typesafe.config.Config -import com.typesafe.config.ConfigException import com.typesafe.config.ConfigRenderOptions import net.corda.cliutils.CliWrapperBase import net.corda.cliutils.ExitCodes -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 +import net.corda.common.configuration.parsing.internal.Configuration import net.corda.core.utilities.loggerFor import net.corda.node.SharedNodeCmdLineOptions import net.corda.node.internal.initLogging -import net.corda.node.services.config.NodeConfiguration -import picocli.CommandLine.* -import java.nio.file.Path +import net.corda.node.services.config.schema.v1.V1NodeConfigurationSpec +import net.corda.nodeapi.internal.config.toConfigValue +import picocli.CommandLine.Mixin internal class ValidateConfigurationCli : CliWrapperBase("validate-configuration", "Validate the configuration without starting the node.") { internal companion object { private val logger by lazy { loggerFor() } - internal fun logConfigurationErrors(errors: Iterable, configFile: Path) { - errors.forEach { error -> - when (error) { - is ConfigException.IO -> logger.error(configFileNotFoundMessage(configFile)) - else -> logger.error("Error while parsing node configuration.", error) - } - } + private val configRenderingOptions = ConfigRenderOptions.defaults().setFormatted(true).setComments(false).setOriginComments(false) + + internal fun logConfigurationErrors(errors: Iterable) { + logger.error(errors.joinToString(System.lineSeparator(), "Error(s) while parsing node configuration:${System.lineSeparator()}") { error -> "\t- ${error.description()}" }) } - private fun configFileNotFoundMessage(configFile: Path): String { - return """ - Unable to load the node config file from '$configFile'. - - Try setting the --base-directory flag to change which directory the node - is looking in, or use the --config-file flag to specify it explicitly. - """.trimIndent() + private fun Configuration.Validation.Error.description(): String { + return "for path: \"$pathAsString\": $message" } + + internal fun logRawConfig(config: Config) = logger.debug("Actual configuration:\n${V1NodeConfigurationSpec.describe(config, Any::toConfigValue).render(configRenderingOptions)}") } @Mixin @@ -44,41 +35,7 @@ internal class ValidateConfigurationCli : CliWrapperBase("validate-configuration override fun initLogging() = initLogging(cmdLineOptions.baseDirectory) override fun runProgram(): Int { - val configuration = cmdLineOptions.nodeConfiguration() - if (configuration.isInvalid) { - logConfigurationErrors(configuration.errors, cmdLineOptions.configFile) - return ExitCodes.FAILURE - } - return ExitCodes.SUCCESS + val rawConfig = cmdLineOptions.rawConfiguration().doOnErrors(cmdLineOptions::logRawConfigurationErrors).optional ?: return ExitCodes.FAILURE + return cmdLineOptions.parseConfiguration(rawConfig).doIfValid { logRawConfig(rawConfig) }.doOnErrors(::logConfigurationErrors).optional?.let { ExitCodes.SUCCESS } ?: ExitCodes.FAILURE } -} - -internal fun SharedNodeCmdLineOptions.nodeConfiguration(): Valid = NodeConfigurationParser.invoke(this) - -private object NodeConfigurationParser : (SharedNodeCmdLineOptions) -> Valid { - private val logger by lazy { loggerFor() } - - private val configRenderingOptions = ConfigRenderOptions.defaults().setComments(false).setOriginComments(false).setFormatted(true) - - override fun invoke(cmds: SharedNodeCmdLineOptions): Valid { - return attempt(cmds::rawConfiguration).doIfValid(::log).attemptMap(cmds::parseConfiguration).mapValid(::validate) - } - - internal fun log(config: Config) = logger.debug("Actual configuration:\n${config.root().render(configRenderingOptions)}") - - private fun validate(configuration: NodeConfiguration): Valid { - return Validated.withResult(configuration, configuration.validate().asSequence().map { error -> IllegalArgumentException(error) }.toSet()) - } - - private fun Valid.attemptMap(convert: (VALUE) -> MAPPED): Valid = mapValid { value -> attempt { convert.invoke(value) } } - - private fun attempt(action: () -> VALUE): Valid { - return try { - valid(action.invoke()) - } catch (exception: Exception) { - return invalid(exception) - } - } -} - -private typealias Valid = Validated \ No newline at end of file +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index b8f1cf001e..27fea92b99 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -1,24 +1,22 @@ package net.corda.node.services.config import com.typesafe.config.Config -import com.typesafe.config.ConfigException +import net.corda.common.configuration.parsing.internal.Configuration +import net.corda.common.validation.internal.Validated import net.corda.core.context.AuthServiceId import net.corda.core.identity.CordaX500Name import net.corda.core.internal.TimedFlow -import net.corda.core.internal.div import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.seconds import net.corda.node.services.config.rpc.NodeRpcOptions +import net.corda.node.services.config.schema.v1.V1NodeConfigurationSpec import net.corda.node.services.keys.cryptoservice.BCCryptoService import net.corda.node.services.keys.cryptoservice.SupportedCryptoServices -import net.corda.nodeapi.BrokerRpcSslOptions -import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES -import net.corda.nodeapi.internal.config.* +import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier +import net.corda.nodeapi.internal.config.MutualSslConfiguration +import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.cryptoservice.CryptoService import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.tools.shell.SSHDConfiguration -import org.slf4j.Logger import java.net.URL import java.nio.file.Path import java.time.Duration @@ -27,10 +25,6 @@ import javax.security.auth.x500.X500Principal val Int.MB: Long get() = this * 1024L * 1024L -private val DEFAULT_FLOW_MONITOR_PERIOD_MILLIS: Duration = Duration.ofMinutes(1) -private val DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS: Duration = Duration.ofMinutes(1) -private const val CORDAPPS_DIR_NAME_DEFAULT = "cordapps" - interface NodeConfiguration { val myLegalName: CordaX500Name val emailAddress: String @@ -91,23 +85,24 @@ interface NodeConfiguration { val cryptoServiceName: SupportedCryptoServices? val cryptoServiceConf: String? // Location for the cryptoService conf file. - fun validate(): List - companion object { // default to at least 8MB and a bit extra for larger heap sizes - val defaultTransactionCacheSize: Long = 8.MB + getAdditionalCacheMemory() + internal val defaultTransactionCacheSize: Long = 8.MB + getAdditionalCacheMemory() + + internal val DEFAULT_FLOW_MONITOR_PERIOD_MILLIS: Duration = Duration.ofMinutes(1) + internal val DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS: Duration = Duration.ofMinutes(1) // add 5% of any heapsize over 300MB to the default transaction cache size private fun getAdditionalCacheMemory(): Long { return Math.max((Runtime.getRuntime().maxMemory() - 300.MB) / 20, 0) } - val defaultAttachmentContentCacheSize: Long = 10.MB - const val defaultAttachmentCacheBound = 1024L + internal val defaultAttachmentContentCacheSize: Long = 10.MB + internal const val defaultAttachmentCacheBound = 1024L const val cordappDirectoriesKey = "cordappDirectories" - val defaultJmxReporterType = JmxReporterType.JOLOKIA + internal val defaultJmxReporterType = JmxReporterType.JOLOKIA } fun makeCryptoService(): CryptoService { @@ -128,7 +123,14 @@ enum class JmxReporterType { JOLOKIA, NEW_RELIC } -data class DevModeOptions(val disableCheckpointChecker: Boolean = false, val allowCompatibilityZone: Boolean = false) +data class DevModeOptions(val disableCheckpointChecker: Boolean = Defaults.disableCheckpointChecker, val allowCompatibilityZone: Boolean = Defaults.disableCheckpointChecker) { + + internal object Defaults { + + val disableCheckpointChecker = false + val allowCompatibilityZone = false + } +} fun NodeConfiguration.shouldCheckCheckpoints(): Boolean { return this.devMode && this.devModeOptions?.disableCheckpointChecker != true @@ -182,245 +184,9 @@ data class FlowTimeoutConfiguration( val backoffBase: Double ) -fun Config.parseAsNodeConfiguration(onUnknownKeys: ((Set, logger: Logger) -> Unit) = UnknownConfigKeysPolicy.FAIL::handle): NodeConfiguration = parseAs(onUnknownKeys) +internal typealias Valid = Validated -data class NodeConfigurationImpl( - /** This is not retrieved from the config file but rather from a command line argument. */ - override val baseDirectory: Path, - override val myLegalName: CordaX500Name, - override val jmxMonitoringHttpPort: Int? = null, - override val emailAddress: String, - private val keyStorePassword: String, - private val trustStorePassword: String, - override val crlCheckSoftFail: Boolean, - override val dataSourceProperties: Properties, - override val compatibilityZoneURL: URL? = null, - override var networkServices: NetworkServicesConfig? = null, - override val tlsCertCrlDistPoint: URL? = null, - override val tlsCertCrlIssuer: X500Principal? = null, - override val rpcUsers: List, - override val security: SecurityConfiguration? = null, - override val verifierType: VerifierType, - override val flowTimeout: FlowTimeoutConfiguration, - override val p2pAddress: NetworkHostAndPort, - override val additionalP2PAddresses: List = emptyList(), - private val rpcAddress: NetworkHostAndPort? = null, - private val rpcSettings: NodeRpcSettings, - override val messagingServerAddress: NetworkHostAndPort?, - override val messagingServerExternal: Boolean = (messagingServerAddress != null), - override val notary: NotaryConfig?, - @Suppress("DEPRECATION") - @Deprecated("Do not configure") - override val certificateChainCheckPolicies: List = emptyList(), - override val devMode: Boolean = false, - override val noLocalShell: Boolean = false, - override val devModeOptions: DevModeOptions? = null, - override val useTestClock: Boolean = false, - override val lazyBridgeStart: Boolean = true, - override val detectPublicIp: Boolean = true, - // TODO See TODO above. Rename this to nodeInfoPollingFrequency and make it of type Duration - override val additionalNodeInfoPollingFrequencyMsec: Long = 5.seconds.toMillis(), - override val sshd: SSHDConfiguration? = null, - override val database: DatabaseConfig = DatabaseConfig(initialiseSchema = devMode, exportHibernateJMXStatistics = devMode), - private val transactionCacheSizeMegaBytes: Int? = null, - private val attachmentContentCacheSizeMegaBytes: Int? = null, - override val attachmentCacheBound: Long = NodeConfiguration.defaultAttachmentCacheBound, - override val extraNetworkMapKeys: List = emptyList(), - // do not use or remove (breaks DemoBench together with rejection of unknown configuration keys during parsing) - private val h2port: Int? = null, - private val h2Settings: NodeH2Settings? = null, - // do not use or remove (used by Capsule) - private val jarDirs: List = emptyList(), - override val flowMonitorPeriodMillis: Duration = DEFAULT_FLOW_MONITOR_PERIOD_MILLIS, - override val flowMonitorSuspensionLoggingThresholdMillis: Duration = DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS, - override val cordappDirectories: List = listOf(baseDirectory / CORDAPPS_DIR_NAME_DEFAULT), - override val jmxReporterType: JmxReporterType? = JmxReporterType.JOLOKIA, - override val flowOverrides: FlowOverrideConfig?, - override val cordappSignerKeyFingerprintBlacklist: List = DEV_PUB_KEY_HASHES.map { it.toString() }, - override val cryptoServiceName: SupportedCryptoServices? = null, - override val cryptoServiceConf: String? = null -) : NodeConfiguration { - companion object { - private val logger = loggerFor() - // private val supportedCryptoServiceNames = setOf("BC", "UTIMACO", "GEMALTO-LUNA", "AZURE-KEY-VAULT") - } - - private val actualRpcSettings: NodeRpcSettings - - init { - actualRpcSettings = when { - rpcAddress != null -> { - require(rpcSettings.address == null) { "Can't provide top-level rpcAddress and rpcSettings.address (they control the same property)." } - logger.warn("Top-level declaration of property 'rpcAddress' is deprecated. Please use 'rpcSettings.address' instead.") - - rpcSettings.copy(address = rpcAddress) - } - else -> { - rpcSettings.address ?: throw ConfigException.Missing("rpcSettings.address") - rpcSettings - } - } - } - - override val certificatesDirectory = baseDirectory / "certificates" - - private val signingCertificateStorePath = certificatesDirectory / "nodekeystore.jks" - private val p2pKeystorePath: Path get() = certificatesDirectory / "sslkeystore.jks" - - // TODO: There are two implications here: - // 1. "signingCertificateStore" and "p2pKeyStore" have the same passwords. In the future we should re-visit this "rule" and see of they can be made different; - // 2. The passwords for store and for keys in this store are the same, this is due to limitations of Artemis. - override val signingCertificateStore = FileBasedCertificateStoreSupplier(signingCertificateStorePath, keyStorePassword, keyStorePassword) - private val p2pKeyStore = FileBasedCertificateStoreSupplier(p2pKeystorePath, keyStorePassword, keyStorePassword) - - private val p2pTrustStoreFilePath: Path get() = certificatesDirectory / "truststore.jks" - private val p2pTrustStore = FileBasedCertificateStoreSupplier(p2pTrustStoreFilePath, trustStorePassword, trustStorePassword) - override val p2pSslOptions: MutualSslConfiguration = SslConfiguration.mutual(p2pKeyStore, p2pTrustStore) - - override val rpcOptions: NodeRpcOptions - get() { - return actualRpcSettings.asOptions() - } - - private fun validateTlsCertCrlConfig(): List { - val errors = mutableListOf() - if (tlsCertCrlIssuer != null) { - if (tlsCertCrlDistPoint == null) { - errors += "tlsCertCrlDistPoint needs to be specified when tlsCertCrlIssuer is not NULL" - } - } - if (!crlCheckSoftFail && tlsCertCrlDistPoint == null) { - errors += "tlsCertCrlDistPoint needs to be specified when crlCheckSoftFail is FALSE" - } - return errors - } - - private fun validateCryptoService(): List { - val errors = mutableListOf() - if (cryptoServiceName == null && cryptoServiceConf != null) { - errors += "cryptoServiceName is null, but cryptoServiceConf is set to $cryptoServiceConf" - } - return errors - } - - override fun validate(): List { - val errors = mutableListOf() - errors += validateDevModeOptions() - val rpcSettingsErrors = validateRpcSettings(rpcSettings) - errors += rpcSettingsErrors - if (rpcSettingsErrors.isEmpty()) { - // Forces lazy property to initialise in order to throw exceptions - rpcOptions - } - errors += validateTlsCertCrlConfig() - errors += validateNetworkServices() - errors += validateH2Settings() - errors += validateCryptoService() - return errors - } - - private fun validateH2Settings(): List { - val errors = mutableListOf() - if (h2port != null && h2Settings != null) { - errors += "Cannot specify both 'h2port' and 'h2Settings' in configuration" - } - return errors - } - - private fun validateRpcSettings(options: NodeRpcSettings): List { - val errors = mutableListOf() - if (options.adminAddress == null) { - errors += "'rpcSettings.adminAddress': missing" - } - if (options.useSsl && options.ssl == null) { - errors += "'rpcSettings.ssl': missing (rpcSettings.useSsl was set to true)." - } - return errors - } - - private fun validateDevModeOptions(): List { - if (devMode) { - compatibilityZoneURL?.let { - if (devModeOptions?.allowCompatibilityZone != true) { - return listOf("'compatibilityZoneURL': present. Property cannot be set when 'devMode' is true unless devModeOptions.allowCompatibilityZone is also true") - } - } - - // if compatibiliZoneURL is set then it will be copied into the networkServices field and thus skipping - // this check by returning above is fine. - networkServices?.let { - if (devModeOptions?.allowCompatibilityZone != true) { - return listOf("'networkServices': present. Property cannot be set when 'devMode' is true unless devModeOptions.allowCompatibilityZone is also true") - } - } - } - return emptyList() - } - - private fun validateNetworkServices(): List { - val errors = mutableListOf() - - if (compatibilityZoneURL != null && networkServices != null && !(networkServices!!.inferred)) { - errors += "Cannot configure both compatibilityZoneUrl and networkServices simultaneously" - } - - return errors - } - - override val transactionCacheSizeBytes: Long - get() = transactionCacheSizeMegaBytes?.MB ?: super.transactionCacheSizeBytes - override val attachmentContentCacheSizeBytes: Long - get() = attachmentContentCacheSizeMegaBytes?.MB ?: super.attachmentContentCacheSizeBytes - - override val effectiveH2Settings: NodeH2Settings? - get() = when { - h2port != null -> NodeH2Settings(address = NetworkHostAndPort(host = "localhost", port = h2port)) - else -> h2Settings - } - - init { - // This is a sanity feature do not remove. - require(!useTestClock || devMode) { "Cannot use test clock outside of dev mode" } - require(devModeOptions == null || devMode) { "Cannot use devModeOptions outside of dev mode" } - require(security == null || rpcUsers.isEmpty()) { - "Cannot specify both 'rpcUsers' and 'security' in configuration" - } - @Suppress("DEPRECATION") - if (certificateChainCheckPolicies.isNotEmpty()) { - logger.warn("""You are configuring certificateChainCheckPolicies. This is a setting that is not used, and will be removed in a future version. - |Please contact the R3 team on the public slack to discuss your use case. - """.trimMargin()) - } - - // Support the deprecated method of configuring network services with a single compatibilityZoneURL option - if (compatibilityZoneURL != null && networkServices == null) { - networkServices = NetworkServicesConfig(compatibilityZoneURL, compatibilityZoneURL, inferred = true) - } - require(h2port == null || h2Settings == null) { "Cannot specify both 'h2port' and 'h2Settings' in configuration" } - } -} - -data class NodeRpcSettings( - val address: NetworkHostAndPort?, - val adminAddress: NetworkHostAndPort?, - val standAloneBroker: Boolean = false, - val useSsl: Boolean = false, - val ssl: BrokerRpcSslOptions? -) { - fun asOptions(): NodeRpcOptions { - return object : NodeRpcOptions { - override val address = this@NodeRpcSettings.address!! - override val adminAddress = this@NodeRpcSettings.adminAddress!! - override val standAloneBroker = this@NodeRpcSettings.standAloneBroker - override val useSsl = this@NodeRpcSettings.useSsl - override val sslConfig = this@NodeRpcSettings.ssl - - override fun toString(): String { - return "address: $address, adminAddress: $adminAddress, standAloneBroker: $standAloneBroker, useSsl: $useSsl, sslConfig: $sslConfig" - } - } - } -} +fun Config.parseAsNodeConfiguration(options: Configuration.Validation.Options = Configuration.Validation.Options(strict = true)): Valid = V1NodeConfigurationSpec.parse(this, options) data class NodeH2Settings( val address: NetworkHostAndPort? @@ -494,7 +260,7 @@ data class SecurityConfiguration(val authService: SecurityConfiguration.AuthServ // Provider of users credentials and permissions data data class DataSource(val type: AuthDataSourceType, - val passwordEncryption: PasswordEncryption = PasswordEncryption.NONE, + val passwordEncryption: PasswordEncryption = Defaults.passwordEncryption, val connection: Properties? = null, val users: List? = null) { init { @@ -503,6 +269,10 @@ data class SecurityConfiguration(val authService: SecurityConfiguration.AuthServ AuthDataSourceType.DB -> require(users == null && connection != null) } } + + internal object Defaults { + val passwordEncryption = PasswordEncryption.NONE + } } companion object { diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt new file mode 100644 index 0000000000..54fda39e5a --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt @@ -0,0 +1,303 @@ +package net.corda.node.services.config + +import com.typesafe.config.ConfigException +import net.corda.core.identity.CordaX500Name +import net.corda.core.internal.div +import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.seconds +import net.corda.node.services.config.rpc.NodeRpcOptions +import net.corda.node.services.keys.cryptoservice.SupportedCryptoServices +import net.corda.nodeapi.BrokerRpcSslOptions +import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES +import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier +import net.corda.nodeapi.internal.config.MutualSslConfiguration +import net.corda.nodeapi.internal.config.SslConfiguration +import net.corda.nodeapi.internal.config.User +import net.corda.nodeapi.internal.persistence.DatabaseConfig +import net.corda.tools.shell.SSHDConfiguration +import java.net.URL +import java.nio.file.Path +import java.time.Duration +import java.util.* +import javax.security.auth.x500.X500Principal + +data class NodeConfigurationImpl( + /** This is not retrieved from the config file but rather from a command line argument. */ + override val baseDirectory: Path, + override val myLegalName: CordaX500Name, + override val jmxMonitoringHttpPort: Int? = Defaults.jmxMonitoringHttpPort, + override val emailAddress: String, + private val keyStorePassword: String, + private val trustStorePassword: String, + override val crlCheckSoftFail: Boolean, + override val dataSourceProperties: Properties, + override val compatibilityZoneURL: URL? = Defaults.compatibilityZoneURL, + override var networkServices: NetworkServicesConfig? = Defaults.networkServices, + override val tlsCertCrlDistPoint: URL? = Defaults.tlsCertCrlDistPoint, + override val tlsCertCrlIssuer: X500Principal? = Defaults.tlsCertCrlIssuer, + override val rpcUsers: List, + override val security: SecurityConfiguration? = Defaults.security, + override val verifierType: VerifierType, + override val flowTimeout: FlowTimeoutConfiguration, + override val p2pAddress: NetworkHostAndPort, + override val additionalP2PAddresses: List = Defaults.additionalP2PAddresses, + private val rpcAddress: NetworkHostAndPort? = Defaults.rpcAddress, + private val rpcSettings: NodeRpcSettings, + override val messagingServerAddress: NetworkHostAndPort?, + override val messagingServerExternal: Boolean = Defaults.messagingServerExternal(messagingServerAddress), + override val notary: NotaryConfig?, + @Suppress("DEPRECATION") + @Deprecated("Do not configure") + override val certificateChainCheckPolicies: List = Defaults.certificateChainCheckPolicies, + override val devMode: Boolean = Defaults.devMode, + override val noLocalShell: Boolean = Defaults.noLocalShell, + override val devModeOptions: DevModeOptions? = Defaults.devModeOptions, + override val useTestClock: Boolean = Defaults.useTestClock, + override val lazyBridgeStart: Boolean = Defaults.lazyBridgeStart, + override val detectPublicIp: Boolean = Defaults.detectPublicIp, + // TODO See TODO above. Rename this to nodeInfoPollingFrequency and make it of type Duration + override val additionalNodeInfoPollingFrequencyMsec: Long = Defaults.additionalNodeInfoPollingFrequencyMsec, + override val sshd: SSHDConfiguration? = Defaults.sshd, + override val database: DatabaseConfig = Defaults.database(devMode), + private val transactionCacheSizeMegaBytes: Int? = Defaults.transactionCacheSizeMegaBytes, + private val attachmentContentCacheSizeMegaBytes: Int? = Defaults.attachmentContentCacheSizeMegaBytes, + override val attachmentCacheBound: Long = Defaults.attachmentCacheBound, + override val extraNetworkMapKeys: List = Defaults.extraNetworkMapKeys, + // do not use or remove (breaks DemoBench together with rejection of unknown configuration keys during parsing) + private val h2port: Int? = Defaults.h2port, + private val h2Settings: NodeH2Settings? = Defaults.h2Settings, + // do not use or remove (used by Capsule) + private val jarDirs: List = Defaults.jarDirs, + override val flowMonitorPeriodMillis: Duration = Defaults.flowMonitorPeriodMillis, + override val flowMonitorSuspensionLoggingThresholdMillis: Duration = Defaults.flowMonitorSuspensionLoggingThresholdMillis, + override val cordappDirectories: List = Defaults.cordappsDirectories(baseDirectory), + override val jmxReporterType: JmxReporterType? = Defaults.jmxReporterType, + override val flowOverrides: FlowOverrideConfig?, + override val cordappSignerKeyFingerprintBlacklist: List = Defaults.cordappSignerKeyFingerprintBlacklist, + override val cryptoServiceName: SupportedCryptoServices? = null, + override val cryptoServiceConf: String? = null +) : NodeConfiguration { + internal object Defaults { + val jmxMonitoringHttpPort: Int? = null + val compatibilityZoneURL: URL? = null + val networkServices: NetworkServicesConfig? = null + val tlsCertCrlDistPoint: URL? = null + val tlsCertCrlIssuer: X500Principal? = null + val security: SecurityConfiguration? = null + val additionalP2PAddresses: List = emptyList() + val rpcAddress: NetworkHostAndPort? = null + @Suppress("DEPRECATION") + val certificateChainCheckPolicies: List = emptyList() + const val devMode: Boolean = false + const val noLocalShell: Boolean = false + val devModeOptions: DevModeOptions? = null + const val useTestClock: Boolean = false + const val lazyBridgeStart: Boolean = true + const val detectPublicIp: Boolean = true + val additionalNodeInfoPollingFrequencyMsec: Long = 5.seconds.toMillis() + val sshd: SSHDConfiguration? = null + val transactionCacheSizeMegaBytes: Int? = null + val attachmentContentCacheSizeMegaBytes: Int? = null + const val attachmentCacheBound: Long = NodeConfiguration.defaultAttachmentCacheBound + val extraNetworkMapKeys: List = emptyList() + val h2port: Int? = null + val h2Settings: NodeH2Settings? = null + val jarDirs: List = emptyList() + val flowMonitorPeriodMillis: Duration = NodeConfiguration.DEFAULT_FLOW_MONITOR_PERIOD_MILLIS + val flowMonitorSuspensionLoggingThresholdMillis: Duration = NodeConfiguration.DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS + val jmxReporterType: JmxReporterType = NodeConfiguration.defaultJmxReporterType + val cordappSignerKeyFingerprintBlacklist: List = DEV_PUB_KEY_HASHES.map { it.toString() } + + fun cordappsDirectories(baseDirectory: Path) = listOf(baseDirectory / CORDAPPS_DIR_NAME_DEFAULT) + + fun messagingServerExternal(messagingServerAddress: NetworkHostAndPort?) = messagingServerAddress != null + + fun database(devMode: Boolean) = DatabaseConfig(initialiseSchema = devMode, exportHibernateJMXStatistics = devMode) + } + + companion object { + private const val CORDAPPS_DIR_NAME_DEFAULT = "cordapps" + + private val logger = loggerFor() + } + + private val actualRpcSettings: NodeRpcSettings + + init { + actualRpcSettings = when { + rpcAddress != null -> { + require(rpcSettings.address == null) { "Can't provide top-level rpcAddress and rpcSettings.address (they control the same property)." } + logger.warn("Top-level declaration of property 'rpcAddress' is deprecated. Please use 'rpcSettings.address' instead.") + + rpcSettings.copy(address = rpcAddress) + } + else -> { + rpcSettings.address ?: throw ConfigException.Missing("rpcSettings.address") + rpcSettings + } + } + + // This is a sanity feature do not remove. + require(!useTestClock || devMode) { "Cannot use test clock outside of dev mode" } + require(devModeOptions == null || devMode) { "Cannot use devModeOptions outside of dev mode" } + require(security == null || rpcUsers.isEmpty()) { + "Cannot specify both 'rpcUsers' and 'security' in configuration" + } + @Suppress("DEPRECATION") + if (certificateChainCheckPolicies.isNotEmpty()) { + logger.warn("""You are configuring certificateChainCheckPolicies. This is a setting that is not used, and will be removed in a future version. + |Please contact the R3 team on the public Slack to discuss your use case. + """.trimMargin()) + } + + // Support the deprecated method of configuring network services with a single compatibilityZoneURL option + if (compatibilityZoneURL != null && networkServices == null) { + networkServices = NetworkServicesConfig(compatibilityZoneURL, compatibilityZoneURL, inferred = true) + } + require(h2port == null || h2Settings == null) { "Cannot specify both 'h2port' and 'h2Settings' in configuration" } + } + + override val certificatesDirectory = baseDirectory / "certificates" + + private val signingCertificateStorePath = certificatesDirectory / "nodekeystore.jks" + private val p2pKeystorePath: Path get() = certificatesDirectory / "sslkeystore.jks" + + // TODO: There are two implications here: + // 1. "signingCertificateStore" and "p2pKeyStore" have the same passwords. In the future we should re-visit this "rule" and see of they can be made different; + // 2. The passwords for store and for keys in this store are the same, this is due to limitations of Artemis. + override val signingCertificateStore = FileBasedCertificateStoreSupplier(signingCertificateStorePath, keyStorePassword, keyStorePassword) + private val p2pKeyStore = FileBasedCertificateStoreSupplier(p2pKeystorePath, keyStorePassword, keyStorePassword) + + private val p2pTrustStoreFilePath: Path get() = certificatesDirectory / "truststore.jks" + private val p2pTrustStore = FileBasedCertificateStoreSupplier(p2pTrustStoreFilePath, trustStorePassword, trustStorePassword) + override val p2pSslOptions: MutualSslConfiguration = SslConfiguration.mutual(p2pKeyStore, p2pTrustStore) + + override val rpcOptions: NodeRpcOptions + get() { + return actualRpcSettings.asOptions() + } + + override val transactionCacheSizeBytes: Long + get() = transactionCacheSizeMegaBytes?.MB ?: super.transactionCacheSizeBytes + override val attachmentContentCacheSizeBytes: Long + get() = attachmentContentCacheSizeMegaBytes?.MB ?: super.attachmentContentCacheSizeBytes + + override val effectiveH2Settings: NodeH2Settings? + get() = when { + h2port != null -> NodeH2Settings(address = NetworkHostAndPort(host = "localhost", port = h2port)) + else -> h2Settings + } + + fun validate(): List { + val errors = mutableListOf() + errors += validateDevModeOptions() + val rpcSettingsErrors = validateRpcSettings(rpcSettings) + errors += rpcSettingsErrors + if (rpcSettingsErrors.isEmpty()) { + // Forces lazy property to initialise in order to throw exceptions + rpcOptions + } + errors += validateTlsCertCrlConfig() + errors += validateNetworkServices() + errors += validateH2Settings() + errors += validateCryptoService() + return errors + } + + private fun validateTlsCertCrlConfig(): List { + val errors = mutableListOf() + if (tlsCertCrlIssuer != null) { + if (tlsCertCrlDistPoint == null) { + errors += "'tlsCertCrlDistPoint' is mandatory when 'tlsCertCrlIssuer' is specified" + } + } + if (!crlCheckSoftFail && tlsCertCrlDistPoint == null) { + errors += "'tlsCertCrlDistPoint' is mandatory when 'crlCheckSoftFail' is false" + } + return errors + } + + private fun validateH2Settings(): List { + val errors = mutableListOf() + if (h2port != null && h2Settings != null) { + errors += "cannot specify both 'h2port' and 'h2Settings'" + } + return errors + } + + private fun validateCryptoService(): List { + val errors = mutableListOf() + if (cryptoServiceName == null && cryptoServiceConf != null) { + errors += "'cryptoServiceName' is mandatory when 'cryptoServiceConf' is specified" + } + return errors + } + + private fun validateRpcSettings(options: NodeRpcSettings): List { + val errors = mutableListOf() + if (options.adminAddress == null) { + errors += "'rpcSettings.adminAddress' is mandatory" + } + if (options.useSsl && options.ssl == null) { + errors += "'rpcSettings.ssl' is mandatory when 'rpcSettings.useSsl' is specified" + } + return errors + } + + private fun validateDevModeOptions(): List { + if (devMode) { + compatibilityZoneURL?.let { + if (devModeOptions?.allowCompatibilityZone != true) { + return listOf("cannot specify 'compatibilityZoneURL' when 'devMode' is true, unless 'devModeOptions.allowCompatibilityZone' is also true") + } + } + + // if compatibilityZoneURL is set then it will be copied into the networkServices field and thus skipping + // this check by returning above is fine. + networkServices?.let { + if (devModeOptions?.allowCompatibilityZone != true) { + return listOf("cannot specify 'networkServices' when 'devMode' is true, unless 'devModeOptions.allowCompatibilityZone' is also true") + } + } + } + return emptyList() + } + + private fun validateNetworkServices(): List { + val errors = mutableListOf() + + if (compatibilityZoneURL != null && networkServices != null && !(networkServices!!.inferred)) { + errors += "cannot specify both 'compatibilityZoneUrl' and 'networkServices'" + } + + return errors + } +} + +data class NodeRpcSettings( + val address: NetworkHostAndPort?, + val adminAddress: NetworkHostAndPort?, + val standAloneBroker: Boolean = Defaults.standAloneBroker, + val useSsl: Boolean = Defaults.useSsl, + val ssl: BrokerRpcSslOptions? +) { + internal object Defaults { + val standAloneBroker = false + val useSsl = false + } + + fun asOptions(): NodeRpcOptions { + return object : NodeRpcOptions { + override val address = this@NodeRpcSettings.address!! + override val adminAddress = this@NodeRpcSettings.adminAddress!! + override val standAloneBroker = this@NodeRpcSettings.standAloneBroker + override val useSsl = this@NodeRpcSettings.useSsl + override val sslConfig = this@NodeRpcSettings.ssl + + override fun toString(): String { + return "address: $address, adminAddress: $adminAddress, standAloneBroker: $standAloneBroker, useSsl: $useSsl, sslConfig: $sslConfig" + } + } + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/config/schema/parsers/StandardConfigValueParsers.kt b/node/src/main/kotlin/net/corda/node/services/config/schema/parsers/StandardConfigValueParsers.kt new file mode 100644 index 0000000000..e2c3b2e367 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/config/schema/parsers/StandardConfigValueParsers.kt @@ -0,0 +1,51 @@ +package net.corda.node.services.config.schema.parsers + +import com.typesafe.config.Config +import com.typesafe.config.ConfigObject +import com.typesafe.config.ConfigUtil +import net.corda.common.configuration.parsing.internal.Configuration +import net.corda.common.validation.internal.Validated.Companion.invalid +import net.corda.common.validation.internal.Validated.Companion.valid +import net.corda.core.identity.CordaX500Name +import net.corda.core.utilities.NetworkHostAndPort +import net.corda.node.services.config.Valid +import java.net.MalformedURLException +import java.net.URL +import java.nio.file.InvalidPathException +import java.nio.file.Path +import java.nio.file.Paths +import java.util.* +import javax.security.auth.x500.X500Principal + +internal fun toProperties(rawValue: ConfigObject): Properties = rawValue.toConfig().toProperties() + +private fun Config.toProperties() = entrySet().associateByTo(Properties(), { ConfigUtil.splitPath(it.key).joinToString(".") }, { it.value.unwrapped().toString() }) + +internal fun toCordaX500Name(rawValue: String) = attempt { CordaX500Name.parse(rawValue) } + +internal fun toURL(rawValue: String) = attempt { URL(rawValue) } + +internal fun toUUID(rawValue: String) = attempt { UUID.fromString(rawValue) } + +internal fun toNetworkHostAndPort(rawValue: String) = attempt { NetworkHostAndPort.parse(rawValue) } + +internal fun toPrincipal(rawValue: String) = attempt { X500Principal(rawValue) } + +internal fun toPath(rawValue: String) = attempt { Paths.get(rawValue) } + +private inline fun attempt(action: () -> RESULT, message: (ERROR) -> String): Valid { + return try { + valid(action.invoke()) + } catch (e: Exception) { + when (e) { + is ERROR -> badValue(message.invoke(e)) + else -> throw e + } + } +} + +internal inline fun attempt(action: () -> RESULT) = attempt(action, { e: ERROR -> "value does not comply with ${RESULT::class.java.simpleName} specification (${e.message})" }) + +internal fun validValue(result: RESULT) = valid(result) + +internal fun badValue(message: String) = invalid(Configuration.Validation.Error.BadValue.of(message)) \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt b/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt new file mode 100644 index 0000000000..314f435b14 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt @@ -0,0 +1,236 @@ +@file:Suppress("DEPRECATION") + +package net.corda.node.services.config.schema.v1 + +import com.typesafe.config.Config +import com.typesafe.config.ConfigObject +import net.corda.common.configuration.parsing.internal.Configuration +import net.corda.common.configuration.parsing.internal.get +import net.corda.common.configuration.parsing.internal.listOrEmpty +import net.corda.common.configuration.parsing.internal.map +import net.corda.common.configuration.parsing.internal.mapValid +import net.corda.common.configuration.parsing.internal.nested +import net.corda.common.validation.internal.Validated.Companion.invalid +import net.corda.common.validation.internal.Validated.Companion.valid +import net.corda.core.context.AuthServiceId +import net.corda.node.services.config.AuthDataSourceType +import net.corda.node.services.config.CertChainPolicyConfig +import net.corda.node.services.config.CertChainPolicyType +import net.corda.node.services.config.DevModeOptions +import net.corda.node.services.config.FlowOverride +import net.corda.node.services.config.FlowOverrideConfig +import net.corda.node.services.config.FlowTimeoutConfiguration +import net.corda.node.services.config.NetworkServicesConfig +import net.corda.node.services.config.NodeH2Settings +import net.corda.node.services.config.NodeRpcSettings +import net.corda.node.services.config.NotaryConfig +import net.corda.node.services.config.PasswordEncryption +import net.corda.node.services.config.SecurityConfiguration +import net.corda.node.services.config.SecurityConfiguration.AuthService.Companion.defaultAuthServiceId +import net.corda.node.services.config.Valid +import net.corda.node.services.config.schema.parsers.attempt +import net.corda.node.services.config.schema.parsers.badValue +import net.corda.node.services.config.schema.parsers.toCordaX500Name +import net.corda.node.services.config.schema.parsers.toNetworkHostAndPort +import net.corda.node.services.config.schema.parsers.toPath +import net.corda.node.services.config.schema.parsers.toProperties +import net.corda.node.services.config.schema.parsers.toURL +import net.corda.node.services.config.schema.parsers.toUUID +import net.corda.node.services.config.schema.parsers.validValue +import net.corda.nodeapi.BrokerRpcSslOptions +import net.corda.nodeapi.internal.config.User +import net.corda.nodeapi.internal.persistence.DatabaseConfig +import net.corda.nodeapi.internal.persistence.TransactionIsolationLevel +import net.corda.tools.shell.SSHDConfiguration + +internal object UserSpec : Configuration.Specification("User") { + private val username by string().optional() + private val user by string().optional() + private val password by string(sensitive = true) + private val permissions by string().listOrEmpty() + + override fun parseValid(configuration: Config): Valid { + val username = configuration[username] ?: configuration[user] + return when (username) { + null -> invalid(Configuration.Validation.Error.MissingValue.forKey("username")) + else -> valid(User(username, configuration[password], configuration[permissions].toSet())) + } + } +} + +internal object SecurityConfigurationSpec : Configuration.Specification("SecurityConfiguration") { + private object AuthServiceSpec : Configuration.Specification("AuthService") { + private object DataSourceSpec : Configuration.Specification("DataSource") { + private val type by enum(AuthDataSourceType::class) + private val passwordEncryption by enum(PasswordEncryption::class).optional().withDefaultValue(SecurityConfiguration.AuthService.DataSource.Defaults.passwordEncryption) + private val connection by nestedObject(sensitive = true).map(::toProperties).optional() + private val users by nested(UserSpec).list().optional() + + override fun parseValid(configuration: Config): Valid { + val type = configuration[type] + val passwordEncryption = configuration[passwordEncryption] + val connection = configuration[connection] + val users = configuration[users] + + return when { + type == AuthDataSourceType.INMEMORY && (users == null || connection != null) -> badValue("\"INMEMORY\" data source type requires \"users\" and cannot specify \"connection\"") + type == AuthDataSourceType.DB && (users != null || connection == null) -> badValue("\"DB\" data source type requires \"connection\" and cannot specify \"users\"") + else -> valid(SecurityConfiguration.AuthService.DataSource(type, passwordEncryption, connection, users)) + } + } + } + + private object OptionsSpec : Configuration.Specification("Options") { + private object CacheSpec : Configuration.Specification("Cache") { + private val expireAfterSecs by long().mapValid { value -> if (value >= 0) validValue(value) else badValue("cannot be less than 0'") } + private val maxEntries by long().mapValid { value -> if (value >= 0) validValue(value) else badValue("cannot be less than 0'") } + + override fun parseValid(configuration: Config): Valid { + return valid(SecurityConfiguration.AuthService.Options.Cache(configuration[expireAfterSecs], configuration[maxEntries])) + } + } + + private val cache by nested(CacheSpec).optional() + + override fun parseValid(configuration: Config): Valid { + return valid(SecurityConfiguration.AuthService.Options(configuration[cache])) + } + } + + private val dataSource by nested(DataSourceSpec) + private val id by string().map(::AuthServiceId).optional() + val options by nested(OptionsSpec).optional() + + override fun parseValid(configuration: Config): Valid { + val dataSource = configuration[dataSource] + val id = configuration[id] ?: defaultAuthServiceId(dataSource.type) + val options = configuration[options] + return when { + dataSource.type == AuthDataSourceType.INMEMORY && options?.cache != null -> badValue("no cache supported for \"INMEMORY\" data provider") + else -> valid(SecurityConfiguration.AuthService(dataSource, id, options)) + } + } + } + + private val authService by nested(AuthServiceSpec) + + override fun parseValid(configuration: Config): Valid { + return valid(SecurityConfiguration(configuration[authService])) + } +} + +internal object DevModeOptionsSpec : Configuration.Specification("DevModeOptions") { + private val disableCheckpointChecker by boolean().optional().withDefaultValue(DevModeOptions.Defaults.disableCheckpointChecker) + private val allowCompatibilityZone by boolean().optional().withDefaultValue(DevModeOptions.Defaults.allowCompatibilityZone) + + override fun parseValid(configuration: Config): Valid { + return valid(DevModeOptions(configuration[disableCheckpointChecker], configuration[allowCompatibilityZone])) + } +} + +internal object NetworkServicesConfigSpec : Configuration.Specification("NetworkServicesConfig") { + private val doormanURL by string().mapValid(::toURL) + private val networkMapURL by string().mapValid(::toURL) + private val pnm by string().mapValid(::toUUID).optional() + private val inferred by boolean().optional().withDefaultValue(false) + + override fun parseValid(configuration: Config): Valid { + return valid(NetworkServicesConfig(configuration[doormanURL], configuration[networkMapURL], configuration[pnm], configuration[inferred])) + } +} + +@Suppress("DEPRECATION") +internal object CertChainPolicyConfigSpec : Configuration.Specification("CertChainPolicyConfig") { + private val role by string() + private val policy by enum(CertChainPolicyType::class) + private val trustedAliases by string().listOrEmpty() + + override fun parseValid(configuration: Config): Valid { + return valid(CertChainPolicyConfig(configuration[role], configuration[policy], configuration[trustedAliases].toSet())) + } +} + +internal object FlowTimeoutConfigurationSpec : Configuration.Specification("FlowTimeoutConfiguration") { + private val timeout by duration() + private val maxRestartCount by int() + private val backoffBase by double() + + override fun parseValid(configuration: Config): Valid { + return valid(FlowTimeoutConfiguration(configuration[timeout], configuration[maxRestartCount], configuration[backoffBase])) + } +} + +internal object NotaryConfigSpec : Configuration.Specification("NotaryConfig") { + private val validating by boolean() + private val serviceLegalName by string().mapValid(::toCordaX500Name).optional() + private val className by string().optional().withDefaultValue("net.corda.node.services.transactions.SimpleNotaryService") + private val extraConfig by nestedObject().map(ConfigObject::toConfig).optional() + + override fun parseValid(configuration: Config): Valid { + return valid(NotaryConfig(configuration[validating], configuration[serviceLegalName], configuration[className], configuration[extraConfig])) + } +} + +internal object NodeRpcSettingsSpec : Configuration.Specification("NodeRpcSettings") { + internal object BrokerRpcSslOptionsSpec : Configuration.Specification("BrokerRpcSslOptions") { + private val keyStorePath by string().mapValid(::toPath) + private val keyStorePassword by string(sensitive = true) + + override fun parseValid(configuration: Config): Valid { + return valid(BrokerRpcSslOptions(configuration[keyStorePath], configuration[keyStorePassword])) + } + } + + private val address by string().mapValid(::toNetworkHostAndPort).optional() + private val adminAddress by string().mapValid(::toNetworkHostAndPort).optional() + private val standAloneBroker by boolean().optional().withDefaultValue(NodeRpcSettings.Defaults.standAloneBroker) + private val useSsl by boolean().optional().withDefaultValue(NodeRpcSettings.Defaults.useSsl) + private val ssl by nested(BrokerRpcSslOptionsSpec).optional() + + override fun parseValid(configuration: Config): Valid { + return valid(NodeRpcSettings(configuration[address], configuration[adminAddress], configuration[standAloneBroker], configuration[useSsl], configuration[ssl])) + } +} + +internal object SSHDConfigurationSpec : Configuration.Specification("SSHDConfiguration") { + private val port by int() + + override fun parseValid(configuration: Config): Valid = attempt { SSHDConfiguration(configuration[port]) } +} + + +internal object DatabaseConfigSpec : Configuration.Specification("DatabaseConfig") { + private val initialiseSchema by boolean().optional().withDefaultValue(DatabaseConfig.Defaults.initialiseSchema) + private val transactionIsolationLevel by enum(TransactionIsolationLevel::class).optional().withDefaultValue(DatabaseConfig.Defaults.transactionIsolationLevel) + private val exportHibernateJMXStatistics by boolean().optional().withDefaultValue(DatabaseConfig.Defaults.exportHibernateJMXStatistics) + private val mappedSchemaCacheSize by long().optional().withDefaultValue(DatabaseConfig.Defaults.mappedSchemaCacheSize) + + override fun parseValid(configuration: Config): Valid { + return valid(DatabaseConfig(configuration[initialiseSchema], configuration[transactionIsolationLevel], configuration[exportHibernateJMXStatistics], configuration[mappedSchemaCacheSize])) + } +} + +internal object NodeH2SettingsSpec : Configuration.Specification("NodeH2Settings") { + private val address by string().mapValid(::toNetworkHostAndPort).optional() + + override fun parseValid(configuration: Config): Valid { + return valid(NodeH2Settings(configuration[address])) + } +} + +internal object FlowOverridesConfigSpec : Configuration.Specification("FlowOverrideConfig") { + internal object SingleSpec : Configuration.Specification("FlowOverride") { + private val initiator by string() + private val responder by string() + + override fun parseValid(configuration: Config): Valid { + return valid(FlowOverride(configuration[initiator], configuration[responder])) + } + } + + private val overrides by nested(FlowOverridesConfigSpec.SingleSpec).listOrEmpty() + + override fun parseValid(configuration: Config): Valid { + return valid(FlowOverrideConfig(configuration[overrides])) + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/config/schema/v1/V1NodeConfigurationSpec.kt b/node/src/main/kotlin/net/corda/node/services/config/schema/v1/V1NodeConfigurationSpec.kt new file mode 100644 index 0000000000..dc51187a10 --- /dev/null +++ b/node/src/main/kotlin/net/corda/node/services/config/schema/v1/V1NodeConfigurationSpec.kt @@ -0,0 +1,149 @@ +package net.corda.node.services.config.schema.v1 + +import com.typesafe.config.Config +import com.typesafe.config.ConfigException +import net.corda.common.configuration.parsing.internal.Configuration +import net.corda.common.configuration.parsing.internal.get +import net.corda.common.configuration.parsing.internal.listOrEmpty +import net.corda.common.configuration.parsing.internal.map +import net.corda.common.configuration.parsing.internal.mapValid +import net.corda.common.configuration.parsing.internal.nested +import net.corda.common.configuration.parsing.internal.toValidationError +import net.corda.common.validation.internal.Validated.Companion.invalid +import net.corda.common.validation.internal.Validated.Companion.valid +import net.corda.node.services.config.JmxReporterType +import net.corda.node.services.config.NodeConfiguration +import net.corda.node.services.config.NodeConfigurationImpl +import net.corda.node.services.config.NodeConfigurationImpl.Defaults +import net.corda.node.services.config.Valid +import net.corda.node.services.config.VerifierType +import net.corda.node.services.config.schema.parsers.badValue +import net.corda.node.services.config.schema.parsers.toCordaX500Name +import net.corda.node.services.config.schema.parsers.toNetworkHostAndPort +import net.corda.node.services.config.schema.parsers.toPath +import net.corda.node.services.config.schema.parsers.toPrincipal +import net.corda.node.services.config.schema.parsers.toProperties +import net.corda.node.services.config.schema.parsers.toURL +import net.corda.node.services.config.schema.parsers.toUUID +import net.corda.node.services.keys.cryptoservice.SupportedCryptoServices + +internal object V1NodeConfigurationSpec : Configuration.Specification("NodeConfiguration") { + private val myLegalName by string().mapValid(::toCordaX500Name) + private val emailAddress by string() + private val jmxMonitoringHttpPort by int().optional() + private val dataSourceProperties by nestedObject(sensitive = true).map(::toProperties) + private val rpcUsers by nested(UserSpec).listOrEmpty() + private val security by nested(SecurityConfigurationSpec).optional() + private val devMode by boolean().optional().withDefaultValue(Defaults.devMode) + private val devModeOptions by nested(DevModeOptionsSpec).optional() + private val compatibilityZoneURL by string().mapValid(::toURL).optional() + private val networkServices by nested(NetworkServicesConfigSpec).optional() + private val certificateChainCheckPolicies by nested(CertChainPolicyConfigSpec).list().optional().withDefaultValue(Defaults.certificateChainCheckPolicies) + private val verifierType by enum(VerifierType::class) + private val flowTimeout by nested(FlowTimeoutConfigurationSpec) + private val notary by nested(NotaryConfigSpec).optional() + private val additionalNodeInfoPollingFrequencyMsec by long().optional().withDefaultValue(Defaults.additionalNodeInfoPollingFrequencyMsec) + private val p2pAddress by string().mapValid(::toNetworkHostAndPort) + private val additionalP2PAddresses by string().mapValid(::toNetworkHostAndPort).list().optional().withDefaultValue(Defaults.additionalP2PAddresses) + private val rpcSettings by nested(NodeRpcSettingsSpec) + private val messagingServerAddress by string().mapValid(::toNetworkHostAndPort).optional() + private val messagingServerExternal by boolean().optional() + private val useTestClock by boolean().optional().withDefaultValue(Defaults.useTestClock) + private val lazyBridgeStart by boolean().optional().withDefaultValue(Defaults.lazyBridgeStart) + private val detectPublicIp by boolean().optional().withDefaultValue(Defaults.detectPublicIp) + private val sshd by nested(SSHDConfigurationSpec).optional() + private val database by nested(DatabaseConfigSpec).optional() + private val noLocalShell by boolean().optional().withDefaultValue(Defaults.noLocalShell) + private val attachmentCacheBound by long().optional().withDefaultValue(Defaults.attachmentCacheBound) + private val extraNetworkMapKeys by string().mapValid(::toUUID).list().optional().withDefaultValue(Defaults.extraNetworkMapKeys) + private val tlsCertCrlDistPoint by string().mapValid(::toURL).optional() + private val tlsCertCrlIssuer by string().mapValid(::toPrincipal).optional() + private val h2Settings by nested(NodeH2SettingsSpec).optional() + private val flowMonitorPeriodMillis by duration().optional().withDefaultValue(Defaults.flowMonitorPeriodMillis) + private val flowMonitorSuspensionLoggingThresholdMillis by duration().optional().withDefaultValue(Defaults.flowMonitorSuspensionLoggingThresholdMillis) + private val crlCheckSoftFail by boolean() + private val jmxReporterType by enum(JmxReporterType::class).optional().withDefaultValue(Defaults.jmxReporterType) + private val baseDirectory by string().mapValid(::toPath) + private val flowOverrides by nested(FlowOverridesConfigSpec).optional() + private val keyStorePassword by string(sensitive = true) + private val trustStorePassword by string(sensitive = true) + private val rpcAddress by string().mapValid(::toNetworkHostAndPort).optional() + private val transactionCacheSizeMegaBytes by int().optional() + private val attachmentContentCacheSizeMegaBytes by int().optional() + private val h2port by int().optional() + private val jarDirs by string().list().optional().withDefaultValue(Defaults.jarDirs) + private val cordappDirectories by string().mapValid(::toPath).list().optional() + private val cordappSignerKeyFingerprintBlacklist by string().list().optional().withDefaultValue(Defaults.cordappSignerKeyFingerprintBlacklist) + private val cryptoServiceName by enum(SupportedCryptoServices::class).optional() + private val cryptoServiceConf by string().optional() + @Suppress("unused") + private val custom by nestedObject().optional() + + override fun parseValid(configuration: Config): Valid { + + val messagingServerExternal = configuration[messagingServerExternal] ?: Defaults.messagingServerExternal(configuration[messagingServerAddress]) + val database = configuration[database] ?: Defaults.database(configuration[devMode]) + val cordappDirectories = configuration[cordappDirectories] ?: Defaults.cordappsDirectories(configuration[baseDirectory]) + val result = try { + valid(NodeConfigurationImpl( + baseDirectory = configuration[baseDirectory], + myLegalName = configuration[myLegalName], + emailAddress = configuration[emailAddress], + p2pAddress = configuration[p2pAddress], + keyStorePassword = configuration[keyStorePassword], + trustStorePassword = configuration[trustStorePassword], + crlCheckSoftFail = configuration[crlCheckSoftFail], + dataSourceProperties = configuration[dataSourceProperties], + rpcUsers = configuration[rpcUsers], + verifierType = configuration[verifierType], + flowTimeout = configuration[flowTimeout], + rpcSettings = configuration[rpcSettings], + messagingServerAddress = configuration[messagingServerAddress], + notary = configuration[notary], + flowOverrides = configuration[flowOverrides], + additionalP2PAddresses = configuration[additionalP2PAddresses], + additionalNodeInfoPollingFrequencyMsec = configuration[additionalNodeInfoPollingFrequencyMsec], + jmxMonitoringHttpPort = configuration[jmxMonitoringHttpPort], + security = configuration[security], + devMode = configuration[devMode], + devModeOptions = configuration[devModeOptions], + compatibilityZoneURL = configuration[compatibilityZoneURL], + networkServices = configuration[networkServices], + certificateChainCheckPolicies = configuration[certificateChainCheckPolicies], + messagingServerExternal = messagingServerExternal, + useTestClock = configuration[useTestClock], + lazyBridgeStart = configuration[lazyBridgeStart], + detectPublicIp = configuration[detectPublicIp], + sshd = configuration[sshd], + database = database, + noLocalShell = configuration[noLocalShell], + attachmentCacheBound = configuration[attachmentCacheBound], + extraNetworkMapKeys = configuration[extraNetworkMapKeys], + tlsCertCrlDistPoint = configuration[tlsCertCrlDistPoint], + tlsCertCrlIssuer = configuration[tlsCertCrlIssuer], + h2Settings = configuration[h2Settings], + flowMonitorPeriodMillis = configuration[flowMonitorPeriodMillis], + flowMonitorSuspensionLoggingThresholdMillis = configuration[flowMonitorSuspensionLoggingThresholdMillis], + jmxReporterType = configuration[jmxReporterType], + rpcAddress = configuration[rpcAddress], + transactionCacheSizeMegaBytes = configuration[transactionCacheSizeMegaBytes], + attachmentContentCacheSizeMegaBytes = configuration[attachmentContentCacheSizeMegaBytes], + h2port = configuration[h2port], + jarDirs = configuration[jarDirs], + cordappDirectories = cordappDirectories, + cordappSignerKeyFingerprintBlacklist = configuration[cordappSignerKeyFingerprintBlacklist], + cryptoServiceName = configuration[cryptoServiceName], + cryptoServiceConf = configuration[cryptoServiceConf] + )) + } catch (e: Exception) { + return when (e) { + is ConfigException -> invalid(e.toValidationError(typeName = "NodeConfiguration")) + is IllegalArgumentException -> badValue(e.message!!) + else -> throw e + } + } + return result.mapValid { conf -> Valid.withResult(conf as NodeConfiguration, conf.validate().map(::toError).toSet()) } + } +} + +private fun toError(validationErrorMessage: String): Configuration.Validation.Error = Configuration.Validation.Error.BadValue.of(validationErrorMessage) \ No newline at end of file diff --git a/node/src/main/resources/net.corda.node.internal.NodeStartupCli.yml b/node/src/main/resources/net.corda.node.internal.NodeStartupCli.yml index ea59505694..53e924d8bc 100644 --- a/node/src/main/resources/net.corda.node.internal.NodeStartupCli.yml +++ b/node/src/main/resources/net.corda.node.internal.NodeStartupCli.yml @@ -72,7 +72,6 @@ multiParam: false acceptableValues: - "FAIL" - - "WARN" - "IGNORE" - parameterName: "--sshd" parameterType: "boolean" diff --git a/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt b/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt index 93e973df7d..3c24a25a65 100644 --- a/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt @@ -1,6 +1,7 @@ package net.corda.node.services.config import com.typesafe.config.* +import net.corda.common.configuration.parsing.internal.Configuration import net.corda.core.internal.toPath import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.seconds @@ -33,14 +34,16 @@ class NodeConfigurationImplTest { fun `can't have tlsCertCrlDistPoint null when tlsCertCrlIssuer is given`() { val configValidationResult = configTlsCertCrlOptions(null, "C=US, L=New York, OU=Corda, O=R3 HoldCo LLC, CN=Corda Root CA").validate() assertTrue { configValidationResult.isNotEmpty() } - assertThat(configValidationResult.first()).contains("tlsCertCrlDistPoint needs to be specified when tlsCertCrlIssuer is not NULL") + assertThat(configValidationResult.first()).contains("tlsCertCrlDistPoint") + assertThat(configValidationResult.first()).contains("tlsCertCrlIssuer") } @Test fun `can't have tlsCertCrlDistPoint null when crlCheckSoftFail is false`() { val configValidationResult = configTlsCertCrlOptions(null, null, false).validate() assertTrue { configValidationResult.isNotEmpty() } - assertThat(configValidationResult.first()).contains("tlsCertCrlDistPoint needs to be specified when crlCheckSoftFail is FALSE") + assertThat(configValidationResult.first()).contains("tlsCertCrlDistPoint") + assertThat(configValidationResult.first()).contains("crlCheckSoftFail") } @Test @@ -143,9 +146,9 @@ class NodeConfigurationImplTest { val missingPropertyPath = "rpcSettings.address" rawConfig = rawConfig.withoutPath(missingPropertyPath) - assertThatThrownBy { rawConfig.parseAsNodeConfiguration() }.isInstanceOfSatisfying(ConfigException.Missing::class.java) { exception -> - assertThat(exception.message).isNotNull() - assertThat(exception.message).contains(missingPropertyPath) + assertThat(rawConfig.parseAsNodeConfiguration().errors.single()).isInstanceOfSatisfying(Configuration.Validation.Error.MissingValue::class.java) { error -> + assertThat(error.message).contains(missingPropertyPath) + assertThat(error.typeName).isEqualTo(NodeConfiguration::class.java.simpleName) } } @@ -191,7 +194,10 @@ class NodeConfigurationImplTest { fun `fail on wrong cryptoServiceName`() { var rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false)) rawConfig = rawConfig.withValue("cryptoServiceName", ConfigValueFactory.fromAnyRef("UNSUPPORTED")) - assertThatThrownBy { rawConfig.parseAsNodeConfiguration() }.hasMessageStartingWith("UNSUPPORTED is not one of") + + val config = rawConfig.parseAsNodeConfiguration() + + assertThat(config.errors.asSequence().map(Configuration.Validation.Error::message).filter { it.contains("has no constant of the name 'UNSUPPORTED'") }.toList()).isNotEmpty } @Test @@ -200,7 +206,7 @@ class NodeConfigurationImplTest { rawConfig = rawConfig.withoutPath("rpcSettings.address") rawConfig = rawConfig.withValue("rpcAddress", ConfigValueFactory.fromAnyRef("localhost:4444")) - assertThatCode { rawConfig.parseAsNodeConfiguration() }.doesNotThrowAnyException() + assertThat(rawConfig.parseAsNodeConfiguration().isValid).isTrue() } @Test @@ -210,11 +216,11 @@ class NodeConfigurationImplTest { val config = rawConfig.parseAsNodeConfiguration() - assertThat(config.validate().filter { it.contains("rpcSettings.adminAddress") }).isNotEmpty + assertThat(config.errors.asSequence().map(Configuration.Validation.Error::message).filter { it.contains("rpcSettings.adminAddress") }.toList()).isNotEmpty } @Test - fun `compatiilityZoneURL populates NetworkServices`() { + fun `compatibilityZoneURL populates NetworkServices`() { val compatibilityZoneURL = URI.create("https://r3.com").toURL() val configuration = testConfiguration.copy( devMode = false, @@ -228,7 +234,7 @@ class NodeConfigurationImplTest { @Test fun `jmxReporterType is null and defaults to Jokolia`() { val rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true))) - val nodeConfig = rawConfig.parseAsNodeConfiguration() + val nodeConfig = rawConfig.parseAsNodeConfiguration().orThrow() assertTrue(JmxReporterType.JOLOKIA.toString() == nodeConfig.jmxReporterType.toString()) } @@ -236,7 +242,7 @@ class NodeConfigurationImplTest { fun `jmxReporterType is not null and is set to New Relic`() { var rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true))) rawConfig = rawConfig.withValue("jmxReporterType", ConfigValueFactory.fromAnyRef("NEW_RELIC")) - val nodeConfig = rawConfig.parseAsNodeConfiguration() + val nodeConfig = rawConfig.parseAsNodeConfiguration().orThrow() assertTrue(JmxReporterType.NEW_RELIC.toString() == nodeConfig.jmxReporterType.toString()) } @@ -244,15 +250,15 @@ class NodeConfigurationImplTest { fun `jmxReporterType is not null and set to Jokolia`() { var rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true))) rawConfig = rawConfig.withValue("jmxReporterType", ConfigValueFactory.fromAnyRef("JOLOKIA")) - val nodeConfig = rawConfig.parseAsNodeConfiguration() + val nodeConfig = rawConfig.parseAsNodeConfiguration().orThrow() assertTrue(JmxReporterType.JOLOKIA.toString() == nodeConfig.jmxReporterType.toString()) } - private fun configDebugOptions(devMode: Boolean, devModeOptions: DevModeOptions?): NodeConfiguration { + private fun configDebugOptions(devMode: Boolean, devModeOptions: DevModeOptions?): NodeConfigurationImpl { return testConfiguration.copy(devMode = devMode, devModeOptions = devModeOptions) } - private fun configTlsCertCrlOptions(tlsCertCrlDistPoint: URL?, tlsCertCrlIssuer: String?, crlCheckSoftFail: Boolean = true): NodeConfiguration { + private fun configTlsCertCrlOptions(tlsCertCrlDistPoint: URL?, tlsCertCrlIssuer: String?, crlCheckSoftFail: Boolean = true): NodeConfigurationImpl { return testConfiguration.copy(tlsCertCrlDistPoint = tlsCertCrlDistPoint, tlsCertCrlIssuer = tlsCertCrlIssuer?.let { X500Principal(it) }, crlCheckSoftFail = crlCheckSoftFail) } diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index 0564802e39..f111057302 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -133,7 +133,7 @@ class DriverDSLImpl( if (inMemoryDB && corda.dataSourceProperties.getProperty("dataSource.url").startsWith("jdbc:h2:")) { val jdbcUrl = "jdbc:h2:mem:persistence${inMemoryCounter.getAndIncrement()};DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;WRITE_DELAY=100" corda.dataSourceProperties.setProperty("dataSource.url", jdbcUrl) - NodeConfig(typesafe = typesafe + mapOf("dataSourceProperties" to mapOf("dataSource.url" to jdbcUrl)), corda = corda) + NodeConfig(typesafe = typesafe + mapOf("dataSourceProperties" to mapOf("dataSource.url" to jdbcUrl))) } else { this } @@ -693,11 +693,8 @@ class DriverDSLImpl( * Simple holder class to capture the node configuration both as the raw [Config] object and the parsed [NodeConfiguration]. * Keeping [Config] around is needed as the user may specify extra config options not specified in [NodeConfiguration]. */ - private class NodeConfig(val typesafe: Config, val corda: NodeConfiguration = typesafe.parseAsNodeConfiguration()) { - init { - val errors = corda.validate() - require(errors.isEmpty()) { "Invalid node configuration. Errors where:\n${errors.joinToString("\n")}" } - } + private class NodeConfig(val typesafe: Config) { + val corda: NodeConfiguration = typesafe.parseAsNodeConfiguration().orThrow() } companion object { diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt index 21988565aa..4da6895cbf 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/NodeBasedTest.kt @@ -116,12 +116,7 @@ abstract class NodeBasedTest(private val cordappPackages: List = emptyLi val specificConfig = config.withValue(NodeConfiguration.cordappDirectoriesKey, ConfigValueFactory.fromIterable(cordappDirectories.toSet())) - val parsedConfig = specificConfig.parseAsNodeConfiguration().also { nodeConfiguration -> - val errors = nodeConfiguration.validate() - if (errors.isNotEmpty()) { - throw IllegalStateException("Invalid node configuration. Errors where:${System.lineSeparator()}${errors.joinToString(System.lineSeparator())}") - } - } + val parsedConfig = specificConfig.parseAsNodeConfiguration().orThrow() defaultNetworkParameters.install(baseDirectory) val node = InProcessNode(parsedConfig, MOCK_VERSION_INFO.copy(platformVersion = platformVersion), flowManager = flowManager) diff --git a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt b/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt index e07f18331b..df5889e17c 100644 --- a/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt +++ b/tools/demobench/src/test/kotlin/net/corda/demobench/model/NodeConfigTest.kt @@ -39,7 +39,7 @@ class NodeConfigTest { .withFallback(ConfigFactory.parseResources("reference.conf")) .withFallback(ConfigFactory.parseMap(mapOf("devMode" to true))) .resolve() - val fullConfig = nodeConfig.parseAsNodeConfiguration() + val fullConfig = nodeConfig.parseAsNodeConfiguration().orThrow() // No custom configuration is created by default. assertFailsWith { nodeConfig.getConfig("custom") } diff --git a/tools/network-bootstrapper/src/main/kotlin/net/corda/bootstrapper/nodes/NodeBuilder.kt b/tools/network-bootstrapper/src/main/kotlin/net/corda/bootstrapper/nodes/NodeBuilder.kt index bf67ee336a..e9e850016f 100644 --- a/tools/network-bootstrapper/src/main/kotlin/net/corda/bootstrapper/nodes/NodeBuilder.kt +++ b/tools/network-bootstrapper/src/main/kotlin/net/corda/bootstrapper/nodes/NodeBuilder.kt @@ -5,6 +5,8 @@ import com.typesafe.config.Config import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigValueFactory import net.corda.bootstrapper.docker.DockerUtils +import net.corda.common.configuration.parsing.internal.Configuration +import net.corda.common.validation.internal.Validated import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.parseAsNodeConfiguration import org.slf4j.LoggerFactory @@ -30,14 +32,13 @@ open class NodeBuilder { .withBaseDirectory(nodeDir) .exec(BuildImageResultCallback()).awaitImageId() LOG.info("finished building docker image for: $nodeDir with id: $nodeImageId") - val config = nodeConfig.parseAsNodeConfigWithFallback(ConfigFactory.parseFile(copiedNode.configFile)) + val config = nodeConfig.parseAsNodeConfigWithFallback(ConfigFactory.parseFile(copiedNode.configFile)).orThrow() return copiedNode.builtNode(config, nodeImageId) } } - -fun Config.parseAsNodeConfigWithFallback(preCopyConfig: Config): NodeConfiguration { +fun Config.parseAsNodeConfigWithFallback(preCopyConfig: Config): Validated { val nodeConfig = this .withValue("baseDirectory", ConfigValueFactory.fromAnyRef("")) .withFallback(ConfigFactory.parseResources("reference.conf")) From bd336ed4a7d4f59914bd0990ba0e6b77c703026f Mon Sep 17 00:00:00 2001 From: Michele Sollecito Date: Thu, 8 Nov 2018 16:48:11 +0000 Subject: [PATCH 06/13] Fixed compilation for NodeConfigurationImplTest --- .../config/NodeConfigurationImplTest.kt | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt b/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt index 55da75e5b2..6a59a0b908 100644 --- a/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/config/NodeConfigurationImplTest.kt @@ -1,28 +1,33 @@ package net.corda.node.services.config -import com.typesafe.config.* -import net.corda.common.configuration.parsing.internal.Configuration +import com.typesafe.config.Config +import com.typesafe.config.ConfigFactory +import com.typesafe.config.ConfigParseOptions +import com.typesafe.config.ConfigValueFactory import com.zaxxer.hikari.HikariConfig +import net.corda.common.configuration.parsing.internal.Configuration import net.corda.core.internal.toPath import net.corda.core.utilities.NetworkHostAndPort -import net.corda.nodeapi.internal.persistence.CordaPersistence.DataSourceConfigTag import net.corda.core.utilities.seconds -import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy import net.corda.nodeapi.internal.config.getBooleanCaseInsensitive +import net.corda.nodeapi.internal.persistence.CordaPersistence.DataSourceConfigTag import net.corda.testing.core.ALICE_NAME import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.tools.shell.SSHDConfiguration -import org.assertj.core.api.Assertions.* +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Test import java.net.InetAddress -import java.net.URL import java.net.URI +import java.net.URL import java.nio.file.Paths -import javax.security.auth.x500.X500Principal import java.util.* -import kotlin.test.* +import javax.security.auth.x500.X500Principal +import kotlin.test.assertFalse +import kotlin.test.assertNull +import kotlin.test.assertTrue class NodeConfigurationImplTest { @Test @@ -173,7 +178,7 @@ class NodeConfigurationImplTest { @Test fun `mutual exclusion machineName set to default if not explicitly set`() { - val config = getConfig("test-config-mutualExclusion-noMachineName.conf").parseAsNodeConfiguration(UnknownConfigKeysPolicy.IGNORE::handle) + val config = getConfig("test-config-mutualExclusion-noMachineName.conf").parseAsNodeConfiguration(options = Configuration.Validation.Options(strict = false)).orThrow() assertEquals(InetAddress.getLocalHost().hostName, config.enterpriseConfiguration.mutualExclusionConfiguration.machineName) } From fe581f8cf1c719e9c62d26d7f8d9c2e22a36b19e Mon Sep 17 00:00:00 2001 From: Michele Sollecito Date: Thu, 8 Nov 2018 16:52:30 +0000 Subject: [PATCH 07/13] Working on merging changes to NodeConfigurationImpl. --- .../services/config/NodeConfigurationImpl.kt | 73 +++++++------------ 1 file changed, 26 insertions(+), 47 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt index 5d42d5f538..bbffe3ae18 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt @@ -14,6 +14,7 @@ import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier import net.corda.nodeapi.internal.config.MutualSslConfiguration import net.corda.nodeapi.internal.config.SslConfiguration import net.corda.nodeapi.internal.config.User +import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.tools.shell.SSHDConfiguration import java.net.URL @@ -154,6 +155,31 @@ data class NodeConfigurationImpl( require(security == null || rpcUsers.isEmpty()) { "Cannot specify both 'rpcUsers' and 'security' in configuration" } + + // ensure our datasource configuration is sane + require(dataSourceProperties.get("autoCommit") != true) { "Datbase auto commit cannot be enabled, Corda requires transactional behaviour" } + dataSourceProperties.set("autoCommit", false) + if (dataSourceProperties.get("transactionIsolation") == null) { + dataSourceProperties["transactionIsolation"] = database.transactionIsolationLevel.jdbcString + } + + // TODO sollecitom check if you need to add these to validation + // enforce that SQLServer does not get sent all strings as Unicode - hibernate handles this "cleverly" + val dataSourceUrl = dataSourceProperties.getProperty(CordaPersistence.DataSourceConfigTag.DATA_SOURCE_URL, "") + if (dataSourceUrl.contains(":sqlserver:") && !dataSourceUrl.contains("sendStringParametersAsUnicode", true)) { + dataSourceProperties[CordaPersistence.DataSourceConfigTag.DATA_SOURCE_URL] = "$dataSourceUrl;sendStringParametersAsUnicode=false" + } + + // Adjust connection pool size depending on N=flow thread pool size. + // If there is no configured pool size set it to N + 1, otherwise check that it's greater than N. + val flowThreadPoolSize = enterpriseConfiguration.tuning.flowThreadPoolSize + val maxConnectionPoolSize = dataSourceProperties.getProperty("maximumPoolSize") + if (maxConnectionPoolSize == null) { + dataSourceProperties.setProperty("maximumPoolSize", (flowThreadPoolSize + 1).toString()) + } else { + require(maxConnectionPoolSize.toInt() > flowThreadPoolSize) + } + @Suppress("DEPRECATION") if (certificateChainCheckPolicies.isNotEmpty()) { logger.warn("""You are configuring certificateChainCheckPolicies. This is a setting that is not used, and will be removed in a future version. @@ -283,53 +309,6 @@ data class NodeConfigurationImpl( h2port != null -> NodeH2Settings(address = NetworkHostAndPort(host = "localhost", port = h2port)) else -> h2Settings } - - // TODO sollecitom check these lines -// init { -// // This is a sanity feature do not remove. -// require(!useTestClock || devMode) { "Cannot use test clock outside of dev mode" } -// require(devModeOptions == null || devMode) { "Cannot use devModeOptions outside of dev mode" } -// require(security == null || rpcUsers.isEmpty()) { -// "Cannot specify both 'rpcUsers' and 'security' in configuration" -// } -// -// // ensure our datasource configuration is sane -// require(dataSourceProperties.get("autoCommit") != true) { "Datbase auto commit cannot be enabled, Corda requires transactional behaviour" } -// dataSourceProperties.set("autoCommit", false) -// if (dataSourceProperties.get("transactionIsolation") == null) { -// dataSourceProperties["transactionIsolation"] = database.transactionIsolationLevel.jdbcString -// } -// -// // enforce that SQLServer does not get sent all strings as Unicode - hibernate handles this "cleverly" -// val dataSourceUrl = dataSourceProperties.getProperty(DataSourceConfigTag.DATA_SOURCE_URL, "") -// if (dataSourceUrl.contains(":sqlserver:") && !dataSourceUrl.contains("sendStringParametersAsUnicode", true)) { -// dataSourceProperties[DataSourceConfigTag.DATA_SOURCE_URL] = dataSourceUrl + ";sendStringParametersAsUnicode=false" -// } -// -// // Adjust connection pool size depending on N=flow thread pool size. -// // If there is no configured pool size set it to N + 1, otherwise check that it's greater than N. -// val flowThreadPoolSize = enterpriseConfiguration.tuning.flowThreadPoolSize -// val maxConnectionPoolSize = dataSourceProperties.getProperty("maximumPoolSize") -// if (maxConnectionPoolSize == null) { -// dataSourceProperties.setProperty("maximumPoolSize", (flowThreadPoolSize + 1).toString()) -// } else { -// require(maxConnectionPoolSize.toInt() > flowThreadPoolSize) -// } -// -// // Check for usage of deprecated config -// @Suppress("DEPRECATION") -// if (certificateChainCheckPolicies.isNotEmpty()) { -// logger.warn("""You are configuring certificateChainCheckPolicies. This is a setting that is not used, and will be removed in a future version. -// |Please contact the R3 team on the public slack to discuss your use case. -// """.trimMargin()) -// } -// -// // Support the deprecated method of configuring network services with a single compatibilityZoneURL option -// if (compatibilityZoneURL != null && networkServices == null) { -// networkServices = NetworkServicesConfig(compatibilityZoneURL, compatibilityZoneURL, inferred = true) -// } -// require(h2port == null || h2Settings == null) { "Cannot specify both 'h2port' and 'h2Settings' in configuration" } -// } } data class NodeRpcSettings( From 1671f7da3101cdc8671ccdae4d0e029e6425bcab Mon Sep 17 00:00:00 2001 From: Michele Sollecito Date: Thu, 8 Nov 2018 17:00:03 +0000 Subject: [PATCH 08/13] Working on merging changes to NodeConfigurationImpl. --- .../services/config/NodeConfigurationImpl.kt | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt index bbffe3ae18..263062698f 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt @@ -84,12 +84,6 @@ data class NodeConfigurationImpl( override val cryptoServiceName: SupportedCryptoServices? = Defaults.cryptoServiceName, override val cryptoServiceConf: String? = Defaults.cryptoServiceConf ) : NodeConfiguration { - companion object { - private const val CORDAPPS_DIR_NAME_DEFAULT = "cordapps" - private val logger = loggerFor() - // private val supportedCryptoServiceNames = setOf("BC", "UTIMACO", "GEMALTO-LUNA", "AZURE-KEY-VAULT") - } - internal object Defaults { val jmxMonitoringHttpPort: Int? = null val compatibilityZoneURL: URL? = null @@ -133,6 +127,14 @@ data class NodeConfigurationImpl( fun database(devMode: Boolean) = DatabaseConfig(initialiseSchema = devMode, exportHibernateJMXStatistics = devMode) } + companion object { + private const val CORDAPPS_DIR_NAME_DEFAULT = "cordapps" + + private val logger = loggerFor() + + // private val supportedCryptoServiceNames = setOf("BC", "UTIMACO", "GEMALTO-LUNA", "AZURE-KEY-VAULT") + } + private val actualRpcSettings: NodeRpcSettings init { @@ -157,9 +159,9 @@ data class NodeConfigurationImpl( } // ensure our datasource configuration is sane - require(dataSourceProperties.get("autoCommit") != true) { "Datbase auto commit cannot be enabled, Corda requires transactional behaviour" } - dataSourceProperties.set("autoCommit", false) - if (dataSourceProperties.get("transactionIsolation") == null) { + require(dataSourceProperties["autoCommit"] != true) { "Datbase auto commit cannot be enabled, Corda requires transactional behaviour" } + dataSourceProperties["autoCommit"] = false + if (dataSourceProperties["transactionIsolation"] == null) { dataSourceProperties["transactionIsolation"] = database.transactionIsolationLevel.jdbcString } @@ -214,6 +216,17 @@ data class NodeConfigurationImpl( return actualRpcSettings.asOptions() } + override val transactionCacheSizeBytes: Long + get() = transactionCacheSizeMegaBytes?.MB ?: super.transactionCacheSizeBytes + override val attachmentContentCacheSizeBytes: Long + get() = attachmentContentCacheSizeMegaBytes?.MB ?: super.attachmentContentCacheSizeBytes + + override val effectiveH2Settings: NodeH2Settings? + get() = when { + h2port != null -> NodeH2Settings(address = NetworkHostAndPort(host = "localhost", port = h2port)) + else -> h2Settings + } + fun validate(): List { val errors = mutableListOf() errors += validateDevModeOptions() @@ -298,17 +311,6 @@ data class NodeConfigurationImpl( return errors } - - override val transactionCacheSizeBytes: Long - get() = transactionCacheSizeMegaBytes?.MB ?: super.transactionCacheSizeBytes - override val attachmentContentCacheSizeBytes: Long - get() = attachmentContentCacheSizeMegaBytes?.MB ?: super.attachmentContentCacheSizeBytes - - override val effectiveH2Settings: NodeH2Settings? - get() = when { - h2port != null -> NodeH2Settings(address = NetworkHostAndPort(host = "localhost", port = h2port)) - else -> h2Settings - } } data class NodeRpcSettings( From 92ab3ab025d2dc867e2988dc992ea5bd3155a5fa Mon Sep 17 00:00:00 2001 From: Michele Sollecito Date: Thu, 8 Nov 2018 17:17:26 +0000 Subject: [PATCH 09/13] Working on merging changes to NodeConfigurationImpl. --- .../node/services/config/NodeConfiguration.kt | 6 +++++- .../services/config/NodeConfigurationImpl.kt | 2 +- .../config/schema/v1/ConfigSections.kt | 19 ++++++++++++++++++- .../schema/v1/V1NodeConfigurationSpec.kt | 4 +++- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index f7d229e311..ae6be20d61 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -312,4 +312,8 @@ data class RelayConfiguration(val relayHost: String, val username: String, val privateKeyFile: Path, val publicKeyFile: Path, - val sshPort: Int = 22) + val sshPort: Int = Defaults.sshPort) { + internal object Defaults { + val sshPort = 22 + } +} diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt index 263062698f..cf2bd48ea0 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt @@ -165,7 +165,6 @@ data class NodeConfigurationImpl( dataSourceProperties["transactionIsolation"] = database.transactionIsolationLevel.jdbcString } - // TODO sollecitom check if you need to add these to validation // enforce that SQLServer does not get sent all strings as Unicode - hibernate handles this "cleverly" val dataSourceUrl = dataSourceProperties.getProperty(CordaPersistence.DataSourceConfigTag.DATA_SOURCE_URL, "") if (dataSourceUrl.contains(":sqlserver:") && !dataSourceUrl.contains("sendStringParametersAsUnicode", true)) { @@ -174,6 +173,7 @@ data class NodeConfigurationImpl( // Adjust connection pool size depending on N=flow thread pool size. // If there is no configured pool size set it to N + 1, otherwise check that it's greater than N. + // TODO sollecitom add these to validation val flowThreadPoolSize = enterpriseConfiguration.tuning.flowThreadPoolSize val maxConnectionPoolSize = dataSourceProperties.getProperty("maximumPoolSize") if (maxConnectionPoolSize == null) { diff --git a/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt b/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt index 314f435b14..8ad3fe4357 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt @@ -25,6 +25,7 @@ import net.corda.node.services.config.NodeH2Settings import net.corda.node.services.config.NodeRpcSettings import net.corda.node.services.config.NotaryConfig import net.corda.node.services.config.PasswordEncryption +import net.corda.node.services.config.RelayConfiguration import net.corda.node.services.config.SecurityConfiguration import net.corda.node.services.config.SecurityConfiguration.AuthService.Companion.defaultAuthServiceId import net.corda.node.services.config.Valid @@ -204,9 +205,12 @@ internal object DatabaseConfigSpec : Configuration.Specification private val transactionIsolationLevel by enum(TransactionIsolationLevel::class).optional().withDefaultValue(DatabaseConfig.Defaults.transactionIsolationLevel) private val exportHibernateJMXStatistics by boolean().optional().withDefaultValue(DatabaseConfig.Defaults.exportHibernateJMXStatistics) private val mappedSchemaCacheSize by long().optional().withDefaultValue(DatabaseConfig.Defaults.mappedSchemaCacheSize) + private val runMigration by boolean().optional().withDefaultValue(DatabaseConfig.Defaults.runMigration) + private val hibernateDialect by string().optional() + private val schema by string().optional() override fun parseValid(configuration: Config): Valid { - return valid(DatabaseConfig(configuration[initialiseSchema], configuration[transactionIsolationLevel], configuration[exportHibernateJMXStatistics], configuration[mappedSchemaCacheSize])) + return valid(DatabaseConfig(configuration[runMigration], configuration[initialiseSchema], configuration[transactionIsolationLevel], configuration[schema], configuration[exportHibernateJMXStatistics], configuration[hibernateDialect], configuration[mappedSchemaCacheSize])) } } @@ -233,4 +237,17 @@ internal object FlowOverridesConfigSpec : Configuration.Specification { return valid(FlowOverrideConfig(configuration[overrides])) } +} + +internal object RelayConfigurationSpec : Configuration.Specification("RelayConfiguration") { + private val relayHost by string() + private val remoteInboundPort by int() + private val username by string() + private val privateKeyFile by string().mapValid(::toPath) + private val publicKeyFile by string().mapValid(::toPath) + private val sshPort by int().optional().withDefaultValue(RelayConfiguration.Defaults.sshPort) + + override fun parseValid(configuration: Config): Valid { + return valid(RelayConfiguration(configuration[relayHost], configuration[remoteInboundPort], configuration[username], configuration[privateKeyFile], configuration[publicKeyFile], configuration[sshPort])) + } } \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/config/schema/v1/V1NodeConfigurationSpec.kt b/node/src/main/kotlin/net/corda/node/services/config/schema/v1/V1NodeConfigurationSpec.kt index dc51187a10..5f7c2ffbeb 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/schema/v1/V1NodeConfigurationSpec.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/schema/v1/V1NodeConfigurationSpec.kt @@ -78,6 +78,7 @@ internal object V1NodeConfigurationSpec : Configuration.Specification { @@ -133,7 +134,8 @@ internal object V1NodeConfigurationSpec : Configuration.Specification Date: Thu, 8 Nov 2018 17:18:44 +0000 Subject: [PATCH 10/13] Working on merging changes to NodeConfigurationImpl. --- .../services/config/schema/v1/V1NodeConfigurationSpec.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/node/src/main/kotlin/net/corda/node/services/config/schema/v1/V1NodeConfigurationSpec.kt b/node/src/main/kotlin/net/corda/node/services/config/schema/v1/V1NodeConfigurationSpec.kt index 5f7c2ffbeb..9e59514676 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/schema/v1/V1NodeConfigurationSpec.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/schema/v1/V1NodeConfigurationSpec.kt @@ -79,6 +79,8 @@ internal object V1NodeConfigurationSpec : Configuration.Specification { @@ -135,7 +137,9 @@ internal object V1NodeConfigurationSpec : Configuration.Specification Date: Thu, 8 Nov 2018 17:25:41 +0000 Subject: [PATCH 11/13] Working on merging changes to NodeConfigurationImpl. --- .../corda/node/services/config/NodeConfiguration.kt | 10 ++++++++-- .../node/services/config/schema/v1/ConfigSections.kt | 12 ++++++++++++ .../config/schema/v1/V1NodeConfigurationSpec.kt | 4 +++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index ae6be20d61..928953d6f8 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -141,9 +141,15 @@ data class DevModeOptions(val disableCheckpointChecker: Boolean = Defaults.disab data class GraphiteOptions( val server: String, val port: Int, - val prefix: String? = null, // defaults to org name and ip address when null + val prefix: String? = Defaults.prefix, // defaults to org name and ip address when null + // This typo leaking in the node.conf is brilliant + val sampleInvervallSeconds: Long = Defaults.sampleInvervallSeconds +) { + internal object Defaults { + val prefix: String? = null val sampleInvervallSeconds: Long = 60 -) + } +} fun NodeConfiguration.shouldCheckCheckpoints(): Boolean { return this.devMode && this.devModeOptions?.disableCheckpointChecker != true diff --git a/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt b/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt index 8ad3fe4357..5330f9a591 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt @@ -20,6 +20,7 @@ import net.corda.node.services.config.DevModeOptions import net.corda.node.services.config.FlowOverride import net.corda.node.services.config.FlowOverrideConfig import net.corda.node.services.config.FlowTimeoutConfiguration +import net.corda.node.services.config.GraphiteOptions import net.corda.node.services.config.NetworkServicesConfig import net.corda.node.services.config.NodeH2Settings import net.corda.node.services.config.NodeRpcSettings @@ -250,4 +251,15 @@ internal object RelayConfigurationSpec : Configuration.Specification { return valid(RelayConfiguration(configuration[relayHost], configuration[remoteInboundPort], configuration[username], configuration[privateKeyFile], configuration[publicKeyFile], configuration[sshPort])) } +} + +internal object GraphiteOptionsSpec : Configuration.Specification("GraphiteOptions") { + private val server by string() + private val port by int() + private val prefix by string().optional() + private val sampleInvervallSeconds by long().optional().withDefaultValue(GraphiteOptions.Defaults.sampleInvervallSeconds) + + override fun parseValid(configuration: Config): Valid { + return valid(GraphiteOptions(configuration[server], configuration[port], configuration[prefix], configuration[sampleInvervallSeconds])) + } } \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/config/schema/v1/V1NodeConfigurationSpec.kt b/node/src/main/kotlin/net/corda/node/services/config/schema/v1/V1NodeConfigurationSpec.kt index 9e59514676..a6be6ded9b 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/schema/v1/V1NodeConfigurationSpec.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/schema/v1/V1NodeConfigurationSpec.kt @@ -81,6 +81,7 @@ internal object V1NodeConfigurationSpec : Configuration.Specification { @@ -139,7 +140,8 @@ internal object V1NodeConfigurationSpec : Configuration.Specification Date: Thu, 8 Nov 2018 17:54:09 +0000 Subject: [PATCH 12/13] Working on merging changes to NodeConfigurationImpl. --- .../config/EnterpriseConfiguration.kt | 30 +++++++---- .../config/schema/v1/ConfigSections.kt | 52 +++++++++++++++++++ .../schema/v1/V1NodeConfigurationSpec.kt | 4 +- 3 files changed, 75 insertions(+), 11 deletions(-) diff --git a/node/src/main/kotlin/net/corda/node/services/config/EnterpriseConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/EnterpriseConfiguration.kt index b81d20e45f..3f1eff1cd1 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/EnterpriseConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/EnterpriseConfiguration.kt @@ -8,22 +8,32 @@ import net.corda.nodeapi.internal.config.ExternalBrokerConnectionConfiguration data class EnterpriseConfiguration( val mutualExclusionConfiguration: MutualExclusionConfiguration, - val externalBrokerConnectionConfiguration: ExternalBrokerConnectionConfiguration = ExternalBrokerConnectionConfiguration.DEFAULT, - val externalBrokerBackupAddresses: List = emptyList(), - val useMultiThreadedSMM: Boolean = true, - val tuning: PerformanceTuning = PerformanceTuning.default, + val externalBrokerConnectionConfiguration: ExternalBrokerConnectionConfiguration = Defaults.externalBrokerConnectionConfiguration, + val externalBrokerBackupAddresses: List = Defaults.externalBrokerBackupAddresses, + val useMultiThreadedSMM: Boolean = Defaults.useMultiThreadedSMM, + val tuning: PerformanceTuning = Defaults.tuning, val externalBridge: Boolean? = null, - val enableCacheTracing: Boolean = false, + val enableCacheTracing: Boolean = Defaults.enableCacheTracing, + val traceTargetDirectory: Path = Defaults.traceTargetDirectory +) { + internal object Defaults { + val externalBrokerConnectionConfiguration: ExternalBrokerConnectionConfiguration = ExternalBrokerConnectionConfiguration.DEFAULT + val externalBrokerBackupAddresses: List = emptyList() + val useMultiThreadedSMM: Boolean = true + val tuning: PerformanceTuning = PerformanceTuning.default + val enableCacheTracing: Boolean = false val traceTargetDirectory: Path = File(".").toPath() -) + } +} -data class MutualExclusionConfiguration(val on: Boolean = false, - val machineName: String = defaultMachineName, +data class MutualExclusionConfiguration(val on: Boolean = Defaults.on, + val machineName: String = Defaults.machineName, val updateInterval: Long, val waitInterval: Long ) { - companion object { - private val defaultMachineName = InetAddress.getLocalHost().hostName + internal object Defaults { + val machineName = InetAddress.getLocalHost().hostName + val on: Boolean = false } } diff --git a/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt b/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt index 5330f9a591..b26b34c3cf 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/schema/v1/ConfigSections.kt @@ -17,15 +17,18 @@ import net.corda.node.services.config.AuthDataSourceType import net.corda.node.services.config.CertChainPolicyConfig import net.corda.node.services.config.CertChainPolicyType import net.corda.node.services.config.DevModeOptions +import net.corda.node.services.config.EnterpriseConfiguration import net.corda.node.services.config.FlowOverride import net.corda.node.services.config.FlowOverrideConfig import net.corda.node.services.config.FlowTimeoutConfiguration import net.corda.node.services.config.GraphiteOptions +import net.corda.node.services.config.MutualExclusionConfiguration import net.corda.node.services.config.NetworkServicesConfig import net.corda.node.services.config.NodeH2Settings import net.corda.node.services.config.NodeRpcSettings import net.corda.node.services.config.NotaryConfig import net.corda.node.services.config.PasswordEncryption +import net.corda.node.services.config.PerformanceTuning import net.corda.node.services.config.RelayConfiguration import net.corda.node.services.config.SecurityConfiguration import net.corda.node.services.config.SecurityConfiguration.AuthService.Companion.defaultAuthServiceId @@ -40,6 +43,7 @@ import net.corda.node.services.config.schema.parsers.toURL import net.corda.node.services.config.schema.parsers.toUUID import net.corda.node.services.config.schema.parsers.validValue import net.corda.nodeapi.BrokerRpcSslOptions +import net.corda.nodeapi.internal.config.ExternalBrokerConnectionConfiguration import net.corda.nodeapi.internal.config.User import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.TransactionIsolationLevel @@ -257,9 +261,57 @@ internal object GraphiteOptionsSpec : Configuration.Specification { return valid(GraphiteOptions(configuration[server], configuration[port], configuration[prefix], configuration[sampleInvervallSeconds])) } +} + +internal object EnterpriseConfigurationSpec : Configuration.Specification("EnterpriseConfiguration") { + private val mutualExclusionConfiguration by nested(MutualExclusionConfigurationSpec) + private val externalBrokerConnectionConfiguration by enum(ExternalBrokerConnectionConfiguration::class).optional().withDefaultValue(EnterpriseConfiguration.Defaults.externalBrokerConnectionConfiguration) + private val externalBrokerBackupAddresses by string().mapValid(::toNetworkHostAndPort).list().optional().withDefaultValue(EnterpriseConfiguration.Defaults.externalBrokerBackupAddresses) + private val useMultiThreadedSMM by boolean().optional().withDefaultValue(EnterpriseConfiguration.Defaults.useMultiThreadedSMM) + private val tuning by nested(PerformanceTuningSpec).optional().withDefaultValue(EnterpriseConfiguration.Defaults.tuning) + private val externalBridge by boolean().optional() + private val enableCacheTracing by boolean().optional().withDefaultValue(EnterpriseConfiguration.Defaults.enableCacheTracing) + private val traceTargetDirectory by string().mapValid(::toPath).optional().withDefaultValue(EnterpriseConfiguration.Defaults.traceTargetDirectory) + + override fun parseValid(configuration: Config): Valid { + return valid(EnterpriseConfiguration( + configuration[mutualExclusionConfiguration], + configuration[externalBrokerConnectionConfiguration], + configuration[externalBrokerBackupAddresses], + configuration[useMultiThreadedSMM], + configuration[tuning], + configuration[externalBridge], + configuration[enableCacheTracing], + configuration[traceTargetDirectory]) + ) + } +} + +internal object MutualExclusionConfigurationSpec : Configuration.Specification("MutualExclusionConfiguration") { + private val on by boolean().optional().withDefaultValue(MutualExclusionConfiguration.Defaults.on) + private val machineName by string().optional().withDefaultValue(MutualExclusionConfiguration.Defaults.machineName) + private val updateInterval by long() + private val waitInterval by long() + + override fun parseValid(configuration: Config): Valid { + return valid(MutualExclusionConfiguration(configuration[on], configuration[machineName], configuration[updateInterval], configuration[waitInterval])) + } +} + +internal object PerformanceTuningSpec : Configuration.Specification("PerformanceTuning") { + private val flowThreadPoolSize by int() + private val maximumMessagingBatchSize by int() + private val rpcThreadPoolSize by int() + private val p2pConfirmationWindowSize by int() + private val brokerConnectionTtlCheckIntervalMs by long() + + override fun parseValid(configuration: Config): Valid { + return valid(PerformanceTuning(configuration[flowThreadPoolSize], configuration[maximumMessagingBatchSize], configuration[rpcThreadPoolSize], configuration[p2pConfirmationWindowSize], configuration[brokerConnectionTtlCheckIntervalMs])) + } } \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/services/config/schema/v1/V1NodeConfigurationSpec.kt b/node/src/main/kotlin/net/corda/node/services/config/schema/v1/V1NodeConfigurationSpec.kt index a6be6ded9b..7c0ed9313f 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/schema/v1/V1NodeConfigurationSpec.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/schema/v1/V1NodeConfigurationSpec.kt @@ -82,6 +82,7 @@ internal object V1NodeConfigurationSpec : Configuration.Specification { @@ -141,7 +142,8 @@ internal object V1NodeConfigurationSpec : Configuration.Specification Date: Thu, 8 Nov 2018 18:00:47 +0000 Subject: [PATCH 13/13] Added enterprise specific fields to V1NodeConfigurationSpec. --- .../net/corda/node/services/config/NodeConfigurationImpl.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt index cf2bd48ea0..38b6014364 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfigurationImpl.kt @@ -173,7 +173,6 @@ data class NodeConfigurationImpl( // Adjust connection pool size depending on N=flow thread pool size. // If there is no configured pool size set it to N + 1, otherwise check that it's greater than N. - // TODO sollecitom add these to validation val flowThreadPoolSize = enterpriseConfiguration.tuning.flowThreadPoolSize val maxConnectionPoolSize = dataSourceProperties.getProperty("maximumPoolSize") if (maxConnectionPoolSize == null) {