mirror of
https://github.com/corda/corda.git
synced 2024-12-24 07:06:44 +00:00
[CORDA-1993]: Replace reflection-based NodeConfiguration parsing with versioned property-based parsing mechanism. (#4132)
This commit is contained in:
parent
9277042db8
commit
6c749889d0
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
@ -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 {
|
||||
|
@ -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() })
|
||||
}
|
@ -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 {
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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 ->
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
}
|
@ -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`.
|
||||
|
@ -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
|
||||
--------
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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}"
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
}
|
@ -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 {
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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))
|
@ -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]))
|
||||
}
|
||||
}
|
@ -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)
|
@ -72,7 +72,6 @@
|
||||
multiParam: false
|
||||
acceptableValues:
|
||||
- "FAIL"
|
||||
- "WARN"
|
||||
- "IGNORE"
|
||||
- parameterName: "--sshd"
|
||||
parameterType: "boolean"
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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") }
|
||||
|
@ -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"))
|
||||
|
Loading…
Reference in New Issue
Block a user