/* * MIT License * * Copyright 2017-2018 Sabre GLBL Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.sabre.oss.conf4j.spring; import com.sabre.oss.conf4j.factory.ConfigurationFactory; import com.sabre.oss.conf4j.internal.model.ConfigurationModelProvider; import com.sabre.oss.conf4j.spring.ConfigurationBeanDefinitionHelper.ConfigurationIndicator; import com.sabre.oss.conf4j.spring.annotation.ConfigurationScan; import com.sabre.oss.conf4j.spring.annotation.ConfigurationType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.beans.factory.support.BeanDefinitionValidationException; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; import org.springframework.core.type.StandardAnnotationMetadata; import org.springframework.stereotype.Component; import org.springframework.util.ClassUtils; import java.lang.annotation.Annotation; import java.util.Set; import static com.sabre.oss.conf4j.spring.Conf4jSpringConstants.CONF4J_CONFIGURATION_FACTORY; import static com.sabre.oss.conf4j.spring.Conf4jSpringConstants.CONF4J_CONFIGURATION_SOURCE; import static com.sabre.oss.conf4j.spring.ConfigurationBeanDefinitionHelper.getConf4jConfigurationIndicator; import static java.lang.String.format; import static java.lang.System.nanoTime; import static java.util.Collections.singleton; import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static org.springframework.core.annotation.AnnotationUtils.findAnnotation; /** * Replaces conf4j configuration abstract classes discovered by {@link ConfigurationScan}, {@link ConfigurationType} * {@code <conf4j:configuration-scan .../>} or {@code <conf4j:configuration .../>} or directly registered in context and annotated * with {@link Component} (or any other annotation meta-annotated by this annotation) * with the actual configuration instance generated by the configuration factory. * <p> * Annotation which is used for detecting configurations can be customized by {@link #setConfigurationAnnotations(Set)} * and by default is {@link Component}. */ public class ConfigurationBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered, BeanClassLoaderAware { private static final Logger log = LoggerFactory.getLogger(ConfigurationBeanFactoryPostProcessor.class); private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); private Set<Class<? extends Annotation>> configurationAnnotations = singleton(Component.class); private int order = Ordered.HIGHEST_PRECEDENCE; private ConfigurationModelProvider configurationModelProvider; @Override public void setBeanClassLoader(ClassLoader classLoader) { this.beanClassLoader = classLoader; } /** * Set of configuration annotations which are used for detecting whether a class is a certain configuration type. * * @param configurationAnnotations set of annotations. */ public void setConfigurationAnnotations(Set<Class<? extends Annotation>> configurationAnnotations) { this.configurationAnnotations = requireNonNull(configurationAnnotations, "configurationAnnotations cannot be null"); } @Override public int getOrder() { return order; } public void setOrder(int order) { this.order = order; } /** * Sets {@link ConfigurationModelProvider} which is used for determining a class is a conf4j configuration. * It must be the same as the model provider configured in {@link ConfigurationFactory} used * for creating configuration instances. * * @param configurationModelProvider configuration model provider. */ public void setConfigurationModelProvider(ConfigurationModelProvider configurationModelProvider) { this.configurationModelProvider = configurationModelProvider; } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { long start = nanoTime(); int foundConfigurations = 0; for (String name : registry.getBeanDefinitionNames()) { BeanDefinition definition = registry.getBeanDefinition(name); /* Skips abstract beans */ if (definition.isAbstract()) { continue; } String beanClassName = definition.getBeanClassName(); /* Skip beans created by factories */ if (beanClassName == null) { continue; } Class<?> configurationType; try { configurationType = ClassUtils.forName(beanClassName, this.beanClassLoader); } catch (ClassNotFoundException ignore) { continue; } if (isConfigurationType(configurationType)) { ConfigurationIndicator indicator = getConf4jConfigurationIndicator(definition); if (indicator != ConfigurationIndicator.ABSENT) { log.trace("conf4j configuration bean {} of type {} found", name, beanClassName); replaceBeanWithInstrumentedClass(definition, configurationType); foundConfigurations++; } } else { ConfigurationIndicator indicator = getConf4jConfigurationIndicator(definition); if (indicator == ConfigurationIndicator.DISCOVERED) { // Configuration type has been discovered so it is a possible class, which is not a valid configuration // was registered unintentionally and it is safe to remove it. for (String alias : registry.getAliases(name)) { registry.removeAlias(alias); } registry.removeBeanDefinition(name); log.warn("conf4j configuration bean {} of type {} is not recognized as configuration type, the bean definition has been removed.", name, beanClassName); } else if (indicator == ConfigurationIndicator.MANUAL) { throw new BeanDefinitionValidationException( format("conf4j configuration bean %s of type %s is not recognized as configuration type, but it was registered explicitly.", name, beanClassName)); } } } log.debug("conf4j configurations post-processing completed, {} configurations found, total time {} ms", foundConfigurations, NANOSECONDS.toMillis(nanoTime() - start)); } private boolean isConfigurationType(Class<?> configurationType) { if (configurationModelProvider != null) { return configurationModelProvider.isConfigurationType(configurationType); } else { // fallback to the simplified logic StandardAnnotationMetadata metadata = new StandardAnnotationMetadata(configurationType); return metadata.isAbstract() && metadata.isIndependent() && configurationAnnotations.stream().anyMatch(a -> findAnnotation(configurationType, a) != null); } } private void replaceBeanWithInstrumentedClass(BeanDefinition definition, Class<?> configurationType) { definition.setBeanClassName(ConfigurationFactoryBean.class.getName()); MutablePropertyValues propertyValues = definition.getPropertyValues(); propertyValues.addPropertyValue("configurationFactory", new RuntimeBeanReference(CONF4J_CONFIGURATION_FACTORY)); propertyValues.addPropertyValue("configurationSource", new RuntimeBeanReference(CONF4J_CONFIGURATION_SOURCE)); propertyValues.addPropertyValue("configurationType", configurationType); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { } }