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 java.io.IOException;
import java.lang.annotation.Annotation;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Handler;
import java.util.logging.LogManager;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;
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.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.annotation.AnnotationUtils;
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.core.type.filter.AnnotationTypeFilter;
import org.springframework.stereotype.Component;
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.github.edgarespina.mwa.Application.Mode;
import com.google.common.base.Strings;
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) { this.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 = AnnotationUtils.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) AnnotationUtils.getValue(annotation); if (valueType == Named.class) { return environment.getProperty(value) != null; } else { // 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 servelt context. * @throws ServletException If something goes wrong. */ @Override public final void onStartup(final ServletContext servletContext) throws ServletException { // redirect java util logging calls. configureJuli(); final AnnotationConfigWebApplicationContext rootContext = new ModernWebAppContext(); servletContext.addListener(new ContextLoaderListener(rootContext)); // Configure the environment final ConfigurableEnvironment env = configureEnvironment(servletContext, rootContext); /** * Creates the application object. */ String contextPath = servletContext.getContextPath(); String name = env.getProperty("application.name"); String mode = env.getProperty("application.mode"); String version = env.getProperty("application.version"); final Application application = new Application(contextPath, Strings.isNullOrEmpty(name) ? contextPath : name, defaultAppVersion(version), Strings.isNullOrEmpty(mode) ? Application.DEV : Mode.valueOf(mode)); // Activate the default profile env.setActiveProfiles(application.mode().name()); logger.debug("Starting application: {}", application); /** * Scan beans under each module's package. */ Class[] modules = modules(); if (modules.length > 0) { registerModules(rootContext, modules); } /** * Register the application object. */ rootContext.addBeanFactoryPostProcessor(new BeanFactoryPostProcessor() { @Override public void postProcessBeanFactory( final ConfigurableListableBeanFactory beanFactory) { beanFactory.registerSingleton("mwa.application", application); } }); /** * 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); } /** * Turn off Juli and redirect Juli to SJF4J. */ private void configureJuli() { java.util.logging.Logger rootLogger = LogManager.getLogManager().getLogger(""); Handler[] handlers = rootLogger.getHandlers(); for (Handler handler : handlers) { rootLogger.removeHandler(handler); } SLF4JBridgeHandler.install(); } /** * 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 failst to load. */ private ConfigurableEnvironment configureEnvironment( final ServletContext servletContext, final ConfigurableWebApplicationContext rootContext) throws ServletException { try { ResourcePatternResolver resourceLoader = new PathMatchingResourcePatternResolver(); Resource[] propertiesFiles = resourceLoader.getResources(properties()); // 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 : propertiesFiles) { 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); } } /** * Build the application version number using the provided version and the * current date. * * @param version The version number. Optional. * @return A unique application version. */ private String defaultAppVersion(final String version) { String startupTime = new SimpleDateFormat(".yyyyMMdd.hhmmss").format(new Date()); return Strings.isNullOrEmpty(version) ? startupTime : version + "." + startupTime; } /** * 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 "/*"; } /** * Add modules to the application context. * * @param context The String application context. * @param modules The list of modules. * @throws ServletException If something goes wrong. */ private void registerModules( final AnnotationConfigWebApplicationContext context, final Class[] modules) throws ServletException { try { ClassPathScanner scanner = new ClassPathScanner(); Set> classes = new LinkedHashSet>(); for (Class module : modules) { scanner.addPackage(module.getPackage()); classes.add(module); } scanner.addFilters(new AnnotationTypeFilter(Component.class)); classes.addAll(scanner.scan()); classes.add(WebDefaults.class); classes.add(ExtendedMvcSupport.class); context.register(classes.toArray(new Class[classes.size()])); } catch (Exception ex) { throw new ServletException("Cannot register modules.", ex); } } /** * 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); } } /** * List the application's modules. * * @return All the application's modules. */ protected abstract Class[] modules(); /** *

* 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 properties() { return "application.properties"; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy