com.github.jknack.mwa.ApplicationContextConfigurer Maven / Gradle / Ivy
package com.github.jknack.mwa;
import static com.github.jknack.mwa.ApplicationConstants.APP_MODE;
import static org.apache.commons.lang3.Validate.notNull;
import static org.springframework.core.GenericTypeResolver.resolveTypeArgument;
import static org.springframework.core.annotation.AnnotationUtils.getValue;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.inject.Named;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.OrderComparator;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.util.StringValueResolver;
/**
* Configure a Spring Application Context with:
*
* - Extra property sources
* - Add a {@link Mode} bean and {@link ModeAware} support. Default mode is: 'dev'.
* - Enable a Spring profile that matches the configured mode.
* - Enable Named/Value annotation for injection environment properties
*
*
* @author edgar.espina
* @since 0.3.3
*/
public final class ApplicationContextConfigurer {
/**
* Configure @Named for resolving properties from the environment.
*
* @author edgar.espina
* @since 0.1
*/
private static class EnvironmentPropertyResolver extends
QualifierAnnotationAutowireCandidateResolver implements
StringValueResolver {
/**
* The list of annotation type to resolve.
*/
private final Set> valueAnnotationTypes;
/**
* The application environment.
*/
private Environment environment;
/**
* Creates a new {@link EnvironmentPropertyResolver}.
*
* @param environment The application environment.
* @param beanFactory The application bean factory.
*/
public EnvironmentPropertyResolver(final Environment environment,
final DefaultListableBeanFactory beanFactory) {
valueAnnotationTypes = new HashSet>();
valueAnnotationTypes.add(Value.class);
valueAnnotationTypes.add(Named.class);
this.environment = environment;
beanFactory.setAutowireCandidateResolver(this);
beanFactory.addEmbeddedValueResolver(this);
}
/**
* {@inheritDoc}
*/
@Override
protected Object findValue(final Annotation[] annotationsToSearch) {
for (Annotation annotation : annotationsToSearch) {
if (isInstance(annotation)) {
Object value = getValue(annotation);
if (value == null) {
throw new IllegalStateException(
"Value/Named annotation must have a value attribute");
}
return value;
}
}
return null;
}
/**
* Returns true if the given annotation is one of Value or Named.
*
* @param annotation The annotation instance.
* @return True if the given annotation is one of Value or Named.
*/
private boolean isInstance(final Annotation annotation) {
for (Class extends Annotation> valueType : valueAnnotationTypes) {
if (valueType.isInstance(annotation)) {
String value = (String) getValue(annotation);
if (valueType == Named.class) {
return environment.getProperty(value) != null;
}
// force to use ${} in @Value
return value.startsWith("${") && value.endsWith("}");
}
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public String resolveStringValue(final String value) {
return environment.getProperty(value, value);
}
}
/**
* Looks for all the {@link ComponentConfigurer} and call them all.
*
* @author edgar.espina
*
*/
private static class ConfigureComponents implements ApplicationListener {
@Override
@SuppressWarnings({"rawtypes", "unchecked" })
public void onApplicationEvent(final ContextRefreshedEvent event) {
ApplicationContext context = event.getApplicationContext();
Collection configurers = context.getBeansOfType(
ComponentConfigurer.class).values();
Map> orderedMap =
new HashMap>();
// Dump all the configurer in the orderedMap
for (ComponentConfigurer configurer : configurers) {
Class componentType = resolveTypeArgument(configurer.getClass(), ComponentConfigurer.class);
notNull(componentType, "Missing component's type for: %s", configurer);
List configurerList = orderedMap.get(componentType);
if (configurerList == null) {
configurerList = new ArrayList();
orderedMap.put(componentType, configurerList);
}
configurerList.add(configurer);
}
// Call each configurer by precedence
for (Entry> entry : orderedMap.entrySet()) {
Class componentType = entry.getKey();
List configurerList = entry.getValue();
OrderComparator.sort(configurerList);
for (ComponentConfigurer configurer : configurerList) {
notNull(componentType, "Missing component type for: " + configurer);
Iterable beans = context.getBeansOfType(componentType).values();
for (Object bean : beans) {
try {
configurer.configure(bean);
} catch (Exception ex) {
throw new BeanInitializationException("Cannot configurer bean: " + componentType, ex);
}
}
}
}
}
}
/**
* The logging system.
*/
private static final Logger logger = LoggerFactory.getLogger(ApplicationContextConfigurer.class);
/**
* Not allowed.
*/
private ApplicationContextConfigurer() {
}
/**
* Configure an application's context with:
*
* - Extra property sources
* - Add a {@link Mode} bean and {@link ModeAware} support. Default mode is: 'dev'.
* - Enable a Spring profile that matches the configured mode.
* - Enable Named/Value annotation for injection environment properties
*
*
* @param context The application's context. Required.
* @param propertySources The property sources. Required.
* @return The given application's context.
*/
public static ConfigurableApplicationContext configure(
final ConfigurableApplicationContext context, final MutablePropertySources propertySources) {
ConfigurableEnvironment env = configureEnvironment(context, propertySources);
String modeProperty = env.getProperty(APP_MODE);
if (StringUtils.isBlank(modeProperty)) {
modeProperty = Mode.DEV.name();
logger.warn("{} isn't set, using: {}", APP_MODE, modeProperty);
} else {
logger.info("{} is: {}", APP_MODE, modeProperty);
}
Mode mode = Mode.valueOf(modeProperty);
// Activate the default profile
env.setActiveProfiles(mode.name());
complement(context, mode);
return context;
}
/**
*
* - Add {@link ModeAware} support.
* - Publish 'mode' in the given environment.
* - Configure Named annotation for injecting environment's properties.
*
*
* @param context The application's context.
* @param mode The application's mode.
*/
private static void complement(final ConfigurableApplicationContext context, final Mode mode) {
context.addApplicationListener(new ConfigureComponents());
context.addBeanFactoryPostProcessor(new BeanFactoryPostProcessor() {
@Override
public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) {
beanFactory.addBeanPostProcessor(modeAwareBeanPostProcessor(mode));
beanFactory.registerSingleton(APP_MODE, mode);
// Enable @Named and @Value
new EnvironmentPropertyResolver(context.getEnvironment(),
(DefaultListableBeanFactory) beanFactory);
}
});
}
/**
* Configure {@link ModeAware} beans.
*
* @param mode The application's mode.
* @return A bean mode aware processor.
*/
private static BeanPostProcessor modeAwareBeanPostProcessor(final Mode mode) {
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(final Object bean,
final String beanName) {
if (bean instanceof ModeAware) {
((ModeAware) bean).setMode(mode);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(final Object bean,
final String beanName) {
return bean;
}
};
}
/**
* Publish application properties files into the environment. Property sources will be added by
* precedence. For example: the element at '0' will have the highest precedence.
*
* @param context The Spring application context. Required.
* @param propertySources The property's source. Required.
* @return The application environment.
*/
public static ConfigurableEnvironment configureEnvironment(
final ConfigurableApplicationContext context, final MutablePropertySources propertySources) {
notNull(context, "The context is required.");
notNull(propertySources, "The propertySources are required.");
final ConfigurableEnvironment env = context.getEnvironment();
if (propertySources.size() == 0) {
logger.warn("No property files were found.");
}
// Add property's by precedence.
MutablePropertySources mutablePropertySources = env.getPropertySources();
for (PropertySource> propertySource : propertySources) {
logger.debug("Adding property file: {}", propertySource);
mutablePropertySources.addLast(propertySource);
}
// Move some less-used property source to the end of the chain.
String[] moveToEnd = {"servletConfigInitParams", "servletContextInitParams", "jndiProperties" };
for (String propertySourceName : moveToEnd) {
PropertySource> propertySource = mutablePropertySources.remove(propertySourceName);
if (propertySource != null) {
mutablePropertySources.addLast(propertySource);
}
}
// Enable @Value
PropertySourcesPlaceholderConfigurer placeholderConfigurer =
new PropertySourcesPlaceholderConfigurer();
placeholderConfigurer.setEnvironment(env);
context.addBeanFactoryPostProcessor(placeholderConfigurer);
return env;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy