added a richer config type for custom configs

This commit is contained in:
Jonathan Shook 2020-04-13 09:57:10 -05:00
parent 477af8c7bc
commit 7f3fcd5769
4 changed files with 182 additions and 0 deletions

View File

@ -0,0 +1,55 @@
# Custom Elements (rename to VirtData Config Elements)
## Status
The current progress of custom elements includes:
- ConfigData config type
- a proof of concept for "customConfig" values
The next step should be to introduce ConfigData to elements that support it via one of:
- automatic injection into constructors by parameter type
- Annotation support
- ThreadLocal configuration types
- ConfigAware injection
## Purpose
Functions may sometimes need to access environmental configuration data. When this is the case, it is not usually
reasonable to impose on the user to pass this into a function as initializer data within the siganature of each function
recipe.
Custom elements are a way to support this. A custom element is simply an object that can be injected into the virtdata's
resolver environment before function resolution continues. This is allows any function which may need custom elements to
ask the virtdata environment for them.
## Custom Elements Structure
Custom Elements are provided to callers as a <String,Object> map. Internally, elements may be structured as a list of
maps, so that layers of config can be added with precedence or overrides.
All access to the custom elements service by accessors should require a typed getter, with a type check in the call for
assignability to the target type. It is expected that functions which use these elements will use this typed accessor in
order to assert that:
1. a (potentially) required element is provided
2. it is of the type required for the caller.
## Documentation
It is important to document what every custom element does, and where it will be used. Specifically, functions which use
custom functions must provide additional details for users that specify what names, types, and effects custom elemements
may have on them.
### Functional Impact
Custom elements may change the semantics of function use. Specifically, when the effective value of custom elements
changes, functions will cease to act as pure functions in some way.
## Using Custom Elements
When you implement a function which uses custom elements, the function should *only* access the elements for the
purposes of instance and type checking in the constructor. Some custom elements may trigger additional initialization
logic before a function can ultimately return a value, but when possible, building any cacheable values should be
deferred to a lazy property to be used in the main apply method.

View File

@ -54,6 +54,7 @@ public class VirtDataComposer {
private final static MethodHandles.Lookup lookup = MethodHandles.publicLookup();
private final VirtDataFunctionLibrary functionLibrary;
private final Map<String,Object> customElements = new HashMap<>();
public VirtDataComposer(VirtDataFunctionLibrary functionLibrary) {

View File

@ -0,0 +1,91 @@
package io.nosqlbench.virtdata.core.config;
import io.nosqlbench.nb.api.errors.BasicError;
import java.util.*;
public class ConfigData {
private final ConfigData inner;
private final LinkedHashMap<String,Object> configs;
public ConfigData(LinkedHashMap<String,Object> configs, ConfigData inner) {
this.configs = configs;
this.inner =inner;
}
public ConfigData(LinkedHashMap<String,Object> configs) {
this.configs = configs;
this.inner = null;
}
public ConfigData() {
this.configs = new LinkedHashMap<>();
this.inner = null;
}
public ConfigData layer() {
return new ConfigData(new LinkedHashMap<>(),this);
}
public ConfigData layer(Map<String,Object> configs ){
return new ConfigData(new LinkedHashMap<>(configs),this);
}
/**
* Get the typed optional value for the requested parameter name.
* @param name The name of the parameter to use
* @param type The class type which the value must be assignable to.
* @param <T> The generic parameter of the class type
* @return An optional of type T
* @throws BasicError if a value is found which can't be returned as the
* specified type.
*/
public <T> Optional<T> get(String name, Class<? extends T> type) {
Object o = configs.get(name);
if (o!=null) {
if (type.isAssignableFrom(o.getClass())) {
return Optional.of(type.cast(o));
} else {
throw new BasicError("Tried to access a virtdata config element named '" + name +
"' as a '" + type.getCanonicalName() + "', but it was not compatible with that type");
}
}
if (inner !=null) {
return inner.get(name, type);
}
return Optional.empty();
}
/**
* Get the typed optional list for the requested list name. This is no different than
* getting an object without the list qualifier, except that the type checking is done for
* you internal to the getList method.
* @param name The name of the parameter to return
* @param type The type of the list element. Every element must be assignable to this type.
* @param <T> The generic parameter of the list element type
* @return An optional list of T
* @throws BasicError if any of the elements are not assignable to the required element type
*/
public <T> Optional<List<T>> getList(String name, Class<? extends T> type) {
Optional<List> found = get(name, List.class);
if (found.isPresent()) {
ArrayList<T> list = new ArrayList<>();
for (Object o : found.get()) {
if (type.isAssignableFrom(o.getClass())) {
list.add(type.cast(o));
} else {
throw new BasicError("Tried to access a virtdata config list element found under name '" + name +
"' as a '" + type.getCanonicalName() + "', but it was not compatible with that type");
}
}
return Optional.of(list);
}
return Optional.empty();
}
public void put(String paramName, List<String> paramValue) {
this.configs.put(paramName,paramValue);
}
}

View File

@ -0,0 +1,35 @@
package io.nosqlbench.virtdata.core.config;
import org.junit.Test;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
public class ConfigDataTest {
@Test
public void testLayer() {
ConfigData conf = new ConfigData();
conf.put("test1", List.of("t","e","s","t","1"));
Optional<List> test1 = conf.get("test1", List.class);
assertThat(test1).isPresent();
assertThat(test1.get()).containsExactly("t","e","s","t","1");
ConfigData layer2 = conf.layer(Map.of("test1",List.of("another")));
Optional<List> test2 = layer2.get("test1", List.class);
assertThat(test2).isPresent();
assertThat(test2.get()).containsExactly("another");
}
@Test
public void testList() {
ConfigData conf = new ConfigData();
conf.put("test1", List.of("t","e","s","t","1"));
Optional<List<String>> test1 = conf.getList("test1", String.class);
assertThat(test1).isPresent();
assertThat(test1.get()).containsExactly("t","e","s","t","1");
}
}