Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configuration Mapper #333

Merged
merged 42 commits into from
Jul 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
9aa768b
Configuration mapper
dmlloyd May 11, 2020
f699816
Fix compilation issues after master merge.
radcortez Jun 16, 2020
db7304e
Move ConfigMapping classes to integrate with SmallRyeConfig.
radcortez Jun 16, 2020
c7ab4ce
Remove mapper project, but kept code if we need to retrieve additiona…
radcortez Jun 17, 2020
d7075e8
Ignore properties validation for unregistered roots.
radcortez Jun 17, 2020
e45f2df
Support Bean style methods.
radcortez Jun 17, 2020
bce4822
Support optional mapping registration in the SmallRyeBuilder.
radcortez Jun 17, 2020
a6875df
Moved generic APIs to main package.
radcortez Jun 17, 2020
5a737a8
Support empty root prefix.
radcortez Jun 29, 2020
116daeb
Add Collections tests.
radcortez Jun 29, 2020
0746069
ConfigMapping defaults with Config.
radcortez Jun 29, 2020
7b6f080
ConfigMapping Converter test.
radcortez Jun 29, 2020
c92e450
Fixed ConfigMapping simple Optionals.
radcortez Jun 30, 2020
6fa6f5c
Remove unused class.
radcortez Jun 30, 2020
560d7d6
Added tests and fixed ConfigMapping Map support.
radcortez Jun 30, 2020
fd7ec82
Added a few more tests to ConfigMapping.
radcortez Jun 30, 2020
13394f3
Rename ConfigMapping to ConfigMappingProvider (to add annotation with…
radcortez Jun 30, 2020
e779b46
Move mapping annotations that can be used outside mapping context.
radcortez Jun 30, 2020
681281f
Remove unused mapper code.
radcortez Jun 30, 2020
4e1d0b2
Removed ConfigMapping on the fly.
radcortez Jul 14, 2020
700100c
Remove JavaBeans getter style method names.
radcortez Jul 14, 2020
1ea215e
Use KeyMap for default values.
radcortez Jul 15, 2020
2ab22a0
Add mapping ignores in the builder.
radcortez Jul 15, 2020
fd47521
Fixed formatting.
radcortez Jul 15, 2020
9e75bfb
Added javadoc to ConfigurationValidationException.
radcortez Jul 16, 2020
c2a84ec
Add configuration property for ConfigMapping debug.
radcortez Jul 16, 2020
d3cae3e
Don't add additional KeyMap on empty paths.
radcortez Jul 16, 2020
98b2ea6
Some code cleanup.
radcortez Jul 16, 2020
07abf93
Validate ConfigMapping on Builder.
radcortez Jul 17, 2020
1428b2a
DefaultConfigSource return empty map.
radcortez Jul 17, 2020
4088829
Improve KeyMap.toString performance.
radcortez Jul 17, 2020
f13b079
Moved ConfigValidationException to main package.
radcortez Jul 17, 2020
1dc64d4
Added Config Mappings directly into SmallRyeConfig.
radcortez Jul 17, 2020
73ffd2f
Added doPrivileged to System property lookup.
radcortez Jul 17, 2020
38fd880
Removed mapper package to remove public access to ConfigMapping classes.
radcortez Jul 23, 2020
7d19772
Better encapsulation of ConfigMapping data and SmallRyeConfig.
radcortez Jul 23, 2020
c7f122c
Fix visibilities if generated class is from different package.
radcortez Jul 24, 2020
a9e0ad3
Initial CDI support for ConfigMapping.
radcortez Jul 24, 2020
5ee3783
Generate classes in the mapper package.
radcortez Jul 24, 2020
547ecea
Use ClassDefiner from SmallRye Commons Classloader instead of Unsafe.
radcortez Jul 30, 2020
942a132
Improve SmallRyeConfig and ConfigMappings integration.
radcortez Jul 30, 2020
40ec717
Remove toMap from KeyMap.
radcortez Jul 31, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions implementation/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@
<groupId>io.smallrye.common</groupId>
<artifactId>smallrye-common-constraint</artifactId>
</dependency>
<dependency>
<groupId>io.smallrye.common</groupId>
<artifactId>smallrye-common-classloader</artifactId>
</dependency>

<dependency>
<groupId>org.jboss.logging</groupId>
Expand All @@ -75,6 +79,11 @@
<artifactId>jboss-logging-processor</artifactId>
</dependency>

<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
Expand Down
14 changes: 14 additions & 0 deletions implementation/src/main/java/io/smallrye/config/ConfigMapping.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.smallrye.config;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ConfigMapping {
String value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
package io.smallrye.config;

import static io.smallrye.config.ConfigMappingInterface.LeafProperty;
import static io.smallrye.config.ConfigMappingInterface.MapProperty;
import static io.smallrye.config.ConfigMappingInterface.PrimitiveProperty;
import static io.smallrye.config.ConfigMappingInterface.Property;
import static io.smallrye.config.ConfigMappingInterface.getConfigurationInterface;
import static io.smallrye.config.ConfigMappingInterface.rawTypeOf;
import static io.smallrye.config.ConfigMappingInterface.typeOfParameter;
import static io.smallrye.config.ConfigValidationException.Problem;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

import org.eclipse.microprofile.config.spi.Converter;

/**
* A mapping context. This is used by generated classes during configuration mapping, and is released once the configuration
* mapping has completed.
*/
public final class ConfigMappingContext {

private final Map<Class<?>, Map<String, Map<Object, Object>>> enclosedThings = new IdentityHashMap<>();
private final Map<Class<?>, Map<String, ConfigMappingObject>> roots = new IdentityHashMap<>();
private final Map<Class<?>, Map<String, Converter<?>>> convertersByTypeAndField = new IdentityHashMap<>();
private final List<Map<Class<?>, Map<String, Converter<?>>>> keyConvertersByDegreeTypeAndField = new ArrayList<>();
private final Map<Class<?>, Converter<?>> converterInstances = new IdentityHashMap<>();
private final List<ConfigMappingObject> allInstances = new ArrayList<>();
private final SmallRyeConfig config;
private final StringBuilder stringBuilder = new StringBuilder();
private final ArrayList<Problem> problems = new ArrayList<>();

ConfigMappingContext(final SmallRyeConfig config) {
this.config = config;
}

public ConfigMappingObject getRoot(Class<?> rootType, String rootPath) {
return roots.getOrDefault(rootType, Collections.emptyMap()).get(rootPath);
}

public void registerRoot(Class<?> rootType, String rootPath, ConfigMappingObject root) {
roots.computeIfAbsent(rootType, x -> new HashMap<>()).put(rootPath, root);
}

public Object getEnclosedField(Class<?> enclosingType, String key, Object enclosingObject) {
return enclosedThings
.getOrDefault(enclosingType, Collections.emptyMap())
.getOrDefault(key, Collections.emptyMap())
.get(enclosingObject);
}

public void registerEnclosedField(Class<?> enclosingType, String key, Object enclosingObject, Object value) {
enclosedThings
.computeIfAbsent(enclosingType, x -> new HashMap<>())
.computeIfAbsent(key, x -> new IdentityHashMap<>())
.put(enclosingObject, value);
}

public <T> T constructGroup(Class<T> interfaceType) {
Constructor<? extends ConfigMappingObject> constructor = getConfigurationInterface(interfaceType).getConstructor();
ConfigMappingObject instance;
try {
instance = constructor.newInstance(this);
} catch (InstantiationException e) {
throw new InstantiationError(e.getMessage());
} catch (IllegalAccessException e) {
throw new IllegalAccessError(e.getMessage());
} catch (InvocationTargetException e) {
try {
throw e.getCause();
} catch (RuntimeException | Error e2) {
throw e2;
} catch (Throwable t) {
throw new UndeclaredThrowableException(t);
}
}
allInstances.add(instance);
return interfaceType.cast(instance);
}

@SuppressWarnings({ "unchecked", "unused" })
public <T> Converter<T> getValueConverter(Class<?> enclosingType, String field) {
return (Converter<T>) convertersByTypeAndField
.computeIfAbsent(enclosingType, x -> new HashMap<>())
.computeIfAbsent(field, x -> {
ConfigMappingInterface ci = getConfigurationInterface(enclosingType);
Property property = ci.getProperty(field);
boolean optional = property.isOptional();
if (property.isLeaf() || optional && property.asOptional().getNestedProperty().isLeaf()) {
LeafProperty leafProperty = optional ? property.asOptional().getNestedProperty().asLeaf()
: property.asLeaf();
if (leafProperty.hasConvertWith()) {
Class<? extends Converter<?>> convertWith = leafProperty.getConvertWith();
// todo: generics
return getConverterInstance(convertWith);
} else {
// todo: replace with generic converter lookup
Class<?> valueRawType = leafProperty.getValueRawType();
if (valueRawType == List.class) {
return Converters.newCollectionConverter(
config.getConverter(rawTypeOf(typeOfParameter(leafProperty.getValueType(), 0))),
ArrayList::new);
} else if (valueRawType == Set.class) {
return Converters.newCollectionConverter(
config.getConverter(rawTypeOf(typeOfParameter(leafProperty.getValueType(), 0))),
HashSet::new);
} else {
return config.getConverter(valueRawType);
}
}
} else if (property.isPrimitive()) {
PrimitiveProperty primitiveProperty = property.asPrimitive();
if (primitiveProperty.hasConvertWith()) {
return getConverterInstance(primitiveProperty.getConvertWith());
} else {
return config.getConverter(primitiveProperty.getBoxType());
}
} else {
throw new IllegalStateException();
}
});
}

@SuppressWarnings("unchecked")
public <T> Converter<T> getKeyConverter(Class<?> enclosingType, String field, int degree) {
List<Map<Class<?>, Map<String, Converter<?>>>> list = this.keyConvertersByDegreeTypeAndField;
while (list.size() <= degree) {
list.add(new IdentityHashMap<>());
}
Map<Class<?>, Map<String, Converter<?>>> map = list.get(degree);
return (Converter<T>) map
.computeIfAbsent(enclosingType, x -> new HashMap<>())
.computeIfAbsent(field, x -> {
ConfigMappingInterface ci = getConfigurationInterface(enclosingType);
MapProperty property = ci.getProperty(field).asMap();
while (degree + 1 > property.getLevels()) {
property = property.getValueProperty().asMap();
}
if (property.hasKeyConvertWith()) {
return getConverterInstance(property.getKeyConvertWith());
} else {
// todo: replace with generic converter lookup
Class<?> valueRawType = property.getKeyRawType();
if (valueRawType == List.class) {
return Converters.newCollectionConverter(
config.getConverter(rawTypeOf(typeOfParameter(property.getKeyType(), 0))), ArrayList::new);
} else if (valueRawType == Set.class) {
return Converters.newCollectionConverter(
config.getConverter(rawTypeOf(typeOfParameter(property.getKeyType(), 0))), HashSet::new);
} else {
return config.getConverter(valueRawType);
}
}
});
}

@SuppressWarnings("unchecked")
public <T> Converter<T> getConverterInstance(Class<? extends Converter<? extends T>> converterType) {
return (Converter<T>) converterInstances.computeIfAbsent(converterType, t -> {
try {
return (Converter<T>) t.getConstructor().newInstance();
} catch (InstantiationException e) {
throw new InstantiationError(e.getMessage());
} catch (IllegalAccessException e) {
throw new IllegalAccessError(e.getMessage());
} catch (InvocationTargetException e) {
try {
throw e.getCause();
} catch (RuntimeException | Error e2) {
throw e2;
} catch (Throwable t2) {
throw new UndeclaredThrowableException(t2);
}
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
});
}

public NoSuchElementException noSuchElement(Class<?> type) {
return new NoSuchElementException("A required configuration group of type " + type.getName() + " was not provided");
}

public void unknownConfigElement(final String propertyName) {
problems.add(new Problem(propertyName + " does not map to any root"));
}

void fillInOptionals() {
for (ConfigMappingObject instance : allInstances) {
instance.fillInOptionals(this);
}
}

public SmallRyeConfig getConfig() {
return config;
}

public StringBuilder getStringBuilder() {
return stringBuilder;
}

public void reportProblem(RuntimeException problem) {
problems.add(new Problem(problem.toString()));
}

ArrayList<Problem> getProblems() {
return problems;
}

Map<Class<?>, Map<String, ConfigMappingObject>> getRootsMap() {
return roots;
}
}
Loading