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 extends Annotation> 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