[CORDA-1993]: Replace reflection-based NodeConfiguration parsing with versioned property-based parsing mechanism. (#4132)

This commit is contained in:
Michele Sollecito 2018-11-08 15:56:00 +00:00 committed by GitHub
parent 9277042db8
commit 6c749889d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1161 additions and 513 deletions

View File

@ -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<TYPE> : Configuration.Property.Metadata, Configuration.Validator, Configuration.Value.Extractor<TYPE>, Configuration.Describer, Configuration.Value.Parser<TYPE> {
/**
* Validates target [Config] with default [Configuration.Validation.Options].
*/
fun validate(target: Config): Valid<Config> = validate(target, Configuration.Validation.Options.defaults)
override fun isSpecifiedBy(configuration: Config): Boolean = configuration.hasPath(key)
/**
@ -120,9 +126,9 @@ object Configuration {
interface Required<TYPE> : Definition<TYPE> {
/**
* 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<TYPE?>
fun optional(): Optional<TYPE>
}
/**
@ -136,6 +142,17 @@ object Configuration {
fun list(): Required<List<TYPE>>
}
/**
* Defines a property that might be missing, resulting in a null value.
*/
interface Optional<TYPE> : Definition<TYPE?> {
/**
* Allows to specify a [defaultValue], returning a required [Configuration.Property.Definition].
*/
fun withDefaultValue(defaultValue: TYPE): Definition<TYPE>
}
/**
* 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 : Enum<ENUM>> enum(key: String, enumClass: KClass<ENUM>, sensitive: Boolean = false): Standard<ENUM> = StandardProperty(key, enumClass.java.simpleName, { conf: Config, propertyKey: String -> conf.getEnum(enumClass.java, propertyKey) }, { conf: Config, propertyKey: String -> conf.getEnumList(enumClass.java, propertyKey) }, sensitive)
}
@ -246,6 +263,13 @@ object Configuration {
*/
val properties: Set<Property.Definition<*>>
/**
* Validates target [Config] with default [Configuration.Validation.Options].
*/
fun validate(target: Config): Valid<Config> = validate(target, Configuration.Validation.Options.defaults)
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue): ConfigValue
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<VALUE>(name: String?, private val prefix: String? = null) : Configuration.Schema, Configuration.Value.Parser<VALUE> {
abstract class Specification<VALUE>(override val name: String, private val prefix: String? = null) : Configuration.Schema, Configuration.Value.Parser<VALUE> {
private val mutableProperties = mutableSetOf<Property.Definition<*>>()
@ -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 : Enum<ENUM>> enum(key: String? = null, enumClass: KClass<ENUM>, sensitive: Boolean = false): PropertyDelegate.Standard<ENUM> = PropertyDelegate.enum(key, prefix, enumClass, sensitive) { mutableProperties.add(it) }
override val name: String? get() = schema.name
/**
* @see enum
*/
fun <ENUM : Enum<ENUM>> enum(enumClass: KClass<ENUM>, sensitive: Boolean = false): PropertyDelegate.Standard<ENUM> = 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<VALUE> = 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = emptyList()): Unknown = contextualize(keyName, containingPath).let { (key, path) -> Unknown(key, path) }
fun of(keyName: String = UNKNOWN, containingPath: List<String> = 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<String> = 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<Int?> {
interface Extractor : Configuration.Value.Parser<Int> {
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)
}
}
}

View File

@ -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<Long>(key, Long::class.javaObjectType.simpleName, Config::getLong, Config::getLongList, sensitive) {
override fun validate(target: Config, options: Configuration.Validation.Options?): Valid<Config> {
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> {
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<TYPE>(override val key: String, typeNameArg: String, private val extractSingleValue: (Config, String) -> TYPE, internal val extractListValue: (Config, String) -> List<TYPE>, override val isSensitive: Boolean = false, final override val schema: Configuration.Schema? = null) : Configuration.Property.Definition.Standard<TYPE> {
internal open class StandardProperty<TYPE : Any>(override val key: String, typeNameArg: String, private val extractSingleValue: (Config, String) -> TYPE, internal val extractListValue: (Config, String) -> List<TYPE>, override val isSensitive: Boolean = false, final override val schema: Configuration.Schema? = null) : Configuration.Property.Definition.Standard<TYPE> {
override fun valueIn(configuration: Config) = extractSingleValue.invoke(configuration, key)
@ -25,21 +29,21 @@ internal open class StandardProperty<TYPE>(override val key: String, typeNameArg
override fun <MAPPED : Any> mapValid(mappedTypeName: String, convert: (TYPE) -> Valid<MAPPED>): Configuration.Property.Definition.Standard<MAPPED> = FunctionalProperty(this, mappedTypeName, extractListValue, convert)
override fun optional(defaultValue: TYPE?): Configuration.Property.Definition<TYPE?> = OptionalProperty(this, defaultValue)
override fun optional(): Configuration.Property.Definition.Optional<TYPE> = OptionalDelegatedProperty(this)
override fun list(): Configuration.Property.Definition.Required<List<TYPE>> = 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<Config> {
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> {
val errors = mutableSetOf<Configuration.Validation.Error>()
errors += errorsWhenExtractingValue(target)
@ -47,7 +51,7 @@ internal open class StandardProperty<TYPE>(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<TYPE>(override val key: String, typeNameArg
override fun toString() = "\"$key\": \"$typeName\""
}
private class ListProperty<TYPE>(delegate: StandardProperty<TYPE>) : RequiredDelegatedProperty<List<TYPE>, StandardProperty<TYPE>>(delegate) {
private class ListProperty<TYPE : Any>(delegate: StandardProperty<TYPE>) : RequiredDelegatedProperty<List<TYPE>, StandardProperty<TYPE>>(delegate) {
override val typeName: String = "List<${delegate.typeName}>"
override fun valueIn(configuration: Config): List<TYPE> = delegate.extractListValue.invoke(configuration, key)
override fun validate(target: Config, options: Configuration.Validation.Options?): Valid<Config> {
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> {
val errors = mutableSetOf<Configuration.Validation.Error>()
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<Configuration.Validation.Error>()) { 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<String> {
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<TYPE>(delegate: Configuration.Property.Definition.Required<TYPE>, private val defaultValue: TYPE?) : DelegatedProperty<TYPE?, Configuration.Property.Definition.Required<TYPE>>(delegate) {
private class OptionalPropertyWithDefault<TYPE : Any>(delegate: Configuration.Property.Definition.Optional<TYPE>, private val defaultValue: TYPE) : DelegatedProperty<TYPE, Configuration.Property.Definition.Optional<TYPE>>(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<Config> {
val result = delegate.validate(target, options)
val error = result.errors.asSequence().filterIsInstance<Configuration.Validation.Error.MissingValue>().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<Config> = delegate.validate(target, options)
}
private class FunctionalProperty<TYPE, MAPPED : Any>(delegate: Configuration.Property.Definition.Standard<TYPE>, private val mappedTypeName: String, internal val extractListValue: (Config, String) -> List<TYPE>, private val convert: (TYPE) -> Valid<MAPPED>) : RequiredDelegatedProperty<MAPPED, Configuration.Property.Definition.Standard<TYPE>>(delegate), Configuration.Property.Definition.Standard<MAPPED> {
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<TYPE, MAPPED : Any>(delegate: Configuration.Pro
override fun list(): Configuration.Property.Definition.Required<List<MAPPED>> = FunctionalListProperty(this)
override fun validate(target: Config, options: Configuration.Validation.Options?): Valid<Config> {
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> {
val errors = mutableSetOf<Configuration.Validation.Error>()
errors += delegate.validate(target, options).errors
@ -131,7 +135,7 @@ private class FunctionalProperty<TYPE, MAPPED : Any>(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<RAW, TYPE : Any>(delegate: FunctionalProperty<RAW, TYPE>) : RequiredDelegatedProperty<List<TYPE>, FunctionalProperty<RAW, TYPE>>(delegate) {
@ -140,7 +144,7 @@ private class FunctionalListProperty<RAW, TYPE : Any>(delegate: FunctionalProper
override fun valueIn(configuration: Config): List<TYPE> = delegate.extractListValue.invoke(configuration, key).asSequence().map { configObject(key to ConfigValueFactory.fromAnyRef(it)) }.map(ConfigObject::toConfig).map(delegate::valueIn).toList()
override fun validate(target: Config, options: Configuration.Validation.Options?): Valid<Config> {
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> {
val list = try {
delegate.extractListValue.invoke(target, key)
@ -151,16 +155,24 @@ private class FunctionalListProperty<RAW, TYPE : Any>(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<Configuration.Validation.Error>()) { 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<String> {
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<TYPE, DELEGATE : Configuration.Property
final override fun toString() = "\"$key\": \"$typeName\""
}
private abstract class RequiredDelegatedProperty<TYPE, DELEGATE : Configuration.Property.Definition.Required<*>>(delegate: DELEGATE) : DelegatedProperty<TYPE, DELEGATE>(delegate), Configuration.Property.Definition.Required<TYPE> {
private class OptionalDelegatedProperty<TYPE : Any>(private val delegate: Configuration.Property.Definition<TYPE>) : Configuration.Property.Metadata by delegate, Configuration.Property.Definition.Optional<TYPE> {
final override fun optional(defaultValue: TYPE?): Configuration.Property.Definition<TYPE?> = 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<Config> {
val result = delegate.validate(target, options)
val errors = result.errors
val missingValueError = errors.asSequence().filterIsInstance<Configuration.Validation.Error.MissingValue>().filter { it.pathAsString == key }.singleOrNull()
return when {
missingValueError != null -> if (errors.size > 1) result else valid(target)
else -> result
}
}
override fun withDefaultValue(defaultValue: TYPE): Configuration.Property.Definition<TYPE> = OptionalPropertyWithDefault(this, defaultValue)
override fun toString() = "\"$key\": \"$typeName\""
}
private fun ConfigException.toValidationError(keyName: String, typeName: String): Configuration.Validation.Error {
private abstract class RequiredDelegatedProperty<TYPE : Any, DELEGATE : Configuration.Property.Definition.Required<*>>(delegate: DELEGATE) : DelegatedProperty<TYPE, DELEGATE>(delegate), Configuration.Property.Definition.Required<TYPE> {
final override fun optional(): Configuration.Property.Definition.Optional<TYPE> = 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) }
private fun isErrorExpected(error: ConfigException) = expectedExceptionTypes.any { expected -> expected.isInstance(error) }
private fun valueDescription(value: Any, serialiseValue: (Any) -> ConfigValue) = serialiseValue.invoke(value)

View File

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

View File

@ -14,7 +14,7 @@ interface PropertyDelegate<TYPE> {
operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty<Any?, Configuration.Property.Definition.Required<TYPE>>
fun optional(defaultValue: TYPE? = null): PropertyDelegate<TYPE?>
fun optional(): PropertyDelegate.Optional<TYPE>
}
interface Single<TYPE> {
@ -24,6 +24,13 @@ interface PropertyDelegate<TYPE> {
fun list(): Required<List<TYPE>>
}
interface Optional<TYPE> {
operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty<Any?, Configuration.Property.Definition.Optional<TYPE>>
fun withDefaultValue(defaultValue: TYPE): PropertyDelegate<TYPE>
}
interface Standard<TYPE> : Required<TYPE>, Single<TYPE> {
override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty<Any?, Configuration.Property.Definition.Standard<TYPE>>
@ -67,35 +74,52 @@ private class PropertyDelegateImpl<TYPE>(private val key: String?, private val p
}
}
override fun list(): PropertyDelegate.Required<List<TYPE>> = ListPropertyDelegateImpl(key, sensitive, addToProperties, { k, s -> construct.invoke(k, s).list() })
override fun list(): PropertyDelegate.Required<List<TYPE>> = ListPropertyDelegateImpl(key, prefix, sensitive, addToProperties, { k, s -> construct.invoke(k, s).list() })
override fun optional(defaultValue: TYPE?): PropertyDelegate<TYPE?> = OptionalPropertyDelegateImpl(key, sensitive, addToProperties, { k, s -> construct.invoke(k, s).optional(defaultValue) })
override fun optional(): PropertyDelegate.Optional<TYPE> = OptionalPropertyDelegateImpl(key, prefix, sensitive, addToProperties, { k, s -> construct.invoke(k, s).optional() })
override fun <MAPPED : Any> mapValid(mappedTypeName: String, convert: (TYPE) -> Valid<MAPPED>): PropertyDelegate.Standard<MAPPED> = PropertyDelegateImpl(key, prefix, sensitive, addToProperties, { k, s -> construct.invoke(k, s).mapValid(mappedTypeName) { value -> convert.invoke(value) } })
}
private class OptionalPropertyDelegateImpl<TYPE>(private val key: String?, private val sensitive: Boolean = false, private val addToProperties: (Configuration.Property.Definition<*>) -> Unit, private val construct: (String, Boolean) -> Configuration.Property.Definition<TYPE?>) : PropertyDelegate<TYPE?> {
private class OptionalPropertyDelegateImpl<TYPE>(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<TYPE>) : PropertyDelegate.Optional<TYPE> {
override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty<Any?, Configuration.Property.Definition<TYPE?>> {
override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty<Any?, Configuration.Property.Definition.Optional<TYPE>> {
val prop = construct.invoke(key ?: property.name, sensitive).also(addToProperties)
return object : ReadOnlyProperty<Any?, Configuration.Property.Definition<TYPE?>> {
val shortName = key ?: property.name
val prop = construct.invoke(prefix?.let { "$prefix.$shortName" } ?: shortName, sensitive).also(addToProperties)
return object : ReadOnlyProperty<Any?, Configuration.Property.Definition.Optional<TYPE>> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Configuration.Property.Definition<TYPE?> = prop
override fun getValue(thisRef: Any?, property: KProperty<*>): Configuration.Property.Definition.Optional<TYPE> = prop
}
}
override fun withDefaultValue(defaultValue: TYPE): PropertyDelegate<TYPE> = OptionalWithDefaultPropertyDelegateImpl(key, prefix, sensitive, addToProperties, { k, s -> construct.invoke(k, s).withDefaultValue(defaultValue) })
}
private class OptionalWithDefaultPropertyDelegateImpl<TYPE>(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<TYPE>) : PropertyDelegate<TYPE> {
override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty<Any?, Configuration.Property.Definition<TYPE>> {
val shortName = key ?: property.name
val prop = construct.invoke(prefix?.let { "$prefix.$shortName" } ?: shortName, sensitive).also(addToProperties)
return object : ReadOnlyProperty<Any?, Configuration.Property.Definition<TYPE>> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Configuration.Property.Definition<TYPE> = prop
}
}
}
private class ListPropertyDelegateImpl<TYPE>(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<TYPE>) : PropertyDelegate.Required<TYPE> {
private class ListPropertyDelegateImpl<TYPE>(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<TYPE>) : PropertyDelegate.Required<TYPE> {
override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty<Any?, Configuration.Property.Definition.Required<TYPE>> {
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<Any?, Configuration.Property.Definition.Required<TYPE>> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Configuration.Property.Definition.Required<TYPE> = prop
}
}
override fun optional(defaultValue: TYPE?): PropertyDelegate<TYPE?> = OptionalPropertyDelegateImpl(key, sensitive, addToProperties, { k, s -> construct.invoke(k, s).optional(defaultValue) })
override fun optional(): PropertyDelegate.Optional<TYPE> = OptionalPropertyDelegateImpl(key, prefix, sensitive, addToProperties, { k, s -> construct.invoke(k, s).optional() })
}

View File

@ -15,6 +15,10 @@ operator fun <TYPE> Config.get(property: Configuration.Property.Definition<TYPE>
inline fun <reified NESTED : Any> Configuration.Specification<*>.nested(specification: Configuration.Specification<NESTED>, key: String? = null, sensitive: Boolean = false): PropertyDelegate.Standard<NESTED> = nestedObject(schema = specification, key = key, sensitive = sensitive).map(ConfigObject::toConfig).mapValid { value -> specification.parse(value) }
fun <TYPE> Configuration.Property.Definition.Single<TYPE>.listOrEmpty(): Configuration.Property.Definition<List<TYPE>> = list().optional().withDefaultValue(emptyList())
fun <TYPE> PropertyDelegate.Single<TYPE>.listOrEmpty(): PropertyDelegate<List<TYPE>> = list().optional().withDefaultValue(emptyList())
@Suppress("UNCHECKED_CAST")
internal fun configObject(vararg entries: Pair<String, Any?>): ConfigObject {

View File

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

View File

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

View File

@ -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<Config, Configuration.Validation.Error, Configuration.Validation.Options> = 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<Config, Configuration.Validation.Error, Configuration.Validation.Options> = 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<Config, Configuration.Validation.Error, Configuration.Validation.Options> = 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<Config, Configuration.Validation.Error, Configuration.Validation.Options> = 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<Config, Configuration.Validation.Error, Configuration.Validation.Options> = 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<Config, Configuration.Validation.Error, Configuration.Validation.Options> = 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<Config, Configuration.Validation.Error, Configuration.Validation.Options> = 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<Config, Configuration.Validation.Error, Configuration.Validation.Options> = 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<Config, Configuration.Validation.Error, Configuration.Validation.Options> = 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<Config, Configuration.Validation.Error, Configuration.Validation.Options> = 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<Config, Configuration.Validation.Error, Configuration.Validation.Options> = 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<Config, Configuration.Validation.Error, Configuration.Validation.Options> = 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<Config, Configuration.Validation.Error, Configuration.Validation.Options> = 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<Config, Configuration.Validation.Error, Configuration.Validation.Options> = 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<Config, Configuration.Validation.Error, Configuration.Validation.Options> = 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<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.string(key).mapValid(::parseAddress)
val property = Configuration.Property.Definition.string(key).mapValid(::parseAddress)
val host = "localhost"
val port = 8080

View File

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

View File

@ -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<Int?> = { config -> versionExtractor.parse(config) }
private val versionExtractor = Configuration.Version.Extractor.fromPath("configuration.metadata.version")
private val extractVersion: (Config) -> Valid<Int> = { 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)
}

View File

@ -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<Int> = { 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<Int> = { 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<RpcSettings>, 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)

View File

@ -39,7 +39,7 @@ interface Validated<TARGET, ERROR> {
*
* @throws IllegalStateException or the result of [exceptionOnErrors] if there are errors.
*/
fun valueOrThrow(exceptionOnErrors: (Set<ERROR>) -> Exception = { errors -> IllegalStateException(errors.joinToString(System.lineSeparator())) }): TARGET
fun orThrow(exceptionOnErrors: (Set<ERROR>) -> 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<TARGET, ERROR> {
class Successful<TARGET, ERROR>(override val value: TARGET) : Result<TARGET, ERROR>(), Validated<TARGET, ERROR> {
override val errors: Set<ERROR> = emptySet<ERROR>()
override fun valueOrThrow(exceptionOnErrors: (Set<ERROR>) -> Exception) = value
override fun orThrow(exceptionOnErrors: (Set<ERROR>) -> Exception) = value
override fun <MAPPED> map(convert: (TARGET) -> MAPPED): Validated<MAPPED, ERROR> {
return valid(convert.invoke(value))
@ -138,7 +138,7 @@ interface Validated<TARGET, ERROR> {
override val value: TARGET get() = throw IllegalStateException("Invalid state.")
override fun valueOrThrow(exceptionOnErrors: (Set<ERROR>) -> Exception) = throw exceptionOnErrors.invoke(errors)
override fun orThrow(exceptionOnErrors: (Set<ERROR>) -> Exception) = throw exceptionOnErrors.invoke(errors)
override fun <MAPPED> map(convert: (TARGET) -> MAPPED): Validated<MAPPED, ERROR> {
return invalid(errors)

View File

@ -8,5 +8,5 @@ interface Validator<TARGET : Any, ERROR : Any, OPTIONS> {
/**
* 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<TARGET, ERROR>
fun validate(target: TARGET, options: OPTIONS): Validated<TARGET, ERROR>
}

View File

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

View File

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

View File

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

View File

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

View File

@ -224,6 +224,8 @@ private fun <T : Enum<T>> enumBridge(clazz: Class<T>, 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<String, Any> {
@ -255,6 +257,28 @@ private fun Any.toConfigMap(): Map<String, Any> {
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<Any?> {
val elementType = (field.genericType as ParameterizedType).actualTypeArguments[0] as Class<*>
@ -282,6 +306,8 @@ private fun Iterable<*>.toConfigIterable(field: Field): Iterable<Any?> {
}
}
private fun Iterable<*>.toConfigIterable(): Iterable<Any?> = 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<String>, 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<String>, logger: Logger) {

View File

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

View File

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

View File

@ -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<SharedNodeCmdLineOptions>() }
}
@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<NodeConfiguration> {
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<Config, ConfigException> {
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<ConfigException>) {
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<NodeConfiguration> {
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<NodeConfiguration> {
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<Config, ConfigException> {
val configOverrides = mutableMapOf<String, Any>()
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)
}
}
}

View File

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

View File

@ -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<ValidateConfigurationCli>() }
internal fun logConfigurationErrors(errors: Iterable<Exception>, 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<Configuration.Validation.Error>) {
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<NodeConfiguration> = NodeConfigurationParser.invoke(this)
private object NodeConfigurationParser : (SharedNodeCmdLineOptions) -> Valid<NodeConfiguration> {
private val logger by lazy { loggerFor<ValidateConfigurationCli>() }
private val configRenderingOptions = ConfigRenderOptions.defaults().setComments(false).setOriginComments(false).setFormatted(true)
override fun invoke(cmds: SharedNodeCmdLineOptions): Valid<NodeConfiguration> {
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<NodeConfiguration> {
return Validated.withResult(configuration, configuration.validate().asSequence().map { error -> IllegalArgumentException(error) }.toSet())
}
private fun <VALUE, MAPPED> Valid<VALUE>.attemptMap(convert: (VALUE) -> MAPPED): Valid<MAPPED> = mapValid { value -> attempt { convert.invoke(value) } }
private fun <VALUE> attempt(action: () -> VALUE): Valid<VALUE> {
return try {
valid(action.invoke())
} catch (exception: Exception) {
return invalid(exception)
}
}
}
private typealias Valid<TARGET> = Validated<TARGET, Exception>
}

View File

@ -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<String>
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<String>, logger: Logger) -> Unit) = UnknownConfigKeysPolicy.FAIL::handle): NodeConfiguration = parseAs<NodeConfigurationImpl>(onUnknownKeys)
internal typealias Valid<TARGET> = Validated<TARGET, Configuration.Validation.Error>
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<User>,
override val security: SecurityConfiguration? = null,
override val verifierType: VerifierType,
override val flowTimeout: FlowTimeoutConfiguration,
override val p2pAddress: NetworkHostAndPort,
override val additionalP2PAddresses: List<NetworkHostAndPort> = 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<CertChainPolicyConfig> = 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<UUID> = 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<String> = 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<Path> = listOf(baseDirectory / CORDAPPS_DIR_NAME_DEFAULT),
override val jmxReporterType: JmxReporterType? = JmxReporterType.JOLOKIA,
override val flowOverrides: FlowOverrideConfig?,
override val cordappSignerKeyFingerprintBlacklist: List<String> = DEV_PUB_KEY_HASHES.map { it.toString() },
override val cryptoServiceName: SupportedCryptoServices? = null,
override val cryptoServiceConf: String? = null
) : NodeConfiguration {
companion object {
private val logger = loggerFor<NodeConfigurationImpl>()
// 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<String> {
val errors = mutableListOf<String>()
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<String> {
val errors = mutableListOf<String>()
if (cryptoServiceName == null && cryptoServiceConf != null) {
errors += "cryptoServiceName is null, but cryptoServiceConf is set to $cryptoServiceConf"
}
return errors
}
override fun validate(): List<String> {
val errors = mutableListOf<String>()
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<String> {
val errors = mutableListOf<String>()
if (h2port != null && h2Settings != null) {
errors += "Cannot specify both 'h2port' and 'h2Settings' in configuration"
}
return errors
}
private fun validateRpcSettings(options: NodeRpcSettings): List<String> {
val errors = mutableListOf<String>()
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<String> {
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<String> {
val errors = mutableListOf<String>()
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<NodeConfiguration> = 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<User>? = 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 {

View File

@ -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<User>,
override val security: SecurityConfiguration? = Defaults.security,
override val verifierType: VerifierType,
override val flowTimeout: FlowTimeoutConfiguration,
override val p2pAddress: NetworkHostAndPort,
override val additionalP2PAddresses: List<NetworkHostAndPort> = 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<CertChainPolicyConfig> = 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<UUID> = 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<String> = Defaults.jarDirs,
override val flowMonitorPeriodMillis: Duration = Defaults.flowMonitorPeriodMillis,
override val flowMonitorSuspensionLoggingThresholdMillis: Duration = Defaults.flowMonitorSuspensionLoggingThresholdMillis,
override val cordappDirectories: List<Path> = Defaults.cordappsDirectories(baseDirectory),
override val jmxReporterType: JmxReporterType? = Defaults.jmxReporterType,
override val flowOverrides: FlowOverrideConfig?,
override val cordappSignerKeyFingerprintBlacklist: List<String> = 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<NetworkHostAndPort> = emptyList()
val rpcAddress: NetworkHostAndPort? = null
@Suppress("DEPRECATION")
val certificateChainCheckPolicies: List<CertChainPolicyConfig> = 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<UUID> = emptyList()
val h2port: Int? = null
val h2Settings: NodeH2Settings? = null
val jarDirs: List<String> = 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<String> = 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<NodeConfigurationImpl>()
}
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<String> {
val errors = mutableListOf<String>()
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<String> {
val errors = mutableListOf<String>()
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<String> {
val errors = mutableListOf<String>()
if (h2port != null && h2Settings != null) {
errors += "cannot specify both 'h2port' and 'h2Settings'"
}
return errors
}
private fun validateCryptoService(): List<String> {
val errors = mutableListOf<String>()
if (cryptoServiceName == null && cryptoServiceConf != null) {
errors += "'cryptoServiceName' is mandatory when 'cryptoServiceConf' is specified"
}
return errors
}
private fun validateRpcSettings(options: NodeRpcSettings): List<String> {
val errors = mutableListOf<String>()
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<String> {
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<String> {
val errors = mutableListOf<String>()
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"
}
}
}
}

View File

@ -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, IllegalArgumentException> { CordaX500Name.parse(rawValue) }
internal fun toURL(rawValue: String) = attempt<URL, MalformedURLException> { URL(rawValue) }
internal fun toUUID(rawValue: String) = attempt<UUID, IllegalArgumentException> { UUID.fromString(rawValue) }
internal fun toNetworkHostAndPort(rawValue: String) = attempt<NetworkHostAndPort, IllegalArgumentException> { NetworkHostAndPort.parse(rawValue) }
internal fun toPrincipal(rawValue: String) = attempt<X500Principal, IllegalArgumentException> { X500Principal(rawValue) }
internal fun toPath(rawValue: String) = attempt<Path, InvalidPathException> { Paths.get(rawValue) }
private inline fun <RESULT, reified ERROR : Exception> attempt(action: () -> RESULT, message: (ERROR) -> String): Valid<RESULT> {
return try {
valid(action.invoke())
} catch (e: Exception) {
when (e) {
is ERROR -> badValue(message.invoke(e))
else -> throw e
}
}
}
internal inline fun <reified RESULT, reified ERROR : Exception> attempt(action: () -> RESULT) = attempt(action, { e: ERROR -> "value does not comply with ${RESULT::class.java.simpleName} specification (${e.message})" })
internal fun <RESULT> validValue(result: RESULT) = valid<RESULT, Configuration.Validation.Error>(result)
internal fun <RESULT> badValue(message: String) = invalid<RESULT, Configuration.Validation.Error>(Configuration.Validation.Error.BadValue.of(message))

View File

@ -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>("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<User> {
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>("SecurityConfiguration") {
private object AuthServiceSpec : Configuration.Specification<SecurityConfiguration.AuthService>("AuthService") {
private object DataSourceSpec : Configuration.Specification<SecurityConfiguration.AuthService.DataSource>("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<SecurityConfiguration.AuthService.DataSource> {
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<SecurityConfiguration.AuthService.Options>("Options") {
private object CacheSpec : Configuration.Specification<SecurityConfiguration.AuthService.Options.Cache>("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<SecurityConfiguration.AuthService.Options.Cache> {
return valid(SecurityConfiguration.AuthService.Options.Cache(configuration[expireAfterSecs], configuration[maxEntries]))
}
}
private val cache by nested(CacheSpec).optional()
override fun parseValid(configuration: Config): Valid<SecurityConfiguration.AuthService.Options> {
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<SecurityConfiguration.AuthService> {
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<SecurityConfiguration> {
return valid(SecurityConfiguration(configuration[authService]))
}
}
internal object DevModeOptionsSpec : Configuration.Specification<DevModeOptions>("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<DevModeOptions> {
return valid(DevModeOptions(configuration[disableCheckpointChecker], configuration[allowCompatibilityZone]))
}
}
internal object NetworkServicesConfigSpec : Configuration.Specification<NetworkServicesConfig>("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<NetworkServicesConfig> {
return valid(NetworkServicesConfig(configuration[doormanURL], configuration[networkMapURL], configuration[pnm], configuration[inferred]))
}
}
@Suppress("DEPRECATION")
internal object CertChainPolicyConfigSpec : Configuration.Specification<CertChainPolicyConfig>("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<CertChainPolicyConfig> {
return valid(CertChainPolicyConfig(configuration[role], configuration[policy], configuration[trustedAliases].toSet()))
}
}
internal object FlowTimeoutConfigurationSpec : Configuration.Specification<FlowTimeoutConfiguration>("FlowTimeoutConfiguration") {
private val timeout by duration()
private val maxRestartCount by int()
private val backoffBase by double()
override fun parseValid(configuration: Config): Valid<FlowTimeoutConfiguration> {
return valid(FlowTimeoutConfiguration(configuration[timeout], configuration[maxRestartCount], configuration[backoffBase]))
}
}
internal object NotaryConfigSpec : Configuration.Specification<NotaryConfig>("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<NotaryConfig> {
return valid(NotaryConfig(configuration[validating], configuration[serviceLegalName], configuration[className], configuration[extraConfig]))
}
}
internal object NodeRpcSettingsSpec : Configuration.Specification<NodeRpcSettings>("NodeRpcSettings") {
internal object BrokerRpcSslOptionsSpec : Configuration.Specification<BrokerRpcSslOptions>("BrokerRpcSslOptions") {
private val keyStorePath by string().mapValid(::toPath)
private val keyStorePassword by string(sensitive = true)
override fun parseValid(configuration: Config): Valid<BrokerRpcSslOptions> {
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<NodeRpcSettings> {
return valid(NodeRpcSettings(configuration[address], configuration[adminAddress], configuration[standAloneBroker], configuration[useSsl], configuration[ssl]))
}
}
internal object SSHDConfigurationSpec : Configuration.Specification<SSHDConfiguration>("SSHDConfiguration") {
private val port by int()
override fun parseValid(configuration: Config): Valid<SSHDConfiguration> = attempt<SSHDConfiguration, IllegalArgumentException> { SSHDConfiguration(configuration[port]) }
}
internal object DatabaseConfigSpec : Configuration.Specification<DatabaseConfig>("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<DatabaseConfig> {
return valid(DatabaseConfig(configuration[initialiseSchema], configuration[transactionIsolationLevel], configuration[exportHibernateJMXStatistics], configuration[mappedSchemaCacheSize]))
}
}
internal object NodeH2SettingsSpec : Configuration.Specification<NodeH2Settings>("NodeH2Settings") {
private val address by string().mapValid(::toNetworkHostAndPort).optional()
override fun parseValid(configuration: Config): Valid<NodeH2Settings> {
return valid(NodeH2Settings(configuration[address]))
}
}
internal object FlowOverridesConfigSpec : Configuration.Specification<FlowOverrideConfig>("FlowOverrideConfig") {
internal object SingleSpec : Configuration.Specification<FlowOverride>("FlowOverride") {
private val initiator by string()
private val responder by string()
override fun parseValid(configuration: Config): Valid<FlowOverride> {
return valid(FlowOverride(configuration[initiator], configuration[responder]))
}
}
private val overrides by nested(FlowOverridesConfigSpec.SingleSpec).listOrEmpty()
override fun parseValid(configuration: Config): Valid<FlowOverrideConfig> {
return valid(FlowOverrideConfig(configuration[overrides]))
}
}

View File

@ -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>("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<NodeConfiguration> {
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, Configuration.Validation.Error>(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)

View File

@ -72,7 +72,6 @@
multiParam: false
acceptableValues:
- "FAIL"
- "WARN"
- "IGNORE"
- parameterName: "--sshd"
parameterType: "boolean"

View File

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

View File

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

View File

@ -116,12 +116,7 @@ abstract class NodeBasedTest(private val cordappPackages: List<String> = 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)

View File

@ -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<ConfigException.Missing> { nodeConfig.getConfig("custom") }

View File

@ -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<NodeConfiguration, Configuration.Validation.Error> {
val nodeConfig = this
.withValue("baseDirectory", ConfigValueFactory.fromAnyRef(""))
.withFallback(ConfigFactory.parseResources("reference.conf"))