Skip to content

Commit

Permalink
Support ConfigMappings in Extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
radcortez committed Sep 30, 2021
1 parent f787bcf commit fca338b
Show file tree
Hide file tree
Showing 20 changed files with 810 additions and 280 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.runtime.configuration.ConfigUtils;
import io.quarkus.runtime.configuration.QuarkusConfigFactory;
import io.smallrye.config.ConfigMappings.ConfigClassWithPrefix;
import io.smallrye.config.KeyMap;
import io.smallrye.config.KeyMapBackedConfigSource;
import io.smallrye.config.NameIterator;
Expand Down Expand Up @@ -164,6 +165,10 @@ public static Consumer<BuildChainBuilder> loadStepsFrom(ClassLoader classLoader,
builder.withSources(ds1, ds2, platformConfigSource, pcs);
}

for (ConfigClassWithPrefix mapping : reader.getBuildTimeVisibleMappings()) {
builder.withMapping(mapping.getKlass(), mapping.getPrefix());
}

if (configCustomizer != null) {
configCustomizer.accept(builder);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,16 @@
* example config files and docs
*/
public final class ConfigDescriptionBuildItem extends MultiBuildItem implements Comparable<ConfigDescriptionBuildItem> {

private final String propertyName;
private final Class<?> type;
private final String defaultValue;
private final String docs;
private final String valueTypeName;
private final List<String> allowedValues;
private final ConfigPhase configPhase;

public ConfigDescriptionBuildItem(String propertyName, Class<?> type, String defaultValue, String docs,
String valueTypeName, List<String> allowedValues, ConfigPhase configPhase) {
public ConfigDescriptionBuildItem(String propertyName, String defaultValue, String docs, String valueTypeName,
List<String> allowedValues, ConfigPhase configPhase) {
this.propertyName = propertyName;
this.type = type;
this.defaultValue = defaultValue;
this.docs = docs;
this.valueTypeName = valueTypeName;
Expand All @@ -34,10 +31,6 @@ public String getPropertyName() {
return propertyName;
}

public Class<?> getType() {
return type;
}

public String getDefaultValue() {
return defaultValue;
}
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import org.jboss.jandex.MethodInfo;

import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.ConfigClassBuildItem;
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
Expand All @@ -38,23 +37,23 @@ public class ConfigMappingUtils {
private ConfigMappingUtils() {
}

@BuildStep
public static void generateConfigClasses(
CombinedIndexBuildItem combinedIndex,
BuildProducer<GeneratedClassBuildItem> generatedClasses,
BuildProducer<ReflectiveClassBuildItem> reflectiveClasses,
BuildProducer<ConfigClassBuildItem> configClasses,
DotName configAnnotation) {

boolean isMapping = configAnnotation.equals(CONFIG_MAPPING_NAME);
for (AnnotationInstance instance : combinedIndex.getIndex().getAnnotations(configAnnotation)) {
AnnotationTarget target = instance.target();
AnnotationValue annotationPrefix = instance.value("prefix");

if (target.kind().equals(FIELD)) {
if (annotationPrefix != null && !annotationPrefix.asString().equals(UNCONFIGURED_PREFIX)) {
configClasses.produce(
toConfigClassBuildItem(instance, toClass(target.asField().type().name()),
annotationPrefix.asString()));
toConfigClassBuildItem(toClass(target.asField().type().name()),
annotationPrefix.asString(), isMapping));
continue;
}
}
Expand All @@ -64,7 +63,7 @@ public static void generateConfigClasses(
ClassType classType = target.asMethodParameter().method().parameters()
.get(target.asMethodParameter().position()).asClassType();
configClasses
.produce(toConfigClassBuildItem(instance, toClass(classType.name()), annotationPrefix.asString()));
.produce(toConfigClassBuildItem(toClass(classType.name()), annotationPrefix.asString(), isMapping));
continue;
}
}
Expand All @@ -75,40 +74,54 @@ public static void generateConfigClasses(

Class<?> configClass = toClass(target.asClass().name());
String prefix = Optional.ofNullable(annotationPrefix).map(AnnotationValue::asString).orElse("");
processConfigClass(configClass, prefix, isMapping, combinedIndex, true, generatedClasses, reflectiveClasses,
configClasses);
}
}

List<ConfigMappingMetadata> configMappingsMetadata = ConfigMappingLoader.getConfigMappingsMetadata(configClass);
Set<String> generatedClassesNames = new HashSet<>();
Set<ClassInfo> mappingsInfo = new HashSet<>();
configMappingsMetadata.forEach(mappingMetadata -> {
generatedClasses.produce(
new GeneratedClassBuildItem(true, mappingMetadata.getClassName(), mappingMetadata.getClassBytes()));
reflectiveClasses
.produce(ReflectiveClassBuildItem.builder(mappingMetadata.getInterfaceType()).methods(true).build());
reflectiveClasses
.produce(ReflectiveClassBuildItem.builder(mappingMetadata.getClassName()).constructors(true).build());

for (Class<?> parent : getHierarchy(mappingMetadata.getInterfaceType())) {
reflectiveClasses.produce(ReflectiveClassBuildItem.builder(parent).methods(true).build());
}

generatedClassesNames.add(mappingMetadata.getClassName());
public static void processConfigClass(
Class<?> configClass,
String prefix,
boolean isMapping,
CombinedIndexBuildItem combinedIndex,
boolean isApplicationClass,
BuildProducer<GeneratedClassBuildItem> generatedClasses,
BuildProducer<ReflectiveClassBuildItem> reflectiveClasses,
BuildProducer<ConfigClassBuildItem> configClasses) {

List<ConfigMappingMetadata> configMappingsMetadata = ConfigMappingLoader.getConfigMappingsMetadata(configClass);
Set<String> generatedClassesNames = new HashSet<>();
Set<ClassInfo> mappingsInfo = new HashSet<>();
configMappingsMetadata.forEach(mappingMetadata -> {
generatedClasses.produce(
new GeneratedClassBuildItem(isApplicationClass, mappingMetadata.getClassName(),
mappingMetadata.getClassBytes()));
reflectiveClasses
.produce(ReflectiveClassBuildItem.builder(mappingMetadata.getInterfaceType()).methods(true).build());
reflectiveClasses
.produce(ReflectiveClassBuildItem.builder(mappingMetadata.getClassName()).constructors(true).build());

for (Class<?> parent : getHierarchy(mappingMetadata.getInterfaceType())) {
reflectiveClasses.produce(ReflectiveClassBuildItem.builder(parent).methods(true).build());
}

ClassInfo mappingInfo = combinedIndex.getIndex()
.getClassByName(DotName.createSimple(mappingMetadata.getInterfaceType().getName()));
if (mappingInfo != null) {
mappingsInfo.add(mappingInfo);
}
});
generatedClassesNames.add(mappingMetadata.getClassName());

// For implicit converters
for (ClassInfo classInfo : mappingsInfo) {
for (MethodInfo method : classInfo.methods()) {
reflectiveClasses.produce(new ReflectiveClassBuildItem(true, false, method.returnType().name().toString()));
}
ClassInfo mappingInfo = combinedIndex.getIndex()
.getClassByName(DotName.createSimple(mappingMetadata.getInterfaceType().getName()));
if (mappingInfo != null) {
mappingsInfo.add(mappingInfo);
}
});

configClasses.produce(toConfigClassBuildItem(instance, configClass, generatedClassesNames, prefix));
// For implicit converters
for (ClassInfo classInfo : mappingsInfo) {
for (MethodInfo method : classInfo.methods()) {
reflectiveClasses.produce(new ReflectiveClassBuildItem(true, false, method.returnType().name().toString()));
}
}

configClasses.produce(toConfigClassBuildItem(configClass, prefix, isMapping, generatedClassesNames));
}

private static Class<?> toClass(DotName dotName) {
Expand All @@ -121,22 +134,18 @@ private static Class<?> toClass(DotName dotName) {
}

private static ConfigClassBuildItem toConfigClassBuildItem(
AnnotationInstance instance,
Class<?> configClass,
String prefix) {
return toConfigClassBuildItem(instance, configClass, emptySet(), prefix);
String prefix,
boolean isMapping) {
return toConfigClassBuildItem(configClass, prefix, isMapping, emptySet());
}

private static ConfigClassBuildItem toConfigClassBuildItem(
AnnotationInstance instance,
Class<?> configClass,
Set<String> generatedClasses,
String prefix) {
if (instance.name().equals(CONFIG_MAPPING_NAME)) {
return new ConfigClassBuildItem(configClass, generatedClasses, prefix, MAPPING);
} else {
return new ConfigClassBuildItem(configClass, generatedClasses, prefix, PROPERTIES);
}
String prefix,
boolean isMapping,
Set<String> generatedClasses) {
return new ConfigClassBuildItem(configClass, generatedClasses, prefix, isMapping ? MAPPING : PROPERTIES);
}

private static List<Class<?>> getHierarchy(Class<?> mapping) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,10 @@ public final class RunTimeConfigurationGenerator {
static final FieldDescriptor C_SPECIFIED_RUN_TIME_CONFIG_SOURCE = FieldDescriptor.of(CONFIG_CLASS_NAME,
"specifiedRunTimeConfigSource",
ConfigSource.class);
static final FieldDescriptor C_UNUSED = FieldDescriptor.of(CONFIG_CLASS_NAME, "unused", List.class);
static final FieldDescriptor C_UNUSED_RUNTIME = FieldDescriptor.of(CONFIG_CLASS_NAME, "unusedRuntime", List.class);
static final FieldDescriptor C_UNKNOWN = FieldDescriptor.of(CONFIG_CLASS_NAME, "unknown", List.class);
static final FieldDescriptor C_UNKNOWN_RUNTIME = FieldDescriptor.of(CONFIG_CLASS_NAME, "unknownRuntime", List.class);
static final MethodDescriptor C_REPORT_UNKNOWN = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, "reportUnknown", void.class,
String.class, List.class);

static final MethodDescriptor CD_INVALID_VALUE = MethodDescriptor.ofMethod(ConfigDiagnostic.class, "invalidValue",
void.class, String.class, IllegalArgumentException.class);
Expand Down Expand Up @@ -187,6 +189,9 @@ public final class RunTimeConfigurationGenerator {
static final MethodDescriptor RCSP_NEW = MethodDescriptor.ofConstructor(RuntimeConfigSourceProvider.class, String.class);
static final MethodDescriptor RCSF_NEW = MethodDescriptor.ofConstructor(RuntimeConfigSourceFactory.class, String.class);

static final MethodDescriptor CS_GET_VALUE = MethodDescriptor.ofMethod(ConfigSource.class, "getValue", String.class,
String.class);

static final MethodDescriptor AL_NEW = MethodDescriptor.ofConstructor(ArrayList.class);
static final MethodDescriptor AL_ADD = MethodDescriptor.ofMethod(ArrayList.class, "add", boolean.class, Object.class);

Expand Down Expand Up @@ -291,6 +296,7 @@ public static final class GenerateOperation implements AutoCloseable {
final ResultHandle clinitNameBuilder;
final BuildTimeConfigurationReader.ReadResult buildTimeConfigResult;
final List<RootDefinition> roots;
final Map<String, String> allBuildTimeValues;
// default values given in the build configuration
final Map<String, String> specifiedRunTimeDefaultValues;
final Map<String, String> buildTimeRunTimeVisibleValues;
Expand Down Expand Up @@ -331,6 +337,7 @@ public static final class GenerateOperation implements AutoCloseable {
this.liveReloadPossible = builder.liveReloadPossible;
final BuildTimeConfigurationReader.ReadResult buildTimeReadResult = builder.buildTimeReadResult;
buildTimeConfigResult = Assert.checkNotNullParam("buildTimeReadResult", buildTimeReadResult);
allBuildTimeValues = Assert.checkNotNullParam("allBuildTimeValues", buildTimeReadResult.getAllBuildTimeValues());
specifiedRunTimeDefaultValues = Assert.checkNotNullParam("specifiedRunTimeDefaultValues",
buildTimeReadResult.getSpecifiedRunTimeDefaultValues());
buildTimeRunTimeVisibleValues = Assert.checkNotNullParam("buildTimeRunTimeVisibleValues",
Expand Down Expand Up @@ -366,11 +373,11 @@ public static final class GenerateOperation implements AutoCloseable {
clinit = cc.getMethodCreator(MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, "<clinit>", void.class));
clinit.setModifiers(Opcodes.ACC_STATIC);

cc.getFieldCreator(C_UNUSED).setModifiers(Opcodes.ACC_STATIC | Opcodes.ACC_FINAL);
clinit.writeStaticField(C_UNUSED, clinit.newInstance(AL_NEW));
cc.getFieldCreator(C_UNKNOWN).setModifiers(Opcodes.ACC_STATIC | Opcodes.ACC_FINAL);
clinit.writeStaticField(C_UNKNOWN, clinit.newInstance(AL_NEW));

cc.getFieldCreator(C_UNUSED_RUNTIME).setModifiers(Opcodes.ACC_STATIC | Opcodes.ACC_FINAL);
clinit.writeStaticField(C_UNUSED_RUNTIME, clinit.newInstance(AL_NEW));
cc.getFieldCreator(C_UNKNOWN_RUNTIME).setModifiers(Opcodes.ACC_STATIC | Opcodes.ACC_FINAL);
clinit.writeStaticField(C_UNKNOWN_RUNTIME, clinit.newInstance(AL_NEW));

clinit.invokeStaticMethod(PM_SET_RUNTIME_DEFAULT_PROFILE, clinit.load(ProfileManager.getActiveProfile()));
clinitNameBuilder = clinit.newInstance(SB_NEW);
Expand Down Expand Up @@ -446,6 +453,8 @@ public static final class GenerateOperation implements AutoCloseable {
// block for converter setup
converterSetup = clinit.createScope();

reportUnknown(cc.getMethodCreator(C_REPORT_UNKNOWN));

// create readBootstrapConfig method - this will always exist whether or not it contains a method body
// the method body will be empty when there are no bootstrap configuration roots
readBootstrapConfig = cc.getMethodCreator(C_BOOTSTRAP_CONFIG);
Expand Down Expand Up @@ -791,17 +800,15 @@ public void run() {
// generate sweep for clinit
configSweepLoop(siParserBody, clinit, clinitConfig, getRegisteredRoots(BUILD_AND_RUN_TIME_FIXED));

clinit.invokeStaticMethod(CD_UNKNOWN_PROPERTIES,
clinit.readStaticField(FieldDescriptor.of(cc.getClassName(), "unused", List.class)));
clinit.invokeStaticMethod(CD_UNKNOWN_PROPERTIES, clinit.readStaticField(C_UNKNOWN));

if (liveReloadPossible) {
configSweepLoop(siParserBody, readConfig, runTimeConfig, getRegisteredRoots(RUN_TIME));
}
// generate sweep for run time
configSweepLoop(rtParserBody, readConfig, runTimeConfig, getRegisteredRoots(RUN_TIME));

readConfig.invokeStaticMethod(CD_UNKNOWN_PROPERTIES_RT,
readConfig.readStaticField(FieldDescriptor.of(cc.getClassName(), "unusedRuntime", List.class)));
readConfig.invokeStaticMethod(CD_UNKNOWN_PROPERTIES_RT, readConfig.readStaticField(C_UNKNOWN_RUNTIME));

if (bootstrapConfigSetupNeeded()) {
// generate sweep for bootstrap config
Expand Down Expand Up @@ -1649,16 +1656,56 @@ private FieldDescriptor getOrCreateConverterInstance(Field field, ConverterType
return fd;
}

private void reportUnknown(BytecodeCreator methodCreator, ResultHandle unusedProperty) {
ResultHandle unused = methodCreator.readStaticField(C_UNUSED);
methodCreator.invokeVirtualMethod(AL_ADD, unused,
methodCreator.invokeVirtualMethod(NI_GET_NAME, unusedProperty));
private void reportUnknown(final MethodCreator mc) {
mc.setModifiers(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC);

ResultHandle unknownProperty = mc.getMethodParam(0);

// Ignore all build property names. This is to ignore any properties mapped with @ConfigMapping, because
// these do not fall into the ignore ConfigPattern.
for (String buildTimeProperty : allBuildTimeValues.keySet()) {
ResultHandle equalsResult = mc.invokeVirtualMethod(
MethodDescriptor.ofMethod(Object.class, "equals", boolean.class, Object.class), unknownProperty,
mc.load(buildTimeProperty));
mc.ifTrue(equalsResult).trueBranch().returnValue(null);
}

ResultHandle unknown = mc.getMethodParam(1);

// Ignore recorded properties. If properties are present here, it means that they were recorded at build
// time as being mapped in a @ConfigMapping
ResultHandle buildTimeRunTimeSource = mc.readStaticField(C_BUILD_TIME_CONFIG_SOURCE);
ResultHandle buildTimeRunTimeValue = mc.invokeInterfaceMethod(CS_GET_VALUE, buildTimeRunTimeSource,
unknownProperty);
mc.ifNotNull(buildTimeRunTimeValue).trueBranch().returnValue(null);

ResultHandle buildTimeRunTimeDefaultsSource = mc.readStaticField(C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE);
ResultHandle buildTimeRunTimeDefaultValue = mc.invokeInterfaceMethod(CS_GET_VALUE, buildTimeRunTimeDefaultsSource,
unknownProperty);
mc.ifNotNull(buildTimeRunTimeDefaultValue).trueBranch().returnValue(null);

ResultHandle runtimeSpecifiedSource = mc.readStaticField(C_SPECIFIED_RUN_TIME_CONFIG_SOURCE);
ResultHandle runtimeSpecifiedValue = mc.invokeInterfaceMethod(CS_GET_VALUE, runtimeSpecifiedSource,
unknownProperty);
mc.ifNotNull(runtimeSpecifiedValue).trueBranch().returnValue(null);

// Add the property as unknown only if all checks fail
mc.invokeVirtualMethod(AL_ADD, unknown, unknownProperty);

mc.returnValue(null);
mc.close();
}

private void reportUnknown(BytecodeCreator bc, ResultHandle unknownProperty) {
ResultHandle property = bc.invokeVirtualMethod(NI_GET_NAME, unknownProperty);
ResultHandle unknown = bc.readStaticField(C_UNKNOWN);
bc.invokeStaticMethod(C_REPORT_UNKNOWN, property, unknown);
}

private void reportUnknownRuntime(BytecodeCreator methodCreator, ResultHandle unusedProperty) {
ResultHandle unused = methodCreator.readStaticField(C_UNUSED_RUNTIME);
methodCreator.invokeVirtualMethod(AL_ADD, unused,
methodCreator.invokeVirtualMethod(NI_GET_NAME, unusedProperty));
private void reportUnknownRuntime(BytecodeCreator bc, ResultHandle unknownProperty) {
ResultHandle property = bc.invokeVirtualMethod(NI_GET_NAME, unknownProperty);
ResultHandle unknown = bc.readStaticField(C_UNKNOWN_RUNTIME);
bc.invokeStaticMethod(C_REPORT_UNKNOWN, property, unknown);
}

public void close() {
Expand Down
Loading

0 comments on commit fca338b

Please sign in to comment.