Merge pull request #1553 from corda/merges/08_11_2018_16_05

Merges: 08/11/2018 at 16:05
This commit is contained in:
Michele Sollecito 2018-11-09 10:50:39 +00:00 committed by GitHub
commit 72be3aa830
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 1472 additions and 660 deletions

View File

@ -4,6 +4,7 @@ import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigObject
import com.typesafe.config.ConfigValue
import com.typesafe.config.ConfigValueFactory
import net.corda.common.configuration.parsing.internal.versioned.VersionExtractor
import net.corda.common.validation.internal.Validated
import net.corda.common.validation.internal.Validated.Companion.invalid
@ -24,7 +25,7 @@ object Configuration {
/**
* Describes a [Config] hiding sensitive data.
*/
fun describe(configuration: Config): ConfigValue
fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue = { value -> ConfigValueFactory.fromAnyRef(value.toString()) }): ConfigValue?
}
object Value {
@ -112,6 +113,11 @@ object Configuration {
*/
interface Definition<TYPE> : Configuration.Property.Metadata, Configuration.Validator, Configuration.Value.Extractor<TYPE>, Configuration.Describer, Configuration.Value.Parser<TYPE> {
/**
* Validates target [Config] with default [Configuration.Validation.Options].
*/
fun validate(target: Config): Valid<Config> = validate(target, Configuration.Validation.Options.defaults)
override fun isSpecifiedBy(configuration: Config): Boolean = configuration.hasPath(key)
/**
@ -120,9 +126,9 @@ object Configuration {
interface Required<TYPE> : Definition<TYPE> {
/**
* Returns an optional property with given [defaultValue]. This property does not produce errors in case the value is unspecified, returning the [defaultValue] instead.
* Returns an optional property. This property does not produce errors in case the value is unspecified.
*/
fun optional(defaultValue: TYPE? = null): Definition<TYPE?>
fun optional(): Optional<TYPE>
}
/**
@ -136,6 +142,17 @@ object Configuration {
fun list(): Required<List<TYPE>>
}
/**
* Defines a property that might be missing, resulting in a null value.
*/
interface Optional<TYPE> : Definition<TYPE?> {
/**
* Allows to specify a [defaultValue], returning a required [Configuration.Property.Definition].
*/
fun withDefaultValue(defaultValue: TYPE): Definition<TYPE>
}
/**
* Default property definition, required and single-value.
*/
@ -219,7 +236,7 @@ object Configuration {
/**
* Returns a [Configuration.Property.Definition.Standard] with value of type [ENUM].
* This property expects the exact [ENUM] value specified as text for the relevant key.
* This property expects a value in the configuration matching one of the cases of [ENUM], as text, in uppercase.
*/
fun <ENUM : Enum<ENUM>> enum(key: String, enumClass: KClass<ENUM>, sensitive: Boolean = false): Standard<ENUM> = StandardProperty(key, enumClass.java.simpleName, { conf: Config, propertyKey: String -> conf.getEnum(enumClass.java, propertyKey) }, { conf: Config, propertyKey: String -> conf.getEnumList(enumClass.java, propertyKey) }, sensitive)
}
@ -246,6 +263,13 @@ object Configuration {
*/
val properties: Set<Property.Definition<*>>
/**
* Validates target [Config] with default [Configuration.Validation.Options].
*/
fun validate(target: Config): Valid<Config> = validate(target, Configuration.Validation.Options.defaults)
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue): ConfigValue
companion object {
/**
@ -270,7 +294,7 @@ object Configuration {
* A [Configuration.Schema] that is also able to parse a raw [Config] object into a [VALUE].
* It is an abstract class to allow extension with delegated properties e.g., object Settings: Specification() { val address by string().optional("localhost:8080") }.
*/
abstract class Specification<VALUE>(name: String?, private val prefix: String? = null) : Configuration.Schema, Configuration.Value.Parser<VALUE> {
abstract class Specification<VALUE>(override val name: String, private val prefix: String? = null) : Configuration.Schema, Configuration.Value.Parser<VALUE> {
private val mutableProperties = mutableSetOf<Property.Definition<*>>()
@ -321,17 +345,20 @@ object Configuration {
/**
* Returns a delegate for a [Configuration.Property.Definition.Standard] of type [ENUM].
* This property expects the exact [ENUM] value specified as text for the relevant key.
* This property expects a value in the configuration matching one of the cases of [ENUM], as text, in uppercase.
*/
fun <ENUM : Enum<ENUM>> enum(key: String? = null, enumClass: KClass<ENUM>, sensitive: Boolean = false): PropertyDelegate.Standard<ENUM> = PropertyDelegate.enum(key, prefix, enumClass, sensitive) { mutableProperties.add(it) }
override val name: String? get() = schema.name
/**
* @see enum
*/
fun <ENUM : Enum<ENUM>> enum(enumClass: KClass<ENUM>, sensitive: Boolean = false): PropertyDelegate.Standard<ENUM> = enum(key = null, enumClass = enumClass, sensitive = sensitive)
override fun description() = schema.description()
override fun validate(target: Config, options: Validation.Options?) = schema.validate(target, options)
override fun validate(target: Config, options: Validation.Options) = schema.validate(target, options)
override fun describe(configuration: Config) = schema.describe(configuration)
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue) = schema.describe(configuration, serialiseValue)
final override fun parse(configuration: Config, options: Configuration.Validation.Options): Valid<VALUE> = validate(configuration, options).mapValid(::parseValid)
@ -397,9 +424,11 @@ object Configuration {
val containingPathAsString: String = containingPath.joinToString(".")
/**
* [pathstr] joined by "." characters.
* [path] joined by "." characters.
*/
val pathAsString: String = path.joinToString(".")
val pathAsString: String get() = path.joinToString(".")
internal fun withContainingPathPrefix(vararg containingPath: String): Error = withContainingPath(*(containingPath.toList() + this.containingPath).toTypedArray())
internal abstract fun withContainingPath(vararg containingPath: String): Error
@ -415,12 +444,14 @@ object Configuration {
*/
class WrongType private constructor(override val keyName: String, override val typeName: String, message: String, containingPath: List<String> = emptyList()) : Configuration.Validation.Error(keyName, typeName, message, containingPath) {
internal companion object {
companion object {
internal fun of(message: String, keyName: String = UNKNOWN, typeName: String = UNKNOWN, containingPath: List<String> = emptyList()): WrongType = contextualize(keyName, containingPath).let { (key, path) -> WrongType(key, typeName, message, path) }
fun of(message: String, keyName: String? = null, typeName: String = UNKNOWN, containingPath: List<String> = emptyList()): WrongType = contextualize(keyName ?: UNKNOWN, containingPath).let { (key, path) -> WrongType(key, typeName, message, path) }
fun forKey(keyName: String, expectedTypeName: String, actualTypeName: String): WrongType = of("$keyName has type ${actualTypeName.toUpperCase()} rather than ${expectedTypeName.toUpperCase()}")
}
override fun withContainingPath(vararg containingPath: String) = WrongType(keyName, typeName, message, containingPath.toList() + this.containingPath)
override fun withContainingPath(vararg containingPath: String) = WrongType(keyName, typeName, message, containingPath.toList())
override fun with(keyName: String, typeName: String): WrongType = WrongType.of(message, keyName, typeName, containingPath)
}
@ -430,12 +461,14 @@ object Configuration {
*/
class MissingValue private constructor(override val keyName: String, override val typeName: String, message: String, containingPath: List<String> = emptyList()) : Configuration.Validation.Error(keyName, typeName, message, containingPath) {
internal companion object {
companion object {
internal fun of(message: String, keyName: String = UNKNOWN, typeName: String = UNKNOWN, containingPath: List<String> = emptyList()): MissingValue = contextualize(keyName, containingPath).let { (key, path) -> MissingValue(key, typeName, message, path) }
fun of(message: String, keyName: String? = null, typeName: String = UNKNOWN, containingPath: List<String> = emptyList()): MissingValue = contextualize(keyName ?: UNKNOWN, containingPath).let { (key, path) -> MissingValue(key, typeName, message, path) }
fun forKey(keyName: String): MissingValue = of("No configuration setting found for key '$keyName'", keyName)
}
override fun withContainingPath(vararg containingPath: String) = MissingValue(keyName, typeName, message, containingPath.toList() + this.containingPath)
override fun withContainingPath(vararg containingPath: String) = MissingValue(keyName, typeName, message, containingPath.toList())
override fun with(keyName: String, typeName: String): MissingValue = MissingValue.of(message, keyName, typeName, containingPath)
}
@ -445,12 +478,12 @@ object Configuration {
*/
class BadValue private constructor(override val keyName: String, override val typeName: String, message: String, containingPath: List<String> = emptyList()) : Configuration.Validation.Error(keyName, typeName, message, containingPath) {
internal companion object {
companion object {
internal fun of(message: String, keyName: String = UNKNOWN, typeName: String = UNKNOWN, containingPath: List<String> = emptyList()): BadValue = contextualize(keyName, containingPath).let { (key, path) -> BadValue(key, typeName, message, path) }
fun of(message: String, keyName: String? = null, typeName: String = UNKNOWN, containingPath: List<String> = emptyList()): BadValue = contextualize(keyName ?: UNKNOWN, containingPath).let { (key, path) -> BadValue(key, typeName, message, path) }
}
override fun withContainingPath(vararg containingPath: String) = BadValue(keyName, typeName, message, containingPath.toList() + this.containingPath)
override fun withContainingPath(vararg containingPath: String) = BadValue(keyName, typeName, message, containingPath.toList())
override fun with(keyName: String, typeName: String): BadValue = BadValue.of(message, keyName, typeName, containingPath)
}
@ -460,12 +493,12 @@ object Configuration {
*/
class BadPath private constructor(override val keyName: String, override val typeName: String, message: String, containingPath: List<String> = emptyList()) : Configuration.Validation.Error(keyName, typeName, message, containingPath) {
internal companion object {
companion object {
internal fun of(message: String, keyName: String = UNKNOWN, typeName: String = UNKNOWN, containingPath: List<String> = emptyList()): BadPath = contextualize(keyName, containingPath).let { (key, path) -> BadPath(key, typeName, message, path) }
fun of(message: String, keyName: String? = null, typeName: String = UNKNOWN, containingPath: List<String> = emptyList()): BadPath = contextualize(keyName ?: UNKNOWN, containingPath).let { (key, path) -> BadPath(key, typeName, message, path) }
}
override fun withContainingPath(vararg containingPath: String) = BadPath(keyName, typeName, message, containingPath.toList() + this.containingPath)
override fun withContainingPath(vararg containingPath: String) = BadPath(keyName, typeName, message, containingPath.toList())
override fun with(keyName: String, typeName: String): BadPath = BadPath.of(message, keyName, typeName, containingPath)
}
@ -475,12 +508,12 @@ object Configuration {
*/
class MalformedStructure private constructor(override val keyName: String, override val typeName: String, message: String, containingPath: List<String> = emptyList()) : Configuration.Validation.Error(keyName, typeName, message, containingPath) {
internal companion object {
companion object {
internal fun of(message: String, keyName: String = UNKNOWN, typeName: String = UNKNOWN, containingPath: List<String> = emptyList()): MalformedStructure = contextualize(keyName, containingPath).let { (key, path) -> MalformedStructure(key, typeName, message, path) }
fun of(message: String, keyName: String? = null, typeName: String = UNKNOWN, containingPath: List<String> = emptyList()): MalformedStructure = contextualize(keyName ?: UNKNOWN, containingPath).let { (key, path) -> MalformedStructure(key, typeName, message, path) }
}
override fun withContainingPath(vararg containingPath: String) = MalformedStructure(keyName, typeName, message, containingPath.toList() + this.containingPath)
override fun withContainingPath(vararg containingPath: String) = MalformedStructure(keyName, typeName, message, containingPath.toList())
override fun with(keyName: String, typeName: String): MalformedStructure = MalformedStructure.of(message, keyName, typeName, containingPath)
}
@ -490,16 +523,14 @@ object Configuration {
*/
class Unknown private constructor(override val keyName: String, containingPath: List<String> = emptyList()) : Configuration.Validation.Error(keyName, null, message(keyName), containingPath) {
internal companion object {
companion object {
private fun message(keyName: String) = "Unknown property \"$keyName\"."
private fun message(keyName: String) = "Unknown property \'$keyName\'"
internal fun of(keyName: String = UNKNOWN, containingPath: List<String> = emptyList()): Unknown = contextualize(keyName, containingPath).let { (key, path) -> Unknown(key, path) }
fun of(keyName: String = UNKNOWN, containingPath: List<String> = emptyList()): Unknown = contextualize(keyName, containingPath).let { (key, path) -> Unknown(key, path) }
}
override val message = message(pathAsString)
override fun withContainingPath(vararg containingPath: String) = Unknown(keyName, containingPath.toList() + this.containingPath)
override fun withContainingPath(vararg containingPath: String) = Unknown(keyName, containingPath.toList())
override fun with(keyName: String, typeName: String): Unknown = Unknown.of(keyName, containingPath)
}
@ -509,12 +540,12 @@ object Configuration {
*/
class UnsupportedVersion private constructor(val version: Int, containingPath: List<String> = emptyList()) : Configuration.Validation.Error(null, null, "Unknown configuration version $version.", containingPath) {
internal companion object {
companion object {
internal fun of(version: Int): UnsupportedVersion = UnsupportedVersion(version)
fun of(version: Int): UnsupportedVersion = UnsupportedVersion(version)
}
override fun withContainingPath(vararg containingPath: String) = UnsupportedVersion(version, containingPath.toList() + this.containingPath)
override fun withContainingPath(vararg containingPath: String) = UnsupportedVersion(version, containingPath.toList())
override fun with(keyName: String, typeName: String): UnsupportedVersion = this
}
@ -526,16 +557,16 @@ object Configuration {
/**
* Defines the contract from extracting a specification version from a [Config] object.
*/
interface Extractor : Configuration.Value.Parser<Int?> {
interface Extractor : Configuration.Value.Parser<Int> {
companion object {
const val DEFAULT_VERSION_VALUE = 1
/**
* Returns a [Configuration.Version.Extractor] that reads the value from given [versionKey], defaulting to [versionDefaultValue] when [versionKey] is unspecified.
* Returns a [Configuration.Version.Extractor] that reads the value from given [versionPath], defaulting to [versionDefaultValue] when [versionPath] is unspecified.
*/
fun fromKey(versionKey: String, versionDefaultValue: Int? = DEFAULT_VERSION_VALUE): Configuration.Version.Extractor = VersionExtractor(versionKey, versionDefaultValue)
fun fromPath(versionPath: String, versionDefaultValue: Int = DEFAULT_VERSION_VALUE): Configuration.Version.Extractor = VersionExtractor(versionPath, versionDefaultValue)
}
}
}

View File

@ -1,13 +1,17 @@
package net.corda.common.configuration.parsing.internal
import com.typesafe.config.*
import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigObject
import com.typesafe.config.ConfigValue
import com.typesafe.config.ConfigValueFactory
import net.corda.common.validation.internal.Validated
import net.corda.common.validation.internal.Validated.Companion.invalid
import net.corda.common.validation.internal.Validated.Companion.valid
internal class LongProperty(key: String, sensitive: Boolean = false) : StandardProperty<Long>(key, Long::class.javaObjectType.simpleName, Config::getLong, Config::getLongList, sensitive) {
override fun validate(target: Config, options: Configuration.Validation.Options?): Valid<Config> {
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> {
val validated = super.validate(target, options)
if (validated.isValid && target.getValue(key).unwrapped().toString().contains(".")) {
@ -17,7 +21,7 @@ internal class LongProperty(key: String, sensitive: Boolean = false) : StandardP
}
}
internal open class StandardProperty<TYPE>(override val key: String, typeNameArg: String, private val extractSingleValue: (Config, String) -> TYPE, internal val extractListValue: (Config, String) -> List<TYPE>, override val isSensitive: Boolean = false, final override val schema: Configuration.Schema? = null) : Configuration.Property.Definition.Standard<TYPE> {
internal open class StandardProperty<TYPE : Any>(override val key: String, typeNameArg: String, private val extractSingleValue: (Config, String) -> TYPE, internal val extractListValue: (Config, String) -> List<TYPE>, override val isSensitive: Boolean = false, final override val schema: Configuration.Schema? = null) : Configuration.Property.Definition.Standard<TYPE> {
override fun valueIn(configuration: Config) = extractSingleValue.invoke(configuration, key)
@ -25,21 +29,21 @@ internal open class StandardProperty<TYPE>(override val key: String, typeNameArg
override fun <MAPPED : Any> mapValid(mappedTypeName: String, convert: (TYPE) -> Valid<MAPPED>): Configuration.Property.Definition.Standard<MAPPED> = FunctionalProperty(this, mappedTypeName, extractListValue, convert)
override fun optional(defaultValue: TYPE?): Configuration.Property.Definition<TYPE?> = OptionalProperty(this, defaultValue)
override fun optional(): Configuration.Property.Definition.Optional<TYPE> = OptionalDelegatedProperty(this)
override fun list(): Configuration.Property.Definition.Required<List<TYPE>> = ListProperty(this)
override fun describe(configuration: Config): ConfigValue {
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue): ConfigValue {
if (isSensitive) {
return ConfigValueFactory.fromAnyRef(Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER)
return valueDescription(Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER, serialiseValue)
}
return schema?.describe(configuration.getConfig(key)) ?: ConfigValueFactory.fromAnyRef(valueIn(configuration))
return schema?.describe(configuration.getConfig(key), serialiseValue) ?: valueDescription(valueIn(configuration), serialiseValue)
}
override val isMandatory = true
override fun validate(target: Config, options: Configuration.Validation.Options?): Valid<Config> {
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> {
val errors = mutableSetOf<Configuration.Validation.Error>()
errors += errorsWhenExtractingValue(target)
@ -47,7 +51,7 @@ internal open class StandardProperty<TYPE>(override val key: String, typeNameArg
schema?.let { nestedSchema ->
val nestedConfig: Config? = target.getConfig(key)
nestedConfig?.let {
errors += nestedSchema.validate(nestedConfig, options).errors.map { error -> error.withContainingPath(*key.split(".").toTypedArray()) }
errors += nestedSchema.validate(nestedConfig, options).errors.map { error -> error.withContainingPathPrefix(*key.split(".").toTypedArray()) }
}
}
}
@ -57,63 +61,63 @@ internal open class StandardProperty<TYPE>(override val key: String, typeNameArg
override fun toString() = "\"$key\": \"$typeName\""
}
private class ListProperty<TYPE>(delegate: StandardProperty<TYPE>) : RequiredDelegatedProperty<List<TYPE>, StandardProperty<TYPE>>(delegate) {
private class ListProperty<TYPE : Any>(delegate: StandardProperty<TYPE>) : RequiredDelegatedProperty<List<TYPE>, StandardProperty<TYPE>>(delegate) {
override val typeName: String = "List<${delegate.typeName}>"
override fun valueIn(configuration: Config): List<TYPE> = delegate.extractListValue.invoke(configuration, key)
override fun validate(target: Config, options: Configuration.Validation.Options?): Valid<Config> {
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> {
val errors = mutableSetOf<Configuration.Validation.Error>()
errors += errorsWhenExtractingValue(target)
if (errors.isEmpty()) {
delegate.schema?.let { schema ->
errors += valueIn(target).asSequence().map { element -> element as ConfigObject }.map(ConfigObject::toConfig).mapIndexed { index, targetConfig -> schema.validate(targetConfig, options).errors.map { error -> error.withContainingPath(key, "[$index]") } }.reduce { one, other -> one + other }
errors += valueIn(target).asSequence().map { element -> element as ConfigObject }.map(ConfigObject::toConfig).mapIndexed { index, targetConfig -> schema.validate(targetConfig, options).errors.map { error -> error.withContainingPath(*error.containingPath(index).toTypedArray()) } }.fold(emptyList<Configuration.Validation.Error>()) { one, other -> one + other }.toSet()
}
}
return Validated.withResult(target, errors)
}
override fun describe(configuration: Config): ConfigValue {
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue): ConfigValue {
if (isSensitive) {
return ConfigValueFactory.fromAnyRef(Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER)
return valueDescription(Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER, serialiseValue)
}
return when {
delegate.schema != null -> {
val elementsDescription = valueIn(configuration).asSequence().map { it as ConfigObject }.map(ConfigObject::toConfig).map { delegate.schema.describe(it, serialiseValue) }.toList()
ConfigValueFactory.fromIterable(elementsDescription)
}
else -> valueDescription(valueIn(configuration), serialiseValue)
}
}
private fun Configuration.Validation.Error.containingPath(index: Int): List<String> {
val newContainingPath = listOf(key, "[$index]")
return when {
containingPath.size > 1 -> newContainingPath + containingPath.subList(1, containingPath.size)
else -> newContainingPath
}
return delegate.schema?.let { schema -> ConfigValueFactory.fromAnyRef(valueIn(configuration).asSequence().map { element -> element as ConfigObject }.map(ConfigObject::toConfig).map { schema.describe(it) }.toList()) } ?: ConfigValueFactory.fromAnyRef(valueIn(configuration))
}
}
private class OptionalProperty<TYPE>(delegate: Configuration.Property.Definition.Required<TYPE>, private val defaultValue: TYPE?) : DelegatedProperty<TYPE?, Configuration.Property.Definition.Required<TYPE>>(delegate) {
private class OptionalPropertyWithDefault<TYPE : Any>(delegate: Configuration.Property.Definition.Optional<TYPE>, private val defaultValue: TYPE) : DelegatedProperty<TYPE, Configuration.Property.Definition.Optional<TYPE>>(delegate) {
override val isMandatory: Boolean = false
override val typeName: String = "${super.typeName}?"
override val typeName: String = delegate.typeName.removeSuffix("?")
override fun describe(configuration: Config) = delegate.describe(configuration)
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue): ConfigValue? = delegate.describe(configuration, serialiseValue) ?: valueDescription(if (isSensitive) Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER else defaultValue, serialiseValue)
override fun valueIn(configuration: Config): TYPE? {
override fun valueIn(configuration: Config): TYPE = delegate.valueIn(configuration) ?: defaultValue
return when {
isSpecifiedBy(configuration) -> delegate.valueIn(configuration)
else -> defaultValue
}
}
override fun validate(target: Config, options: Configuration.Validation.Options?): Valid<Config> {
val result = delegate.validate(target, options)
val error = result.errors.asSequence().filterIsInstance<Configuration.Validation.Error.MissingValue>().singleOrNull()
return when {
error != null -> if (result.errors.size > 1) result else valid(target)
else -> result
}
}
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> = delegate.validate(target, options)
}
private class FunctionalProperty<TYPE, MAPPED : Any>(delegate: Configuration.Property.Definition.Standard<TYPE>, private val mappedTypeName: String, internal val extractListValue: (Config, String) -> List<TYPE>, private val convert: (TYPE) -> Valid<MAPPED>) : RequiredDelegatedProperty<MAPPED, Configuration.Property.Definition.Standard<TYPE>>(delegate), Configuration.Property.Definition.Standard<MAPPED> {
override fun valueIn(configuration: Config) = convert.invoke(delegate.valueIn(configuration)).valueOrThrow()
override fun valueIn(configuration: Config) = convert.invoke(delegate.valueIn(configuration)).orThrow()
override val typeName: String = if (super.typeName == "#$mappedTypeName") super.typeName else "$mappedTypeName(${super.typeName})"
@ -121,7 +125,7 @@ private class FunctionalProperty<TYPE, MAPPED : Any>(delegate: Configuration.Pro
override fun list(): Configuration.Property.Definition.Required<List<MAPPED>> = FunctionalListProperty(this)
override fun validate(target: Config, options: Configuration.Validation.Options?): Valid<Config> {
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> {
val errors = mutableSetOf<Configuration.Validation.Error>()
errors += delegate.validate(target, options).errors
@ -131,7 +135,7 @@ private class FunctionalProperty<TYPE, MAPPED : Any>(delegate: Configuration.Pro
return Validated.withResult(target, errors)
}
override fun describe(configuration: Config) = delegate.describe(configuration)
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue) = delegate.describe(configuration, serialiseValue)
}
private class FunctionalListProperty<RAW, TYPE : Any>(delegate: FunctionalProperty<RAW, TYPE>) : RequiredDelegatedProperty<List<TYPE>, FunctionalProperty<RAW, TYPE>>(delegate) {
@ -140,7 +144,7 @@ private class FunctionalListProperty<RAW, TYPE : Any>(delegate: FunctionalProper
override fun valueIn(configuration: Config): List<TYPE> = delegate.extractListValue.invoke(configuration, key).asSequence().map { configObject(key to ConfigValueFactory.fromAnyRef(it)) }.map(ConfigObject::toConfig).map(delegate::valueIn).toList()
override fun validate(target: Config, options: Configuration.Validation.Options?): Valid<Config> {
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> {
val list = try {
delegate.extractListValue.invoke(target, key)
@ -151,16 +155,24 @@ private class FunctionalListProperty<RAW, TYPE : Any>(delegate: FunctionalProper
throw e
}
}
val errors = list.asSequence().map { configObject(key to ConfigValueFactory.fromAnyRef(it)) }.mapIndexed { index, value -> delegate.validate(value.toConfig(), options).errors.map { error -> error.withContainingPath(key, "[$index]") } }.reduce { one, other -> one + other }.toSet()
val errors = list.asSequence().map { configObject(key to ConfigValueFactory.fromAnyRef(it)) }.mapIndexed { index, value -> delegate.validate(value.toConfig(), options).errors.map { error -> error.withContainingPath(*error.containingPath(index).toTypedArray()) } }.fold(emptyList<Configuration.Validation.Error>()) { one, other -> one + other }.toSet()
return Validated.withResult(target, errors)
}
override fun describe(configuration: Config): ConfigValue {
private fun Configuration.Validation.Error.containingPath(index: Int): List<String> {
val newContainingPath = listOf(key, "[$index]")
return when {
containingPath.size > 1 -> newContainingPath + containingPath.subList(1, containingPath.size)
else -> newContainingPath
}
}
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue): ConfigValue {
if (isSensitive) {
return ConfigValueFactory.fromAnyRef(Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER)
return valueDescription(Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER, serialiseValue)
}
return delegate.schema?.let { schema -> ConfigValueFactory.fromAnyRef(valueIn(configuration).asSequence().map { element -> element as ConfigObject }.map(ConfigObject::toConfig).map { schema.describe(it) }.toList()) } ?: ConfigValueFactory.fromAnyRef(valueIn(configuration))
return delegate.schema?.let { schema -> valueDescription(valueIn(configuration).asSequence().map { element -> valueDescription(element, serialiseValue) }.map { it as ConfigObject }.map(ConfigObject::toConfig).map { schema.describe(it, serialiseValue) }.toList(), serialiseValue) } ?: valueDescription(valueIn(configuration), serialiseValue)
}
}
@ -169,12 +181,45 @@ private abstract class DelegatedProperty<TYPE, DELEGATE : Configuration.Property
final override fun toString() = "\"$key\": \"$typeName\""
}
private abstract class RequiredDelegatedProperty<TYPE, DELEGATE : Configuration.Property.Definition.Required<*>>(delegate: DELEGATE) : DelegatedProperty<TYPE, DELEGATE>(delegate), Configuration.Property.Definition.Required<TYPE> {
private class OptionalDelegatedProperty<TYPE : Any>(private val delegate: Configuration.Property.Definition<TYPE>) : Configuration.Property.Metadata by delegate, Configuration.Property.Definition.Optional<TYPE> {
final override fun optional(defaultValue: TYPE?): Configuration.Property.Definition<TYPE?> = OptionalProperty(this, defaultValue)
override val isMandatory: Boolean = false
override val typeName: String = "${delegate.typeName}?"
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue) = if (isSpecifiedBy(configuration)) delegate.describe(configuration, serialiseValue) else null
override fun valueIn(configuration: Config): TYPE? {
return when {
isSpecifiedBy(configuration) -> delegate.valueIn(configuration)
else -> null
}
}
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> {
val result = delegate.validate(target, options)
val errors = result.errors
val missingValueError = errors.asSequence().filterIsInstance<Configuration.Validation.Error.MissingValue>().filter { it.pathAsString == key }.singleOrNull()
return when {
missingValueError != null -> if (errors.size > 1) result else valid(target)
else -> result
}
}
override fun withDefaultValue(defaultValue: TYPE): Configuration.Property.Definition<TYPE> = OptionalPropertyWithDefault(this, defaultValue)
override fun toString() = "\"$key\": \"$typeName\""
}
private fun ConfigException.toValidationError(keyName: String, typeName: String): Configuration.Validation.Error {
private abstract class RequiredDelegatedProperty<TYPE : Any, DELEGATE : Configuration.Property.Definition.Required<*>>(delegate: DELEGATE) : DelegatedProperty<TYPE, DELEGATE>(delegate), Configuration.Property.Definition.Required<TYPE> {
final override fun optional(): Configuration.Property.Definition.Optional<TYPE> = OptionalDelegatedProperty(this)
}
fun ConfigException.toValidationError(keyName: String? = null, typeName: String): Configuration.Validation.Error {
val toError = when (this) {
is ConfigException.Missing -> Configuration.Validation.Error.MissingValue.Companion::of
@ -202,4 +247,6 @@ private fun Configuration.Property.Definition<*>.errorsWhenExtractingValue(targe
private val expectedExceptionTypes = setOf(ConfigException.Missing::class, ConfigException.WrongType::class, ConfigException.BadValue::class, ConfigException.BadPath::class, ConfigException.Parse::class)
private fun isErrorExpected(error: ConfigException) = expectedExceptionTypes.any { expected -> expected.isInstance(error) }
private fun isErrorExpected(error: ConfigException) = expectedExceptionTypes.any { expected -> expected.isInstance(error) }
private fun valueDescription(value: Any, serialiseValue: (Any) -> ConfigValue) = serialiseValue.invoke(value)

View File

@ -16,10 +16,12 @@ internal class Schema(override val name: String?, unorderedProperties: Iterable<
}
}
override fun validate(target: Config, options: Configuration.Validation.Options?): Valid<Config> {
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> {
val propertyErrors = properties.flatMap { property -> property.validate(target, options).errors }.toMutableSet()
if (options?.strict == true) {
val propertyErrors = properties.flatMap { property ->
property.validate(target, options).errors
}.toMutableSet()
if (options.strict) {
val unknownKeys = target.root().keys - properties.map(Configuration.Property.Definition<*>::key)
propertyErrors += unknownKeys.map { Configuration.Validation.Error.Unknown.of(it) }
}
@ -45,9 +47,9 @@ internal class Schema(override val name: String?, unorderedProperties: Iterable<
return description.toString()
}
override fun describe(configuration: Config): ConfigValue {
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue): ConfigValue {
return properties.asSequence().map { it.key to it.describe(configuration) }.fold(configObject()) { config, (key, value) -> config.withValue(key, value) }
return properties.asSequence().map { it.key to it.describe(configuration, serialiseValue) }.filter { it.second != null }.fold(configObject()) { config, (key, value) -> config.withValue(key, value) }
}
override fun equals(other: Any?): Boolean {

View File

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

View File

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

View File

@ -5,18 +5,21 @@ import net.corda.common.configuration.parsing.internal.Configuration
import net.corda.common.configuration.parsing.internal.Valid
import net.corda.common.configuration.parsing.internal.valid
internal class VersionExtractor(versionKey: String, versionDefaultValue: Int?) : Configuration.Version.Extractor {
internal class VersionExtractor(versionPath: String, versionDefaultValue: Int) : Configuration.Version.Extractor {
private val spec = Spec(versionKey, versionDefaultValue)
private val containingPath = versionPath.split(".").let { if (it.size > 1) it.subList(0, it.size - 1) else null }
private val key = versionPath.split(".").last()
override fun parse(configuration: Config, options: Configuration.Validation.Options): Valid<Int?> {
private val spec = Spec(key, versionDefaultValue, containingPath?.joinToString("."))
override fun parse(configuration: Config, options: Configuration.Validation.Options): Valid<Int> {
return spec.parse(configuration)
}
private class Spec(versionKey: String, versionDefaultValue: Int?) : Configuration.Specification<Int?>("Version") {
private class Spec(key: String, versionDefaultValue: Int, prefix: String?) : Configuration.Specification<Int>("Version", prefix) {
private val version by int(key = versionKey).optional(versionDefaultValue)
private val version by int(key = key).optional().withDefaultValue(versionDefaultValue)
override fun parseValid(configuration: Config) = valid(version.valueIn(configuration))
}

View File

@ -95,7 +95,7 @@ class PropertyTest {
val configuration = configObject(key to null).toConfig()
val defaultValue = listOf(1L, 2L, 3L)
val property = Configuration.Property.Definition.long(key).list().optional(defaultValue)
val property = Configuration.Property.Definition.long(key).list().optional().withDefaultValue(defaultValue)
println(property)
assertThat(property.key).isEqualTo(key)
@ -173,7 +173,7 @@ class PropertyTest {
val configuration = configObject(key to null).toConfig()
val defaultValue = 23L
val property = Configuration.Property.Definition.long(key).optional(defaultValue)
val property = Configuration.Property.Definition.long(key).optional().withDefaultValue(defaultValue)
println(property)
assertThat(property.key).isEqualTo(key)

View File

@ -1,9 +1,7 @@
package net.corda.common.configuration.parsing.internal
import com.typesafe.config.Config
import net.corda.common.validation.internal.Validated.Companion.invalid
import net.corda.common.validation.internal.Validated.Companion.valid
import net.corda.common.validation.internal.Validator
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
@ -15,7 +13,7 @@ class PropertyValidationTest {
val key = "a.b.c"
val configuration = configObject().toConfig()
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.long(key)
val property = Configuration.Property.Definition.long(key)
assertThat(property.validate(configuration).errors).satisfies { errors ->
@ -34,7 +32,7 @@ class PropertyValidationTest {
val key = "a.b.c"
val configuration = configObject(key to null).toConfig()
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.long(key)
val property = Configuration.Property.Definition.long(key)
assertThat(property.validate(configuration).errors).satisfies { errors ->
@ -53,7 +51,7 @@ class PropertyValidationTest {
val key = "a.b.c"
val configuration = configObject().toConfig()
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.long(key).list()
val property = Configuration.Property.Definition.long(key).list()
assertThat(property.validate(configuration).errors).satisfies { errors ->
@ -72,7 +70,7 @@ class PropertyValidationTest {
val key = "a.b.c"
val configuration = configObject(key to null).toConfig()
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.long(key).list()
val property = Configuration.Property.Definition.long(key).list()
assertThat(property.validate(configuration).errors).satisfies { errors ->
@ -90,7 +88,7 @@ class PropertyValidationTest {
val key = "a.b.c"
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.long(key)
val property = Configuration.Property.Definition.long(key)
val configuration = configObject(key to false).toConfig()
@ -110,7 +108,7 @@ class PropertyValidationTest {
val key = "a.b.c"
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.long(key)
val property = Configuration.Property.Definition.long(key)
val configuration = configObject(key to 1.2).toConfig()
@ -130,7 +128,7 @@ class PropertyValidationTest {
val key = "a.b.c"
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.double(key)
val property = Configuration.Property.Definition.double(key)
val configuration = configObject(key to 1).toConfig()
@ -142,7 +140,7 @@ class PropertyValidationTest {
val key = "a.b.c"
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.long(key).list()
val property = Configuration.Property.Definition.long(key).list()
val configuration = configObject(key to listOf(false, true)).toConfig()
@ -162,7 +160,7 @@ class PropertyValidationTest {
val key = "a.b.c"
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.long(key)
val property = Configuration.Property.Definition.long(key)
val configuration = configObject(key to listOf(1, 2, 3)).toConfig()
@ -182,7 +180,7 @@ class PropertyValidationTest {
val key = "a.b.c"
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.long(key).list()
val property = Configuration.Property.Definition.long(key).list()
val configuration = configObject(key to 1).toConfig()
@ -205,7 +203,7 @@ class PropertyValidationTest {
val nestedKey = "d"
val nestedPropertySchema = Configuration.Schema.withProperties(Configuration.Property.Definition.long(nestedKey))
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.nestedObject(key, nestedPropertySchema)
val property = Configuration.Property.Definition.nestedObject(key, nestedPropertySchema)
val configuration = configObject(key to configObject(nestedKey to false)).toConfig()
@ -228,7 +226,7 @@ class PropertyValidationTest {
val nestedKey = "d"
val nestedPropertySchema = Configuration.Schema.withProperties(Configuration.Property.Definition.long(nestedKey))
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.nestedObject(key, nestedPropertySchema)
val property = Configuration.Property.Definition.nestedObject(key, nestedPropertySchema)
val configuration = configObject(key to configObject()).toConfig()
@ -251,7 +249,7 @@ class PropertyValidationTest {
val nestedKey = "d"
val nestedPropertySchema = Configuration.Schema.withProperties(Configuration.Property.Definition.long(nestedKey))
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.nestedObject(key, nestedPropertySchema)
val property = Configuration.Property.Definition.nestedObject(key, nestedPropertySchema)
val configuration = configObject(key to configObject(nestedKey to null)).toConfig()
@ -273,7 +271,7 @@ class PropertyValidationTest {
val nestedKey = "d"
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.nestedObject(key)
val property = Configuration.Property.Definition.nestedObject(key)
val configuration = configObject(key to configObject(nestedKey to false)).toConfig()
@ -285,7 +283,7 @@ class PropertyValidationTest {
val key = "a"
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.string(key).mapValid(::parseAddress)
val property = Configuration.Property.Definition.string(key).mapValid(::parseAddress)
val host = "localhost"
val port = 8080
@ -301,7 +299,7 @@ class PropertyValidationTest {
val key = "a.b.c"
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.string(key).mapValid(::parseAddress)
val property = Configuration.Property.Definition.string(key).mapValid(::parseAddress)
val host = "localhost"
val port = 8080

View File

@ -39,7 +39,7 @@ class SpecificationTest {
val rpcSettings = RpcSettingsSpec.parse(configuration)
assertThat(rpcSettings.isValid).isTrue()
assertThat(rpcSettings.valueOrThrow()).satisfies { value ->
assertThat(rpcSettings.orThrow()).satisfies { value ->
assertThat(value.useSsl).isEqualTo(useSslValue)
assertThat(value.addresses).satisfies { addresses ->

View File

@ -9,8 +9,8 @@ import org.junit.Test
class VersionExtractorTest {
private val versionExtractor = Configuration.Version.Extractor.fromKey("configuration.metadata.version")
private val extractVersion: (Config) -> Valid<Int?> = { config -> versionExtractor.parse(config) }
private val versionExtractor = Configuration.Version.Extractor.fromPath("configuration.metadata.version")
private val extractVersion: (Config) -> Valid<Int> = { config -> versionExtractor.parse(config) }
@Test
fun version_header_extraction_present() {
@ -18,7 +18,7 @@ class VersionExtractorTest {
val versionValue = Configuration.Version.Extractor.DEFAULT_VERSION_VALUE + 1
val rawConfiguration = configObject("configuration" to configObject("metadata" to configObject("version" to versionValue), "node" to configObject("p2pAddress" to "localhost:8080"))).toConfig()
val version = extractVersion.invoke(rawConfiguration).valueOrThrow()
val version = extractVersion.invoke(rawConfiguration).orThrow()
assertThat(version).isEqualTo(versionValue)
}
@ -27,7 +27,7 @@ class VersionExtractorTest {
val rawConfiguration = configObject("configuration" to configObject("node" to configObject("p2pAddress" to "localhost:8080"))).toConfig()
val version = extractVersion.invoke(rawConfiguration).valueOrThrow()
val version = extractVersion.invoke(rawConfiguration).orThrow()
assertThat(version).isEqualTo(Configuration.Version.Extractor.DEFAULT_VERSION_VALUE)
}
@ -36,7 +36,7 @@ class VersionExtractorTest {
val rawConfiguration = configObject("configuration" to configObject("metadata" to configObject(), "node" to configObject("p2pAddress" to "localhost:8080"))).toConfig()
val version = extractVersion.invoke(rawConfiguration).valueOrThrow()
val version = extractVersion.invoke(rawConfiguration).orThrow()
assertThat(version).isEqualTo(Configuration.Version.Extractor.DEFAULT_VERSION_VALUE)
}
@ -46,7 +46,7 @@ class VersionExtractorTest {
val rawConfiguration = configObject("configuration" to configObject("metadata" to configObject("version" to null), "node" to configObject("p2pAddress" to "localhost:8080"))).toConfig()
val version = extractVersion.invoke(rawConfiguration).valueOrThrow()
val version = extractVersion.invoke(rawConfiguration).orThrow()
assertThat(version).isEqualTo(Configuration.Version.Extractor.DEFAULT_VERSION_VALUE)
}
@ -56,7 +56,7 @@ class VersionExtractorTest {
val rawConfiguration = configObject().toConfig()
val version = extractVersion.invoke(rawConfiguration).valueOrThrow()
val version = extractVersion.invoke(rawConfiguration).orThrow()
assertThat(version).isEqualTo(Configuration.Version.Extractor.DEFAULT_VERSION_VALUE)
}

View File

@ -12,7 +12,7 @@ class VersionedParsingExampleTest {
@Test
fun correct_parsing_function_is_used_for_present_version() {
val versionParser = Configuration.Version.Extractor.fromKey("configuration.metadata.version", null)
val versionParser = Configuration.Version.Extractor.fromPath("configuration.metadata.version")
val extractVersion: (Config) -> Valid<Int> = { config -> versionParser.parseRequired(config) }
val parseConfiguration = VersionedSpecificationRegistry.mapping(extractVersion, 1 to RpcSettingsSpec.V1, 2 to RpcSettingsSpec.V2)
@ -35,7 +35,7 @@ class VersionedParsingExampleTest {
fun default_value_is_used_for_absent_version() {
val defaultVersion = 2
val versionParser = Configuration.Version.Extractor.fromKey("configuration.metadata.version", defaultVersion)
val versionParser = Configuration.Version.Extractor.fromPath("configuration.metadata.version", defaultVersion)
val extractVersion: (Config) -> Valid<Int> = { config -> versionParser.parseRequired(config) }
val parseConfiguration = VersionedSpecificationRegistry.mapping(extractVersion, 1 to RpcSettingsSpec.V1, 2 to RpcSettingsSpec.V2)
@ -52,7 +52,7 @@ class VersionedParsingExampleTest {
private fun assertResult(result: Valid<RpcSettings>, principalAddressValue: Address, adminAddressValue: Address) {
assertThat(result.isValid).isTrue()
assertThat(result.valueOrThrow()).satisfies { value ->
assertThat(result.orThrow()).satisfies { value ->
assertThat(value.principal).isEqualTo(principalAddressValue)
assertThat(value.admin).isEqualTo(adminAddressValue)

View File

@ -39,7 +39,7 @@ interface Validated<TARGET, ERROR> {
*
* @throws IllegalStateException or the result of [exceptionOnErrors] if there are errors.
*/
fun valueOrThrow(exceptionOnErrors: (Set<ERROR>) -> Exception = { errors -> IllegalStateException(errors.joinToString(System.lineSeparator())) }): TARGET
fun orThrow(exceptionOnErrors: (Set<ERROR>) -> Exception = { errors -> IllegalStateException(errors.joinToString(System.lineSeparator())) }): TARGET
/**
* Applies the [convert] function to the [TARGET] value, if valid. Otherwise, returns a [Validated] monad with a [MAPPED] generic type and the current errors set.
@ -113,7 +113,7 @@ interface Validated<TARGET, ERROR> {
class Successful<TARGET, ERROR>(override val value: TARGET) : Result<TARGET, ERROR>(), Validated<TARGET, ERROR> {
override val errors: Set<ERROR> = emptySet<ERROR>()
override fun valueOrThrow(exceptionOnErrors: (Set<ERROR>) -> Exception) = value
override fun orThrow(exceptionOnErrors: (Set<ERROR>) -> Exception) = value
override fun <MAPPED> map(convert: (TARGET) -> MAPPED): Validated<MAPPED, ERROR> {
return valid(convert.invoke(value))
@ -138,7 +138,7 @@ interface Validated<TARGET, ERROR> {
override val value: TARGET get() = throw IllegalStateException("Invalid state.")
override fun valueOrThrow(exceptionOnErrors: (Set<ERROR>) -> Exception) = throw exceptionOnErrors.invoke(errors)
override fun orThrow(exceptionOnErrors: (Set<ERROR>) -> Exception) = throw exceptionOnErrors.invoke(errors)
override fun <MAPPED> map(convert: (TARGET) -> MAPPED): Validated<MAPPED, ERROR> {
return invalid(errors)

View File

@ -8,5 +8,5 @@ interface Validator<TARGET : Any, ERROR : Any, OPTIONS> {
/**
* Validates [target] using given [options], producing a [Validated] monad wrapping either a valid [target] or a set of [ERROR]s.
*/
fun validate(target: TARGET, options: OPTIONS? = null): Validated<TARGET, ERROR>
fun validate(target: TARGET, options: OPTIONS): Validated<TARGET, ERROR>
}

View File

@ -35,7 +35,7 @@
<!-- Will generate up to 100 log files for a given day. During every rollover it will delete
those that are older than 60 days, but keep the most recent 10 GB -->
<RollingFile name="RollingFile-Appender"
<RollingRandomAccessFile name="RollingFile-Appender"
fileName="${log-path}/${log-name}.log"
filePattern="${archive}/${log-name}.%date{yyyy-MM-dd}-%i.log.gz">
@ -71,29 +71,44 @@
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</RollingRandomAccessFile>
<Rewrite name="Console-ErrorCode-Appender">
<AppenderRef ref="Console-Appender"/>
<ErrorCodeRewritePolicy/>
</Rewrite>
<Rewrite name="Console-ErrorCode-Appender-Println">
<AppenderRef ref="Console-Appender-Println"/>
<ErrorCodeRewritePolicy/>
</Rewrite>
<Rewrite name="RollingFile-ErrorCode-Appender">
<AppenderRef ref="RollingFile-Appender"/>
<ErrorCodeRewritePolicy/>
</Rewrite>
</Appenders>
<Loggers>
<Root level="${defaultLogLevel}">
<AppenderRef ref="Console-Appender" level="${consoleLogLevel}"/>
<AppenderRef ref="RollingFile-Appender"/>
<AppenderRef ref="Console-ErrorCode-Appender" level="${consoleLogLevel}"/>
<AppenderRef ref="RollingFile-ErrorCode-Appender"/>
</Root>
<Logger name="BasicInfo" additivity="false">
<AppenderRef ref="Console-Appender-Println"/>
<AppenderRef ref="RollingFile-Appender" />
<AppenderRef ref="Console-ErrorCode-Appender-Println"/>
<AppenderRef ref="RollingFile-ErrorCode-Appender"/>
</Logger>
<Logger name="org.hibernate.SQL" level="info" additivity="false">
<AppenderRef ref="Console-Appender"/>
<AppenderRef ref="RollingFile-Appender"/>
<AppenderRef ref="Console-ErrorCode-Appender"/>
<AppenderRef ref="RollingFile-ErrorCode-Appender"/>
</Logger>
<Logger name="org.apache.activemq.artemis.core.server" level="error" additivity="false">
<AppenderRef ref="Console-Appender"/>
<AppenderRef ref="RollingFile-Appender"/>
<AppenderRef ref="Console-ErrorCode-Appender"/>
<AppenderRef ref="RollingFile-ErrorCode-Appender"/>
</Logger>
<Logger name="org.jolokia" additivity="true" level="warn">
<AppenderRef ref="Console-Appender-Println"/>
<AppenderRef ref="RollingFile-Appender" />
<AppenderRef ref="Console-ErrorCode-Appender-Println"/>
<AppenderRef ref="RollingFile-ErrorCode-Appender"/>
</Logger>
</Loggers>
</Configuration>

View File

@ -191,7 +191,7 @@ Unreleased
* Configuration file changes:
* Added program line argument ``on-unknown-config-keys`` to allow specifying behaviour on unknown node configuration property keys.
Values are: [FAIL, WARN, IGNORE], default to FAIL if unspecified.
Values are: [FAIL, IGNORE], default to FAIL if unspecified.
* Introduced a placeholder for custom properties within ``node.conf``; the property key is "custom".
* The deprecated web server now has its own ``web-server.conf`` file, separate from ``node.conf``.
* Property keys with double quotes (e.g. "key") in ``node.conf`` are no longer allowed, for rationale refer to :doc:`corda-configuration-file`.

View File

@ -30,7 +30,8 @@ when it finds double quotes around keys.
This prevents configuration errors when mixing keys that are wrapped in double quotes and contain a dot (.) with keys that don't, e.g.:
If there was a property ``"dataSourceProperties.dataSourceClassName" = "val1"`` in ``node.conf``, it would not overwrite the property ``dataSourceProperties.dataSourceClassName = "val2"`` in ``reference.conf``, potentially leading to an error that would be hard to spot.
By default the node will fail to start in presence of unknown property keys. To alter this behaviour, the command line argument ``on-unknown-config-keys`` can be set to ``WARN`` or ``IGNORE``. Default is ``FAIL`` if unspecified.
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 ``IGNORE``. Default is ``FAIL`` if unspecified.
Defaults
--------

View File

@ -8,7 +8,7 @@ import org.jetbrains.annotations.NotNull;
public class TemplateContract implements Contract {
// This is used to identify our contract when building a transaction.
public static final String TEMPLATE_CONTRACT_ID = "com.template.TemplateContract";
public static final String ID = "com.template.TemplateContract";
/**
* A transaction is considered valid if the verify() function of the contract of each of the transaction's input

View File

@ -13,8 +13,6 @@ import net.corda.core.transactions.SignedTransaction;
import net.corda.core.transactions.TransactionBuilder;
import net.corda.core.utilities.ProgressTracker;
import static com.template.TemplateContract.TEMPLATE_CONTRACT_ID;
// Replace Initiator's definition with:
@InitiatingFlow
@StartableByRPC
@ -53,7 +51,7 @@ public class IOUFlow extends FlowLogic<Void> {
// We create a transaction builder and add the components.
TransactionBuilder txBuilder = new TransactionBuilder(notary)
.addOutputState(outputState, TEMPLATE_CONTRACT_ID)
.addOutputState(outputState, TemplateContract.ID)
.addCommand(cmd);
// Signing the transaction.

View File

@ -18,7 +18,7 @@ import static net.corda.core.contracts.ContractsDSL.requireThat;
// Replace TemplateContract's definition with:
public class IOUContract implements Contract {
public static final String IOU_CONTRACT_ID = "com.template.IOUContract";
public static final String ID = "com.template.IOUContract";
// Our Create command.
public static class Create implements CommandData {

View File

@ -52,7 +52,7 @@ public class IOUFlow extends FlowLogic<Void> {
// We create the transaction components.
IOUState outputState = new IOUState(iouValue, getOurIdentity(), otherParty);
StateAndContract outputContractAndState = new StateAndContract(outputState, IOUContract.IOU_CONTRACT_ID);
StateAndContract outputContractAndState = new StateAndContract(outputState, IOUContract.ID);
List<PublicKey> requiredSigners = ImmutableList.of(getOurIdentity().getOwningKey(), otherParty.getOwningKey());
Command cmd = new Command<>(new IOUContract.Create(), requiredSigners);

View File

@ -16,8 +16,6 @@ import net.corda.core.identity.Party
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
import com.template.TemplateContract.TEMPLATE_CONTRACT_ID
// Replace Initiator's definition with:
@InitiatingFlow
@StartableByRPC
@ -39,7 +37,7 @@ class IOUFlow(val iouValue: Int,
// We create a transaction builder and add the components.
val txBuilder = TransactionBuilder(notary = notary)
.addOutputState(outputState, TEMPLATE_CONTRACT_ID)
.addOutputState(outputState, TemplateContract.ID)
.addCommand(cmd)
// We sign the transaction.

View File

@ -8,10 +8,11 @@ import net.corda.core.transactions.LedgerTransaction
// Add these imports:
import net.corda.core.contracts.*
// Replace IOUContract's contract ID and definition with:
const val IOU_CONTRACT_ID = "com.template.IOUContract"
class IOUContract : Contract {
companion object {
const val ID = "com.template.IOUContract"
}
// Our Create command.
class Create : CommandData

View File

@ -36,7 +36,7 @@ class IOUFlow(val iouValue: Int,
// We create the transaction components.
val outputState = IOUState(iouValue, ourIdentity, otherParty)
val outputContractAndState = StateAndContract(outputState, IOU_CONTRACT_ID)
val outputContractAndState = StateAndContract(outputState, IOUContract.ID)
val cmd = Command(IOUContract.Create(), listOf(ourIdentity.owningKey, otherParty.owningKey))
// We add the items to the builder.

View File

@ -3,6 +3,7 @@ package net.corda.docs
import net.corda.core.internal.toPath
import net.corda.node.services.config.ConfigHelper
import net.corda.node.services.config.parseAsNodeConfiguration
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.nio.file.Path
import java.nio.file.Paths
@ -27,14 +28,9 @@ class ExampleConfigTest {
@Test
fun `example node_confs parses fine`() {
readAndCheckConfigurations(
"example-node.conf"
) {
readAndCheckConfigurations("example-node.conf") {
val baseDirectory = Paths.get("some-example-base-dir")
ConfigHelper.loadConfig(
baseDirectory = baseDirectory,
configFile = it
).parseAsNodeConfiguration()
assertThat(ConfigHelper.loadConfig(baseDirectory = baseDirectory, configFile = it).parseAsNodeConfiguration().isValid).isTrue()
}
}
}

View File

@ -40,7 +40,7 @@ FlowLogic
---------
All flows must subclass ``FlowLogic``. You then define the steps taken by the flow by overriding ``FlowLogic.call``.
Let's define our ``IOUFlow`` in either ``TemplateFlow.java`` or ``App.kt``. Delete the two existing flows in the
Let's define our ``IOUFlow`` in either ``Initiator.java`` or ``Flows.kt``. Delete the two existing flows in the
template (``Initiator`` and ``Responder``), and replace them with the following:
.. container:: codeset
@ -55,7 +55,7 @@ template (``Initiator`` and ``Responder``), and replace them with the following:
:start-after: DOCSTART 01
:end-before: DOCEND 01
If you're following along in Java, you'll also need to rename ``TemplateFlow.java`` to ``IOUFlow.java``. Let's walk
If you're following along in Java, you'll also need to rename ``Initiator.java`` to ``IOUFlow.java``. Let's walk
through this code step-by-step.
We've defined our own ``FlowLogic`` subclass that overrides ``FlowLogic.call``. ``FlowLogic.call`` has a return type

View File

@ -51,7 +51,7 @@ The template has a number of files, but we can ignore most of them. We will only
cordapp-contracts-states/src/main/java/com/template/TemplateState.java
// 2. The flow
cordapp/src/main/java/com/template/TemplateFlow.java
cordapp/src/main/java/com/template/Initiator.java
.. code-block:: kotlin
@ -59,15 +59,7 @@ The template has a number of files, but we can ignore most of them. We will only
cordapp-contracts-states/src/main/kotlin/com/template/StatesAndContracts.kt
// 2. The flow
cordapp/src/main/kotlin/com/template/App.kt
Clean up
--------
To prevent build errors later on, we should delete the following files before we begin:
* Java: ``cordapp/src/main/java/com/template/TemplateClient.java``
* Kotlin: ``cordapp/src/main/kotlin/com/template/Client.kt``
cordapp/src/main/kotlin/com/template/Flows.kt
Progress so far
---------------

View File

@ -51,7 +51,7 @@ The node can optionally be started with the following command-line options:
* ``--config-file``, ``-f``: The path to the config file. Defaults to ``node.conf``.
* ``--dev-mode``, ``-d``: Runs the node in development mode. Unsafe in production. Defaults to true on MacOS and desktop versions of Windows. False otherwise.
* ``--no-local-shell``, ``-n``: Do not start the embedded shell locally.
* ``--on-unknown-config-keys <[FAIL,WARN,INFO]>``: How to behave on unknown node configuration. Defaults to FAIL.
* ``--on-unknown-config-keys <[FAIL,INFO]>``: How to behave on unknown node configuration. Defaults to FAIL.
* ``--sshd``: Enables SSH server for node administration.
* ``--sshd-port``: Sets the port for the SSH server. If not supplied and SSH server is enabled, the port defaults to 2222.
* ``--verbose``, ``--log-to-console``, ``-v``: If set, prints logging to the console as well as to a file.

View File

@ -17,7 +17,7 @@ We'll do this by modifying the flow we wrote in the previous tutorial.
Verifying the transaction
-------------------------
In ``IOUFlow.java``/``App.kt``, change the imports block to the following:
In ``IOUFlow.java``/``Flows.kt``, change the imports block to the following:
.. container:: codeset

View File

@ -224,6 +224,8 @@ private fun <T : Enum<T>> enumBridge(clazz: Class<T>, name: String): T {
*/
fun Any.toConfig(): Config = ConfigValueFactory.fromMap(toConfigMap()).toConfig()
fun Any.toConfigValue(): ConfigValue = if (this is ConfigValue) this else ConfigValueFactory.fromAnyRef(convertValue(this))
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
// Reflect over the fields of the receiver and generate a value Map that can use to create Config object.
private fun Any.toConfigMap(): Map<String, Any> {
@ -255,6 +257,28 @@ private fun Any.toConfigMap(): Map<String, Any> {
return values
}
private fun convertValue(value: Any): Any {
return if (value is String || value is Boolean || value is Number) {
// These types are supported by Config as use as is
value
} else if (value is Temporal || value is NetworkHostAndPort || value is CordaX500Name || value is Path || value is URL || value is UUID || value is X500Principal) {
// These types make sense to be represented as Strings and the exact inverse parsing function for use in parseAs
value.toString()
} else if (value is Enum<*>) {
// Expicitly use the Enum's name in case the toString is overridden, which would make parsing problematic.
value.name
} else if (value is Properties) {
// For Properties we treat keys with . as nested configs
ConfigFactory.parseMap(uncheckedCast(value)).root()
} else if (value is Iterable<*>) {
value.toConfigIterable()
} else {
// Else this is a custom object recursed over
value.toConfigMap()
}
}
// For Iterables figure out the type parameter and apply the same logic as above on the individual elements.
private fun Iterable<*>.toConfigIterable(field: Field): Iterable<Any?> {
val elementType = (field.genericType as ParameterizedType).actualTypeArguments[0] as Class<*>
@ -282,6 +306,8 @@ private fun Iterable<*>.toConfigIterable(field: Field): Iterable<Any?> {
}
}
private fun Iterable<*>.toConfigIterable(): Iterable<Any?> = map { element -> element?.let(::convertValue) }
// The typesafe .getBoolean function is case sensitive, this is a case insensitive version
fun Config.getBooleanCaseInsensitive(path: String): Boolean {
try {
@ -300,7 +326,6 @@ private val logger = LoggerFactory.getLogger("net.corda.nodeapi.internal.config"
enum class UnknownConfigKeysPolicy(private val handle: (Set<String>, logger: Logger) -> Unit) {
FAIL({ unknownKeys, _ -> throw UnknownConfigurationKeysException.of(unknownKeys) }),
WARN({ unknownKeys, logger -> logger.warn("Unknown configuration keys found: ${unknownKeys.joinToString(", ", "[", "]")}.") }),
IGNORE({ _, _ -> });
fun handle(unknownKeys: Set<String>, logger: Logger) {

View File

@ -22,14 +22,22 @@ 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 runMigration: Boolean = false,
val initialiseSchema: Boolean = true,
val transactionIsolationLevel: TransactionIsolationLevel = TransactionIsolationLevel.REPEATABLE_READ,
val runMigration: Boolean = Defaults.runMigration,
val initialiseSchema: Boolean = Defaults.initialiseSchema,
val transactionIsolationLevel: TransactionIsolationLevel = Defaults.transactionIsolationLevel,
val schema: String? = null,
val exportHibernateJMXStatistics: Boolean = false,
val exportHibernateJMXStatistics: Boolean = Defaults.exportHibernateJMXStatistics,
val hibernateDialect: String? = null,
val mappedSchemaCacheSize: Long = 100
)
val mappedSchemaCacheSize: Long = Defaults.mappedSchemaCacheSize
) {
object Defaults {
val runMigration = false
val initialiseSchema = true
val transactionIsolationLevel = TransactionIsolationLevel.REPEATABLE_READ
val exportHibernateJMXStatistics = false
val mappedSchemaCacheSize = 100L
}
}
// This class forms part of the node config and so any changes to it must be handled with care
enum class TransactionIsolationLevel {

View File

@ -73,6 +73,7 @@ dependencies {
compile project(':tools:shell')
compile project(':tools:cliutils')
compile project(':common-validation')
compile project(':common-configuration-parsing')
// Log4J: logging framework (with SLF4J bindings)
compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"

View File

@ -1,10 +1,17 @@
package net.corda.node
import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigFactory
import net.corda.common.configuration.parsing.internal.Configuration
import net.corda.common.validation.internal.Validated
import net.corda.common.validation.internal.Validated.Companion.invalid
import net.corda.common.validation.internal.Validated.Companion.valid
import net.corda.core.internal.div
import net.corda.core.utilities.loggerFor
import net.corda.node.services.config.ConfigHelper
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.Valid
import net.corda.node.services.config.parseAsNodeConfiguration
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
import picocli.CommandLine.Option
@ -12,6 +19,9 @@ import java.nio.file.Path
import java.nio.file.Paths
open class SharedNodeCmdLineOptions {
private companion object {
private val logger by lazy { loggerFor<SharedNodeCmdLineOptions>() }
}
@Option(
names = ["-b", "--base-directory"],
description = ["The node working directory where all the files are kept."]
@ -37,9 +47,19 @@ open class SharedNodeCmdLineOptions {
)
var devMode: Boolean? = null
open fun parseConfiguration(configuration: Config): NodeConfiguration = configuration.parseAsNodeConfiguration(unknownConfigKeysPolicy::handle)
open fun parseConfiguration(configuration: Config): Valid<NodeConfiguration> {
open fun rawConfiguration(): Config = ConfigHelper.loadConfig(baseDirectory, configFile)
val option = Configuration.Validation.Options(strict = unknownConfigKeysPolicy == UnknownConfigKeysPolicy.FAIL)
return configuration.parseAsNodeConfiguration(option)
}
open fun rawConfiguration(): Validated<Config, ConfigException> {
return try {
valid(ConfigHelper.loadConfig(baseDirectory, configFile))
} catch (e: ConfigException) {
return invalid(e)
}
}
fun copyFrom(other: SharedNodeCmdLineOptions) {
baseDirectory = other.baseDirectory
@ -47,11 +67,32 @@ open class SharedNodeCmdLineOptions {
unknownConfigKeysPolicy= other.unknownConfigKeysPolicy
devMode = other.devMode
}
fun logRawConfigurationErrors(errors: Set<ConfigException>) {
if (errors.isNotEmpty()) {
logger.error("There were error(s) while attempting to load the node configuration:")
}
errors.forEach { error ->
when (error) {
is ConfigException.IO -> logger.error(configFileNotFoundMessage(configFile))
else -> logger.error(error.message, error)
}
}
}
private fun configFileNotFoundMessage(configFile: Path): String {
return """
Unable to load the node config file from '$configFile'.
Try setting the --base-directory flag to change which directory the node
is looking in, or use the --config-file flag to specify it explicitly.
""".trimIndent()
}
}
class InitialRegistrationCmdLineOptions : SharedNodeCmdLineOptions() {
override fun parseConfiguration(configuration: Config): NodeConfiguration {
return super.parseConfiguration(configuration).also { config ->
override fun parseConfiguration(configuration: Config): Valid<NodeConfiguration> {
return super.parseConfiguration(configuration).doIfValid { config ->
require(!config.devMode) { "Registration cannot occur in development mode" }
require(config.compatibilityZoneURL != null || config.networkServices != null) {
"compatibilityZoneURL or networkServices must be present in the node configuration file in registration mode."
@ -121,8 +162,8 @@ open class NodeCmdLineOptions : SharedNodeCmdLineOptions() {
)
var networkRootTrustStorePassword: String? = null
override fun parseConfiguration(configuration: Config): NodeConfiguration {
return super.parseConfiguration(configuration).also { config ->
override fun parseConfiguration(configuration: Config): Valid<NodeConfiguration> {
return super.parseConfiguration(configuration).doIfValid { config ->
if (isRegistration) {
require(!config.devMode) { "Registration cannot occur in development mode" }
require(config.compatibilityZoneURL != null || config.networkServices != null) {
@ -132,7 +173,7 @@ open class NodeCmdLineOptions : SharedNodeCmdLineOptions() {
}
}
override fun rawConfiguration(): Config {
override fun rawConfiguration(): Validated<Config, ConfigException> {
val configOverrides = mutableMapOf<String, Any>()
configOverrides += "noLocalShell" to noLocalShell
if (sshdServer) {
@ -141,7 +182,11 @@ open class NodeCmdLineOptions : SharedNodeCmdLineOptions() {
devMode?.let {
configOverrides += "devMode" to it
}
return ConfigHelper.loadConfig(baseDirectory, configFile, configOverrides = ConfigFactory.parseMap(configOverrides))
return try {
valid(ConfigHelper.loadConfig(baseDirectory, configFile, configOverrides = ConfigFactory.parseMap(configOverrides)))
} catch (e: ConfigException) {
return invalid(e)
}
}
}

View File

@ -15,6 +15,7 @@ import net.corda.node.internal.Node.Companion.isInvalidJavaVersion
import net.corda.node.internal.cordapp.MultipleCordappsForFlowException
import net.corda.node.internal.subcommands.*
import net.corda.node.internal.subcommands.ValidateConfigurationCli.Companion.logConfigurationErrors
import net.corda.node.internal.subcommands.ValidateConfigurationCli.Companion.logRawConfig
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.shouldStartLocalShell
import net.corda.node.services.config.shouldStartSSHDaemon
@ -35,7 +36,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 {
@ -140,7 +140,8 @@ open class NodeStartup : NodeStartupLogging {
Node.printBasicNodeInfo(LOGS_CAN_BE_FOUND_IN_STRING, System.getProperty("log-path"))
// Step 5. Load and validate node configuration.
val configuration = cmdLineOptions.nodeConfiguration().doOnErrors { errors -> logConfigurationErrors(errors, cmdLineOptions.configFile) }.optional ?: return ExitCodes.FAILURE
val rawConfig = cmdLineOptions.rawConfiguration().doOnErrors(cmdLineOptions::logRawConfigurationErrors).optional ?: return ExitCodes.FAILURE
val configuration = cmdLineOptions.parseConfiguration(rawConfig).doIfValid { logRawConfig(rawConfig) }.doOnErrors(::logConfigurationErrors).optional ?: return ExitCodes.FAILURE
// Step 6. Configuring special serialisation requirements, i.e., bft-smart relies on Java serialization.
attempt { banJavaSerialisation(configuration) }.doOnException { error -> error.logAsUnexpected("Exception while configuring serialisation") } as? Try.Success

View File

@ -1,41 +1,32 @@
package net.corda.node.internal.subcommands
import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigRenderOptions
import net.corda.cliutils.CliWrapperBase
import net.corda.cliutils.ExitCodes
import net.corda.common.validation.internal.Validated
import net.corda.common.validation.internal.Validated.Companion.invalid
import net.corda.common.validation.internal.Validated.Companion.valid
import net.corda.common.configuration.parsing.internal.Configuration
import net.corda.core.utilities.loggerFor
import net.corda.node.SharedNodeCmdLineOptions
import net.corda.node.internal.initLogging
import net.corda.node.services.config.NodeConfiguration
import picocli.CommandLine.*
import java.nio.file.Path
import net.corda.node.services.config.schema.v1.V1NodeConfigurationSpec
import net.corda.nodeapi.internal.config.toConfigValue
import picocli.CommandLine.Mixin
internal class ValidateConfigurationCli : CliWrapperBase("validate-configuration", "Validate the configuration without starting the node.") {
internal companion object {
private val logger by lazy { loggerFor<ValidateConfigurationCli>() }
internal fun logConfigurationErrors(errors: Iterable<Exception>, configFile: Path) {
errors.forEach { error ->
when (error) {
is ConfigException.IO -> logger.error(configFileNotFoundMessage(configFile))
else -> logger.error("Error while parsing node configuration.", error)
}
}
private val configRenderingOptions = ConfigRenderOptions.defaults().setFormatted(true).setComments(false).setOriginComments(false)
internal fun logConfigurationErrors(errors: Iterable<Configuration.Validation.Error>) {
logger.error(errors.joinToString(System.lineSeparator(), "Error(s) while parsing node configuration:${System.lineSeparator()}") { error -> "\t- ${error.description()}" })
}
private fun configFileNotFoundMessage(configFile: Path): String {
return """
Unable to load the node config file from '$configFile'.
Try setting the --base-directory flag to change which directory the node
is looking in, or use the --config-file flag to specify it explicitly.
""".trimIndent()
private fun Configuration.Validation.Error.description(): String {
return "for path: \"$pathAsString\": $message"
}
internal fun logRawConfig(config: Config) = logger.debug("Actual configuration:\n${V1NodeConfigurationSpec.describe(config, Any::toConfigValue).render(configRenderingOptions)}")
}
@Mixin
@ -44,41 +35,7 @@ internal class ValidateConfigurationCli : CliWrapperBase("validate-configuration
override fun initLogging() = initLogging(cmdLineOptions.baseDirectory)
override fun runProgram(): Int {
val configuration = cmdLineOptions.nodeConfiguration()
if (configuration.isInvalid) {
logConfigurationErrors(configuration.errors, cmdLineOptions.configFile)
return ExitCodes.FAILURE
}
return ExitCodes.SUCCESS
val rawConfig = cmdLineOptions.rawConfiguration().doOnErrors(cmdLineOptions::logRawConfigurationErrors).optional ?: return ExitCodes.FAILURE
return cmdLineOptions.parseConfiguration(rawConfig).doIfValid { logRawConfig(rawConfig) }.doOnErrors(::logConfigurationErrors).optional?.let { ExitCodes.SUCCESS } ?: ExitCodes.FAILURE
}
}
internal fun SharedNodeCmdLineOptions.nodeConfiguration(): Valid<NodeConfiguration> = NodeConfigurationParser.invoke(this)
private object NodeConfigurationParser : (SharedNodeCmdLineOptions) -> Valid<NodeConfiguration> {
private val logger by lazy { loggerFor<ValidateConfigurationCli>() }
private val configRenderingOptions = ConfigRenderOptions.defaults().setComments(false).setOriginComments(false).setFormatted(true)
override fun invoke(cmds: SharedNodeCmdLineOptions): Valid<NodeConfiguration> {
return attempt(cmds::rawConfiguration).doIfValid(::log).attemptMap(cmds::parseConfiguration).mapValid(::validate)
}
internal fun log(config: Config) = logger.debug("Actual configuration:\n${config.root().render(configRenderingOptions)}")
private fun validate(configuration: NodeConfiguration): Valid<NodeConfiguration> {
return Validated.withResult(configuration, configuration.validate().asSequence().map { error -> IllegalArgumentException(error) }.toSet())
}
private fun <VALUE, MAPPED> Valid<VALUE>.attemptMap(convert: (VALUE) -> MAPPED): Valid<MAPPED> = mapValid { value -> attempt { convert.invoke(value) } }
private fun <VALUE> attempt(action: () -> VALUE): Valid<VALUE> {
return try {
valid(action.invoke())
} catch (exception: Exception) {
return invalid(exception)
}
}
}
private typealias Valid<TARGET> = Validated<TARGET, Exception>
}

View File

@ -10,33 +10,46 @@ import net.corda.nodeapi.internal.config.MutualSslConfiguration
data class EnterpriseConfiguration(
val mutualExclusionConfiguration: MutualExclusionConfiguration,
val externalBrokerConnectionConfiguration: ExternalBrokerConnectionConfiguration = ExternalBrokerConnectionConfiguration.DEFAULT,
val externalBrokerBackupAddresses: List<NetworkHostAndPort> = emptyList(),
val externalBrokerConnectionConfiguration: ExternalBrokerConnectionConfiguration = Defaults.externalBrokerConnectionConfiguration,
val externalBrokerBackupAddresses: List<NetworkHostAndPort> = Defaults.externalBrokerBackupAddresses,
val messagingServerSslConfiguration: MessagingServerSslConfiguration? = null,
val useMultiThreadedSMM: Boolean = true,
val tuning: PerformanceTuning = PerformanceTuning.default,
val useMultiThreadedSMM: Boolean = Defaults.useMultiThreadedSMM,
val tuning: PerformanceTuning = Defaults.tuning,
val externalBridge: Boolean? = null,
val enableCacheTracing: Boolean = false,
val enableCacheTracing: Boolean = Defaults.enableCacheTracing,
val traceTargetDirectory: Path = Defaults.traceTargetDirectory
) {
internal object Defaults {
val externalBrokerConnectionConfiguration: ExternalBrokerConnectionConfiguration = ExternalBrokerConnectionConfiguration.DEFAULT
val externalBrokerBackupAddresses: List<NetworkHostAndPort> = emptyList()
val useMultiThreadedSMM: Boolean = true
val tuning: PerformanceTuning = PerformanceTuning.default
val enableCacheTracing: Boolean = false
val traceTargetDirectory: Path = File(".").toPath()
)
}
}
data class MessagingServerSslConfiguration(private val sslKeystore: Path,
private val keyStorePassword: String,
private val trustStoreFile: Path,
private val trustStorePassword: String,
override val useOpenSsl: Boolean = false) : MutualSslConfiguration {
override val useOpenSsl: Boolean = Defaults.useOpenSsl) : MutualSslConfiguration {
internal object Defaults {
val useOpenSsl: Boolean = false
}
override val keyStore = FileBasedCertificateStoreSupplier(sslKeystore, keyStorePassword, keyStorePassword)
override val trustStore = FileBasedCertificateStoreSupplier(trustStoreFile, trustStorePassword, trustStorePassword)
}
data class MutualExclusionConfiguration(val on: Boolean = false,
val machineName: String = defaultMachineName,
data class MutualExclusionConfiguration(val on: Boolean = Defaults.on,
val machineName: String = Defaults.machineName,
val updateInterval: Long,
val waitInterval: Long
) {
companion object {
private val defaultMachineName = InetAddress.getLocalHost().hostName
internal object Defaults {
val machineName = InetAddress.getLocalHost().hostName
val on: Boolean = false
}
}

View File

@ -1,25 +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.CordaPersistence.DataSourceConfigTag
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
@ -29,10 +26,6 @@ import javax.security.auth.x500.X500Principal
val Int.MB: Long get() = this * 1024L * 1024L
val Int.KB: Long get() = this * 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
@ -98,23 +91,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 {
@ -135,14 +129,27 @@ 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
}
}
data class GraphiteOptions(
val server: String,
val port: Int,
val prefix: String? = null, // defaults to org name and ip address when null
val prefix: String? = Defaults.prefix, // defaults to org name and ip address when null
// This typo leaking in the node.conf is brilliant
val sampleInvervallSeconds: Long = Defaults.sampleInvervallSeconds
) {
internal object Defaults {
val prefix: String? = null
val sampleInvervallSeconds: Long = 60
)
}
}
fun NodeConfiguration.shouldCheckCheckpoints(): Boolean {
return this.devMode && this.devModeOptions?.disableCheckpointChecker != true
@ -196,281 +203,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 relay: RelayConfiguration?,
// TODO This field is slightly redundant as p2pAddress is sufficient to hold the address of the node's MQ broker.
// Instead this should be a Boolean indicating whether that broker is an internal one started by the node or an external one
override val messagingServerAddress: NetworkHostAndPort?,
override val messagingServerExternal: Boolean = (messagingServerAddress != null),
override val enterpriseConfiguration: EnterpriseConfiguration,
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(exportHibernateJMXStatistics = devMode),
private val transactionCacheSizeMegaBytes: Int? = null,
private val attachmentContentCacheSizeMegaBytes: Int? = null,
override val attachmentCacheBound: Long = NodeConfiguration.defaultAttachmentCacheBound,
override val graphiteOptions: GraphiteOptions? = null,
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 enableSNI: Boolean = true,
private val useOpenSsl: Boolean = false,
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
}
}
if (messagingServerExternal && messagingServerAddress != null) {
require(enterpriseConfiguration.messagingServerSslConfiguration != null) {"Missing SSL configuration required by broker connection."}
}
}
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, useOpenSsl)
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 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("'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"
}
// ensure our datasource configuration is sane
require(dataSourceProperties.get("autoCommit") != true) { "Datbase auto commit cannot be enabled, Corda requires transactional behaviour" }
dataSourceProperties.set("autoCommit", false)
if (dataSourceProperties.get("transactionIsolation") == null) {
dataSourceProperties["transactionIsolation"] = database.transactionIsolationLevel.jdbcString
}
// enforce that SQLServer does not get sent all strings as Unicode - hibernate handles this "cleverly"
val dataSourceUrl = dataSourceProperties.getProperty(DataSourceConfigTag.DATA_SOURCE_URL, "")
if (dataSourceUrl.contains(":sqlserver:") && !dataSourceUrl.contains("sendStringParametersAsUnicode", true)) {
dataSourceProperties[DataSourceConfigTag.DATA_SOURCE_URL] = dataSourceUrl + ";sendStringParametersAsUnicode=false"
}
// Adjust connection pool size depending on N=flow thread pool size.
// If there is no configured pool size set it to N + 1, otherwise check that it's greater than N.
val flowThreadPoolSize = enterpriseConfiguration.tuning.flowThreadPoolSize
val maxConnectionPoolSize = dataSourceProperties.getProperty("maximumPoolSize")
if (maxConnectionPoolSize == null) {
dataSourceProperties.setProperty("maximumPoolSize", (flowThreadPoolSize + 1).toString())
} else {
require(maxConnectionPoolSize.toInt() > flowThreadPoolSize)
}
// Check for usage of deprecated config
@Suppress("DEPRECATION")
if (certificateChainCheckPolicies.isNotEmpty()) {
logger.warn("""You are configuring certificateChainCheckPolicies. This is a setting that is not used, and will be removed in a future version.
|Please contact the R3 team on the public slack to discuss your use case.
""".trimMargin())
}
// Support the deprecated method of configuring network services with a single compatibilityZoneURL option
if (compatibilityZoneURL != null && networkServices == null) {
networkServices = NetworkServicesConfig(compatibilityZoneURL, compatibilityZoneURL, inferred = true)
}
require(h2port == null || h2Settings == null) { "Cannot specify both 'h2port' and 'h2Settings' in configuration" }
}
}
data class NodeRpcSettings(
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?
@ -544,7 +279,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 {
@ -553,6 +288,10 @@ data class SecurityConfiguration(val authService: SecurityConfiguration.AuthServ
AuthDataSourceType.DB -> require(users == null && connection != null)
}
}
internal object Defaults {
val passwordEncryption = PasswordEncryption.NONE
}
}
companion object {
@ -579,4 +318,8 @@ data class RelayConfiguration(val relayHost: String,
val username: String,
val privateKeyFile: Path,
val publicKeyFile: Path,
val sshPort: Int = 22)
val sshPort: Int = Defaults.sshPort) {
internal object Defaults {
val sshPort = 22
}
}

View File

@ -0,0 +1,344 @@
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.CordaPersistence
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 relay: RelayConfiguration?,
override val messagingServerAddress: NetworkHostAndPort?,
override val messagingServerExternal: Boolean = Defaults.messagingServerExternal(messagingServerAddress),
override val enterpriseConfiguration: EnterpriseConfiguration,
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 graphiteOptions: GraphiteOptions? = Defaults.graphiteOptions,
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 enableSNI: Boolean = Defaults.enableSNI,
private val useOpenSsl: Boolean = Defaults.useOpenSsl,
override val flowOverrides: FlowOverrideConfig?,
override val cordappSignerKeyFingerprintBlacklist: List<String> = Defaults.cordappSignerKeyFingerprintBlacklist,
override val cryptoServiceName: SupportedCryptoServices? = Defaults.cryptoServiceName,
override val cryptoServiceConf: String? = Defaults.cryptoServiceConf
) : 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() }
val graphiteOptions: GraphiteOptions? = null
val enableSNI: Boolean = true
val useOpenSsl: Boolean = false
val cryptoServiceName: SupportedCryptoServices? = null
val cryptoServiceConf: String? = null
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 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
}
}
// This is a sanity feature do not remove.
require(!useTestClock || devMode) { "Cannot use test clock outside of dev mode" }
require(devModeOptions == null || devMode) { "Cannot use devModeOptions outside of dev mode" }
require(security == null || rpcUsers.isEmpty()) {
"Cannot specify both 'rpcUsers' and 'security' in configuration"
}
// ensure our datasource configuration is sane
require(dataSourceProperties["autoCommit"] != true) { "Datbase auto commit cannot be enabled, Corda requires transactional behaviour" }
dataSourceProperties["autoCommit"] = false
if (dataSourceProperties["transactionIsolation"] == null) {
dataSourceProperties["transactionIsolation"] = database.transactionIsolationLevel.jdbcString
}
// enforce that SQLServer does not get sent all strings as Unicode - hibernate handles this "cleverly"
val dataSourceUrl = dataSourceProperties.getProperty(CordaPersistence.DataSourceConfigTag.DATA_SOURCE_URL, "")
if (dataSourceUrl.contains(":sqlserver:") && !dataSourceUrl.contains("sendStringParametersAsUnicode", true)) {
dataSourceProperties[CordaPersistence.DataSourceConfigTag.DATA_SOURCE_URL] = "$dataSourceUrl;sendStringParametersAsUnicode=false"
}
// Adjust connection pool size depending on N=flow thread pool size.
// If there is no configured pool size set it to N + 1, otherwise check that it's greater than N.
val flowThreadPoolSize = enterpriseConfiguration.tuning.flowThreadPoolSize
val maxConnectionPoolSize = dataSourceProperties.getProperty("maximumPoolSize")
if (maxConnectionPoolSize == null) {
dataSourceProperties.setProperty("maximumPoolSize", (flowThreadPoolSize + 1).toString())
} else {
require(maxConnectionPoolSize.toInt() > flowThreadPoolSize)
}
@Suppress("DEPRECATION")
if (certificateChainCheckPolicies.isNotEmpty()) {
logger.warn("""You are configuring certificateChainCheckPolicies. This is a setting that is not used, and will be removed in a future version.
|Please contact the R3 team on the public Slack to discuss your use case.
""".trimMargin())
}
if (messagingServerExternal && messagingServerAddress != null) {
require(enterpriseConfiguration.messagingServerSslConfiguration != null) { "Missing SSL configuration required by broker connection." }
}
// 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, useOpenSsl)
override val rpcOptions: NodeRpcOptions
get() {
return actualRpcSettings.asOptions()
}
override val transactionCacheSizeBytes: Long
get() = transactionCacheSizeMegaBytes?.MB ?: super.transactionCacheSizeBytes
override val attachmentContentCacheSizeBytes: Long
get() = attachmentContentCacheSizeMegaBytes?.MB ?: super.attachmentContentCacheSizeBytes
override val effectiveH2Settings: NodeH2Settings?
get() = when {
h2port != null -> NodeH2Settings(address = NetworkHostAndPort(host = "localhost", port = h2port))
else -> h2Settings
}
fun validate(): List<String> {
val errors = mutableListOf<String>()
errors += validateDevModeOptions()
val rpcSettingsErrors = validateRpcSettings(rpcSettings)
errors += rpcSettingsErrors
if (rpcSettingsErrors.isEmpty()) {
// Forces lazy property to initialise in order to throw exceptions
rpcOptions
}
errors += validateTlsCertCrlConfig()
errors += validateNetworkServices()
errors += validateH2Settings()
errors += validateCryptoService()
return errors
}
private fun validateTlsCertCrlConfig(): List<String> {
val errors = mutableListOf<String>()
if (tlsCertCrlIssuer != null) {
if (tlsCertCrlDistPoint == null) {
errors += "'tlsCertCrlDistPoint' is mandatory when 'tlsCertCrlIssuer' is specified"
}
}
if (!crlCheckSoftFail && tlsCertCrlDistPoint == null) {
errors += "'tlsCertCrlDistPoint' is mandatory when 'crlCheckSoftFail' is false"
}
return errors
}
private fun validateH2Settings(): List<String> {
val errors = mutableListOf<String>()
if (h2port != null && h2Settings != null) {
errors += "cannot specify both 'h2port' and 'h2Settings'"
}
return errors
}
private fun validateCryptoService(): List<String> {
val errors = mutableListOf<String>()
if (cryptoServiceName == null && cryptoServiceConf != null) {
errors += "'cryptoServiceName' is mandatory when 'cryptoServiceConf' is specified"
}
return errors
}
private fun validateRpcSettings(options: NodeRpcSettings): List<String> {
val errors = mutableListOf<String>()
if (options.adminAddress == null) {
errors += "'rpcSettings.adminAddress' is mandatory"
}
if (options.useSsl && options.ssl == null) {
errors += "'rpcSettings.ssl' is mandatory when 'rpcSettings.useSsl' is specified"
}
return errors
}
private fun validateDevModeOptions(): List<String> {
if (devMode) {
compatibilityZoneURL?.let {
if (devModeOptions?.allowCompatibilityZone != true) {
return listOf("cannot specify 'compatibilityZoneURL' when 'devMode' is true, unless 'devModeOptions.allowCompatibilityZone' is also true")
}
}
// if compatibilityZoneURL is set then it will be copied into the networkServices field and thus skipping
// this check by returning above is fine.
networkServices?.let {
if (devModeOptions?.allowCompatibilityZone != true) {
return listOf("cannot specify 'networkServices' when 'devMode' is true, unless 'devModeOptions.allowCompatibilityZone' is also true")
}
}
}
return emptyList()
}
private fun validateNetworkServices(): List<String> {
val errors = mutableListOf<String>()
if (compatibilityZoneURL != null && networkServices != null && !(networkServices!!.inferred)) {
errors += "cannot specify both 'compatibilityZoneUrl' and 'networkServices'"
}
return errors
}
}
data class NodeRpcSettings(
val address: NetworkHostAndPort?,
val adminAddress: NetworkHostAndPort?,
val standAloneBroker: Boolean = Defaults.standAloneBroker,
val useSsl: Boolean = Defaults.useSsl,
val ssl: BrokerRpcSslOptions?
) {
internal object Defaults {
val standAloneBroker = false
val useSsl = false
}
fun asOptions(): NodeRpcOptions {
return object : NodeRpcOptions {
override val address = this@NodeRpcSettings.address!!
override val adminAddress = this@NodeRpcSettings.adminAddress!!
override val standAloneBroker = this@NodeRpcSettings.standAloneBroker
override val useSsl = this@NodeRpcSettings.useSsl
override val sslConfig = this@NodeRpcSettings.ssl
override fun toString(): String {
return "address: $address, adminAddress: $adminAddress, standAloneBroker: $standAloneBroker, useSsl: $useSsl, sslConfig: $sslConfig"
}
}
}
}

View File

@ -0,0 +1,51 @@
package net.corda.node.services.config.schema.parsers
import com.typesafe.config.Config
import com.typesafe.config.ConfigObject
import com.typesafe.config.ConfigUtil
import net.corda.common.configuration.parsing.internal.Configuration
import net.corda.common.validation.internal.Validated.Companion.invalid
import net.corda.common.validation.internal.Validated.Companion.valid
import net.corda.core.identity.CordaX500Name
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.services.config.Valid
import java.net.MalformedURLException
import java.net.URL
import java.nio.file.InvalidPathException
import java.nio.file.Path
import java.nio.file.Paths
import java.util.*
import javax.security.auth.x500.X500Principal
internal fun toProperties(rawValue: ConfigObject): Properties = rawValue.toConfig().toProperties()
private fun Config.toProperties() = entrySet().associateByTo(Properties(), { ConfigUtil.splitPath(it.key).joinToString(".") }, { it.value.unwrapped().toString() })
internal fun toCordaX500Name(rawValue: String) = attempt<CordaX500Name, IllegalArgumentException> { CordaX500Name.parse(rawValue) }
internal fun toURL(rawValue: String) = attempt<URL, MalformedURLException> { URL(rawValue) }
internal fun toUUID(rawValue: String) = attempt<UUID, IllegalArgumentException> { UUID.fromString(rawValue) }
internal fun toNetworkHostAndPort(rawValue: String) = attempt<NetworkHostAndPort, IllegalArgumentException> { NetworkHostAndPort.parse(rawValue) }
internal fun toPrincipal(rawValue: String) = attempt<X500Principal, IllegalArgumentException> { X500Principal(rawValue) }
internal fun toPath(rawValue: String) = attempt<Path, InvalidPathException> { Paths.get(rawValue) }
private inline fun <RESULT, reified ERROR : Exception> attempt(action: () -> RESULT, message: (ERROR) -> String): Valid<RESULT> {
return try {
valid(action.invoke())
} catch (e: Exception) {
when (e) {
is ERROR -> badValue(message.invoke(e))
else -> throw e
}
}
}
internal inline fun <reified RESULT, reified ERROR : Exception> attempt(action: () -> RESULT) = attempt(action, { e: ERROR -> "value does not comply with ${RESULT::class.java.simpleName} specification (${e.message})" })
internal fun <RESULT> validValue(result: RESULT) = valid<RESULT, Configuration.Validation.Error>(result)
internal fun <RESULT> badValue(message: String) = invalid<RESULT, Configuration.Validation.Error>(Configuration.Validation.Error.BadValue.of(message))

View File

@ -0,0 +1,332 @@
@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.EnterpriseConfiguration
import net.corda.node.services.config.FlowOverride
import net.corda.node.services.config.FlowOverrideConfig
import net.corda.node.services.config.FlowTimeoutConfiguration
import net.corda.node.services.config.GraphiteOptions
import net.corda.node.services.config.MessagingServerSslConfiguration
import net.corda.node.services.config.MutualExclusionConfiguration
import net.corda.node.services.config.NetworkServicesConfig
import net.corda.node.services.config.NodeH2Settings
import net.corda.node.services.config.NodeRpcSettings
import net.corda.node.services.config.NotaryConfig
import net.corda.node.services.config.PasswordEncryption
import net.corda.node.services.config.PerformanceTuning
import net.corda.node.services.config.RelayConfiguration
import net.corda.node.services.config.SecurityConfiguration
import net.corda.node.services.config.SecurityConfiguration.AuthService.Companion.defaultAuthServiceId
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.ExternalBrokerConnectionConfiguration
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)
private val runMigration by boolean().optional().withDefaultValue(DatabaseConfig.Defaults.runMigration)
private val hibernateDialect by string().optional()
private val schema by string().optional()
override fun parseValid(configuration: Config): Valid<DatabaseConfig> {
return valid(DatabaseConfig(configuration[runMigration], configuration[initialiseSchema], configuration[transactionIsolationLevel], configuration[schema], configuration[exportHibernateJMXStatistics], configuration[hibernateDialect], 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]))
}
}
internal object RelayConfigurationSpec : Configuration.Specification<RelayConfiguration>("RelayConfiguration") {
private val relayHost by string()
private val remoteInboundPort by int()
private val username by string()
private val privateKeyFile by string().mapValid(::toPath)
private val publicKeyFile by string().mapValid(::toPath)
private val sshPort by int().optional().withDefaultValue(RelayConfiguration.Defaults.sshPort)
override fun parseValid(configuration: Config): Valid<RelayConfiguration> {
return valid(RelayConfiguration(configuration[relayHost], configuration[remoteInboundPort], configuration[username], configuration[privateKeyFile], configuration[publicKeyFile], configuration[sshPort]))
}
}
internal object GraphiteOptionsSpec : Configuration.Specification<GraphiteOptions>("GraphiteOptions") {
private val server by string()
private val port by int()
private val prefix by string().optional()
// TODO fix this typo, even if it means breaking the config schema
private val sampleInvervallSeconds by long().optional().withDefaultValue(GraphiteOptions.Defaults.sampleInvervallSeconds)
override fun parseValid(configuration: Config): Valid<GraphiteOptions> {
return valid(GraphiteOptions(configuration[server], configuration[port], configuration[prefix], configuration[sampleInvervallSeconds]))
}
}
internal object EnterpriseConfigurationSpec : Configuration.Specification<EnterpriseConfiguration>("EnterpriseConfiguration") {
private val mutualExclusionConfiguration by nested(MutualExclusionConfigurationSpec)
private val externalBrokerConnectionConfiguration by enum(ExternalBrokerConnectionConfiguration::class).optional().withDefaultValue(EnterpriseConfiguration.Defaults.externalBrokerConnectionConfiguration)
private val externalBrokerBackupAddresses by string().mapValid(::toNetworkHostAndPort).list().optional().withDefaultValue(EnterpriseConfiguration.Defaults.externalBrokerBackupAddresses)
private val useMultiThreadedSMM by boolean().optional().withDefaultValue(EnterpriseConfiguration.Defaults.useMultiThreadedSMM)
private val tuning by nested(PerformanceTuningSpec).optional().withDefaultValue(EnterpriseConfiguration.Defaults.tuning)
private val externalBridge by boolean().optional()
private val enableCacheTracing by boolean().optional().withDefaultValue(EnterpriseConfiguration.Defaults.enableCacheTracing)
private val traceTargetDirectory by string().mapValid(::toPath).optional().withDefaultValue(EnterpriseConfiguration.Defaults.traceTargetDirectory)
private val messagingServerSslConfiguration by nested(MessagingServerSslConfigurationSpec).optional()
override fun parseValid(configuration: Config): Valid<EnterpriseConfiguration> {
return valid(EnterpriseConfiguration(
configuration[mutualExclusionConfiguration],
configuration[externalBrokerConnectionConfiguration],
configuration[externalBrokerBackupAddresses],
configuration[messagingServerSslConfiguration],
configuration[useMultiThreadedSMM],
configuration[tuning],
configuration[externalBridge],
configuration[enableCacheTracing],
configuration[traceTargetDirectory])
)
}
}
internal object MessagingServerSslConfigurationSpec : Configuration.Specification<MessagingServerSslConfiguration>("MessagingServerSslConfiguration") {
private val sslKeystore by string().mapValid(::toPath)
private val keyStorePassword by string(sensitive = true)
private val trustStoreFile by string().mapValid(::toPath)
private val trustStorePassword by string(sensitive = true)
private val useOpenSsl by boolean().optional().withDefaultValue(MessagingServerSslConfiguration.Defaults.useOpenSsl)
override fun parseValid(configuration: Config): Valid<MessagingServerSslConfiguration> {
return valid(MessagingServerSslConfiguration(configuration[sslKeystore], configuration[keyStorePassword], configuration[trustStoreFile], configuration[trustStorePassword], configuration[useOpenSsl]))
}
}
internal object MutualExclusionConfigurationSpec : Configuration.Specification<MutualExclusionConfiguration>("MutualExclusionConfiguration") {
private val on by boolean().optional().withDefaultValue(MutualExclusionConfiguration.Defaults.on)
private val machineName by string().optional().withDefaultValue(MutualExclusionConfiguration.Defaults.machineName)
private val updateInterval by long()
private val waitInterval by long()
override fun parseValid(configuration: Config): Valid<MutualExclusionConfiguration> {
return valid(MutualExclusionConfiguration(configuration[on], configuration[machineName], configuration[updateInterval], configuration[waitInterval]))
}
}
internal object PerformanceTuningSpec : Configuration.Specification<PerformanceTuning>("PerformanceTuning") {
private val flowThreadPoolSize by int()
private val maximumMessagingBatchSize by int()
private val rpcThreadPoolSize by int()
private val p2pConfirmationWindowSize by int()
private val brokerConnectionTtlCheckIntervalMs by long()
override fun parseValid(configuration: Config): Valid<PerformanceTuning> {
return valid(PerformanceTuning(configuration[flowThreadPoolSize], configuration[maximumMessagingBatchSize], configuration[rpcThreadPoolSize], configuration[p2pConfirmationWindowSize], configuration[brokerConnectionTtlCheckIntervalMs]))
}
}

View File

@ -0,0 +1,159 @@
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()
private val relay by nested(RelayConfigurationSpec).optional()
private val enableSNI by boolean().optional().withDefaultValue(Defaults.enableSNI)
private val useOpenSsl by boolean().optional().withDefaultValue(Defaults.useOpenSsl)
private val graphiteOptions by nested(GraphiteOptionsSpec).optional()
private val enterpriseConfiguration by nested(EnterpriseConfigurationSpec)
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],
relay = configuration[relay],
enableSNI = configuration[enableSNI],
useOpenSsl = configuration[useOpenSsl],
graphiteOptions = configuration[graphiteOptions],
enterpriseConfiguration = configuration[enterpriseConfiguration]
))
} catch (e: Exception) {
return when (e) {
is ConfigException -> invalid(e.toValidationError(typeName = "NodeConfiguration"))
is IllegalArgumentException -> badValue(e.message!!)
else -> throw e
}
}
return result.mapValid { conf -> Valid.withResult(conf as NodeConfiguration, conf.validate().map(::toError).toSet()) }
}
}
private fun toError(validationErrorMessage: String): Configuration.Validation.Error = Configuration.Validation.Error.BadValue.of(validationErrorMessage)

View File

@ -1,2 +1,2 @@
log4jContextSelector=net.corda.node.utilities.logging.AsyncLoggerContextSelectorNoThreadLocal
Log4jContextSelector=net.corda.node.utilities.logging.AsyncLoggerContextSelectorNoThreadLocal
AsyncLogger.RingBufferSize=262144

View File

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

View File

@ -1,27 +1,33 @@
package net.corda.node.services.config
import com.typesafe.config.*
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigValueFactory
import com.zaxxer.hikari.HikariConfig
import net.corda.common.configuration.parsing.internal.Configuration
import net.corda.core.internal.toPath
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.nodeapi.internal.persistence.CordaPersistence.DataSourceConfigTag
import net.corda.core.utilities.seconds
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
import net.corda.nodeapi.internal.config.getBooleanCaseInsensitive
import net.corda.nodeapi.internal.persistence.CordaPersistence.DataSourceConfigTag
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import net.corda.tools.shell.SSHDConfiguration
import org.assertj.core.api.Assertions.*
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Test
import java.net.InetAddress
import java.net.URL
import java.net.URI
import java.net.URL
import java.nio.file.Paths
import javax.security.auth.x500.X500Principal
import java.util.*
import kotlin.test.*
import javax.security.auth.x500.X500Principal
import kotlin.test.assertFalse
import kotlin.test.assertNull
import kotlin.test.assertTrue
class NodeConfigurationImplTest {
@Test
@ -37,14 +43,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
@ -170,7 +178,7 @@ class NodeConfigurationImplTest {
@Test
fun `mutual exclusion machineName set to default if not explicitly set`() {
val config = getConfig("test-config-mutualExclusion-noMachineName.conf").parseAsNodeConfiguration(UnknownConfigKeysPolicy.IGNORE::handle)
val config = getConfig("test-config-mutualExclusion-noMachineName.conf").parseAsNodeConfiguration(options = Configuration.Validation.Options(strict = false)).orThrow()
assertEquals(InetAddress.getLocalHost().hostName, config.enterpriseConfiguration.mutualExclusionConfiguration.machineName)
}
@ -180,9 +188,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)
}
}
@ -228,7 +236,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
@ -237,7 +248,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
@ -247,7 +258,7 @@ 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
@ -265,7 +276,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())
}
@ -273,7 +284,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())
}
@ -281,15 +292,15 @@ class NodeConfigurationImplTest {
fun `jmxReporterType is not null and set to Jokolia`() {
var rawConfig = getConfig("working-config.conf", ConfigFactory.parseMap(mapOf("devMode" to true)))
rawConfig = rawConfig.withValue("jmxReporterType", ConfigValueFactory.fromAnyRef("JOLOKIA"))
val nodeConfig = rawConfig.parseAsNodeConfiguration()
val nodeConfig = rawConfig.parseAsNodeConfiguration().orThrow()
assertTrue(JmxReporterType.JOLOKIA.toString() == nodeConfig.jmxReporterType.toString())
}
private fun configDebugOptions(devMode: Boolean, devModeOptions: DevModeOptions?): NodeConfiguration {
private fun configDebugOptions(devMode: Boolean, devModeOptions: DevModeOptions?): NodeConfigurationImpl {
return testConfiguration.copy(devMode = devMode, devModeOptions = devModeOptions)
}
private fun configTlsCertCrlOptions(tlsCertCrlDistPoint: URL?, tlsCertCrlIssuer: String?, crlCheckSoftFail: Boolean = true): NodeConfiguration {
private fun configTlsCertCrlOptions(tlsCertCrlDistPoint: URL?, tlsCertCrlIssuer: String?, crlCheckSoftFail: Boolean = true): NodeConfigurationImpl {
return testConfiguration.copy(tlsCertCrlDistPoint = tlsCertCrlDistPoint, tlsCertCrlIssuer = tlsCertCrlIssuer?.let { X500Principal(it) }, crlCheckSoftFail = crlCheckSoftFail)
}

View File

@ -0,0 +1,12 @@
package net.corda.node.utilities.logging
import org.junit.Test
import kotlin.test.assertTrue
class AsyncLoggingTest {
@Test
fun `async logging is configured`() {
assertTrue(AsyncLoggerContextSelectorNoThreadLocal.isSelected())
}
}

View File

@ -139,7 +139,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
}
@ -736,11 +736,8 @@ class DriverDSLImpl(
* Simple holder class to capture the node configuration both as the raw [Config] object and the parsed [NodeConfiguration].
* Keeping [Config] around is needed as the user may specify extra config options not specified in [NodeConfiguration].
*/
private class NodeConfig(val typesafe: Config, val corda: NodeConfiguration = typesafe.parseAsNodeConfiguration()) {
init {
val errors = corda.validate()
require(errors.isEmpty()) { "Invalid node configuration. Errors where:\n${errors.joinToString("\n")}" }
}
private class NodeConfig(val typesafe: Config) {
val corda: NodeConfiguration = typesafe.parseAsNodeConfiguration().orThrow()
}
companion object {

View File

@ -118,12 +118,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)
return InProcessNode(parsedConfig, MOCK_VERSION_INFO.copy(platformVersion = platformVersion), flowManager)

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info">
<Configuration status="info" packages="net.corda.cliutils">
<Properties>
<Property name="log-path">${sys:log-path:-logs}</Property>
@ -37,7 +37,7 @@
<!-- Will generate up to 100 log files for a given day. During every rollover it will delete
those that are older than 60 days, but keep the most recent 10 GB -->
<RollingFile name="RollingFile-Appender"
<RollingRandomAccessFile name="RollingFile-Appender"
fileName="${log-path}/${log-name}.log"
filePattern="${archive}/${log-name}.%date{yyyy-MM-dd}-%i.log.gz">
@ -59,32 +59,47 @@
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
</RollingRandomAccessFile>
<Rewrite name="Console-ErrorCode-Appender">
<AppenderRef ref="Console-Appender"/>
<ErrorCodeRewritePolicy/>
</Rewrite>
<Rewrite name="Console-ErrorCode-Appender-Println">
<AppenderRef ref="Console-Appender-Println"/>
<ErrorCodeRewritePolicy/>
</Rewrite>
<Rewrite name="RollingFile-ErrorCode-Appender">
<AppenderRef ref="RollingFile-Appender"/>
<ErrorCodeRewritePolicy/>
</Rewrite>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console-Appender"/>
<AppenderRef ref="Console-ErrorCode-Appender"/>
</Root>
<Logger name="net.corda" level="${defaultLogLevel}" additivity="false">
<AppenderRef ref="Console-Appender"/>
<AppenderRef ref="RollingFile-Appender" />
<AppenderRef ref="Console-ErrorCode-Appender"/>
<AppenderRef ref="RollingFile-ErrorCode-Appender" />
</Logger>
<Logger name="BasicInfo" additivity="false">
<AppenderRef ref="Console-Appender-Println"/>
<AppenderRef ref="RollingFile-Appender" />
<AppenderRef ref="Console-ErrorCode-Appender-Println"/>
<AppenderRef ref="RollingFile-ErrorCode-Appender"/>
</Logger>
<Logger name="org.hibernate.SQL" level="info" additivity="false">
<AppenderRef ref="Console-Appender"/>
<AppenderRef ref="RollingFile-Appender"/>
<AppenderRef ref="Console-ErrorCode-Appender"/>
<AppenderRef ref="RollingFile-ErrorCode-Appender"/>
</Logger>
<Logger name="org.apache.activemq.artemis.core.server" level="error" additivity="false">
<AppenderRef ref="Console-Appender"/>
<AppenderRef ref="RollingFile-Appender"/>
<AppenderRef ref="Console-ErrorCode-Appender"/>
<AppenderRef ref="RollingFile-ErrorCode-Appender"/>
</Logger>
<Logger name="org.jolokia" additivity="true" level="warn">
<AppenderRef ref="Console-Appender-Println"/>
<AppenderRef ref="RollingFile-Appender" />
<AppenderRef ref="Console-ErrorCode-Appender-Println"/>
<AppenderRef ref="RollingFile-ErrorCode-Appender"/>
</Logger>
</Loggers>
</Configuration>

View File

@ -1,34 +0,0 @@
package net.corda.cliutils
import org.apache.logging.log4j.core.LoggerContext
import org.apache.logging.log4j.core.config.Configuration
import org.apache.logging.log4j.core.config.ConfigurationFactory
import org.apache.logging.log4j.core.config.ConfigurationSource
import org.apache.logging.log4j.core.config.Order
import org.apache.logging.log4j.core.config.plugins.Plugin
import org.apache.logging.log4j.core.config.xml.XmlConfiguration
import org.apache.logging.log4j.core.impl.LogEventFactory
@Plugin(name = "CordaLog4j2ConfigFactory", category = "ConfigurationFactory")
@Order(Integer.MAX_VALUE)
class CordaLog4j2ConfigFactory : ConfigurationFactory() {
private companion object {
private val SUPPORTED_TYPES = arrayOf(".xml", "*")
}
override fun getConfiguration(loggerContext: LoggerContext, source: ConfigurationSource): Configuration = ErrorCodeAppendingConfiguration(loggerContext, source)
override fun getSupportedTypes() = SUPPORTED_TYPES
private class ErrorCodeAppendingConfiguration(loggerContext: LoggerContext, source: ConfigurationSource) : XmlConfiguration(loggerContext, source) {
override fun doConfigure() {
super.doConfigure()
loggers.values.forEach {
val existingFactory = it.logEventFactory
it.logEventFactory = LogEventFactory { loggerName, marker, fqcn, level, message, properties, error ->
existingFactory.createEvent(loggerName, marker, fqcn, level, message?.withErrorCodeFor(error, level), properties, error)
}
}
}
}
}

View File

@ -0,0 +1,28 @@
package net.corda.cliutils
import org.apache.logging.log4j.core.Core
import org.apache.logging.log4j.core.LogEvent
import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy
import org.apache.logging.log4j.core.config.plugins.Plugin
import org.apache.logging.log4j.core.config.plugins.PluginFactory
import org.apache.logging.log4j.core.impl.Log4jLogEvent
@Plugin(name = "ErrorCodeRewritePolicy", category = Core.CATEGORY_NAME, elementType = "rewritePolicy", printObject = false)
class ErrorCodeRewritePolicy : RewritePolicy {
override fun rewrite(source: LogEvent?): LogEvent? {
val newMessage = source?.message?.withErrorCodeFor(source.thrown, source.level)
return if (newMessage == source?.message) {
source
} else {
Log4jLogEvent.Builder(source).setMessage(newMessage).build()
}
}
companion object {
@JvmStatic
@PluginFactory
fun createPolicy(): ErrorCodeRewritePolicy {
return ErrorCodeRewritePolicy()
}
}
}

View File

@ -1 +0,0 @@
log4j.configurationFactory=net.corda.cliutils.CordaLog4j2ConfigFactory

View File

@ -40,7 +40,7 @@ class NodeConfigTest {
.withFallback(ConfigFactory.parseResources("reference.conf"))
.withFallback(ConfigFactory.parseMap(mapOf("devMode" to true)))
.resolve()
val fullConfig = nodeConfig.parseAsNodeConfiguration()
val fullConfig = nodeConfig.parseAsNodeConfiguration().orThrow()
// No custom configuration is created by default.
assertFailsWith<ConfigException.Missing> { nodeConfig.getConfig("custom") }

View File

@ -5,6 +5,8 @@ import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigValueFactory
import net.corda.bootstrapper.docker.DockerUtils
import net.corda.common.configuration.parsing.internal.Configuration
import net.corda.common.validation.internal.Validated
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.parseAsNodeConfiguration
import org.slf4j.LoggerFactory
@ -30,14 +32,13 @@ open class NodeBuilder {
.withBaseDirectory(nodeDir)
.exec(BuildImageResultCallback()).awaitImageId()
LOG.info("finished building docker image for: $nodeDir with id: $nodeImageId")
val config = nodeConfig.parseAsNodeConfigWithFallback(ConfigFactory.parseFile(copiedNode.configFile))
val config = nodeConfig.parseAsNodeConfigWithFallback(ConfigFactory.parseFile(copiedNode.configFile)).orThrow()
return copiedNode.builtNode(config, nodeImageId)
}
}
fun Config.parseAsNodeConfigWithFallback(preCopyConfig: Config): NodeConfiguration {
fun Config.parseAsNodeConfigWithFallback(preCopyConfig: Config): Validated<NodeConfiguration, Configuration.Validation.Error> {
val nodeConfig = this
.withValue("baseDirectory", ConfigValueFactory.fromAnyRef(""))
.withFallback(ConfigFactory.parseResources("reference.conf"))