mirror of
https://github.com/corda/corda.git
synced 2025-03-15 00:36:49 +00:00
Merge pull request #1553 from corda/merges/08_11_2018_16_05
Merges: 08/11/2018 at 16:05
This commit is contained in:
commit
72be3aa830
@ -4,6 +4,7 @@ import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigException
|
||||
import com.typesafe.config.ConfigObject
|
||||
import com.typesafe.config.ConfigValue
|
||||
import com.typesafe.config.ConfigValueFactory
|
||||
import net.corda.common.configuration.parsing.internal.versioned.VersionExtractor
|
||||
import net.corda.common.validation.internal.Validated
|
||||
import net.corda.common.validation.internal.Validated.Companion.invalid
|
||||
@ -24,7 +25,7 @@ object Configuration {
|
||||
/**
|
||||
* Describes a [Config] hiding sensitive data.
|
||||
*/
|
||||
fun describe(configuration: Config): ConfigValue
|
||||
fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue = { value -> ConfigValueFactory.fromAnyRef(value.toString()) }): ConfigValue?
|
||||
}
|
||||
|
||||
object Value {
|
||||
@ -112,6 +113,11 @@ object Configuration {
|
||||
*/
|
||||
interface Definition<TYPE> : Configuration.Property.Metadata, Configuration.Validator, Configuration.Value.Extractor<TYPE>, Configuration.Describer, Configuration.Value.Parser<TYPE> {
|
||||
|
||||
/**
|
||||
* Validates target [Config] with default [Configuration.Validation.Options].
|
||||
*/
|
||||
fun validate(target: Config): Valid<Config> = validate(target, Configuration.Validation.Options.defaults)
|
||||
|
||||
override fun isSpecifiedBy(configuration: Config): Boolean = configuration.hasPath(key)
|
||||
|
||||
/**
|
||||
@ -120,9 +126,9 @@ object Configuration {
|
||||
interface Required<TYPE> : Definition<TYPE> {
|
||||
|
||||
/**
|
||||
* Returns an optional property with given [defaultValue]. This property does not produce errors in case the value is unspecified, returning the [defaultValue] instead.
|
||||
* Returns an optional property. This property does not produce errors in case the value is unspecified.
|
||||
*/
|
||||
fun optional(defaultValue: TYPE? = null): Definition<TYPE?>
|
||||
fun optional(): Optional<TYPE>
|
||||
}
|
||||
|
||||
/**
|
||||
@ -136,6 +142,17 @@ object Configuration {
|
||||
fun list(): Required<List<TYPE>>
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a property that might be missing, resulting in a null value.
|
||||
*/
|
||||
interface Optional<TYPE> : Definition<TYPE?> {
|
||||
|
||||
/**
|
||||
* Allows to specify a [defaultValue], returning a required [Configuration.Property.Definition].
|
||||
*/
|
||||
fun withDefaultValue(defaultValue: TYPE): Definition<TYPE>
|
||||
}
|
||||
|
||||
/**
|
||||
* Default property definition, required and single-value.
|
||||
*/
|
||||
@ -219,7 +236,7 @@ object Configuration {
|
||||
|
||||
/**
|
||||
* Returns a [Configuration.Property.Definition.Standard] with value of type [ENUM].
|
||||
* This property expects the exact [ENUM] value specified as text for the relevant key.
|
||||
* This property expects a value in the configuration matching one of the cases of [ENUM], as text, in uppercase.
|
||||
*/
|
||||
fun <ENUM : Enum<ENUM>> enum(key: String, enumClass: KClass<ENUM>, sensitive: Boolean = false): Standard<ENUM> = StandardProperty(key, enumClass.java.simpleName, { conf: Config, propertyKey: String -> conf.getEnum(enumClass.java, propertyKey) }, { conf: Config, propertyKey: String -> conf.getEnumList(enumClass.java, propertyKey) }, sensitive)
|
||||
}
|
||||
@ -246,6 +263,13 @@ object Configuration {
|
||||
*/
|
||||
val properties: Set<Property.Definition<*>>
|
||||
|
||||
/**
|
||||
* Validates target [Config] with default [Configuration.Validation.Options].
|
||||
*/
|
||||
fun validate(target: Config): Valid<Config> = validate(target, Configuration.Validation.Options.defaults)
|
||||
|
||||
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue): ConfigValue
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
@ -270,7 +294,7 @@ object Configuration {
|
||||
* A [Configuration.Schema] that is also able to parse a raw [Config] object into a [VALUE].
|
||||
* It is an abstract class to allow extension with delegated properties e.g., object Settings: Specification() { val address by string().optional("localhost:8080") }.
|
||||
*/
|
||||
abstract class Specification<VALUE>(name: String?, private val prefix: String? = null) : Configuration.Schema, Configuration.Value.Parser<VALUE> {
|
||||
abstract class Specification<VALUE>(override val name: String, private val prefix: String? = null) : Configuration.Schema, Configuration.Value.Parser<VALUE> {
|
||||
|
||||
private val mutableProperties = mutableSetOf<Property.Definition<*>>()
|
||||
|
||||
@ -321,17 +345,20 @@ object Configuration {
|
||||
|
||||
/**
|
||||
* Returns a delegate for a [Configuration.Property.Definition.Standard] of type [ENUM].
|
||||
* This property expects the exact [ENUM] value specified as text for the relevant key.
|
||||
* This property expects a value in the configuration matching one of the cases of [ENUM], as text, in uppercase.
|
||||
*/
|
||||
fun <ENUM : Enum<ENUM>> enum(key: String? = null, enumClass: KClass<ENUM>, sensitive: Boolean = false): PropertyDelegate.Standard<ENUM> = PropertyDelegate.enum(key, prefix, enumClass, sensitive) { mutableProperties.add(it) }
|
||||
|
||||
override val name: String? get() = schema.name
|
||||
/**
|
||||
* @see enum
|
||||
*/
|
||||
fun <ENUM : Enum<ENUM>> enum(enumClass: KClass<ENUM>, sensitive: Boolean = false): PropertyDelegate.Standard<ENUM> = enum(key = null, enumClass = enumClass, sensitive = sensitive)
|
||||
|
||||
override fun description() = schema.description()
|
||||
|
||||
override fun validate(target: Config, options: Validation.Options?) = schema.validate(target, options)
|
||||
override fun validate(target: Config, options: Validation.Options) = schema.validate(target, options)
|
||||
|
||||
override fun describe(configuration: Config) = schema.describe(configuration)
|
||||
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue) = schema.describe(configuration, serialiseValue)
|
||||
|
||||
final override fun parse(configuration: Config, options: Configuration.Validation.Options): Valid<VALUE> = validate(configuration, options).mapValid(::parseValid)
|
||||
|
||||
@ -397,9 +424,11 @@ object Configuration {
|
||||
val containingPathAsString: String = containingPath.joinToString(".")
|
||||
|
||||
/**
|
||||
* [pathstr] joined by "." characters.
|
||||
* [path] joined by "." characters.
|
||||
*/
|
||||
val pathAsString: String = path.joinToString(".")
|
||||
val pathAsString: String get() = path.joinToString(".")
|
||||
|
||||
internal fun withContainingPathPrefix(vararg containingPath: String): Error = withContainingPath(*(containingPath.toList() + this.containingPath).toTypedArray())
|
||||
|
||||
internal abstract fun withContainingPath(vararg containingPath: String): Error
|
||||
|
||||
@ -415,12 +444,14 @@ object Configuration {
|
||||
*/
|
||||
class WrongType private constructor(override val keyName: String, override val typeName: String, message: String, containingPath: List<String> = emptyList()) : Configuration.Validation.Error(keyName, typeName, message, containingPath) {
|
||||
|
||||
internal companion object {
|
||||
companion object {
|
||||
|
||||
internal fun of(message: String, keyName: String = UNKNOWN, typeName: String = UNKNOWN, containingPath: List<String> = emptyList()): WrongType = contextualize(keyName, containingPath).let { (key, path) -> WrongType(key, typeName, message, path) }
|
||||
fun of(message: String, keyName: String? = null, typeName: String = UNKNOWN, containingPath: List<String> = emptyList()): WrongType = contextualize(keyName ?: UNKNOWN, containingPath).let { (key, path) -> WrongType(key, typeName, message, path) }
|
||||
|
||||
fun forKey(keyName: String, expectedTypeName: String, actualTypeName: String): WrongType = of("$keyName has type ${actualTypeName.toUpperCase()} rather than ${expectedTypeName.toUpperCase()}")
|
||||
}
|
||||
|
||||
override fun withContainingPath(vararg containingPath: String) = WrongType(keyName, typeName, message, containingPath.toList() + this.containingPath)
|
||||
override fun withContainingPath(vararg containingPath: String) = WrongType(keyName, typeName, message, containingPath.toList())
|
||||
|
||||
override fun with(keyName: String, typeName: String): WrongType = WrongType.of(message, keyName, typeName, containingPath)
|
||||
}
|
||||
@ -430,12 +461,14 @@ object Configuration {
|
||||
*/
|
||||
class MissingValue private constructor(override val keyName: String, override val typeName: String, message: String, containingPath: List<String> = emptyList()) : Configuration.Validation.Error(keyName, typeName, message, containingPath) {
|
||||
|
||||
internal companion object {
|
||||
companion object {
|
||||
|
||||
internal fun of(message: String, keyName: String = UNKNOWN, typeName: String = UNKNOWN, containingPath: List<String> = emptyList()): MissingValue = contextualize(keyName, containingPath).let { (key, path) -> MissingValue(key, typeName, message, path) }
|
||||
fun of(message: String, keyName: String? = null, typeName: String = UNKNOWN, containingPath: List<String> = emptyList()): MissingValue = contextualize(keyName ?: UNKNOWN, containingPath).let { (key, path) -> MissingValue(key, typeName, message, path) }
|
||||
|
||||
fun forKey(keyName: String): MissingValue = of("No configuration setting found for key '$keyName'", keyName)
|
||||
}
|
||||
|
||||
override fun withContainingPath(vararg containingPath: String) = MissingValue(keyName, typeName, message, containingPath.toList() + this.containingPath)
|
||||
override fun withContainingPath(vararg containingPath: String) = MissingValue(keyName, typeName, message, containingPath.toList())
|
||||
|
||||
override fun with(keyName: String, typeName: String): MissingValue = MissingValue.of(message, keyName, typeName, containingPath)
|
||||
}
|
||||
@ -445,12 +478,12 @@ object Configuration {
|
||||
*/
|
||||
class BadValue private constructor(override val keyName: String, override val typeName: String, message: String, containingPath: List<String> = emptyList()) : Configuration.Validation.Error(keyName, typeName, message, containingPath) {
|
||||
|
||||
internal companion object {
|
||||
companion object {
|
||||
|
||||
internal fun of(message: String, keyName: String = UNKNOWN, typeName: String = UNKNOWN, containingPath: List<String> = emptyList()): BadValue = contextualize(keyName, containingPath).let { (key, path) -> BadValue(key, typeName, message, path) }
|
||||
fun of(message: String, keyName: String? = null, typeName: String = UNKNOWN, containingPath: List<String> = emptyList()): BadValue = contextualize(keyName ?: UNKNOWN, containingPath).let { (key, path) -> BadValue(key, typeName, message, path) }
|
||||
}
|
||||
|
||||
override fun withContainingPath(vararg containingPath: String) = BadValue(keyName, typeName, message, containingPath.toList() + this.containingPath)
|
||||
override fun withContainingPath(vararg containingPath: String) = BadValue(keyName, typeName, message, containingPath.toList())
|
||||
|
||||
override fun with(keyName: String, typeName: String): BadValue = BadValue.of(message, keyName, typeName, containingPath)
|
||||
}
|
||||
@ -460,12 +493,12 @@ object Configuration {
|
||||
*/
|
||||
class BadPath private constructor(override val keyName: String, override val typeName: String, message: String, containingPath: List<String> = emptyList()) : Configuration.Validation.Error(keyName, typeName, message, containingPath) {
|
||||
|
||||
internal companion object {
|
||||
companion object {
|
||||
|
||||
internal fun of(message: String, keyName: String = UNKNOWN, typeName: String = UNKNOWN, containingPath: List<String> = emptyList()): BadPath = contextualize(keyName, containingPath).let { (key, path) -> BadPath(key, typeName, message, path) }
|
||||
fun of(message: String, keyName: String? = null, typeName: String = UNKNOWN, containingPath: List<String> = emptyList()): BadPath = contextualize(keyName ?: UNKNOWN, containingPath).let { (key, path) -> BadPath(key, typeName, message, path) }
|
||||
}
|
||||
|
||||
override fun withContainingPath(vararg containingPath: String) = BadPath(keyName, typeName, message, containingPath.toList() + this.containingPath)
|
||||
override fun withContainingPath(vararg containingPath: String) = BadPath(keyName, typeName, message, containingPath.toList())
|
||||
|
||||
override fun with(keyName: String, typeName: String): BadPath = BadPath.of(message, keyName, typeName, containingPath)
|
||||
}
|
||||
@ -475,12 +508,12 @@ object Configuration {
|
||||
*/
|
||||
class MalformedStructure private constructor(override val keyName: String, override val typeName: String, message: String, containingPath: List<String> = emptyList()) : Configuration.Validation.Error(keyName, typeName, message, containingPath) {
|
||||
|
||||
internal companion object {
|
||||
companion object {
|
||||
|
||||
internal fun of(message: String, keyName: String = UNKNOWN, typeName: String = UNKNOWN, containingPath: List<String> = emptyList()): MalformedStructure = contextualize(keyName, containingPath).let { (key, path) -> MalformedStructure(key, typeName, message, path) }
|
||||
fun of(message: String, keyName: String? = null, typeName: String = UNKNOWN, containingPath: List<String> = emptyList()): MalformedStructure = contextualize(keyName ?: UNKNOWN, containingPath).let { (key, path) -> MalformedStructure(key, typeName, message, path) }
|
||||
}
|
||||
|
||||
override fun withContainingPath(vararg containingPath: String) = MalformedStructure(keyName, typeName, message, containingPath.toList() + this.containingPath)
|
||||
override fun withContainingPath(vararg containingPath: String) = MalformedStructure(keyName, typeName, message, containingPath.toList())
|
||||
|
||||
override fun with(keyName: String, typeName: String): MalformedStructure = MalformedStructure.of(message, keyName, typeName, containingPath)
|
||||
}
|
||||
@ -490,16 +523,14 @@ object Configuration {
|
||||
*/
|
||||
class Unknown private constructor(override val keyName: String, containingPath: List<String> = emptyList()) : Configuration.Validation.Error(keyName, null, message(keyName), containingPath) {
|
||||
|
||||
internal companion object {
|
||||
companion object {
|
||||
|
||||
private fun message(keyName: String) = "Unknown property \"$keyName\"."
|
||||
private fun message(keyName: String) = "Unknown property \'$keyName\'"
|
||||
|
||||
internal fun of(keyName: String = UNKNOWN, containingPath: List<String> = emptyList()): Unknown = contextualize(keyName, containingPath).let { (key, path) -> Unknown(key, path) }
|
||||
fun of(keyName: String = UNKNOWN, containingPath: List<String> = emptyList()): Unknown = contextualize(keyName, containingPath).let { (key, path) -> Unknown(key, path) }
|
||||
}
|
||||
|
||||
override val message = message(pathAsString)
|
||||
|
||||
override fun withContainingPath(vararg containingPath: String) = Unknown(keyName, containingPath.toList() + this.containingPath)
|
||||
override fun withContainingPath(vararg containingPath: String) = Unknown(keyName, containingPath.toList())
|
||||
|
||||
override fun with(keyName: String, typeName: String): Unknown = Unknown.of(keyName, containingPath)
|
||||
}
|
||||
@ -509,12 +540,12 @@ object Configuration {
|
||||
*/
|
||||
class UnsupportedVersion private constructor(val version: Int, containingPath: List<String> = emptyList()) : Configuration.Validation.Error(null, null, "Unknown configuration version $version.", containingPath) {
|
||||
|
||||
internal companion object {
|
||||
companion object {
|
||||
|
||||
internal fun of(version: Int): UnsupportedVersion = UnsupportedVersion(version)
|
||||
fun of(version: Int): UnsupportedVersion = UnsupportedVersion(version)
|
||||
}
|
||||
|
||||
override fun withContainingPath(vararg containingPath: String) = UnsupportedVersion(version, containingPath.toList() + this.containingPath)
|
||||
override fun withContainingPath(vararg containingPath: String) = UnsupportedVersion(version, containingPath.toList())
|
||||
|
||||
override fun with(keyName: String, typeName: String): UnsupportedVersion = this
|
||||
}
|
||||
@ -526,16 +557,16 @@ object Configuration {
|
||||
/**
|
||||
* Defines the contract from extracting a specification version from a [Config] object.
|
||||
*/
|
||||
interface Extractor : Configuration.Value.Parser<Int?> {
|
||||
interface Extractor : Configuration.Value.Parser<Int> {
|
||||
|
||||
companion object {
|
||||
|
||||
const val DEFAULT_VERSION_VALUE = 1
|
||||
|
||||
/**
|
||||
* Returns a [Configuration.Version.Extractor] that reads the value from given [versionKey], defaulting to [versionDefaultValue] when [versionKey] is unspecified.
|
||||
* Returns a [Configuration.Version.Extractor] that reads the value from given [versionPath], defaulting to [versionDefaultValue] when [versionPath] is unspecified.
|
||||
*/
|
||||
fun fromKey(versionKey: String, versionDefaultValue: Int? = DEFAULT_VERSION_VALUE): Configuration.Version.Extractor = VersionExtractor(versionKey, versionDefaultValue)
|
||||
fun fromPath(versionPath: String, versionDefaultValue: Int = DEFAULT_VERSION_VALUE): Configuration.Version.Extractor = VersionExtractor(versionPath, versionDefaultValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,17 @@
|
||||
package net.corda.common.configuration.parsing.internal
|
||||
|
||||
import com.typesafe.config.*
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigException
|
||||
import com.typesafe.config.ConfigObject
|
||||
import com.typesafe.config.ConfigValue
|
||||
import com.typesafe.config.ConfigValueFactory
|
||||
import net.corda.common.validation.internal.Validated
|
||||
import net.corda.common.validation.internal.Validated.Companion.invalid
|
||||
import net.corda.common.validation.internal.Validated.Companion.valid
|
||||
|
||||
internal class LongProperty(key: String, sensitive: Boolean = false) : StandardProperty<Long>(key, Long::class.javaObjectType.simpleName, Config::getLong, Config::getLongList, sensitive) {
|
||||
|
||||
override fun validate(target: Config, options: Configuration.Validation.Options?): Valid<Config> {
|
||||
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> {
|
||||
|
||||
val validated = super.validate(target, options)
|
||||
if (validated.isValid && target.getValue(key).unwrapped().toString().contains(".")) {
|
||||
@ -17,7 +21,7 @@ internal class LongProperty(key: String, sensitive: Boolean = false) : StandardP
|
||||
}
|
||||
}
|
||||
|
||||
internal open class StandardProperty<TYPE>(override val key: String, typeNameArg: String, private val extractSingleValue: (Config, String) -> TYPE, internal val extractListValue: (Config, String) -> List<TYPE>, override val isSensitive: Boolean = false, final override val schema: Configuration.Schema? = null) : Configuration.Property.Definition.Standard<TYPE> {
|
||||
internal open class StandardProperty<TYPE : Any>(override val key: String, typeNameArg: String, private val extractSingleValue: (Config, String) -> TYPE, internal val extractListValue: (Config, String) -> List<TYPE>, override val isSensitive: Boolean = false, final override val schema: Configuration.Schema? = null) : Configuration.Property.Definition.Standard<TYPE> {
|
||||
|
||||
override fun valueIn(configuration: Config) = extractSingleValue.invoke(configuration, key)
|
||||
|
||||
@ -25,21 +29,21 @@ internal open class StandardProperty<TYPE>(override val key: String, typeNameArg
|
||||
|
||||
override fun <MAPPED : Any> mapValid(mappedTypeName: String, convert: (TYPE) -> Valid<MAPPED>): Configuration.Property.Definition.Standard<MAPPED> = FunctionalProperty(this, mappedTypeName, extractListValue, convert)
|
||||
|
||||
override fun optional(defaultValue: TYPE?): Configuration.Property.Definition<TYPE?> = OptionalProperty(this, defaultValue)
|
||||
override fun optional(): Configuration.Property.Definition.Optional<TYPE> = OptionalDelegatedProperty(this)
|
||||
|
||||
override fun list(): Configuration.Property.Definition.Required<List<TYPE>> = ListProperty(this)
|
||||
|
||||
override fun describe(configuration: Config): ConfigValue {
|
||||
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue): ConfigValue {
|
||||
|
||||
if (isSensitive) {
|
||||
return ConfigValueFactory.fromAnyRef(Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER)
|
||||
return valueDescription(Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER, serialiseValue)
|
||||
}
|
||||
return schema?.describe(configuration.getConfig(key)) ?: ConfigValueFactory.fromAnyRef(valueIn(configuration))
|
||||
return schema?.describe(configuration.getConfig(key), serialiseValue) ?: valueDescription(valueIn(configuration), serialiseValue)
|
||||
}
|
||||
|
||||
override val isMandatory = true
|
||||
|
||||
override fun validate(target: Config, options: Configuration.Validation.Options?): Valid<Config> {
|
||||
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> {
|
||||
|
||||
val errors = mutableSetOf<Configuration.Validation.Error>()
|
||||
errors += errorsWhenExtractingValue(target)
|
||||
@ -47,7 +51,7 @@ internal open class StandardProperty<TYPE>(override val key: String, typeNameArg
|
||||
schema?.let { nestedSchema ->
|
||||
val nestedConfig: Config? = target.getConfig(key)
|
||||
nestedConfig?.let {
|
||||
errors += nestedSchema.validate(nestedConfig, options).errors.map { error -> error.withContainingPath(*key.split(".").toTypedArray()) }
|
||||
errors += nestedSchema.validate(nestedConfig, options).errors.map { error -> error.withContainingPathPrefix(*key.split(".").toTypedArray()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -57,63 +61,63 @@ internal open class StandardProperty<TYPE>(override val key: String, typeNameArg
|
||||
override fun toString() = "\"$key\": \"$typeName\""
|
||||
}
|
||||
|
||||
private class ListProperty<TYPE>(delegate: StandardProperty<TYPE>) : RequiredDelegatedProperty<List<TYPE>, StandardProperty<TYPE>>(delegate) {
|
||||
private class ListProperty<TYPE : Any>(delegate: StandardProperty<TYPE>) : RequiredDelegatedProperty<List<TYPE>, StandardProperty<TYPE>>(delegate) {
|
||||
|
||||
override val typeName: String = "List<${delegate.typeName}>"
|
||||
|
||||
override fun valueIn(configuration: Config): List<TYPE> = delegate.extractListValue.invoke(configuration, key)
|
||||
|
||||
override fun validate(target: Config, options: Configuration.Validation.Options?): Valid<Config> {
|
||||
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> {
|
||||
|
||||
val errors = mutableSetOf<Configuration.Validation.Error>()
|
||||
errors += errorsWhenExtractingValue(target)
|
||||
if (errors.isEmpty()) {
|
||||
delegate.schema?.let { schema ->
|
||||
errors += valueIn(target).asSequence().map { element -> element as ConfigObject }.map(ConfigObject::toConfig).mapIndexed { index, targetConfig -> schema.validate(targetConfig, options).errors.map { error -> error.withContainingPath(key, "[$index]") } }.reduce { one, other -> one + other }
|
||||
errors += valueIn(target).asSequence().map { element -> element as ConfigObject }.map(ConfigObject::toConfig).mapIndexed { index, targetConfig -> schema.validate(targetConfig, options).errors.map { error -> error.withContainingPath(*error.containingPath(index).toTypedArray()) } }.fold(emptyList<Configuration.Validation.Error>()) { one, other -> one + other }.toSet()
|
||||
}
|
||||
}
|
||||
return Validated.withResult(target, errors)
|
||||
}
|
||||
|
||||
override fun describe(configuration: Config): ConfigValue {
|
||||
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue): ConfigValue {
|
||||
|
||||
if (isSensitive) {
|
||||
return ConfigValueFactory.fromAnyRef(Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER)
|
||||
return valueDescription(Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER, serialiseValue)
|
||||
}
|
||||
return when {
|
||||
delegate.schema != null -> {
|
||||
val elementsDescription = valueIn(configuration).asSequence().map { it as ConfigObject }.map(ConfigObject::toConfig).map { delegate.schema.describe(it, serialiseValue) }.toList()
|
||||
ConfigValueFactory.fromIterable(elementsDescription)
|
||||
}
|
||||
else -> valueDescription(valueIn(configuration), serialiseValue)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Configuration.Validation.Error.containingPath(index: Int): List<String> {
|
||||
val newContainingPath = listOf(key, "[$index]")
|
||||
return when {
|
||||
containingPath.size > 1 -> newContainingPath + containingPath.subList(1, containingPath.size)
|
||||
else -> newContainingPath
|
||||
}
|
||||
return delegate.schema?.let { schema -> ConfigValueFactory.fromAnyRef(valueIn(configuration).asSequence().map { element -> element as ConfigObject }.map(ConfigObject::toConfig).map { schema.describe(it) }.toList()) } ?: ConfigValueFactory.fromAnyRef(valueIn(configuration))
|
||||
}
|
||||
}
|
||||
|
||||
private class OptionalProperty<TYPE>(delegate: Configuration.Property.Definition.Required<TYPE>, private val defaultValue: TYPE?) : DelegatedProperty<TYPE?, Configuration.Property.Definition.Required<TYPE>>(delegate) {
|
||||
private class OptionalPropertyWithDefault<TYPE : Any>(delegate: Configuration.Property.Definition.Optional<TYPE>, private val defaultValue: TYPE) : DelegatedProperty<TYPE, Configuration.Property.Definition.Optional<TYPE>>(delegate) {
|
||||
|
||||
override val isMandatory: Boolean = false
|
||||
|
||||
override val typeName: String = "${super.typeName}?"
|
||||
override val typeName: String = delegate.typeName.removeSuffix("?")
|
||||
|
||||
override fun describe(configuration: Config) = delegate.describe(configuration)
|
||||
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue): ConfigValue? = delegate.describe(configuration, serialiseValue) ?: valueDescription(if (isSensitive) Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER else defaultValue, serialiseValue)
|
||||
|
||||
override fun valueIn(configuration: Config): TYPE? {
|
||||
override fun valueIn(configuration: Config): TYPE = delegate.valueIn(configuration) ?: defaultValue
|
||||
|
||||
return when {
|
||||
isSpecifiedBy(configuration) -> delegate.valueIn(configuration)
|
||||
else -> defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
override fun validate(target: Config, options: Configuration.Validation.Options?): Valid<Config> {
|
||||
|
||||
val result = delegate.validate(target, options)
|
||||
val error = result.errors.asSequence().filterIsInstance<Configuration.Validation.Error.MissingValue>().singleOrNull()
|
||||
return when {
|
||||
error != null -> if (result.errors.size > 1) result else valid(target)
|
||||
else -> result
|
||||
}
|
||||
}
|
||||
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> = delegate.validate(target, options)
|
||||
}
|
||||
|
||||
private class FunctionalProperty<TYPE, MAPPED : Any>(delegate: Configuration.Property.Definition.Standard<TYPE>, private val mappedTypeName: String, internal val extractListValue: (Config, String) -> List<TYPE>, private val convert: (TYPE) -> Valid<MAPPED>) : RequiredDelegatedProperty<MAPPED, Configuration.Property.Definition.Standard<TYPE>>(delegate), Configuration.Property.Definition.Standard<MAPPED> {
|
||||
|
||||
override fun valueIn(configuration: Config) = convert.invoke(delegate.valueIn(configuration)).valueOrThrow()
|
||||
override fun valueIn(configuration: Config) = convert.invoke(delegate.valueIn(configuration)).orThrow()
|
||||
|
||||
override val typeName: String = if (super.typeName == "#$mappedTypeName") super.typeName else "$mappedTypeName(${super.typeName})"
|
||||
|
||||
@ -121,7 +125,7 @@ private class FunctionalProperty<TYPE, MAPPED : Any>(delegate: Configuration.Pro
|
||||
|
||||
override fun list(): Configuration.Property.Definition.Required<List<MAPPED>> = FunctionalListProperty(this)
|
||||
|
||||
override fun validate(target: Config, options: Configuration.Validation.Options?): Valid<Config> {
|
||||
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> {
|
||||
|
||||
val errors = mutableSetOf<Configuration.Validation.Error>()
|
||||
errors += delegate.validate(target, options).errors
|
||||
@ -131,7 +135,7 @@ private class FunctionalProperty<TYPE, MAPPED : Any>(delegate: Configuration.Pro
|
||||
return Validated.withResult(target, errors)
|
||||
}
|
||||
|
||||
override fun describe(configuration: Config) = delegate.describe(configuration)
|
||||
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue) = delegate.describe(configuration, serialiseValue)
|
||||
}
|
||||
|
||||
private class FunctionalListProperty<RAW, TYPE : Any>(delegate: FunctionalProperty<RAW, TYPE>) : RequiredDelegatedProperty<List<TYPE>, FunctionalProperty<RAW, TYPE>>(delegate) {
|
||||
@ -140,7 +144,7 @@ private class FunctionalListProperty<RAW, TYPE : Any>(delegate: FunctionalProper
|
||||
|
||||
override fun valueIn(configuration: Config): List<TYPE> = delegate.extractListValue.invoke(configuration, key).asSequence().map { configObject(key to ConfigValueFactory.fromAnyRef(it)) }.map(ConfigObject::toConfig).map(delegate::valueIn).toList()
|
||||
|
||||
override fun validate(target: Config, options: Configuration.Validation.Options?): Valid<Config> {
|
||||
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> {
|
||||
|
||||
val list = try {
|
||||
delegate.extractListValue.invoke(target, key)
|
||||
@ -151,16 +155,24 @@ private class FunctionalListProperty<RAW, TYPE : Any>(delegate: FunctionalProper
|
||||
throw e
|
||||
}
|
||||
}
|
||||
val errors = list.asSequence().map { configObject(key to ConfigValueFactory.fromAnyRef(it)) }.mapIndexed { index, value -> delegate.validate(value.toConfig(), options).errors.map { error -> error.withContainingPath(key, "[$index]") } }.reduce { one, other -> one + other }.toSet()
|
||||
val errors = list.asSequence().map { configObject(key to ConfigValueFactory.fromAnyRef(it)) }.mapIndexed { index, value -> delegate.validate(value.toConfig(), options).errors.map { error -> error.withContainingPath(*error.containingPath(index).toTypedArray()) } }.fold(emptyList<Configuration.Validation.Error>()) { one, other -> one + other }.toSet()
|
||||
return Validated.withResult(target, errors)
|
||||
}
|
||||
|
||||
override fun describe(configuration: Config): ConfigValue {
|
||||
private fun Configuration.Validation.Error.containingPath(index: Int): List<String> {
|
||||
val newContainingPath = listOf(key, "[$index]")
|
||||
return when {
|
||||
containingPath.size > 1 -> newContainingPath + containingPath.subList(1, containingPath.size)
|
||||
else -> newContainingPath
|
||||
}
|
||||
}
|
||||
|
||||
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue): ConfigValue {
|
||||
|
||||
if (isSensitive) {
|
||||
return ConfigValueFactory.fromAnyRef(Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER)
|
||||
return valueDescription(Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER, serialiseValue)
|
||||
}
|
||||
return delegate.schema?.let { schema -> ConfigValueFactory.fromAnyRef(valueIn(configuration).asSequence().map { element -> element as ConfigObject }.map(ConfigObject::toConfig).map { schema.describe(it) }.toList()) } ?: ConfigValueFactory.fromAnyRef(valueIn(configuration))
|
||||
return delegate.schema?.let { schema -> valueDescription(valueIn(configuration).asSequence().map { element -> valueDescription(element, serialiseValue) }.map { it as ConfigObject }.map(ConfigObject::toConfig).map { schema.describe(it, serialiseValue) }.toList(), serialiseValue) } ?: valueDescription(valueIn(configuration), serialiseValue)
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,12 +181,45 @@ private abstract class DelegatedProperty<TYPE, DELEGATE : Configuration.Property
|
||||
final override fun toString() = "\"$key\": \"$typeName\""
|
||||
}
|
||||
|
||||
private abstract class RequiredDelegatedProperty<TYPE, DELEGATE : Configuration.Property.Definition.Required<*>>(delegate: DELEGATE) : DelegatedProperty<TYPE, DELEGATE>(delegate), Configuration.Property.Definition.Required<TYPE> {
|
||||
private class OptionalDelegatedProperty<TYPE : Any>(private val delegate: Configuration.Property.Definition<TYPE>) : Configuration.Property.Metadata by delegate, Configuration.Property.Definition.Optional<TYPE> {
|
||||
|
||||
final override fun optional(defaultValue: TYPE?): Configuration.Property.Definition<TYPE?> = OptionalProperty(this, defaultValue)
|
||||
override val isMandatory: Boolean = false
|
||||
|
||||
override val typeName: String = "${delegate.typeName}?"
|
||||
|
||||
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue) = if (isSpecifiedBy(configuration)) delegate.describe(configuration, serialiseValue) else null
|
||||
|
||||
override fun valueIn(configuration: Config): TYPE? {
|
||||
|
||||
return when {
|
||||
isSpecifiedBy(configuration) -> delegate.valueIn(configuration)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> {
|
||||
|
||||
val result = delegate.validate(target, options)
|
||||
val errors = result.errors
|
||||
val missingValueError = errors.asSequence().filterIsInstance<Configuration.Validation.Error.MissingValue>().filter { it.pathAsString == key }.singleOrNull()
|
||||
return when {
|
||||
missingValueError != null -> if (errors.size > 1) result else valid(target)
|
||||
else -> result
|
||||
}
|
||||
}
|
||||
|
||||
override fun withDefaultValue(defaultValue: TYPE): Configuration.Property.Definition<TYPE> = OptionalPropertyWithDefault(this, defaultValue)
|
||||
|
||||
override fun toString() = "\"$key\": \"$typeName\""
|
||||
}
|
||||
|
||||
private fun ConfigException.toValidationError(keyName: String, typeName: String): Configuration.Validation.Error {
|
||||
|
||||
private abstract class RequiredDelegatedProperty<TYPE : Any, DELEGATE : Configuration.Property.Definition.Required<*>>(delegate: DELEGATE) : DelegatedProperty<TYPE, DELEGATE>(delegate), Configuration.Property.Definition.Required<TYPE> {
|
||||
|
||||
final override fun optional(): Configuration.Property.Definition.Optional<TYPE> = OptionalDelegatedProperty(this)
|
||||
}
|
||||
|
||||
fun ConfigException.toValidationError(keyName: String? = null, typeName: String): Configuration.Validation.Error {
|
||||
|
||||
val toError = when (this) {
|
||||
is ConfigException.Missing -> Configuration.Validation.Error.MissingValue.Companion::of
|
||||
@ -202,4 +247,6 @@ private fun Configuration.Property.Definition<*>.errorsWhenExtractingValue(targe
|
||||
|
||||
private val expectedExceptionTypes = setOf(ConfigException.Missing::class, ConfigException.WrongType::class, ConfigException.BadValue::class, ConfigException.BadPath::class, ConfigException.Parse::class)
|
||||
|
||||
private fun isErrorExpected(error: ConfigException) = expectedExceptionTypes.any { expected -> expected.isInstance(error) }
|
||||
private fun isErrorExpected(error: ConfigException) = expectedExceptionTypes.any { expected -> expected.isInstance(error) }
|
||||
|
||||
private fun valueDescription(value: Any, serialiseValue: (Any) -> ConfigValue) = serialiseValue.invoke(value)
|
@ -16,10 +16,12 @@ internal class Schema(override val name: String?, unorderedProperties: Iterable<
|
||||
}
|
||||
}
|
||||
|
||||
override fun validate(target: Config, options: Configuration.Validation.Options?): Valid<Config> {
|
||||
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> {
|
||||
|
||||
val propertyErrors = properties.flatMap { property -> property.validate(target, options).errors }.toMutableSet()
|
||||
if (options?.strict == true) {
|
||||
val propertyErrors = properties.flatMap { property ->
|
||||
property.validate(target, options).errors
|
||||
}.toMutableSet()
|
||||
if (options.strict) {
|
||||
val unknownKeys = target.root().keys - properties.map(Configuration.Property.Definition<*>::key)
|
||||
propertyErrors += unknownKeys.map { Configuration.Validation.Error.Unknown.of(it) }
|
||||
}
|
||||
@ -45,9 +47,9 @@ internal class Schema(override val name: String?, unorderedProperties: Iterable<
|
||||
return description.toString()
|
||||
}
|
||||
|
||||
override fun describe(configuration: Config): ConfigValue {
|
||||
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue): ConfigValue {
|
||||
|
||||
return properties.asSequence().map { it.key to it.describe(configuration) }.fold(configObject()) { config, (key, value) -> config.withValue(key, value) }
|
||||
return properties.asSequence().map { it.key to it.describe(configuration, serialiseValue) }.filter { it.second != null }.fold(configObject()) { config, (key, value) -> config.withValue(key, value) }
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
|
@ -14,7 +14,7 @@ interface PropertyDelegate<TYPE> {
|
||||
|
||||
operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty<Any?, Configuration.Property.Definition.Required<TYPE>>
|
||||
|
||||
fun optional(defaultValue: TYPE? = null): PropertyDelegate<TYPE?>
|
||||
fun optional(): PropertyDelegate.Optional<TYPE>
|
||||
}
|
||||
|
||||
interface Single<TYPE> {
|
||||
@ -24,6 +24,13 @@ interface PropertyDelegate<TYPE> {
|
||||
fun list(): Required<List<TYPE>>
|
||||
}
|
||||
|
||||
interface Optional<TYPE> {
|
||||
|
||||
operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty<Any?, Configuration.Property.Definition.Optional<TYPE>>
|
||||
|
||||
fun withDefaultValue(defaultValue: TYPE): PropertyDelegate<TYPE>
|
||||
}
|
||||
|
||||
interface Standard<TYPE> : Required<TYPE>, Single<TYPE> {
|
||||
|
||||
override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty<Any?, Configuration.Property.Definition.Standard<TYPE>>
|
||||
@ -67,35 +74,52 @@ private class PropertyDelegateImpl<TYPE>(private val key: String?, private val p
|
||||
}
|
||||
}
|
||||
|
||||
override fun list(): PropertyDelegate.Required<List<TYPE>> = ListPropertyDelegateImpl(key, sensitive, addToProperties, { k, s -> construct.invoke(k, s).list() })
|
||||
override fun list(): PropertyDelegate.Required<List<TYPE>> = ListPropertyDelegateImpl(key, prefix, sensitive, addToProperties, { k, s -> construct.invoke(k, s).list() })
|
||||
|
||||
override fun optional(defaultValue: TYPE?): PropertyDelegate<TYPE?> = OptionalPropertyDelegateImpl(key, sensitive, addToProperties, { k, s -> construct.invoke(k, s).optional(defaultValue) })
|
||||
override fun optional(): PropertyDelegate.Optional<TYPE> = OptionalPropertyDelegateImpl(key, prefix, sensitive, addToProperties, { k, s -> construct.invoke(k, s).optional() })
|
||||
|
||||
override fun <MAPPED : Any> mapValid(mappedTypeName: String, convert: (TYPE) -> Valid<MAPPED>): PropertyDelegate.Standard<MAPPED> = PropertyDelegateImpl(key, prefix, sensitive, addToProperties, { k, s -> construct.invoke(k, s).mapValid(mappedTypeName) { value -> convert.invoke(value) } })
|
||||
}
|
||||
|
||||
private class OptionalPropertyDelegateImpl<TYPE>(private val key: String?, private val sensitive: Boolean = false, private val addToProperties: (Configuration.Property.Definition<*>) -> Unit, private val construct: (String, Boolean) -> Configuration.Property.Definition<TYPE?>) : PropertyDelegate<TYPE?> {
|
||||
private class OptionalPropertyDelegateImpl<TYPE>(private val key: String?, private val prefix: String?, private val sensitive: Boolean = false, private val addToProperties: (Configuration.Property.Definition<*>) -> Unit, private val construct: (String, Boolean) -> Configuration.Property.Definition.Optional<TYPE>) : PropertyDelegate.Optional<TYPE> {
|
||||
|
||||
override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty<Any?, Configuration.Property.Definition<TYPE?>> {
|
||||
override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty<Any?, Configuration.Property.Definition.Optional<TYPE>> {
|
||||
|
||||
val prop = construct.invoke(key ?: property.name, sensitive).also(addToProperties)
|
||||
return object : ReadOnlyProperty<Any?, Configuration.Property.Definition<TYPE?>> {
|
||||
val shortName = key ?: property.name
|
||||
val prop = construct.invoke(prefix?.let { "$prefix.$shortName" } ?: shortName, sensitive).also(addToProperties)
|
||||
return object : ReadOnlyProperty<Any?, Configuration.Property.Definition.Optional<TYPE>> {
|
||||
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): Configuration.Property.Definition<TYPE?> = prop
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): Configuration.Property.Definition.Optional<TYPE> = prop
|
||||
}
|
||||
}
|
||||
|
||||
override fun withDefaultValue(defaultValue: TYPE): PropertyDelegate<TYPE> = OptionalWithDefaultPropertyDelegateImpl(key, prefix, sensitive, addToProperties, { k, s -> construct.invoke(k, s).withDefaultValue(defaultValue) })
|
||||
}
|
||||
|
||||
private class OptionalWithDefaultPropertyDelegateImpl<TYPE>(private val key: String?, private val prefix: String?, private val sensitive: Boolean = false, private val addToProperties: (Configuration.Property.Definition<*>) -> Unit, private val construct: (String, Boolean) -> Configuration.Property.Definition<TYPE>) : PropertyDelegate<TYPE> {
|
||||
|
||||
override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty<Any?, Configuration.Property.Definition<TYPE>> {
|
||||
|
||||
val shortName = key ?: property.name
|
||||
val prop = construct.invoke(prefix?.let { "$prefix.$shortName" } ?: shortName, sensitive).also(addToProperties)
|
||||
return object : ReadOnlyProperty<Any?, Configuration.Property.Definition<TYPE>> {
|
||||
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): Configuration.Property.Definition<TYPE> = prop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ListPropertyDelegateImpl<TYPE>(private val key: String?, private val sensitive: Boolean = false, private val addToProperties: (Configuration.Property.Definition<*>) -> Unit, private val construct: (String, Boolean) -> Configuration.Property.Definition.Required<TYPE>) : PropertyDelegate.Required<TYPE> {
|
||||
private class ListPropertyDelegateImpl<TYPE>(private val key: String?, private val prefix: String?, private val sensitive: Boolean = false, private val addToProperties: (Configuration.Property.Definition<*>) -> Unit, private val construct: (String, Boolean) -> Configuration.Property.Definition.Required<TYPE>) : PropertyDelegate.Required<TYPE> {
|
||||
|
||||
override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty<Any?, Configuration.Property.Definition.Required<TYPE>> {
|
||||
|
||||
val prop = construct.invoke(key ?: property.name, sensitive).also(addToProperties)
|
||||
val shortName = key ?: property.name
|
||||
val prop = construct.invoke(prefix?.let { "$prefix.$shortName" } ?: shortName, sensitive).also(addToProperties)
|
||||
return object : ReadOnlyProperty<Any?, Configuration.Property.Definition.Required<TYPE>> {
|
||||
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): Configuration.Property.Definition.Required<TYPE> = prop
|
||||
}
|
||||
}
|
||||
|
||||
override fun optional(defaultValue: TYPE?): PropertyDelegate<TYPE?> = OptionalPropertyDelegateImpl(key, sensitive, addToProperties, { k, s -> construct.invoke(k, s).optional(defaultValue) })
|
||||
override fun optional(): PropertyDelegate.Optional<TYPE> = OptionalPropertyDelegateImpl(key, prefix, sensitive, addToProperties, { k, s -> construct.invoke(k, s).optional() })
|
||||
}
|
@ -15,6 +15,10 @@ operator fun <TYPE> Config.get(property: Configuration.Property.Definition<TYPE>
|
||||
|
||||
inline fun <reified NESTED : Any> Configuration.Specification<*>.nested(specification: Configuration.Specification<NESTED>, key: String? = null, sensitive: Boolean = false): PropertyDelegate.Standard<NESTED> = nestedObject(schema = specification, key = key, sensitive = sensitive).map(ConfigObject::toConfig).mapValid { value -> specification.parse(value) }
|
||||
|
||||
fun <TYPE> Configuration.Property.Definition.Single<TYPE>.listOrEmpty(): Configuration.Property.Definition<List<TYPE>> = list().optional().withDefaultValue(emptyList())
|
||||
|
||||
fun <TYPE> PropertyDelegate.Single<TYPE>.listOrEmpty(): PropertyDelegate<List<TYPE>> = list().optional().withDefaultValue(emptyList())
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal fun configObject(vararg entries: Pair<String, Any?>): ConfigObject {
|
||||
|
||||
|
@ -5,18 +5,21 @@ import net.corda.common.configuration.parsing.internal.Configuration
|
||||
import net.corda.common.configuration.parsing.internal.Valid
|
||||
import net.corda.common.configuration.parsing.internal.valid
|
||||
|
||||
internal class VersionExtractor(versionKey: String, versionDefaultValue: Int?) : Configuration.Version.Extractor {
|
||||
internal class VersionExtractor(versionPath: String, versionDefaultValue: Int) : Configuration.Version.Extractor {
|
||||
|
||||
private val spec = Spec(versionKey, versionDefaultValue)
|
||||
private val containingPath = versionPath.split(".").let { if (it.size > 1) it.subList(0, it.size - 1) else null }
|
||||
private val key = versionPath.split(".").last()
|
||||
|
||||
override fun parse(configuration: Config, options: Configuration.Validation.Options): Valid<Int?> {
|
||||
private val spec = Spec(key, versionDefaultValue, containingPath?.joinToString("."))
|
||||
|
||||
override fun parse(configuration: Config, options: Configuration.Validation.Options): Valid<Int> {
|
||||
|
||||
return spec.parse(configuration)
|
||||
}
|
||||
|
||||
private class Spec(versionKey: String, versionDefaultValue: Int?) : Configuration.Specification<Int?>("Version") {
|
||||
private class Spec(key: String, versionDefaultValue: Int, prefix: String?) : Configuration.Specification<Int>("Version", prefix) {
|
||||
|
||||
private val version by int(key = versionKey).optional(versionDefaultValue)
|
||||
private val version by int(key = key).optional().withDefaultValue(versionDefaultValue)
|
||||
|
||||
override fun parseValid(configuration: Config) = valid(version.valueIn(configuration))
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ class PropertyTest {
|
||||
val configuration = configObject(key to null).toConfig()
|
||||
|
||||
val defaultValue = listOf(1L, 2L, 3L)
|
||||
val property = Configuration.Property.Definition.long(key).list().optional(defaultValue)
|
||||
val property = Configuration.Property.Definition.long(key).list().optional().withDefaultValue(defaultValue)
|
||||
println(property)
|
||||
|
||||
assertThat(property.key).isEqualTo(key)
|
||||
@ -173,7 +173,7 @@ class PropertyTest {
|
||||
val configuration = configObject(key to null).toConfig()
|
||||
|
||||
val defaultValue = 23L
|
||||
val property = Configuration.Property.Definition.long(key).optional(defaultValue)
|
||||
val property = Configuration.Property.Definition.long(key).optional().withDefaultValue(defaultValue)
|
||||
println(property)
|
||||
|
||||
assertThat(property.key).isEqualTo(key)
|
||||
|
@ -1,9 +1,7 @@
|
||||
package net.corda.common.configuration.parsing.internal
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import net.corda.common.validation.internal.Validated.Companion.invalid
|
||||
import net.corda.common.validation.internal.Validated.Companion.valid
|
||||
import net.corda.common.validation.internal.Validator
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
|
||||
@ -15,7 +13,7 @@ class PropertyValidationTest {
|
||||
val key = "a.b.c"
|
||||
val configuration = configObject().toConfig()
|
||||
|
||||
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.long(key)
|
||||
val property = Configuration.Property.Definition.long(key)
|
||||
|
||||
assertThat(property.validate(configuration).errors).satisfies { errors ->
|
||||
|
||||
@ -34,7 +32,7 @@ class PropertyValidationTest {
|
||||
val key = "a.b.c"
|
||||
val configuration = configObject(key to null).toConfig()
|
||||
|
||||
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.long(key)
|
||||
val property = Configuration.Property.Definition.long(key)
|
||||
|
||||
assertThat(property.validate(configuration).errors).satisfies { errors ->
|
||||
|
||||
@ -53,7 +51,7 @@ class PropertyValidationTest {
|
||||
val key = "a.b.c"
|
||||
val configuration = configObject().toConfig()
|
||||
|
||||
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.long(key).list()
|
||||
val property = Configuration.Property.Definition.long(key).list()
|
||||
|
||||
assertThat(property.validate(configuration).errors).satisfies { errors ->
|
||||
|
||||
@ -72,7 +70,7 @@ class PropertyValidationTest {
|
||||
val key = "a.b.c"
|
||||
val configuration = configObject(key to null).toConfig()
|
||||
|
||||
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.long(key).list()
|
||||
val property = Configuration.Property.Definition.long(key).list()
|
||||
|
||||
assertThat(property.validate(configuration).errors).satisfies { errors ->
|
||||
|
||||
@ -90,7 +88,7 @@ class PropertyValidationTest {
|
||||
|
||||
val key = "a.b.c"
|
||||
|
||||
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.long(key)
|
||||
val property = Configuration.Property.Definition.long(key)
|
||||
|
||||
val configuration = configObject(key to false).toConfig()
|
||||
|
||||
@ -110,7 +108,7 @@ class PropertyValidationTest {
|
||||
|
||||
val key = "a.b.c"
|
||||
|
||||
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.long(key)
|
||||
val property = Configuration.Property.Definition.long(key)
|
||||
|
||||
val configuration = configObject(key to 1.2).toConfig()
|
||||
|
||||
@ -130,7 +128,7 @@ class PropertyValidationTest {
|
||||
|
||||
val key = "a.b.c"
|
||||
|
||||
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.double(key)
|
||||
val property = Configuration.Property.Definition.double(key)
|
||||
|
||||
val configuration = configObject(key to 1).toConfig()
|
||||
|
||||
@ -142,7 +140,7 @@ class PropertyValidationTest {
|
||||
|
||||
val key = "a.b.c"
|
||||
|
||||
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.long(key).list()
|
||||
val property = Configuration.Property.Definition.long(key).list()
|
||||
|
||||
val configuration = configObject(key to listOf(false, true)).toConfig()
|
||||
|
||||
@ -162,7 +160,7 @@ class PropertyValidationTest {
|
||||
|
||||
val key = "a.b.c"
|
||||
|
||||
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.long(key)
|
||||
val property = Configuration.Property.Definition.long(key)
|
||||
|
||||
val configuration = configObject(key to listOf(1, 2, 3)).toConfig()
|
||||
|
||||
@ -182,7 +180,7 @@ class PropertyValidationTest {
|
||||
|
||||
val key = "a.b.c"
|
||||
|
||||
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.long(key).list()
|
||||
val property = Configuration.Property.Definition.long(key).list()
|
||||
|
||||
val configuration = configObject(key to 1).toConfig()
|
||||
|
||||
@ -205,7 +203,7 @@ class PropertyValidationTest {
|
||||
val nestedKey = "d"
|
||||
val nestedPropertySchema = Configuration.Schema.withProperties(Configuration.Property.Definition.long(nestedKey))
|
||||
|
||||
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.nestedObject(key, nestedPropertySchema)
|
||||
val property = Configuration.Property.Definition.nestedObject(key, nestedPropertySchema)
|
||||
|
||||
val configuration = configObject(key to configObject(nestedKey to false)).toConfig()
|
||||
|
||||
@ -228,7 +226,7 @@ class PropertyValidationTest {
|
||||
val nestedKey = "d"
|
||||
val nestedPropertySchema = Configuration.Schema.withProperties(Configuration.Property.Definition.long(nestedKey))
|
||||
|
||||
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.nestedObject(key, nestedPropertySchema)
|
||||
val property = Configuration.Property.Definition.nestedObject(key, nestedPropertySchema)
|
||||
|
||||
val configuration = configObject(key to configObject()).toConfig()
|
||||
|
||||
@ -251,7 +249,7 @@ class PropertyValidationTest {
|
||||
val nestedKey = "d"
|
||||
val nestedPropertySchema = Configuration.Schema.withProperties(Configuration.Property.Definition.long(nestedKey))
|
||||
|
||||
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.nestedObject(key, nestedPropertySchema)
|
||||
val property = Configuration.Property.Definition.nestedObject(key, nestedPropertySchema)
|
||||
|
||||
val configuration = configObject(key to configObject(nestedKey to null)).toConfig()
|
||||
|
||||
@ -273,7 +271,7 @@ class PropertyValidationTest {
|
||||
|
||||
val nestedKey = "d"
|
||||
|
||||
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.nestedObject(key)
|
||||
val property = Configuration.Property.Definition.nestedObject(key)
|
||||
|
||||
val configuration = configObject(key to configObject(nestedKey to false)).toConfig()
|
||||
|
||||
@ -285,7 +283,7 @@ class PropertyValidationTest {
|
||||
|
||||
val key = "a"
|
||||
|
||||
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.string(key).mapValid(::parseAddress)
|
||||
val property = Configuration.Property.Definition.string(key).mapValid(::parseAddress)
|
||||
|
||||
val host = "localhost"
|
||||
val port = 8080
|
||||
@ -301,7 +299,7 @@ class PropertyValidationTest {
|
||||
|
||||
val key = "a.b.c"
|
||||
|
||||
val property: Validator<Config, Configuration.Validation.Error, Configuration.Validation.Options> = Configuration.Property.Definition.string(key).mapValid(::parseAddress)
|
||||
val property = Configuration.Property.Definition.string(key).mapValid(::parseAddress)
|
||||
|
||||
val host = "localhost"
|
||||
val port = 8080
|
||||
|
@ -39,7 +39,7 @@ class SpecificationTest {
|
||||
val rpcSettings = RpcSettingsSpec.parse(configuration)
|
||||
|
||||
assertThat(rpcSettings.isValid).isTrue()
|
||||
assertThat(rpcSettings.valueOrThrow()).satisfies { value ->
|
||||
assertThat(rpcSettings.orThrow()).satisfies { value ->
|
||||
|
||||
assertThat(value.useSsl).isEqualTo(useSslValue)
|
||||
assertThat(value.addresses).satisfies { addresses ->
|
||||
|
@ -9,8 +9,8 @@ import org.junit.Test
|
||||
|
||||
class VersionExtractorTest {
|
||||
|
||||
private val versionExtractor = Configuration.Version.Extractor.fromKey("configuration.metadata.version")
|
||||
private val extractVersion: (Config) -> Valid<Int?> = { config -> versionExtractor.parse(config) }
|
||||
private val versionExtractor = Configuration.Version.Extractor.fromPath("configuration.metadata.version")
|
||||
private val extractVersion: (Config) -> Valid<Int> = { config -> versionExtractor.parse(config) }
|
||||
|
||||
@Test
|
||||
fun version_header_extraction_present() {
|
||||
@ -18,7 +18,7 @@ class VersionExtractorTest {
|
||||
val versionValue = Configuration.Version.Extractor.DEFAULT_VERSION_VALUE + 1
|
||||
val rawConfiguration = configObject("configuration" to configObject("metadata" to configObject("version" to versionValue), "node" to configObject("p2pAddress" to "localhost:8080"))).toConfig()
|
||||
|
||||
val version = extractVersion.invoke(rawConfiguration).valueOrThrow()
|
||||
val version = extractVersion.invoke(rawConfiguration).orThrow()
|
||||
assertThat(version).isEqualTo(versionValue)
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ class VersionExtractorTest {
|
||||
|
||||
val rawConfiguration = configObject("configuration" to configObject("node" to configObject("p2pAddress" to "localhost:8080"))).toConfig()
|
||||
|
||||
val version = extractVersion.invoke(rawConfiguration).valueOrThrow()
|
||||
val version = extractVersion.invoke(rawConfiguration).orThrow()
|
||||
assertThat(version).isEqualTo(Configuration.Version.Extractor.DEFAULT_VERSION_VALUE)
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ class VersionExtractorTest {
|
||||
|
||||
val rawConfiguration = configObject("configuration" to configObject("metadata" to configObject(), "node" to configObject("p2pAddress" to "localhost:8080"))).toConfig()
|
||||
|
||||
val version = extractVersion.invoke(rawConfiguration).valueOrThrow()
|
||||
val version = extractVersion.invoke(rawConfiguration).orThrow()
|
||||
|
||||
assertThat(version).isEqualTo(Configuration.Version.Extractor.DEFAULT_VERSION_VALUE)
|
||||
}
|
||||
@ -46,7 +46,7 @@ class VersionExtractorTest {
|
||||
|
||||
val rawConfiguration = configObject("configuration" to configObject("metadata" to configObject("version" to null), "node" to configObject("p2pAddress" to "localhost:8080"))).toConfig()
|
||||
|
||||
val version = extractVersion.invoke(rawConfiguration).valueOrThrow()
|
||||
val version = extractVersion.invoke(rawConfiguration).orThrow()
|
||||
|
||||
assertThat(version).isEqualTo(Configuration.Version.Extractor.DEFAULT_VERSION_VALUE)
|
||||
}
|
||||
@ -56,7 +56,7 @@ class VersionExtractorTest {
|
||||
|
||||
val rawConfiguration = configObject().toConfig()
|
||||
|
||||
val version = extractVersion.invoke(rawConfiguration).valueOrThrow()
|
||||
val version = extractVersion.invoke(rawConfiguration).orThrow()
|
||||
|
||||
assertThat(version).isEqualTo(Configuration.Version.Extractor.DEFAULT_VERSION_VALUE)
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ class VersionedParsingExampleTest {
|
||||
@Test
|
||||
fun correct_parsing_function_is_used_for_present_version() {
|
||||
|
||||
val versionParser = Configuration.Version.Extractor.fromKey("configuration.metadata.version", null)
|
||||
val versionParser = Configuration.Version.Extractor.fromPath("configuration.metadata.version")
|
||||
val extractVersion: (Config) -> Valid<Int> = { config -> versionParser.parseRequired(config) }
|
||||
val parseConfiguration = VersionedSpecificationRegistry.mapping(extractVersion, 1 to RpcSettingsSpec.V1, 2 to RpcSettingsSpec.V2)
|
||||
|
||||
@ -35,7 +35,7 @@ class VersionedParsingExampleTest {
|
||||
fun default_value_is_used_for_absent_version() {
|
||||
|
||||
val defaultVersion = 2
|
||||
val versionParser = Configuration.Version.Extractor.fromKey("configuration.metadata.version", defaultVersion)
|
||||
val versionParser = Configuration.Version.Extractor.fromPath("configuration.metadata.version", defaultVersion)
|
||||
val extractVersion: (Config) -> Valid<Int> = { config -> versionParser.parseRequired(config) }
|
||||
val parseConfiguration = VersionedSpecificationRegistry.mapping(extractVersion, 1 to RpcSettingsSpec.V1, 2 to RpcSettingsSpec.V2)
|
||||
|
||||
@ -52,7 +52,7 @@ class VersionedParsingExampleTest {
|
||||
private fun assertResult(result: Valid<RpcSettings>, principalAddressValue: Address, adminAddressValue: Address) {
|
||||
|
||||
assertThat(result.isValid).isTrue()
|
||||
assertThat(result.valueOrThrow()).satisfies { value ->
|
||||
assertThat(result.orThrow()).satisfies { value ->
|
||||
|
||||
assertThat(value.principal).isEqualTo(principalAddressValue)
|
||||
assertThat(value.admin).isEqualTo(adminAddressValue)
|
||||
|
@ -39,7 +39,7 @@ interface Validated<TARGET, ERROR> {
|
||||
*
|
||||
* @throws IllegalStateException or the result of [exceptionOnErrors] if there are errors.
|
||||
*/
|
||||
fun valueOrThrow(exceptionOnErrors: (Set<ERROR>) -> Exception = { errors -> IllegalStateException(errors.joinToString(System.lineSeparator())) }): TARGET
|
||||
fun orThrow(exceptionOnErrors: (Set<ERROR>) -> Exception = { errors -> IllegalStateException(errors.joinToString(System.lineSeparator())) }): TARGET
|
||||
|
||||
/**
|
||||
* Applies the [convert] function to the [TARGET] value, if valid. Otherwise, returns a [Validated] monad with a [MAPPED] generic type and the current errors set.
|
||||
@ -113,7 +113,7 @@ interface Validated<TARGET, ERROR> {
|
||||
class Successful<TARGET, ERROR>(override val value: TARGET) : Result<TARGET, ERROR>(), Validated<TARGET, ERROR> {
|
||||
override val errors: Set<ERROR> = emptySet<ERROR>()
|
||||
|
||||
override fun valueOrThrow(exceptionOnErrors: (Set<ERROR>) -> Exception) = value
|
||||
override fun orThrow(exceptionOnErrors: (Set<ERROR>) -> Exception) = value
|
||||
|
||||
override fun <MAPPED> map(convert: (TARGET) -> MAPPED): Validated<MAPPED, ERROR> {
|
||||
return valid(convert.invoke(value))
|
||||
@ -138,7 +138,7 @@ interface Validated<TARGET, ERROR> {
|
||||
|
||||
override val value: TARGET get() = throw IllegalStateException("Invalid state.")
|
||||
|
||||
override fun valueOrThrow(exceptionOnErrors: (Set<ERROR>) -> Exception) = throw exceptionOnErrors.invoke(errors)
|
||||
override fun orThrow(exceptionOnErrors: (Set<ERROR>) -> Exception) = throw exceptionOnErrors.invoke(errors)
|
||||
|
||||
override fun <MAPPED> map(convert: (TARGET) -> MAPPED): Validated<MAPPED, ERROR> {
|
||||
return invalid(errors)
|
||||
|
@ -8,5 +8,5 @@ interface Validator<TARGET : Any, ERROR : Any, OPTIONS> {
|
||||
/**
|
||||
* Validates [target] using given [options], producing a [Validated] monad wrapping either a valid [target] or a set of [ERROR]s.
|
||||
*/
|
||||
fun validate(target: TARGET, options: OPTIONS? = null): Validated<TARGET, ERROR>
|
||||
fun validate(target: TARGET, options: OPTIONS): Validated<TARGET, ERROR>
|
||||
}
|
@ -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>
|
||||
|
@ -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`.
|
||||
|
@ -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
|
||||
--------
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
---------------
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -224,6 +224,8 @@ private fun <T : Enum<T>> enumBridge(clazz: Class<T>, name: String): T {
|
||||
*/
|
||||
fun Any.toConfig(): Config = ConfigValueFactory.fromMap(toConfigMap()).toConfig()
|
||||
|
||||
fun Any.toConfigValue(): ConfigValue = if (this is ConfigValue) this else ConfigValueFactory.fromAnyRef(convertValue(this))
|
||||
|
||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
||||
// Reflect over the fields of the receiver and generate a value Map that can use to create Config object.
|
||||
private fun Any.toConfigMap(): Map<String, Any> {
|
||||
@ -255,6 +257,28 @@ private fun Any.toConfigMap(): Map<String, Any> {
|
||||
return values
|
||||
}
|
||||
|
||||
private fun convertValue(value: Any): Any {
|
||||
|
||||
return if (value is String || value is Boolean || value is Number) {
|
||||
// These types are supported by Config as use as is
|
||||
value
|
||||
} else if (value is Temporal || value is NetworkHostAndPort || value is CordaX500Name || value is Path || value is URL || value is UUID || value is X500Principal) {
|
||||
// These types make sense to be represented as Strings and the exact inverse parsing function for use in parseAs
|
||||
value.toString()
|
||||
} else if (value is Enum<*>) {
|
||||
// Expicitly use the Enum's name in case the toString is overridden, which would make parsing problematic.
|
||||
value.name
|
||||
} else if (value is Properties) {
|
||||
// For Properties we treat keys with . as nested configs
|
||||
ConfigFactory.parseMap(uncheckedCast(value)).root()
|
||||
} else if (value is Iterable<*>) {
|
||||
value.toConfigIterable()
|
||||
} else {
|
||||
// Else this is a custom object recursed over
|
||||
value.toConfigMap()
|
||||
}
|
||||
}
|
||||
|
||||
// For Iterables figure out the type parameter and apply the same logic as above on the individual elements.
|
||||
private fun Iterable<*>.toConfigIterable(field: Field): Iterable<Any?> {
|
||||
val elementType = (field.genericType as ParameterizedType).actualTypeArguments[0] as Class<*>
|
||||
@ -282,6 +306,8 @@ private fun Iterable<*>.toConfigIterable(field: Field): Iterable<Any?> {
|
||||
}
|
||||
}
|
||||
|
||||
private fun Iterable<*>.toConfigIterable(): Iterable<Any?> = map { element -> element?.let(::convertValue) }
|
||||
|
||||
// The typesafe .getBoolean function is case sensitive, this is a case insensitive version
|
||||
fun Config.getBooleanCaseInsensitive(path: String): Boolean {
|
||||
try {
|
||||
@ -300,7 +326,6 @@ private val logger = LoggerFactory.getLogger("net.corda.nodeapi.internal.config"
|
||||
enum class UnknownConfigKeysPolicy(private val handle: (Set<String>, logger: Logger) -> Unit) {
|
||||
|
||||
FAIL({ unknownKeys, _ -> throw UnknownConfigurationKeysException.of(unknownKeys) }),
|
||||
WARN({ unknownKeys, logger -> logger.warn("Unknown configuration keys found: ${unknownKeys.joinToString(", ", "[", "]")}.") }),
|
||||
IGNORE({ _, _ -> });
|
||||
|
||||
fun handle(unknownKeys: Set<String>, logger: Logger) {
|
||||
|
@ -22,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 {
|
||||
|
@ -73,6 +73,7 @@ dependencies {
|
||||
compile project(':tools:shell')
|
||||
compile project(':tools:cliutils')
|
||||
compile project(':common-validation')
|
||||
compile project(':common-configuration-parsing')
|
||||
|
||||
// Log4J: logging framework (with SLF4J bindings)
|
||||
compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
|
||||
|
@ -1,10 +1,17 @@
|
||||
package net.corda.node
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigException
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import net.corda.common.configuration.parsing.internal.Configuration
|
||||
import net.corda.common.validation.internal.Validated
|
||||
import net.corda.common.validation.internal.Validated.Companion.invalid
|
||||
import net.corda.common.validation.internal.Validated.Companion.valid
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.node.services.config.ConfigHelper
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.config.Valid
|
||||
import net.corda.node.services.config.parseAsNodeConfiguration
|
||||
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
|
||||
import picocli.CommandLine.Option
|
||||
@ -12,6 +19,9 @@ import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
open class SharedNodeCmdLineOptions {
|
||||
private companion object {
|
||||
private val logger by lazy { loggerFor<SharedNodeCmdLineOptions>() }
|
||||
}
|
||||
@Option(
|
||||
names = ["-b", "--base-directory"],
|
||||
description = ["The node working directory where all the files are kept."]
|
||||
@ -37,9 +47,19 @@ open class SharedNodeCmdLineOptions {
|
||||
)
|
||||
var devMode: Boolean? = null
|
||||
|
||||
open fun parseConfiguration(configuration: Config): NodeConfiguration = configuration.parseAsNodeConfiguration(unknownConfigKeysPolicy::handle)
|
||||
open fun parseConfiguration(configuration: Config): Valid<NodeConfiguration> {
|
||||
|
||||
open fun rawConfiguration(): Config = ConfigHelper.loadConfig(baseDirectory, configFile)
|
||||
val option = Configuration.Validation.Options(strict = unknownConfigKeysPolicy == UnknownConfigKeysPolicy.FAIL)
|
||||
return configuration.parseAsNodeConfiguration(option)
|
||||
}
|
||||
|
||||
open fun rawConfiguration(): Validated<Config, ConfigException> {
|
||||
return try {
|
||||
valid(ConfigHelper.loadConfig(baseDirectory, configFile))
|
||||
} catch (e: ConfigException) {
|
||||
return invalid(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun copyFrom(other: SharedNodeCmdLineOptions) {
|
||||
baseDirectory = other.baseDirectory
|
||||
@ -47,11 +67,32 @@ open class SharedNodeCmdLineOptions {
|
||||
unknownConfigKeysPolicy= other.unknownConfigKeysPolicy
|
||||
devMode = other.devMode
|
||||
}
|
||||
|
||||
fun logRawConfigurationErrors(errors: Set<ConfigException>) {
|
||||
if (errors.isNotEmpty()) {
|
||||
logger.error("There were error(s) while attempting to load the node configuration:")
|
||||
}
|
||||
errors.forEach { error ->
|
||||
when (error) {
|
||||
is ConfigException.IO -> logger.error(configFileNotFoundMessage(configFile))
|
||||
else -> logger.error(error.message, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun configFileNotFoundMessage(configFile: Path): String {
|
||||
return """
|
||||
Unable to load the node config file from '$configFile'.
|
||||
|
||||
Try setting the --base-directory flag to change which directory the node
|
||||
is looking in, or use the --config-file flag to specify it explicitly.
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
class InitialRegistrationCmdLineOptions : SharedNodeCmdLineOptions() {
|
||||
override fun parseConfiguration(configuration: Config): NodeConfiguration {
|
||||
return super.parseConfiguration(configuration).also { config ->
|
||||
override fun parseConfiguration(configuration: Config): Valid<NodeConfiguration> {
|
||||
return super.parseConfiguration(configuration).doIfValid { config ->
|
||||
require(!config.devMode) { "Registration cannot occur in development mode" }
|
||||
require(config.compatibilityZoneURL != null || config.networkServices != null) {
|
||||
"compatibilityZoneURL or networkServices must be present in the node configuration file in registration mode."
|
||||
@ -121,8 +162,8 @@ open class NodeCmdLineOptions : SharedNodeCmdLineOptions() {
|
||||
)
|
||||
var networkRootTrustStorePassword: String? = null
|
||||
|
||||
override fun parseConfiguration(configuration: Config): NodeConfiguration {
|
||||
return super.parseConfiguration(configuration).also { config ->
|
||||
override fun parseConfiguration(configuration: Config): Valid<NodeConfiguration> {
|
||||
return super.parseConfiguration(configuration).doIfValid { config ->
|
||||
if (isRegistration) {
|
||||
require(!config.devMode) { "Registration cannot occur in development mode" }
|
||||
require(config.compatibilityZoneURL != null || config.networkServices != null) {
|
||||
@ -132,7 +173,7 @@ open class NodeCmdLineOptions : SharedNodeCmdLineOptions() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun rawConfiguration(): Config {
|
||||
override fun rawConfiguration(): Validated<Config, ConfigException> {
|
||||
val configOverrides = mutableMapOf<String, Any>()
|
||||
configOverrides += "noLocalShell" to noLocalShell
|
||||
if (sshdServer) {
|
||||
@ -141,7 +182,11 @@ open class NodeCmdLineOptions : SharedNodeCmdLineOptions() {
|
||||
devMode?.let {
|
||||
configOverrides += "devMode" to it
|
||||
}
|
||||
return ConfigHelper.loadConfig(baseDirectory, configFile, configOverrides = ConfigFactory.parseMap(configOverrides))
|
||||
return try {
|
||||
valid(ConfigHelper.loadConfig(baseDirectory, configFile, configOverrides = ConfigFactory.parseMap(configOverrides)))
|
||||
} catch (e: ConfigException) {
|
||||
return invalid(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ import net.corda.node.internal.Node.Companion.isInvalidJavaVersion
|
||||
import net.corda.node.internal.cordapp.MultipleCordappsForFlowException
|
||||
import net.corda.node.internal.subcommands.*
|
||||
import net.corda.node.internal.subcommands.ValidateConfigurationCli.Companion.logConfigurationErrors
|
||||
import net.corda.node.internal.subcommands.ValidateConfigurationCli.Companion.logRawConfig
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.config.shouldStartLocalShell
|
||||
import net.corda.node.services.config.shouldStartSSHDaemon
|
||||
@ -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
|
||||
|
@ -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>
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package net.corda.node.services.config.schema.parsers
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigObject
|
||||
import com.typesafe.config.ConfigUtil
|
||||
import net.corda.common.configuration.parsing.internal.Configuration
|
||||
import net.corda.common.validation.internal.Validated.Companion.invalid
|
||||
import net.corda.common.validation.internal.Validated.Companion.valid
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.node.services.config.Valid
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URL
|
||||
import java.nio.file.InvalidPathException
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.*
|
||||
import javax.security.auth.x500.X500Principal
|
||||
|
||||
internal fun toProperties(rawValue: ConfigObject): Properties = rawValue.toConfig().toProperties()
|
||||
|
||||
private fun Config.toProperties() = entrySet().associateByTo(Properties(), { ConfigUtil.splitPath(it.key).joinToString(".") }, { it.value.unwrapped().toString() })
|
||||
|
||||
internal fun toCordaX500Name(rawValue: String) = attempt<CordaX500Name, IllegalArgumentException> { CordaX500Name.parse(rawValue) }
|
||||
|
||||
internal fun toURL(rawValue: String) = attempt<URL, MalformedURLException> { URL(rawValue) }
|
||||
|
||||
internal fun toUUID(rawValue: String) = attempt<UUID, IllegalArgumentException> { UUID.fromString(rawValue) }
|
||||
|
||||
internal fun toNetworkHostAndPort(rawValue: String) = attempt<NetworkHostAndPort, IllegalArgumentException> { NetworkHostAndPort.parse(rawValue) }
|
||||
|
||||
internal fun toPrincipal(rawValue: String) = attempt<X500Principal, IllegalArgumentException> { X500Principal(rawValue) }
|
||||
|
||||
internal fun toPath(rawValue: String) = attempt<Path, InvalidPathException> { Paths.get(rawValue) }
|
||||
|
||||
private inline fun <RESULT, reified ERROR : Exception> attempt(action: () -> RESULT, message: (ERROR) -> String): Valid<RESULT> {
|
||||
return try {
|
||||
valid(action.invoke())
|
||||
} catch (e: Exception) {
|
||||
when (e) {
|
||||
is ERROR -> badValue(message.invoke(e))
|
||||
else -> throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun <reified RESULT, reified ERROR : Exception> attempt(action: () -> RESULT) = attempt(action, { e: ERROR -> "value does not comply with ${RESULT::class.java.simpleName} specification (${e.message})" })
|
||||
|
||||
internal fun <RESULT> validValue(result: RESULT) = valid<RESULT, Configuration.Validation.Error>(result)
|
||||
|
||||
internal fun <RESULT> badValue(message: String) = invalid<RESULT, Configuration.Validation.Error>(Configuration.Validation.Error.BadValue.of(message))
|
@ -0,0 +1,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]))
|
||||
}
|
||||
}
|
@ -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)
|
@ -1,2 +1,2 @@
|
||||
log4jContextSelector=net.corda.node.utilities.logging.AsyncLoggerContextSelectorNoThreadLocal
|
||||
Log4jContextSelector=net.corda.node.utilities.logging.AsyncLoggerContextSelectorNoThreadLocal
|
||||
AsyncLogger.RingBufferSize=262144
|
@ -72,7 +72,6 @@
|
||||
multiParam: false
|
||||
acceptableValues:
|
||||
- "FAIL"
|
||||
- "WARN"
|
||||
- "IGNORE"
|
||||
- parameterName: "--sshd"
|
||||
parameterType: "boolean"
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
log4j.configurationFactory=net.corda.cliutils.CordaLog4j2ConfigFactory
|
@ -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") }
|
||||
|
@ -5,6 +5,8 @@ import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import com.typesafe.config.ConfigValueFactory
|
||||
import net.corda.bootstrapper.docker.DockerUtils
|
||||
import net.corda.common.configuration.parsing.internal.Configuration
|
||||
import net.corda.common.validation.internal.Validated
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.config.parseAsNodeConfiguration
|
||||
import org.slf4j.LoggerFactory
|
||||
@ -30,14 +32,13 @@ open class NodeBuilder {
|
||||
.withBaseDirectory(nodeDir)
|
||||
.exec(BuildImageResultCallback()).awaitImageId()
|
||||
LOG.info("finished building docker image for: $nodeDir with id: $nodeImageId")
|
||||
val config = nodeConfig.parseAsNodeConfigWithFallback(ConfigFactory.parseFile(copiedNode.configFile))
|
||||
val config = nodeConfig.parseAsNodeConfigWithFallback(ConfigFactory.parseFile(copiedNode.configFile)).orThrow()
|
||||
return copiedNode.builtNode(config, nodeImageId)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
fun Config.parseAsNodeConfigWithFallback(preCopyConfig: Config): NodeConfiguration {
|
||||
fun Config.parseAsNodeConfigWithFallback(preCopyConfig: Config): Validated<NodeConfiguration, Configuration.Validation.Error> {
|
||||
val nodeConfig = this
|
||||
.withValue("baseDirectory", ConfigValueFactory.fromAnyRef(""))
|
||||
.withFallback(ConfigFactory.parseResources("reference.conf"))
|
||||
|
Loading…
x
Reference in New Issue
Block a user