All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.edgarespina.mwa.Startup Maven / Gradle / Ivy

package com.github.edgarespina.mwa;

import static org.springframework.core.annotation.AnnotationUtils.findAnnotation;
import static org.springframework.core.annotation.AnnotationUtils.getValue;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import javax.inject.Named;
import javax.servlet.DispatcherType;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePropertySource;
import org.springframework.util.StringValueResolver;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import com.google.common.collect.Sets;

/**
 * 

* A Servlet 3.0 Spring bootstrapper that offers the following functionality: *

*
    *
  • XML free configuration. *
  • Application context 'root' for Spring. *
  • Configure the {@link DispatcherServlet} with the root application * context. *
  • Configure {@link Environment} using an application properties files. *
  • Configure the {@link PropertySourcesPlaceholderConfigurer} for * {@link Value} usage. *
  • Organize your application in modules: (a.k.a Spring Configuration). *
  • Module's package are scanned for detecting Spring beans (a.k.a component * scanning). *
  • Publish an {@link Application} object with: the application's name, * contextPath, version and mode. *
* * @author edgar.espina * @since 0.1 * @see WebApplicationInitializer */ public abstract class Startup implements WebApplicationInitializer { /** * Configure @Named for resolving properties from the environment. * * @author edgar.espina * @since 0.1 */ private static class ExtendedAutowireCandidateResolver 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 ExtendedAutowireCandidateResolver}. * * @param environment The application environment. * @param beanFactory The application bean factory. */ @SuppressWarnings("unchecked") public ExtendedAutowireCandidateResolver(final Environment environment, final DefaultListableBeanFactory beanFactory) { valueAnnotationTypes = Sets.newHashSet(Value.class, 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 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); } } /** * Extend {@link AnnotationConfigWebApplicationContext} with more features. * * @author edgar.espina * @see ExtendedAutowireCandidateResolver */ private static class ModernWebAppContext extends AnnotationConfigWebApplicationContext { /** * {@inheritDoc} */ @Override protected void customizeBeanFactory( final DefaultListableBeanFactory beanFactory) { super.customizeBeanFactory(beanFactory); // Override the autowire candidate resolver new ExtendedAutowireCandidateResolver(getEnvironment(), beanFactory); } } /** * The logging system. */ private final Logger logger = LoggerFactory.getLogger(getClass()); /** *

* A Servlet 3.0 Spring bootstrapper that offers the following functionality: *

*
    *
  • XML free configuration. *
  • Application context 'root' for Spring. *
  • Configure the {@link DispatcherServlet} with the root application * context. *
  • Configure {@link Environment} using an application properties files. *
  • Configure the {@link PropertySourcesPlaceholderConfigurer} for * {@link Value} usage. *
  • Organize your application in modules: (a.k.a Spring Configuration). *
  • Module's package are scanned for detecting Spring beans (a.k.a * component scanning). *
  • Publish an {@link Application} object with: the application's name, * contextPath, version and mode. *
* * @param servletContext The servlet context. * @throws ServletException If something goes wrong. */ @Override public final void onStartup(final ServletContext servletContext) throws ServletException { final AnnotationConfigWebApplicationContext rootContext = new ModernWebAppContext(); servletContext.addListener(new ContextLoaderListener(rootContext)); // Configure the environment final ConfigurableEnvironment env = configureEnvironment(servletContext, rootContext); String modeProperty = env.getProperty("application.mode"); if (StringUtils.isBlank(modeProperty)) { modeProperty = Mode.DEV.name(); logger.warn("application.mode isn't set, using: {}", modeProperty); } Mode mode = Mode.valueOf(modeProperty); // Activate the default profile env.setActiveProfiles(mode.name()); /** * Configure modules. */ registerModules(rootContext); /** * Special beans. */ rootContext.addBeanFactoryPostProcessor(registerSingletons(mode, rootPackages())); /** * Creates the Spring MVC dispatcher servlet. */ ServletRegistration.Dynamic dispatcher = servletContext.addServlet( "spring-dispatcher", new DispatcherServlet(rootContext)); dispatcher.setLoadOnStartup(1); dispatcher.addMapping(dispatcherMapping()); // Add the forwarding filter servletContext.addFilter("forwardingFilter", new ForwardingFilter( rootContext)) .addMappingForUrlPatterns( EnumSet.of(DispatcherType.REQUEST), false, dispatcherMapping()); onStartup(servletContext, rootContext); } /** * Publish application properties files into the environment. Additionally, it * enabled the use of {@link Value} annotation. * * @param servletContext The servlet context. * @param rootContext The Spring application context. * @return The application environment. * @throws ServletException If the properties files fail to load. */ private ConfigurableEnvironment configureEnvironment( final ServletContext servletContext, final ConfigurableWebApplicationContext rootContext) throws ServletException { try { Set properties = findResources(propertySources()); if (properties.size() == 0) { logger.warn("No property files were found."); } // Add to the environment final ConfigurableEnvironment env = rootContext.getEnvironment(); Map webproperties = new HashMap(); webproperties.put("contextPath", servletContext.getContextPath()); webproperties.put("servletContextName", servletContext.getServletContextName()); MutablePropertySources propertySources = env.getPropertySources(); propertySources.addFirst(new MapPropertySource(servletContext .getContextPath(), webproperties)); for (Resource propertyFile : properties) { logger.debug("Adding property file: {}", propertyFile); propertySources.addFirst(asPropertySource(propertyFile)); } // Enable @Value PropertySourcesPlaceholderConfigurer placeholderConfigurer = new PropertySourcesPlaceholderConfigurer(); placeholderConfigurer.setEnvironment(env); rootContext.addBeanFactoryPostProcessor(placeholderConfigurer); return env; } catch (IOException ex) { throw new ServletException("The environment cannot be configured.", ex); } } /** * Add application's filters, listener and servlets. * * @param servletContext The servlet's context. * @param rootContext The Spring MVC application context. */ protected void onStartup(final ServletContext servletContext, final ConfigurableWebApplicationContext rootContext) { } /** * The mapping for the Spring {@link DispatcherServlet dispatcher} servlet. * Default is: '/*'. * * @return The mapping for the Spring {@link DispatcherServlet dispatcher} * servlet. Default is: '/*'. */ protected String[] dispatcherMapping() { return new String[] {"/*"}; } /** * Add modules to the application context. * * @param context The String application context. * @throws ServletException If something goes wrong. */ private void registerModules( final AnnotationConfigWebApplicationContext context) throws ServletException { try { Set> classes = new LinkedHashSet>(); Class[] modules = imports(); if (modules != null && modules.length > 0) { for (Class module : modules) { if (isConfiguration(module)) { classes.add(module); } else { throw new ServletException( "Class must be marked with @Configuration: " + module.getName()); } } } classes.add(WebDefaults.class); context.register(classes.toArray(new Class[classes.size()])); // Scan all the packages of the main class recursively. context.scan(rootPackageNames()); } catch (Exception ex) { throw new ServletException("Cannot register modules.", ex); } } /** * Return true if the candidate class is marked as {@link Configuration}. * * @param candidate The candidate class. * @return True if the candidate class is marked as {@link Configuration}. */ private boolean isConfiguration(final Class candidate) { return findAnnotation(candidate, Configuration.class) != null; } /** * A list with all the packages that will be added to the classpath scanning. * By default it scan all the package of the main or bootstrapper class. * * @return A list with all the packages that will be added to the classpath * scanning. By default it scan all the package of the main or * bootstrapper class. */ protected Package[] rootPackages() { return new Package[] {getClass().getPackage()}; } /** * A list with all the packages that will be added to the classpath scanning. * By default it scan all the package of the main or bootstrapper class. * * @return A list with all the packages that will be added to the classpath * scanning. By default it scan all the package of the main or * bootstrapper class. */ private String[] rootPackageNames() { Package[] roots = rootPackages(); Set names = Sets.newHashSet(); for (Package root : roots) { names.add(root.getName()); } return names.toArray(new String[names.size()]); } /** * Get a {@link PropertySource} from the resource. * * @param resource The resource. * @return A {@link PropertySource}. * @throws ServletException If the disk fails. */ private ResourcePropertySource asPropertySource(final Resource resource) throws ServletException { try { return new ResourcePropertySource(resource); } catch (IOException ex) { throw new ServletException(ex); } } /** * Import external modules required by the application. This is the same mark * the class with {@link Configuration} and add {@link Import} expressions. * * @return All the imported modules. */ protected Class[] imports() { return new Class[] {}; } /** *

* Provide the location of the application properties file, such as * {@code "classpath:/com/myco/foo.properties"} or * {@code "file:/path/to/file.properties"}. *

*

* Default is: application.properties. *

* * @return Provide the location of the application properties file, such as * {@code "classpath:/com/myco/foo.properties"} or * {@code "file:/path/to/file.properties"}. * @see ClassPathResource * @see FileSystemResource * @see UrlResource * @see Resource */ protected String propertySource() { return "application.properties"; } /** *

* Provide the location of the application properties file, such as * {@code "classpath:/com/myco/foo.properties"} or * {@code "file:/path/to/file.properties"}. *

*

* Default is: application.properties. *

* * @return Provide the location of the application properties file, such as * {@code "classpath:/com/myco/foo.properties"} or * {@code "file:/path/to/file.properties"}. * @see ClassPathResource * @see FileSystemResource * @see UrlResource * @see Resource */ protected String[] propertySources() { return new String[] {propertySource()}; } /** * Find all the resources for the given location. * * @param locations The locations. * @return A resource set. * @throws IOException If a resource file fail to be loaded. */ private Set findResources(final String[] locations) throws IOException { ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Set resources = Sets.newLinkedHashSet(); for (String location : locations) { Resource[] candidates = resolver.getResources(location); for (Resource resource : candidates) { if (resource.exists()) { resources.add(resource); } } } return resources; } /** * Register the application mode in the spring context. * * @param mode The application's mode. * @param roots The roots packages. * @return A new {@link BeanFactoryPostProcessor}. */ private static BeanFactoryPostProcessor registerSingletons( final Mode mode, final Package[] roots) { return new BeanFactoryPostProcessor() { @Override public void postProcessBeanFactory( final ConfigurableListableBeanFactory beanFactory) { beanFactory.addBeanPostProcessor(modeAwareBeanPostProcessor(mode)); beanFactory.registerSingleton("#mode", mode); // register roots for (int i = 0; i < roots.length; i++) { beanFactory.registerSingleton("#root$" + i, roots[i]); } } }; } /** * 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; } }; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy