Config API updates

This commit is contained in:
Jonathan Shook 2021-07-22 17:38:08 -05:00
parent bb865d9aae
commit 9f54032c43
7 changed files with 174 additions and 52 deletions

View File

@ -0,0 +1,9 @@
package io.nosqlbench.nb.api.config.standard;
import io.nosqlbench.nb.api.errors.BasicError;
public class NBConfigError extends BasicError {
public NBConfigError(String s) {
super(s);
}
}

View File

@ -1,18 +1,43 @@
package io.nosqlbench.nb.api.config.standard;
import java.util.List;
import java.util.Map;
/**
* This configuration model describes what is valid to submit
* for configuration for a given configurable object.
* <p>This configuration model describes what is valid to submit
* for configuration for a given configurable object. Once this
* is provided by a configurable element, it is used internally
* by NoSQLBench to ensure that only valid configuration are
* given to newly built objects.</p>
*
* <p>It is conventional to put the config model at the bottom of any
* implementing class for quick reference.</p>
*/
public interface NBConfigModel {
Map<String, Param<?>> getElements();
Map<String, Param<?>> getNamedParams();
List<Param<?>> getParams();
Class<?> getOf();
void assertValidConfig(Map<String, ?> config);
NBConfiguration apply(Map<String, ?> config);
<V> Param<V> getParam(String... name);
/**
* Extract the fields from the shared config into a separate config,
* removing those that are defined in this model and leaving
* extraneous config fields in the provided model.
*
* <em>This method mutates the map that is provided.</em>
*
* @param sharedConfig A config map which can provide fields to multiple models
* @return A new configuration for the extracted fields only.
*/
NBConfiguration extract(Map<String, ?> sharedConfig);
NBConfigModel add(NBConfigModel otherModel);
}

View File

@ -1,5 +1,5 @@
package io.nosqlbench.nb.api.config.standard;
public interface NBCanValidateConfig {
public interface NBConfigModelProvider {
NBConfigModel getConfigModel();
}

View File

@ -3,12 +3,31 @@ package io.nosqlbench.nb.api.config.standard;
/**
* All implementation types which wish to have a type-marshalled configuration
* should implement this interface.
*
* When a type which implements this interface is instantiated, and the
* {@link NBConfiguration} was not injected into its constructor,
* the builder should call {@link #applyConfig(NBConfiguration)} immediately
* after calling the constructor.
*/
public interface NBConfigurable extends NBCanConfigure, NBCanValidateConfig {
public interface NBConfigurable extends NBCanConfigure, NBConfigModelProvider {
/**
* Implementors should take care to ensure that this can be called after
* initial construction without unexpected interactions between
* construction parameters and configuration parameters.
* @param cfg The configuration data to be applied to a new instance
*/
@Override
void applyConfig(NBConfiguration cfg);
/**
* Implement this method by returning an instance of {@link ConfigModel}.
* Any configuration which is provided to the {@link #applyConfig(NBConfiguration)}
* method will be validated through this model. A configuration model
* is <em>required</em> in order to build a validated configuration
* from source data provided by a user.
* @return A valid configuration model for the implementing class
*/
@Override
NBConfigModel getConfigModel();
}

View File

@ -1,7 +1,6 @@
package io.nosqlbench.nb.api.config.standard;
import io.nosqlbench.nb.api.NBEnvironment;
import io.nosqlbench.nb.api.errors.BasicError;
import java.util.Arrays;
import java.util.LinkedHashMap;
@ -10,7 +9,7 @@ import java.util.Optional;
public class NBConfiguration {
private final LinkedHashMap<String, Object> data;
private final NBConfigModel configModel;
private final NBConfigModel model;
/**
* Create a NBConfigReader from a known valid configuration and a config model.
@ -22,7 +21,7 @@ public class NBConfiguration {
*/
protected NBConfiguration(NBConfigModel model, LinkedHashMap<String, Object> validConfig) {
this.data = validConfig;
this.configModel = model;
this.model = model;
}
/**
@ -39,41 +38,55 @@ public class NBConfiguration {
String span = optionalValue.get();
Optional<String> maybeInterpolated = NBEnvironment.INSTANCE.interpolate(span);
if (maybeInterpolated.isEmpty()) {
throw new BasicError("Unable to interpolate '" + span +"' with env vars.");
throw new NBConfigError("Unable to interpolate '" + span +"' with env vars.");
}
return maybeInterpolated;
}
public String paramEnv(String name) {
return paramEnv(name, String.class);
public String getWithEnv(String name) {
return getWithEnv(name, String.class);
}
public <T> T paramEnv(String name, Class<? extends T> vclass) {
T param = param(name, vclass);
if (param instanceof String) {
Optional<String> interpolated = NBEnvironment.INSTANCE.interpolate(param.toString());
public <T> T getWithEnv(String name, Class<? extends T> vclass) {
T value = get(name, vclass);
if (value==null) {
}
if (value instanceof String) {
Optional<String> interpolated = NBEnvironment.INSTANCE.interpolate(value.toString());
if (interpolated.isEmpty()) {
throw new RuntimeException("Unable to interpolate env and sys props in '" + param + "'");
throw new NBConfigError("Unable to interpolate env and sys props in '" + value + "'");
}
return (T) interpolated.get();
String result = interpolated.get();
return ConfigModel.convertValueTo(this.getClass().getSimpleName(),name, result, vclass);
} else {
return param;
return value;
}
}
public String get(String name) {
return get(name, String.class);
}
public <T> T get(String name, Class<? extends T> type) {
if (!configModel.getElements().containsKey(name)) {
throw new BasicError("Parameter named '" + name + "' is not valid for " + configModel.getOf().getSimpleName() + ".");
Param<T> param = model.getParam(name);
if (param==null) {
throw new NBConfigError("Parameter named '" + name + "' is not valid for " + model.getOf().getSimpleName() + ".");
}
if (!param.isRequired()) {
throw new NBConfigError("Non-optional get on parameter declared optional '" + name + "'");
}
Object o = data.get(name);
if (o == null) {
throw new BasicError("config param '" + name + "' was not defined.");
throw new NBConfigError("config param '" + name + "' was not defined.");
}
if (type.isAssignableFrom(o.getClass())) {
return (T) o;
}
throw new BasicError("config param '" + name + "' was not assignable to class '" + type.getCanonicalName() + "'");
return ConfigModel.convertValueTo(this.getClass().getSimpleName(), name,o,type);
// if (type.isAssignableFrom(o.getClass())) {
// return (T) o;
// }
// throw new NBConfigError("config param '" + name + "' was not assignable to class '" + type.getCanonicalName() + "'");
}
public Optional<String> getOptional(String name) {
@ -87,9 +100,12 @@ public class NBConfiguration {
public <T> Optional<T> getOptional(Class<T> type, String... names) {
Object o = null;
for (String name : names) {
o = data.get(name);
if (o!=null) {
break;
Param<?> param = model.getParam(names);
if (param!=null) {
o = data.get(param.getNames());
if (o!=null) {
break;
}
}
}
if (o==null) {
@ -98,7 +114,7 @@ public class NBConfiguration {
if (type.isAssignableFrom(o.getClass())) {
return Optional.of((T) o);
}
throw new BasicError("config param " + Arrays.toString(names) +" was not assignable to class '" + type.getCanonicalName() + "'");
throw new NBConfigError("config param " + Arrays.toString(names) +" was not assignable to class '" + type.getCanonicalName() + "'");
}
public <T> T getOrDefault(String name, T defaultValue) {
@ -109,14 +125,14 @@ public class NBConfiguration {
if (defaultValue.getClass().isAssignableFrom(o.getClass())) {
return (T) o;
}
throw new BasicError("config parameter '" + name + "' is not assignable to required type '" + defaultValue.getClass() + "'");
throw new NBConfigError("config parameter '" + name + "' is not assignable to required type '" + defaultValue.getClass() + "'");
}
public <T> T param(String name, Class<? extends T> vclass) {
Object o = data.get(name);
Param<?> elem = configModel.getElements().get(name);
Param<?> elem = model.getNamedParams().get(name);
if (elem == null) {
throw new RuntimeException("Invalid config element named '" + name + "'");
throw new NBConfigError("Invalid config element named '" + name + "'");
}
Class<T> type = (Class<T>) elem.getType();
T typeCastedValue = type.cast(o);
@ -125,8 +141,8 @@ public class NBConfiguration {
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(this.configModel.getOf().getSimpleName()).append(":");
sb.append(this.configModel);
sb.append(this.model.getOf().getSimpleName()).append(":");
sb.append(this.model);
return sb.toString();
}

View File

@ -2,6 +2,6 @@ package io.nosqlbench.nb.api.config.standard;
import java.util.Map;
public interface NBMapConfigurable extends NBCanValidateConfig {
public interface NBMapConfigurable extends NBConfigModelProvider {
void applyConfig(Map<String, ?> providedConfig);
}

View File

@ -1,5 +1,6 @@
package io.nosqlbench.nb.api.config.standard;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -11,7 +12,7 @@ import java.util.regex.Pattern;
*/
public class Param<T> {
public final String name;
public final List<String> names;
public final Class<? extends T> type;
public String description;
private final T defaultValue;
@ -19,35 +20,85 @@ public class Param<T> {
private Pattern regex;
public Param(
String name,
List<String> names,
Class<? extends T> type,
String description,
boolean required,
T defaultValue
) {
this.name = name;
this.names = names;
this.type = type;
this.description = description;
this.required = required;
this.defaultValue = defaultValue;
}
public static <V> Param<V> optional(String name) {
return (Param<V>) optional(name,String.class);
/**
* Declare an optional String parameter with the given name.
* @param name the name of the parameter
*/
public static Param<String> optional(String name) {
return optional(List.of(name), String.class);
}
public static <V> Param<V> optional(String name, Class<V> type) {
return new Param<V>(name,type,null,false,null);
/**
* Declare an optional String parameter specified by any of the names. They act as synonyms.
* When users provide more than one of these in configuration data, it is considered an error.
*
* @param names one or more names that the parameter can be specified with.
*/
public static Param<String> optional(List<String> names) {
return optional(names, String.class);
}
public static <V> Param<V> defaultTo(String name, V defaultValue) {
return new Param<V>(name,(Class<V>) defaultValue.getClass(),null,false,null);
/**
* Declare an optional parameter specified by any of the names which must be assignable to
* (returnable as) the specified type.
* When users provide more than one of these in configuration data, it is considered an error.
*
* @param names one or more names that the parameter can be specified with.
* @param type The type of value that the provided configuration value must be returnable as (assignable to)
* @param <V> Generic type for inference.
*/
public static <V> Param<V> optional(List<String> names, Class<V> type) {
return new Param<V>(names, type, null, false, null);
}
/**
* Declare an optional parameter for the given name which must be assignable to
* (returnable as) the specified type.
* When users provide more than one of these in configuration data, it is considered an error.
*
* @param name the name of the parameter
* @param type The type of value that the provided configuration value must be returnable as (assignable to)
* @param <V> Generic type for inference.
*/
public static <V> Param<V> optional(String name, Class<V> type) {
return new Param<V>(List.of(name), type, null, false, null);
}
public static <V> Param<V> defaultTo(String name, V defaultValue) {
return new Param<V>(List.of(name), (Class<V>) defaultValue.getClass(), null, false, null);
}
public static <V> Param<V> defaultTo(List<String> names, V defaultValue) {
return new Param<V>(names, (Class<V>) defaultValue.getClass(), null, false, null);
}
public static <V> Param<V> required(String name, Class<V> type) {
return new Param<V>(List.of(name), type, null, true, null);
}
public static <V> Param<V> required(List<String> names, Class<V> type) {
return new Param<V>(names, type, null, true, null);
}
@Override
public String toString() {
return "Element{" +
"name='" + name + '\'' +
"names='" + names.toString() + '\'' +
", type=" + type +
", description='" + description + '\'' +
", required=" + required +
@ -55,8 +106,8 @@ public class Param<T> {
'}';
}
public String getName() {
return name;
public List<String> getNames() {
return names;
}
public Class<?> getType() {
@ -71,8 +122,9 @@ public class Param<T> {
return required;
}
public void setRequired(boolean required) {
public Param<?> setRequired(boolean required) {
this.required = required;
return this;
}
public T getDefaultValue() {
@ -88,6 +140,7 @@ public class Param<T> {
this.regex = regex;
return this;
}
public Param<T> setRegex(String pattern) {
this.regex = Pattern.compile(pattern);
return this;
@ -102,9 +155,9 @@ public class Param<T> {
if (value == null) {
if (isRequired()) {
return CheckResult.INVALID(this, null, "Value is null but " + this.getName() + " is required");
return CheckResult.INVALID(this, null, "Value is null but " + this.getNames() + " is required");
} else {
return CheckResult.VALID(this, null, "Value is null, but " + this.getName() + " is not required");
return CheckResult.VALID(this, null, "Value is null, but " + this.getNames() + " is not required");
}
}
@ -119,11 +172,11 @@ public class Param<T> {
if (!matcher.matches()) {
return CheckResult.INVALID(this, value,
"Could not match required pattern (" + getRegex().toString() +
") with value '" + value + "' for field '" + getName() + "'");
") with value '" + value + "' for field '" + getNames() + "'");
}
}
}
return CheckResult.VALID(this,value,"All validators passed for field '" + getName() + "'");
return CheckResult.VALID(this, value, "All validators passed for field '" + getNames() + "'");
}
public final static class CheckResult<T> {