blob: a941dfd6513bd60e848981730dde8bb3102bae32 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.geronimo.config.cdi;
import static java.util.stream.Collectors.toList;
import org.apache.geronimo.config.ConfigImpl;
import org.apache.geronimo.config.DefaultConfigBuilder;
import org.apache.geronimo.config.cdi.configsource.Reloadable;
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.AfterDeploymentValidation;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.BeforeShutdown;
import javax.enterprise.inject.spi.DeploymentException;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.InjectionPoint;
import javax.enterprise.inject.spi.ProcessAnnotatedType;
import javax.enterprise.inject.spi.ProcessInjectionPoint;
import javax.inject.Provider;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* @author <a href="mailto:struberg@yahoo.de">Mark Struberg</a>
*/
public class ConfigExtension implements Extension {
private ConfigImpl config;
private static final Predicate<InjectionPoint> NOT_PROVIDERS = ip -> (ip.getType() instanceof Class) || (ip.getType() instanceof ParameterizedType && ((ParameterizedType)ip.getType()).getRawType() != Provider.class);
private static final Map<Type, Type> REPLACED_TYPES = new HashMap<>();
static {
REPLACED_TYPES.put(double.class, Double.class);
REPLACED_TYPES.put(int.class, Integer.class);
REPLACED_TYPES.put(float.class, Float.class);
REPLACED_TYPES.put(long.class, Long.class);
REPLACED_TYPES.put(boolean.class, Boolean.class);
REPLACED_TYPES.put(byte.class, Byte.class);
REPLACED_TYPES.put(short.class, Short.class);
REPLACED_TYPES.put(char.class, Character.class);
}
private Set<InjectionPoint> injectionPoints = new HashSet<>();
private Set<Class<?>> proxies = new HashSet<>();
private List<Class<?>> validProxies;
private List<ProxyBean<?>> proxyBeans;
private boolean hasConfigProxy;
private ConfigBean configBean;
public ConfigExtension() {
final Config raw = ConfigProvider.getConfig();
// ensure to store the ref the whole lifecycle, java gc is aggressive now
this.config = ConfigImpl.class.cast(ConfigImpl.class.isInstance(raw) ?
raw : // custom overrided config, unlikely but possible in wrong setups
new DefaultConfigBuilder()
.forClassLoader(Thread.currentThread().getContextClassLoader())
.addDefaultSources()
.addDiscoveredSources()
.addDiscoveredConverters()
.build());
}
public void findProxies(@Observes ProcessAnnotatedType<?> pat) {
final Class<?> javaClass = pat.getAnnotatedType().getJavaClass();
if (javaClass.isInterface() &&
Stream.of(javaClass.getMethods()).anyMatch(m -> m.isAnnotationPresent(ConfigProperty.class))) {
proxies.add(javaClass);
}
}
public void collectConfigProducer(@Observes ProcessInjectionPoint<?, ?> pip) {
ConfigProperty configProperty = pip.getInjectionPoint().getAnnotated().getAnnotation(ConfigProperty.class);
if (configProperty != null) {
injectionPoints.add(pip.getInjectionPoint());
}
}
public void registerConfigProducer(@Observes AfterBeanDiscovery abd, BeanManager bm) {
Set<Type> types = injectionPoints.stream()
.filter(NOT_PROVIDERS)
.map(ip -> REPLACED_TYPES.getOrDefault(ip.getType(), ip.getType()))
.collect(Collectors.toSet());
Set<Type> providerTypes = injectionPoints.stream()
.filter(NOT_PROVIDERS.negate())
.map(ip -> ((ParameterizedType)ip.getType()).getActualTypeArguments()[0])
.collect(Collectors.toSet());
types.addAll(providerTypes);
types.stream()
.peek(type -> {
if (type == Config.class) {
hasConfigProxy = true;
}
})
.map(type -> new ConfigInjectionBean(bm, type))
.forEach(abd::addBean);
validProxies = proxies.stream()
.filter(this::isValidProxy)
.collect(toList());
if (validProxies.size() == proxies.size()) {
proxyBeans = validProxies.stream()
.map((Function<Class<?>, ? extends ProxyBean<?>>) ProxyBean::new)
.collect(toList());
proxyBeans.forEach(abd::addBean);
} // else there are errors
if (!hasConfigProxy) {
configBean = new ConfigBean();
abd.addBean(configBean);
}
}
public void validate(@Observes AfterDeploymentValidation add) {
List<String> deploymentProblems = new ArrayList<>();
StreamSupport.stream(config.getConfigSources().spliterator(), false)
.filter(Reloadable.class::isInstance)
.map(Reloadable.class::cast)
.forEach(Reloadable::reload);
if (!hasConfigProxy) {
configBean.init(config);
}
proxyBeans.forEach(b -> b.init(config));
proxyBeans.clear();
for (InjectionPoint injectionPoint : injectionPoints) {
Type type = injectionPoint.getType();
// replace native types with their Wrapper types
type = REPLACED_TYPES.getOrDefault(type, type);
ConfigProperty configProperty = injectionPoint.getAnnotated().getAnnotation(ConfigProperty.class);
if (type instanceof Class) {
// a direct injection of a ConfigProperty
// that means a Converter must exist.
String key = ConfigInjectionBean.getConfigKey(injectionPoint, configProperty);
if ((isDefaultUnset(configProperty.defaultValue()))
&& !config.getOptionalValue(key, (Class) type).isPresent()) {
deploymentProblems.add("No Config Value exists for " + key);
}
}
}
if (!deploymentProblems.isEmpty()) {
add.addDeploymentProblem(new DeploymentException("Error while validating Configuration\n"
+ String.join("\n", deploymentProblems)));
}
if (validProxies.size() != proxies.size()) {
proxies.stream()
.filter(p -> !validProxies.contains(p))
.forEach(p -> add.addDeploymentProblem(
new DeploymentException("Invalid proxy: " + p + ". All method should have @ConfigProperty.")));
}
proxies.clear();
}
public void shutdown(@Observes BeforeShutdown bsd) {
ConfigProviderResolver.instance().releaseConfig(config);
}
private boolean isValidProxy(final Class<?> api) {
return Stream.of(api.getMethods())
.allMatch(m -> m.isAnnotationPresent(ConfigProperty.class) || Object.class == m.getDeclaringClass());
}
static boolean isDefaultUnset(String defaultValue) {
return ConfigProperty.UNCONFIGURED_VALUE.equals(defaultValue);
}
}