Skip to content

Commit

Permalink
Register implicit converters for reflection
Browse files Browse the repository at this point in the history
  • Loading branch information
radcortez committed Apr 6, 2023
1 parent 4746a69 commit c012988
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
import java.util.Optional;
import java.util.Set;

import org.eclipse.microprofile.config.spi.Converter;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.Type;

import io.quarkus.deployment.annotations.BuildProducer;
Expand All @@ -26,19 +26,21 @@
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.util.ReflectUtil;
import io.smallrye.config.ConfigMapping;
import io.smallrye.config.ConfigMappingInterface;
import io.smallrye.config.ConfigMappingInterface.LeafProperty;
import io.smallrye.config.ConfigMappingInterface.MapProperty;
import io.smallrye.config.ConfigMappingInterface.Property;
import io.smallrye.config.ConfigMappingLoader;
import io.smallrye.config.ConfigMappingMetadata;

public class ConfigMappingUtils {

public static final DotName CONFIG_MAPPING_NAME = DotName.createSimple(ConfigMapping.class.getName());

private static final DotName OPTIONAL = DotName.createSimple(Optional.class.getName());

private ConfigMappingUtils() {
}

public static void generateConfigClasses(
public static void processConfigClasses(
CombinedIndexBuildItem combinedIndex,
BuildProducer<GeneratedClassBuildItem> generatedClasses,
BuildProducer<ReflectiveClassBuildItem> reflectiveClasses,
Expand Down Expand Up @@ -73,7 +75,7 @@ public static void processExtensionConfigMapping(
configClasses);
}

static void processConfigClass(
private static void processConfigClass(
Class<?> configClass,
Kind configClassKind,
String prefix,
Expand All @@ -85,45 +87,76 @@ static void processConfigClass(

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().build());
reflectiveClasses
.produce(ReflectiveClassBuildItem.builder(mappingMetadata.getClassName())
.methods().build());

generatedClassesNames.add(mappingMetadata.getClassName());
// This is the generated implementation of the mapping by SmallRye Config.
generatedClasses.produce(new GeneratedClassBuildItem(isApplicationClass, mappingMetadata.getClassName(),
mappingMetadata.getClassBytes()));
// Register the interface and implementation methods for reflection. This is required for Bean Validation.
reflectiveClasses.produce(ReflectiveClassBuildItem.builder(mappingMetadata.getInterfaceType()).methods().build());
reflectiveClasses.produce(ReflectiveClassBuildItem.builder(mappingMetadata.getClassName()).methods().build());
// Register also the interface hierarchy
for (Class<?> parent : getHierarchy(mappingMetadata.getInterfaceType())) {
reflectiveClasses.produce(ReflectiveClassBuildItem.builder(parent).methods().build());
}

generatedClassesNames.add(mappingMetadata.getClassName());
processProperties(mappingMetadata.getInterfaceType(), reflectiveClasses);
});

ClassInfo mappingInfo = combinedIndex.getIndex()
.getClassByName(DotName.createSimple(mappingMetadata.getInterfaceType().getName()));
if (mappingInfo != null) {
mappingsInfo.add(mappingInfo);
configClasses.produce(new ConfigClassBuildItem(configClass, collectTypes(combinedIndex, configClass),
generatedClassesNames, prefix, configClassKind));
}

private static void processProperties(
Class<?> configClass,
BuildProducer<ReflectiveClassBuildItem> reflectiveClasses) {

ConfigMappingInterface mapping = ConfigMappingLoader.getConfigMapping(configClass);
for (Property property : mapping.getProperties()) {
Class<?> returnType = property.getMethod().getReturnType();
reflectiveClasses.produce(ReflectiveClassBuildItem.builder(returnType).methods().build());

if (property.hasConvertWith()) {
Class<? extends Converter<?>> convertWith;
if (property.isLeaf()) {
convertWith = property.asLeaf().getConvertWith();
} else {
convertWith = property.asPrimitive().getConvertWith();
}
reflectiveClasses.produce(ReflectiveClassBuildItem.builder(convertWith).build());
}
});

// For implicit converters
for (ClassInfo classInfo : mappingsInfo) {
for (MethodInfo method : classInfo.methods()) {
Type type = method.returnType();
if (type.name().equals(OPTIONAL)) {
// E.g. Optional<Foo>
type = type.asParameterizedType().arguments().get(0);
registerImplicitConverter(property, reflectiveClasses);

if (property.isMap()) {
MapProperty mapProperty = property.asMap();
if (mapProperty.hasKeyConvertWith()) {
reflectiveClasses.produce(ReflectiveClassBuildItem.builder(mapProperty.getKeyConvertWith()).build());
} else {
reflectiveClasses.produce(ReflectiveClassBuildItem.builder(mapProperty.getKeyRawType()).build());
}
reflectiveClasses
.produce(ReflectiveClassBuildItem.builder(type.name().toString()).methods().build());

registerImplicitConverter(mapProperty.getValueProperty(), reflectiveClasses);
}
}
}

configClasses.produce(new ConfigClassBuildItem(configClass, collectTypes(combinedIndex, configClass),
generatedClassesNames, prefix, configClassKind));
private static void registerImplicitConverter(
Property property,
BuildProducer<ReflectiveClassBuildItem> reflectiveClasses) {

if (property.isLeaf() && !property.isOptional()) {
LeafProperty leafProperty = property.asLeaf();
if (leafProperty.hasConvertWith()) {
reflectiveClasses.produce(ReflectiveClassBuildItem.builder(leafProperty.getConvertWith()).build());
} else {
reflectiveClasses.produce(ReflectiveClassBuildItem.builder(leafProperty.getValueRawType()).methods().build());
}
} else if (property.isOptional()) {
registerImplicitConverter(property.asOptional().getNestedProperty(), reflectiveClasses);
} else if (property.isCollection()) {
registerImplicitConverter(property.asCollection().getElement(), reflectiveClasses);
}
}

public static Object newInstance(Class<?> configClass) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.deployment.steps;

import static io.quarkus.deployment.configuration.ConfigMappingUtils.CONFIG_MAPPING_NAME;
import static io.quarkus.deployment.configuration.ConfigMappingUtils.processConfigClasses;
import static io.quarkus.deployment.configuration.ConfigMappingUtils.processExtensionConfigMapping;
import static io.quarkus.deployment.steps.ConfigBuildSteps.SERVICES_PREFIX;
import static io.quarkus.deployment.util.ServiceUtil.classNamesNamedIn;
Expand Down Expand Up @@ -204,6 +206,16 @@ void runtimeDefaultsConfig(
runTimeConfigBuilder.produce(new RunTimeConfigBuilderBuildItem(builderClassName));
}

@BuildStep
void mappings(
CombinedIndexBuildItem combinedIndex,
BuildProducer<GeneratedClassBuildItem> generatedClasses,
BuildProducer<ReflectiveClassBuildItem> reflectiveClasses,
BuildProducer<ConfigClassBuildItem> configClasses) {

processConfigClasses(combinedIndex, generatedClasses, reflectiveClasses, configClasses, CONFIG_MAPPING_NAME);
}

@BuildStep
void extensionMappings(ConfigurationBuildItem configItem,
CombinedIndexBuildItem combinedIndex,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static io.quarkus.deployment.builditem.ConfigClassBuildItem.Kind.MAPPING;
import static io.quarkus.deployment.builditem.ConfigClassBuildItem.Kind.PROPERTIES;
import static io.quarkus.deployment.configuration.ConfigMappingUtils.CONFIG_MAPPING_NAME;
import static io.quarkus.deployment.configuration.ConfigMappingUtils.processConfigClasses;
import static io.smallrye.config.ConfigMappings.ConfigClassWithPrefix.configClassWithPrefix;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
Expand Down Expand Up @@ -67,13 +68,11 @@
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.configuration.ConfigMappingUtils;
import io.quarkus.deployment.configuration.definition.RootDefinition;
import io.quarkus.deployment.recording.RecorderContext;
import io.quarkus.gizmo.ResultHandle;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.smallrye.config.ConfigMappings.ConfigClassWithPrefix;
import io.smallrye.config.WithConverter;
import io.smallrye.config.inject.ConfigProducer;

/**
Expand All @@ -89,7 +88,6 @@ public class ConfigBuildStep {

private static final DotName SR_CONFIG = DotName.createSimple(io.smallrye.config.SmallRyeConfig.class.getName());
private static final DotName SR_CONFIG_VALUE_NAME = DotName.createSimple(io.smallrye.config.ConfigValue.class.getName());
private static final DotName SR_WITH_CONVERTER = DotName.createSimple(WithConverter.class.getName());

private static final DotName MAP_NAME = DotName.createSimple(Map.class.getName());
private static final DotName SET_NAME = DotName.createSimple(Set.class.getName());
Expand Down Expand Up @@ -277,17 +275,13 @@ public void transform(TransformationContext context) {
}

@BuildStep
void generateConfigClasses(
void generateConfigProperties(
CombinedIndexBuildItem combinedIndex,
BuildProducer<GeneratedClassBuildItem> generatedClasses,
BuildProducer<ReflectiveClassBuildItem> reflectiveClasses,
BuildProducer<ConfigClassBuildItem> configClasses) {

// TODO - Generation of Mapping interface classes can be done in core because they don't require CDI
ConfigMappingUtils.generateConfigClasses(combinedIndex, generatedClasses, reflectiveClasses, configClasses,
CONFIG_MAPPING_NAME);
ConfigMappingUtils.generateConfigClasses(combinedIndex, generatedClasses, reflectiveClasses, configClasses,
MP_CONFIG_PROPERTIES_NAME);
processConfigClasses(combinedIndex, generatedClasses, reflectiveClasses, configClasses, MP_CONFIG_PROPERTIES_NAME);
}

@BuildStep
Expand Down Expand Up @@ -378,19 +372,6 @@ void registerConfigPropertiesBean(
}
}

@BuildStep
void registerConfigMappingConverters(CombinedIndexBuildItem indexBuildItem,
BuildProducer<ReflectiveClassBuildItem> producer) {

String[] valueTypes = indexBuildItem.getIndex().getAnnotations(SR_WITH_CONVERTER).stream()
.map(i -> i.value().asClass().name().toString())
.toArray(String[]::new);
if (valueTypes.length > 0) {
producer.produce(
ReflectiveClassBuildItem.builder(valueTypes).build());
}
}

@BuildStep
void validateConfigMappingsInjectionPoints(
ArcConfig arcConfig,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package io.quarkus.it.smallrye.config;

import java.util.List;
import java.util.Map;
import java.util.Optional;

import io.smallrye.config.ConfigMapping;
import io.smallrye.config.WithDefault;

@ConfigMapping(prefix = "implicit.converters")
public interface ImplicitConverters {
@WithDefault("value")
Optional<ImplicitOptional> optional();

@WithDefault("value")
List<ImplicitElement> list();

Map<String, ImplicitValue> map();

class ImplicitOptional {
private final String value;

public ImplicitOptional(final String value) {
this.value = value;
}

public String getValue() {
return value;
}

public static ImplicitOptional of(String value) {
return new ImplicitOptional("converted");
}
}

class ImplicitElement {
private final String value;

public ImplicitElement(final String value) {
this.value = value;
}

public String getValue() {
return value;
}

public static ImplicitElement of(String value) {
return new ImplicitElement("converted");
}
}

class ImplicitValue {
private final String value;

public ImplicitValue(final String value) {
this.value = value;
}

public String getValue() {
return value;
}

public static ImplicitValue of(String value) {
return new ImplicitValue("converted");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.quarkus.it.smallrye.config;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Response;

@Path("/implicit/converters")
public class ImplicitConvertersResource {
@Inject
ImplicitConverters implicitConverters;

@GET
@Path("/optional")
public Response optional() {
return Response.ok(implicitConverters.optional().get().getValue()).build();
}

@GET
@Path("/list")
public Response list() {
return Response.ok(implicitConverters.list().get(0).getValue()).build();
}

@GET
@Path("/map")
public Response map() {
return Response.ok(implicitConverters.map().get("key").getValue()).build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@ smallrye.config.secret-handler.aes-gcm-nopadding.encryption-key=somearbitrarycra
smallrye.config.source.keystore.test.path=keystore
smallrye.config.source.keystore.test.password=secret
smallrye.config.source.keystore.test.handler=aes-gcm-nopadding

#implicit converters
implicit.converters.map."key"=value
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.quarkus.it.smallrye.config;

import io.quarkus.test.junit.QuarkusIntegrationTest;

@QuarkusIntegrationTest
public class ImplicitConvertersIT extends ImplicitConvertersTest {

}
Loading

0 comments on commit c012988

Please sign in to comment.