Skip to content

Commit

Permalink
Support OpenWebBeans as a CDI implementation (#803)
Browse files Browse the repository at this point in the history
  • Loading branch information
radcortez committed Aug 11, 2022
1 parent 677b392 commit 56888d8
Show file tree
Hide file tree
Showing 7 changed files with 265 additions and 111 deletions.
101 changes: 60 additions & 41 deletions cdi/src/main/java/io/smallrye/config/inject/ConfigExtension.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
import static io.smallrye.config.ConfigMappings.registerConfigMappings;
import static io.smallrye.config.ConfigMappings.registerConfigProperties;
import static io.smallrye.config.ConfigMappings.ConfigClassWithPrefix.configClassWithPrefix;
import static io.smallrye.config.inject.ConfigMappingInjectionBean.getPrefixFromInjectionPoint;
import static io.smallrye.config.inject.ConfigMappingInjectionBean.getPrefixFromType;
import static io.smallrye.config.inject.ConfigProducer.isClassHandledByConfigProducer;
import static io.smallrye.config.inject.InjectionMessages.formatInjectionPoint;
import static io.smallrye.config.inject.SecuritySupport.getContextClassLoader;
Expand Down Expand Up @@ -50,6 +48,8 @@
import javax.enterprise.inject.spi.ProcessAnnotatedType;
import javax.enterprise.inject.spi.ProcessInjectionPoint;
import javax.enterprise.inject.spi.WithAnnotations;
import javax.enterprise.inject.spi.configurator.AnnotatedTypeConfigurator;
import javax.enterprise.util.Nonbinding;
import javax.inject.Provider;

import org.eclipse.microprofile.config.ConfigProvider;
Expand All @@ -68,37 +68,64 @@
*/
public class ConfigExtension implements Extension {
private final Set<InjectionPoint> configPropertyInjectionPoints = new HashSet<>();

private final Set<AnnotatedType<?>> configProperties = new HashSet<>();
private final Set<InjectionPoint> configPropertiesInjectionPoints = new HashSet<>();
private final Set<AnnotatedType<?>> configMappings = new HashSet<>();
private final Set<InjectionPoint> configMappingInjectionPoints = new HashSet<>();
/** ConfigProperties for SmallRye Config */
private final Set<ConfigClassWithPrefix> configProperties = new HashSet<>();
/** ConfigProperties for CDI */
private final Set<ConfigClassWithPrefix> configPropertiesBeans = new HashSet<>();
/** ConfigMappings for SmallRye Config */
private final Set<ConfigClassWithPrefix> configMappings = new HashSet<>();
/** ConfigMappings for CDI */
private final Set<ConfigClassWithPrefix> configMappingBeans = new HashSet<>();

public ConfigExtension() {
}

protected void beforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd, BeanManager bm) {
AnnotatedType<ConfigProducer> configBean = bm.createAnnotatedType(ConfigProducer.class);
bbd.addAnnotatedType(configBean, ConfigProducer.class.getName());

// Remove NonBinding annotation. OWB is not able to look up CDI beans programmatically with NonBinding in the
// case the look-up changed the non-binding parameters (in this case the prefix)
AnnotatedTypeConfigurator<ConfigProperties> configPropertiesConfigurator = bbd
.configureQualifier(ConfigProperties.class);
configPropertiesConfigurator.methods().forEach(methodConfigurator -> methodConfigurator
.remove(annotation -> annotation.annotationType().equals(Nonbinding.class)));
}

protected void processConfigProperties(
@Observes @WithAnnotations(ConfigProperties.class) ProcessAnnotatedType<?> processAnnotatedType) {
// Even if we filter in the CDI event, beans containing injection points of ConfigMapping are also fired.
// Even if we filter in the CDI event, beans containing injection points of ConfigProperties are also fired.
if (processAnnotatedType.getAnnotatedType().isAnnotationPresent(ConfigProperties.class)) {
// We are going to veto, because it may be a managed bean and we will use a configurator bean
// We are going to veto, because it may be a managed bean, and we will use a configurator bean
processAnnotatedType.veto();
configProperties.add(processAnnotatedType.getAnnotatedType());

// Each config class is both in SmallRyeConfig and managed by a configurator bean.
// CDI requires more beans for injection points due to binding prefix.
ConfigClassWithPrefix properties = configClassWithPrefix(processAnnotatedType.getAnnotatedType().getJavaClass(),
processAnnotatedType.getAnnotatedType().getAnnotation(ConfigProperties.class).prefix());
// Unconfigured is represented as an empty String in SmallRye Config
if (!properties.getPrefix().equals(ConfigProperties.UNCONFIGURED_PREFIX)) {
configProperties.add(properties);
} else {
configProperties.add(ConfigClassWithPrefix.configClassWithPrefix(properties.getKlass(), ""));
}
configPropertiesBeans.add(properties);
}
}

protected void processConfigMappings(
@Observes @WithAnnotations(ConfigMapping.class) ProcessAnnotatedType<?> processAnnotatedType) {
// Even if we filter in the CDI event, beans containing injection points of ConfigMapping are also fired.
if (processAnnotatedType.getAnnotatedType().isAnnotationPresent(ConfigMapping.class)) {
// We are going to veto, because it may be a managed bean and we will use a configurator bean
// We are going to veto, because it may be a managed bean, and we will use a configurator bean
processAnnotatedType.veto();
configMappings.add(processAnnotatedType.getAnnotatedType());

// Each config class is both in SmallRyeConfig and managed by a configurator bean.
// CDI requires a single configurator bean per class due to non-binding prefix.
ConfigClassWithPrefix mapping = configClassWithPrefix(processAnnotatedType.getAnnotatedType().getJavaClass(),
processAnnotatedType.getAnnotatedType().getAnnotation(ConfigMapping.class).prefix());
configMappings.add(mapping);
configMappingBeans.add(mapping);
}
}

Expand All @@ -108,11 +135,25 @@ protected void processConfigInjectionPoints(@Observes ProcessInjectionPoint<?, ?
}

if (pip.getInjectionPoint().getAnnotated().isAnnotationPresent(ConfigProperties.class)) {
configPropertiesInjectionPoints.add(pip.getInjectionPoint());
ConfigClassWithPrefix properties = configClassWithPrefix((Class<?>) pip.getInjectionPoint().getType(),
pip.getInjectionPoint().getAnnotated().getAnnotation(ConfigProperties.class).prefix());

// If the prefix is empty at the injection point, fallbacks to the class prefix (already registered)
if (!properties.getPrefix().equals(ConfigProperties.UNCONFIGURED_PREFIX)) {
configProperties.add(properties);
}
// Cover all combinations of the configurator bean for ConfigProperties because prefix is binding
configPropertiesBeans.add(properties);
}

// Add to SmallRyeConfig config classes with a different prefix by injection point
if (pip.getInjectionPoint().getAnnotated().isAnnotationPresent(ConfigMapping.class)) {
configMappingInjectionPoints.add(pip.getInjectionPoint());
ConfigClassWithPrefix mapping = configClassWithPrefix((Class<?>) pip.getInjectionPoint().getType(),
pip.getInjectionPoint().getAnnotated().getAnnotation(ConfigMapping.class).prefix());
// If the prefix is empty at the injection point, fallbacks to the class prefix (already registered)
if (!mapping.getPrefix().isEmpty()) {
configMappings.add(mapping);
}
}
}

Expand All @@ -136,10 +177,9 @@ protected void registerCustomBeans(@Observes AfterBeanDiscovery abd, BeanManager
}
}

customTypes.stream().map(customType -> new ConfigInjectionBean<>(bm, customType)).forEach(abd::addBean);
configProperties.stream().map(annotatedType -> new ConfigMappingInjectionBean<>(bm, annotatedType))
.forEach(abd::addBean);
configMappings.stream().map(annotatedType -> new ConfigMappingInjectionBean<>(bm, annotatedType)).forEach(abd::addBean);
customTypes.forEach(customType -> abd.addBean(new ConfigInjectionBean<>(bm, customType)));
configPropertiesBeans.forEach(properties -> abd.addBean(new ConfigPropertiesInjectionBean<>(properties)));
configMappingBeans.forEach(mapping -> abd.addBean(new ConfigMappingInjectionBean<>(mapping, bm)));
}

protected void validate(@Observes AfterDeploymentValidation adv) {
Expand Down Expand Up @@ -193,14 +233,9 @@ protected void validate(@Observes AfterDeploymentValidation adv) {
}
}

Set<ConfigClassWithPrefix> configMappingsWithPrefix = mapToConfigObjectWithPrefix(configMappings,
configMappingInjectionPoints);
Set<ConfigClassWithPrefix> configPropertiesWithPrefix = mapToConfigObjectWithPrefix(configProperties,
configPropertiesInjectionPoints);

try {
registerConfigMappings(config, configMappingsWithPrefix);
registerConfigProperties(config, configPropertiesWithPrefix);
registerConfigMappings(config, configMappings);
registerConfigProperties(config, configProperties);
} catch (ConfigValidationException e) {
adv.addDeploymentProblem(e);
}
Expand All @@ -210,22 +245,6 @@ protected Set<InjectionPoint> getConfigPropertyInjectionPoints() {
return configPropertyInjectionPoints;
}

private static Set<ConfigClassWithPrefix> mapToConfigObjectWithPrefix(
Set<AnnotatedType<?>> annotatedTypes,
Set<InjectionPoint> injectionPoints) {

Set<ConfigClassWithPrefix> configMappingsWithPrefix = new HashSet<>();
for (AnnotatedType<?> annotatedType : annotatedTypes) {
configMappingsWithPrefix
.add(configClassWithPrefix(annotatedType.getJavaClass(), getPrefixFromType(annotatedType)));
}
for (InjectionPoint injectionPoint : injectionPoints) {
getPrefixFromInjectionPoint(injectionPoint).ifPresent(prefix -> configMappingsWithPrefix
.add(configClassWithPrefix((Class<?>) injectionPoint.getType(), prefix)));
}
return configMappingsWithPrefix;
}

private static boolean isIndexed(Type type, String name, SmallRyeConfig config) {
return type instanceof ParameterizedType &&
(List.class.isAssignableFrom((Class<?>) ((ParameterizedType) type).getRawType()) ||
Expand Down
Original file line number Diff line number Diff line change
@@ -1,48 +1,37 @@
package io.smallrye.config.inject;

import static io.smallrye.config.inject.SecuritySupport.getContextClassLoader;
import static java.util.Optional.ofNullable;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

import javax.enterprise.context.Dependent;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.Default;
import javax.enterprise.inject.spi.Annotated;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.InjectionPoint;

import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.config.inject.ConfigProperties;

import io.smallrye.config.ConfigMapping;
import io.smallrye.config.ConfigMappings.ConfigClassWithPrefix;
import io.smallrye.config.SmallRyeConfig;

public class ConfigMappingInjectionBean<T> implements Bean<T> {
private final BeanManager bm;
private final Class<T> klass;
private final String prefix;
private final Set<Annotation> qualifiers = new HashSet<>();
private final ConfigClassWithPrefix configClassWithPrefix;

public ConfigMappingInjectionBean(final BeanManager bm, final AnnotatedType<T> type) {
public ConfigMappingInjectionBean(final ConfigClassWithPrefix configClassWithPrefix, final BeanManager bm) {
this.bm = bm;
this.klass = type.getJavaClass();
this.prefix = getPrefixFromType(type);
this.qualifiers.add(type.isAnnotationPresent(ConfigProperties.class) ? ConfigProperties.Literal.of(prefix)
: Default.Literal.INSTANCE);
this.configClassWithPrefix = configClassWithPrefix;
}

@Override
public Class<?> getBeanClass() {
return klass;
public Class<T> getBeanClass() {
return (Class<T>) configClassWithPrefix.getKlass();
}

@Override
Expand All @@ -60,8 +49,16 @@ public T create(final CreationalContext<T> creationalContext) {
InjectionPoint injectionPoint = (InjectionPoint) bm.getInjectableReference(new MetadataInjectionPoint(),
creationalContext);

String prefix = configClassWithPrefix.getPrefix();
if (injectionPoint != null && injectionPoint.getAnnotated() != null) {
ConfigMapping configMapping = injectionPoint.getAnnotated().getAnnotation(ConfigMapping.class);
if (configMapping != null) {
prefix = configMapping.prefix();
}
}

SmallRyeConfig config = (SmallRyeConfig) ConfigProvider.getConfig(getContextClassLoader());
return config.getConfigMapping(klass, getPrefixFromInjectionPoint(injectionPoint).orElse(prefix));
return config.getConfigMapping(getBeanClass(), prefix);
}

@Override
Expand All @@ -71,12 +68,12 @@ public void destroy(final T instance, final CreationalContext<T> creationalConte

@Override
public Set<Type> getTypes() {
return Collections.singleton(klass);
return Collections.singleton(configClassWithPrefix.getKlass());
}

@Override
public Set<Annotation> getQualifiers() {
return qualifiers;
return Collections.singleton(Default.Literal.INSTANCE);
}

@Override
Expand All @@ -86,7 +83,7 @@ public Class<? extends Annotation> getScope() {

@Override
public String getName() {
return this.getClass() + "_" + klass.getName();
return this.getClass().getSimpleName() + "_" + configClassWithPrefix.getKlass().getName();
}

@Override
Expand All @@ -98,44 +95,4 @@ public Set<Class<? extends Annotation>> getStereotypes() {
public boolean isAlternative() {
return false;
}

public static String getPrefixFromType(final Annotated annotated) {
final Optional<String> prefixFromConfigMappingClass = ofNullable(annotated.getBaseType()).map(type -> (Class<?>) type)
.map(c -> c.getAnnotation(ConfigMapping.class))
.map(ConfigMapping::prefix);

final Optional<String> prefixFromConfigPropertiesClass = ofNullable(annotated.getBaseType())
.map(type -> (Class<?>) type)
.map(c -> c.getAnnotation(ConfigProperties.class))
.map(ConfigProperties::prefix)
.filter(prefix -> !prefix.equals(ConfigProperties.UNCONFIGURED_PREFIX));

return Stream.of(prefixFromConfigMappingClass, prefixFromConfigPropertiesClass)
.flatMap(s -> s.map(Stream::of).orElseGet(Stream::empty))
.findFirst()
.orElse("");
}

public static Optional<String> getPrefixFromInjectionPoint(final InjectionPoint injectionPoint) {
final Optional<String> prefixFromConfigMapping = Optional.ofNullable(injectionPoint.getAnnotated())
.map(a -> a.getAnnotation(ConfigMapping.class))
.map(ConfigMapping::prefix)
.filter(prefix -> !prefix.isEmpty());

final Optional<String> prefixFromConfigProperties = Optional.ofNullable(injectionPoint.getAnnotated())
.map(a -> a.getAnnotation(ConfigProperties.class))
.map(ConfigProperties::prefix)
.filter(prefix -> !prefix.equals(ConfigProperties.UNCONFIGURED_PREFIX));

final Optional<String> prefixFromQualifier = injectionPoint.getQualifiers().stream()
.filter(ConfigProperties.class::isInstance)
.map(ConfigProperties.class::cast)
.map(ConfigProperties::prefix)
.filter(prefix -> !prefix.equals(ConfigProperties.UNCONFIGURED_PREFIX))
.findFirst();

return Stream.of(prefixFromConfigMapping, prefixFromConfigProperties, prefixFromQualifier)
.flatMap(s -> s.map(Stream::of).orElseGet(Stream::empty))
.findFirst();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
import java.util.function.IntFunction;
import java.util.function.Supplier;

import javax.enterprise.inject.Instance;
import javax.enterprise.inject.spi.AnnotatedMember;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.InjectionPoint;
import javax.inject.Provider;

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigValue;
Expand Down Expand Up @@ -55,7 +57,19 @@ private ConfigProducerUtil() {
* @return the converted configuration value.
*/
public static <T> T getValue(InjectionPoint injectionPoint, Config config) {
return getValue(getName(injectionPoint), injectionPoint.getType(), getDefaultValue(injectionPoint), config);
return getValue(getName(injectionPoint), getType(injectionPoint), getDefaultValue(injectionPoint), config);
}

private static Type getType(InjectionPoint injectionPoint) {
Type type = injectionPoint.getType();
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
if (parameterizedType.getRawType().equals(Provider.class)
|| parameterizedType.getRawType().equals(Instance.class)) {
return parameterizedType.getActualTypeArguments()[0];
}
}
return type;
}

/**
Expand Down
Loading

0 comments on commit 56888d8

Please sign in to comment.